Skip to content

Commit

Permalink
Change DAU pixel for VPN to weekly (#2552)
Browse files Browse the repository at this point in the history
Task/Issue URL: https://app.asana.com/0/0/1206685546588311/f

**Description**:

Reported `cohort` now uses the week number.
  • Loading branch information
quanganhdo authored Apr 9, 2024
1 parent 0d7c9de commit ebb09c3
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider {
PixelKit.fire(
NetworkProtectionPixelEvent.networkProtectionActiveUser,
frequency: .dailyOnly,
withAdditionalParameters: ["cohort": PixelKit.dateString(for: defaults.vpnFirstEnabled)],
withAdditionalParameters: [PixelKit.Parameters.vpnCohort: PixelKit.cohort(from: defaults.vpnFirstEnabled)],
includeAppVersionParameter: true)
case .reportConnectionAttempt(attempt: let attempt):
switch attempt {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ public extension PixelKit {
public static let vpnBreakageMetadata = "breakageMetadata"

public static let reason = "reason"

public static let vpnCohort = "cohort"
}

enum Values {
Expand Down
32 changes: 20 additions & 12 deletions LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,7 @@ public final class PixelKit {
return calendar
}()

private var dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.calendar = defaultDailyPixelCalendar
dateFormatter.timeZone = defaultDailyPixelCalendar.timeZone
dateFormatter.dateFormat = "yyyy-MM-dd"
return dateFormatter
}()
private static let weeksToCoalesceCohort = 6

private let dateGenerator: () -> Date

Expand All @@ -99,13 +93,17 @@ public final class PixelKit {
source: String? = nil,
defaultHeaders: [String: String],
log: OSLog,
dailyPixelCalendar: Calendar? = nil,
dateGenerator: @escaping () -> Date = Date.init,
defaults: UserDefaults,
fireRequest: @escaping FireRequest) {
shared = PixelKit(dryRun: dryRun,
appVersion: appVersion,
source: source,
defaultHeaders: defaultHeaders,
log: log,
dailyPixelCalendar: dailyPixelCalendar,
dateGenerator: dateGenerator,
defaults: defaults,
fireRequest: fireRequest)
}
Expand Down Expand Up @@ -318,13 +316,23 @@ public final class PixelKit {
onComplete: onComplete)
}

private func dateString(for date: Date?) -> String? {
guard let date else { return nil }
return dateFormatter.string(from: date)
private func cohort(from cohortLocalDate: Date?, dateGenerator: () -> Date = Date.init) -> String? {
guard let cohortLocalDate,
let baseDate = pixelCalendar.date(from: .init(year: 2023, month: 1, day: 1)),
let weeksSinceCohortAssigned = pixelCalendar.dateComponents([.weekOfYear], from: cohortLocalDate, to: dateGenerator()).weekOfYear,
let assignedCohort = pixelCalendar.dateComponents([.weekOfYear], from: baseDate, to: cohortLocalDate).weekOfYear else {
return nil
}

if weeksSinceCohortAssigned > Self.weeksToCoalesceCohort {
return ""
} else {
return "week-" + String(assignedCohort + 1)
}
}

public static func dateString(for date: Date?) -> String {
Self.shared?.dateString(for: date) ?? ""
public static func cohort(from cohortLocalDate: Date?, dateGenerator: () -> Date = Date.init) -> String {
Self.shared?.cohort(from: cohortLocalDate, dateGenerator: dateGenerator) ?? ""
}

public static func pixelLastFireDate(event: Event) -> Date? {
Expand Down
96 changes: 81 additions & 15 deletions LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -258,17 +258,18 @@ final class PixelKitTests: XCTestCase {
// Run test
pixelKit.fire(event, frequency: .dailyOnly) // Fired

timeMachine.travel(by: 60 * 60 * 2)
pixelKit.fire(event, frequency: .dailyOnly) // Skipped (2 hours since last fire)
timeMachine.travel(by: .hour, value: 2)
pixelKit.fire(event, frequency: .dailyOnly) // Skipped

timeMachine.travel(by: 60 * 60 * 24 + 1000)
pixelKit.fire(event, frequency: .dailyOnly) // Fired (24 hours + 1000 seconds since last fire)
timeMachine.travel(by: .day, value: 1)
timeMachine.travel(by: .hour, value: 2)
pixelKit.fire(event, frequency: .dailyOnly) // Fired

timeMachine.travel(by: 60 * 60 * 10)
pixelKit.fire(event, frequency: .dailyOnly) // Skipped (10 hours since last fire)
timeMachine.travel(by: .hour, value: 10)
pixelKit.fire(event, frequency: .dailyOnly) // Skipped

timeMachine.travel(by: 60 * 60 * 14)
pixelKit.fire(event, frequency: .dailyOnly) // Fired (24 hours since last fire)
timeMachine.travel(by: .day, value: 1)
pixelKit.fire(event, frequency: .dailyOnly) // Fired

// Wait for expectations to be fulfilled
wait(for: [fireCallbackCalled], timeout: 0.5)
Expand Down Expand Up @@ -303,28 +304,93 @@ final class PixelKitTests: XCTestCase {
// Run test
pixelKit.fire(event, frequency: .justOnce) // Fired

timeMachine.travel(by: 60 * 60 * 2)
timeMachine.travel(by: .hour, value: 2)
pixelKit.fire(event, frequency: .justOnce) // Skipped (already fired)

timeMachine.travel(by: 60 * 60 * 24 + 1000)
timeMachine.travel(by: .day, value: 1)
timeMachine.travel(by: .hour, value: 2)
pixelKit.fire(event, frequency: .justOnce) // Skipped (already fired)

timeMachine.travel(by: 60 * 60 * 10)
timeMachine.travel(by: .hour, value: 10)
pixelKit.fire(event, frequency: .justOnce) // Skipped (already fired)

timeMachine.travel(by: 60 * 60 * 14)
timeMachine.travel(by: .day, value: 1)
pixelKit.fire(event, frequency: .justOnce) // Skipped (already fired)

// Wait for expectations to be fulfilled
wait(for: [fireCallbackCalled], timeout: 0.5)
}

func testVPNCohort() {
XCTAssertEqual(PixelKit.cohort(from: nil), "")
assertCohortEqual(.init(year: 2023, month: 1, day: 1), reportAs: "week-1")
assertCohortEqual(.init(year: 2024, month: 2, day: 24), reportAs: "week-60")
}

private func assertCohortEqual(_ cohort: DateComponents, reportAs reportedCohort: String) {
var calendar = Calendar.current
calendar.timeZone = TimeZone(secondsFromGMT: 0)!
calendar.locale = Locale(identifier: "en_US_POSIX")

let cohort = calendar.date(from: cohort)
let timeMachine = TimeMachine(calendar: calendar, date: cohort)

PixelKit.setUp(appVersion: "test",
defaultHeaders: [:],
log: .disabled,
dailyPixelCalendar: calendar,
dateGenerator: timeMachine.now,
defaults: userDefaults()) { _, _, _, _, _, _ in }

// 1st week
XCTAssertEqual(PixelKit.cohort(from: cohort, dateGenerator: timeMachine.now), reportedCohort)

// 2nd week
timeMachine.travel(by: .weekOfYear, value: 1)
XCTAssertEqual(PixelKit.cohort(from: cohort, dateGenerator: timeMachine.now), reportedCohort)

// 3rd week
timeMachine.travel(by: .weekOfYear, value: 1)
XCTAssertEqual(PixelKit.cohort(from: cohort, dateGenerator: timeMachine.now), reportedCohort)

// 4th week
timeMachine.travel(by: .weekOfYear, value: 1)
XCTAssertEqual(PixelKit.cohort(from: cohort, dateGenerator: timeMachine.now), reportedCohort)

// 5th week
timeMachine.travel(by: .weekOfYear, value: 1)
XCTAssertEqual(PixelKit.cohort(from: cohort, dateGenerator: timeMachine.now), reportedCohort)

// 6th week
timeMachine.travel(by: .weekOfYear, value: 1)
XCTAssertEqual(PixelKit.cohort(from: cohort, dateGenerator: timeMachine.now), reportedCohort)

// 7th week
timeMachine.travel(by: .weekOfYear, value: 1)
XCTAssertEqual(PixelKit.cohort(from: cohort, dateGenerator: timeMachine.now), reportedCohort)

// 8th week
timeMachine.travel(by: .weekOfYear, value: 1)
XCTAssertEqual(PixelKit.cohort(from: cohort, dateGenerator: timeMachine.now), "")
}
}

private class TimeMachine {
private var date = Date(timeIntervalSince1970: 0)
private var date: Date
private let calendar: Calendar

init(calendar: Calendar? = nil, date: Date? = nil) {
self.calendar = calendar ?? {
var calendar = Calendar.current
calendar.timeZone = TimeZone(secondsFromGMT: 0)!
calendar.locale = Locale(identifier: "en_US_POSIX")
return calendar
}()
self.date = date ?? .init(timeIntervalSince1970: 0)
}

func travel(by timeInterval: TimeInterval) {
date = date.addingTimeInterval(timeInterval)
func travel(by component: Calendar.Component, value: Int) {
date = calendar.date(byAdding: component, value: value, to: now())!
}

func now() -> Date {
Expand Down

0 comments on commit ebb09c3

Please sign in to comment.