Skip to content

Commit

Permalink
DBP: Implement pixels to observe initial scan loading times (#2706)
Browse files Browse the repository at this point in the history
  • Loading branch information
jotaemepereira authored Apr 29, 2024
1 parent f14a2d4 commit 79d648d
Show file tree
Hide file tree
Showing 27 changed files with 204 additions and 51 deletions.
4 changes: 2 additions & 2 deletions DuckDuckGo/DBP/DataBrokerProtectionAppEvents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ struct DataBrokerProtectionAppEvents {

// If we don't have profileQueries it means there's no user profile saved in our DB
// In this case, let's disable the agent and delete any left-over data because there's nothing for it to do
if let profileQueries = try? DataBrokerProtectionManager.shared.dataManager.fetchBrokerProfileQueryData(ignoresCache: true),
profileQueries.count > 0 {
if let profileQueriesCount = try? DataBrokerProtectionManager.shared.dataManager.profileQueriesCount(),
profileQueriesCount > 0 {
restartBackgroundAgent(loginItemsManager: loginItemsManager)
} else {
featureVisibility.disableAndDeleteForWaitlistUsers()
Expand Down
2 changes: 1 addition & 1 deletion DuckDuckGo/DBP/DataBrokerProtectionDebugMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ final class DataBrokerProtectionDebugMenu: NSMenu {
os_log("Running scan operations...", log: .dataBrokerProtection)
let showWebView = sender.representedObject as? Bool ?? false

DataBrokerProtectionManager.shared.scheduler.startManualScan(showWebView: showWebView) { errors in
DataBrokerProtectionManager.shared.scheduler.startManualScan(showWebView: showWebView, startTime: Date()) { errors in
if let errors = errors {
if let oneTimeError = errors.oneTimeError {
os_log("scan operations finished, error: %{public}@", log: .dataBrokerProtection, oneTimeError.localizedDescription)
Expand Down
3 changes: 2 additions & 1 deletion DuckDuckGo/DBP/DataBrokerProtectionLoginItemScheduler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,10 @@ extension DataBrokerProtectionLoginItemScheduler: DataBrokerProtectionScheduler
}

func startManualScan(showWebView: Bool,
startTime: Date,
completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)?) {
enableLoginItem()
ipcScheduler.startManualScan(showWebView: showWebView, completion: completion)
ipcScheduler.startManualScan(showWebView: showWebView, startTime: startTime, completion: completion)
}

func startScheduler(showWebView: Bool) {
Expand Down
6 changes: 5 additions & 1 deletion DuckDuckGo/DBP/DataBrokerProtectionPixelsHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,11 @@ public class DataBrokerProtectionPixelsHandler: EventMapping<DataBrokerProtectio
.webUILoadingStarted,
.webUILoadingSuccess,
.emptyAccessTokenDaily,
.generateEmailHTTPErrorDaily:
.generateEmailHTTPErrorDaily,
.initialScanTotalDuration,
.initialScanSiteLoadDuration,
.initialScanPostLoadingDuration,
.initialScanPreStartDuration:
PixelKit.fire(event)

case .homeViewShowNoPermissionError,
Expand Down
3 changes: 2 additions & 1 deletion DuckDuckGoDBPBackgroundAgent/IPCServiceManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,10 @@ extension IPCServiceManager: IPCServerInterface {
}

func startManualScan(showWebView: Bool,
startTime: Date,
completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)) {
pixelHandler.fire(.ipcServerScanAllBrokersReceivedByAgent)
scheduler.startManualScan(showWebView: showWebView) { errors in
scheduler.startManualScan(showWebView: showWebView, startTime: startTime) { errors in
if let error = errors?.oneTimeError {
switch error {
case DataBrokerProtectionSchedulerError.operationsInterrupted:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,7 @@ public protocol DataBrokerProtectionDataManaging {
func fetchBrokerProfileQueryData(ignoresCache: Bool) throws -> [BrokerProfileQueryData]
func prepareBrokerProfileQueryDataCache() throws
func hasMatches() throws -> Bool
}

extension DataBrokerProtectionDataManaging {
func fetchBrokerProfileQueryData() throws -> [BrokerProfileQueryData] {
try fetchBrokerProfileQueryData(ignoresCache: false)
}
func profileQueriesCount() throws -> Int
}

public protocol DataBrokerProtectionDataManagerDelegate: AnyObject {
Expand Down Expand Up @@ -74,6 +69,18 @@ public class DataBrokerProtectionDataManager: DataBrokerProtectionDataManaging {
return cache.profile
}

return try fetchProfileFromDB()
}

public func profileQueriesCount() throws -> Int {
guard let profile = try fetchProfileFromDB() else {
throw DataBrokerProtectionError.dataNotInDatabase
}

return profile.profileQueries.count
}

private func fetchProfileFromDB() throws -> DataBrokerProtectionProfile? {
if let profile = try database.fetchProfile() {
cache.profile = profile
return profile
Expand Down Expand Up @@ -287,7 +294,7 @@ extension InMemoryDataCache: DBPUICommunicationDelegate {
}

func startScanAndOptOut() -> Bool {
return scanDelegate?.startScan() ?? false
return scanDelegate?.startScan(startDate: Date()) ?? false
}

func getInitialScanState() async -> DBPUIInitialScanState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ final class DataBrokerRunCustomJSONViewModel: ObservableObject {

private let runnerProvider: OperationRunnerProvider
private let privacyConfigManager: PrivacyConfigurationManaging
private let fakePixelHandler: EventMapping<DataBrokerProtectionPixels> = EventMapping { event, _, _, _ in
print(event)
}
private let contentScopeProperties: ContentScopeProperties
private let csvColumns = ["name_input", "age_input", "city_input", "state_input", "name_scraped", "age_scraped", "address_scraped", "relatives_scraped", "url", "broker name", "screenshot_id", "error", "matched_fields", "result_match", "expected_match"]

Expand Down Expand Up @@ -347,7 +350,7 @@ final class DataBrokerRunCustomJSONViewModel: ObservableObject {

Task {
do {
let extractedProfiles = try await runner.scan(query, stageCalculator: FakeStageDurationCalculator(), showWebView: true) { true }
let extractedProfiles = try await runner.scan(query, stageCalculator: FakeStageDurationCalculator(), pixelHandler: fakePixelHandler, showWebView: true) { true }

DispatchQueue.main.async {
for extractedProfile in extractedProfiles {
Expand Down Expand Up @@ -383,7 +386,7 @@ final class DataBrokerRunCustomJSONViewModel: ObservableObject {
)
Task {
do {
try await runner.optOut(profileQuery: brokerProfileQueryData, extractedProfile: scanResult.extractedProfile, stageCalculator: FakeStageDurationCalculator(), showWebView: true) {
try await runner.optOut(profileQuery: brokerProfileQueryData, extractedProfile: scanResult.extractedProfile, stageCalculator: FakeStageDurationCalculator(), pixelHandler: fakePixelHandler, showWebView: true) {
true
}

Expand Down Expand Up @@ -473,6 +476,7 @@ final class DataBrokerRunCustomJSONViewModel: ObservableObject {

final class FakeStageDurationCalculator: StageDurationCalculator {
var attemptId: UUID = UUID()
var isManualScan: Bool = false

func durationSinceLastStage() -> Double {
0.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ final class DebugScanOperation: DataBrokerOperation {
var scanURL: String?
let clickAwaitTime: TimeInterval
let cookieHandler: CookieHandler
let pixelHandler: EventMapping<DataBrokerProtectionPixels>
var postLoadingSiteStartTime: Date?

private let fileManager = FileManager.default
private let debugScanContentPath: String?
Expand Down Expand Up @@ -96,6 +98,9 @@ final class DebugScanOperation: DataBrokerOperation {
}
self.cookieHandler = EmptyCookieHandler()
stageCalculator = FakeStageDurationCalculator()
pixelHandler = EventMapping(mapping: { _, _, _, _ in
// We do not need the pixel handler for the debug
})
}

func run(inputValue: Void,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ extension DataBrokerProtectionIPCClient: IPCServerInterface {
}

public func startManualScan(showWebView: Bool,
startTime: Date,
completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)) {
self.pixelHandler.fire(.ipcServerScanAllBrokersCalledByApp)

Expand All @@ -155,7 +156,7 @@ extension DataBrokerProtectionIPCClient: IPCServerInterface {
}

xpc.execute(call: { server in
server.startManualScan(showWebView: showWebView) { errors in
server.startManualScan(showWebView: showWebView, startTime: startTime) { errors in
if let error = errors?.oneTimeError {
let nsError = error as NSError
let interruptedError = DataBrokerProtectionSchedulerError.operationsInterrupted as NSError
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ public final class DataBrokerProtectionIPCScheduler: DataBrokerProtectionSchedul
}

public func startManualScan(showWebView: Bool,
startTime: Date,
completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)?) {
let completion = completion ?? { _ in }
ipcClient.startManualScan(showWebView: showWebView, completion: completion)
ipcClient.startManualScan(showWebView: showWebView, startTime: startTime, completion: completion)
}

public func runQueuedOperations(showWebView: Bool,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ public protocol IPCServerInterface: AnyObject {
func optOutAllBrokers(showWebView: Bool,
completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void))
func startManualScan(showWebView: Bool,
startTime: Date,
completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void))
func runQueuedOperations(showWebView: Bool,
completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void))
Expand Down Expand Up @@ -136,6 +137,7 @@ protocol XPCServerInterface {
func optOutAllBrokers(showWebView: Bool,
completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void))
func startManualScan(showWebView: Bool,
startTime: Date,
completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void))
func runQueuedOperations(showWebView: Bool,
completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void))
Expand Down Expand Up @@ -214,8 +216,9 @@ extension DataBrokerProtectionIPCServer: XPCServerInterface {
}

func startManualScan(showWebView: Bool,
startTime: Date,
completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)) {
serverDelegate?.startManualScan(showWebView: showWebView, completion: completion)
serverDelegate?.startManualScan(showWebView: showWebView, startTime: startTime, completion: completion)
}

func runQueuedOperations(showWebView: Bool,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import BrowserServicesKit
import Common

protocol DBPUIScanOps: AnyObject {
func startScan() -> Bool
func startScan(startDate: Date) -> Bool
func updateCacheWithCurrentScans() async
func getBackgroundAgentMetadata() async -> DBPBackgroundAgentMetadata?
}
Expand Down Expand Up @@ -74,8 +74,8 @@ final class DBPUIViewModel {
}

extension DBPUIViewModel: DBPUIScanOps {
func startScan() -> Bool {
scheduler.startManualScan()
func startScan(startDate: Date) -> Bool {
scheduler.startManualScan(startTime: startDate)
return true
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ protocol DataBrokerOperation: CCFCommunicationDelegate {
var captchaService: CaptchaServiceProtocol { get }
var cookieHandler: CookieHandler { get }
var stageCalculator: StageDurationCalculator { get }
var pixelHandler: EventMapping<DataBrokerProtectionPixels> { get }

var webViewHandler: WebViewHandler? { get set }
var actionsHandler: ActionsHandler? { get }
Expand All @@ -41,6 +42,7 @@ protocol DataBrokerOperation: CCFCommunicationDelegate {
var shouldRunNextStep: () -> Bool { get }
var retriesCountOnError: Int { get set }
var clickAwaitTime: TimeInterval { get }
var postLoadingSiteStartTime: Date? { get set }

func run(inputValue: InputValue,
webViewHandler: WebViewHandler?,
Expand Down Expand Up @@ -151,11 +153,13 @@ extension DataBrokerOperation {
}

func complete(_ value: ReturnValue) {
self.firePostLoadingDurationPixel(hasError: false)
self.continuation?.resume(returning: value)
self.continuation = nil
}

func failed(with error: Error) {
self.firePostLoadingDurationPixel(hasError: true)
self.continuation?.resume(throwing: error)
self.continuation = nil
}
Expand All @@ -175,6 +179,8 @@ extension DataBrokerOperation {
// MARK: - CSSCommunicationDelegate

func loadURL(url: URL) async {
let webSiteStartLoadingTime = Date()

do {
// https://app.asana.com/0/1204167627774280/1206912494469284/f
if query.dataBroker.url == "spokeo.com" {
Expand All @@ -183,12 +189,31 @@ extension DataBrokerOperation {
}
}
try await webViewHandler?.load(url: url)
fireSiteLoadingPixel(startTime: webSiteStartLoadingTime, hasError: false)
postLoadingSiteStartTime = Date()
await executeNextStep()
} catch {
fireSiteLoadingPixel(startTime: webSiteStartLoadingTime, hasError: true)
await onError(error: error)
}
}

private func fireSiteLoadingPixel(startTime: Date, hasError: Bool) {
if stageCalculator.isManualScan {
let dataBrokerURL = self.query.dataBroker.url
let durationInMs = (Date().timeIntervalSince(startTime) * 1000).rounded(.towardZero)
pixelHandler.fire(.initialScanSiteLoadDuration(duration: durationInMs, hasError: hasError, brokerURL: dataBrokerURL))
}
}

func firePostLoadingDurationPixel(hasError: Bool) {
if stageCalculator.isManualScan, let postLoadingSiteStartTime = self.postLoadingSiteStartTime {
let dataBrokerURL = self.query.dataBroker.url
let durationInMs = (Date().timeIntervalSince(postLoadingSiteStartTime) * 1000).rounded(.towardZero)
pixelHandler.fire(.initialScanPostLoadingDuration(duration: durationInMs, hasError: hasError, brokerURL: dataBrokerURL))
}
}

func success(actionId: String, actionType: ActionType) async {
switch actionType {
case .click:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ protocol WebOperationRunner {

func scan(_ profileQuery: BrokerProfileQueryData,
stageCalculator: StageDurationCalculator,
pixelHandler: EventMapping<DataBrokerProtectionPixels>,
showWebView: Bool,
shouldRunNextStep: @escaping () -> Bool) async throws -> [ExtractedProfile]

func optOut(profileQuery: BrokerProfileQueryData,
extractedProfile: ExtractedProfile,
stageCalculator: StageDurationCalculator,
pixelHandler: EventMapping<DataBrokerProtectionPixels>,
showWebView: Bool,
shouldRunNextStep: @escaping () -> Bool) async throws
}
Expand All @@ -38,22 +40,26 @@ extension WebOperationRunner {

func scan(_ profileQuery: BrokerProfileQueryData,
stageCalculator: StageDurationCalculator,
pixelHandler: EventMapping<DataBrokerProtectionPixels>,
shouldRunNextStep: @escaping () -> Bool) async throws -> [ExtractedProfile] {

try await scan(profileQuery,
stageCalculator: stageCalculator,
pixelHandler: pixelHandler,
showWebView: false,
shouldRunNextStep: shouldRunNextStep)
}

func optOut(profileQuery: BrokerProfileQueryData,
extractedProfile: ExtractedProfile,
stageCalculator: StageDurationCalculator,
pixelHandler: EventMapping<DataBrokerProtectionPixels>,
shouldRunNextStep: @escaping () -> Bool) async throws {

try await optOut(profileQuery: profileQuery,
extractedProfile: extractedProfile,
stageCalculator: stageCalculator,
pixelHandler: pixelHandler,
showWebView: false,
shouldRunNextStep: shouldRunNextStep)
}
Expand All @@ -78,6 +84,7 @@ final class DataBrokerOperationRunner: WebOperationRunner {

func scan(_ profileQuery: BrokerProfileQueryData,
stageCalculator: StageDurationCalculator,
pixelHandler: EventMapping<DataBrokerProtectionPixels>,
showWebView: Bool,
shouldRunNextStep: @escaping () -> Bool) async throws -> [ExtractedProfile] {
let scan = ScanOperation(
Expand All @@ -87,6 +94,7 @@ final class DataBrokerOperationRunner: WebOperationRunner {
emailService: emailService,
captchaService: captchaService,
stageDurationCalculator: stageCalculator,
pixelHandler: pixelHandler,
shouldRunNextStep: shouldRunNextStep
)
return try await scan.run(inputValue: (), showWebView: showWebView)
Expand All @@ -95,6 +103,7 @@ final class DataBrokerOperationRunner: WebOperationRunner {
func optOut(profileQuery: BrokerProfileQueryData,
extractedProfile: ExtractedProfile,
stageCalculator: StageDurationCalculator,
pixelHandler: EventMapping<DataBrokerProtectionPixels>,
showWebView: Bool,
shouldRunNextStep: @escaping () -> Bool) async throws {
let optOut = OptOutOperation(
Expand All @@ -104,6 +113,7 @@ final class DataBrokerOperationRunner: WebOperationRunner {
emailService: emailService,
captchaService: captchaService,
stageCalculator: stageCalculator,
pixelHandler: pixelHandler,
shouldRunNextStep: shouldRunNextStep
)
try await optOut.run(inputValue: extractedProfile, showWebView: showWebView)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ final class DataBrokerOperationsCollection: Operation {
runner: runner,
pixelHandler: pixelHandler,
showWebView: showWebView,
isManualScan: operationType == .scan,
userNotificationService: userNotificationService,
shouldRunNextStep: { [weak self] in
guard let self = self else { return false }
Expand Down
Loading

0 comments on commit 79d648d

Please sign in to comment.