Skip to content

Commit

Permalink
Merge branch 'main' into daniel/duckplayer.overlay.pixels.base
Browse files Browse the repository at this point in the history
# Conflicts:
#	Core/PixelEvent.swift
  • Loading branch information
afterxleep committed Nov 5, 2024
2 parents d7600a7 + b50b7fa commit 1a5a622
Show file tree
Hide file tree
Showing 62 changed files with 990 additions and 93 deletions.
55 changes: 55 additions & 0 deletions Core/BoolFileMarker.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// BoolFileMarker.swift
// DuckDuckGo
//
// Copyright © 2024 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

public struct BoolFileMarker {
let fileManager = FileManager.default
private let url: URL

public var isPresent: Bool {
fileManager.fileExists(atPath: url.path)
}

public func mark() {
if !isPresent {
fileManager.createFile(atPath: url.path, contents: nil, attributes: [.protectionKey: FileProtectionType.none])
}
}

public func unmark() {
if isPresent {
try? fileManager.removeItem(at: url)
}
}

public init?(name: Name) {
guard let applicationSupportDirectory = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
return nil
}

self.url = applicationSupportDirectory.appendingPathComponent(name.rawValue)
}

public struct Name: RawRepresentable {
public let rawValue: String

public init(rawValue: String) {
self.rawValue = "\(rawValue).marker"
}
}
}
58 changes: 58 additions & 0 deletions Core/BoolFileMarkerTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// BoolFileMarkerTests.swift
// DuckDuckGo
//
// Copyright © 2024 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import XCTest
@testable import Core

final class BoolFileMarkerTests: XCTestCase {

private let marker = BoolFileMarker(name: .init(rawValue: "test"))!

override func tearDown() {
super.tearDown()

marker.unmark()
}

private var testFileURL: URL? {
FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first?.appendingPathComponent("test.marker")
}

func testMarkCreatesCorrectFile() throws {

marker.mark()

let fileURL = try XCTUnwrap(testFileURL)

let attributes = try FileManager.default.attributesOfItem(atPath: fileURL.path)
XCTAssertNil(attributes[.protectionKey])
XCTAssertTrue(FileManager.default.fileExists(atPath: fileURL.path))
XCTAssertEqual(marker.isPresent, true)
}

func testUnmarkRemovesFile() throws {
marker.mark()
marker.unmark()

let fileURL = try XCTUnwrap(testFileURL)

XCTAssertFalse(marker.isPresent)
XCTAssertFalse(FileManager.default.fileExists(atPath: fileURL.path))
}
}
10 changes: 10 additions & 0 deletions Core/PixelEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,11 @@ extension Pixel {

// MARK: WebView Error Page Shown
case webViewErrorPageShown

// MARK: UserDefaults incositency monitoring
case protectedDataUnavailableWhenBecomeActive
case statisticsLoaderATBStateMismatch
case adAttributionReportStateMismatch

// MARK: - DuckPlayer Overlay Navigation
case duckPlayerYouTubeOverlayNavigationBack
Expand Down Expand Up @@ -1674,6 +1679,11 @@ extension Pixel.Event {

// MARK: - DuckPlayer FE Application Telemetry
case .duckPlayerLandscapeLayoutImpressions: return "duckplayer_landscape_layout_impressions"

// MARK: UserDefaults incositency monitoring
case .protectedDataUnavailableWhenBecomeActive: return "m_protected_data_unavailable_when_become_active"
case .statisticsLoaderATBStateMismatch: return "m_statistics_loader_atb_state_mismatch"
case .adAttributionReportStateMismatch: return "m_ad_attribution_report_state_mismatch"

// MARK: - DuckPlayer Overlay Navigation
case .duckPlayerYouTubeOverlayNavigationBack: return "duckplayer.youtube.overlay.navigation.back"
Expand Down
25 changes: 23 additions & 2 deletions Core/StatisticsLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,29 @@ public class StatisticsLoader {
private let returnUserMeasurement: ReturnUserMeasurement
private let usageSegmentation: UsageSegmenting
private let parser = AtbParser()
private let atbPresenceFileMarker = BoolFileMarker(name: .isATBPresent)
private let inconsistencyMonitoring: StatisticsStoreInconsistencyMonitoring

init(statisticsStore: StatisticsStore = StatisticsUserDefaults(),
returnUserMeasurement: ReturnUserMeasurement = KeychainReturnUserMeasurement(),
usageSegmentation: UsageSegmenting = UsageSegmentation()) {
usageSegmentation: UsageSegmenting = UsageSegmentation(),
inconsistencyMonitoring: StatisticsStoreInconsistencyMonitoring = StorageInconsistencyMonitor()) {
self.statisticsStore = statisticsStore
self.returnUserMeasurement = returnUserMeasurement
self.usageSegmentation = usageSegmentation
self.inconsistencyMonitoring = inconsistencyMonitoring
}

public func load(completion: @escaping Completion = {}) {
if statisticsStore.hasInstallStatistics {
let hasFileMarker = atbPresenceFileMarker?.isPresent ?? false
let hasInstallStatistics = statisticsStore.hasInstallStatistics

inconsistencyMonitoring.statisticsDidLoad(hasFileMarker: hasFileMarker, hasInstallStatistics: hasInstallStatistics)

if hasInstallStatistics {
// Synchronize file marker with current state
createATBFileMarker()

completion()
return
}
Expand Down Expand Up @@ -85,10 +97,15 @@ public class StatisticsLoader {
self.statisticsStore.installDate = Date()
self.statisticsStore.atb = atb.version
self.returnUserMeasurement.installCompletedWithATB(atb)
self.createATBFileMarker()
completion()
}
}

private func createATBFileMarker() {
atbPresenceFileMarker?.mark()
}

public func refreshSearchRetentionAtb(completion: @escaping Completion = {}) {
guard let url = StatisticsDependentURLFactory(statisticsStore: statisticsStore).makeSearchAtbURL() else {
requestInstallStatistics {
Expand Down Expand Up @@ -169,3 +186,7 @@ public class StatisticsLoader {
processUsageSegmentation(atb: nil, activityType: activityType)
}
}

private extension BoolFileMarker.Name {
static let isATBPresent = BoolFileMarker.Name(rawValue: "atb-present")
}
66 changes: 66 additions & 0 deletions Core/StorageInconsistencyMonitor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// StorageInconsistencyMonitor.swift
// DuckDuckGo
//
// Copyright © 2024 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import UIKit

public protocol AppActivationInconsistencyMonitoring {
/// See `StorageInconsistencyMonitor` for details
func didBecomeActive(isProtectedDataAvailable: Bool)
}

public protocol StatisticsStoreInconsistencyMonitoring {
/// See `StorageInconsistencyMonitor` for details
func statisticsDidLoad(hasFileMarker: Bool, hasInstallStatistics: Bool)
}

public protocol AdAttributionReporterInconsistencyMonitoring {
/// See `StorageInconsistencyMonitor` for details
func addAttributionReporter(hasFileMarker: Bool, hasCompletedFlag: Bool)
}

/// Takes care of reporting inconsistency in storage availability and/or state.
/// See https://app.asana.com/0/481882893211075/1208618515043198/f for details.
public struct StorageInconsistencyMonitor: AppActivationInconsistencyMonitoring & StatisticsStoreInconsistencyMonitoring & AdAttributionReporterInconsistencyMonitoring {

public init() { }

/// Reports a pixel if data is not available while app is active
public func didBecomeActive(isProtectedDataAvailable: Bool) {
if !isProtectedDataAvailable {
Pixel.fire(pixel: .protectedDataUnavailableWhenBecomeActive)
assertionFailure("This is unexpected state, debug if possible")
}
}

/// Reports a pixel if file marker exists but installStatistics are missing
public func statisticsDidLoad(hasFileMarker: Bool, hasInstallStatistics: Bool) {
if hasFileMarker == true && hasInstallStatistics == false {
Pixel.fire(pixel: .statisticsLoaderATBStateMismatch)
assertionFailure("This is unexpected state, debug if possible")
}
}

/// Reports a pixel if file marker exists but completion flag is false
public func addAttributionReporter(hasFileMarker: Bool, hasCompletedFlag: Bool) {
if hasFileMarker == true && hasCompletedFlag == false {
Pixel.fire(pixel: .adAttributionReportStateMismatch)
assertionFailure("This is unexpected state, debug if possible")
}
}
}
1 change: 0 additions & 1 deletion Core/UserDefaultsPropertyWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,6 @@ public struct UserDefaultsWrapper<T> {
// Debug keys
case debugNewTabPageSectionsEnabledKey = "com.duckduckgo.ios.debug.newTabPageSectionsEnabled"
case debugOnboardingHighlightsEnabledKey = "com.duckduckgo.ios.debug.onboardingHighlightsEnabled"
case debugOnboardingAddToDockEnabledKey = "com.duckduckgo.ios.debug.onboardingAddToDockEnabled"

// Duck Player Pixel Experiment
case duckPlayerPixelExperimentInstalled = "com.duckduckgo.ios.duckplayer.pixel.experiment.installed.v2"
Expand Down
Loading

0 comments on commit 1a5a622

Please sign in to comment.