Skip to content

Commit

Permalink
Implement stats pixels
Browse files Browse the repository at this point in the history
  • Loading branch information
jotaemepereira committed May 23, 2024
1 parent 872455d commit 92121f9
Show file tree
Hide file tree
Showing 11 changed files with 903 additions and 28 deletions.
6 changes: 5 additions & 1 deletion DuckDuckGo/DBP/DataBrokerProtectionPixelsHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,11 @@ public class DataBrokerProtectionPixelsHandler: EventMapping<DataBrokerProtectio
.initialScanTotalDuration,
.initialScanSiteLoadDuration,
.initialScanPostLoadingDuration,
.initialScanPreStartDuration:
.initialScanPreStartDuration,
.globalMetricsWeeklyStats,
.globalMetricsMonthlyStats,
.dataBrokerMetricsWeeklyStats,
.dataBrokerMetricsMonthlyStats:
PixelKit.fire(event)

case .homeViewShowNoPermissionError,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,13 @@ public struct HistoryEvent: Identifiable, Sendable {
return 0
}
}

func isMatchEvent() -> Bool {
switch type {
case .noMatchFound, .matchesFound(_):

Check failure on line 66 in LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/HistoryEvent.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Arguments can be omitted when matching enums with associated values if they are not used (empty_enum_arguments)
return true
default:
return false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,11 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager {
stageCalculator.fireScanSuccess(matchesFound: extractedProfiles.count)
let event = HistoryEvent(brokerId: brokerId, profileQueryId: profileQueryId, type: .matchesFound(count: extractedProfiles.count))
try database.add(event)
let extractedProfilesForBroker = try database.fetchExtractedProfiles(for: brokerId)

for extractedProfile in extractedProfiles {

// We check if the profile exists in the database.
let extractedProfilesForBroker = try database.fetchExtractedProfiles(for: brokerId)
let doesProfileExistsInDatabase = extractedProfilesForBroker.contains { $0.identifier == extractedProfile.identifier }

// If the profile exists we do not create a new opt-out operation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,6 @@ final class DataBrokerProtectionEngagementPixelsUserDefaults: DataBrokerProtecti
- MAU Pixel Last Sent 2024-03-19
*/
final class DataBrokerProtectionEngagementPixels {

enum ActiveUserFrequency: Int {
case daily = 1
case weekly = 7
case monthly = 28
}

private let calendar = Calendar.current
private let database: DataBrokerProtectionRepository
private let repository: DataBrokerProtectionEngagementPixelsRepository
private let handler: EventMapping<DataBrokerProtectionPixels>
Expand Down Expand Up @@ -139,36 +131,22 @@ final class DataBrokerProtectionEngagementPixels {
return true
}

return shouldWeFirePixel(startDate: latestPixelFire, endDate: date, daysDifference: .daily)
return DataBrokerProtectionPixelsUtilities.shouldWeFirePixel(startDate: latestPixelFire, endDate: date, daysDifference: .daily)
}

private func shouldWeFireWeeklyPixel(date: Date) -> Bool {
guard let latestPixelFire = repository.getLatestWeeklyPixel() else {
return true
}

return shouldWeFirePixel(startDate: latestPixelFire, endDate: date, daysDifference: .weekly)
return DataBrokerProtectionPixelsUtilities.shouldWeFirePixel(startDate: latestPixelFire, endDate: date, daysDifference: .weekly)
}

private func shouldWeFireMonthlyPixel(date: Date) -> Bool {
guard let latestPixelFire = repository.getLatestMonthlyPixel() else {
return true
}

return shouldWeFirePixel(startDate: latestPixelFire, endDate: date, daysDifference: .monthly)
}

private func shouldWeFirePixel(startDate: Date, endDate: Date, daysDifference: ActiveUserFrequency) -> Bool {
if let differenceBetweenDates = differenceBetweenDates(startDate: startDate, endDate: endDate) {
return differenceBetweenDates >= daysDifference.rawValue
}

return false
}

private func differenceBetweenDates(startDate: Date, endDate: Date) -> Int? {
let components = calendar.dateComponents([.day], from: startDate, to: endDate)

return components.day
return DataBrokerProtectionPixelsUtilities.shouldWeFirePixel(startDate: latestPixelFire, endDate: date, daysDifference: .monthly)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ public enum DataBrokerProtectionPixels {
static let hasError = "has_error"
static let brokerURL = "broker_url"
static let sleepDuration = "sleep_duration"
static let numberOfRecordsFound = "num_found"
static let numberOfOptOutsInProgress = "num_inprogress"
static let numberOfSucessfulOptOuts = "num_optoutsuccess"
static let numberOfOptOutsFailure = "num_optoutfailure"
static let durationOfFirstOptOut = "duration_firstoptout"
static let numberOfNewRecordsFound = "num_new_found"
static let numberOfReappereances = "num_reappeared"
}

case error(error: DataBrokerProtectionError, dataBroker: String)
Expand Down Expand Up @@ -169,6 +176,12 @@ public enum DataBrokerProtectionPixels {
case entitlementCheckValid
case entitlementCheckInvalid
case entitlementCheckError
// Measure success/failure rate of Personal Information Removal Pixels
// https://app.asana.com/0/1204006570077678/1206889724879222/f
case globalMetricsWeeklyStats(profilesFound: Int, optOutsInProgress: Int, successfulOptOuts: Int, failedOptOuts: Int, durationOfFirstOptOut: Int, numberOfNewRecordsFound: Int)
case globalMetricsMonthlyStats(profilesFound: Int, optOutsInProgress: Int, successfulOptOuts: Int, failedOptOuts: Int, durationOfFirstOptOut: Int, numberOfNewRecordsFound: Int)
case dataBrokerMetricsWeeklyStats(dataBrokerURL: String, profilesFound: Int, optOutsInProgress: Int, successfulOptOuts: Int, failedOptOuts: Int, durationOfFirstOptOut: Int, numberOfNewRecordsFound: Int, numberOfReappereances: Int)
case dataBrokerMetricsMonthlyStats(dataBrokerURL: String, profilesFound: Int, optOutsInProgress: Int, successfulOptOuts: Int, failedOptOuts: Int, durationOfFirstOptOut: Int, numberOfNewRecordsFound: Int, numberOfReappereances: Int)
}

extension DataBrokerProtectionPixels: PixelKitEvent {
Expand Down Expand Up @@ -277,6 +290,10 @@ extension DataBrokerProtectionPixels: PixelKitEvent {
case .entitlementCheckValid: return "m_mac_dbp_macos_entitlement_valid"
case .entitlementCheckInvalid: return "m_mac_dbp_macos_entitlement_invalid"
case .entitlementCheckError: return "m_mac_dbp_macos_entitlement_error"
case .globalMetricsWeeklyStats: return "m_mac_dbp_weekly_stats"
case .globalMetricsMonthlyStats: return "m_mac_dbp_monthly_stats"
case .dataBrokerMetricsWeeklyStats: return "m_mac_dbp_databroker_weekly_stats"
case .dataBrokerMetricsMonthlyStats: return "m_mac_dbp_databroker_monthly_stats"
}
}

Expand Down Expand Up @@ -413,6 +430,38 @@ extension DataBrokerProtectionPixels: PixelKitEvent {
return [Consts.durationInMs: String(duration), Consts.hasError: hasError.description, Consts.brokerURL: brokerURL, Consts.sleepDuration: String(sleepDuration)]
case .initialScanPreStartDuration(let duration):
return [Consts.durationInMs: String(duration)]
case .globalMetricsWeeklyStats(let profilesFound, let optOutsInProgress, let successfulOptOuts, let failedOptOuts, let durationOfFirstOptOut, let numberOfNewRecordsFound):
return [Consts.numberOfRecordsFound: String(profilesFound),
Consts.numberOfOptOutsInProgress: String(optOutsInProgress),
Consts.numberOfSucessfulOptOuts: String(successfulOptOuts),
Consts.numberOfOptOutsFailure: String(failedOptOuts),

Check failure on line 437 in LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Lines should not have trailing whitespace (trailing_whitespace)
Consts.durationOfFirstOptOut: String(durationOfFirstOptOut),
Consts.numberOfNewRecordsFound: String(numberOfNewRecordsFound)]
case .globalMetricsMonthlyStats(let profilesFound, let optOutsInProgress, let successfulOptOuts, let failedOptOuts, let durationOfFirstOptOut, let numberOfNewRecordsFound):
return [Consts.numberOfRecordsFound: String(profilesFound),
Consts.numberOfOptOutsInProgress: String(optOutsInProgress),
Consts.numberOfSucessfulOptOuts: String(successfulOptOuts),
Consts.numberOfOptOutsFailure: String(failedOptOuts),

Check failure on line 444 in LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Lines should not have trailing whitespace (trailing_whitespace)
Consts.durationOfFirstOptOut: String(durationOfFirstOptOut),
Consts.numberOfNewRecordsFound: String(numberOfNewRecordsFound)]
case .dataBrokerMetricsWeeklyStats(let dataBrokerURL, let profilesFound, let optOutsInProgress, let successfulOptOuts, let failedOptOuts, let durationOfFirstOptOut, let numberOfNewRecordsFound, let numberOfReappereances):
return [Consts.dataBrokerParamKey: dataBrokerURL,
Consts.numberOfRecordsFound: String(profilesFound),
Consts.numberOfOptOutsInProgress: String(optOutsInProgress),
Consts.numberOfSucessfulOptOuts: String(successfulOptOuts),
Consts.numberOfOptOutsFailure: String(failedOptOuts),
Consts.durationOfFirstOptOut: String(durationOfFirstOptOut),
Consts.numberOfNewRecordsFound: String(numberOfNewRecordsFound),
Consts.numberOfReappereances: String(numberOfReappereances)]
case .dataBrokerMetricsMonthlyStats(let dataBrokerURL, let profilesFound, let optOutsInProgress, let successfulOptOuts, let failedOptOuts, let durationOfFirstOptOut, let numberOfNewRecordsFound, let numberOfReappereances):
return [Consts.dataBrokerParamKey: dataBrokerURL,
Consts.numberOfRecordsFound: String(profilesFound),
Consts.numberOfOptOutsInProgress: String(optOutsInProgress),
Consts.numberOfSucessfulOptOuts: String(successfulOptOuts),
Consts.numberOfOptOutsFailure: String(failedOptOuts),
Consts.durationOfFirstOptOut: String(durationOfFirstOptOut),
Consts.numberOfNewRecordsFound: String(numberOfNewRecordsFound),
Consts.numberOfReappereances: String(numberOfReappereances)]
}
}
}
Expand Down Expand Up @@ -490,7 +539,11 @@ public class DataBrokerProtectionPixelsHandler: EventMapping<DataBrokerProtectio
.initialScanTotalDuration,
.initialScanSiteLoadDuration,
.initialScanPostLoadingDuration,
.initialScanPreStartDuration:
.initialScanPreStartDuration,

Check failure on line 542 in LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Lines should not have trailing whitespace (trailing_whitespace)
.globalMetricsWeeklyStats,
.globalMetricsMonthlyStats,
.dataBrokerMetricsWeeklyStats,
.dataBrokerMetricsMonthlyStats:

PixelKit.fire(event)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// DataBrokerProtectionPixelsUtilities.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

enum Frequency: Int {
case daily = 1
case weekly = 7
case monthly = 28
}

final class DataBrokerProtectionPixelsUtilities {
private static let calendar = Calendar.current

static func shouldWeFirePixel(startDate: Date, endDate: Date, daysDifference: Frequency) -> Bool {
if let differenceBetweenDates = differenceBetweenDates(startDate: startDate, endDate: endDate) {
return differenceBetweenDates >= daysDifference.rawValue
}

return false
}

static func differenceBetweenDates(startDate: Date, endDate: Date) -> Int? {
let components = calendar.dateComponents([.day], from: startDate, to: endDate)

return components.day
}
}
Loading

0 comments on commit 92121f9

Please sign in to comment.