Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

experiment default metrics pixels #1107

Merged
merged 57 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
216a00e
implement experiment manager
SabrinaTardio Nov 8, 2024
d8b385c
Merge branch 'main' into sabrina/add-experiment-manager
SabrinaTardio Nov 8, 2024
914a5ed
fix lint issues
SabrinaTardio Nov 8, 2024
93247a3
fix linting issue
SabrinaTardio Nov 8, 2024
5d530f2
wrap UserDefaults
SabrinaTardio Nov 8, 2024
2dcef1e
fix linting
SabrinaTardio Nov 8, 2024
5aec88d
refactor
SabrinaTardio Nov 8, 2024
b188d67
fix linting
SabrinaTardio Nov 8, 2024
1054923
address some comments
SabrinaTardio Nov 8, 2024
c05aa96
minor refactor
SabrinaTardio Nov 8, 2024
8387472
use Constants enum
SabrinaTardio Nov 8, 2024
e862ad8
add Targets and configurations
SabrinaTardio Nov 11, 2024
6090a15
fix linting issues
SabrinaTardio Nov 11, 2024
14f92e9
initial implementation
SabrinaTardio Nov 12, 2024
8bda08c
add tests
SabrinaTardio Nov 14, 2024
9a19db2
Merge branch 'main' into sabrina/add-experiment-logic
SabrinaTardio Nov 14, 2024
56c54a7
refactor
SabrinaTardio Nov 15, 2024
86b7c97
getAllActiveExperiments tests
SabrinaTardio Nov 15, 2024
2595fcf
sort linting issues
SabrinaTardio Nov 15, 2024
86d6f7b
fix linting
SabrinaTardio Nov 15, 2024
95c4470
don't break the API
SabrinaTardio Nov 15, 2024
c30f0b4
Merge branch 'main' into sabrina/add-experiment-logic
SabrinaTardio Nov 15, 2024
727e813
implement PixelExperimentKit
SabrinaTardio Nov 21, 2024
69dc2fd
implement framework
SabrinaTardio Nov 27, 2024
cfe3b13
clean up
SabrinaTardio Nov 27, 2024
1e426e2
Merge branch 'main' into sabrina/experiment-framework
SabrinaTardio Nov 27, 2024
26fa2ed
fix linting
SabrinaTardio Nov 28, 2024
ee2f891
have only one interface method
SabrinaTardio Nov 28, 2024
b3224fe
fix lint
SabrinaTardio Nov 28, 2024
b057431
address comments
SabrinaTardio Nov 28, 2024
e92cbb5
Merge branch 'sabrina/experiment-framework' into sabrina/experiment-p…
SabrinaTardio Nov 28, 2024
4756b60
update after merge
SabrinaTardio Nov 29, 2024
4f2d28b
Merge branch 'main' into sabrina/experiment-pixels
SabrinaTardio Nov 29, 2024
d202980
cleanup
SabrinaTardio Nov 29, 2024
bfb7a5e
fix lint errors
SabrinaTardio Nov 29, 2024
a98e9c4
add pixel unique parameter test
SabrinaTardio Nov 29, 2024
7747bdc
remove unwanted code
SabrinaTardio Nov 29, 2024
92bb24f
refactor
SabrinaTardio Nov 29, 2024
e7801f9
refactor and add more tests
SabrinaTardio Dec 2, 2024
fc1c31e
add tests
SabrinaTardio Dec 2, 2024
f03d0e3
Merge branch 'main' into sabrina/experiment-pixels
SabrinaTardio Dec 2, 2024
f24740a
fix linting
SabrinaTardio Dec 2, 2024
aef82dd
linting
SabrinaTardio Dec 2, 2024
9b65cc7
linting
SabrinaTardio Dec 2, 2024
4992efe
implement firing enrolment pixel
SabrinaTardio Dec 3, 2024
4412bd4
Merge branch 'main' into sabrina/experiment-pixels
SabrinaTardio Dec 3, 2024
b202b44
fix tests
SabrinaTardio Dec 3, 2024
c62fac4
generalise headers and prefix name
SabrinaTardio Dec 3, 2024
cc6c1b6
address comments
SabrinaTardio Dec 4, 2024
b567a22
add isDry for new unique params
SabrinaTardio Dec 4, 2024
2b0f434
fix linting
SabrinaTardio Dec 4, 2024
15eed7b
fix tests
SabrinaTardio Dec 4, 2024
e0e45ca
fix iOS pixel naming
SabrinaTardio Dec 4, 2024
cd6d89a
address comments and remove m_ from experiments
SabrinaTardio Dec 5, 2024
9568123
remove unwanted change
SabrinaTardio Dec 5, 2024
8db91da
fix mac pixel names
SabrinaTardio Dec 9, 2024
8c2a9c6
fix test
SabrinaTardio Dec 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,20 @@
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PixelExperimentKit"
BuildableName = "PixelExperimentKit"
BlueprintName = "PixelExperimentKit"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
Expand Down Expand Up @@ -842,6 +856,16 @@
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PixelExperimentKitTests"
BuildableName = "PixelExperimentKitTests"
BlueprintName = "PixelExperimentKitTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
Expand Down
20 changes: 20 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ let package = Package(
.library(name: "DuckPlayer", targets: ["DuckPlayer"]),
.library(name: "MaliciousSiteProtection", targets: ["MaliciousSiteProtection"]),
.library(name: "Onboarding", targets: ["Onboarding"]),
.library(name: "PixelExperimentKit", targets: ["PixelExperimentKit"]),
.library(name: "BrokenSitePrompt", targets: ["BrokenSitePrompt"]),
.library(name: "PageRefreshMonitor", targets: ["PageRefreshMonitor"]),
.library(name: "PrivacyStats", targets: ["PrivacyStats"]),
Expand Down Expand Up @@ -433,6 +434,19 @@ let package = Package(
.define("DEBUG", .when(configuration: .debug))
]
),
.target(
name: "PixelExperimentKit",
dependencies: [
"PixelKit",
"BrowserServicesKit"
],
resources: [
.process("Resources")
],
swiftSettings: [
.define("DEBUG", .when(configuration: .debug))
]
),
.target(
name: "BrokenSitePrompt",
dependencies: [
Expand Down Expand Up @@ -683,6 +697,12 @@ let package = Package(
"Onboarding"
]
),
.testTarget(
name: "PixelExperimentKitTests",
dependencies: [
"PixelExperimentKit"
]
),
.testTarget(
name: "SpecialErrorPagesTests",
dependencies: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public class ExperimentCohortsManager: ExperimentCohortsManaging {
private var store: ExperimentsDataStoring
private let randomizer: (Range<Double>) -> Double
private let queue = DispatchQueue(label: "com.ExperimentCohortsManager.queue")
private let fireCohortAssigned: (_ subfeatureID: SubfeatureID, _ experiment: ExperimentData) -> Void

public var experiments: Experiments? {
get {
Expand All @@ -81,9 +82,11 @@ public class ExperimentCohortsManager: ExperimentCohortsManaging {
}
}

public init(store: ExperimentsDataStoring = ExperimentsDataStore(), randomizer: @escaping (Range<Double>) -> Double = Double.random(in:)) {
public init(store: ExperimentsDataStoring = ExperimentsDataStore(), randomizer: @escaping (Range<Double>) -> Double = Double.random(in:),
fireCohortAssigned: @escaping (_ subfeatureID: SubfeatureID, _ experiment: ExperimentData) -> Void) {
self.store = store
self.randomizer = randomizer
self.fireCohortAssigned = fireCohortAssigned
}

public func resolveCohort(for experiment: ExperimentSubfeature, allowCohortReassignment: Bool) -> CohortID? {
Expand Down Expand Up @@ -113,6 +116,7 @@ extension ExperimentCohortsManager {
cumulativeWeight += Double(cohort.weight)
if randomValue < cumulativeWeight {
saveCohort(cohort.name, in: subfeature.subfeatureID, parentID: subfeature.parentID)
fireCohortAssigned(subfeature.subfeatureID, ExperimentData(parentID: subfeature.parentID, cohortID: cohort.name, enrollmentDate: Date()))
return cohort.name
}
}
Expand Down
80 changes: 80 additions & 0 deletions Sources/PixelExperimentKit/ExperimentEventTracker.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// ExperimentEventTracker.swift
//
// 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 Foundation

public typealias ThresholdCheckResult = Bool
public typealias ExprimentPixelNameAndParameters = String
public typealias NumberOfActions = Int

public protocol ExperimentActionPixelStore {
func removeObject(forKey defaultName: String)
func integer(forKey defaultName: String) -> Int
func set(_ value: Int, forKey defaultName: String)
}

public protocol ExperimentEventTracking {
/// Increments the count for a given event key and checks if the threshold has been exceeded.
///
/// This method performs the following actions:
/// 1. If the `isInWindow` parameter is `false`, it removes the stored count for the key and returns `false`.
/// 2. If `isInWindow` is `true`, it increments the count for the key.
/// 3. If the updated count meets or exceeds the specified `threshold`, the stored count is removed, and the method returns `true`.
/// 4. If the updated count does not meet the threshold, it updates the count and returns `false`.
///
/// - Parameters:
/// - key: The key used to store and retrieve the count.
/// - threshold: The count threshold that triggers a return of `true`.
/// - isInWindow: A flag indicating if the count should be considered (e.g., within a time window).
/// - Returns: `true` if the threshold is exceeded and the count is reset, otherwise `false`.
func incrementAndCheckThreshold(forKey key: ExprimentPixelNameAndParameters, threshold: NumberOfActions, isInWindow: Bool) -> ThresholdCheckResult
}

public struct ExperimentEventTracker: ExperimentEventTracking {
private let store: ExperimentActionPixelStore
private let syncQueue = DispatchQueue(label: "com.pixelkit.experimentActionSyncQueue")

public init(store: ExperimentActionPixelStore = UserDefaults.standard) {
self.store = store
}

public func incrementAndCheckThreshold(forKey key: ExprimentPixelNameAndParameters, threshold: NumberOfActions, isInWindow: Bool) -> ThresholdCheckResult {
syncQueue.sync {
// Remove the key if is not in window
guard isInWindow else {
store.removeObject(forKey: key)
return false
}

// Increment the current count
let currentCount = store.integer(forKey: key)
let newCount = currentCount + 1
store.set(newCount, forKey: key)

// Check if the threshold is exceeded
if newCount >= threshold {
store.removeObject(forKey: key)
return true
}
return false
}
}

}

extension UserDefaults: ExperimentActionPixelStore {}
Loading
Loading