Skip to content

Commit

Permalink
Merge branch 'main' into sam/migrate-daily-and-count-type-to-legacy-s…
Browse files Browse the repository at this point in the history
…tatus

# By Graeme Arthur (4) and others
# Via GitHub (1) and Graeme Arthur (1)
* main:
  Adding app backgrounded result to rule compilation (#3533)
  Send pixel on sync secure storage failure (#3542)
  Onboarding Add to Dock Refactor for Intro scenario (#3538)
  Update C-S-S to 6.29.0 (#3541)
  Change save password Never for Site button to Not Now (#3471)
  Release 7.144.0-1 (#3540)
  UserDefaults misbehavior monitoring (#3510)
  Send pixel on sync secure storage read failure (#3530)
  Remove NewTabPage retain cycles (#3532)
  Update release notes (#3529)

# Conflicts:
#	DuckDuckGo.xcodeproj/project.pbxproj
#	DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
  • Loading branch information
samsymons committed Nov 6, 2024
2 parents 3896e57 + 779b5bb commit 074f339
Show file tree
Hide file tree
Showing 68 changed files with 1,058 additions and 359 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))
}
}
13 changes: 13 additions & 0 deletions Core/PixelEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,7 @@ extension Pixel {
case syncRemoveDeviceError
case syncDeleteAccountError
case syncLoginExistingAccountError
case syncSecureStorageReadError

case syncGetOtherDevices
case syncGetOtherDevicesCopy
Expand Down Expand Up @@ -835,6 +836,11 @@ extension Pixel {

// MARK: WebView Error Page Shown
case webViewErrorPageShown

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

}
Expand Down Expand Up @@ -1432,6 +1438,7 @@ extension Pixel.Event {
case .syncRemoveDeviceError: return "m_d_sync_remove_device_error"
case .syncDeleteAccountError: return "m_d_sync_delete_account_error"
case .syncLoginExistingAccountError: return "m_d_sync_login_existing_account_error"
case .syncSecureStorageReadError: return "m_d_sync_secure_storage_error"

case .syncGetOtherDevices: return "sync_get_other_devices"
case .syncGetOtherDevicesCopy: return "sync_get_other_devices_copy"
Expand Down Expand Up @@ -1666,6 +1673,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"
}
}
}
Expand Down Expand Up @@ -1717,6 +1729,7 @@ extension Pixel.Event {

case tabClosed = "tab_closed"
case appQuit = "app_quit"
case appBackgrounded = "app_backgrounded"
case success

}
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")
}
}
}
2 changes: 2 additions & 0 deletions Core/SyncErrorHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ public class SyncErrorHandler: EventMapping<SyncError> {
Pixel.fire(pixel: .syncFailedToLoadAccount, error: error)
case .failedToSetupEngine:
Pixel.fire(pixel: .syncFailedToSetupEngine, error: error)
case .failedToReadSecureStore:
Pixel.fire(pixel: .syncSecureStorageReadError, error: error)
default:
// Should this be so generic?
let domainEvent = Pixel.Event.syncSentUnauthenticatedRequest
Expand Down
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 074f339

Please sign in to comment.