Skip to content

Commit

Permalink
DBP: Implement stats pixels (#2812)
Browse files Browse the repository at this point in the history
  • Loading branch information
jotaemepereira authored Jun 9, 2024
1 parent a6fe664 commit d5f19a2
Show file tree
Hide file tree
Showing 12 changed files with 915 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 @@ -93,7 +93,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 @@ -67,6 +67,14 @@ struct MirrorSite: Codable, Sendable {
removedAt = try? container.decode(Date.self, forKey: .removedAt)

}

func wasRemoved(since: Date = Date()) -> Bool {
guard let removedAt = self.removedAt else {
return false
}

return removedAt < since
}
}

public enum DataBrokerHierarchy: Int {
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:
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 @@ -171,6 +178,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 @@ -281,6 +294,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 @@ -419,6 +436,24 @@ 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),
.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),
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),
.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 @@ -498,7 +533,11 @@ public class DataBrokerProtectionPixelsHandler: EventMapping<DataBrokerProtectio
.initialScanTotalDuration,
.initialScanSiteLoadDuration,
.initialScanPostLoadingDuration,
.initialScanPreStartDuration:
.initialScanPreStartDuration,
.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 = numberOfDaysFrom(startDate: startDate, endDate: endDate) {
return differenceBetweenDates >= daysDifference.rawValue
}

return false
}

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

return components.day
}
}
Loading

0 comments on commit d5f19a2

Please sign in to comment.