Skip to content

Commit

Permalink
Tds experiment metrics (#3839)
Browse files Browse the repository at this point in the history
Task/Issue URL:
https://app.asana.com/0/1204186595873227/1208547600159201/f
Tech Design URL:
https://app.asana.com/0/1204186595873227/1209001242859416/f

**Description**: Adds metric for TDS override experiment
  • Loading branch information
SabrinaTardio authored Jan 23, 2025
1 parent 7e2313b commit fd448a4
Show file tree
Hide file tree
Showing 11 changed files with 139 additions and 46 deletions.
19 changes: 17 additions & 2 deletions Core/ContentBlocking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import Foundation
import BrowserServicesKit
import Combine
import Common
import PixelExperimentKit

public final class ContentBlocking {

Expand All @@ -41,6 +42,11 @@ public final class ContentBlocking {
}
}

enum PixelParameterName {
static let experimentName = "experimentName"
static let etag = "etag"
}

private init(privacyConfigurationManager: PrivacyConfigurationManaging? = nil) {
let internalUserDecider = DefaultInternalUserDecider(store: InternalUserStore())
let statisticsStore = StatisticsUserDefaults()
Expand Down Expand Up @@ -80,9 +86,14 @@ public final class ContentBlocking {

private static let debugEvents = EventMapping<ContentBlockerDebugEvents> { event, error, parameters, onComplete in
let domainEvent: Pixel.Event
var finalParameters = parameters ?? [:]
switch event {
case .trackerDataParseFailed:
domainEvent = .trackerDataParseFailed
if let experimentName = TDSOverrideExperimentMetrics.activeTDSExperimentNameWithCohort {
finalParameters[PixelParameterName.experimentName] = experimentName
finalParameters[PixelParameterName.etag] = UserDefaultsETagStorage().loadEtag(for: .trackerDataSet)
}

case .trackerDataReloadFailed:
domainEvent = .trackerDataReloadFailed
Expand Down Expand Up @@ -131,16 +142,20 @@ public final class ContentBlocking {
case .contentBlockingCompilationTaskPerformance(let retryCount, let timeBucketAggregation):
domainEvent = .contentBlockingCompilationTaskPerformance(iterationCount: retryCount,
timeBucketAggregation: Pixel.Event.CompileTimeBucketAggregation(number: timeBucketAggregation))
if let experimentName = TDSOverrideExperimentMetrics.activeTDSExperimentNameWithCohort {
finalParameters[PixelParameterName.experimentName] = experimentName
finalParameters[PixelParameterName.etag] = UserDefaultsETagStorage().loadEtag(for: .trackerDataSet)
}
}

if let error = error {
Pixel.fire(pixel: domainEvent,
error: error,
withAdditionalParameters: parameters ?? [:],
withAdditionalParameters: finalParameters,
onComplete: onComplete)
} else {
Pixel.fire(pixel: domainEvent,
withAdditionalParameters: parameters ?? [:],
withAdditionalParameters: finalParameters,
includedParameters: [],
onComplete: onComplete)
}
Expand Down
11 changes: 10 additions & 1 deletion Core/FileStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import Foundation
import Configuration
import PixelExperimentKit

public class FileStore {

Expand Down Expand Up @@ -79,7 +80,15 @@ public class FileStore {
} catch {
let nserror = error as NSError
if nserror.domain != NSCocoaErrorDomain || nserror.code != NSFileReadNoSuchFileError {
Pixel.fire(pixel: .trackerDataCouldNotBeLoaded, error: error)
if configuration == .trackerDataSet, let experimentName = TDSOverrideExperimentMetrics.activeTDSExperimentNameWithCohort {
let parameters = [
"experimentName": experimentName,
"etag": UserDefaultsETagStorage().loadEtag(for: .trackerDataSet) ?? ""
]
Pixel.fire(pixel: .trackerDataCouldNotBeLoaded, error: error, withAdditionalParameters: parameters)
} else {
Pixel.fire(pixel: .trackerDataCouldNotBeLoaded, error: error)
}
}
}
}
Expand Down
9 changes: 6 additions & 3 deletions Core/PixelEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,9 @@ extension Pixel {
case debugWebsiteDataStoresCleared

case debugBookmarksMigratedMoreThanOnce


case debugBreakageExperiment

// Return user measurement
case debugReturnUserAddATB
case debugReturnUserUpdateATB
Expand Down Expand Up @@ -1498,9 +1500,9 @@ extension Pixel.Event {

case .configurationFetchInfo: return "m_d_cfgfetch"

case .trackerDataParseFailed: return "m_d_tds_p"
case .trackerDataParseFailed: return "m_d_tracker_data_parse_failed"
case .trackerDataReloadFailed: return "m_d_tds_r"
case .trackerDataCouldNotBeLoaded: return "m_d_tds_l"
case .trackerDataCouldNotBeLoaded: return "m_d_tracker_data_could_not_be_loaded"
case .fileStoreWriteFailed: return "m_d_fswf"
case .fileStoreCoordinatorFailed: return "m_d_configuration_file_coordinator_error"
case .privacyConfigurationReloadFailed: return "m_d_pc_r"
Expand Down Expand Up @@ -1935,6 +1937,7 @@ extension Pixel.Event {
// MARK: Lifecycle
case .appDidTransitionToUnexpectedState: return "m_debug_app-did-transition-to-unexpected-state-3"

case .debugBreakageExperiment: return "m_debug_breakage_experiment_u"
}
}
}
Expand Down
18 changes: 13 additions & 5 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@
564DE4572C4150E600D23241 /* NewTabPageControllerDaxDialogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564DE4562C4150E600D23241 /* NewTabPageControllerDaxDialogTests.swift */; };
564DE45A2C450BE600D23241 /* DaxDialogsNewTabTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564DE4592C450BE600D23241 /* DaxDialogsNewTabTests.swift */; };
564DE45E2C45218500D23241 /* OnboardingNavigationDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564DE45D2C45218500D23241 /* OnboardingNavigationDelegateTests.swift */; };
5650E36F2D3E8E5900D41ECF /* PageRefreshMonitorExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5650E36E2D3E8E5900D41ECF /* PageRefreshMonitorExtensionTests.swift */; };
566B73702BECD46800FF1959 /* MainViewController+SyncAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566B736F2BECD46800FF1959 /* MainViewController+SyncAlerts.swift */; };
566B73732BECE4F200FF1959 /* SyncErrorHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566B73712BECE4F200FF1959 /* SyncErrorHandling.swift */; };
566B73762BECE53D00FF1959 /* SyncPausedStateManaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566B73742BECE53D00FF1959 /* SyncPausedStateManaging.swift */; };
Expand Down Expand Up @@ -1028,7 +1029,6 @@
CB3C788D2D06D3A700A7E4ED /* AppStateTransitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBAD0F072CFE27D5006267B8 /* AppStateTransitions.swift */; };
CB3C788E2D06D3A700A7E4ED /* Initializing.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBAD0EF82CFE1D35006267B8 /* Initializing.swift */; };
CB3C788F2D06D3A700A7E4ED /* Suspending.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBAD0EFE2CFE1D4E006267B8 /* Suspending.swift */; };
CB48D3332B90CE9F00631D8B /* PageRefreshStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB48D3312B90CE9F00631D8B /* PageRefreshStore.swift */; };
CB4FA44E2C78AACE00A16F5A /* SpecialErrorPageUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB4FA44D2C78AACE00A16F5A /* SpecialErrorPageUserScript.swift */; };
CB5516D0286500290079B175 /* TrackerRadarIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85519124247468580010FDD0 /* TrackerRadarIntegrationTests.swift */; };
CB5516D1286500290079B175 /* ContentBlockingRulesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CA904C24FD2DB000D41DDF /* ContentBlockingRulesTests.swift */; };
Expand Down Expand Up @@ -1694,6 +1694,7 @@
564DE4562C4150E600D23241 /* NewTabPageControllerDaxDialogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageControllerDaxDialogTests.swift; sourceTree = "<group>"; };
564DE4592C450BE600D23241 /* DaxDialogsNewTabTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaxDialogsNewTabTests.swift; sourceTree = "<group>"; };
564DE45D2C45218500D23241 /* OnboardingNavigationDelegateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingNavigationDelegateTests.swift; sourceTree = "<group>"; };
5650E36E2D3E8E5900D41ECF /* PageRefreshMonitorExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageRefreshMonitorExtensionTests.swift; sourceTree = "<group>"; };
566B736F2BECD46800FF1959 /* MainViewController+SyncAlerts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainViewController+SyncAlerts.swift"; sourceTree = "<group>"; };
566B73712BECE4F200FF1959 /* SyncErrorHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncErrorHandling.swift; sourceTree = "<group>"; };
566B73742BECE53D00FF1959 /* SyncPausedStateManaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncPausedStateManaging.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2962,7 +2963,6 @@
CB2A7EF3285383B300885F67 /* AppLastCompiledRulesStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLastCompiledRulesStore.swift; sourceTree = "<group>"; };
CB2C47822AF6D55800AEDCD9 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = "<group>"; };
CB4448752AF6D51D001F93F7 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/InfoPlist.strings; sourceTree = "<group>"; };
CB48D3312B90CE9F00631D8B /* PageRefreshStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PageRefreshStore.swift; sourceTree = "<group>"; };
CB4FA44D2C78AACE00A16F5A /* SpecialErrorPageUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpecialErrorPageUserScript.swift; sourceTree = "<group>"; };
CB5038622AF6D563007FD69F /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
CB6ABD002AF6D52B004A8224 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/InfoPlist.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4097,6 +4097,14 @@
path = Configuration;
sourceTree = "<group>";
};
5650E36D2D3E8E3100D41ECF /* PageRefreshMonitor */ = {
isa = PBXGroup;
children = (
5650E36E2D3E8E5900D41ECF /* PageRefreshMonitorExtensionTests.swift */,
);
path = PageRefreshMonitor;
sourceTree = "<group>";
};
566B736E2BECD3DC00FF1959 /* Utilities */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -4635,6 +4643,7 @@
84E341A91E2F7EFB00BDBA6F /* UnitTests */ = {
isa = PBXGroup;
children = (
5650E36D2D3E8E3100D41ECF /* PageRefreshMonitor */,
F1BDDC012C340DDF00459306 /* SyncUI */,
F12D98401F266B30003C2EE3 /* DuckDuckGo */,
F1E092B31E92A6B900732CCC /* Core */,
Expand Down Expand Up @@ -5746,7 +5755,6 @@
isa = PBXGroup;
children = (
CBECDB792CD981C6005B8B87 /* AppPageRefreshMonitor.swift */,
CB48D3312B90CE9F00631D8B /* PageRefreshStore.swift */,
);
name = PageRefreshMonitor;
sourceTree = "<group>";
Expand Down Expand Up @@ -8344,7 +8352,6 @@
9FE05CEE2C36424E00D9046B /* OnboardingPixelReporter.swift in Sources */,
310C4B47281B60E300BA79A9 /* AutofillLoginDetailsViewModel.swift in Sources */,
85EE7F572246685B000FE757 /* WebContainerViewController.swift in Sources */,
CB48D3332B90CE9F00631D8B /* PageRefreshStore.swift in Sources */,
1EC458462948932500CB2B13 /* UIHostingControllerExtension.swift in Sources */,
1E4DCF4E27B6A69600961E25 /* DownloadsListHostingController.swift in Sources */,
850F93DB2B594AB800823EEA /* ZippedPassKitPreviewHelper.swift in Sources */,
Expand Down Expand Up @@ -8740,6 +8747,7 @@
6AC98419288055C1005FA9CA /* BarsAnimatorTests.swift in Sources */,
85E065C12C73ADDD00D73E2A /* UsageSegmentationStorageTests.swift in Sources */,
8536A1CA209AF6490050739E /* HomeRowReminderTests.swift in Sources */,
5650E36F2D3E8E5900D41ECF /* PageRefreshMonitorExtensionTests.swift in Sources */,
C1106F312D0EFD8B0054A221 /* FreeTrialsFeatureFlagExperimentTests.swift in Sources */,
851DFD8A212C5EE800D95F20 /* TabSwitcherButtonTests.swift in Sources */,
98983096255B5019003339A2 /* BookmarksCachingSearchTests.swift in Sources */,
Expand Down Expand Up @@ -11953,7 +11961,7 @@
repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit";
requirement = {
kind = exactVersion;
version = 228.0.0;
version = 229.0.0;
};
};
9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/DuckDuckGo/BrowserServicesKit",
"state" : {
"revision" : "12227e2c97ad93f8924551e5ead831c79386b19b",
"version" : "228.0.0"
"revision" : "4232acb81dc42311832cdaab042cbcafd530f9bc",
"version" : "229.0.0"
}
},
{
Expand Down
3 changes: 1 addition & 2 deletions DuckDuckGo/AppDependencyProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ final class AppDependencyProvider: DependencyProvider {
let configurationManager: ConfigurationManager
let configurationStore = ConfigurationStore()

let pageRefreshMonitor = PageRefreshMonitor(onDidDetectRefreshPattern: PageRefreshMonitor.onDidDetectRefreshPattern,
store: PageRefreshStore())
let pageRefreshMonitor = PageRefreshMonitor(onDidDetectRefreshPattern: PageRefreshMonitor.onDidDetectRefreshPattern)

// Subscription
let subscriptionManager: SubscriptionManager
Expand Down
18 changes: 16 additions & 2 deletions DuckDuckGo/AppPageRefreshMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,25 @@
import Core
import Common
import PageRefreshMonitor
import PixelExperimentKit

extension PageRefreshMonitor {

static let onDidDetectRefreshPattern: () -> Void = {
Pixel.fire(pixel: .pageRefreshThreeTimesWithin20Seconds)
static let onDidDetectRefreshPattern: (NumberOfRefreshes) -> Void = { numberOfRefreshes in
let tdsEtag = AppDependencyProvider.shared.configurationStore.loadEtag(for: .trackerDataSet) ?? ""
switch numberOfRefreshes {
case 2:
TDSOverrideExperimentMetrics.fireTDSExperimentMetric(metricType: .refresh2X, etag: tdsEtag, fireDebugExperiment: { parameters in
UniquePixel.fire(pixel: .debugBreakageExperiment, withAdditionalParameters: parameters)
})
case 3:
Pixel.fire(pixel: .pageRefreshThreeTimesWithin20Seconds)
TDSOverrideExperimentMetrics.fireTDSExperimentMetric(metricType: .refresh3X, etag: tdsEtag, fireDebugExperiment: { parameters in
UniquePixel.fire(pixel: .debugBreakageExperiment, withAdditionalParameters: parameters)
})
default:
return
}
}

}
29 changes: 0 additions & 29 deletions DuckDuckGo/PageRefreshStore.swift

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import BrowserServicesKit
import PrivacyDashboard
import Common
import os.log
import PixelExperimentKit

final class PrivacyDashboardViewController: UIViewController {

Expand Down Expand Up @@ -136,6 +137,10 @@ final class PrivacyDashboardViewController: UIViewController {
ActionMessageView.present(message: UserText.messageProtectionDisabled.format(arguments: domain))
}
Pixel.fire(pixel: .dashboardProtectionAllowlistAdd, withAdditionalParameters: pixelParam)
let tdsEtag = AppDependencyProvider.shared.configurationStore.loadEtag(for: .trackerDataSet) ?? ""
TDSOverrideExperimentMetrics.fireTDSExperimentMetric(metricType: .privacyToggleUsed, etag: tdsEtag) { parameters in
UniquePixel.fire(pixel: .debugBreakageExperiment, withAdditionalParameters: parameters)
}
}

contentBlockingManager.scheduleCompilation()
Expand Down
5 changes: 5 additions & 0 deletions DuckDuckGo/TabViewControllerBrowsingMenuExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import simd
import WidgetKit
import Common
import PrivacyDashboard
import PixelExperimentKit

extension TabViewController {

Expand Down Expand Up @@ -505,6 +506,10 @@ extension TabViewController {
togglePrivacyProtection(domain: domain)
}
Pixel.fire(pixel: isProtected ? .browsingMenuDisableProtection : .browsingMenuEnableProtection)
let tdsEtag = AppDependencyProvider.shared.configurationStore.loadEtag(for: .trackerDataSet) ?? ""
TDSOverrideExperimentMetrics.fireTDSExperimentMetric(metricType: .privacyToggleUsed, etag: tdsEtag) { parameters in
UniquePixel.fire(pixel: .debugBreakageExperiment, withAdditionalParameters: parameters)
}
}

private func togglePrivacyProtection(domain: String, didSendReport: Bool = false) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// PageRefreshMonitorExtensionTests.swift
// DuckDuckGo
//
// Copyright © 2025 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 DuckDuckGo
@testable import PixelExperimentKit
import PageRefreshMonitor
import BrowserServicesKit

final class PageRefreshMonitorExtensionTests: XCTestCase {

var captureMetric: String?

override func setUpWithError() throws {
TDSOverrideExperimentMetrics.configureTDSOverrideExperimentMetrics { _, metric, _, _ in
self.captureMetric = metric
}
}

override func tearDownWithError() throws {
captureMetric = nil
}

func test_OnDidDetectRefreshPattern_WithValue1_FireExperimentFuncNotCalled() throws {
PageRefreshMonitor.onDidDetectRefreshPattern(1)

XCTAssertNil(captureMetric)
}

func test_OnDidDetectRefreshPattern_WithValue2_ExpectedFireExperimentFuncCalled() throws {
PageRefreshMonitor.onDidDetectRefreshPattern(2)

XCTAssertEqual(captureMetric, "2XRefresh")
}

func test_OnDidDetectRefreshPattern_WithValue3_ExpectedFireExperimentFuncCalled() throws {
PageRefreshMonitor.onDidDetectRefreshPattern(3)

XCTAssertEqual(captureMetric, "3XRefresh")
}

func test_OnDidDetectRefreshPattern_WithValue4_FireExperimentFuncNotCalled() throws {
PageRefreshMonitor.onDidDetectRefreshPattern(4)

XCTAssertNil(captureMetric)
}

}

0 comments on commit fd448a4

Please sign in to comment.