From 2490e1580e8d99041162ed429c0f39fe1a24387f Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 4 Apr 2024 13:41:29 +0100 Subject: [PATCH 01/31] DailyPixels moved to PixelKit --- DuckDuckGo.xcodeproj/project.pbxproj | 91 +- DuckDuckGo/Application/AppDelegate.swift | 52 +- ...okerProtectionExternalWaitlistPixels.swift | 11 +- .../DataBrokerProtectionLoginItemPixels.swift | 11 +- .../Model/HomePageContinueSetUpModel.swift | 12 +- .../View/HomePageViewController.swift | 6 +- DuckDuckGo/LoginItems/LoginItemsManager.swift | 18 +- .../MainWindow/MainViewController.swift | 3 +- .../NavigationBar/View/MoreOptionsMenu.swift | 3 +- .../View/NavigationBarViewController.swift | 5 +- .../NetworkProtectionNavBarButtonModel.swift | 5 +- .../VPNLocation/VPNLocationViewModel.swift | 7 +- .../Model/DefaultBrowserPreferences.swift | 6 +- .../View/PreferencesRootView.swift | 25 +- .../SmarterEncryption/PrivacyFeatures.swift | 20 +- DuckDuckGo/Statistics/GeneralPixel.swift | 874 ++++++++++++++++++ .../{ => Pixel Legacy}/DailyPixel.swift | 0 .../Statistics/{ => Pixel Legacy}/Pixel.swift | 0 .../{ => Pixel Legacy}/PixelArguments.swift | 0 .../PixelDataModel.xcdatamodel/contents | 0 .../{ => Pixel Legacy}/PixelDataRecord.swift | 0 .../{ => Pixel Legacy}/PixelDataStore.swift | 0 .../{ => Pixel Legacy}/PixelEvent.swift | 0 .../{ => Pixel Legacy}/PixelParameters.swift | 0 DuckDuckGo/Statistics/PrivacyProPixel.swift | 128 +++ DuckDuckGo/Tab/Model/Tab.swift | 3 +- .../SubscriptionAppStoreRestorer.swift | 6 +- .../SubscriptionErrorReporter.swift | 9 +- .../SubscriptionPagesUserScript.swift | 35 +- .../WaitlistThankYouPromptPresenter.swift | 7 +- DuckDuckGo/Waitlist/Waitlist.swift | 3 +- ...tlistTermsAndConditionsActionHandler.swift | 10 +- .../YoutubeOverlayUserScript.swift | 11 +- .../PixelKit/Extensions/URL+PixelKit.swift | 2 +- .../PixelKit/Sources/PixelKit/PixelKit.swift | 8 +- .../Sources/PixelKit/PixelKitEvent.swift | 8 + .../Sources/PixelKit/PixelKitEventV2.swift | 2 +- 37 files changed, 1177 insertions(+), 204 deletions(-) create mode 100644 DuckDuckGo/Statistics/GeneralPixel.swift rename DuckDuckGo/Statistics/{ => Pixel Legacy}/DailyPixel.swift (100%) rename DuckDuckGo/Statistics/{ => Pixel Legacy}/Pixel.swift (100%) rename DuckDuckGo/Statistics/{ => Pixel Legacy}/PixelArguments.swift (100%) rename DuckDuckGo/Statistics/{ => Pixel Legacy}/PixelDataModel.xcdatamodeld/PixelDataModel.xcdatamodel/contents (100%) rename DuckDuckGo/Statistics/{ => Pixel Legacy}/PixelDataRecord.swift (100%) rename DuckDuckGo/Statistics/{ => Pixel Legacy}/PixelDataStore.swift (100%) rename DuckDuckGo/Statistics/{ => Pixel Legacy}/PixelEvent.swift (100%) rename DuckDuckGo/Statistics/{ => Pixel Legacy}/PixelParameters.swift (100%) create mode 100644 DuckDuckGo/Statistics/PrivacyProPixel.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 2c93efafdd..8502815745 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -285,7 +285,6 @@ 3706FAB2293F65D500E42796 /* TabInstrumentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB88B4F25B7BA2B006F6B06 /* TabInstrumentation.swift */; }; 3706FAB5293F65D500E42796 /* ConfigurationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D33F1125C82EB3002B91A6 /* ConfigurationManager.swift */; }; 3706FAB6293F65D500E42796 /* YoutubePlayerUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31F28C4C28C8EEC500119F70 /* YoutubePlayerUserScript.swift */; }; - 3706FAB7293F65D500E42796 /* PixelParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E48326146AAB0067D1B9 /* PixelParameters.swift */; }; 3706FAB8293F65D500E42796 /* FaviconImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5FA696275F90C400DCE9C9 /* FaviconImageCache.swift */; }; 3706FAB9293F65D500E42796 /* TabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1430DFF424D0580F00B8978C /* TabBarViewController.swift */; }; 3706FABA293F65D500E42796 /* BookmarkOutlineViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92929126670D2A00AD2C21 /* BookmarkOutlineViewDataSource.swift */; }; @@ -423,7 +422,6 @@ 3706FB60293F65D500E42796 /* PasswordManagementIdentityItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE6546E271FCD40008D1D63 /* PasswordManagementIdentityItemView.swift */; }; 3706FB61293F65D500E42796 /* ProgressExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F41030264D2B23003DA42C /* ProgressExtension.swift */; }; 3706FB62293F65D500E42796 /* CSVParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B723DF626B0002B00E14D75 /* CSVParser.swift */; }; - 3706FB64293F65D500E42796 /* PixelDataModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44062616B30600DD1EC2 /* PixelDataModel.xcdatamodeld */; }; 3706FB65293F65D500E42796 /* PrivacyDashboardWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B63BDF7D27FDAA640072D75B /* PrivacyDashboardWebView.swift */; }; 3706FB66293F65D500E42796 /* AppearancePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CD54C727F2FDD100F1F7B9 /* AppearancePreferences.swift */; }; 3706FB67293F65D500E42796 /* DownloadListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B1E87A26D381710062C350 /* DownloadListCoordinator.swift */; }; @@ -542,9 +540,6 @@ 3706FBF0293F65D500E42796 /* PasswordManagementItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85CC1D7C26A05F250062F04E /* PasswordManagementItemModel.swift */; }; 3706FBF2293F65D500E42796 /* FindInPageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85A0118125AF60E700FA6A0C /* FindInPageModel.swift */; }; 3706FBF3293F65D500E42796 /* PseudoFolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92929826670D2A00AD2C21 /* PseudoFolder.swift */; }; - 3706FBF5293F65D500E42796 /* PixelDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */; }; - 3706FBF6293F65D500E42796 /* Pixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E45226142B070067D1B9 /* Pixel.swift */; }; - 3706FBF7293F65D500E42796 /* PixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E47626146A570067D1B9 /* PixelEvent.swift */; }; 3706FBF8293F65D500E42796 /* TabBarFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2CB1342587C29500AA6FBE /* TabBarFooter.swift */; }; 3706FBF9293F65D500E42796 /* BookmarksBarCollectionViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE5336A286912D40019DBFD /* BookmarksBarCollectionViewItem.swift */; }; 3706FBFA293F65D500E42796 /* FileDownloadError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C0B23826E742610031CB7F /* FileDownloadError.swift */; }; @@ -610,7 +605,6 @@ 3706FC3F293F65D500E42796 /* ApplicationDockMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA97BF4525135DD30014931A /* ApplicationDockMenu.swift */; }; 3706FC40293F65D500E42796 /* SaveIdentityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8A4DFE27C83B29005F40E8 /* SaveIdentityViewController.swift */; }; 3706FC41293F65D500E42796 /* FileStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BA1A69A258B076900F6F690 /* FileStore.swift */; }; - 3706FC42293F65D500E42796 /* PixelArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E47E26146A800067D1B9 /* PixelArguments.swift */; }; 3706FC43293F65D500E42796 /* PinnedTabsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BF3F1E286F0A7A00BD9014 /* PinnedTabsViewModel.swift */; }; 3706FC44293F65D500E42796 /* BookmarkList.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4CF25D6A709007F5990 /* BookmarkList.swift */; }; 3706FC45293F65D500E42796 /* BookmarkTableRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292C92667123700AD2C21 /* BookmarkTableRowView.swift */; }; @@ -652,7 +646,6 @@ 3706FC6F293F65D500E42796 /* FirePopoverViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA13DCB3271480B0006D48D3 /* FirePopoverViewModel.swift */; }; 3706FC71293F65D500E42796 /* NSColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41D174025CB131900472416 /* NSColorExtension.swift */; }; 3706FC73293F65D500E42796 /* AddressBarButtonsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4F525D6BF2C007F5990 /* AddressBarButtonsViewController.swift */; }; - 3706FC76293F65D500E42796 /* PixelDataRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */; }; 3706FC77293F65D500E42796 /* PageObserverUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 853014D525E671A000FB8205 /* PageObserverUserScript.swift */; }; 3706FC78293F65D500E42796 /* SecureVaultErrorReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B642738127B65BAC0005DFD1 /* SecureVaultErrorReporter.swift */; }; 3706FC79293F65D500E42796 /* NSImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B139AFC26B60BD800894F82 /* NSImageExtensions.swift */; }; @@ -1258,8 +1251,6 @@ 4B677433255DBEB800025BD8 /* httpsMobileV2Bloom.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B677428255DBEB800025BD8 /* httpsMobileV2Bloom.bin */; }; 4B677435255DBEB800025BD8 /* httpsMobileV2FalsePositives.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B67742A255DBEB800025BD8 /* httpsMobileV2FalsePositives.json */; }; 4B677442255DBEEA00025BD8 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B677440255DBEEA00025BD8 /* Database.swift */; }; - 4B67853F2AA7C726008A5004 /* DailyPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B67853E2AA7C726008A5004 /* DailyPixel.swift */; }; - 4B6785402AA7C726008A5004 /* DailyPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B67853E2AA7C726008A5004 /* DailyPixel.swift */; }; 4B6785472AA8DE68008A5004 /* NetworkProtectionFeatureDisabler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6785432AA8DE1F008A5004 /* NetworkProtectionFeatureDisabler.swift */; }; 4B6785482AA8DE69008A5004 /* NetworkProtectionFeatureDisabler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6785432AA8DE1F008A5004 /* NetworkProtectionFeatureDisabler.swift */; }; 4B67854A2AA8DE75008A5004 /* NetworkProtectionFeatureVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD8679A2A9E9E000063B9F7 /* NetworkProtectionFeatureVisibility.swift */; }; @@ -1395,7 +1386,6 @@ 4B9579842AC7AE700062CA31 /* TabInstrumentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB88B4F25B7BA2B006F6B06 /* TabInstrumentation.swift */; }; 4B9579872AC7AE700062CA31 /* ConfigurationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D33F1125C82EB3002B91A6 /* ConfigurationManager.swift */; }; 4B9579882AC7AE700062CA31 /* YoutubePlayerUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31F28C4C28C8EEC500119F70 /* YoutubePlayerUserScript.swift */; }; - 4B9579892AC7AE700062CA31 /* PixelParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E48326146AAB0067D1B9 /* PixelParameters.swift */; }; 4B95798B2AC7AE700062CA31 /* FaviconImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5FA696275F90C400DCE9C9 /* FaviconImageCache.swift */; }; 4B95798C2AC7AE700062CA31 /* TabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1430DFF424D0580F00B8978C /* TabBarViewController.swift */; }; 4B95798D2AC7AE700062CA31 /* BookmarkOutlineViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92929126670D2A00AD2C21 /* BookmarkOutlineViewDataSource.swift */; }; @@ -1588,7 +1578,6 @@ 4B957A582AC7AE700062CA31 /* PasswordManagementIdentityItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE6546E271FCD40008D1D63 /* PasswordManagementIdentityItemView.swift */; }; 4B957A592AC7AE700062CA31 /* ProgressExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F41030264D2B23003DA42C /* ProgressExtension.swift */; }; 4B957A5A2AC7AE700062CA31 /* CSVParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B723DF626B0002B00E14D75 /* CSVParser.swift */; }; - 4B957A5B2AC7AE700062CA31 /* PixelDataModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44062616B30600DD1EC2 /* PixelDataModel.xcdatamodeld */; }; 4B957A5C2AC7AE700062CA31 /* PrivacyDashboardWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B63BDF7D27FDAA640072D75B /* PrivacyDashboardWebView.swift */; }; 4B957A5D2AC7AE700062CA31 /* AppearancePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CD54C727F2FDD100F1F7B9 /* AppearancePreferences.swift */; }; 4B957A5E2AC7AE700062CA31 /* DownloadListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B1E87A26D381710062C350 /* DownloadListCoordinator.swift */; }; @@ -1730,7 +1719,6 @@ 4B957AEF2AC7AE700062CA31 /* String+Punycode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65783E625F8AAFB00D8DB33 /* String+Punycode.swift */; }; 4B957AF02AC7AE700062CA31 /* NSException+Catch.m in Sources */ = {isa = PBXBuildFile; fileRef = B657841925FA484B00D8DB33 /* NSException+Catch.m */; }; 4B957AF12AC7AE700062CA31 /* AppStateRestorationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B684592E25C93FBF00DC17B6 /* AppStateRestorationManager.swift */; }; - 4B957AF22AC7AE700062CA31 /* DailyPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B67853E2AA7C726008A5004 /* DailyPixel.swift */; }; 4B957AF32AC7AE700062CA31 /* NavigationHotkeyHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66260E529ACAE4B00E9E3EE /* NavigationHotkeyHandler.swift */; }; 4B957AF42AC7AE700062CA31 /* ClickToLoadUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0BA3A8272217E6002A0B6C /* ClickToLoadUserScript.swift */; }; 4B957AF52AC7AE700062CA31 /* WindowControllersManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAA892E9250A4CEF005B37B2 /* WindowControllersManager.swift */; }; @@ -1750,10 +1738,7 @@ 4B957B042AC7AE700062CA31 /* UpdateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD86E51267A0DFF005C11BE /* UpdateController.swift */; }; 4B957B052AC7AE700062CA31 /* FindInPageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85A0118125AF60E700FA6A0C /* FindInPageModel.swift */; }; 4B957B062AC7AE700062CA31 /* PseudoFolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92929826670D2A00AD2C21 /* PseudoFolder.swift */; }; - 4B957B082AC7AE700062CA31 /* PixelDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */; }; 4B957B092AC7AE700062CA31 /* WaitlistStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB00E2A983B24000927DB /* WaitlistStorage.swift */; }; - 4B957B0A2AC7AE700062CA31 /* Pixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E45226142B070067D1B9 /* Pixel.swift */; }; - 4B957B0B2AC7AE700062CA31 /* PixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E47626146A570067D1B9 /* PixelEvent.swift */; }; 4B957B0C2AC7AE700062CA31 /* TabBarFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2CB1342587C29500AA6FBE /* TabBarFooter.swift */; }; 4B957B0D2AC7AE700062CA31 /* JSAlertViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEC111E5294D06290086524F /* JSAlertViewModel.swift */; }; 4B957B0E2AC7AE700062CA31 /* BookmarksBarCollectionViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE5336A286912D40019DBFD /* BookmarksBarCollectionViewItem.swift */; }; @@ -1831,7 +1816,6 @@ 4B957B582AC7AE700062CA31 /* SaveIdentityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8A4DFE27C83B29005F40E8 /* SaveIdentityViewController.swift */; }; 4B957B592AC7AE700062CA31 /* AppLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD7A6E2A1D3E1F002A24E7 /* AppLauncher.swift */; }; 4B957B5A2AC7AE700062CA31 /* FileStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BA1A69A258B076900F6F690 /* FileStore.swift */; }; - 4B957B5B2AC7AE700062CA31 /* PixelArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E47E26146A800067D1B9 /* PixelArguments.swift */; }; 4B957B5C2AC7AE700062CA31 /* PinnedTabsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BF3F1E286F0A7A00BD9014 /* PinnedTabsViewModel.swift */; }; 4B957B5D2AC7AE700062CA31 /* BookmarkList.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4CF25D6A709007F5990 /* BookmarkList.swift */; }; 4B957B5E2AC7AE700062CA31 /* NEOnDemandRuleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B602E81C2A1E25B0006D261F /* NEOnDemandRuleExtension.swift */; }; @@ -1888,7 +1872,6 @@ 4B957B952AC7AE700062CA31 /* NSColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41D174025CB131900472416 /* NSColorExtension.swift */; }; 4B957B972AC7AE700062CA31 /* AddressBarButtonsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4F525D6BF2C007F5990 /* AddressBarButtonsViewController.swift */; }; 4B957B982AC7AE700062CA31 /* BWError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDF076028F815AD00EDFBE3 /* BWError.swift */; }; - 4B957B9A2AC7AE700062CA31 /* PixelDataRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */; }; 4B957B9B2AC7AE700062CA31 /* PageObserverUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 853014D525E671A000FB8205 /* PageObserverUserScript.swift */; }; 4B957B9C2AC7AE700062CA31 /* SecureVaultErrorReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B642738127B65BAC0005DFD1 /* SecureVaultErrorReporter.swift */; }; 4B957B9D2AC7AE700062CA31 /* NSImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B139AFC26B60BD800894F82 /* NSImageExtensions.swift */; }; @@ -2957,7 +2940,6 @@ B689ECD526C247DB006FB0C5 /* BackForwardListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B689ECD426C247DB006FB0C5 /* BackForwardListItem.swift */; }; B68C2FB227706E6A00BF2C7D /* ProcessExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C2FB127706E6A00BF2C7D /* ProcessExtension.swift */; }; B68C92C1274E3EF4002AC6B0 /* PopUpWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C0274E3EF4002AC6B0 /* PopUpWindow.swift */; }; - B68C92C42750EF76002AC6B0 /* PixelDataRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */; }; B68D21C32ACBC916002DA3C2 /* ContentBlockingMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BDD9F429409DDD00F68088 /* ContentBlockingMock.swift */; }; B68D21C42ACBC917002DA3C2 /* ContentBlockingMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BDD9F429409DDD00F68088 /* ContentBlockingMock.swift */; }; B68D21C82ACBC96D002DA3C2 /* MockPrivacyConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B626A76F29928C4100053070 /* MockPrivacyConfiguration.swift */; }; @@ -3025,11 +3007,7 @@ B6A5A2A025B96E8300AA7ADA /* AppStateChangePublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A5A29F25B96E8300AA7ADA /* AppStateChangePublisherTests.swift */; }; B6A5A2A825BAA35500AA7ADA /* WindowManagerStateRestorationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A5A2A725BAA35500AA7ADA /* WindowManagerStateRestorationTests.swift */; }; B6A924D92664C72E001A28CA /* WebKitDownloadTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A924D82664C72D001A28CA /* WebKitDownloadTask.swift */; }; - B6A9E45326142B070067D1B9 /* Pixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E45226142B070067D1B9 /* Pixel.swift */; }; B6A9E46B2614618A0067D1B9 /* OperatingSystemVersionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E46A2614618A0067D1B9 /* OperatingSystemVersionExtension.swift */; }; - B6A9E47726146A570067D1B9 /* PixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E47626146A570067D1B9 /* PixelEvent.swift */; }; - B6A9E47F26146A800067D1B9 /* PixelArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E47E26146A800067D1B9 /* PixelArguments.swift */; }; - B6A9E48426146AAB0067D1B9 /* PixelParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E48326146AAB0067D1B9 /* PixelParameters.swift */; }; B6AA64732994B43300D99CD6 /* FutureExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AA64722994B43300D99CD6 /* FutureExtensionTests.swift */; }; B6AA64742994B43300D99CD6 /* FutureExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AA64722994B43300D99CD6 /* FutureExtensionTests.swift */; }; B6AAAC2D260330580029438D /* PublishedAfter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AAAC2C260330580029438D /* PublishedAfter.swift */; }; @@ -3136,8 +3114,6 @@ B6DA06E42913ECEE00225DE2 /* ContextMenuManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA06E32913ECEE00225DE2 /* ContextMenuManager.swift */; }; B6DA06E62913F39400225DE2 /* MenuItemSelectors.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA06E52913F39400225DE2 /* MenuItemSelectors.swift */; }; B6DA06E8291401D700225DE2 /* WKMenuItemIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA06E7291401D700225DE2 /* WKMenuItemIdentifier.swift */; }; - B6DA44022616B28300DD1EC2 /* PixelDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */; }; - B6DA44082616B30600DD1EC2 /* PixelDataModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44062616B30600DD1EC2 /* PixelDataModel.xcdatamodeld */; }; B6DA44112616C0FC00DD1EC2 /* PixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44102616C0FC00DD1EC2 /* PixelTests.swift */; }; B6DA44172616C13800DD1EC2 /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = B6DA44162616C13800DD1EC2 /* OHHTTPStubs */; }; B6DA44192616C13800DD1EC2 /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = B6DA44182616C13800DD1EC2 /* OHHTTPStubsSwift */; }; @@ -3307,6 +3283,12 @@ EEDE50122BA360C80017F3C4 /* NetworkProtection+VPNAgentConvenienceInitializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEDE50102BA360C80017F3C4 /* NetworkProtection+VPNAgentConvenienceInitializers.swift */; }; EEF12E6F2A2111880023E6BF /* MacPacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF12E6D2A2111880023E6BF /* MacPacketTunnelProvider.swift */; }; EEF53E182950CED5002D78F4 /* JSAlertViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */; }; + F188267C2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */; }; + F188267D2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */; }; + F188267E2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */; }; + F18826802BBEB58100D9AC4F /* PrivacyProPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */; }; + F18826812BBEB58100D9AC4F /* PrivacyProPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */; }; + F18826822BBEB58100D9AC4F /* PrivacyProPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */; }; F1B33DF22BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */; }; F1B33DF32BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */; }; F1B33DF42BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */; }; @@ -4746,6 +4728,8 @@ EEDE50102BA360C80017F3C4 /* NetworkProtection+VPNAgentConvenienceInitializers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NetworkProtection+VPNAgentConvenienceInitializers.swift"; sourceTree = ""; }; EEF12E6D2A2111880023E6BF /* MacPacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacPacketTunnelProvider.swift; sourceTree = ""; }; EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSAlertViewModelTests.swift; sourceTree = ""; }; + F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPixel.swift; sourceTree = ""; }; + F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyProPixel.swift; sourceTree = ""; }; F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionAppStoreRestorer.swift; sourceTree = ""; }; F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionErrorReporter.swift; sourceTree = ""; }; F1D43AED2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MainMenuActions+VanillaBrowser.swift"; sourceTree = ""; }; @@ -8489,17 +8473,12 @@ B6A9E44E26142AF90067D1B9 /* Statistics */ = { isa = PBXGroup; children = ( - 857E5AF32A79044900FC0FB4 /* Experiment */, B69B50332726A10700758A2B /* ATB */, - B6A9E45226142B070067D1B9 /* Pixel.swift */, - 4B67853E2AA7C726008A5004 /* DailyPixel.swift */, - B6A9E47626146A570067D1B9 /* PixelEvent.swift */, - B6A9E47E26146A800067D1B9 /* PixelArguments.swift */, - B6A9E48326146AAB0067D1B9 /* PixelParameters.swift */, + 857E5AF32A79044900FC0FB4 /* Experiment */, + F18826712BBEAF9200D9AC4F /* Pixel Legacy */, B610F2BA27A145C500FCEBE9 /* RulesCompilationMonitor.swift */, - B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */, - B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */, - B6DA44062616B30600DD1EC2 /* PixelDataModel.xcdatamodeld */, + F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */, + F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */, ); path = Statistics; sourceTree = ""; @@ -8787,6 +8766,21 @@ path = JSAlert; sourceTree = ""; }; + F18826712BBEAF9200D9AC4F /* Pixel Legacy */ = { + isa = PBXGroup; + children = ( + B6A9E45226142B070067D1B9 /* Pixel.swift */, + 4B67853E2AA7C726008A5004 /* DailyPixel.swift */, + B6A9E47626146A570067D1B9 /* PixelEvent.swift */, + B6A9E47E26146A800067D1B9 /* PixelArguments.swift */, + B6A9E48326146AAB0067D1B9 /* PixelParameters.swift */, + B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */, + B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */, + B6DA44062616B30600DD1EC2 /* PixelDataModel.xcdatamodeld */, + ); + path = "Pixel Legacy"; + sourceTree = ""; + }; F1B33DF92BAD9C83001128B3 /* Subscription */ = { isa = PBXGroup; children = ( @@ -10269,7 +10263,6 @@ 3706FAB6293F65D500E42796 /* YoutubePlayerUserScript.swift in Sources */, 1D8057C92A83CB3C00F4FED6 /* SupportedOsChecker.swift in Sources */, 373D9B4929EEAC1B00381FDD /* SyncMetadataDatabase.swift in Sources */, - 3706FAB7293F65D500E42796 /* PixelParameters.swift in Sources */, 3706FAB8293F65D500E42796 /* FaviconImageCache.swift in Sources */, 3706FAB9293F65D500E42796 /* TabBarViewController.swift in Sources */, 4B9DB0332A983B24000927DB /* EnableWaitlistFeatureView.swift in Sources */, @@ -10405,6 +10398,7 @@ 3706FB1B293F65D500E42796 /* SafariDataImporter.swift in Sources */, 3706FB1D293F65D500E42796 /* StatisticsLoader.swift in Sources */, 3793FDD829535EBA00A2E28F /* Assertions.swift in Sources */, + F188267D2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */, B62B48572ADE730D000DECE5 /* FileImportView.swift in Sources */, B6676BE22AA986A700525A21 /* AddressBarTextEditor.swift in Sources */, 3706FB1F293F65D500E42796 /* DataClearingPreferences.swift in Sources */, @@ -10488,7 +10482,6 @@ 3706FB60293F65D500E42796 /* PasswordManagementIdentityItemView.swift in Sources */, 3706FB61293F65D500E42796 /* ProgressExtension.swift in Sources */, 3706FB62293F65D500E42796 /* CSVParser.swift in Sources */, - 3706FB64293F65D500E42796 /* PixelDataModel.xcdatamodeld in Sources */, B626A75B29921FAA00053070 /* NavigationActionPolicyExtension.swift in Sources */, 4B9DB02A2A983B24000927DB /* WaitlistStorage.swift in Sources */, 3706FB65293F65D500E42796 /* PrivacyDashboardWebView.swift in Sources */, @@ -10548,6 +10541,7 @@ 3706FB85293F65D500E42796 /* AddBookmarkPopover.swift in Sources */, 7BBD45B22A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift in Sources */, 3706FB87293F65D500E42796 /* ProcessExtension.swift in Sources */, + F18826812BBEB58100D9AC4F /* PrivacyProPixel.swift in Sources */, 3706FB88293F65D500E42796 /* PermissionAuthorizationQuery.swift in Sources */, 3706FB89293F65D500E42796 /* BadgeAnimationView.swift in Sources */, 4B4D60C32A0C849100BCD287 /* EventMapping+NetworkProtectionError.swift in Sources */, @@ -10704,10 +10698,7 @@ 9F56CFAE2B84326C00BB7F11 /* AddEditBookmarkDialogViewModel.swift in Sources */, 3706FBF3293F65D500E42796 /* PseudoFolder.swift in Sources */, 1D26EBAD2B74BECB0002A93F /* NSImageSendable.swift in Sources */, - 3706FBF5293F65D500E42796 /* PixelDataStore.swift in Sources */, 1D220BFD2B87AACF00F8BBC6 /* PrivacyProtectionStatus.swift in Sources */, - 3706FBF6293F65D500E42796 /* Pixel.swift in Sources */, - 3706FBF7293F65D500E42796 /* PixelEvent.swift in Sources */, 3706FBF8293F65D500E42796 /* TabBarFooter.swift in Sources */, B626A7612992407D00053070 /* CancellableExtension.swift in Sources */, 3706FBF9293F65D500E42796 /* BookmarksBarCollectionViewItem.swift in Sources */, @@ -10733,7 +10724,6 @@ 1DCFBC8B29ADF32B00313531 /* BurnerHomePageView.swift in Sources */, 3706FC06293F65D500E42796 /* OnboardingViewModel.swift in Sources */, 3706FC07293F65D500E42796 /* ScriptSourceProviding.swift in Sources */, - 4B6785402AA7C726008A5004 /* DailyPixel.swift in Sources */, 31EF1E832B63FFCA00E6DB17 /* LoginItem+DataBrokerProtection.swift in Sources */, B6619EFC2B111CC600CD9186 /* InstructionsFormatParser.swift in Sources */, 3706FC08293F65D500E42796 /* CoreDataBookmarkImporter.swift in Sources */, @@ -10818,7 +10808,6 @@ 3706FC40293F65D500E42796 /* SaveIdentityViewController.swift in Sources */, 3706FC41293F65D500E42796 /* FileStore.swift in Sources */, 1DB67F2E2B6FEFDB003DF243 /* ViewSnapshotRenderer.swift in Sources */, - 3706FC42293F65D500E42796 /* PixelArguments.swift in Sources */, 3706FC43293F65D500E42796 /* PinnedTabsViewModel.swift in Sources */, 85D0327C2B8E3D090041D1FB /* HistoryCoordinatorExtension.swift in Sources */, B6685E4329A61C470043D2EE /* DownloadsTabExtension.swift in Sources */, @@ -10888,7 +10877,6 @@ 3706FC71293F65D500E42796 /* NSColorExtension.swift in Sources */, 1DB9618229F67F6100CF5568 /* FaviconNullStore.swift in Sources */, 3706FC73293F65D500E42796 /* AddressBarButtonsViewController.swift in Sources */, - 3706FC76293F65D500E42796 /* PixelDataRecord.swift in Sources */, 7BFE955A2A9DF4550081ABE9 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift in Sources */, 9FDA6C222B79A59D00E099A9 /* BookmarkFavoriteView.swift in Sources */, 3706FC77293F65D500E42796 /* PageObserverUserScript.swift in Sources */, @@ -11507,7 +11495,6 @@ 4B9579842AC7AE700062CA31 /* TabInstrumentation.swift in Sources */, 4B9579872AC7AE700062CA31 /* ConfigurationManager.swift in Sources */, 4B9579882AC7AE700062CA31 /* YoutubePlayerUserScript.swift in Sources */, - 4B9579892AC7AE700062CA31 /* PixelParameters.swift in Sources */, 4B95798B2AC7AE700062CA31 /* FaviconImageCache.swift in Sources */, 4B95798C2AC7AE700062CA31 /* TabBarViewController.swift in Sources */, 4B95798D2AC7AE700062CA31 /* BookmarkOutlineViewDataSource.swift in Sources */, @@ -11626,6 +11613,7 @@ 4B9579EF2AC7AE700062CA31 /* ProductWaitlistRequest.swift in Sources */, 7BEC20442B0F505F00243D3E /* AddBookmarkPopoverView.swift in Sources */, 4B9579F02AC7AE700062CA31 /* Bookmark.xcdatamodeld in Sources */, + F18826822BBEB58100D9AC4F /* PrivacyProPixel.swift in Sources */, 4B9579F12AC7AE700062CA31 /* DefaultBrowserPromptView.swift in Sources */, 4B9579F22AC7AE700062CA31 /* WaitlistActivationDateStore.swift in Sources */, 4B9579F42AC7AE700062CA31 /* FaviconManager.swift in Sources */, @@ -11747,7 +11735,6 @@ 4B957A592AC7AE700062CA31 /* ProgressExtension.swift in Sources */, 4B44FEF52B1FEF5A000619D8 /* FocusableTextEditor.swift in Sources */, 4B957A5A2AC7AE700062CA31 /* CSVParser.swift in Sources */, - 4B957A5B2AC7AE700062CA31 /* PixelDataModel.xcdatamodeld in Sources */, 4B957A5C2AC7AE700062CA31 /* PrivacyDashboardWebView.swift in Sources */, B6656E5B2B2ADB1C008798A1 /* RequestFilePermissionView.swift in Sources */, 4B957A5D2AC7AE700062CA31 /* AppearancePreferences.swift in Sources */, @@ -11921,7 +11908,6 @@ 4B957AEF2AC7AE700062CA31 /* String+Punycode.swift in Sources */, 4B957AF02AC7AE700062CA31 /* NSException+Catch.m in Sources */, 4B957AF12AC7AE700062CA31 /* AppStateRestorationManager.swift in Sources */, - 4B957AF22AC7AE700062CA31 /* DailyPixel.swift in Sources */, 9FDA6C232B79A59D00E099A9 /* BookmarkFavoriteView.swift in Sources */, 4B957AF32AC7AE700062CA31 /* NavigationHotkeyHandler.swift in Sources */, B6CC266E2BAD9CD800F53F8D /* FileProgressPresenter.swift in Sources */, @@ -11944,10 +11930,7 @@ 4B957B052AC7AE700062CA31 /* FindInPageModel.swift in Sources */, 4B957B062AC7AE700062CA31 /* PseudoFolder.swift in Sources */, 4B2F565D2B38F93E001214C0 /* NetworkProtectionSubscriptionEventHandler.swift in Sources */, - 4B957B082AC7AE700062CA31 /* PixelDataStore.swift in Sources */, 4B957B092AC7AE700062CA31 /* WaitlistStorage.swift in Sources */, - 4B957B0A2AC7AE700062CA31 /* Pixel.swift in Sources */, - 4B957B0B2AC7AE700062CA31 /* PixelEvent.swift in Sources */, 4B957B0C2AC7AE700062CA31 /* TabBarFooter.swift in Sources */, C168B9AE2B31DC7F001AFAD9 /* AutofillNeverPromptWebsitesManager.swift in Sources */, 4B957B0D2AC7AE700062CA31 /* JSAlertViewModel.swift in Sources */, @@ -11987,6 +11970,7 @@ 4B957B2B2AC7AE700062CA31 /* TabViewModel.swift in Sources */, 4B957B2C2AC7AE700062CA31 /* TabDragAndDropManager.swift in Sources */, B677FC572B064A9C0099EB04 /* DataImportViewModel.swift in Sources */, + F188267E2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */, 4B957B2D2AC7AE700062CA31 /* NSNotificationName+Favicons.swift in Sources */, 4B957B2E2AC7AE700062CA31 /* PinningManager.swift in Sources */, 4B957B2F2AC7AE700062CA31 /* SyncMetadataDatabase.swift in Sources */, @@ -12040,7 +12024,6 @@ 4B957B592AC7AE700062CA31 /* AppLauncher.swift in Sources */, 4B957B5A2AC7AE700062CA31 /* FileStore.swift in Sources */, 1DB67F2F2B6FEFDB003DF243 /* ViewSnapshotRenderer.swift in Sources */, - 4B957B5B2AC7AE700062CA31 /* PixelArguments.swift in Sources */, 4B957B5C2AC7AE700062CA31 /* PinnedTabsViewModel.swift in Sources */, 4B957B5D2AC7AE700062CA31 /* BookmarkList.swift in Sources */, 4B957B5E2AC7AE700062CA31 /* NEOnDemandRuleExtension.swift in Sources */, @@ -12118,7 +12101,6 @@ 4B957B952AC7AE700062CA31 /* NSColorExtension.swift in Sources */, 4B957B972AC7AE700062CA31 /* AddressBarButtonsViewController.swift in Sources */, 4B957B982AC7AE700062CA31 /* BWError.swift in Sources */, - 4B957B9A2AC7AE700062CA31 /* PixelDataRecord.swift in Sources */, 4B957B9B2AC7AE700062CA31 /* PageObserverUserScript.swift in Sources */, 4B957B9C2AC7AE700062CA31 /* SecureVaultErrorReporter.swift in Sources */, 4B68DDFF2ACBA14100FB0973 /* FileLineError.swift in Sources */, @@ -12333,12 +12315,12 @@ 1D26EBAC2B74BECB0002A93F /* NSImageSendable.swift in Sources */, B6B4D1C52B0B3B5400C26286 /* DataImportReportModel.swift in Sources */, 4B92928D26670D1700AD2C21 /* BookmarkOutlineCellView.swift in Sources */, + F18826802BBEB58100D9AC4F /* PrivacyProPixel.swift in Sources */, B604085C274B8FBA00680351 /* UnprotectedDomains.xcdatamodeld in Sources */, 4BB88B5025B7BA2B006F6B06 /* TabInstrumentation.swift in Sources */, 85D33F1225C82EB3002B91A6 /* ConfigurationManager.swift in Sources */, 31F28C4F28C8EEC500119F70 /* YoutubePlayerUserScript.swift in Sources */, 4B41EDAE2B168AFF001EEDF4 /* VPNFeedbackFormViewController.swift in Sources */, - B6A9E48426146AAB0067D1B9 /* PixelParameters.swift in Sources */, AA5FA697275F90C400DCE9C9 /* FaviconImageCache.swift in Sources */, 1430DFF524D0580F00B8978C /* TabBarViewController.swift in Sources */, B62B483E2ADE48DE000DECE5 /* ArrayBuilder.swift in Sources */, @@ -12560,7 +12542,6 @@ B68412142B694BA10092F66A /* NSObject+performSelector.m in Sources */, B6F41031264D2B23003DA42C /* ProgressExtension.swift in Sources */, 4B723E0F26B0006500E14D75 /* CSVParser.swift in Sources */, - B6DA44082616B30600DD1EC2 /* PixelDataModel.xcdatamodeld in Sources */, B63BDF7E27FDAA640072D75B /* PrivacyDashboardWebView.swift in Sources */, 37CD54CF27F2FDD100F1F7B9 /* AppearancePreferences.swift in Sources */, B6B1E87B26D381710062C350 /* DownloadListCoordinator.swift in Sources */, @@ -12694,6 +12675,7 @@ AA6FFB4424DC33320028F4D0 /* NSViewExtension.swift in Sources */, B6C0B23E26E8BF1F0031CB7F /* DownloadListViewModel.swift in Sources */, 4B9292D52667123700AD2C21 /* BookmarkManagementDetailViewController.swift in Sources */, + F188267C2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */, 4B723E1026B0006700E14D75 /* CSVImporter.swift in Sources */, 37A4CEBA282E992F00D75B89 /* StartupPreferences.swift in Sources */, 4B41EDB12B168B1E001EEDF4 /* VPNFeedbackFormView.swift in Sources */, @@ -12743,7 +12725,6 @@ B65783E725F8AAFB00D8DB33 /* String+Punycode.swift in Sources */, B657841A25FA484B00D8DB33 /* NSException+Catch.m in Sources */, B684592F25C93FBF00DC17B6 /* AppStateRestorationManager.swift in Sources */, - 4B67853F2AA7C726008A5004 /* DailyPixel.swift in Sources */, B66260E629ACAE4B00E9E3EE /* NavigationHotkeyHandler.swift in Sources */, EA0BA3A9272217E6002A0B6C /* ClickToLoadUserScript.swift in Sources */, AAA892EA250A4CEF005B37B2 /* WindowControllersManager.swift in Sources */, @@ -12770,10 +12751,7 @@ 1DDD3EC42B84F96B004CBF2B /* CookiePopupProtectionPreferences.swift in Sources */, 4BCF15D92ABB8A7F0083F6DF /* NetworkProtectionRemoteMessage.swift in Sources */, 4B05265E2B1AE5C70054955A /* VPNMetadataCollector.swift in Sources */, - B6DA44022616B28300DD1EC2 /* PixelDataStore.swift in Sources */, 4B9DB0292A983B24000927DB /* WaitlistStorage.swift in Sources */, - B6A9E45326142B070067D1B9 /* Pixel.swift in Sources */, - B6A9E47726146A570067D1B9 /* PixelEvent.swift in Sources */, AA2CB1352587C29500AA6FBE /* TabBarFooter.swift in Sources */, EEC111E6294D06290086524F /* JSAlertViewModel.swift in Sources */, 4BE5336C286912D40019DBFD /* BookmarksBarCollectionViewItem.swift in Sources */, @@ -12871,7 +12849,6 @@ F1B33DF62BAD970E001128B3 /* SubscriptionErrorReporter.swift in Sources */, EEC589D92A4F1CE300BCD60C /* AppLauncher.swift in Sources */, 4BA1A69B258B076900F6F690 /* FileStore.swift in Sources */, - B6A9E47F26146A800067D1B9 /* PixelArguments.swift in Sources */, 1D01A3D02B88CEC600FE8150 /* PreferencesAccessibilityView.swift in Sources */, 37BF3F21286F0A7A00BD9014 /* PinnedTabsViewModel.swift in Sources */, EEC4A6692B2C87D300F7C0AA /* VPNLocationView.swift in Sources */, @@ -12942,7 +12919,6 @@ AAC5E4F625D6BF2C007F5990 /* AddressBarButtonsViewController.swift in Sources */, B6F9BDE42B45CD1900677B33 /* ModalView.swift in Sources */, 1D2DC0072901679C008083A1 /* BWError.swift in Sources */, - B68C92C42750EF76002AC6B0 /* PixelDataRecord.swift in Sources */, 853014D625E671A000FB8205 /* PageObserverUserScript.swift in Sources */, B677FC4F2B06376B0099EB04 /* ReportFeedbackView.swift in Sources */, B642738227B65BAC0005DFD1 /* SecureVaultErrorReporter.swift in Sources */, @@ -14476,7 +14452,6 @@ }; 3706FA6B293F65D500E42796 /* TrackerRadarKit */ = { isa = XCSwiftPackageProductDependency; - package = 3706FA6C293F65D500E42796 /* XCRemoteSwiftPackageReference "TrackerRadarKit" */; productName = TrackerRadarKit; }; 3706FA71293F65D500E42796 /* BrowserServicesKit */ = { diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 1ee442cfe3..a0a10c0edd 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -112,10 +112,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate { if NSApplication.runType.requiresEnvironment { #if DEBUG - Pixel.setUp(dryRun: true) Self.setUpPixelKit(dryRun: true) #else - Pixel.setUp() Self.setUpPixelKit(dryRun: false) #endif @@ -124,9 +122,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate { switch error { case CoreDataDatabase.Error.containerLocationCouldNotBePrepared(let underlyingError): - Pixel.fire(.debug(event: .dbContainerInitializationError, error: underlyingError)) + PixelKit.fire(DebugEvent(GeneralPixel.dbContainerInitializationError(error: underlyingError))) default: - Pixel.fire(.debug(event: .dbInitializationError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.dbInitializationError(error: error))) } // Give Pixel a chance to be sent, but not too long @@ -135,12 +133,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { } let preMigrationErrorHandling = EventMapping { _, error, _, _ in - if let error = error { - Pixel.fire(.debug(event: .bookmarksCouldNotLoadDatabase, error: error)) - } else { - Pixel.fire(.debug(event: .bookmarksCouldNotLoadDatabase)) - } - + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksCouldNotLoadDatabase(error: error))) Thread.sleep(forTimeInterval: 1) fatalError("Could not create Bookmarks database stack: \(error?.localizedDescription ?? "err")") } @@ -154,12 +147,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { BookmarkDatabase.shared.db.loadStore { context, error in guard let context = context else { - if let error = error { - Pixel.fire(.debug(event: .bookmarksCouldNotLoadDatabase, error: error)) - } else { - Pixel.fire(.debug(event: .bookmarksCouldNotLoadDatabase)) - } - + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksCouldNotLoadDatabase(error: error))) Thread.sleep(forTimeInterval: 1) fatalError("Could not create Bookmarks database stack: \(error?.localizedDescription ?? "err")") } @@ -233,10 +221,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate { if PixelExperiment.allocatedCohortDoesNotMatchCurrentCohorts { PixelExperiment.cleanup() } - if LocalStatisticsStore().atb == nil { - Pixel.firstLaunchDate = Date() - // MARK: Enable pixel experiments here - } +// if LocalStatisticsStore().atb == nil { +// Pixel.firstLaunchDate = Date() // TODO: WTF is this? reimplement? +// // MARK: Enable pixel experiments here +// } AtbAndVariantCleanup.cleanup() DefaultVariantManager().assignVariantIfNeeded { _ in // MARK: perform first time launch logic here @@ -442,9 +430,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate { .filter { $0 } .asVoid() .sink { [weak syncService] in - Pixel.fire(.syncDaily, limitTo: .dailyFirst) + PixelKit.fire(GeneralPixel.syncDaily, frequency: .dailyOnly) syncService?.syncDailyStats.sendStatsIfNeeded(handler: { params in - Pixel.fire(.syncSuccessRateDaily, withAdditionalParameters: params) + PixelKit.fire(GeneralPixel.syncSuccessRateDaily, withAdditionalParameters: params) }) } @@ -525,10 +513,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate { } private func emailDidSignInNotification(_ notification: Notification) { - Pixel.fire(.emailEnabled) - if Pixel.isNewUser { - Pixel.fire(.emailEnabledInitial, limitTo: .initial) - } + PixelKit.fire(GeneralPixel.emailEnabled) +// if Pixel.isNewUser { // TODO: reimplement Pixel.isNewUser +// PixelKit.fire(GeneralPixel.emailEnabledInitial, frequency: .justOnce) +// } if let object = notification.object as? EmailManager, let emailManager = syncDataProviders.settingsAdapter.emailManager, object !== emailManager { syncService?.scheduler.notifyDataChanged() @@ -536,16 +524,16 @@ final class AppDelegate: NSObject, NSApplicationDelegate { } private func emailDidSignOutNotification(_ notification: Notification) { - Pixel.fire(.emailDisabled) + PixelKit.fire(GeneralPixel.emailDisabled) if let object = notification.object as? EmailManager, let emailManager = syncDataProviders.settingsAdapter.emailManager, object !== emailManager { syncService?.scheduler.notifyDataChanged() } } @objc private func dataImportCompleteNotification(_ notification: Notification) { - if Pixel.isNewUser { - Pixel.fire(.importDataInitial, limitTo: .initial) - } +// if Pixel.isNewUser { // TODO: reimplement Pixel.isNewUser +// PixelKit.fire(GeneralPixel.importDataInitial, frequency: .justOnce) +// } } } @@ -559,7 +547,7 @@ func updateSubscriptionStatus() { if case .success(let subscription) = await SubscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) { if subscription.isActive { - DailyPixel.fire(pixel: .privacyProSubscriptionActive, frequency: .dailyOnly) + PixelKit.fire(GeneralPixel.privacyProSubscriptionActive, frequency: .dailyOnly) } } @@ -586,7 +574,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate { #if NETWORK_PROTECTION if response.notification.request.identifier == NetworkProtectionWaitlist.notificationIdentifier { if NetworkProtectionWaitlist().readyToAcceptTermsAndConditions { - DailyPixel.fire(pixel: .networkProtectionWaitlistNotificationTapped, frequency: .dailyAndCount) + PixelKit.fire(GeneralPixel.networkProtectionWaitlistNotificationTapped, frequency: .dailyAndContinuous) NetworkProtectionWaitlistViewControllerPresenter.show() } } diff --git a/DuckDuckGo/DBP/DataBrokerProtectionExternalWaitlistPixels.swift b/DuckDuckGo/DBP/DataBrokerProtectionExternalWaitlistPixels.swift index 79a6d85bf5..f23a4884a5 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionExternalWaitlistPixels.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionExternalWaitlistPixels.swift @@ -17,6 +17,7 @@ // import Foundation +import PixelKit struct DataBrokerProtectionExternalWaitlistPixels { @@ -35,15 +36,11 @@ struct DataBrokerProtectionExternalWaitlistPixels { return (regionCode ?? "US") == "US" } - static func fire(pixel: Pixel.Event, frequency: DailyPixel.PixelFrequency) { + static func fire(pixel: PixelKitEventV2, frequency: PixelKit.Frequency) { if Self.isUserLocaleAllowed { let isInternalUser = NSApp.delegateTyped.internalUserDecider.isInternalUser - DailyPixel.fire(pixel: pixel, - frequency: frequency, - withAdditionalParameters: [ - "isInternalUser": isInternalUser.description - ] - ) + let parameters = ["isInternalUser": isInternalUser.description] + PixelKit.fire(pixel, frequency: frequency, withAdditionalParameters: parameters) } } } diff --git a/DuckDuckGo/DBP/DataBrokerProtectionLoginItemPixels.swift b/DuckDuckGo/DBP/DataBrokerProtectionLoginItemPixels.swift index 30e2373a7f..6b10effcf1 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionLoginItemPixels.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionLoginItemPixels.swift @@ -17,19 +17,16 @@ // import Foundation +import PixelKit struct DataBrokerProtectionLoginItemPixels { - static func fire(pixel: Pixel.Event, frequency: DailyPixel.PixelFrequency) { + static func fire(pixel: PixelKitEventV2, frequency: PixelKit.Frequency) { DispatchQueue.main.async { // delegateTyped needs to be called in the main thread let isInternalUser = NSApp.delegateTyped.internalUserDecider.isInternalUser - DailyPixel.fire(pixel: pixel, - frequency: frequency, - withAdditionalParameters: [ - "isInternalUser": isInternalUser.description - ] - ) + let parameters = ["isInternalUser": isInternalUser.description] + PixelKit.fire(pixel, frequency: frequency, withAdditionalParameters: parameters) } } } diff --git a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift index 41c8bc3075..f6571f004e 100644 --- a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift +++ b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift @@ -20,6 +20,7 @@ import AppKit import BrowserServicesKit import Common import Foundation +import PixelKit #if NETWORK_PROTECTION import NetworkProtection @@ -250,20 +251,13 @@ extension HomePage.Models { for message in homePageRemoteMessaging.dataBrokerProtectionRemoteMessaging.presentableRemoteMessages() { features.append(.dataBrokerProtectionRemoteMessage(message)) - DailyPixel.fire( - pixel: .dataBrokerProtectionRemoteMessageDisplayed(messageID: message.id), - frequency: .dailyOnly - ) + PixelKit.fire(GeneralPixel.dataBrokerProtectionRemoteMessageDisplayed(messageID: message.id), frequency: .dailyOnly) } #endif #if NETWORK_PROTECTION for message in homePageRemoteMessaging.networkProtectionRemoteMessaging.presentableRemoteMessages() { - features.append(.networkProtectionRemoteMessage(message)) - DailyPixel.fire( - pixel: .networkProtectionRemoteMessageDisplayed(messageID: message.id), - frequency: .dailyOnly - ) + PixelKit.fire(GeneralPixel.networkProtectionRemoteMessageDisplayed(messageID: message.id), frequency: .dailyOnly) } #endif diff --git a/DuckDuckGo/HomePage/View/HomePageViewController.swift b/DuckDuckGo/HomePage/View/HomePageViewController.swift index 96fb2aca47..dc3997e6a8 100644 --- a/DuckDuckGo/HomePage/View/HomePageViewController.swift +++ b/DuckDuckGo/HomePage/View/HomePageViewController.swift @@ -106,9 +106,9 @@ final class HomePageViewController: NSViewController { override func viewWillAppear() { super.viewWillAppear() - if OnboardingViewModel.isOnboardingFinished && Pixel.isNewUser { - Pixel.fire(.newTabInitial, limitTo: .initial) - } +// if OnboardingViewModel.isOnboardingFinished && Pixel.isNewUser { +// Pixel.fire(.newTabInitial, limitTo: .initial) +// } // TODO: reimplement Pixel.isNewUser subscribeToHistory() } diff --git a/DuckDuckGo/LoginItems/LoginItemsManager.swift b/DuckDuckGo/LoginItems/LoginItemsManager.swift index e0a44a4fb4..2c0e4ab00f 100644 --- a/DuckDuckGo/LoginItems/LoginItemsManager.swift +++ b/DuckDuckGo/LoginItems/LoginItemsManager.swift @@ -19,6 +19,7 @@ import Common import Foundation import LoginItems +import PixelKit /// Class to manage the login items for the VPN and DBP /// @@ -66,17 +67,12 @@ final class LoginItemsManager { } private func handleError(for item: LoginItem, action: Action, error: NSError) { - let event = Pixel.Event.Debug.loginItemUpdateError( - loginItemBundleID: item.agentBundleID, - action: "enable", - buildType: AppVersion.shared.buildType, - osVersion: AppVersion.shared.osVersion - ) - DailyPixel.fire(pixel: .debug(event: event, error: error), frequency: .dailyAndCount) - - os_log("🔴 Could not enable %{public}@: %{public}@", - item.debugDescription, - error.debugDescription) + let event = GeneralPixel.loginItemUpdateError(loginItemBundleID: item.agentBundleID, + action: "enable", + buildType: AppVersion.shared.buildType, + osVersion: AppVersion.shared.osVersion) + PixelKit.fire(DebugEvent(event, error: error) , frequency: .dailyAndContinuous) + os_log("🔴 Could not enable %{public}@: %{public}@", item.debugDescription, error.debugDescription) } // MARK: - Debug Interactions diff --git a/DuckDuckGo/MainWindow/MainViewController.swift b/DuckDuckGo/MainWindow/MainViewController.swift index 17c166e932..ab264e8b49 100644 --- a/DuckDuckGo/MainWindow/MainViewController.swift +++ b/DuckDuckGo/MainWindow/MainViewController.swift @@ -24,6 +24,7 @@ import Common #if NETWORK_PROTECTION import NetworkProtection import NetworkProtectionIPC +import PixelKit #endif final class MainViewController: NSViewController { @@ -453,7 +454,7 @@ final class MainViewController: NSViewController { #if NETWORK_PROTECTION private func sendActiveNetworkProtectionWaitlistUserPixel() { if DefaultNetworkProtectionVisibility().waitlistIsOngoing { - DailyPixel.fire(pixel: .networkProtectionWaitlistUserActive, frequency: .dailyOnly) + PixelKit.fire(GeneralPixel.networkProtectionWaitlistUserActive, frequency: .dailyOnly) } } #endif diff --git a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift index 4598e190e4..d3c0e63cb5 100644 --- a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift +++ b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift @@ -20,6 +20,7 @@ import Cocoa import Combine import Common import BrowserServicesKit +import PixelKit #if NETWORK_PROTECTION import NetworkProtection @@ -365,7 +366,7 @@ final class MoreOptionsMenu: NSMenu { } #endif - DailyPixel.fire(pixel: .networkProtectionWaitlistEntryPointMenuItemDisplayed, frequency: .dailyAndCount, includeAppVersionParameter: true) + PixelKit.fire(GeneralPixel.networkProtectionWaitlistEntryPointMenuItemDisplayed, frequency: .dailyAndContinuous, includeAppVersionParameter: true) } else { networkProtectionFeatureVisibility.disableForWaitlistUsers() } diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift index 2148a66473..4f3937b652 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift @@ -20,6 +20,7 @@ import Cocoa import Combine import Common import BrowserServicesKit +import PixelKit #if NETWORK_PROTECTION import NetworkProtection @@ -344,12 +345,12 @@ final class NavigationBarViewController: NSViewController { if NetworkProtectionWaitlist().shouldShowWaitlistViewController { NetworkProtectionWaitlistViewControllerPresenter.show() - DailyPixel.fire(pixel: .networkProtectionWaitlistIntroDisplayed, frequency: .dailyAndCount) + PixelKit.fire(GeneralPixel.networkProtectionWaitlistIntroDisplayed, frequency: .dailyAndContinuous) } else if NetworkProtectionKeychainTokenStore().isFeatureActivated { popovers.toggleNetworkProtectionPopover(usingView: networkProtectionButton, withDelegate: networkProtectionButtonModel) } else { NetworkProtectionWaitlistViewControllerPresenter.show() - DailyPixel.fire(pixel: .networkProtectionWaitlistIntroDisplayed, frequency: .dailyAndCount) + PixelKit.fire(GeneralPixel.networkProtectionWaitlistIntroDisplayed, frequency: .dailyAndContinuous) } } #endif diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift index 5ee8f863a0..88d8339c22 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift @@ -24,6 +24,7 @@ import Foundation import NetworkProtection import NetworkProtectionIPC import NetworkProtectionUI +import PixelKit /// Model for managing the NetP button in the Nav Bar. /// @@ -180,9 +181,7 @@ final class NetworkProtectionNavBarButtonModel: NSObject, ObservableObject { let networkProtectionVisibility = DefaultNetworkProtectionVisibility() if networkProtectionVisibility.isNetworkProtectionBetaVisible() { if NetworkProtectionWaitlist().readyToAcceptTermsAndConditions { - DailyPixel.fire(pixel: .networkProtectionWaitlistEntryPointToolbarButtonDisplayed, - frequency: .dailyOnly, - includeAppVersionParameter: true) + PixelKit.fire(GeneralPixel.networkProtectionWaitlistEntryPointToolbarButtonDisplayed, frequency: .dailyOnly, includeAppVersionParameter: true) showButton = true return } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift index 24dfd9c4ab..346e9c830d 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift @@ -21,6 +21,7 @@ import Foundation import Combine import NetworkProtection +import PixelKit final class VPNLocationViewModel: ObservableObject { private static var cachedLocations: [VPNCountryItemModel]? @@ -75,13 +76,13 @@ final class VPNLocationViewModel: ObservableObject { } func onNearestItemSelection() async { - DailyPixel.fire(pixel: .networkProtectionGeoswitchingSetNearest, frequency: .dailyAndCount) + PixelKit.fire(GeneralPixel.networkProtectionGeoswitchingSetNearest, frequency: .dailyAndContinuous) selectedLocation = .nearest await reloadList() } func onCountryItemSelection(id: String, cityId: String? = nil) async { - DailyPixel.fire(pixel: .networkProtectionGeoswitchingSetCustom, frequency: .dailyAndCount) + PixelKit.fire(GeneralPixel.networkProtectionGeoswitchingSetCustom, frequency: .dailyAndContinuous) let location = NetworkProtectionSelectedLocation(country: id, city: cityId) selectedLocation = .location(location) await reloadList() @@ -95,7 +96,7 @@ final class VPNLocationViewModel: ObservableObject { private func reloadList() async { guard let locations = try? await locationListRepository.fetchLocationList().sortedByName() else { return } if locations.isEmpty { - DailyPixel.fire(pixel: .networkProtectionGeoswitchingNoLocations, frequency: .dailyAndCount) + PixelKit.fire(GeneralPixel.networkProtectionGeoswitchingNoLocations, frequency: .dailyAndContinuous) } let isNearestSelected = selectedLocation == .nearest self.isNearestSelected = isNearestSelected diff --git a/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift b/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift index a40e390510..b26eec7c28 100644 --- a/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift +++ b/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift @@ -79,9 +79,9 @@ final class DefaultBrowserPreferences: ObservableObject { #if DEBUG guard NSApp.runType.requiresEnvironment else { return } #endif - if Pixel.isNewUser && isDefault { - Pixel.fire(.setAsDefaultInitial, limitTo: .initial) - } +// if Pixel.isNewUser && isDefault { // TODO: reimplement Pixel.isNewUser +// Pixel.fire(.setAsDefaultInitial, limitTo: .initial) +// } } } @Published private(set) var restorePreviousSession: Bool = false diff --git a/DuckDuckGo/Preferences/View/PreferencesRootView.swift b/DuckDuckGo/Preferences/View/PreferencesRootView.swift index 922b2fe501..7c714ae842 100644 --- a/DuckDuckGo/Preferences/View/PreferencesRootView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesRootView.swift @@ -22,6 +22,7 @@ import SwiftUI import SwiftUIExtensions import SyncUI import BrowserServicesKit +import PixelKit #if SUBSCRIPTION import Subscription @@ -147,32 +148,32 @@ enum Preferences { DispatchQueue.main.async { switch event { case .openVPN: - Pixel.fire(.privacyProVPNSettings) + PixelKit.fire(PrivacyProPixel.privacyProVPNSettings) NotificationCenter.default.post(name: .ToggleNetworkProtectionInMainWindow, object: self, userInfo: nil) case .openDB: - Pixel.fire(.privacyProPersonalInformationRemovalSettings) + PixelKit.fire(PrivacyProPixel.privacyProPersonalInformationRemovalSettings) WindowControllersManager.shared.showTab(with: .dataBrokerProtection) case .openITR: - Pixel.fire(.privacyProIdentityRestorationSettings) + PixelKit.fire(PrivacyProPixel.privacyProIdentityRestorationSettings) WindowControllersManager.shared.showTab(with: .identityTheftRestoration(.identityTheftRestoration)) case .iHaveASubscriptionClick: - Pixel.fire(.privacyProRestorePurchaseClick) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseClick) case .activateAddEmailClick: - DailyPixel.fire(pixel: .privacyProRestorePurchaseEmailStart, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailStart, frequency: .dailyAndContinuous) case .postSubscriptionAddEmailClick: - Pixel.fire(.privacyProWelcomeAddDevice, limitTo: .initial) + PixelKit.fire(PrivacyProPixel.privacyProWelcomeAddDevice, frequency: .justOnce) case .restorePurchaseStoreClick: - DailyPixel.fire(pixel: .privacyProRestorePurchaseStoreStart, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreStart, frequency: .dailyAndContinuous) case .addToAnotherDeviceClick: - Pixel.fire(.privacyProSettingsAddDevice) + PixelKit.fire(PrivacyProPixel.privacyProSettingsAddDevice) case .addDeviceEnterEmail: - Pixel.fire(.privacyProAddDeviceEnterEmail) + PixelKit.fire(PrivacyProPixel.privacyProAddDeviceEnterEmail) case .activeSubscriptionSettingsClick: - Pixel.fire(.privacyProSubscriptionSettings) + PixelKit.fire(PrivacyProPixel.privacyProSubscriptionSettings) case .changePlanOrBillingClick: - Pixel.fire(.privacyProSubscriptionManagementPlanBilling) + PixelKit.fire(PrivacyProPixel.privacyProSubscriptionManagementPlanBilling) case .removeSubscriptionClick: - Pixel.fire(.privacyProSubscriptionManagementRemoval) + PixelKit.fire(PrivacyProPixel.privacyProSubscriptionManagementRemoval) } } } diff --git a/DuckDuckGo/SmarterEncryption/PrivacyFeatures.swift b/DuckDuckGo/SmarterEncryption/PrivacyFeatures.swift index e76155b196..37067cdfc8 100644 --- a/DuckDuckGo/SmarterEncryption/PrivacyFeatures.swift +++ b/DuckDuckGo/SmarterEncryption/PrivacyFeatures.swift @@ -20,6 +20,7 @@ import BrowserServicesKit import Common import Foundation import Persistence +import PixelKit protocol PrivacyFeaturesProtocol { var contentBlocking: AnyContentBlocking { get } @@ -42,22 +43,31 @@ final class AppPrivacyFeatures: PrivacyFeaturesProtocol { let httpsUpgrade: HTTPSUpgrade private static let httpsUpgradeDebugEvents = EventMapping { event, error, parameters, onComplete in - let domainEvent: Pixel.Event.Debug + let domainEvent: GeneralPixel let dailyAndCount: Bool switch event { case .dbSaveBloomFilterError: - domainEvent = .dbSaveBloomFilterError + domainEvent = GeneralPixel.dbSaveBloomFilterError(error: error) dailyAndCount = true case .dbSaveExcludedHTTPSDomainsError: - domainEvent = .dbSaveExcludedHTTPSDomainsError + domainEvent = GeneralPixel.dbSaveExcludedHTTPSDomainsError(error: error) dailyAndCount = false } if dailyAndCount { - DailyPixel.fire(pixel: .debug(event: domainEvent, error: error), frequency: .dailyAndCount, includeAppVersionParameter: true, withAdditionalParameters: parameters ?? [:], onComplete: onComplete) + PixelKit.fire(DebugEvent(domainEvent, error: error), + frequency: .dailyAndContinuous, + withAdditionalParameters: parameters ?? [:], + includeAppVersionParameter: true) { success, error in + onComplete(error) + } } else { - Pixel.fire(.debug(event: domainEvent, error: error), withAdditionalParameters: parameters, onComplete: onComplete) + PixelKit.fire(DebugEvent(domainEvent, error: error), + frequency: .dailyAndContinuous, + withAdditionalParameters: parameters ?? [:]) { success, error in + onComplete(error) + } } } private static var embeddedBloomFilterResources: EmbeddedBloomFilterResources { diff --git a/DuckDuckGo/Statistics/GeneralPixel.swift b/DuckDuckGo/Statistics/GeneralPixel.swift new file mode 100644 index 0000000000..0eedc389c4 --- /dev/null +++ b/DuckDuckGo/Statistics/GeneralPixel.swift @@ -0,0 +1,874 @@ +// +// GeneralPixel.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 +import PixelKit +import BrowserServicesKit +import DDGSync +import Configuration + +enum GeneralPixel: PixelKitEventV2 { + + case crash + case brokenSiteReport + case compileRulesWait(onboardingShown: OnboardingShown, waitTime: CompileRulesWaitTime, result: WaitResult) + case launchInitial(cohort: String) + + case serp + case serpInitial(cohort: String) + case serpDay21to27(cohort: String) + + case dailyOsVersionCounter + + case dataImportFailed(source: DataImport.Source, sourceVersion: String?, error: any DataImportError) + + case formAutofilled(kind: FormAutofillKind) + case autofillItemSaved(kind: FormAutofillKind) + + case autofillLoginsSaveLoginModalExcludeSiteConfirmed + case autofillLoginsSettingsResetExcludedDisplayed + case autofillLoginsSettingsResetExcludedConfirmed + case autofillLoginsSettingsResetExcludedDismissed + + case bitwardenPasswordAutofilled + case bitwardenPasswordSaved + + case ampBlockingRulesCompilationFailed + + case adClickAttributionDetected + case adClickAttributionActive + case adClickAttributionPageLoads + + case emailEnabled + case emailDisabled + case emailUserPressedUseAddress + case emailUserPressedUseAlias + case emailUserCreatedAlias + + case jsPixel(_ pixel: AutofillUserScript.JSPixel) + + // Activation Points + case newTabInitial + case emailEnabledInitial + case watchInDuckPlayerInitial + case setAsDefaultInitial + case importDataInitial + + // New Tab section removed + case favoriteSectionHidden + case recentActivitySectionHidden + case continueSetUpSectionHidden + + // Fire Button + case fireButtonFirstBurn + case fireButton(option: FireButtonOption) + + // Duck Player + case duckPlayerDailyUniqueView + case duckPlayerViewFromYoutubeViaMainOverlay + case duckPlayerViewFromYoutubeViaHoverButton + case duckPlayerViewFromYoutubeAutomatic + case duckPlayerViewFromSERP + case duckPlayerViewFromOther + case duckPlayerSettingAlways + case duckPlayerSettingNever + case duckPlayerSettingBackToDefault + + // Dashboard + case dashboardProtectionAllowlistAdd(triggerOrigin: String?) + case dashboardProtectionAllowlistRemove(triggerOrigin: String?) + + // VPN + case vpnBreakageReport(category: String, description: String, metadata: String) + + // VPN + case networkProtectionWaitlistUserActive + case networkProtectionWaitlistEntryPointMenuItemDisplayed + case networkProtectionWaitlistEntryPointToolbarButtonDisplayed + case networkProtectionWaitlistIntroDisplayed + case networkProtectionWaitlistNotificationShown + case networkProtectionWaitlistNotificationTapped + case networkProtectionWaitlistTermsAndConditionsDisplayed + case networkProtectionWaitlistTermsAndConditionsAccepted + case networkProtectionRemoteMessageDisplayed(messageID: String) + case networkProtectionRemoteMessageDismissed(messageID: String) + case networkProtectionRemoteMessageOpened(messageID: String) + case networkProtectionEnabledOnSearch + case networkProtectionGeoswitchingOpened + case networkProtectionGeoswitchingSetNearest + case networkProtectionGeoswitchingSetCustom + case networkProtectionGeoswitchingNoLocations + + // Sync + case syncSignupDirect + case syncSignupConnect + case syncLogin + case syncDaily + case syncDuckAddressOverride + case syncSuccessRateDaily + case syncLocalTimestampResolutionTriggered(Feature) + case syncBookmarksCountLimitExceededDaily + case syncCredentialsCountLimitExceededDaily + case syncBookmarksRequestSizeLimitExceededDaily + case syncCredentialsRequestSizeLimitExceededDaily + + // DataBroker Protection Waitlist + case dataBrokerProtectionWaitlistUserActive + case dataBrokerProtectionWaitlistEntryPointMenuItemDisplayed + case dataBrokerProtectionWaitlistIntroDisplayed + case dataBrokerProtectionWaitlistNotificationShown + case dataBrokerProtectionWaitlistNotificationTapped + case dataBrokerProtectionWaitlistCardUITapped + case dataBrokerProtectionWaitlistTermsAndConditionsDisplayed + case dataBrokerProtectionWaitlistTermsAndConditionsAccepted + case dataBrokerProtectionRemoteMessageDisplayed(messageID: String) + case dataBrokerProtectionRemoteMessageDismissed(messageID: String) + case dataBrokerProtectionRemoteMessageOpened(messageID: String) + + // Login Item events + case dataBrokerEnableLoginItemDaily + case dataBrokerDisableLoginItemDaily + case dataBrokerResetLoginItemDaily + case dataBrokerDisableAndDeleteDaily + + // DataBrokerProtection Other + case dataBrokerProtectionErrorWhenFetchingSubscriptionAuthTokenAfterSignIn + + // Default Browser + case defaultRequestedFromHomepage + case defaultRequestedFromHomepageSetupView + case defaultRequestedFromSettings + case defaultRequestedFromOnboarding + + case protectionToggledOffBreakageReport + case toggleProtectionsDailyCount + case toggleReportDoNotSend + case toggleReportDismiss + + // Password Import Keychain Prompt + case passwordImportKeychainPrompt + case passwordImportKeychainPromptDenied + + // MARK: - Debug + + case assertionFailure(message: String, file: StaticString, line: UInt) + + case dbMakeDatabaseError(error: Error) + case dbContainerInitializationError(error: Error) + case dbInitializationError(error: Error) + case dbSaveExcludedHTTPSDomainsError(error: Error?) + case dbSaveBloomFilterError(error: Error?) + + case configurationFetchError(error: Error) + + case trackerDataParseFailed + case trackerDataReloadFailed + case trackerDataCouldNotBeLoaded + + case privacyConfigurationParseFailed + case privacyConfigurationReloadFailed + case privacyConfigurationCouldNotBeLoaded + + case fileStoreWriteFailed + case fileMoveToDownloadsFailed + case fileAccessRelatedItemFailed + case fileGetDownloadLocationFailed + case fileDownloadCreatePresentersFailed + case downloadResumeDataCodingFailed + + case suggestionsFetchFailed + case appOpenURLFailed + case appStateRestorationFailed + + case contentBlockingErrorReportingIssue + + case contentBlockingCompilationFailed(listType: CompileRulesListType, component: ContentBlockerDebugEvents.Component) + + case contentBlockingCompilationTime + + case secureVaultInitError(error: Error) + case secureVaultError(error: Error) + + case feedbackReportingFailed + + case blankNavigationOnBurnFailed + + case historyRemoveFailed + case historyReloadFailed + case historyCleanEntriesFailed + case historyCleanVisitsFailed + case historySaveFailed + case historyInsertVisitFailed + case historyRemoveVisitsFailed + + case emailAutofillKeychainError(error: Error) + + case bookmarksStoreRootFolderMigrationFailed + case bookmarksStoreFavoritesFolderMigrationFailed + + case adAttributionCompilationFailedForAttributedRulesList + case adAttributionGlobalAttributedRulesDoNotExist + case adAttributionDetectionHeuristicsDidNotMatchDomain + case adAttributionLogicUnexpectedStateOnRulesCompiled + case adAttributionLogicUnexpectedStateOnInheritedAttribution + case adAttributionLogicUnexpectedStateOnRulesCompilationFailed + case adAttributionDetectionInvalidDomainInParameter + case adAttributionLogicRequestingAttributionTimedOut + case adAttributionLogicWrongVendorOnSuccessfulCompilation + case adAttributionLogicWrongVendorOnFailedCompilation + + case webKitDidTerminate + + case removedInvalidBookmarkManagedObjects + + case bitwardenNotResponding + // case bitwardenRespondedCannotDecryptUnique(repetition: Repetition = .init(key: "bitwardenRespondedCannotDecryptUnique")) // TODO: REIMPLEMENTATION?? + case bitwardenHandshakeFailed + case bitwardenDecryptionOfSharedKeyFailed + case bitwardenStoringOfTheSharedKeyFailed + case bitwardenCredentialRetrievalFailed + case bitwardenCredentialCreationFailed + case bitwardenCredentialUpdateFailed + case bitwardenRespondedWithError(error: Error) + case bitwardenNoActiveVault + case bitwardenParsingFailed + case bitwardenStatusParsingFailed + case bitwardenHmacComparisonFailed + case bitwardenDecryptionFailed + case bitwardenSendingOfMessageFailed + case bitwardenSharedKeyInjectionFailed + + case updaterAborted + case userSelectedToSkipUpdate + case userSelectedToInstallUpdate + case userSelectedToDismissUpdate + + case faviconDecryptionFailedUnique + case downloadListItemDecryptionFailedUnique + case historyEntryDecryptionFailedUnique + case permissionDecryptionFailedUnique + + // Errors from Bookmarks Module + case missingParent + case bookmarksSaveFailed + case bookmarksSaveFailedOnImport + + case bookmarksCouldNotLoadDatabase(error: Error?) + case bookmarksCouldNotPrepareDatabase + case bookmarksMigrationAlreadyPerformed + case bookmarksMigrationFailed + case bookmarksMigrationCouldNotPrepareDatabase + case bookmarksMigrationCouldNotPrepareDatabaseOnFailedMigration + case bookmarksMigrationCouldNotRemoveOldStore + case bookmarksMigrationCouldNotPrepareMultipleFavoriteFolders + + case syncSentUnauthenticatedRequest + case syncMetadataCouldNotLoadDatabase + case syncBookmarksProviderInitializationFailed + case syncBookmarksFailed + case syncCredentialsProviderInitializationFailed + case syncCredentialsFailed + case syncSettingsFailed + case syncSettingsMetadataUpdateFailed + case syncSignupError(error: Error) + case syncLoginError(error: Error) + case syncLogoutError(error: Error) + case syncUpdateDeviceError(error: Error) + case syncRemoveDeviceError(error: Error) + case syncDeleteAccountError(error: Error) + case syncLoginExistingAccountError(error: Error) + case syncCannotCreateRecoveryPDF + + case bookmarksCleanupFailed + case bookmarksCleanupAttemptedWhileSyncWasEnabled + case favoritesCleanupFailed + case bookmarksFaviconsFetcherStateStoreInitializationFailed + case bookmarksFaviconsFetcherFailed + + case credentialsDatabaseCleanupFailed + case credentialsCleanupAttemptedWhileSyncWasEnabled + + case invalidPayload(Configuration) // BSK>Configuration + + case burnerTabMisplaced + + case networkProtectionRemoteMessageFetchingFailed + case networkProtectionRemoteMessageStorageFailed + case dataBrokerProtectionRemoteMessageFetchingFailed + case dataBrokerProtectionRemoteMessageStorageFailed + + case loginItemUpdateError(loginItemBundleID: String, action: String, buildType: String, osVersion: String) + + var name: String { + switch self { + + case .crash: + return "m_mac_crash" + + case .brokenSiteReport: + return "epbf_macos_desktop" + + case .compileRulesWait(onboardingShown: let onboardingShown, waitTime: let waitTime, result: let result): + return "m_mac_cbr-wait_\(onboardingShown)_\(waitTime)_\(result)" + + case .serp: + return "m_mac_navigation_search" + + case .dailyOsVersionCounter: + return "m_mac_daily-os-version-counter" + + case .dataImportFailed(source: let source, sourceVersion: _, error: let error) where error.action == .favicons: + return "m_mac_favicon-import-failed_\(source)" + case .dataImportFailed(source: let source, sourceVersion: _, error: let error): + return "m_mac_data-import-failed_\(error.action)_\(source)" + + case .formAutofilled(kind: let kind): + return "m_mac_autofill_\(kind)" + + case .autofillItemSaved(kind: let kind): + return "m_mac_save_\(kind)" + + case .autofillLoginsSaveLoginModalExcludeSiteConfirmed: + return "m_mac_autofill_logins_save_login_exclude_site_confirmed" + case .autofillLoginsSettingsResetExcludedDisplayed: + return "m_mac_autofill_settings_reset_excluded_displayed" + case .autofillLoginsSettingsResetExcludedConfirmed: + return "m_mac_autofill_settings_reset_excluded_confirmed" + case .autofillLoginsSettingsResetExcludedDismissed: + return "m_mac_autofill_settings_reset_excluded_dismissed" + + case .bitwardenPasswordAutofilled: + return "m_mac_bitwarden_autofill_password" + + case .bitwardenPasswordSaved: + return "m_mac_bitwarden_save_password" + + case .ampBlockingRulesCompilationFailed: + return "m_mac_amp_rules_compilation_failed" + + case .adClickAttributionDetected: + return "m_mac_ad_click_detected" + + case .adClickAttributionActive: + return "m_mac_ad_click_active" + + case .adClickAttributionPageLoads: + return "m_mac_ad_click_page_loads" + + // Deliberately omit the `m_mac_` prefix in order to format these pixels the same way as other platforms + case .emailEnabled: return "email_enabled_macos_desktop" + case .emailDisabled: return "email_disabled_macos_desktop" + case .emailUserPressedUseAddress: return "email_filled_main_macos_desktop" + case .emailUserPressedUseAlias: return "email_filled_random_macos_desktop" + case .emailUserCreatedAlias: return "email_generated_button_macos_desktop" + + case .jsPixel(let pixel): + // Email pixels deliberately avoid using the `m_mac_` prefix. + if pixel.isEmailPixel { + return "\(pixel.pixelName)_macos_desktop" + } else { + return "m_mac_\(pixel.pixelName)" + } + case .emailEnabledInitial: + return "m_mac.enable-email-protection.initial" + + case .watchInDuckPlayerInitial: + return "m_mac.watch-in-duckplayer.initial" + case .setAsDefaultInitial: + return "m_mac.set-as-default.initial" + case .importDataInitial: + return "m_mac.import-data.initial" + case .newTabInitial: + return "m_mac.new-tab-opened.initial" + case .favoriteSectionHidden: + return "m_mac.favorite-section-hidden" + case .recentActivitySectionHidden: + return "m_mac.recent-activity-section-hidden" + case .continueSetUpSectionHidden: + return "m_mac.continue-setup-section-hidden" + + // Fire Button + case .fireButtonFirstBurn: + return "m_mac_fire_button_first_burn" + case .fireButton(option: let option): + return "m_mac_fire_button_\(option)" + + case .duckPlayerDailyUniqueView: + return "m_mac_duck-player_daily-unique-view" + case .duckPlayerViewFromYoutubeViaMainOverlay: + return "m_mac_duck-player_view-from_youtube_main-overlay" + case .duckPlayerViewFromYoutubeViaHoverButton: + return "m_mac_duck-player_view-from_youtube_hover-button" + case .duckPlayerViewFromYoutubeAutomatic: + return "m_mac_duck-player_view-from_youtube_automatic" + case .duckPlayerViewFromSERP: + return "m_mac_duck-player_view-from_serp" + case .duckPlayerViewFromOther: + return "m_mac_duck-player_view-from_other" + case .duckPlayerSettingAlways: + return "m_mac_duck-player_setting_always" + case .duckPlayerSettingNever: + return "m_mac_duck-player_setting_never" + case .duckPlayerSettingBackToDefault: + return "m_mac_duck-player_setting_back-to-default" + + case .dashboardProtectionAllowlistAdd: + return "m_mac_mp_wla" + case .dashboardProtectionAllowlistRemove: + return "m_mac_mp_wlr" + + case .launchInitial: + return "m.mac.first-launch" + case .serpInitial: + return "m.mac.navigation.first-search" + case .serpDay21to27: + return "m.mac.search-day-21-27.initial" + + case .vpnBreakageReport: + return "m_mac_vpn_breakage_report" + + case .networkProtectionWaitlistUserActive: + return "m_mac_netp_waitlist_user_active" + case .networkProtectionWaitlistEntryPointMenuItemDisplayed: + return "m_mac_netp_imp_settings_entry_menu_item" + case .networkProtectionWaitlistEntryPointToolbarButtonDisplayed: + return "m_mac_netp_imp_settings_entry_toolbar_button" + case .networkProtectionWaitlistIntroDisplayed: + return "m_mac_netp_imp_intro_screen" + case .networkProtectionWaitlistNotificationShown: + return "m_mac_netp_ev_waitlist_notification_shown" + case .networkProtectionWaitlistNotificationTapped: + return "m_mac_netp_ev_waitlist_notification_launched" + case .networkProtectionWaitlistTermsAndConditionsDisplayed: + return "m_mac_netp_imp_terms" + case .networkProtectionWaitlistTermsAndConditionsAccepted: + return "m_mac_netp_ev_terms_accepted" + case .networkProtectionRemoteMessageDisplayed(let messageID): + return "m_mac_netp_remote_message_displayed_\(messageID)" + case .networkProtectionRemoteMessageDismissed(let messageID): + return "m_mac_netp_remote_message_dismissed_\(messageID)" + case .networkProtectionRemoteMessageOpened(let messageID): + return "m_mac_netp_remote_message_opened_\(messageID)" + case .networkProtectionEnabledOnSearch: + return "m_mac_netp_ev_enabled_on_search" + + // Sync + case .syncSignupDirect: + return "m_mac_sync_signup_direct" + case .syncSignupConnect: + return "m_mac_sync_signup_connect" + case .syncLogin: + return "m_mac_sync_login" + case .syncDaily: + return "m_mac_sync_daily" + case .syncDuckAddressOverride: + return "m_mac_sync_duck_address_override" + case .syncSuccessRateDaily: + return "m_mac_sync_success_rate_daily" + case .syncLocalTimestampResolutionTriggered(let feature): + return "m_mac_sync_\(feature.name)_local_timestamp_resolution_triggered" + case .syncBookmarksCountLimitExceededDaily: return "m_mac_sync_bookmarks_count_limit_exceeded_daily" + case .syncCredentialsCountLimitExceededDaily: return "m_mac_sync_credentials_count_limit_exceeded_daily" + case .syncBookmarksRequestSizeLimitExceededDaily: return "m_mac_sync_bookmarks_request_size_limit_exceeded_daily" + case .syncCredentialsRequestSizeLimitExceededDaily: return "m_mac_sync_credentials_request_size_limit_exceeded_daily" + + case .dataBrokerProtectionWaitlistUserActive: + return "m_mac_dbp_waitlist_user_active" + case .dataBrokerProtectionWaitlistEntryPointMenuItemDisplayed: + return "m_mac_dbp_imp_settings_entry_menu_item" + case .dataBrokerProtectionWaitlistIntroDisplayed: + return "m_mac_dbp_imp_intro_screen" + case .dataBrokerProtectionWaitlistNotificationShown: + return "m_mac_dbp_ev_waitlist_notification_shown" + case .dataBrokerProtectionWaitlistNotificationTapped: + return "m_mac_dbp_ev_waitlist_notification_launched" + case .dataBrokerProtectionWaitlistCardUITapped: + return "m_mac_dbp_ev_waitlist_card_ui_launched" + case .dataBrokerProtectionWaitlistTermsAndConditionsDisplayed: + return "m_mac_dbp_imp_terms" + case .dataBrokerProtectionWaitlistTermsAndConditionsAccepted: + return "m_mac_dbp_ev_terms_accepted" + case .dataBrokerProtectionErrorWhenFetchingSubscriptionAuthTokenAfterSignIn: + return "m_mac_dbp_error_when_fetching_subscription_auth_token_after_sign_in" + case .dataBrokerProtectionRemoteMessageDisplayed(let messageID): + return "m_mac_dbp_remote_message_displayed_\(messageID)" + case .dataBrokerProtectionRemoteMessageDismissed(let messageID): + return "m_mac_dbp_remote_message_dismissed_\(messageID)" + case .dataBrokerProtectionRemoteMessageOpened(let messageID): + return "m_mac_dbp_remote_message_opened_\(messageID)" + + case .dataBrokerEnableLoginItemDaily: return "m_mac_dbp_daily_login-item_enable" + case .dataBrokerDisableLoginItemDaily: return "m_mac_dbp_daily_login-item_disable" + case .dataBrokerResetLoginItemDaily: return "m_mac_dbp_daily_login-item_reset" + case .dataBrokerDisableAndDeleteDaily: return "m_mac_dbp_daily_disable-and-delete" + + case .networkProtectionGeoswitchingOpened: + return "m_mac_netp_imp_geoswitching_c" + case .networkProtectionGeoswitchingSetNearest: + return "m_mac_netp_ev_geoswitching_set_nearest" + case .networkProtectionGeoswitchingSetCustom: + return "m_mac_netp_ev_geoswitching_set_custom" + case .networkProtectionGeoswitchingNoLocations: + return "m_mac_netp_ev_geoswitching_no_locations" + + case .defaultRequestedFromHomepage: return "m_mac_default_requested_from_homepage" + case .defaultRequestedFromHomepageSetupView: return "m_mac_default_requested_from_homepage_setup_view" + case .defaultRequestedFromSettings: return "m_mac_default_requested_from_settings" + case .defaultRequestedFromOnboarding: return "m_mac_default_requested_from_onboarding" + + case .protectionToggledOffBreakageReport: return "m_mac_protection-toggled-off-breakage-report" + case .toggleProtectionsDailyCount: return "m_mac_toggle-protections-daily-count" + case .toggleReportDoNotSend: return "m_mac_toggle-report-do-not-send" + case .toggleReportDismiss: return "m_mac_toggle-report-dismiss" + + // Password Import Keychain Prompt + case .passwordImportKeychainPrompt: return "m_mac_password_import_keychain_prompt" + case .passwordImportKeychainPromptDenied: return "m_mac_password_import_keychain_prompt_denied" + + // DEBUG + case .assertionFailure: + return "assertion_failure" + + case .dbMakeDatabaseError: + return "database_make_database_error" + case .dbContainerInitializationError: + return "database_container_error" + case .dbInitializationError: + return "dbie" + case .dbSaveExcludedHTTPSDomainsError: + return "dbsw" + case .dbSaveBloomFilterError: + return "dbsb" + + case .configurationFetchError: + return "cfgfetch" + + case .trackerDataParseFailed: + return "tds_p" + case .trackerDataReloadFailed: + return "tds_r" + case .trackerDataCouldNotBeLoaded: + return "tds_l" + + case .privacyConfigurationParseFailed: + return "pcf_p" + case .privacyConfigurationReloadFailed: + return "pcf_r" + case .privacyConfigurationCouldNotBeLoaded: + return "pcf_l" + + case .fileStoreWriteFailed: + return "fswf" + case .fileMoveToDownloadsFailed: + return "df" + case .fileGetDownloadLocationFailed: + return "dl" + case .fileAccessRelatedItemFailed: + return "dari" + case .fileDownloadCreatePresentersFailed: + return "dfpf" + case .downloadResumeDataCodingFailed: + return "drdc" + + case .suggestionsFetchFailed: + return "sgf" + case .appOpenURLFailed: + return "url" + case .appStateRestorationFailed: + return "srf" + + case .contentBlockingErrorReportingIssue: + return "content_blocking_error_reporting_issue" + + case .contentBlockingCompilationFailed(let listType, let component): + let componentString: String + switch component { + case .tds: + componentString = "fetched_tds" + case .allowlist: + componentString = "allow_list" + case .tempUnprotected: + componentString = "temp_list" + case .localUnprotected: + componentString = "unprotected_list" + case .fallbackTds: + componentString = "fallback_tds" + } + return "content_blocking_\(listType)_compilation_error_\(componentString)" + + case .contentBlockingCompilationTime: + return "content_blocking_compilation_time" + + case .secureVaultInitError: + return "secure_vault_init_error" + case .secureVaultError: + return "secure_vault_error" + + case .feedbackReportingFailed: + return "feedback_reporting_failed" + + case .blankNavigationOnBurnFailed: + return "blank_navigation_on_burn_failed" + + case .historyRemoveFailed: + return "history_remove_failed" + case .historyReloadFailed: + return "history_reload_failed" + case .historyCleanEntriesFailed: + return "history_clean_entries_failed" + case .historyCleanVisitsFailed: + return "history_clean_visits_failed" + case .historySaveFailed: + return "history_save_failed" + case .historyInsertVisitFailed: + return "history_insert_visit_failed" + case .historyRemoveVisitsFailed: + return "history_remove_visits_failed" + + case .emailAutofillKeychainError: + return "email_autofill_keychain_error" + + case .bookmarksStoreRootFolderMigrationFailed: + return "bookmarks_store_root_folder_migration_failed" + case .bookmarksStoreFavoritesFolderMigrationFailed: + return "bookmarks_store_favorites_folder_migration_failed" + + case .adAttributionCompilationFailedForAttributedRulesList: + return "ad_attribution_compilation_failed_for_attributed_rules_list" + case .adAttributionGlobalAttributedRulesDoNotExist: + return "ad_attribution_global_attributed_rules_do_not_exist" + case .adAttributionDetectionHeuristicsDidNotMatchDomain: + return "ad_attribution_detection_heuristics_did_not_match_domain" + case .adAttributionLogicUnexpectedStateOnRulesCompiled: + return "ad_attribution_logic_unexpected_state_on_rules_compiled" + case .adAttributionLogicUnexpectedStateOnInheritedAttribution: + return "ad_attribution_logic_unexpected_state_on_inherited_attribution_2" + case .adAttributionLogicUnexpectedStateOnRulesCompilationFailed: + return "ad_attribution_logic_unexpected_state_on_rules_compilation_failed" + case .adAttributionDetectionInvalidDomainInParameter: + return "ad_attribution_detection_invalid_domain_in_parameter" + case .adAttributionLogicRequestingAttributionTimedOut: + return "ad_attribution_logic_requesting_attribution_timed_out" + case .adAttributionLogicWrongVendorOnSuccessfulCompilation: + return "ad_attribution_logic_wrong_vendor_on_successful_compilation" + case .adAttributionLogicWrongVendorOnFailedCompilation: + return "ad_attribution_logic_wrong_vendor_on_failed_compilation" + + case .webKitDidTerminate: + return "webkit_did_terminate" + + case .removedInvalidBookmarkManagedObjects: + return "removed_invalid_bookmark_managed_objects" + + case .bitwardenNotResponding: + return "bitwarden_not_responding" + // case .bitwardenRespondedCannotDecryptUnique: + // return "bitwarden_responded_cannot_decrypt_unique" + case .bitwardenHandshakeFailed: + return "bitwarden_handshake_failed" + case .bitwardenDecryptionOfSharedKeyFailed: + return "bitwarden_decryption_of_shared_key_failed" + case .bitwardenStoringOfTheSharedKeyFailed: + return "bitwarden_storing_of_the_shared_key_failed" + case .bitwardenCredentialRetrievalFailed: + return "bitwarden_credential_retrieval_failed" + case .bitwardenCredentialCreationFailed: + return "bitwarden_credential_creation_failed" + case .bitwardenCredentialUpdateFailed: + return "bitwarden_credential_update_failed" + case .bitwardenRespondedWithError: + return "bitwarden_responded_with_error" + case .bitwardenNoActiveVault: + return "bitwarden_no_active_vault" + case .bitwardenParsingFailed: + return "bitwarden_parsing_failed" + case .bitwardenStatusParsingFailed: + return "bitwarden_status_parsing_failed" + case .bitwardenHmacComparisonFailed: + return "bitwarden_hmac_comparison_failed" + case .bitwardenDecryptionFailed: + return "bitwarden_decryption_failed" + case .bitwardenSendingOfMessageFailed: + return "bitwarden_sending_of_message_failed" + case .bitwardenSharedKeyInjectionFailed: + return "bitwarden_shared_key_injection_failed" + + case .updaterAborted: + return "updater_aborted" + case .userSelectedToSkipUpdate: + return "user_selected_to_skip_update" + case .userSelectedToInstallUpdate: + return "user_selected_to_install_update" + case .userSelectedToDismissUpdate: + return "user_selected_to_dismiss_update" + + case .faviconDecryptionFailedUnique: + return "favicon_decryption_failed_unique" + case .downloadListItemDecryptionFailedUnique: + return "download_list_item_decryption_failed_unique" + case .historyEntryDecryptionFailedUnique: + return "history_entry_decryption_failed_unique" + case .permissionDecryptionFailedUnique: + return "permission_decryption_failed_unique" + + case .missingParent: return "bookmark_missing_parent" + case .bookmarksSaveFailed: return "bookmarks_save_failed" + case .bookmarksSaveFailedOnImport: return "bookmarks_save_failed_on_import" + + case .bookmarksCouldNotLoadDatabase: return "bookmarks_could_not_load_database" + case .bookmarksCouldNotPrepareDatabase: return "bookmarks_could_not_prepare_database" + case .bookmarksMigrationAlreadyPerformed: return "bookmarks_migration_already_performed" + case .bookmarksMigrationFailed: return "bookmarks_migration_failed" + case .bookmarksMigrationCouldNotPrepareDatabase: return "bookmarks_migration_could_not_prepare_database" + case .bookmarksMigrationCouldNotPrepareDatabaseOnFailedMigration: + return "bookmarks_migration_could_not_prepare_database_on_failed_migration" + case .bookmarksMigrationCouldNotRemoveOldStore: return "bookmarks_migration_could_not_remove_old_store" + case .bookmarksMigrationCouldNotPrepareMultipleFavoriteFolders: + return "bookmarks_migration_could_not_prepare_multiple_favorite_folders" + + case .syncSentUnauthenticatedRequest: return "sync_sent_unauthenticated_request" + case .syncMetadataCouldNotLoadDatabase: return "sync_metadata_could_not_load_database" + case .syncBookmarksProviderInitializationFailed: return "sync_bookmarks_provider_initialization_failed" + case .syncBookmarksFailed: return "sync_bookmarks_failed" + case .syncCredentialsProviderInitializationFailed: return "sync_credentials_provider_initialization_failed" + case .syncCredentialsFailed: return "sync_credentials_failed" + case .syncSettingsFailed: return "sync_settings_failed" + case .syncSettingsMetadataUpdateFailed: return "sync_settings_metadata_update_failed" + case .syncSignupError: return "sync_signup_error" + case .syncLoginError: return "sync_login_error" + case .syncLogoutError: return "sync_logout_error" + case .syncUpdateDeviceError: return "sync_update_device_error" + case .syncRemoveDeviceError: return "sync_remove_device_error" + case .syncDeleteAccountError: return "sync_delete_account_error" + case .syncLoginExistingAccountError: return "sync_login_existing_account_error" + case .syncCannotCreateRecoveryPDF: return "sync_cannot_create_recovery_pdf" + + case .bookmarksCleanupFailed: return "bookmarks_cleanup_failed" + case .bookmarksCleanupAttemptedWhileSyncWasEnabled: return "bookmarks_cleanup_attempted_while_sync_was_enabled" + case .favoritesCleanupFailed: return "favorites_cleanup_failed" + case .bookmarksFaviconsFetcherStateStoreInitializationFailed: return "bookmarks_favicons_fetcher_state_store_initialization_failed" + case .bookmarksFaviconsFetcherFailed: return "bookmarks_favicons_fetcher_failed" + + case .credentialsDatabaseCleanupFailed: return "credentials_database_cleanup_failed" + case .credentialsCleanupAttemptedWhileSyncWasEnabled: return "credentials_cleanup_attempted_while_sync_was_enabled" + + case .invalidPayload(let configuration): return "m_d_\(configuration.rawValue)_invalid_payload".lowercased() + + case .burnerTabMisplaced: return "burner_tab_misplaced" + + case .networkProtectionRemoteMessageFetchingFailed: return "netp_remote_message_fetching_failed" + case .networkProtectionRemoteMessageStorageFailed: return "netp_remote_message_storage_failed" + + case .dataBrokerProtectionRemoteMessageFetchingFailed: return "dbp_remote_message_fetching_failed" + case .dataBrokerProtectionRemoteMessageStorageFailed: return "dbp_remote_message_storage_failed" + + case .loginItemUpdateError: return "login-item_update-error" + } + } + + var error: (any Error)? { + switch self { + case .dbMakeDatabaseError(let error), + .dbContainerInitializationError(let error), + .dbInitializationError(let error), + .dbSaveExcludedHTTPSDomainsError(let error?), + .dbSaveBloomFilterError(let error?), + .configurationFetchError(let error), + .secureVaultInitError(let error), + .secureVaultError(let error), + .emailAutofillKeychainError(let error), + .bitwardenRespondedWithError(let error), + .syncSignupError(let error), + .syncLoginError(let error), + .syncLogoutError(let error), + .syncUpdateDeviceError(let error), + .syncRemoveDeviceError(let error), + .syncDeleteAccountError(let error), + .syncLoginExistingAccountError(let error), + .bookmarksCouldNotLoadDatabase(let error?): + return error + default: return nil + } + } + + var parameters: [String: String]? { + return nil + } +} + +public enum CompileRulesListType: String, CustomStringConvertible { + + public var description: String { rawValue } + + case tds = "tracker_data" + case clickToLoad = "click_to_load" + case blockingAttribution = "blocking_attribution" + case attributed = "attributed" + case unknown = "unknown" + +} + +enum OnboardingShown: String, CustomStringConvertible { + var description: String { rawValue } + + init(_ value: Bool) { + if value { + self = .onboardingShown + } else { + self = .regularNavigation + } + } + case onboardingShown = "onboarding-shown" + case regularNavigation = "regular-nav" +} + +enum WaitResult: String, CustomStringConvertible { + var description: String { rawValue } + + case closed + case quit + case success +} + +enum CompileRulesWaitTime: String, CustomStringConvertible { + var description: String { rawValue } + + case noWait = "0" + case lessThan1s = "1" + case lessThan5s = "5" + case lessThan10s = "10" + case lessThan20s = "20" + case lessThan40s = "40" + case more = "more" +} + +enum FormAutofillKind: String, CustomStringConvertible { + var description: String { rawValue } + + case password + case card + case identity +} + +enum FireButtonOption: String, CustomStringConvertible { + var description: String { rawValue } + + case tab + case window + case allSites = "all-sites" +} diff --git a/DuckDuckGo/Statistics/DailyPixel.swift b/DuckDuckGo/Statistics/Pixel Legacy/DailyPixel.swift similarity index 100% rename from DuckDuckGo/Statistics/DailyPixel.swift rename to DuckDuckGo/Statistics/Pixel Legacy/DailyPixel.swift diff --git a/DuckDuckGo/Statistics/Pixel.swift b/DuckDuckGo/Statistics/Pixel Legacy/Pixel.swift similarity index 100% rename from DuckDuckGo/Statistics/Pixel.swift rename to DuckDuckGo/Statistics/Pixel Legacy/Pixel.swift diff --git a/DuckDuckGo/Statistics/PixelArguments.swift b/DuckDuckGo/Statistics/Pixel Legacy/PixelArguments.swift similarity index 100% rename from DuckDuckGo/Statistics/PixelArguments.swift rename to DuckDuckGo/Statistics/Pixel Legacy/PixelArguments.swift diff --git a/DuckDuckGo/Statistics/PixelDataModel.xcdatamodeld/PixelDataModel.xcdatamodel/contents b/DuckDuckGo/Statistics/Pixel Legacy/PixelDataModel.xcdatamodeld/PixelDataModel.xcdatamodel/contents similarity index 100% rename from DuckDuckGo/Statistics/PixelDataModel.xcdatamodeld/PixelDataModel.xcdatamodel/contents rename to DuckDuckGo/Statistics/Pixel Legacy/PixelDataModel.xcdatamodeld/PixelDataModel.xcdatamodel/contents diff --git a/DuckDuckGo/Statistics/PixelDataRecord.swift b/DuckDuckGo/Statistics/Pixel Legacy/PixelDataRecord.swift similarity index 100% rename from DuckDuckGo/Statistics/PixelDataRecord.swift rename to DuckDuckGo/Statistics/Pixel Legacy/PixelDataRecord.swift diff --git a/DuckDuckGo/Statistics/PixelDataStore.swift b/DuckDuckGo/Statistics/Pixel Legacy/PixelDataStore.swift similarity index 100% rename from DuckDuckGo/Statistics/PixelDataStore.swift rename to DuckDuckGo/Statistics/Pixel Legacy/PixelDataStore.swift diff --git a/DuckDuckGo/Statistics/PixelEvent.swift b/DuckDuckGo/Statistics/Pixel Legacy/PixelEvent.swift similarity index 100% rename from DuckDuckGo/Statistics/PixelEvent.swift rename to DuckDuckGo/Statistics/Pixel Legacy/PixelEvent.swift diff --git a/DuckDuckGo/Statistics/PixelParameters.swift b/DuckDuckGo/Statistics/Pixel Legacy/PixelParameters.swift similarity index 100% rename from DuckDuckGo/Statistics/PixelParameters.swift rename to DuckDuckGo/Statistics/Pixel Legacy/PixelParameters.swift diff --git a/DuckDuckGo/Statistics/PrivacyProPixel.swift b/DuckDuckGo/Statistics/PrivacyProPixel.swift new file mode 100644 index 0000000000..50f02bd016 --- /dev/null +++ b/DuckDuckGo/Statistics/PrivacyProPixel.swift @@ -0,0 +1,128 @@ +// +// PrivacyProPixel.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 +import PixelKit + +// swiftlint:disable private_over_fileprivate +#if APPSTORE +fileprivate let appDistribution = "store" +#else +fileprivate let appDistribution = "direct" +#endif +// swiftlint:enable private_over_fileprivate + +enum PrivacyProPixel: PixelKitEventV2 { + // Subscription + case privacyProFeatureEnabled + case privacyProBetaUserThankYouVPN + case privacyProBetaUserThankYouDBP + case privacyProSubscriptionActive + case privacyProOfferScreenImpression + case privacyProPurchaseAttempt + case privacyProPurchaseFailure + case privacyProPurchaseFailureStoreError + case privacyProPurchaseFailureBackendError + case privacyProPurchaseFailureAccountNotCreated + case privacyProPurchaseSuccess + case privacyProRestorePurchaseOfferPageEntry + case privacyProRestorePurchaseClick + case privacyProRestorePurchaseSettingsMenuEntry + case privacyProRestorePurchaseEmailStart + case privacyProRestorePurchaseStoreStart + case privacyProRestorePurchaseEmailSuccess + case privacyProRestorePurchaseStoreSuccess + case privacyProRestorePurchaseStoreFailureNotFound + case privacyProRestorePurchaseStoreFailureOther + case privacyProRestoreAfterPurchaseAttempt + case privacyProSubscriptionActivated + case privacyProWelcomeAddDevice + case privacyProSettingsAddDevice + case privacyProAddDeviceEnterEmail + case privacyProWelcomeVPN + case privacyProWelcomePersonalInformationRemoval + case privacyProWelcomeIdentityRestoration + case privacyProSubscriptionSettings + case privacyProVPNSettings + case privacyProPersonalInformationRemovalSettings + case privacyProIdentityRestorationSettings + case privacyProSubscriptionManagementEmail + case privacyProSubscriptionManagementPlanBilling + case privacyProSubscriptionManagementRemoval + case privacyProPurchaseStripeSuccess + // Web pixels + case privacyProOfferMonthlyPriceClick + case privacyProOfferYearlyPriceClick + case privacyProAddEmailSuccess + case privacyProWelcomeFAQClick + + var name: String { + switch self { + case .privacyProFeatureEnabled: return + "m_mac_\(appDistribution)_privacy-pro_feature_enabled" + case .privacyProBetaUserThankYouVPN: return "m_mac_\(appDistribution)_privacy-pro_promotion-dialog_shown_vpn" + case .privacyProBetaUserThankYouDBP: return "m_mac_\(appDistribution)_privacy-pro_promotion-dialog_shown_dbp" + case .privacyProSubscriptionActive: return "m_mac_\(appDistribution)_privacy-pro_app_subscription_active" + case .privacyProOfferScreenImpression: return "m_mac_\(appDistribution)_privacy-pro_offer_screen_impression" + case .privacyProPurchaseAttempt: return "m_mac_\(appDistribution)_privacy-pro_terms-conditions_subscribe_click" + case .privacyProPurchaseFailure: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_failure_other" + case .privacyProPurchaseFailureStoreError: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_failure_store" + case .privacyProPurchaseFailureBackendError: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_failure_backend" + case .privacyProPurchaseFailureAccountNotCreated: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_failure_account-creation" + case .privacyProPurchaseSuccess: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_success" + case .privacyProRestorePurchaseOfferPageEntry: return "m_mac_\(appDistribution)_privacy-pro_offer_restore-purchase_click" + case .privacyProRestorePurchaseClick: return "m_mac_\(appDistribution)_privacy-pro_settings_restore-purchase_click" + case .privacyProRestorePurchaseSettingsMenuEntry: return "m_mac_\(appDistribution)_privacy-pro_settings_restore-purchase_click" + case .privacyProRestorePurchaseEmailStart: return "m_mac_\(appDistribution)_privacy-pro_activate-subscription_enter-email_click" + case .privacyProRestorePurchaseStoreStart: return "m_mac_\(appDistribution)_privacy-pro_activate-subscription_restore-purchase_click" + case .privacyProRestorePurchaseEmailSuccess: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-restore-using-email_success" + case .privacyProRestorePurchaseStoreSuccess: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-restore-using-store_success" + case .privacyProRestorePurchaseStoreFailureNotFound: return "m_mac_\(appDistribution)_privacy-pro_subscription-restore-using-store_failure_not-found" + case .privacyProRestorePurchaseStoreFailureOther: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-restore-using-store_failure_other" + case .privacyProRestoreAfterPurchaseAttempt: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-restore-after-purchase-attempt_success" + case .privacyProSubscriptionActivated: return "m_mac_\(appDistribution)_privacy-pro_app_subscription_activated_u" + case .privacyProWelcomeAddDevice: return "m_mac_\(appDistribution)_privacy-pro_welcome_add-device_click_u" + case .privacyProSettingsAddDevice: return "m_mac_\(appDistribution)_privacy-pro_settings_add-device_click" + case .privacyProAddDeviceEnterEmail: return "m_mac_\(appDistribution)_privacy-pro_add-device_enter-email_click" + case .privacyProWelcomeVPN: return "m_mac_\(appDistribution)_privacy-pro_welcome_vpn_click_u" + case .privacyProWelcomePersonalInformationRemoval: return "m_mac_\(appDistribution)_privacy-pro_welcome_personal-information-removal_click_u" + case .privacyProWelcomeIdentityRestoration: return "m_mac_\(appDistribution)_privacy-pro_welcome_identity-theft-restoration_click_u" + case .privacyProSubscriptionSettings: return "m_mac_\(appDistribution)_privacy-pro_settings_screen_impression" + case .privacyProVPNSettings: return "m_mac_\(appDistribution)_privacy-pro_settings_vpn_click" + case .privacyProPersonalInformationRemovalSettings: return "m_mac_\(appDistribution)_privacy-pro_settings_personal-information-removal_click" + case .privacyProIdentityRestorationSettings: return "m_mac_\(appDistribution)_privacy-pro_settings_identity-theft-restoration_click" + case .privacyProSubscriptionManagementEmail: return "m_mac_\(appDistribution)_privacy-pro_manage-email_edit_click" + case .privacyProSubscriptionManagementPlanBilling: return "m_mac_\(appDistribution)_privacy-pro_settings_change-plan-or-billing_click" + case .privacyProSubscriptionManagementRemoval: return "m_mac_\(appDistribution)_privacy-pro_settings_remove-from-device_click" + case .privacyProPurchaseStripeSuccess: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_stripe_success" + // Web + case .privacyProOfferMonthlyPriceClick: return "m_mac_\(appDistribution)_privacy-pro_offer_monthly-price_click" + case .privacyProOfferYearlyPriceClick: return "m_mac_\(appDistribution)_privacy-pro_offer_yearly-price_click" + case .privacyProAddEmailSuccess: return "m_mac_\(appDistribution)_privacy-pro_app_add-email_success_u" + case .privacyProWelcomeFAQClick: return "m_mac_\(appDistribution)_privacy-pro_welcome_faq_click_u" + } + } + + var error: (any Error)? { + return nil + } + + var parameters: [String : String]? { + return nil + } +} diff --git a/DuckDuckGo/Tab/Model/Tab.swift b/DuckDuckGo/Tab/Model/Tab.swift index 6fd7bffd3a..ac38a79a3d 100644 --- a/DuckDuckGo/Tab/Model/Tab.swift +++ b/DuckDuckGo/Tab/Model/Tab.swift @@ -26,6 +26,7 @@ import UserScript import WebKit import History import PrivacyDashboard +import PixelKit #if SUBSCRIPTION import Subscription @@ -1467,7 +1468,7 @@ extension Tab/*: NavigationResponder*/ { // to be moved to Tab+Navigation.swift #if NETWORK_PROTECTION if navigation.url.isDuckDuckGoSearch, tunnelController?.isConnected == true { - DailyPixel.fire(pixel: .networkProtectionEnabledOnSearch, frequency: .dailyAndCount) + PixelKit.fire(GeneralPixel.networkProtectionEnabledOnSearch, frequency: .dailyAndContinuous) } #endif } diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift index 1a3605a102..75d08cb0a1 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift @@ -20,6 +20,7 @@ import Foundation import Subscription import SubscriptionUI import enum StoreKit.StoreKitError +import PixelKit @available(macOS 12.0, *) struct SubscriptionAppStoreRestorer { @@ -66,13 +67,12 @@ struct SubscriptionAppStoreRestorer { switch result { case .success: - DailyPixel.fire(pixel: .privacyProRestorePurchaseStoreSuccess, frequency: .dailyAndCount) - + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreSuccess, frequency: .dailyAndContinuous) case .failure(let error): switch error { case .missingAccountOrTransactions: break default: - DailyPixel.fire(pixel: .privacyProRestorePurchaseStoreFailureOther, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreFailureOther, frequency: .dailyAndContinuous) } switch error { diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionErrorReporter.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionErrorReporter.swift index bdce936804..2a3de620ea 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionErrorReporter.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionErrorReporter.swift @@ -18,6 +18,7 @@ import Foundation import Common +import PixelKit enum SubscriptionError: Error { case purchaseFailed, @@ -60,7 +61,7 @@ struct SubscriptionErrorReporter { case .failedToRestorePastPurchase: isStoreError = true case .subscriptionNotFound: - DailyPixel.fire(pixel: .privacyProRestorePurchaseStoreFailureNotFound, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreFailureNotFound, frequency: .dailyAndContinuous) isStoreError = true case .subscriptionExpired: isStoreError = true @@ -69,17 +70,17 @@ struct SubscriptionErrorReporter { isBackendError = true case .cancelledByUser: break case .accountCreationFailed: - DailyPixel.fire(pixel: .privacyProPurchaseFailureAccountNotCreated, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailureAccountNotCreated, frequency: .dailyAndContinuous) case .activeSubscriptionAlreadyPresent: break case .generalError: break } if isStoreError { - DailyPixel.fire(pixel: .privacyProPurchaseFailureStoreError, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailureStoreError, frequency: .dailyAndContinuous) } if isBackendError { - DailyPixel.fire(pixel: .privacyProPurchaseFailureBackendError, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailureBackendError, frequency: .dailyAndContinuous) } } } diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift index 76cdb94e7c..f4fe52b9b9 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift @@ -27,6 +27,7 @@ import WebKit import UserScript import Subscription import SubscriptionUI +import PixelKit public extension Notification.Name { static let subscriptionPageCloseAndOpenPreferences = Notification.Name("com.duckduckgo.subscriptionPage.CloseAndOpenPreferences") @@ -152,7 +153,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { func setSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { - DailyPixel.fire(pixel: .privacyProRestorePurchaseEmailSuccess, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailSuccess, frequency: .dailyAndContinuous) guard let subscriptionValues: SubscriptionValues = DecodableHelper.decode(from: params) else { assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") @@ -211,8 +212,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { // swiftlint:disable:next function_body_length cyclomatic_complexity func subscriptionSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { - DailyPixel.fire(pixel: .privacyProPurchaseAttempt, frequency: .dailyAndCount) - + PixelKit.fire(PrivacyProPixel.privacyProPurchaseAttempt, frequency: .dailyAndContinuous) struct SubscriptionSelection: Decodable { let id: String } @@ -242,9 +242,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { // Check for active subscriptions if await PurchaseManager.hasActiveSubscription() { - - Pixel.fire(.privacyProRestoreAfterPurchaseAttempt) - + PixelKit.fire(PrivacyProPixel.privacyProRestoreAfterPurchaseAttempt) os_log(.info, log: .subscription, "[Purchase] Found active subscription during purchase") SubscriptionErrorReporter.report(subscriptionActivationError: .hasActiveSubscription) await WindowControllersManager.shared.lastKeyMainWindowController?.showSubscriptionFoundAlert(originalMessage: message) @@ -290,8 +288,8 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { switch await AppStorePurchaseFlow.completeSubscriptionPurchase(with: purchaseTransactionJWS, subscriptionAppGroup: subscriptionAppGroup) { case .success(let purchaseUpdate): os_log(.info, log: .subscription, "[Purchase] Purchase complete") - DailyPixel.fire(pixel: .privacyProPurchaseSuccess, frequency: .dailyAndCount) - Pixel.fire(.privacyProSubscriptionActivated, limitTo: .initial) + PixelKit.fire(PrivacyProPixel.privacyProPurchaseSuccess, frequency: .dailyAndContinuous) + PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActivated, frequency: .justOnce) await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: purchaseUpdate) case .failure(let error): switch error { @@ -365,7 +363,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { }, uiActionHandler: { event in switch event { case .activateAddEmailClick: - DailyPixel.fire(pixel: .privacyProRestorePurchaseEmailStart, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailStart, frequency: .dailyAndContinuous) default: break } @@ -424,20 +422,19 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { await StripePurchaseFlow.completeSubscriptionPurchase(subscriptionAppGroup: subscriptionAppGroup) await mainViewController?.dismiss(progressViewController) - DailyPixel.fire(pixel: .privacyProPurchaseStripeSuccess, frequency: .dailyAndCount) - + PixelKit.fire(PrivacyProPixel.privacyProPurchaseStripeSuccess, frequency: .dailyAndContinuous) return [String: String]() // cannot be nil, the web app expect something back before redirecting the user to the final page } // MARK: Pixel related actions func subscriptionsMonthlyPriceClicked(params: Any, original: WKScriptMessage) async -> Encodable? { - Pixel.fire(.privacyProOfferMonthlyPriceClick) + PixelKit.fire(PrivacyProPixel.privacyProOfferMonthlyPriceClick) return nil } func subscriptionsYearlyPriceClicked(params: Any, original: WKScriptMessage) async -> Encodable? { - Pixel.fire(.privacyProOfferYearlyPriceClick) + PixelKit.fire(PrivacyProPixel.privacyProOfferYearlyPriceClick) return nil } @@ -447,12 +444,12 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } func subscriptionsAddEmailSuccess(params: Any, original: WKScriptMessage) async -> Encodable? { - Pixel.fire(.privacyProAddEmailSuccess, limitTo: .initial) + PixelKit.fire(PrivacyProPixel.privacyProAddEmailSuccess, frequency: .justOnce) return nil } func subscriptionsWelcomeFaqClicked(params: Any, original: WKScriptMessage) async -> Encodable? { - Pixel.fire(.privacyProWelcomeFAQClick, limitTo: .initial) + PixelKit.fire(PrivacyProPixel.privacyProWelcomeFAQClick, frequency: .justOnce) return nil } @@ -477,7 +474,7 @@ extension MainWindowController { @MainActor func showSomethingWentWrongAlert() { - DailyPixel.fire(pixel: .privacyProPurchaseFailure, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailure, frequency: .dailyAndContinuous) guard let window else { return } window.show(.somethingWentWrongAlert()) @@ -489,7 +486,7 @@ extension MainWindowController { window.show(.subscriptionNotFoundAlert(), firstButtonAction: { WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) - Pixel.fire(.privacyProOfferScreenImpression) + PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) }) } @@ -499,7 +496,7 @@ extension MainWindowController { window.show(.subscriptionInactiveAlert(), firstButtonAction: { WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) - Pixel.fire(.privacyProOfferScreenImpression) + PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) }) } @@ -513,7 +510,7 @@ extension MainWindowController { let result = await AppStoreRestoreFlow.restoreAccountFromPastPurchase(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) switch result { case .success: - DailyPixel.fire(pixel: .privacyProRestorePurchaseStoreSuccess, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreSuccess, frequency: .dailyAndContinuous) case .failure: break } originalMessage.webView?.reload() diff --git a/DuckDuckGo/Waitlist/Views/WaitlistThankYouPromptPresenter.swift b/DuckDuckGo/Waitlist/Views/WaitlistThankYouPromptPresenter.swift index 2237c32222..900de13ea8 100644 --- a/DuckDuckGo/Waitlist/Views/WaitlistThankYouPromptPresenter.swift +++ b/DuckDuckGo/Waitlist/Views/WaitlistThankYouPromptPresenter.swift @@ -19,6 +19,7 @@ import AppKit import Foundation import Subscription +import PixelKit final class WaitlistThankYouPromptPresenter { @@ -55,7 +56,7 @@ final class WaitlistThankYouPromptPresenter { // Wiring this here since it's mostly useful for rolling out PrivacyPro, and should // go away once PPro is fully rolled out. if DefaultSubscriptionFeatureAvailability().isFeatureAvailable { - DailyPixel.fire(pixel: .privacyProFeatureEnabled, frequency: .dailyOnly) + PixelKit.fire(PrivacyProPixel.privacyProFeatureEnabled, frequency: .dailyOnly) } guard canShowPromptCheck() else { @@ -64,11 +65,11 @@ final class WaitlistThankYouPromptPresenter { if isPIRBetaTester() { saveDidShowPromptCheck() - DailyPixel.fire(pixel: Pixel.Event.privacyProBetaUserThankYouDBP, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProBetaUserThankYouDBP, frequency: .dailyAndContinuous) presentPIRThankYouPrompt(in: window) } else if isVPNBetaTester() { saveDidShowPromptCheck() - DailyPixel.fire(pixel: Pixel.Event.privacyProBetaUserThankYouVPN, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProBetaUserThankYouVPN, frequency: .dailyAndContinuous) presentVPNThankYouPrompt(in: window) } } diff --git a/DuckDuckGo/Waitlist/Waitlist.swift b/DuckDuckGo/Waitlist/Waitlist.swift index 8b91f0b8de..0a9979769d 100644 --- a/DuckDuckGo/Waitlist/Waitlist.swift +++ b/DuckDuckGo/Waitlist/Waitlist.swift @@ -25,6 +25,7 @@ import NetworkProtection import BrowserServicesKit import Common import Subscription +import PixelKit protocol WaitlistConstants { static var identifier: String { get } @@ -223,7 +224,7 @@ struct NetworkProtectionWaitlist: Waitlist { try await networkProtectionCodeRedemption.redeem(inviteCode) NotificationCenter.default.post(name: .networkProtectionWaitlistAccessChanged, object: nil) sendInviteCodeAvailableNotification { - DailyPixel.fire(pixel: .networkProtectionWaitlistNotificationShown, frequency: .dailyAndCount) + PixelKit.fire(GeneralPixel.networkProtectionWaitlistNotificationShown, frequency: .dailyAndContinuous) } completion(nil) } catch { diff --git a/DuckDuckGo/Waitlist/WaitlistTermsAndConditionsActionHandler.swift b/DuckDuckGo/Waitlist/WaitlistTermsAndConditionsActionHandler.swift index 0c0f8bb0cb..7b3a90edcb 100644 --- a/DuckDuckGo/Waitlist/WaitlistTermsAndConditionsActionHandler.swift +++ b/DuckDuckGo/Waitlist/WaitlistTermsAndConditionsActionHandler.swift @@ -20,6 +20,7 @@ import Foundation import UserNotifications +import PixelKit protocol WaitlistTermsAndConditionsActionHandler { var acceptedTermsAndConditions: Bool { get } @@ -36,7 +37,7 @@ struct NetworkProtectionWaitlistTermsAndConditionsActionHandler: WaitlistTermsAn var acceptedTermsAndConditions: Bool func didShow() { - DailyPixel.fire(pixel: .networkProtectionWaitlistTermsAndConditionsDisplayed, frequency: .dailyAndCount) + PixelKit.fire(GeneralPixel.networkProtectionWaitlistTermsAndConditionsDisplayed, frequency: .dailyAndContinuous) } mutating func didAccept() { @@ -44,7 +45,7 @@ struct NetworkProtectionWaitlistTermsAndConditionsActionHandler: WaitlistTermsAn // Remove delivered NetP notifications in case the user didn't click them. UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [NetworkProtectionWaitlist.notificationIdentifier]) - DailyPixel.fire(pixel: .networkProtectionWaitlistTermsAndConditionsAccepted, frequency: .dailyAndCount) + PixelKit.fire(GeneralPixel.networkProtectionWaitlistTermsAndConditionsAccepted, frequency: .dailyAndContinuous) } } @@ -57,15 +58,14 @@ struct DataBrokerProtectionWaitlistTermsAndConditionsActionHandler: WaitlistTerm var acceptedTermsAndConditions: Bool func didShow() { - DataBrokerProtectionExternalWaitlistPixels.fire(pixel: .dataBrokerProtectionWaitlistTermsAndConditionsDisplayed, frequency: .dailyAndCount) + PixelKit.fire(GeneralPixel.dataBrokerProtectionWaitlistTermsAndConditionsDisplayed, frequency: .dailyAndContinuous) } mutating func didAccept() { acceptedTermsAndConditions = true // Remove delivered NetP notifications in case the user didn't click them. UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [DataBrokerProtectionWaitlist.notificationIdentifier]) - - DataBrokerProtectionExternalWaitlistPixels.fire(pixel: .dataBrokerProtectionWaitlistTermsAndConditionsAccepted, frequency: .dailyAndCount) + PixelKit.fire(GeneralPixel.dataBrokerProtectionWaitlistTermsAndConditionsAccepted, frequency: .dailyAndContinuous) } } diff --git a/DuckDuckGo/YoutubePlayer/YoutubeOverlayUserScript.swift b/DuckDuckGo/YoutubePlayer/YoutubeOverlayUserScript.swift index 60906938bc..46cb058dc4 100644 --- a/DuckDuckGo/YoutubePlayer/YoutubeOverlayUserScript.swift +++ b/DuckDuckGo/YoutubePlayer/YoutubeOverlayUserScript.swift @@ -20,6 +20,7 @@ import Foundation import WebKit import Common import UserScript +import PixelKit protocol YoutubeOverlayUserScriptDelegate: AnyObject { func youtubeOverlayUserScriptDidRequestDuckPlayer(with url: URL, in webView: WKWebView) @@ -113,16 +114,16 @@ extension YoutubeOverlayUserScript { if pixelName == "play.use" || pixelName == "play.do_not_use" { duckPlayerPreferences.youtubeOverlayAnyButtonPressed = true if pixelName == "play.use" { - Pixel.fire(.duckPlayerViewFromYoutubeViaMainOverlay) + PixelKit.fire(GeneralPixel.duckPlayerViewFromYoutubeViaMainOverlay) } } // Temporary pixel for first time user uses Duck Player - if !Pixel.isNewUser { - return nil - } +// if !Pixel.isNewUser { // TODO: reimplement Pixel.isNewUser +// return nil +// } if pixelName == "play.use" { - Pixel.fire(.watchInDuckPlayerInitial, limitTo: .initial) + PixelKit.fire(GeneralPixel.watchInDuckPlayerInitial, frequency: .justOnce) } return nil } diff --git a/LocalPackages/PixelKit/Sources/PixelKit/Extensions/URL+PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKit/Extensions/URL+PixelKit.swift index 98bb77c2c4..7b17318cb4 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/Extensions/URL+PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/Extensions/URL+PixelKit.swift @@ -22,7 +22,7 @@ extension URL { static let pixelBase = ProcessInfo.processInfo.environment["PIXEL_BASE_URL", default: "https://improving.duckduckgo.com"] - public static func pixelUrl(forPixelNamed pixelName: String) -> URL { + public static func pixelUrl(forPixelNamed pixelName: String) -> URL { // TODO: make private or remove URL extension let urlString = "\(Self.pixelBase)/t/\(pixelName)" return URL(string: urlString)! } diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift index ea94093fcb..6db95c4e31 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift @@ -30,15 +30,15 @@ public final class PixelKit { case standard /// Sent only once ever. The timestamp for this pixel is stored. Name for pixels of this type must end with `_u`. - case justOnce + case justOnce // TODO: Rename unique /// Sent once per day. The last timestamp for this pixel is stored and compared to the current date. Pixels of this type will have `_d` appended to their name. - case dailyOnly + case dailyOnly // TODO: Rename daily /// Sent once per day with a `_d` suffix, in addition to every time it is called with a `_c` suffix. /// This means a pixel will get sent twice the first time it is called per-day, and subsequent calls that day will only send the `_c` variant. /// This is useful in situations where pixels receive spikes in volume, as the daily pixel can be used to determine how many users are actually affected. - case dailyAndContinuous + case dailyAndContinuous // TODO: Rename dailyAndCount } public enum Header { @@ -204,7 +204,7 @@ public final class PixelKit { } } - private func printDebugInfo(pixelName: String, parameters: [String: String], skipped: Bool = false) { + private func printDebugInfo(pixelName: String, parameters: [String: String], skipped: Bool = false) { // TODO: re-do all logging with Logger #if DEBUG let params = parameters.filter { key, _ in !["test"].contains(key) } os_log(.debug, log: log, "👾 [%{public}@] %{public}@ %{public}@", skipped ? "SKIPPED" : "FIRED", pixelName.replacingOccurrences(of: "_", with: "."), params) diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEvent.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEvent.swift index ca352f3347..4f69e777b6 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEvent.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEvent.swift @@ -31,6 +31,7 @@ public final class DebugEvent: PixelKitEvent { public enum EventType { case assertionFailure(message: String, file: StaticString, line: UInt) case custom(_ event: PixelKitEvent) + case customV2(_ event: PixelKitEventV2) } public let eventType: EventType @@ -46,12 +47,19 @@ public final class DebugEvent: PixelKitEvent { self.error = error } + public init(_ event: PixelKitEventV2) { + self.eventType = .customV2(event) + self.error = event.error + } + public var name: String { switch eventType { case .assertionFailure: return "assertion_failure" case .custom(let event): return event.name + case .customV2(let event): + return event.name } } diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEventV2.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEventV2.swift index 811bf4643d..3452123609 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEventV2.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEventV2.swift @@ -39,7 +39,7 @@ extension PixelKitEventErrorDetails { /// /// This new implementation seeks to unify the handling of standard pixel parameters inside PixelKit. /// The starting example of how this can be useful is error parameter handling - this protocol allows -/// the implementer to speciy an error without having to know about the parametrization of the error. +/// the implementer to specify an error without having to know about its parameterisation. /// /// The reason this wasn't done directly in `PixelKitEvent` is to reduce the risk of breaking existing /// pixels, and to allow us to migrate towards this incrementally. From a08d2874c4e23f8ac46ffb99b19a8254c949cd5b Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 4 Apr 2024 16:53:38 +0100 Subject: [PATCH 02/31] many more pixels migrated to PixelKit --- DuckDuckGo.xcodeproj/project.pbxproj | 32 +++- DuckDuckGo/Application/AppDelegate.swift | 16 +- DuckDuckGo/Application/URLEventHandler.swift | 11 +- DuckDuckGo/Application/UpdateController.swift | 9 +- .../Autofill/AutofillActionExecutor.swift | 3 +- .../ContentOverlayViewController.swift | 9 +- .../Legacy/LegacyBookmarkStore.swift | 5 +- .../LegacyBookmarksStoreMigration.swift | 13 +- .../Model/BookmarksCleanupErrorHandling.swift | 5 +- .../Services/LocalBookmarkStore.swift | 18 +-- DuckDuckGo/Common/Database/Database.swift | 7 +- DuckDuckGo/Common/FileSystem/FileStore.swift | 3 +- .../Configuration/ConfigurationManager.swift | 7 +- .../Configuration/ConfigurationStore.swift | 3 +- .../ContentBlocker/ContentBlocking.swift | 22 +-- .../CrashReports/Model/CrashReporter.swift | 2 +- DuckDuckGo/DBP/DBPHomeViewController.swift | 7 +- .../DBP/DataBrokerProtectionAppEvents.swift | 8 +- .../DataBrokerProtectionFeatureDisabler.swift | 2 +- .../DataBrokerProtectionLoginItemPixels.swift | 2 +- ...taBrokerProtectionLoginItemScheduler.swift | 4 +- ...erProtectionSubscriptionEventHandler.swift | 3 +- .../DataBrokerProtectionRemoteMessaging.swift | 5 +- DuckDuckGo/DataExport/CSVLoginExporter.swift | 3 +- .../Bookmarks/Safari/SafariDataImporter.swift | 4 +- .../Chromium/ChromiumDataImporter.swift | 3 +- .../Chromium/ChromiumKeychainPrompt.swift | 3 +- .../Logins/Firefox/FirefoxDataImporter.swift | 3 +- .../Model/DataImportViewModel.swift | 8 +- .../Email/EmailManagerRequestDelegate.swift | 3 +- .../Favicons/Services/FaviconStore.swift | 3 +- .../Feedback/Model/FeedbackSender.swift | 3 +- .../Model/WebKitDownloadTask.swift | 9 +- .../Services/DownloadListCoordinator.swift | 3 +- .../Services/DownloadListStore.swift | 3 +- .../Fire/Model/TabCleanupPreparer.swift | 3 +- .../Fire/ViewModel/FirePopoverViewModel.swift | 11 +- .../Services/EncryptedHistoryStore.swift | 25 +-- .../Model/DataImportStatusProviding.swift | 3 +- .../Model/HomePageContinueSetUpModel.swift | 14 +- .../View/HomePageViewController.swift | 5 +- DuckDuckGo/Menus/MainMenuActions.swift | 8 +- .../AddressBarButtonsViewController.swift | 4 +- .../NavigationBar/View/MoreOptionsMenu.swift | 4 +- .../View/NavigationBarViewController.swift | 2 +- .../VPNLocation/VPNLocationViewModel.swift | 2 +- .../NetworkProtectionRemoteMessaging.swift | 5 +- .../Bitwarden/Model/BWManager.swift | 47 +++--- .../PasswordManagerCoordinator.swift | 5 +- .../Permissions/Model/StoredPermission.swift | 3 +- .../Model/AppearancePreferences.swift | 7 +- .../Model/DefaultBrowserPreferences.swift | 2 +- .../Preferences/Model/SyncPreferences.swift | 36 ++--- .../View/PreferencesAutofillView.swift | 7 +- .../View/PreferencesDefaultBrowserView.swift | 3 +- .../View/PrivacyDashboardViewController.swift | 26 ++- .../AutofillNeverPromptWebsitesManager.swift | 7 +- .../SecureVaultErrorReporter.swift | 5 +- .../PasswordManagementViewController.swift | 17 +- .../View/SaveCredentialsViewController.swift | 7 +- .../View/SaveIdentityViewController.swift | 5 +- .../SavePaymentMethodViewController.swift | 3 +- .../AppStateRestorationManager.swift | 9 +- .../PixelDataModel.xcdatamodel/contents | 0 .../PixelDataRecord.swift | 0 .../PixelDataStore.swift | 0 .../Statistics/ATB/StatisticsLoader.swift | 7 +- .../Experiment/PixelExperiment.swift | 9 +- DuckDuckGo/Statistics/GeneralPixel.swift | 152 +++++++++++------- .../Statistics/PixelKit+Assertion.swift | 25 +++ .../Statistics/RulesCompilationMonitor.swift | 4 +- .../Model/SuggestionContainer.swift | 3 +- .../CredentialsCleanupErrorHandling.swift | 5 +- DuckDuckGo/Sync/SyncBookmarksAdapter.swift | 13 +- DuckDuckGo/Sync/SyncCredentialsAdapter.swift | 11 +- DuckDuckGo/Sync/SyncDataProviders.swift | 5 +- DuckDuckGo/Sync/SyncErrorHandler.swift | 3 +- .../Sync/SyncMetricsEventsHandler.swift | 5 +- DuckDuckGo/Sync/SyncSettingsAdapter.swift | 7 +- DuckDuckGo/Tab/Model/Tab.swift | 2 +- .../TabExtensions/AutofillTabExtension.swift | 7 +- .../DuckPlayerTabExtension.swift | 13 +- .../NavigationProtectionTabExtension.swift | 3 +- .../SubscriptionPagesUserScript.swift | 8 +- .../Tab/View/BrowserTabViewController.swift | 3 +- .../ViewModel/TabCollectionViewModel.swift | 3 +- .../VPNFeedbackForm/VPNFeedbackSender.swift | 5 +- DuckDuckGo/Waitlist/Waitlist.swift | 2 +- DuckDuckGo/YoutubePlayer/DuckPlayer.swift | 7 +- .../PixelKit/Sources/PixelKit/PixelKit.swift | 26 ++- .../CBRCompileTimeReporterTests.swift | 13 +- UnitTests/Statistics/PixelTests.swift | 6 +- 92 files changed, 513 insertions(+), 353 deletions(-) rename DuckDuckGo/Statistics/{Pixel Legacy => ATB}/PixelDataModel.xcdatamodeld/PixelDataModel.xcdatamodel/contents (100%) rename DuckDuckGo/Statistics/{Pixel Legacy => ATB}/PixelDataRecord.swift (100%) rename DuckDuckGo/Statistics/{Pixel Legacy => ATB}/PixelDataStore.swift (100%) create mode 100644 DuckDuckGo/Statistics/PixelKit+Assertion.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 8502815745..69b82917ca 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -3289,6 +3289,18 @@ F18826802BBEB58100D9AC4F /* PrivacyProPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */; }; F18826812BBEB58100D9AC4F /* PrivacyProPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */; }; F18826822BBEB58100D9AC4F /* PrivacyProPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */; }; + F18826842BBEE31700D9AC4F /* PixelKit+Assertion.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */; }; + F18826852BBEE31700D9AC4F /* PixelKit+Assertion.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */; }; + F18826862BBEE31700D9AC4F /* PixelKit+Assertion.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */; }; + F18826872BBF01B600D9AC4F /* PixelDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */; }; + F18826882BBF01B600D9AC4F /* PixelDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */; }; + F18826892BBF01B800D9AC4F /* PixelDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */; }; + F188268A2BBF01BE00D9AC4F /* PixelDataRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */; }; + F188268B2BBF01BE00D9AC4F /* PixelDataRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */; }; + F188268C2BBF01C000D9AC4F /* PixelDataRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */; }; + F188268D2BBF01C300D9AC4F /* PixelDataModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44062616B30600DD1EC2 /* PixelDataModel.xcdatamodeld */; }; + F188268E2BBF01C400D9AC4F /* PixelDataModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44062616B30600DD1EC2 /* PixelDataModel.xcdatamodeld */; }; + F188268F2BBF01C500D9AC4F /* PixelDataModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44062616B30600DD1EC2 /* PixelDataModel.xcdatamodeld */; }; F1B33DF22BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */; }; F1B33DF32BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */; }; F1B33DF42BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */; }; @@ -4730,6 +4742,7 @@ EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSAlertViewModelTests.swift; sourceTree = ""; }; F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPixel.swift; sourceTree = ""; }; F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyProPixel.swift; sourceTree = ""; }; + F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PixelKit+Assertion.swift"; sourceTree = ""; }; F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionAppStoreRestorer.swift; sourceTree = ""; }; F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionErrorReporter.swift; sourceTree = ""; }; F1D43AED2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MainMenuActions+VanillaBrowser.swift"; sourceTree = ""; }; @@ -8430,6 +8443,9 @@ B69B50392726A12500758A2B /* LocalStatisticsStore.swift */, B69B50372726A12000758A2B /* VariantManager.swift */, B69B50562727D16900758A2B /* AtbAndVariantCleanup.swift */, + B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */, + B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */, + B6DA44062616B30600DD1EC2 /* PixelDataModel.xcdatamodeld */, ); path = ATB; sourceTree = ""; @@ -8478,6 +8494,7 @@ F18826712BBEAF9200D9AC4F /* Pixel Legacy */, B610F2BA27A145C500FCEBE9 /* RulesCompilationMonitor.swift */, F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */, + F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */, F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */, ); path = Statistics; @@ -8774,9 +8791,6 @@ B6A9E47626146A570067D1B9 /* PixelEvent.swift */, B6A9E47E26146A800067D1B9 /* PixelArguments.swift */, B6A9E48326146AAB0067D1B9 /* PixelParameters.swift */, - B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */, - B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */, - B6DA44062616B30600DD1EC2 /* PixelDataModel.xcdatamodeld */, ); path = "Pixel Legacy"; sourceTree = ""; @@ -10324,6 +10338,7 @@ B6CC26692BAD959500F53F8D /* DownloadProgress.swift in Sources */, 3706FAE8293F65D500E42796 /* RecentlyClosedTab.swift in Sources */, 1D36E65C298ACD2900AA485D /* AppIconChanger.swift in Sources */, + F188268B2BBF01BE00D9AC4F /* PixelDataRecord.swift in Sources */, 3706FAE9293F65D500E42796 /* PDFSearchTextMenuItemHandler.swift in Sources */, 3706FAEB293F65D500E42796 /* HistoryMenu.swift in Sources */, 3706FAEC293F65D500E42796 /* ContentScopeFeatureFlagging.swift in Sources */, @@ -10349,6 +10364,7 @@ F1B33DF72BAD970E001128B3 /* SubscriptionErrorReporter.swift in Sources */, 3706FEC0293F6EFF00E42796 /* BWRequest.swift in Sources */, 3706FAF7293F65D500E42796 /* FireproofDomainsViewController.swift in Sources */, + F18826852BBEE31700D9AC4F /* PixelKit+Assertion.swift in Sources */, 4BF0E5062AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, 3706FAF8293F65D500E42796 /* URLEventHandler.swift in Sources */, 37197EA72942443D00394917 /* AuthenticationAlert.swift in Sources */, @@ -10620,6 +10636,7 @@ 3706FBBE293F65D500E42796 /* DownloadListViewModel.swift in Sources */, 3706FBBF293F65D500E42796 /* BookmarkManagementDetailViewController.swift in Sources */, B6B4D1CB2B0C8C9200C26286 /* FirefoxCompatibilityPreferences.swift in Sources */, + F188268E2BBF01C400D9AC4F /* PixelDataModel.xcdatamodeld in Sources */, 3706FBC0293F65D500E42796 /* CSVImporter.swift in Sources */, 3706FBC1293F65D500E42796 /* StartupPreferences.swift in Sources */, 3706FBC2293F65D500E42796 /* MainMenu.swift in Sources */, @@ -10664,6 +10681,7 @@ 3706FBDD293F65D500E42796 /* PaddedImageButton.swift in Sources */, 37CEFCAA2A6737A2001EF741 /* CredentialsCleanupErrorHandling.swift in Sources */, 3706FBDE293F65D500E42796 /* EncryptionKeyStoring.swift in Sources */, + F18826882BBF01B600D9AC4F /* PixelDataStore.swift in Sources */, 4B4D60E32A0C883A00BCD287 /* AppMain.swift in Sources */, 37197EA12942441700394917 /* Tab+UIDelegate.swift in Sources */, 56D6A3D729DB2BAB0055215A /* ContinueSetUpView.swift in Sources */, @@ -11435,6 +11453,7 @@ 4B9579552AC7AE700062CA31 /* Logging.swift in Sources */, 4B9579562AC7AE700062CA31 /* CrashReportPromptPresenter.swift in Sources */, 1DDC84F92B83558F00670238 /* PreferencesPrivateSearchView.swift in Sources */, + F18826892BBF01B800D9AC4F /* PixelDataStore.swift in Sources */, B6B4D1CD2B0C8C9200C26286 /* FirefoxCompatibilityPreferences.swift in Sources */, 9FA173ED2B7B232200EE4E6E /* AddEditBookmarkDialogView.swift in Sources */, 4B9579572AC7AE700062CA31 /* BWCredential.swift in Sources */, @@ -11830,6 +11849,7 @@ B65E5DB12B74E6AA00480415 /* TrackerNetwork.swift in Sources */, B6E6B9E52BA1F5F1008AA7E1 /* FilePresenter.swift in Sources */, 31F2D2022AF026D800BF0144 /* WaitlistTermsAndConditionsActionHandler.swift in Sources */, + F18826862BBEE31700D9AC4F /* PixelKit+Assertion.swift in Sources */, 4B957AAC2AC7AE700062CA31 /* EncryptedValueTransformer.swift in Sources */, 4B957AAD2AC7AE700062CA31 /* Tab+Dialogs.swift in Sources */, 4B957AAE2AC7AE700062CA31 /* PasteboardBookmark.swift in Sources */, @@ -11841,6 +11861,7 @@ B6F9BDE62B45CD1900677B33 /* ModalView.swift in Sources */, 4B957AB32AC7AE700062CA31 /* NetworkProtectionControllerErrorStore.swift in Sources */, 4B957AB42AC7AE700062CA31 /* DataImport.swift in Sources */, + F188268C2BBF01C000D9AC4F /* PixelDataRecord.swift in Sources */, 4B957AB52AC7AE700062CA31 /* NetworkProtectionDebugMenu.swift in Sources */, 4B957AB62AC7AE700062CA31 /* FireproofDomains.xcdatamodeld in Sources */, 3158B14F2B0BF74F00AF130C /* DataBrokerProtectionManager.swift in Sources */, @@ -12040,6 +12061,7 @@ 4B957B672AC7AE700062CA31 /* AtbAndVariantCleanup.swift in Sources */, 4B957B692AC7AE700062CA31 /* FeedbackWindow.swift in Sources */, 4B957B6A2AC7AE700062CA31 /* WorkspaceProtocol.swift in Sources */, + F188268F2BBF01C500D9AC4F /* PixelDataModel.xcdatamodeld in Sources */, 4B957B6B2AC7AE700062CA31 /* RecentlyVisitedView.swift in Sources */, 4B957B6C2AC7AE700062CA31 /* MouseOverAnimationButton.swift in Sources */, 4B957B6D2AC7AE700062CA31 /* TabBarScrollView.swift in Sources */, @@ -12308,6 +12330,7 @@ AAC30A26268DFEE200D2D9CD /* CrashReporter.swift in Sources */, B60D64492AAF1B7C00B26F50 /* AddressBarTextSelectionNavigation.swift in Sources */, 3184AC6D288F29D800C35E4B /* BadgeNotificationAnimationModel.swift in Sources */, + F18826872BBF01B600D9AC4F /* PixelDataStore.swift in Sources */, 857FFEC027D239DC00415E7A /* HyperLink.swift in Sources */, B65E5DAF2B74DE6D00480415 /* TrackerNetwork.swift in Sources */, 37445F992A1566420029F789 /* SyncDataProviders.swift in Sources */, @@ -12621,6 +12644,7 @@ 85707F2A276A35FE00DC0649 /* ActionSpeech.swift in Sources */, B6BE9FAA293F7955006363C6 /* ModalSheetCancellable.swift in Sources */, B6830963274CDEC7004B46BB /* FireproofDomainsStore.swift in Sources */, + F188268D2BBF01C300D9AC4F /* PixelDataModel.xcdatamodeld in Sources */, 7B430EA12A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift in Sources */, 1E7E2E942902AC0E00C01B54 /* PrivacyDashboardPermissionHandler.swift in Sources */, AA9FF95F24A1FB690039E328 /* TabCollectionViewModel.swift in Sources */, @@ -12914,10 +12938,12 @@ 31F28C5328C8EECA00119F70 /* DuckURLSchemeHandler.swift in Sources */, AA13DCB4271480B0006D48D3 /* FirePopoverViewModel.swift in Sources */, 1DDC84F72B83558F00670238 /* PreferencesPrivateSearchView.swift in Sources */, + F188268A2BBF01BE00D9AC4F /* PixelDataRecord.swift in Sources */, 1D43EB38292B636E0065E5D6 /* BWCommand.swift in Sources */, F41D174125CB131900472416 /* NSColorExtension.swift in Sources */, AAC5E4F625D6BF2C007F5990 /* AddressBarButtonsViewController.swift in Sources */, B6F9BDE42B45CD1900677B33 /* ModalView.swift in Sources */, + F18826842BBEE31700D9AC4F /* PixelKit+Assertion.swift in Sources */, 1D2DC0072901679C008083A1 /* BWError.swift in Sources */, 853014D625E671A000FB8205 /* PageObserverUserScript.swift in Sources */, B677FC4F2B06376B0099EB04 /* ReportFeedbackView.swift in Sources */, diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index a0a10c0edd..0ba9143e11 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -111,11 +111,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { internalUserDecider = DefaultInternalUserDecider(store: internalUserDeciderStore) if NSApplication.runType.requiresEnvironment { -#if DEBUG - Self.setUpPixelKit(dryRun: true) -#else - Self.setUpPixelKit(dryRun: false) -#endif + Self.configurePixelKit() Database.shared.loadStore { _, error in guard let error = error else { return } @@ -181,6 +177,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate { #endif } + static func configurePixelKit() { +#if DEBUG + Self.setUpPixelKit(dryRun: true) +#else + Self.setUpPixelKit(dryRun: false) +#endif + } + func applicationWillFinishLaunching(_ notification: Notification) { APIRequest.Headers.setUserAgent(UserAgent.duckDuckGoUserAgent()) Configuration.setURLProvider(AppConfigurationURLProvider()) @@ -547,7 +551,7 @@ func updateSubscriptionStatus() { if case .success(let subscription) = await SubscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) { if subscription.isActive { - PixelKit.fire(GeneralPixel.privacyProSubscriptionActive, frequency: .dailyOnly) + PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActive, frequency: .dailyOnly) } } diff --git a/DuckDuckGo/Application/URLEventHandler.swift b/DuckDuckGo/Application/URLEventHandler.swift index 7ae97a807f..186549f515 100644 --- a/DuckDuckGo/Application/URLEventHandler.swift +++ b/DuckDuckGo/Application/URLEventHandler.swift @@ -19,6 +19,7 @@ import Common import Foundation import AppKit +import PixelKit #if NETWORK_PROTECTION import NetworkProtectionUI @@ -63,15 +64,15 @@ final class URLEventHandler { @objc func handleUrlEvent(event: NSAppleEventDescriptor, reply: NSAppleEventDescriptor) { guard let stringValue = event.paramDescriptor(forKeyword: keyDirectObject)?.stringValue else { os_log("UrlEventListener: unable to determine path", type: .error) - Pixel.fire(.debug(event: .appOpenURLFailed, - error: NSError(domain: "CouldNotGetPath", code: -1, userInfo: nil))) + let error = NSError(domain: "CouldNotGetPath", code: -1, userInfo: nil) + PixelKit.fire(DebugEvent(GeneralPixel.appOpenURLFailed, error: error)) return } guard let url = URL.makeURL(from: stringValue) else { os_log("UrlEventListener: failed to construct URL from path %s", type: .error, stringValue) - Pixel.fire(.debug(event: .appOpenURLFailed, - error: NSError(domain: "CouldNotConstructURL", code: -1, userInfo: nil))) + let error = NSError(domain: "CouldNotConstructURL", code: -1, userInfo: nil) + PixelKit.fire(DebugEvent(GeneralPixel.appOpenURLFailed, error: error)) return } @@ -161,7 +162,7 @@ final class URLEventHandler { #if SUBSCRIPTION case AppLaunchCommand.showPrivacyPro.launchURL: WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) - Pixel.fire(.privacyProOfferScreenImpression) + PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) #endif #if !APPSTORE && !DEBUG case AppLaunchCommand.moveAppToApplications.launchURL: diff --git a/DuckDuckGo/Application/UpdateController.swift b/DuckDuckGo/Application/UpdateController.swift index 13fcacd30d..56f8d5c0fb 100644 --- a/DuckDuckGo/Application/UpdateController.swift +++ b/DuckDuckGo/Application/UpdateController.swift @@ -21,6 +21,7 @@ import Combine import Sparkle import BrowserServicesKit import SwiftUIExtensions +import PixelKit #if SPARKLE @@ -107,7 +108,7 @@ extension UpdateController: SPUUpdaterDelegate { return } - Pixel.fire(.debug(event: .updaterAborted, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.updaterAborted, error: error)) } func updater(_ updater: SPUUpdater, @@ -116,11 +117,11 @@ extension UpdateController: SPUUpdaterDelegate { state: SPUUserUpdateState) { switch choice { case .skip: - Pixel.fire(.debug(event: .userSelectedToSkipUpdate)) + PixelKit.fire(DebugEvent(GeneralPixel.userSelectedToSkipUpdate)) case .install: - Pixel.fire(.debug(event: .userSelectedToInstallUpdate)) + PixelKit.fire(DebugEvent(GeneralPixel.userSelectedToInstallUpdate)) case .dismiss: - Pixel.fire(.debug(event: .userSelectedToDismissUpdate)) + PixelKit.fire(DebugEvent(GeneralPixel.userSelectedToDismissUpdate)) @unknown default: break } diff --git a/DuckDuckGo/Autofill/AutofillActionExecutor.swift b/DuckDuckGo/Autofill/AutofillActionExecutor.swift index f574c7e974..cbdc445d1f 100644 --- a/DuckDuckGo/Autofill/AutofillActionExecutor.swift +++ b/DuckDuckGo/Autofill/AutofillActionExecutor.swift @@ -20,6 +20,7 @@ import Foundation import BrowserServicesKit import DDGSync import AppKit +import PixelKit /// Conforming types provide an `execute` method which performs some action on autofill types (e.g delete all passwords) protocol AutofillActionExecutor { @@ -68,7 +69,7 @@ struct AutofillDeleteAllPasswordsExecutor: AutofillActionExecutor { syncService.scheduler.notifyDataChanged() onSuccess?() } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } return diff --git a/DuckDuckGo/Autofill/ContentOverlayViewController.swift b/DuckDuckGo/Autofill/ContentOverlayViewController.swift index d90b35f71d..9e325ef8b0 100644 --- a/DuckDuckGo/Autofill/ContentOverlayViewController.swift +++ b/DuckDuckGo/Autofill/ContentOverlayViewController.swift @@ -22,6 +22,7 @@ import Combine import BrowserServicesKit import SecureStorage import Autofill +import PixelKit @MainActor public final class ContentOverlayViewController: NSViewController, EmailManagerRequestDelegate { @@ -191,7 +192,7 @@ public final class ContentOverlayViewController: NSViewController, EmailManagerR parameters["keychain_operation"] = "save" } - Pixel.fire(.debug(event: .emailAutofillKeychainError, error: error), withAdditionalParameters: parameters) + PixelKit.fire(DebugEvent(GeneralPixel.emailAutofillKeychainError), withAdditionalParameters: parameters) } private enum Constants { @@ -294,7 +295,7 @@ extension ContentOverlayViewController: SecureVaultManagerDelegate { } public func secureVaultManager(_: SecureVaultManager, didAutofill type: AutofillType, withObjectId objectId: String) { - Pixel.fire(.formAutofilled(kind: type.formAutofillKind)) + PixelKit.fire(GeneralPixel.formAutofilled(kind: type.formAutofillKind)) if type.formAutofillKind == .password && passwordManagerCoordinator.isEnabled { @@ -320,9 +321,9 @@ extension ContentOverlayViewController: SecureVaultManagerDelegate { self.emailManager.updateLastUseDate() - Pixel.fire(.jsPixel(pixel), withAdditionalParameters: pixelParameters) + PixelKit.fire(GeneralPixel.jsPixel(pixel), withAdditionalParameters: pixelParameters) } else { - Pixel.fire(.jsPixel(pixel), withAdditionalParameters: pixel.pixelParameters) + PixelKit.fire(GeneralPixel.jsPixel(pixel), withAdditionalParameters: pixel.pixelParameters) } } diff --git a/DuckDuckGo/Bookmarks/Legacy/LegacyBookmarkStore.swift b/DuckDuckGo/Bookmarks/Legacy/LegacyBookmarkStore.swift index 9634ca221f..ebfc15be43 100644 --- a/DuckDuckGo/Bookmarks/Legacy/LegacyBookmarkStore.swift +++ b/DuckDuckGo/Bookmarks/Legacy/LegacyBookmarkStore.swift @@ -19,6 +19,7 @@ import Foundation import CoreData import Cocoa +import PixelKit fileprivate extension UUID { @@ -171,7 +172,7 @@ extension LegacyBookmarkStore { try context.save() } catch { - Pixel.fire(.debug(event: .bookmarksStoreRootFolderMigrationFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksStoreRootFolderMigrationFailed, error: error)) } } @@ -211,7 +212,7 @@ extension LegacyBookmarkStore { // 4. Save the migration: try context.save() } catch { - Pixel.fire(.debug(event: .bookmarksStoreRootFolderMigrationFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksStoreRootFolderMigrationFailed, error: error)) } } } diff --git a/DuckDuckGo/Bookmarks/Legacy/LegacyBookmarksStoreMigration.swift b/DuckDuckGo/Bookmarks/Legacy/LegacyBookmarksStoreMigration.swift index c5ecef1b7c..734275e1c5 100644 --- a/DuckDuckGo/Bookmarks/Legacy/LegacyBookmarksStoreMigration.swift +++ b/DuckDuckGo/Bookmarks/Legacy/LegacyBookmarksStoreMigration.swift @@ -20,6 +20,7 @@ import Foundation import CoreData import Bookmarks import Persistence +import PixelKit public class LegacyBookmarksStoreMigration { @@ -55,7 +56,7 @@ public class LegacyBookmarksStoreMigration { guard BookmarkUtils.fetchRootFolder(destination) == nil else { // There should be no data left as migration has been done already if !bookmarkRoots.isEmpty { - Pixel.fire(.debug(event: .bookmarksMigrationAlreadyPerformed)) + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksMigrationAlreadyPerformed)) cleanupOldData(in: source) } @@ -72,9 +73,9 @@ public class LegacyBookmarksStoreMigration { let newFavoritesRoot = BookmarkUtils.fetchLegacyFavoritesFolder(destination) else { if bookmarkRoots.isEmpty { - Pixel.fire(.debug(event: .bookmarksCouldNotPrepareDatabase)) + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksCouldNotPrepareDatabase)) } else { - Pixel.fire(.debug(event: .bookmarksMigrationCouldNotPrepareDatabase)) + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksMigrationCouldNotPrepareDatabase)) } Thread.sleep(forTimeInterval: 2) @@ -157,7 +158,7 @@ public class LegacyBookmarksStoreMigration { } do { - try destination.save(onErrorFire: .bookmarksMigrationFailed) + try destination.save(onErrorFire: GeneralPixel.bookmarksMigrationFailed) cleanupOldData(in: source) } catch { @@ -165,7 +166,7 @@ public class LegacyBookmarksStoreMigration { BookmarkUtils.prepareLegacyFoldersStructure(in: destination) do { - try destination.save(onErrorFire: .bookmarksMigrationCouldNotPrepareDatabaseOnFailedMigration) + try destination.save(onErrorFire: GeneralPixel.bookmarksMigrationCouldNotPrepareDatabaseOnFailedMigration) } catch { Thread.sleep(forTimeInterval: 2) fatalError("Could not write to Bookmarks DB") @@ -181,7 +182,7 @@ public class LegacyBookmarksStoreMigration { allObjects.forEach { context.delete($0) } if context.hasChanges { - try? context.save(onErrorFire: .bookmarksMigrationCouldNotRemoveOldStore) + try? context.save(onErrorFire: GeneralPixel.bookmarksMigrationCouldNotRemoveOldStore) } } diff --git a/DuckDuckGo/Bookmarks/Model/BookmarksCleanupErrorHandling.swift b/DuckDuckGo/Bookmarks/Model/BookmarksCleanupErrorHandling.swift index 815dc8ecf9..467af32bbd 100644 --- a/DuckDuckGo/Bookmarks/Model/BookmarksCleanupErrorHandling.swift +++ b/DuckDuckGo/Bookmarks/Model/BookmarksCleanupErrorHandling.swift @@ -20,18 +20,19 @@ import Foundation import Bookmarks import Common import Persistence +import PixelKit public class BookmarksCleanupErrorHandling: EventMapping { public init() { super.init { event, _, _, _ in if event.cleanupError is BookmarksCleanupCancelledError { - Pixel.fire(.debug(event: .bookmarksCleanupAttemptedWhileSyncWasEnabled)) + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksCleanupAttemptedWhileSyncWasEnabled)) } else { let processedErrors = CoreDataErrorsParser.parse(error: event.cleanupError as NSError) let params = processedErrors.errorPixelParameters - Pixel.fire(.debug(event: .bookmarksCleanupFailed, error: event.cleanupError), withAdditionalParameters: params) + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksCleanupFailed, error: event.cleanupError), withAdditionalParameters: params) } } } diff --git a/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift b/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift index e2be507a44..fb21f3222f 100644 --- a/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift +++ b/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift @@ -199,10 +199,10 @@ final class LocalBookmarkStore: BookmarkStore { var params = processedErrors.errorPixelParameters params[PixelKit.Parameters.errorSource] = source - Pixel.fire(.debug(event: .bookmarksSaveFailed, error: error), + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksSaveFailed, error: error), withAdditionalParameters: params) } else { - Pixel.fire(.debug(event: .bookmarksSaveFailed, error: localError), + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksSaveFailed, error: localError), withAdditionalParameters: [PixelKit.Parameters.errorSource: source]) } } else { @@ -211,7 +211,7 @@ final class LocalBookmarkStore: BookmarkStore { var params = processedErrors.errorPixelParameters params[PixelKit.Parameters.errorSource] = source - Pixel.fire(.debug(event: .bookmarksSaveFailed, error: error), + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksSaveFailed, error: error), withAdditionalParameters: params) } } @@ -228,7 +228,7 @@ final class LocalBookmarkStore: BookmarkStore { ) if context.hasChanges { - try context.save(onErrorFire: .bookmarksMigrationCouldNotPrepareMultipleFavoriteFolders) + try context.save(onErrorFire: GeneralPixel.bookmarksMigrationCouldNotPrepareMultipleFavoriteFolders) } } catch { Thread.sleep(forTimeInterval: 1) @@ -310,7 +310,7 @@ final class LocalBookmarkStore: BookmarkStore { } else if let root = bookmarksRoot(in: context) { parentEntity = root } else { - Pixel.fire(.debug(event: .missingParent)) + PixelKit.fire(DebugEvent(GeneralPixel.missingParent)) throw BookmarkStoreError.missingParent } @@ -506,7 +506,7 @@ final class LocalBookmarkStore: BookmarkStore { } else if let root = self.bookmarksRoot(in: context) { parentEntity = root } else { - Pixel.fire(.debug(event: .missingParent)) + PixelKit.fire(DebugEvent(GeneralPixel.missingParent)) throw BookmarkStoreError.missingParent } @@ -720,7 +720,7 @@ final class LocalBookmarkStore: BookmarkStore { let processedErrors = CoreDataErrorsParser.parse(error: error) if NSApp.runType.requiresEnvironment { - Pixel.fire(.debug(event: .bookmarksSaveFailedOnImport, error: error), + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksSaveFailedOnImport, error: error), withAdditionalParameters: processedErrors.errorPixelParameters) assertionFailure("LocalBookmarkStore: Saving of context failed, error: \(error.localizedDescription)") } @@ -856,7 +856,7 @@ final class LocalBookmarkStore: BookmarkStore { } if deletedEntityCount > 0 { - Pixel.fire(.debug(event: .removedInvalidBookmarkManagedObjects)) + PixelKit.fire(DebugEvent(GeneralPixel.removedInvalidBookmarkManagedObjects)) } } onError: { [weak self] error in self?.commonOnSaveErrorHandler(error) @@ -904,7 +904,7 @@ final class LocalBookmarkStore: BookmarkStore { let nsError = error as NSError let processedErrors = CoreDataErrorsParser.parse(error: nsError) let params = processedErrors.errorPixelParameters - Pixel.fire(.debug(event: .favoritesCleanupFailed, error: error), withAdditionalParameters: params) + PixelKit.fire(DebugEvent(GeneralPixel.favoritesCleanupFailed, error: error), withAdditionalParameters: params) } onDidSave: {} } diff --git a/DuckDuckGo/Common/Database/Database.swift b/DuckDuckGo/Common/Database/Database.swift index 10e116d3a7..dee8aef2d8 100644 --- a/DuckDuckGo/Common/Database/Database.swift +++ b/DuckDuckGo/Common/Database/Database.swift @@ -21,6 +21,7 @@ import BrowserServicesKit import CoreData import Foundation import Persistence +import PixelKit final class Database { @@ -100,7 +101,7 @@ final class Database { // Fire the pixel once a day at max if lastPixelSentAt < Date.daysAgo(1) { lastDatabaseFactoryFailurePixelDate = Date() - Pixel.fire(.debug(event: .dbMakeDatabaseError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.dbMakeDatabaseError(error: error))) } } } @@ -185,14 +186,14 @@ extension NSManagedObjectModel { extension NSManagedObjectContext { - func save(onErrorFire event: Pixel.Event.Debug) throws { + func save(onErrorFire event: PixelKitEventV2) throws { do { try save() } catch { let nsError = error as NSError let processedErrors = CoreDataErrorsParser.parse(error: nsError) - Pixel.fire(.debug(event: event, error: error), + PixelKit.fire(DebugEvent(event, error: error), withAdditionalParameters: processedErrors.errorPixelParameters) throw error diff --git a/DuckDuckGo/Common/FileSystem/FileStore.swift b/DuckDuckGo/Common/FileSystem/FileStore.swift index 99b03e5a01..22dc52c7d1 100644 --- a/DuckDuckGo/Common/FileSystem/FileStore.swift +++ b/DuckDuckGo/Common/FileSystem/FileStore.swift @@ -18,6 +18,7 @@ import Foundation import CryptoKit +import PixelKit protocol FileStore { func persist(_ data: Data, url: URL) -> Bool @@ -57,7 +58,7 @@ final class EncryptedFileStore: FileStore { return true } catch { - Pixel.fire(.debug(event: .fileStoreWriteFailed, error: error), + PixelKit.fire(DebugEvent(GeneralPixel.fileStoreWriteFailed, error: error), withAdditionalParameters: ["config": url.lastPathComponent]) return false } diff --git a/DuckDuckGo/Configuration/ConfigurationManager.swift b/DuckDuckGo/Configuration/ConfigurationManager.swift index a64673dfc0..531fbe4985 100644 --- a/DuckDuckGo/Configuration/ConfigurationManager.swift +++ b/DuckDuckGo/Configuration/ConfigurationManager.swift @@ -22,6 +22,7 @@ import BrowserServicesKit import Configuration import Common import Networking +import PixelKit @MainActor final class ConfigurationManager { @@ -72,13 +73,13 @@ final class ConfigurationManager { eventMapping: Self.configurationDebugEvents) private static let configurationDebugEvents = EventMapping { event, error, _, _ in - let domainEvent: Pixel.Event.Debug + let domainEvent: GeneralPixel switch event { case .invalidPayload(let configuration): domainEvent = .invalidPayload(configuration) } - Pixel.fire(.debug(event: domainEvent, error: error)) + PixelKit.fire(DebugEvent(domainEvent, error: error)) } func start() { @@ -170,7 +171,7 @@ final class ConfigurationManager { } os_log("Failed to complete configuration update %@", log: .config, type: .error, error.localizedDescription) - Pixel.fire(.debug(event: .configurationFetchError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.configurationFetchError(error: error))) tryAgainSoon() } diff --git a/DuckDuckGo/Configuration/ConfigurationStore.swift b/DuckDuckGo/Configuration/ConfigurationStore.swift index 179e5addd7..fe58ca3df2 100644 --- a/DuckDuckGo/Configuration/ConfigurationStore.swift +++ b/DuckDuckGo/Configuration/ConfigurationStore.swift @@ -19,6 +19,7 @@ import Common import Foundation import Configuration +import PixelKit final class ConfigurationStore: ConfigurationStoring { @@ -99,7 +100,7 @@ final class ConfigurationStore: ConfigurationStoring { let nserror = error as NSError if nserror.domain != NSCocoaErrorDomain || nserror.code != NSFileReadNoSuchFileError { - Pixel.fire(.debug(event: .trackerDataCouldNotBeLoaded, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.trackerDataCouldNotBeLoaded, error: error)) } return nil diff --git a/DuckDuckGo/ContentBlocker/ContentBlocking.swift b/DuckDuckGo/ContentBlocker/ContentBlocking.swift index 795f5ec581..d935081755 100644 --- a/DuckDuckGo/ContentBlocker/ContentBlocking.swift +++ b/DuckDuckGo/ContentBlocker/ContentBlocking.swift @@ -21,6 +21,7 @@ import WebKit import Combine import BrowserServicesKit import Common +import PixelKit protocol ContentBlockingProtocol { @@ -98,18 +99,18 @@ final class AppContentBlocking { } private let toggleProtectionsEvents = EventMapping { event, _, parameters, _ in - let domainEvent: Pixel.Event + let domainEvent: GeneralPixel switch event { case .toggleProtectionsCounterDaily: domainEvent = .toggleProtectionsDailyCount } - Pixel.fire(domainEvent, withAdditionalParameters: parameters ?? [:]) + PixelKit.fire(domainEvent, withAdditionalParameters: parameters ?? [:]) } private static let debugEvents = EventMapping { event, error, parameters, onComplete in guard NSApp.runType.requiresEnvironment else { return } - let domainEvent: Pixel.Event.Debug + let domainEvent: GeneralPixel switch event { case .trackerDataParseFailed: domainEvent = .trackerDataParseFailed @@ -132,7 +133,7 @@ final class AppContentBlocking { case .contentBlockingCompilationFailed(let listName, let component): let defaultTDSListName = DefaultContentBlockerRulesListsSource.Constants.trackerDataSetRulesListName - let listType: Pixel.Event.CompileRulesListType + let listType: GeneralPixel.CompileRulesListType switch listName { case defaultTDSListName: listType = .tds @@ -154,13 +155,15 @@ final class AppContentBlocking { return } - Pixel.fire(.debug(event: domainEvent, error: error), withAdditionalParameters: parameters, onComplete: onComplete) + PixelKit.fire(DebugEvent(domainEvent, error: error), withAdditionalParameters: parameters) { _, error in + onComplete(error) + } } // MARK: - Ad Click Attribution let attributionEvents: EventMapping? = .init { event, _, parameters, _ in - let domainEvent: Pixel.Event + let domainEvent: GeneralPixel switch event { case .adAttributionDetected: domainEvent = .adClickAttributionDetected @@ -170,11 +173,11 @@ final class AppContentBlocking { domainEvent = .adClickAttributionPageLoads } - Pixel.fire(domainEvent, withAdditionalParameters: parameters ?? [:]) + PixelKit.fire(domainEvent, withAdditionalParameters: parameters ?? [:]) } let attributionDebugEvents: EventMapping? = .init { event, _, _, _ in - let domainEvent: Pixel.Event.Debug + let domainEvent: GeneralPixel switch event { case .adAttributionCompilationFailedForAttributedRulesList: domainEvent = .adAttributionCompilationFailedForAttributedRulesList @@ -198,8 +201,7 @@ final class AppContentBlocking { domainEvent = .adAttributionLogicWrongVendorOnFailedCompilation } - Pixel.fire(.debug(event: domainEvent, error: nil), - includeAppVersionParameter: false) + PixelKit.fire(DebugEvent(domainEvent), includeAppVersionParameter: false) } } diff --git a/DuckDuckGo/CrashReports/Model/CrashReporter.swift b/DuckDuckGo/CrashReports/Model/CrashReporter.swift index 39288b6043..00e8e69d63 100644 --- a/DuckDuckGo/CrashReports/Model/CrashReporter.swift +++ b/DuckDuckGo/CrashReports/Model/CrashReporter.swift @@ -47,7 +47,7 @@ final class CrashReporter { return } - Pixel.fire(.crash) + PixelKit.fire(GeneralPixel.crash) latestCrashReport = latest promptPresenter.showPrompt(self, for: latest) diff --git a/DuckDuckGo/DBP/DBPHomeViewController.swift b/DuckDuckGo/DBP/DBPHomeViewController.swift index fdfca10e97..e77fed342e 100644 --- a/DuckDuckGo/DBP/DBPHomeViewController.swift +++ b/DuckDuckGo/DBP/DBPHomeViewController.swift @@ -145,12 +145,11 @@ public class DataBrokerProtectionPixelsHandler: EventMapping (URL?, UTType?) @@ -266,7 +267,7 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable self.download.cancel() self.finish(with: .failure(.failedToCompleteDownloadTask(underlyingError: error, resumeData: nil, isRetryable: false))) - Pixel.fire(.debug(event: .fileGetDownloadLocationFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.fileGetDownloadLocationFailed, error: error)) } return nil } @@ -326,7 +327,7 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable self.download.cancel() self.finish(with: .failure(.failedToCompleteDownloadTask(underlyingError: error, resumeData: nil, isRetryable: false))) - Pixel.fire(.debug(event: .fileDownloadCreatePresentersFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.fileDownloadCreatePresentersFailed, error: error)) } } @@ -382,7 +383,7 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable } catch { // fallback: move failed, keep the temp file in the original location os_log(.error, log: log, "🙁 fallback with \(error), will use \(tempURL.path)") - Pixel.fire(.debug(event: .fileAccessRelatedItemFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.fileAccessRelatedItemFailed, error: error)) return tempURL } return duckloadURL @@ -646,7 +647,7 @@ extension WebKitDownloadTask: WKDownloadDelegate { self.finish(with: .success(destinationFile)) } catch { - Pixel.fire(.debug(event: .fileMoveToDownloadsFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.fileMoveToDownloadsFailed, error: error)) os_log(.error, log: log, "fileMoveToDownloadsFailed: \(error)") self.finish(with: .failure(.failedToCompleteDownloadTask(underlyingError: error, resumeData: nil, isRetryable: false))) } diff --git a/DuckDuckGo/FileDownload/Services/DownloadListCoordinator.swift b/DuckDuckGo/FileDownload/Services/DownloadListCoordinator.swift index a7d0b3e1d3..1685394ed2 100644 --- a/DuckDuckGo/FileDownload/Services/DownloadListCoordinator.swift +++ b/DuckDuckGo/FileDownload/Services/DownloadListCoordinator.swift @@ -20,6 +20,7 @@ import Combine import Common import Foundation import Navigation +import PixelKit @MainActor private func getFirstAvailableWebView() -> WKWebView? { @@ -477,7 +478,7 @@ final class DownloadListCoordinator { } } catch { assertionFailure("Resume data coding failed: \(error)") - Pixel.fire(.debug(event: .downloadResumeDataCodingFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.downloadResumeDataCodingFailed, error: error)) } webView.resumeDownload(fromResumeData: resumeData, diff --git a/DuckDuckGo/FileDownload/Services/DownloadListStore.swift b/DuckDuckGo/FileDownload/Services/DownloadListStore.swift index cec1d0e0da..6b884bea73 100644 --- a/DuckDuckGo/FileDownload/Services/DownloadListStore.swift +++ b/DuckDuckGo/FileDownload/Services/DownloadListStore.swift @@ -21,6 +21,7 @@ import Common import CoreData import Foundation import UniformTypeIdentifiers +import PixelKit protocol DownloadListStoring { @@ -191,7 +192,7 @@ extension DownloadListItem { let modified = managedObject.modified, let url = managedObject.urlEncrypted as? URL else { - Pixel.fire(.debug(event: .downloadListItemDecryptionFailedUnique), limitTo: .dailyFirst) + PixelKit.fire(DebugEvent(GeneralPixel.downloadListItemDecryptionFailedUnique), frequency: .dailyOnly) assertionFailure("DownloadListItem: Failed to init from ManagedObject") return nil } diff --git a/DuckDuckGo/Fire/Model/TabCleanupPreparer.swift b/DuckDuckGo/Fire/Model/TabCleanupPreparer.swift index 798ebc2f7c..30314266da 100644 --- a/DuckDuckGo/Fire/Model/TabCleanupPreparer.swift +++ b/DuckDuckGo/Fire/Model/TabCleanupPreparer.swift @@ -17,6 +17,7 @@ // import Foundation +import PixelKit protocol TabDataClearing { func prepareForDataClearing(caller: TabCleanupPreparer) @@ -65,7 +66,7 @@ final class TabCleanupPreparer: NSObject, WKNavigationDelegate { } func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { - Pixel.fire(.debug(event: .blankNavigationOnBurnFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.blankNavigationOnBurnFailed, error: error)) processedTabs += 1 notifyIfDone() diff --git a/DuckDuckGo/Fire/ViewModel/FirePopoverViewModel.swift b/DuckDuckGo/Fire/ViewModel/FirePopoverViewModel.swift index b10b728212..80d2470c45 100644 --- a/DuckDuckGo/Fire/ViewModel/FirePopoverViewModel.swift +++ b/DuckDuckGo/Fire/ViewModel/FirePopoverViewModel.swift @@ -20,6 +20,7 @@ import Cocoa import BrowserServicesKit import Common import History +import PixelKit @MainActor final class FirePopoverViewModel { @@ -188,7 +189,7 @@ final class FirePopoverViewModel { // MARK: - Burning func burn() { - Pixel.fire(.fireButtonFirstBurn, limitTo: .dailyFirst) + PixelKit.fire(GeneralPixel.fireButtonFirstBurn, frequency: .dailyOnly) switch (clearingOption, areAllSelected) { case (.currentTab, _): @@ -197,7 +198,7 @@ final class FirePopoverViewModel { assertionFailure("No tab selected") return } - Pixel.fire(.fireButton(option: .tab)) + PixelKit.fire(GeneralPixel.fireButton(option: .tab)) let burningEntity = Fire.BurningEntity.tab(tabViewModel: tabViewModel, selectedDomains: selectedDomains, parentTabCollectionViewModel: tabCollectionViewModel) @@ -207,17 +208,17 @@ final class FirePopoverViewModel { assertionFailure("FirePopoverViewModel: TabCollectionViewModel is not present") return } - Pixel.fire(.fireButton(option: .window)) + PixelKit.fire(GeneralPixel.fireButton(option: .window)) let burningEntity = Fire.BurningEntity.window(tabCollectionViewModel: tabCollectionViewModel, selectedDomains: selectedDomains) fireViewModel.fire.burnEntity(entity: burningEntity) case (.allData, true): - Pixel.fire(.fireButton(option: .allSites)) + PixelKit.fire(GeneralPixel.fireButton(option: .allSites)) fireViewModel.fire.burnAll() case (.allData, false): - Pixel.fire(.fireButton(option: .allSites)) + PixelKit.fire(GeneralPixel.fireButton(option: .allSites)) fireViewModel.fire.burnEntity(entity: .allWindows(mainWindowControllers: WindowControllersManager.shared.mainWindowControllers, selectedDomains: selectedDomains)) } diff --git a/DuckDuckGo/History/Services/EncryptedHistoryStore.swift b/DuckDuckGo/History/Services/EncryptedHistoryStore.swift index 361ab26a81..7e1c5984f0 100644 --- a/DuckDuckGo/History/Services/EncryptedHistoryStore.swift +++ b/DuckDuckGo/History/Services/EncryptedHistoryStore.swift @@ -21,6 +21,7 @@ import Foundation import CoreData import Combine import History +import PixelKit final class EncryptedHistoryStore: HistoryStoring { @@ -91,7 +92,7 @@ final class EncryptedHistoryStore: HistoryStoring { } os_log("%d items cleaned from history", log: .history, entriesToDelete.count) } catch { - Pixel.fire(.debug(event: .historyRemoveFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.historyRemoveFailed, error: error)) self.context.reset() return .failure(error) } @@ -100,7 +101,7 @@ final class EncryptedHistoryStore: HistoryStoring { do { try context.save() } catch { - Pixel.fire(.debug(event: .historyRemoveFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.historyRemoveFailed, error: error)) context.reset() return .failure(error) } @@ -117,7 +118,7 @@ final class EncryptedHistoryStore: HistoryStoring { let history = BrowsingHistory(historyEntries: historyEntries) return .success(history) } catch { - Pixel.fire(.debug(event: .historyReloadFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.historyReloadFailed, error: error)) return .failure(error) } } @@ -133,7 +134,7 @@ final class EncryptedHistoryStore: HistoryStoring { } try context.save() } catch { - Pixel.fire(.debug(event: .historyCleanEntriesFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.historyCleanEntriesFailed, error: error)) context.reset() return .failure(error) } @@ -149,7 +150,7 @@ final class EncryptedHistoryStore: HistoryStoring { try context.save() return .success(()) } catch { - Pixel.fire(.debug(event: .historyCleanVisitsFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.historyCleanVisitsFailed, error: error)) context.reset() return .failure(error) } @@ -174,7 +175,7 @@ final class EncryptedHistoryStore: HistoryStoring { do { fetchedObjects = try self.context.fetch(fetchRequest) } catch { - Pixel.fire(.debug(event: .historySaveFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.historySaveFailed, error: error)) promise(.failure(error)) return } @@ -202,14 +203,14 @@ final class EncryptedHistoryStore: HistoryStoring { context: self.context) switch insertionResult { case .failure(let error): - Pixel.fire(.debug(event: .historySaveFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.historySaveFailed, error: error)) context.reset() promise(.failure(error)) case .success(let visitMOs): do { try self.context.save() } catch { - Pixel.fire(.debug(event: .historySaveFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.historySaveFailed, error: error)) context.reset() promise(.failure(HistoryStoreError.savingFailed)) return @@ -259,7 +260,7 @@ final class EncryptedHistoryStore: HistoryStoring { context: NSManagedObjectContext) -> Result { let insertedObject = NSEntityDescription.insertNewObject(forEntityName: VisitManagedObject.className(), into: context) guard let visitMO = insertedObject as? VisitManagedObject else { - Pixel.fire(.debug(event: .historyInsertVisitFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.historyInsertVisitFailed)) context.reset() return .failure(HistoryStoreError.savingFailed) } @@ -307,7 +308,7 @@ final class EncryptedHistoryStore: HistoryStoring { context.delete(visit) } } catch { - Pixel.fire(.debug(event: .historyRemoveVisitsFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.historyRemoveVisitsFailed, error: error)) return .failure(error) } } @@ -315,7 +316,7 @@ final class EncryptedHistoryStore: HistoryStoring { do { try context.save() } catch { - Pixel.fire(.debug(event: .historyRemoveVisitsFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.historyRemoveVisitsFailed, error: error)) context.reset() return .failure(error) } @@ -343,7 +344,7 @@ fileprivate extension HistoryEntry { guard let url = historyEntryMO.urlEncrypted as? URL, let identifier = historyEntryMO.identifier, let lastVisit = historyEntryMO.lastVisit else { - Pixel.fire(.debug(event: .historyEntryDecryptionFailedUnique), limitTo: .dailyFirst) + PixelKit.fire(DebugEvent(GeneralPixel.historyEntryDecryptionFailedUnique), frequency: .dailyOnly) assertionFailure("HistoryEntry: Failed to init HistoryEntry from HistoryEntryManagedObject") return nil } diff --git a/DuckDuckGo/HomePage/Model/DataImportStatusProviding.swift b/DuckDuckGo/HomePage/Model/DataImportStatusProviding.swift index 7c729d99ad..4d65b77c05 100644 --- a/DuckDuckGo/HomePage/Model/DataImportStatusProviding.swift +++ b/DuckDuckGo/HomePage/Model/DataImportStatusProviding.swift @@ -19,6 +19,7 @@ import Foundation import Bookmarks import BrowserServicesKit +import PixelKit protocol DataImportStatusProviding { var didImport: Bool { get } @@ -80,7 +81,7 @@ final class BookmarksAndPasswordsImportStatusProvider: DataImportStatusProviding let identitiesDates = try secureVault.identities().map(\.created) dates.append(contentsOf: identitiesDates) } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } guard dates.count >= 2 else { return false diff --git a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift index f6571f004e..d2da000baf 100644 --- a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift +++ b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift @@ -168,7 +168,7 @@ extension HomePage.Models { switch featureType { case .defaultBrowser: do { - Pixel.fire(.defaultRequestedFromHomepageSetupView) + PixelKit.fire(GeneralPixel.defaultRequestedFromHomepageSetupView) try defaultBrowserProvider.presentDefaultBrowserPrompt() } catch { defaultBrowserProvider.openSystemPreferences() @@ -224,12 +224,12 @@ extension HomePage.Models { case .networkProtectionRemoteMessage(let message): #if NETWORK_PROTECTION homePageRemoteMessaging.networkProtectionRemoteMessaging.dismiss(message: message) - Pixel.fire(.networkProtectionRemoteMessageDismissed(messageID: message.id)) + PixelKit.fire(GeneralPixel.networkProtectionRemoteMessageDismissed(messageID: message.id)) #endif case .dataBrokerProtectionRemoteMessage(let message): #if DBP homePageRemoteMessaging.dataBrokerProtectionRemoteMessaging.dismiss(message: message) - Pixel.fire(.dataBrokerProtectionRemoteMessageDismissed(messageID: message.id)) + PixelKit.fire(GeneralPixel.dataBrokerProtectionRemoteMessageDismissed(messageID: message.id)) #endif case .dataBrokerProtectionWaitlistInvited: shouldShowDBPWaitlistInvitedCardUI = false @@ -441,7 +441,7 @@ extension HomePage.Models { @MainActor private func handle(remoteMessage: NetworkProtectionRemoteMessage) { #if NETWORK_PROTECTION guard let actionType = remoteMessage.action.actionType else { - Pixel.fire(.networkProtectionRemoteMessageDismissed(messageID: remoteMessage.id)) + PixelKit.fire(GeneralPixel.networkProtectionRemoteMessageDismissed(messageID: remoteMessage.id)) homePageRemoteMessaging.networkProtectionRemoteMessaging.dismiss(message: remoteMessage) refreshFeaturesMatrix() return @@ -454,7 +454,7 @@ extension HomePage.Models { if let surveyURL = remoteMessage.presentableSurveyURL() { let tab = Tab(content: .url(surveyURL, source: .ui), shouldLoadInBackground: true) tabCollectionViewModel.append(tab: tab) - Pixel.fire(.networkProtectionRemoteMessageOpened(messageID: remoteMessage.id)) + PixelKit.fire(GeneralPixel.networkProtectionRemoteMessageOpened(messageID: remoteMessage.id)) // Dismiss the message after the user opens the URL, even if they just close the tab immediately afterwards. homePageRemoteMessaging.networkProtectionRemoteMessaging.dismiss(message: remoteMessage) @@ -467,7 +467,7 @@ extension HomePage.Models { @MainActor private func handle(remoteMessage: DataBrokerProtectionRemoteMessage) { #if DBP guard let actionType = remoteMessage.action.actionType else { - Pixel.fire(.dataBrokerProtectionRemoteMessageDismissed(messageID: remoteMessage.id)) + PixelKit.fire(GeneralPixel.dataBrokerProtectionRemoteMessageDismissed(messageID: remoteMessage.id)) homePageRemoteMessaging.dataBrokerProtectionRemoteMessaging.dismiss(message: remoteMessage) refreshFeaturesMatrix() return @@ -480,7 +480,7 @@ extension HomePage.Models { if let surveyURL = remoteMessage.presentableSurveyURL() { let tab = Tab(content: .url(surveyURL, source: .ui), shouldLoadInBackground: true) tabCollectionViewModel.append(tab: tab) - Pixel.fire(.dataBrokerProtectionRemoteMessageOpened(messageID: remoteMessage.id)) + PixelKit.fire(GeneralPixel.dataBrokerProtectionRemoteMessageOpened(messageID: remoteMessage.id)) // Dismiss the message after the user opens the URL, even if they just close the tab immediately afterwards. homePageRemoteMessaging.dataBrokerProtectionRemoteMessaging.dismiss(message: remoteMessage) diff --git a/DuckDuckGo/HomePage/View/HomePageViewController.swift b/DuckDuckGo/HomePage/View/HomePageViewController.swift index dc3997e6a8..457d3f4fd2 100644 --- a/DuckDuckGo/HomePage/View/HomePageViewController.swift +++ b/DuckDuckGo/HomePage/View/HomePageViewController.swift @@ -20,6 +20,7 @@ import Cocoa import Combine import SwiftUI import History +import PixelKit @MainActor final class HomePageViewController: NSViewController { @@ -107,7 +108,7 @@ final class HomePageViewController: NSViewController { override func viewWillAppear() { super.viewWillAppear() // if OnboardingViewModel.isOnboardingFinished && Pixel.isNewUser { -// Pixel.fire(.newTabInitial, limitTo: .initial) +// PixelKit.fire(GeneralPixel.newTabInitial, limitTo: .initial) // } // TODO: reimplement Pixel.isNewUser subscribeToHistory() } @@ -158,7 +159,7 @@ final class HomePageViewController: NSViewController { func createDefaultBrowserModel() -> HomePage.Models.DefaultBrowserModel { return .init(isDefault: DefaultBrowserPreferences.shared.isDefault, wasClosed: defaultBrowserDismissed, requestSetDefault: { [weak self] in - Pixel.fire(.defaultRequestedFromHomepage) + PixelKit.fire(GeneralPixel.defaultRequestedFromHomepage) let defaultBrowserPreferencesModel = DefaultBrowserPreferences.shared defaultBrowserPreferencesModel.becomeDefault { [weak self] isDefault in _ = defaultBrowserPreferencesModel diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index 51141f5da2..70bfc546d1 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -742,13 +742,13 @@ extension MainViewController { UserDefaults.netP.networkProtectionEntitlementsExpired = false // Clear pixel data - DailyPixel.clearLastFireDate(pixel: .privacyProFeatureEnabled) - Pixel.shared?.clearRepetitions(for: .privacyProBetaUserThankYouDBP) - Pixel.shared?.clearRepetitions(for: .privacyProBetaUserThankYouVPN) + PixelKit.clearFrequencyHistoryFor(pixel: PrivacyProPixel.privacyProFeatureEnabled) + PixelKit.clearFrequencyHistoryFor(pixel: PrivacyProPixel.privacyProBetaUserThankYouDBP) + PixelKit.clearFrequencyHistoryFor(pixel: PrivacyProPixel.privacyProBetaUserThankYouVPN) } @objc func resetDailyPixels(_ sender: Any?) { - UserDefaults.standard.removePersistentDomain(forName: DailyPixel.Constant.dailyPixelStorageIdentifier) + PixelKit.clearFrequencyHistoryForAllPixels() } @objc func in10PercentSurveyOn(_ sender: Any?) { diff --git a/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift b/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift index 7afc044118..27842e11dd 100644 --- a/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift +++ b/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift @@ -289,7 +289,7 @@ final class AddressBarButtonsViewController: NSViewController { bookmarkButton.isHidden = !showBookmarkButton } - func openBookmarkPopover(setFavorite: Bool, accessPoint: Pixel.Event.AccessPoint) { + func openBookmarkPopover(setFavorite: Bool, accessPoint: GeneralPixel.AccessPoint) { let result = bookmarkForCurrentUrl(setFavorite: setFavorite, accessPoint: accessPoint) guard let bookmark = result.bookmark else { assertionFailure("Failed to get a bookmark for the popover") @@ -909,7 +909,7 @@ final class AddressBarButtonsViewController: NSViewController { } } - private func bookmarkForCurrentUrl(setFavorite: Bool, accessPoint: Pixel.Event.AccessPoint) -> (bookmark: Bookmark?, isNew: Bool) { + private func bookmarkForCurrentUrl(setFavorite: Bool, accessPoint: GeneralPixel.AccessPoint) -> (bookmark: Bookmark?, isNew: Bool) { guard let tabViewModel, let url = tabViewModel.tab.content.url else { assertionFailure("No URL for bookmarking") diff --git a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift index d3c0e63cb5..ad34551d95 100644 --- a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift +++ b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift @@ -399,7 +399,7 @@ final class MoreOptionsMenu: NSMenu { } #endif - DataBrokerProtectionExternalWaitlistPixels.fire(pixel: .dataBrokerProtectionWaitlistEntryPointMenuItemDisplayed, frequency: .dailyAndCount) + DataBrokerProtectionExternalWaitlistPixels.fire(pixel: GeneralPixel.dataBrokerProtectionWaitlistEntryPointMenuItemDisplayed, frequency: .dailyAndContinuous) } else { DefaultDataBrokerProtectionFeatureVisibility().disableAndDeleteForWaitlistUsers() @@ -571,7 +571,7 @@ final class EmailOptionsButtonSubMenu: NSMenu { let pixelParameters = self.emailManager.emailPixelParameters self.emailManager.updateLastUseDate() - Pixel.fire(.emailUserCreatedAlias, withAdditionalParameters: pixelParameters) + PixelKit.fire(GeneralPixel.emailUserCreatedAlias, withAdditionalParameters: pixelParameters) NSPasteboard.general.copy(address) NotificationCenter.default.post(name: NSNotification.Name.privateEmailCopiedToClipboard, object: nil) diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift index 4f3937b652..b2faf4a56e 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift @@ -1095,7 +1095,7 @@ extension NavigationBarViewController: OptionsButtonMenuDelegate { #if SUBSCRIPTION func optionsButtonMenuRequestedSubscriptionPurchasePage(_ menu: NSMenu) { WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) - Pixel.fire(.privacyProOfferScreenImpression) + PixelKit.fire(PrivacyProPixel.privacyProOfferScreenImpression) } func optionsButtonMenuRequestedIdentityTheftRestoration(_ menu: NSMenu) { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift index 346e9c830d..8659580807 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift @@ -66,7 +66,7 @@ final class VPNLocationViewModel: ObservableObject { } func onViewAppeared() async { - Pixel.fire(.networkProtectionGeoswitchingOpened) + PixelKit.fire(GeneralPixel.networkProtectionGeoswitchingOpened) await reloadList() } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessaging.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessaging.swift index 382a4f1bd8..b69bda2022 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessaging.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionRemoteMessaging/NetworkProtectionRemoteMessaging.swift @@ -18,6 +18,7 @@ import Foundation import Networking +import PixelKit #if NETWORK_PROTECTION @@ -92,7 +93,7 @@ final class DefaultNetworkProtectionRemoteMessaging: NetworkProtectionRemoteMess try self.messageStorage.store(messages: messages) self.updateLastRefreshDate() // Update last refresh date on success, otherwise let the app try again next time } catch { - Pixel.fire(.debug(event: .networkProtectionRemoteMessageStorageFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.networkProtectionRemoteMessageStorageFailed, error: error)) } case .failure(let error): // Ignore 403 errors, those happen when a file can't be found on S3 @@ -101,7 +102,7 @@ final class DefaultNetworkProtectionRemoteMessaging: NetworkProtectionRemoteMess return } - Pixel.fire(.debug(event: .networkProtectionRemoteMessageFetchingFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.networkProtectionRemoteMessageFetchingFailed, error: error)) } } diff --git a/DuckDuckGo/PasswordManager/Bitwarden/Model/BWManager.swift b/DuckDuckGo/PasswordManager/Bitwarden/Model/BWManager.swift index 24be234ef2..1e03cf245a 100644 --- a/DuckDuckGo/PasswordManager/Bitwarden/Model/BWManager.swift +++ b/DuckDuckGo/PasswordManager/Bitwarden/Model/BWManager.swift @@ -20,6 +20,7 @@ import Common import Foundation import SwiftUI import OpenSSL +import PixelKit final class BWManager: BWManagement, ObservableObject { @@ -121,7 +122,7 @@ final class BWManager: BWManagement, ObservableObject { try communicator.runProxyProcess() } catch { os_log("BWManagement: Running of proxy process failed", type: .error) - Pixel.fire(.debug(event: .bitwardenNotResponding)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenNotResponding)) status = .error(error: .runningOfProxyProcessFailed) scheduleConnectionAttempt() } @@ -191,7 +192,7 @@ final class BWManager: BWManagement, ObservableObject { if let sharedKey = sharedKey { guard let sharedKeyData = Data(base64Encoded: sharedKey), encryption.setSharedKey(sharedKeyData) else { - Pixel.fire(.debug(event: .bitwardenSharedKeyInjectionFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenSharedKeyInjectionFailed)) status = .error(error: .sharedKeyInjectionFailed) return } @@ -222,7 +223,7 @@ final class BWManager: BWManagement, ObservableObject { self?.verificationTimer = nil if self?.status == .waitingForStatusResponse { - Pixel.fire(.debug(event: .bitwardenNotResponding)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenNotResponding)) BWNotRespondingAlert.show() } } @@ -233,9 +234,9 @@ final class BWManager: BWManagement, ObservableObject { case "cannot-decrypt": logOrAssertionFailure("BWManagement: Bitwarden error - cannot decrypt") - if Pixel.Event.Repetition(key: "bitwardenRespondedCannotDecryptUnique", update: false) != .repetitive { - Pixel.fire(.debug(event: .bitwardenRespondedCannotDecryptUnique())) - } +// if Pixel.Event.Repetition(key: "bitwardenRespondedCannotDecryptUnique", update: false) != .repetitive { // TODO: reimplement? +// PixelKit.fire(DebugEvent(GeneralPixel.bitwardenRespondedCannotDecryptUnique)) +// } case "locked": if case let .connected(vault) = status { status = .connected(vault: vault.locked) @@ -244,20 +245,20 @@ final class BWManager: BWManagement, ObservableObject { } return default: logOrAssertionFailure("BWManager: Unhandled error") - Pixel.fire(.debug(event: .bitwardenRespondedWithError)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenRespondedWithError)) } } private func handleHandshakeResponse(encryptedSharedKey: String, status: String) { guard status == "success" else { - Pixel.fire(.debug(event: .bitwardenHandshakeFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenHandshakeFailed)) self.status = .error(error: .handshakeFailed) cancelConnectionAndScheduleNextAttempt() return } guard let sharedKey = encryption.decryptSharedKey(encryptedSharedKey) else { - Pixel.fire(.debug(event: .bitwardenDecryptionOfSharedKeyFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenDecryptionOfSharedKeyFailed)) self.status = .error(error: .decryptionOfSharedKeyFailed) cancelConnectionAndScheduleNextAttempt() return @@ -266,7 +267,7 @@ final class BWManager: BWManagement, ObservableObject { do { try keyStorage.save(sharedKey: sharedKey) } catch { - Pixel.fire(.debug(event: .bitwardenStoringOfTheSharedKeyFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenStoringOfTheSharedKeyFailed)) self.status = .error(error: .storingOfTheSharedKeyFailed) return } @@ -284,7 +285,7 @@ final class BWManager: BWManagement, ObservableObject { let hmacString = encryptedPayload.mac, let hmac = Data(base64Encoded: hmacString) else { - Pixel.fire(.debug(event: .bitwardenParsingFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenParsingFailed)) status = .error(error: .parsingFailed) return } @@ -292,20 +293,20 @@ final class BWManager: BWManagement, ObservableObject { // Compare HMAC let ourHmac = encryption.computeHmac(data, iv: ivData) guard ourHmac == hmac else { - Pixel.fire(.debug(event: .bitwardenHmacComparisonFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenHmacComparisonFailed)) logOrAssertionFailure("BWManager: HMAC comparison failed") return } let decryptedData = encryption.decryptData(data, andIv: ivData) guard decryptedData.count > 0 else { - Pixel.fire(.debug(event: .bitwardenDecryptionFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenDecryptionFailed)) status = .error(error: .decryptionOfDataFailed) return } guard let response = BWResponse(from: decryptedData) else { - Pixel.fire(.debug(event: .bitwardenParsingFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenParsingFailed)) status = .error(error: .parsingFailed) return } @@ -343,7 +344,7 @@ final class BWManager: BWManagement, ObservableObject { private func handleStatusResponse(payloadItemArray: [BWResponse.PayloadItem]) { // Find the active vault guard let activePayloadItem = payloadItemArray.filter({ $0.active ?? false }).first else { - Pixel.fire(.debug(event: .bitwardenNoActiveVault)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenNoActiveVault)) status = .error(error: .noActiveVault) return } @@ -369,7 +370,7 @@ final class BWManager: BWManagement, ObservableObject { } handleError(error) - Pixel.fire(.debug(event: .bitwardenCredentialRetrievalFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenCredentialRetrievalFailed)) completion([], BWError.credentialRetrievalFailed) } } @@ -385,7 +386,7 @@ final class BWManager: BWManagement, ObservableObject { if payloadItem.status == "success" { completion(nil) } else { - Pixel.fire(.debug(event: .bitwardenCredentialCreationFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenCredentialCreationFailed)) completion(BWError.credentialCreationFailed) } } @@ -401,7 +402,7 @@ final class BWManager: BWManagement, ObservableObject { if payloadItem.status == "success" { completion(nil) } else { - Pixel.fire(.debug(event: .bitwardenCredentialUpdateFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenCredentialUpdateFailed)) completion(BWError.credentialUpdateFailed) } } @@ -431,7 +432,7 @@ final class BWManager: BWManagement, ObservableObject { let messageData = BWRequest.makeEncryptedCommandRequest(encryptedCommand: encryptedCommand, messageId: messageIdGenerator.generateMessageId()).data else { logOrAssertionFailure("BWManager: Making the status message failed") - Pixel.fire(.debug(event: .bitwardenSendingOfMessageFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenSendingOfMessageFailed)) status = .error(error: .sendingOfMessageFailed) return } @@ -447,7 +448,7 @@ final class BWManager: BWManagement, ObservableObject { let messageData = BWRequest.makeEncryptedCommandRequest(encryptedCommand: encryptedCommand, messageId: messageId).data else { logOrAssertionFailure("BWManager: Making the credential retrieval message failed") - Pixel.fire(.debug(event: .bitwardenSendingOfMessageFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenSendingOfMessageFailed)) status = .error(error: .sendingOfMessageFailed) return } @@ -467,7 +468,7 @@ final class BWManager: BWManagement, ObservableObject { let messageData = BWRequest.makeEncryptedCommandRequest(encryptedCommand: encryptedCommand, messageId: messageId).data else { logOrAssertionFailure("BWManager: Making the credential creation message failed") - Pixel.fire(.debug(event: .bitwardenSendingOfMessageFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenSendingOfMessageFailed)) status = .error(error: .sendingOfMessageFailed) return } @@ -488,7 +489,7 @@ final class BWManager: BWManagement, ObservableObject { let messageData = BWRequest.makeEncryptedCommandRequest(encryptedCommand: encryptedCommand, messageId: messageId).data else { logOrAssertionFailure("BWManager: Making the credential update message failed") - Pixel.fire(.debug(event: .bitwardenSendingOfMessageFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenSendingOfMessageFailed)) status = .error(error: .sendingOfMessageFailed) return } @@ -546,7 +547,7 @@ final class BWManager: BWManagement, ObservableObject { let email = payloadItem.email, let statusString = payloadItem.status, let status = BWVault.Status(rawValue: statusString) else { - Pixel.fire(.debug(event: .bitwardenStatusParsingFailed)) + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenStatusParsingFailed)) self.status = .error(error: .statusParsingFailed) return } diff --git a/DuckDuckGo/PasswordManager/PasswordManagerCoordinator.swift b/DuckDuckGo/PasswordManager/PasswordManagerCoordinator.swift index 39c5b222df..d4158aed5c 100644 --- a/DuckDuckGo/PasswordManager/PasswordManagerCoordinator.swift +++ b/DuckDuckGo/PasswordManager/PasswordManagerCoordinator.swift @@ -20,6 +20,7 @@ import Foundation import BrowserServicesKit import Combine import Common +import PixelKit protocol PasswordManagerCoordinating: BrowserServicesKit.PasswordManager { @@ -247,7 +248,7 @@ final class PasswordManagerCoordinator: PasswordManagerCoordinating { return } - Pixel.fire(.bitwardenPasswordAutofilled) + PixelKit.fire(GeneralPixel.bitwardenPasswordAutofilled) } func reportPasswordSave() { @@ -255,7 +256,7 @@ final class PasswordManagerCoordinator: PasswordManagerCoordinating { return } - Pixel.fire(.bitwardenPasswordSaved) + PixelKit.fire(GeneralPixel.bitwardenPasswordSaved) } // MARK: - Cache diff --git a/DuckDuckGo/Permissions/Model/StoredPermission.swift b/DuckDuckGo/Permissions/Model/StoredPermission.swift index 5fb8ee39ad..b846eac0b5 100644 --- a/DuckDuckGo/Permissions/Model/StoredPermission.swift +++ b/DuckDuckGo/Permissions/Model/StoredPermission.swift @@ -17,6 +17,7 @@ // import Foundation +import PixelKit enum PersistedPermissionDecision { case deny @@ -64,7 +65,7 @@ struct PermissionEntity: Equatable { guard let domain = managedObject.domainEncrypted as? String, let permissionTypeString = managedObject.permissionType, let permissionType = PermissionType(rawValue: permissionTypeString) else { - Pixel.fire(.debug(event: .permissionDecryptionFailedUnique), limitTo: .dailyFirst) + PixelKit.fire(DebugEvent(GeneralPixel.permissionDecryptionFailedUnique), frequency: .dailyOnly) assertionFailure("\(#file): Failed to create PermissionEntity from PermissionManagedObject") return nil } diff --git a/DuckDuckGo/Preferences/Model/AppearancePreferences.swift b/DuckDuckGo/Preferences/Model/AppearancePreferences.swift index 833bab73f0..2957f39fbc 100644 --- a/DuckDuckGo/Preferences/Model/AppearancePreferences.swift +++ b/DuckDuckGo/Preferences/Model/AppearancePreferences.swift @@ -20,6 +20,7 @@ import Foundation import AppKit import Bookmarks import Common +import PixelKit protocol AppearancePreferencesPersistor { var showFullURL: Bool { get set } @@ -163,7 +164,7 @@ final class AppearancePreferences: ObservableObject { persistor.isFavoriteVisible = isFavoriteVisible // Temporary Pixel if !isFavoriteVisible { - Pixel.fire(.favoriteSectionHidden) + PixelKit.fire(GeneralPixel.favoriteSectionHidden) } } } @@ -173,7 +174,7 @@ final class AppearancePreferences: ObservableObject { persistor.isContinueSetUpVisible = isContinueSetUpVisible // Temporary Pixel if !isContinueSetUpVisible { - Pixel.fire(.continueSetUpSectionHidden) + PixelKit.fire(GeneralPixel.continueSetUpSectionHidden) } } } @@ -183,7 +184,7 @@ final class AppearancePreferences: ObservableObject { persistor.isRecentActivityVisible = isRecentActivityVisible // Temporary Pixel if !isRecentActivityVisible { - Pixel.fire(.recentActivitySectionHidden) + PixelKit.fire(GeneralPixel.recentActivitySectionHidden) } } } diff --git a/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift b/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift index b26eec7c28..fd09994721 100644 --- a/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift +++ b/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift @@ -80,7 +80,7 @@ final class DefaultBrowserPreferences: ObservableObject { guard NSApp.runType.requiresEnvironment else { return } #endif // if Pixel.isNewUser && isDefault { // TODO: reimplement Pixel.isNewUser -// Pixel.fire(.setAsDefaultInitial, limitTo: .initial) +// PixelKit.fire(GeneralPixel.setAsDefaultInitial, limitTo: .initial) // } } } diff --git a/DuckDuckGo/Preferences/Model/SyncPreferences.swift b/DuckDuckGo/Preferences/Model/SyncPreferences.swift index 28be6ba74d..fc63e7c21c 100644 --- a/DuckDuckGo/Preferences/Model/SyncPreferences.swift +++ b/DuckDuckGo/Preferences/Model/SyncPreferences.swift @@ -244,7 +244,7 @@ final class SyncPreferences: ObservableObject, SyncUI.ManagementViewModel { UserDefaults.standard.set(false, forKey: UserDefaultsWrapper.Key.syncCredentialsPaused.rawValue) } catch { managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToTurnSyncOff, description: error.localizedDescription) - firePixelIfNeeded(event: .debug(event: .syncLogoutError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.syncLogoutError(error: error))) } } } @@ -396,7 +396,7 @@ extension SyncPreferences: ManagementDialogModelDelegate { UserDefaults.standard.set(false, forKey: UserDefaultsWrapper.Key.syncCredentialsPaused.rawValue) } catch { managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToDeleteData, description: error.localizedDescription) - firePixelIfNeeded(event: .debug(event: .syncDeleteAccountError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.syncDeleteAccountError(error: error))) } } } @@ -411,7 +411,7 @@ extension SyncPreferences: ManagementDialogModelDelegate { mapDevices(devices) } catch { managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToUpdateDeviceName, description: error.localizedDescription) - firePixelIfNeeded(event: .debug(event: .syncUpdateDeviceError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.syncUpdateDeviceError(error: error))) } syncService.scheduler.resumeSyncQueue() } @@ -427,7 +427,7 @@ extension SyncPreferences: ManagementDialogModelDelegate { let device = deviceInfo() let devices = try await syncService.login(recoveryKey, deviceName: device.name, deviceType: device.type) mapDevices(devices) - Pixel.fire(.syncLogin) + PixelKit.fire(GeneralPixel.syncLogin) DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { if isRecovery { self.showDevicesSynced() @@ -449,11 +449,11 @@ extension SyncPreferences: ManagementDialogModelDelegate { let device = deviceInfo() presentDialog(for: .prepareToSync) try await syncService.createAccount(deviceName: device.name, deviceType: device.type) - Pixel.fire(.syncSignupDirect) + PixelKit.fire(GeneralPixel.syncSignupDirect) presentDialog(for: .saveRecoveryCode(recoveryCode ?? "")) } catch { managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToSyncToServer, description: error.localizedDescription) - firePixelIfNeeded(event: .debug(event: .syncSignupError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.syncSignupError(error: error))) } } } @@ -489,7 +489,7 @@ extension SyncPreferences: ManagementDialogModelDelegate { description: error.localizedDescription ) } - firePixelIfNeeded(event: .debug(event: .syncLoginError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.syncLoginError(error: error))) } } } @@ -512,14 +512,14 @@ extension SyncPreferences: ManagementDialogModelDelegate { try await loginAndShowPresentedDialog(recoveryKey, isRecovery: fromRecoveryScreen) } catch { managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToMergeTwoAccounts, description: "") - firePixelIfNeeded(event: .debug(event: .syncLoginExistingAccountError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.syncLoginExistingAccountError(error: error))) } } else if let connectKey = syncCode.connect { do { if syncService.account == nil { let device = deviceInfo() try await syncService.createAccount(deviceName: device.name, deviceType: device.type) - Pixel.fire(.syncSignupConnect) + PixelKit.fire(GeneralPixel.syncSignupConnect) presentDialog(for: .saveRecoveryCode(recoveryCode)) } @@ -538,7 +538,7 @@ extension SyncPreferences: ManagementDialogModelDelegate { type: .unableToSyncToOtherDevice, description: error.localizedDescription ) - firePixelIfNeeded(event: .debug(event: .syncLoginError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.syncLoginError(error: error))) } } else { managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .invalidCode, description: "") @@ -580,7 +580,7 @@ extension SyncPreferences: ManagementDialogModelDelegate { } } catch { managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableCreateRecoveryPDF, description: error.localizedDescription) - firePixelIfNeeded(event: .debug(event: .syncCannotCreateRecoveryPDF, error: nil)) + PixelKit.fire(DebugEvent(GeneralPixel.syncCannotCreateRecoveryPDF)) } } @@ -595,7 +595,7 @@ extension SyncPreferences: ManagementDialogModelDelegate { managementDialogModel.endFlow() } catch { managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToRemoveDevice, description: error.localizedDescription) - firePixelIfNeeded(event: .debug(event: .syncRemoveDeviceError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.syncRemoveDeviceError(error: error))) } } } @@ -664,16 +664,4 @@ extension SyncPreferences: ManagementDialogModelDelegate { func recoveryCodePasted(_ code: String, fromRecoveryScreen: Bool) { recoverDevice(recoveryCode: code, fromRecoveryScreen: fromRecoveryScreen) } - - private func firePixelIfNeeded(event: Pixel.Event) { - if case let .debug(_, debugError) = event { - if debugError == nil { - Pixel.fire(event) - } - if let syncError = debugError as? SyncError, !syncError.isServerError { - Pixel.fire(event, withAdditionalParameters: syncError.errorParameters) - } - } - } - } diff --git a/DuckDuckGo/Preferences/View/PreferencesAutofillView.swift b/DuckDuckGo/Preferences/View/PreferencesAutofillView.swift index 63cdb989d5..b87117a34e 100644 --- a/DuckDuckGo/Preferences/View/PreferencesAutofillView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesAutofillView.swift @@ -19,6 +19,7 @@ import PreferencesViews import SwiftUI import SwiftUIExtensions +import PixelKit fileprivate extension Preferences.Const { static let autoLockWarningOffset: CGFloat = { @@ -133,7 +134,7 @@ extension Preferences { Button(UserText.autofillExcludedSitesReset) { showingResetNeverPromptSitesSheet.toggle() if showingResetNeverPromptSitesSheet { - Pixel.fire(.autofillLoginsSettingsResetExcludedDisplayed) + PixelKit.fire(GeneralPixel.autofillLoginsSettingsResetExcludedDisplayed) } } }.sheet(isPresented: $showingResetNeverPromptSitesSheet) { @@ -348,7 +349,7 @@ struct ResetNeverPromptSitesSheet: View { Spacer() Button(UserText.cancel) { isSheetPresented.toggle() - Pixel.fire(.autofillLoginsSettingsResetExcludedDismissed) + PixelKit.fire(GeneralPixel.autofillLoginsSettingsResetExcludedDismissed) } Button(action: { saveChanges() @@ -365,7 +366,7 @@ struct ResetNeverPromptSitesSheet: View { private func saveChanges() { autofillPreferencesModel.resetNeverPromptWebsites() isSheetPresented.toggle() - Pixel.fire(.autofillLoginsSettingsResetExcludedConfirmed) + PixelKit.fire(GeneralPixel.autofillLoginsSettingsResetExcludedConfirmed) } } diff --git a/DuckDuckGo/Preferences/View/PreferencesDefaultBrowserView.swift b/DuckDuckGo/Preferences/View/PreferencesDefaultBrowserView.swift index cc7692b366..8cc530b171 100644 --- a/DuckDuckGo/Preferences/View/PreferencesDefaultBrowserView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesDefaultBrowserView.swift @@ -21,6 +21,7 @@ import Combine import PreferencesViews import SwiftUI import SwiftUIExtensions +import PixelKit extension Preferences { @@ -52,7 +53,7 @@ extension Preferences { } .padding(.trailing, 8) Button(action: { - Pixel.fire(.defaultRequestedFromSettings) + PixelKit.fire(GeneralPixel.defaultRequestedFromSettings) defaultBrowserModel.becomeDefault() }) { Text(UserText.makeDefaultBrowser) diff --git a/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift b/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift index 4a5d5113f3..ecb56bcdd2 100644 --- a/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift +++ b/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift @@ -22,6 +22,7 @@ import Combine import BrowserServicesKit import PrivacyDashboard import Common +import PixelKit protocol PrivacyDashboardViewControllerSizeDelegate: AnyObject { @@ -44,30 +45,27 @@ final class PrivacyDashboardViewController: NSViewController { private let brokenSiteReporter: BrokenSiteReporter = { BrokenSiteReporter(pixelHandler: { parameters in - Pixel.fire( - .brokenSiteReport, - withAdditionalParameters: parameters, - allowedQueryReservedCharacters: BrokenSiteReport.allowedQueryReservedCharacters - ) + PixelKit.fire(GeneralPixel.brokenSiteReport, + withAdditionalParameters: parameters, + allowedQueryReservedCharacters: BrokenSiteReport.allowedQueryReservedCharacters) }, keyValueStoring: UserDefaults.standard) }() private let toggleProtectionsOffReporter: BrokenSiteReporter = { BrokenSiteReporter(pixelHandler: { parameters in - Pixel.fire( - .protectionToggledOffBreakageReport, - withAdditionalParameters: parameters, - allowedQueryReservedCharacters: BrokenSiteReport.allowedQueryReservedCharacters) + PixelKit.fire(GeneralPixel.protectionToggledOffBreakageReport, + withAdditionalParameters: parameters, + allowedQueryReservedCharacters: BrokenSiteReport.allowedQueryReservedCharacters) }, keyValueStoring: UserDefaults.standard) }() private let toggleReportEvents = EventMapping { event, _, parameters, _ in - let domainEvent: Pixel.Event + let domainEvent: GeneralPixel switch event { case .toggleReportDismiss: domainEvent = .toggleReportDismiss case .toggleReportDoNotSend: domainEvent = .toggleReportDoNotSend } - Pixel.fire(domainEvent, withAdditionalParameters: parameters) + PixelKit.fire(domainEvent, withAdditionalParameters: parameters) } private let permissionHandler = PrivacyDashboardPermissionHandler() @@ -177,10 +175,10 @@ final class PrivacyDashboardViewController: NSViewController { let configuration = ContentBlocking.shared.privacyConfigurationManager.privacyConfig if state.isProtected && configuration.isUserUnprotected(domain: domain) { configuration.userEnabledProtection(forDomain: domain) - Pixel.fire(.dashboardProtectionAllowlistRemove(triggerOrigin: state.eventOrigin.screen.rawValue), includeAppVersionParameter: false) + PixelKit.fire(GeneralPixel.dashboardProtectionAllowlistRemove(triggerOrigin: state.eventOrigin.screen.rawValue), includeAppVersionParameter: false) } else { configuration.userDisabledProtection(forDomain: domain) - Pixel.fire(.dashboardProtectionAllowlistAdd(triggerOrigin: state.eventOrigin.screen.rawValue), includeAppVersionParameter: false) + PixelKit.fire(GeneralPixel.dashboardProtectionAllowlistAdd(triggerOrigin: state.eventOrigin.screen.rawValue), includeAppVersionParameter: false) } let completionToken = ContentBlocking.shared.contentBlockingManager.scheduleCompilation() @@ -193,7 +191,7 @@ final class PrivacyDashboardViewController: NSViewController { extension PrivacyDashboardViewController: PrivacyDashboardControllerDelegate { func privacyDashboardControllerDidRequestShowReportBrokenSite(_ privacyDashboardController: PrivacyDashboard.PrivacyDashboardController) { - // Not used in macOS: Pixel.fire(.privacyDashboardReportBrokenSite) + // Not used in macOS: PixelKit.fire(GeneralPixel.privacyDashboardReportBrokenSite) } func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, diff --git a/DuckDuckGo/SecureVault/Model/AutofillNeverPromptWebsitesManager.swift b/DuckDuckGo/SecureVault/Model/AutofillNeverPromptWebsitesManager.swift index 60d8eb0309..015f0606f3 100644 --- a/DuckDuckGo/SecureVault/Model/AutofillNeverPromptWebsitesManager.swift +++ b/DuckDuckGo/SecureVault/Model/AutofillNeverPromptWebsitesManager.swift @@ -18,6 +18,7 @@ import Foundation import BrowserServicesKit +import PixelKit final class AutofillNeverPromptWebsitesManager { @@ -48,7 +49,7 @@ final class AutofillNeverPromptWebsitesManager { fetchNeverPromptWebsites() return id } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) throw error } } @@ -64,7 +65,7 @@ final class AutofillNeverPromptWebsitesManager { fetchNeverPromptWebsites() return true } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) return false } } @@ -77,7 +78,7 @@ final class AutofillNeverPromptWebsitesManager { do { neverPromptWebsites = try secureVault.neverPromptWebsites() } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) neverPromptWebsites = [] } } diff --git a/DuckDuckGo/SecureVault/SecureVaultErrorReporter.swift b/DuckDuckGo/SecureVault/SecureVaultErrorReporter.swift index 5b5722bf34..2533f9c6ee 100644 --- a/DuckDuckGo/SecureVault/SecureVaultErrorReporter.swift +++ b/DuckDuckGo/SecureVault/SecureVaultErrorReporter.swift @@ -19,6 +19,7 @@ import Foundation import BrowserServicesKit import SecureStorage +import PixelKit final class SecureVaultErrorReporter: SecureVaultErrorReporting { static let shared = SecureVaultErrorReporter() @@ -29,9 +30,9 @@ final class SecureVaultErrorReporter: SecureVaultErrorReporting { switch error { case .initFailed, .failedToOpenDatabase: - Pixel.fire(.debug(event: .secureVaultInitError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultInitError, error: error)) default: - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError, error: error)) } } diff --git a/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift b/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift index ab6ea02921..eb418179ba 100644 --- a/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift +++ b/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift @@ -24,6 +24,7 @@ import DDGSync import Foundation import SecureStorage import SwiftUI +import PixelKit protocol PasswordManagementDelegate: AnyObject { @@ -548,7 +549,7 @@ final class PasswordManagementViewController: NSViewController { if case SecureStorageError.duplicateRecord = error { showDuplicateAlert() } else { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } } } @@ -571,7 +572,7 @@ final class PasswordManagementViewController: NSViewController { postChange() } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } } @@ -615,7 +616,7 @@ final class PasswordManagementViewController: NSViewController { postChange() } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } } @@ -634,7 +635,7 @@ final class PasswordManagementViewController: NSViewController { self.requestSync() self.refreshData() } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError, error: error)) } default: @@ -657,7 +658,7 @@ final class PasswordManagementViewController: NSViewController { try self.secureVault?.deleteIdentityFor(identityId: id) self.refreshData() } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError, error: error)) } default: @@ -699,7 +700,7 @@ final class PasswordManagementViewController: NSViewController { try self.secureVault?.deleteCreditCardFor(cardId: id) self.refreshData() } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError, error: error)) } default: @@ -753,7 +754,7 @@ final class PasswordManagementViewController: NSViewController { self?.syncModelsOnNote(note) } } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError, error: error)) } } @@ -857,7 +858,7 @@ final class PasswordManagementViewController: NSViewController { items = cards.map(SecureVaultItem.card) } } catch { - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError, error: error)) } DispatchQueue.main.async { diff --git a/DuckDuckGo/SecureVault/View/SaveCredentialsViewController.swift b/DuckDuckGo/SecureVault/View/SaveCredentialsViewController.swift index 4647fd79a8..fa4da02ff7 100644 --- a/DuckDuckGo/SecureVault/View/SaveCredentialsViewController.swift +++ b/DuckDuckGo/SecureVault/View/SaveCredentialsViewController.swift @@ -20,6 +20,7 @@ import AppKit import BrowserServicesKit import Combine import Common +import PixelKit protocol SaveCredentialsDelegate: AnyObject { @@ -227,10 +228,10 @@ final class SaveCredentialsViewController: NSViewController { } } catch { os_log("%s:%s: failed to store credentials %s", type: .error, className, #function, error.localizedDescription) - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } - Pixel.fire(.autofillItemSaved(kind: .password)) + PixelKit.fire(GeneralPixel.autofillItemSaved(kind: .password)) if passwordManagerCoordinator.isEnabled { passwordManagerCoordinator.reportPasswordSave() @@ -313,7 +314,7 @@ final class SaveCredentialsViewController: NSViewController { } catch { os_log("%: failed to save never prompt for website %s", type: .error, #function, error.localizedDescription) } - Pixel.fire(.autofillLoginsSaveLoginModalExcludeSiteConfirmed) + PixelKit.fire(GeneralPixel.autofillLoginsSaveLoginModalExcludeSiteConfirmed) onNotNowClicked(sender: nil) } diff --git a/DuckDuckGo/SecureVault/View/SaveIdentityViewController.swift b/DuckDuckGo/SecureVault/View/SaveIdentityViewController.swift index 075e31befd..d29870499b 100644 --- a/DuckDuckGo/SecureVault/View/SaveIdentityViewController.swift +++ b/DuckDuckGo/SecureVault/View/SaveIdentityViewController.swift @@ -20,6 +20,7 @@ import AppKit import BrowserServicesKit import Combine import Common +import PixelKit protocol SaveIdentityDelegate: AnyObject { @@ -71,10 +72,10 @@ final class SaveIdentityViewController: NSViewController { do { try AutofillSecureVaultFactory.makeVault(errorReporter: SecureVaultErrorReporter.shared).storeIdentity(identity) - Pixel.fire(.autofillItemSaved(kind: .identity)) + PixelKit.fire(GeneralPixel.autofillItemSaved(kind: .identity)) } catch { os_log("%s:%s: failed to store identity %s", type: .error, className, #function, error.localizedDescription) - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError, error: error)) } } diff --git a/DuckDuckGo/SecureVault/View/SavePaymentMethodViewController.swift b/DuckDuckGo/SecureVault/View/SavePaymentMethodViewController.swift index 652c0aa6ae..83d28bd349 100644 --- a/DuckDuckGo/SecureVault/View/SavePaymentMethodViewController.swift +++ b/DuckDuckGo/SecureVault/View/SavePaymentMethodViewController.swift @@ -20,6 +20,7 @@ import Foundation import BrowserServicesKit import Combine import Common +import PixelKit protocol SavePaymentMethodDelegate: AnyObject { @@ -94,7 +95,7 @@ final class SavePaymentMethodViewController: NSViewController { try AutofillSecureVaultFactory.makeVault(errorReporter: SecureVaultErrorReporter.shared).storeCreditCard(paymentMethod) } catch { os_log("%s:%s: failed to store payment method %s", type: .error, className, #function, error.localizedDescription) - Pixel.fire(.debug(event: .secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError, error: error)) } } diff --git a/DuckDuckGo/StateRestoration/AppStateRestorationManager.swift b/DuckDuckGo/StateRestoration/AppStateRestorationManager.swift index 521b01b490..e99fd44f8c 100644 --- a/DuckDuckGo/StateRestoration/AppStateRestorationManager.swift +++ b/DuckDuckGo/StateRestoration/AppStateRestorationManager.swift @@ -19,6 +19,7 @@ import Foundation import Combine import Common +import PixelKit @MainActor final class AppStateRestorationManager: NSObject { @@ -71,10 +72,8 @@ final class AppStateRestorationManager: NSObject { // ignore } catch { os_log("App state could not be decoded: %s", "\(error)") - Pixel.fire( - .debug(event: .appStateRestorationFailed, error: error), - withAdditionalParameters: ["interactive": String(interactive)] - ) + PixelKit.fire(DebugEvent(GeneralPixel.appStateRestorationFailed, error: error), + withAdditionalParameters: ["interactive": String(interactive)]) } return state @@ -140,7 +139,7 @@ final class AppStateRestorationManager: NSObject { // ignore } catch { os_log("Pinned tabs state could not be decoded: %s", "\(error)") - Pixel.fire(.debug(event: .appStateRestorationFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.appStateRestorationFailed, error: error)) } } diff --git a/DuckDuckGo/Statistics/Pixel Legacy/PixelDataModel.xcdatamodeld/PixelDataModel.xcdatamodel/contents b/DuckDuckGo/Statistics/ATB/PixelDataModel.xcdatamodeld/PixelDataModel.xcdatamodel/contents similarity index 100% rename from DuckDuckGo/Statistics/Pixel Legacy/PixelDataModel.xcdatamodeld/PixelDataModel.xcdatamodel/contents rename to DuckDuckGo/Statistics/ATB/PixelDataModel.xcdatamodeld/PixelDataModel.xcdatamodel/contents diff --git a/DuckDuckGo/Statistics/Pixel Legacy/PixelDataRecord.swift b/DuckDuckGo/Statistics/ATB/PixelDataRecord.swift similarity index 100% rename from DuckDuckGo/Statistics/Pixel Legacy/PixelDataRecord.swift rename to DuckDuckGo/Statistics/ATB/PixelDataRecord.swift diff --git a/DuckDuckGo/Statistics/Pixel Legacy/PixelDataStore.swift b/DuckDuckGo/Statistics/ATB/PixelDataStore.swift similarity index 100% rename from DuckDuckGo/Statistics/Pixel Legacy/PixelDataStore.swift rename to DuckDuckGo/Statistics/ATB/PixelDataStore.swift diff --git a/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift b/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift index 296f67e901..073bf8ea41 100644 --- a/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift +++ b/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift @@ -20,6 +20,7 @@ import Common import Foundation import BrowserServicesKit import Networking +import PixelKit final class StatisticsLoader { @@ -53,7 +54,7 @@ final class StatisticsLoader { completion() } } - Pixel.fire(.serp) + PixelKit.fire(GeneralPixel.serp) self.fireDailyOsVersionCounterPixel() } else if !self.statisticsStore.isAppRetentionFiredToday { self.refreshAppRetentionAtb(completion: completion) @@ -217,8 +218,8 @@ final class StatisticsLoader { let randomDelay = Double.random(in: 0.5...5) DispatchQueue.global().asyncAfter(deadline: .now() + randomDelay) { - Pixel.fire(.dailyOsVersionCounter, - limitTo: .dailyFirst, + PixelKit.fire(GeneralPixel.dailyOsVersionCounter, + frequency: .dailyOnly, includeAppVersionParameter: false) } } diff --git a/DuckDuckGo/Statistics/Experiment/PixelExperiment.swift b/DuckDuckGo/Statistics/Experiment/PixelExperiment.swift index 64af60e55b..7584feec0c 100644 --- a/DuckDuckGo/Statistics/Experiment/PixelExperiment.swift +++ b/DuckDuckGo/Statistics/Experiment/PixelExperiment.swift @@ -17,6 +17,7 @@ // import Foundation +import PixelKit enum PixelExperiment: String, CaseIterable { @@ -136,16 +137,16 @@ final internal class PixelExperimentLogic { // Often used func fireFirstSerpPixel() { guard allocatedCohort != nil, let cohort else { return } - Pixel.fire(.serpInitial(cohort: cohort.rawValue), limitTo: .initial, includeAppVersionParameter: false) + PixelKit.fire(GeneralPixel.serpInitial(cohort: cohort.rawValue), frequency: .justOnce, includeAppVersionParameter: false) } // Often used for retention experiments func fireDay21To27SerpPixel() { guard allocatedCohort != nil, let cohort else { return } - if now() >= Pixel.firstLaunchDate.adding(.days(21)) && now() <= Pixel.firstLaunchDate.adding(.days(27)) { - Pixel.fire(.serpDay21to27(cohort: cohort.rawValue), limitTo: .initial, includeAppVersionParameter: false) - } +// if now() >= Pixel.firstLaunchDate.adding(.days(21)) && now() <= Pixel.firstLaunchDate.adding(.days(27)) { // TODO: reimplement +// PixelKit.fire(GeneralPixel.serpDay21to27(cohort: cohort.rawValue), frequency: .justOnce, includeAppVersionParameter: false) +// } } func cleanup() { diff --git a/DuckDuckGo/Statistics/GeneralPixel.swift b/DuckDuckGo/Statistics/GeneralPixel.swift index 0eedc389c4..862b56a8fb 100644 --- a/DuckDuckGo/Statistics/GeneralPixel.swift +++ b/DuckDuckGo/Statistics/GeneralPixel.swift @@ -22,6 +22,7 @@ import BrowserServicesKit import DDGSync import Configuration +// swiftlint:disable:next type_body_length enum GeneralPixel: PixelKitEventV2 { case crash @@ -168,7 +169,7 @@ enum GeneralPixel: PixelKitEventV2 { case assertionFailure(message: String, file: StaticString, line: UInt) - case dbMakeDatabaseError(error: Error) + case dbMakeDatabaseError(error: Error?) case dbContainerInitializationError(error: Error) case dbInitializationError(error: Error) case dbSaveExcludedHTTPSDomainsError(error: Error?) @@ -216,7 +217,7 @@ enum GeneralPixel: PixelKitEventV2 { case historyInsertVisitFailed case historyRemoveVisitsFailed - case emailAutofillKeychainError(error: Error) + case emailAutofillKeychainError case bookmarksStoreRootFolderMigrationFailed case bookmarksStoreFavoritesFolderMigrationFailed @@ -237,14 +238,14 @@ enum GeneralPixel: PixelKitEventV2 { case removedInvalidBookmarkManagedObjects case bitwardenNotResponding - // case bitwardenRespondedCannotDecryptUnique(repetition: Repetition = .init(key: "bitwardenRespondedCannotDecryptUnique")) // TODO: REIMPLEMENTATION?? + case bitwardenRespondedCannotDecryptUnique //(repetition: Repetition = .init(key: "bitwardenRespondedCannotDecryptUnique")) // TODO: REIMPLEMENTATION?? case bitwardenHandshakeFailed case bitwardenDecryptionOfSharedKeyFailed case bitwardenStoringOfTheSharedKeyFailed case bitwardenCredentialRetrievalFailed case bitwardenCredentialCreationFailed case bitwardenCredentialUpdateFailed - case bitwardenRespondedWithError(error: Error) + case bitwardenRespondedWithError case bitwardenNoActiveVault case bitwardenParsingFailed case bitwardenStatusParsingFailed @@ -677,8 +678,8 @@ enum GeneralPixel: PixelKitEventV2 { case .bitwardenNotResponding: return "bitwarden_not_responding" - // case .bitwardenRespondedCannotDecryptUnique: - // return "bitwarden_responded_cannot_decrypt_unique" + case .bitwardenRespondedCannotDecryptUnique: + return "bitwarden_responded_cannot_decrypt_unique" case .bitwardenHandshakeFailed: return "bitwarden_handshake_failed" case .bitwardenDecryptionOfSharedKeyFailed: @@ -783,7 +784,7 @@ enum GeneralPixel: PixelKitEventV2 { var error: (any Error)? { switch self { - case .dbMakeDatabaseError(let error), + case .dbMakeDatabaseError(let error?), .dbContainerInitializationError(let error), .dbInitializationError(let error), .dbSaveExcludedHTTPSDomainsError(let error?), @@ -791,8 +792,6 @@ enum GeneralPixel: PixelKitEventV2 { .configurationFetchError(let error), .secureVaultInitError(let error), .secureVaultError(let error), - .emailAutofillKeychainError(let error), - .bitwardenRespondedWithError(let error), .syncSignupError(let error), .syncLoginError(let error), .syncLogoutError(let error), @@ -807,68 +806,109 @@ enum GeneralPixel: PixelKitEventV2 { } var parameters: [String: String]? { - return nil + switch self { + case .loginItemUpdateError(let loginItemBundleID, let action, let buildType, let osVersion): + return ["loginItemBundleID": loginItemBundleID, "action": action, "buildType": buildType, "macosVersion": osVersion] + default: return nil + } } -} -public enum CompileRulesListType: String, CustomStringConvertible { + public enum CompileRulesListType: String, CustomStringConvertible { - public var description: String { rawValue } + public var description: String { rawValue } - case tds = "tracker_data" - case clickToLoad = "click_to_load" - case blockingAttribution = "blocking_attribution" - case attributed = "attributed" - case unknown = "unknown" + case tds = "tracker_data" + case clickToLoad = "click_to_load" + case blockingAttribution = "blocking_attribution" + case attributed = "attributed" + case unknown = "unknown" -} + } -enum OnboardingShown: String, CustomStringConvertible { - var description: String { rawValue } + enum OnboardingShown: String, CustomStringConvertible { + var description: String { rawValue } - init(_ value: Bool) { - if value { - self = .onboardingShown - } else { - self = .regularNavigation + init(_ value: Bool) { + if value { + self = .onboardingShown + } else { + self = .regularNavigation + } } + case onboardingShown = "onboarding-shown" + case regularNavigation = "regular-nav" } - case onboardingShown = "onboarding-shown" - case regularNavigation = "regular-nav" -} -enum WaitResult: String, CustomStringConvertible { - var description: String { rawValue } + enum WaitResult: String, CustomStringConvertible { + var description: String { rawValue } - case closed - case quit - case success -} + case closed + case quit + case success + } -enum CompileRulesWaitTime: String, CustomStringConvertible { - var description: String { rawValue } + enum CompileRulesWaitTime: String, CustomStringConvertible { + var description: String { rawValue } - case noWait = "0" - case lessThan1s = "1" - case lessThan5s = "5" - case lessThan10s = "10" - case lessThan20s = "20" - case lessThan40s = "40" - case more = "more" -} + case noWait = "0" + case lessThan1s = "1" + case lessThan5s = "5" + case lessThan10s = "10" + case lessThan20s = "20" + case lessThan40s = "40" + case more = "more" + } -enum FormAutofillKind: String, CustomStringConvertible { - var description: String { rawValue } + enum FormAutofillKind: String, CustomStringConvertible { + var description: String { rawValue } - case password - case card - case identity -} + case password + case card + case identity + } + + enum FireButtonOption: String, CustomStringConvertible { + var description: String { rawValue } -enum FireButtonOption: String, CustomStringConvertible { - var description: String { rawValue } + case tab + case window + case allSites = "all-sites" + } - case tab - case window - case allSites = "all-sites" + enum AccessPoint: String, CustomStringConvertible { + var description: String { rawValue } + + case button = "source-button" + case mainMenu = "source-menu" + case tabMenu = "source-tab-menu" + case hotKey = "source-keyboard" + case moreMenu = "source-more-menu" + case newTab = "source-new-tab" + + init(sender: Any, default: AccessPoint, mainMenuCheck: (NSMenu?) -> Bool = { $0 is MainMenu }) { + switch sender { + case let menuItem as NSMenuItem: + if mainMenuCheck(menuItem.topMenu) { + if let event = NSApp.currentEvent, + case .keyDown = event.type, + event.characters == menuItem.keyEquivalent { + + self = .hotKey + } else { + self = .mainMenu + } + } else { + self = `default` + } + + case is NSButton: + self = .button + + default: + assertionFailure("AccessPoint: Unexpected type of sender: \(type(of: sender))") + self = `default` + } + } + + } } diff --git a/DuckDuckGo/Statistics/PixelKit+Assertion.swift b/DuckDuckGo/Statistics/PixelKit+Assertion.swift new file mode 100644 index 0000000000..e76dd74ef2 --- /dev/null +++ b/DuckDuckGo/Statistics/PixelKit+Assertion.swift @@ -0,0 +1,25 @@ +// +// PixelKit+Assertion.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 +import PixelKit + +public func pixelAssertionFailure(_ message: @autoclosure () -> String = String(), file: StaticString = #fileID, line: UInt = #line) { + PixelKit.fire(DebugEvent(GeneralPixel.assertionFailure(message: message(), file: file, line: line))) + Swift.assertionFailure(message(), file: file, line: line) +} diff --git a/DuckDuckGo/Statistics/RulesCompilationMonitor.swift b/DuckDuckGo/Statistics/RulesCompilationMonitor.swift index 86e704f4d7..689a6c6174 100644 --- a/DuckDuckGo/Statistics/RulesCompilationMonitor.swift +++ b/DuckDuckGo/Statistics/RulesCompilationMonitor.swift @@ -54,14 +54,14 @@ final class AbstractContentBlockingAssetsCompilationTimeReporter Void) = { _ in }) { + private func report(waitTime: TimeInterval, result: GeneralPixel.WaitResult, completionHandler: @escaping ((Error?) -> Void) = { _ in }) { // report only once isFinished = true completionHandler(nil) // This is temporarily disabled: // - // Pixel.fire(.compileRulesWait(onboardingShown: self.onboardingShown, waitTime: waitTime, result: result), + // PixelKit.fire(GeneralPixel.compileRulesWait(onboardingShown: self.onboardingShown, waitTime: waitTime, result: result), // withAdditionalParameters: ["waitTime": String(waitTime)], // onComplete: completionHandler) diff --git a/DuckDuckGo/Suggestions/Model/SuggestionContainer.swift b/DuckDuckGo/Suggestions/Model/SuggestionContainer.swift index 9ca0f3aaa9..5d4435eb83 100644 --- a/DuckDuckGo/Suggestions/Model/SuggestionContainer.swift +++ b/DuckDuckGo/Suggestions/Model/SuggestionContainer.swift @@ -20,6 +20,7 @@ import Foundation import Suggestions import Common import History +import PixelKit final class SuggestionContainer { @@ -63,7 +64,7 @@ final class SuggestionContainer { os_log("Suggestions: Failed to get suggestions - %s", type: .error, "\(String(describing: error))") - Pixel.fire(.debug(event: .suggestionsFetchFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.suggestionsFetchFailed, error: error)) return } diff --git a/DuckDuckGo/Sync/CredentialsCleanupErrorHandling.swift b/DuckDuckGo/Sync/CredentialsCleanupErrorHandling.swift index cac2ff4cee..386dd306a5 100644 --- a/DuckDuckGo/Sync/CredentialsCleanupErrorHandling.swift +++ b/DuckDuckGo/Sync/CredentialsCleanupErrorHandling.swift @@ -20,18 +20,19 @@ import Foundation import BrowserServicesKit import Common import Persistence +import PixelKit public class CredentialsCleanupErrorHandling: EventMapping { public init() { super.init { event, _, _, _ in if event.cleanupError is CredentialsCleanupCancelledError { - Pixel.fire(.debug(event: .credentialsCleanupAttemptedWhileSyncWasEnabled)) + PixelKit.fire(DebugEvent(GeneralPixel.credentialsCleanupAttemptedWhileSyncWasEnabled)) } else { let processedErrors = CoreDataErrorsParser.parse(error: event.cleanupError as NSError) let params = processedErrors.errorPixelParameters - Pixel.fire(.debug(event: .credentialsDatabaseCleanupFailed, error: event.cleanupError), withAdditionalParameters: params) + PixelKit.fire(DebugEvent(GeneralPixel.credentialsDatabaseCleanupFailed, error: event.cleanupError), withAdditionalParameters: params) } } } diff --git a/DuckDuckGo/Sync/SyncBookmarksAdapter.swift b/DuckDuckGo/Sync/SyncBookmarksAdapter.swift index d7a9651d14..97cc7db276 100644 --- a/DuckDuckGo/Sync/SyncBookmarksAdapter.swift +++ b/DuckDuckGo/Sync/SyncBookmarksAdapter.swift @@ -22,12 +22,13 @@ import Common import DDGSync import Persistence import SyncDataProviders +import PixelKit public class BookmarksFaviconsFetcherErrorHandler: EventMapping { public init() { super.init { event, _, _, _ in - Pixel.fire(.debug(event: .bookmarksFaviconsFetcherFailed, error: event.underlyingError)) + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksFaviconsFetcherFailed, error: event.underlyingError)) } } @@ -147,7 +148,7 @@ final class SyncBookmarksAdapter { do { stateStore = try BookmarksFaviconsFetcherStateStore(applicationSupportURL: URL.sandboxApplicationSupportURL) } catch { - Pixel.fire(.debug(event: .bookmarksFaviconsFetcherStateStoreInitializationFailed, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.bookmarksFaviconsFetcherStateStoreInitializationFailed, error: error)) os_log(.error, log: OSLog.sync, "Failed to initialize BookmarksFaviconsFetcherStateStore: %{public}s", String(reflecting: error)) return nil } @@ -167,17 +168,17 @@ final class SyncBookmarksAdapter { .sink { [weak self] error in switch error { case let syncError as SyncError: - Pixel.fire(.debug(event: .syncBookmarksFailed, error: syncError)) + PixelKit.fire(DebugEvent(GeneralPixel.syncBookmarksFailed, error: syncError)) switch syncError { case .unexpectedStatusCode(409): // If bookmarks count limit has been exceeded self?.isSyncBookmarksPaused = true - Pixel.fire(.syncBookmarksCountLimitExceededDaily, limitTo: .dailyFirst) + PixelKit.fire(GeneralPixel.syncBookmarksCountLimitExceededDaily, frequency: .dailyOnly) self?.showSyncPausedAlert() case .unexpectedStatusCode(413): // If bookmarks request size limit has been exceeded self?.isSyncBookmarksPaused = true - Pixel.fire(.syncBookmarksRequestSizeLimitExceededDaily, limitTo: .dailyFirst) + PixelKit.fire(GeneralPixel.syncBookmarksRequestSizeLimitExceededDaily, frequency: .dailyOnly) self?.showSyncPausedAlert() default: break @@ -187,7 +188,7 @@ final class SyncBookmarksAdapter { if nsError.domain != NSURLErrorDomain { let processedErrors = CoreDataErrorsParser.parse(error: error as NSError) let params = processedErrors.errorPixelParameters - Pixel.fire(.debug(event: .syncBookmarksFailed, error: error), withAdditionalParameters: params) + PixelKit.fire(DebugEvent(GeneralPixel.syncBookmarksFailed, error: error), withAdditionalParameters: params) } } os_log(.error, log: OSLog.sync, "Bookmarks Sync error: %{public}s", String(reflecting: error)) diff --git a/DuckDuckGo/Sync/SyncCredentialsAdapter.swift b/DuckDuckGo/Sync/SyncCredentialsAdapter.swift index 444e7a1872..979e8cee66 100644 --- a/DuckDuckGo/Sync/SyncCredentialsAdapter.swift +++ b/DuckDuckGo/Sync/SyncCredentialsAdapter.swift @@ -22,6 +22,7 @@ import Common import DDGSync import Persistence import SyncDataProviders +import PixelKit final class SyncCredentialsAdapter { @@ -85,17 +86,17 @@ final class SyncCredentialsAdapter { .sink { [weak self] error in switch error { case let syncError as SyncError: - Pixel.fire(.debug(event: .syncCredentialsFailed, error: syncError)) + PixelKit.fire(DebugEvent(GeneralPixel.syncCredentialsFailed, error: syncError)) switch syncError { case .unexpectedStatusCode(409): // If credentials count limit has been exceeded self?.isSyncCredentialsPaused = true - Pixel.fire(.syncCredentialsCountLimitExceededDaily, limitTo: .dailyFirst) + PixelKit.fire(GeneralPixel.syncCredentialsCountLimitExceededDaily, frequency: .dailyOnly) self?.showSyncPausedAlert() case .unexpectedStatusCode(413): // If credentials request size limit has been exceeded self?.isSyncCredentialsPaused = true - Pixel.fire(.syncCredentialsRequestSizeLimitExceededDaily, limitTo: .dailyFirst) + PixelKit.fire(GeneralPixel.syncCredentialsRequestSizeLimitExceededDaily, frequency: .dailyOnly) self?.showSyncPausedAlert() default: break @@ -105,7 +106,7 @@ final class SyncCredentialsAdapter { if nsError.domain != NSURLErrorDomain { let processedErrors = CoreDataErrorsParser.parse(error: error as NSError) let params = processedErrors.errorPixelParameters - Pixel.fire(.debug(event: .syncCredentialsFailed, error: error), withAdditionalParameters: params) + PixelKit.fire(DebugEvent(GeneralPixel.syncCredentialsFailed, error: error), withAdditionalParameters: params) } } os_log(.error, log: OSLog.sync, "Credentials Sync error: %{public}s", String(reflecting: error)) @@ -116,7 +117,7 @@ final class SyncCredentialsAdapter { } catch let error as NSError { let processedErrors = CoreDataErrorsParser.parse(error: error) let params = processedErrors.errorPixelParameters - Pixel.fire(.debug(event: .syncCredentialsProviderInitializationFailed, error: error), withAdditionalParameters: params) + PixelKit.fire(DebugEvent(GeneralPixel.syncCredentialsProviderInitializationFailed, error: error), withAdditionalParameters: params) } } diff --git a/DuckDuckGo/Sync/SyncDataProviders.swift b/DuckDuckGo/Sync/SyncDataProviders.swift index 2858e2664a..4da4a2d5c5 100644 --- a/DuckDuckGo/Sync/SyncDataProviders.swift +++ b/DuckDuckGo/Sync/SyncDataProviders.swift @@ -22,6 +22,7 @@ import Common import DDGSync import Persistence import SyncDataProviders +import PixelKit final class SyncDataProviders: DataProvidersSource { public let bookmarksAdapter: SyncBookmarksAdapter @@ -109,9 +110,9 @@ final class SyncDataProviders: DataProvidersSource { syncMetadataDatabase.db.loadStore { context, error in guard context != nil else { if let error = error { - Pixel.fire(.debug(event: .syncMetadataCouldNotLoadDatabase, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.syncMetadataCouldNotLoadDatabase, error: error)) } else { - Pixel.fire(.debug(event: .syncMetadataCouldNotLoadDatabase)) + PixelKit.fire(DebugEvent(GeneralPixel.syncMetadataCouldNotLoadDatabase)) } Thread.sleep(forTimeInterval: 1) diff --git a/DuckDuckGo/Sync/SyncErrorHandler.swift b/DuckDuckGo/Sync/SyncErrorHandler.swift index 15f94ac550..036c041fb7 100644 --- a/DuckDuckGo/Sync/SyncErrorHandler.swift +++ b/DuckDuckGo/Sync/SyncErrorHandler.swift @@ -19,12 +19,13 @@ import Common import DDGSync import Foundation +import PixelKit public class SyncErrorHandler: EventMapping { public init() { super.init { event, _, _, _ in - Pixel.fire(.debug(event: .syncSentUnauthenticatedRequest, error: event)) + PixelKit.fire(DebugEvent(GeneralPixel.syncSentUnauthenticatedRequest, error: event)) } } diff --git a/DuckDuckGo/Sync/SyncMetricsEventsHandler.swift b/DuckDuckGo/Sync/SyncMetricsEventsHandler.swift index 2c94d341f6..f5c29f6e7d 100644 --- a/DuckDuckGo/Sync/SyncMetricsEventsHandler.swift +++ b/DuckDuckGo/Sync/SyncMetricsEventsHandler.swift @@ -19,6 +19,7 @@ import Common import SyncDataProviders import Foundation +import PixelKit public class SyncMetricsEventsHandler: EventMapping { @@ -26,9 +27,9 @@ public class SyncMetricsEventsHandler: EventMapping { super.init { event, _, _, _ in switch event { case .overrideEmailProtectionSettings: - Pixel.fire(.syncDuckAddressOverride) + PixelKit.fire(GeneralPixel.syncDuckAddressOverride) case .localTimestampResolutionTriggered(let feature): - Pixel.fire(.syncLocalTimestampResolutionTriggered(feature)) + PixelKit.fire(GeneralPixel.syncLocalTimestampResolutionTriggered(feature)) } } } diff --git a/DuckDuckGo/Sync/SyncSettingsAdapter.swift b/DuckDuckGo/Sync/SyncSettingsAdapter.swift index 96fc030a0d..f726673087 100644 --- a/DuckDuckGo/Sync/SyncSettingsAdapter.swift +++ b/DuckDuckGo/Sync/SyncSettingsAdapter.swift @@ -23,6 +23,7 @@ import Common import DDGSync import Persistence import SyncDataProviders +import PixelKit extension SettingsProvider.Setting { static let favoritesDisplayMode = SettingsProvider.Setting(key: "favorites_display_mode") @@ -63,18 +64,18 @@ final class SyncSettingsAdapter { .sink { error in switch error { case let syncError as SyncError: - Pixel.fire(.debug(event: .syncSettingsFailed, error: syncError)) + PixelKit.fire(DebugEvent(GeneralPixel.syncSettingsFailed, error: syncError)) case let settingsMetadataError as SettingsSyncMetadataSaveError: let underlyingError = settingsMetadataError.underlyingError let processedErrors = CoreDataErrorsParser.parse(error: underlyingError as NSError) let params = processedErrors.errorPixelParameters - Pixel.fire(.debug(event: .syncSettingsMetadataUpdateFailed, error: underlyingError), withAdditionalParameters: params) + PixelKit.fire(DebugEvent(GeneralPixel.syncSettingsMetadataUpdateFailed, error: underlyingError), withAdditionalParameters: params) default: let nsError = error as NSError if nsError.domain != NSURLErrorDomain { let processedErrors = CoreDataErrorsParser.parse(error: error as NSError) let params = processedErrors.errorPixelParameters - Pixel.fire(.debug(event: .syncSettingsFailed, error: error), withAdditionalParameters: params) + PixelKit.fire(DebugEvent(GeneralPixel.syncSettingsFailed, error: error), withAdditionalParameters: params) } } os_log(.error, log: OSLog.sync, "Settings Sync error: %{public}s", String(reflecting: error)) diff --git a/DuckDuckGo/Tab/Model/Tab.swift b/DuckDuckGo/Tab/Model/Tab.swift index ac38a79a3d..72f48916fa 100644 --- a/DuckDuckGo/Tab/Model/Tab.swift +++ b/DuckDuckGo/Tab/Model/Tab.swift @@ -1520,7 +1520,7 @@ extension Tab/*: NavigationResponder*/ { // to be moved to Tab+Navigation.swift loadErrorHTML(error, header: UserText.webProcessCrashPageHeader, forUnreachableURL: url, alternate: true) } - Pixel.fire(.debug(event: .webKitDidTerminate, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.webKitDidTerminate, error: error)) } @MainActor diff --git a/DuckDuckGo/Tab/TabExtensions/AutofillTabExtension.swift b/DuckDuckGo/Tab/TabExtensions/AutofillTabExtension.swift index 5a973d76cc..867702bd7e 100644 --- a/DuckDuckGo/Tab/TabExtensions/AutofillTabExtension.swift +++ b/DuckDuckGo/Tab/TabExtensions/AutofillTabExtension.swift @@ -20,6 +20,7 @@ import BrowserServicesKit import Combine import Foundation import SecureStorage +import PixelKit final class AutofillTabExtension: TabExtension { @@ -148,7 +149,7 @@ extension AutofillTabExtension: SecureVaultManagerDelegate { } func secureVaultManager(_: SecureVaultManager, didAutofill type: AutofillType, withObjectId objectId: String) { - Pixel.fire(.formAutofilled(kind: type.formAutofillKind)) + PixelKit.fire(GeneralPixel.formAutofilled(kind: type.formAutofillKind)) if type.formAutofillKind == .password && passwordManagerCoordinator.isEnabled { @@ -167,7 +168,7 @@ extension AutofillTabExtension: SecureVaultManagerDelegate { } public func secureVaultManager(_: BrowserServicesKit.SecureVaultManager, didReceivePixel pixel: AutofillUserScript.JSPixel) { - Pixel.fire(.jsPixel(pixel)) + PixelKit.fire(GeneralPixel.jsPixel(pixel)) } public func secureVaultManager(_: SecureVaultManager, didRequestCreditCardsManagerForDomain domain: String) { @@ -206,7 +207,7 @@ extension AutofillTabExtension: SecureVaultManagerDelegate { } extension AutofillType { - var formAutofillKind: Pixel.Event.FormAutofillKind { + var formAutofillKind: GeneralPixel.FormAutofillKind { switch self { case .password: return .password case .card: return .card diff --git a/DuckDuckGo/Tab/TabExtensions/DuckPlayerTabExtension.swift b/DuckDuckGo/Tab/TabExtensions/DuckPlayerTabExtension.swift index 6b41cd838f..def1d649a2 100644 --- a/DuckDuckGo/Tab/TabExtensions/DuckPlayerTabExtension.swift +++ b/DuckDuckGo/Tab/TabExtensions/DuckPlayerTabExtension.swift @@ -21,6 +21,7 @@ import Common import ContentBlocking import Foundation import Navigation +import PixelKit protocol YoutubeScriptsProvider { var youtubeOverlayScript: YoutubeOverlayUserScript? { get } @@ -111,9 +112,9 @@ extension DuckPlayerTabExtension: YoutubeOverlayUserScriptDelegate { func youtubeOverlayUserScriptDidRequestDuckPlayer(with url: URL, in webView: WKWebView) { if duckPlayer.mode == .enabled { - Pixel.fire(.duckPlayerViewFromYoutubeAutomatic) + PixelKit.fire(GeneralPixel.duckPlayerViewFromYoutubeAutomatic) } else { - Pixel.fire(.duckPlayerViewFromYoutubeViaHoverButton) + PixelKit.fire(GeneralPixel.duckPlayerViewFromYoutubeViaHoverButton) } // to be standardised across the app let isRequestingNewTab = NSApp.isCommandPressed @@ -191,7 +192,7 @@ extension DuckPlayerTabExtension: NavigationResponder { // Always allow loading Private Player URLs (local HTML) if navigationAction.url.isDuckURLScheme || navigationAction.url.isDuckPlayer { if navigationAction.request.allHTTPHeaderFields?["Referer"] == URL.duckDuckGo.absoluteString { - Pixel.fire(.duckPlayerViewFromSERP) + PixelKit.fire(GeneralPixel.duckPlayerViewFromSERP) } return .allow } @@ -278,10 +279,10 @@ extension DuckPlayerTabExtension: NavigationResponder { switch navigationAction.navigationType { case .custom, .redirect(.server): - Pixel.fire(.duckPlayerViewFromOther) + PixelKit.fire(GeneralPixel.duckPlayerViewFromOther) case .other: if navigationAction.request.allHTTPHeaderFields?["Referer"] == URL.duckDuckGo.absoluteString { - Pixel.fire(.duckPlayerViewFromSERP) + PixelKit.fire(GeneralPixel.duckPlayerViewFromSERP) } default: break @@ -300,7 +301,7 @@ extension DuckPlayerTabExtension: NavigationResponder { return } if navigation.url.isDuckPlayer { - Pixel.fire(.duckPlayerDailyUniqueView, limitTo: .dailyFirst) + PixelKit.fire(GeneralPixel.duckPlayerDailyUniqueView, frequency: .dailyOnly) } } diff --git a/DuckDuckGo/Tab/TabExtensions/NavigationProtectionTabExtension.swift b/DuckDuckGo/Tab/TabExtensions/NavigationProtectionTabExtension.swift index aa8fa230fc..e5a9a8d0e7 100644 --- a/DuckDuckGo/Tab/TabExtensions/NavigationProtectionTabExtension.swift +++ b/DuckDuckGo/Tab/TabExtensions/NavigationProtectionTabExtension.swift @@ -22,6 +22,7 @@ import Common import Foundation import Navigation import WebKit +import PixelKit final class NavigationProtectionTabExtension { @@ -30,7 +31,7 @@ final class NavigationProtectionTabExtension { private static let debugEvents = EventMapping { event, _, _, _ in switch event { case .ampBlockingRulesCompilationFailed: - Pixel.fire(.ampBlockingRulesCompilationFailed) + PixelKit.fire(GeneralPixel.ampBlockingRulesCompilationFailed) } } diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift index f4fe52b9b9..afa8362a74 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift @@ -342,7 +342,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { func activateSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { - Pixel.fire(.privacyProRestorePurchaseOfferPageEntry) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseOfferPageEntry) guard let mainViewController = await WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController, let windowControllerManager = await WindowControllersManager.shared.lastKeyMainWindowController else { return nil @@ -400,14 +400,14 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { case .appTrackingProtection: NotificationCenter.default.post(name: .openAppTrackingProtection, object: self, userInfo: nil) case .vpn: - Pixel.fire(.privacyProWelcomeVPN, limitTo: .initial) + PixelKit.fire(PrivacyProPixel.privacyProWelcomeVPN, frequency: .justOnce) NotificationCenter.default.post(name: .ToggleNetworkProtectionInMainWindow, object: self, userInfo: nil) case .personalInformationRemoval: - Pixel.fire(.privacyProWelcomePersonalInformationRemoval, limitTo: .initial) + PixelKit.fire(PrivacyProPixel.privacyProWelcomePersonalInformationRemoval, frequency: .justOnce) NotificationCenter.default.post(name: .openPersonalInformationRemoval, object: self, userInfo: nil) await WindowControllersManager.shared.showTab(with: .dataBrokerProtection) case .identityTheftRestoration: - Pixel.fire(.privacyProWelcomeIdentityRestoration, limitTo: .initial) + PixelKit.fire(PrivacyProPixel.privacyProWelcomeIdentityRestoration, frequency: .justOnce) await WindowControllersManager.shared.showTab(with: .identityTheftRestoration(.identityTheftRestoration)) } diff --git a/DuckDuckGo/Tab/View/BrowserTabViewController.swift b/DuckDuckGo/Tab/View/BrowserTabViewController.swift index d3e4ebbc31..322af25df0 100644 --- a/DuckDuckGo/Tab/View/BrowserTabViewController.swift +++ b/DuckDuckGo/Tab/View/BrowserTabViewController.swift @@ -23,6 +23,7 @@ import Common import SwiftUI import WebKit import Subscription +import PixelKit // swiftlint:disable file_length // swiftlint:disable:next type_body_length @@ -1105,7 +1106,7 @@ extension BrowserTabViewController: OnboardingDelegate { return } - Pixel.fire(.defaultRequestedFromOnboarding) + PixelKit.fire(GeneralPixel.defaultRequestedFromOnboarding) defaultBrowserPreferences.becomeDefault { _ in _ = defaultBrowserPreferences withAnimation { diff --git a/DuckDuckGo/TabBar/ViewModel/TabCollectionViewModel.swift b/DuckDuckGo/TabBar/ViewModel/TabCollectionViewModel.swift index e1fd1751c0..0317aed9b9 100644 --- a/DuckDuckGo/TabBar/ViewModel/TabCollectionViewModel.swift +++ b/DuckDuckGo/TabBar/ViewModel/TabCollectionViewModel.swift @@ -20,6 +20,7 @@ import Common import Foundation import Combine import History +import PixelKit /** * The delegate callbacks are triggered for events related to unpinned tabs only. @@ -657,7 +658,7 @@ final class TabCollectionViewModel: NSObject { // Make sure the tab is burner if it is supposed to be if newTabs.first(where: { $0.burnerMode != self.burnerMode }) != nil { - Pixel.fire(.debug(event: .burnerTabMisplaced)) + PixelKit.fire(DebugEvent(GeneralPixel.burnerTabMisplaced)) fatalError("Error in burner tab management") } } .store(in: &cancellables) diff --git a/DuckDuckGo/VPNFeedbackForm/VPNFeedbackSender.swift b/DuckDuckGo/VPNFeedbackForm/VPNFeedbackSender.swift index 52a951acbd..cb65dd926a 100644 --- a/DuckDuckGo/VPNFeedbackForm/VPNFeedbackSender.swift +++ b/DuckDuckGo/VPNFeedbackForm/VPNFeedbackSender.swift @@ -19,6 +19,7 @@ #if NETWORK_PROTECTION import Foundation +import PixelKit protocol VPNFeedbackSender { func send(metadata: VPNMetadata, category: VPNFeedbackCategory, userText: String) async throws @@ -29,10 +30,10 @@ struct DefaultVPNFeedbackSender: VPNFeedbackSender { func send(metadata: VPNMetadata, category: VPNFeedbackCategory, userText: String) async throws { let urlAllowed: CharacterSet = .alphanumerics.union(.init(charactersIn: "-._~")) let encodedUserText = userText.addingPercentEncoding(withAllowedCharacters: urlAllowed) ?? userText - let pixelEvent = Pixel.Event.vpnBreakageReport(category: category.rawValue, description: encodedUserText, metadata: metadata.toBase64()) + let pixelEvent = GeneralPixel.vpnBreakageReport(category: category.rawValue, description: encodedUserText, metadata: metadata.toBase64()) try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - Pixel.fire(pixelEvent) { error in + PixelKit.fire(pixelEvent) { succes, error in if let error { continuation.resume(throwing: error) } else { diff --git a/DuckDuckGo/Waitlist/Waitlist.swift b/DuckDuckGo/Waitlist/Waitlist.swift index 0a9979769d..9102d43b6f 100644 --- a/DuckDuckGo/Waitlist/Waitlist.swift +++ b/DuckDuckGo/Waitlist/Waitlist.swift @@ -353,7 +353,7 @@ struct DataBrokerProtectionWaitlist: Waitlist { sendInviteCodeAvailableNotification { DispatchQueue.main.async { - DataBrokerProtectionExternalWaitlistPixels.fire(pixel: .dataBrokerProtectionWaitlistNotificationShown, frequency: .dailyAndCount) + DataBrokerProtectionExternalWaitlistPixels.fire(pixel: GeneralPixel.dataBrokerProtectionWaitlistNotificationShown, frequency: .dailyAndContinuous) } } } diff --git a/DuckDuckGo/YoutubePlayer/DuckPlayer.swift b/DuckDuckGo/YoutubePlayer/DuckPlayer.swift index 53620c7236..abbd1fb71e 100644 --- a/DuckDuckGo/YoutubePlayer/DuckPlayer.swift +++ b/DuckDuckGo/YoutubePlayer/DuckPlayer.swift @@ -23,6 +23,7 @@ import Foundation import Navigation import WebKit import UserScript +import PixelKit enum DuckPlayerMode: Equatable, Codable { case enabled, alwaysAsk, disabled @@ -152,11 +153,11 @@ final class DuckPlayer { .handleEvents(receiveOutput: { mode in switch mode { case .enabled: - Pixel.fire(.duckPlayerSettingAlways) + PixelKit.fire(GeneralPixel.duckPlayerSettingAlways) case .alwaysAsk: - Pixel.fire(.duckPlayerSettingBackToDefault) + PixelKit.fire(GeneralPixel.duckPlayerSettingBackToDefault) case .disabled: - Pixel.fire(.duckPlayerSettingNever) + PixelKit.fire(GeneralPixel.duckPlayerSettingNever) } }) .prepend(preferences.duckPlayerMode) diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift index 6db95c4e31..01e848e56c 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift @@ -110,7 +110,7 @@ public final class PixelKit { fireRequest: fireRequest) } - static func tearDown() { + public static func tearDown() { shared = nil } @@ -332,7 +332,11 @@ public final class PixelKit { } public func pixelLastFireDate(pixelName: String) -> Date? { - defaults.object(forKey: userDefaultsKeyName(forPixelName: pixelName)) as? Date + var date = defaults.object(forKey: userDefaultsKeyName(forPixelName: pixelName)) as? Date + if date == nil { + date = defaults.object(forKey: legacyUserDefaultsKeyName(forPixelName: pixelName)) as? Date + } + return date } public func pixelLastFireDate(event: Event) -> Date? { @@ -364,12 +368,28 @@ public final class PixelKit { pixelLastFireDate(pixelName: name) != nil } - private func userDefaultsKeyName(forPixelName pixelName: String) -> String { + public static func clearFrequencyHistoryFor(pixel: PixelKitEventV2) { + guard let name = Self.shared?.userDefaultsKeyName(forPixelName: pixel.name) else { + return + } + Self.shared?.defaults.removeObject(forKey: name) + } + + public static func clearFrequencyHistoryForAllPixels() { + //TODO: Implement changing storage approach + } + + /// Initially PixelKit was configured only for serving netP so these very specific keys were used, now PixelKit serves the entire app so we need to move away from them. + /// NOTE: I would remove this 6 months after release + private func legacyUserDefaultsKeyName(forPixelName pixelName: String) -> String { dryRun ? "com.duckduckgo.network-protection.pixel.\(pixelName).dry-run" : "com.duckduckgo.network-protection.pixel.\(pixelName)" } + private func userDefaultsKeyName(forPixelName pixelName: String) -> String { + return "com.duckduckgo.pixel-date.\(pixelName)\( dryRun ? ".dry-run" : "" )" + } } extension Dictionary where Key == String, Value == String { diff --git a/UnitTests/Statistics/CBRCompileTimeReporterTests.swift b/UnitTests/Statistics/CBRCompileTimeReporterTests.swift index 95187b5346..ec435d6dae 100644 --- a/UnitTests/Statistics/CBRCompileTimeReporterTests.swift +++ b/UnitTests/Statistics/CBRCompileTimeReporterTests.swift @@ -19,6 +19,7 @@ import XCTest import OHHTTPStubs import OHHTTPStubsSwift +//import PixelKit @testable import DuckDuckGo_Privacy_Browser class CBRCompileTimeReporterTests: XCTestCase { @@ -29,13 +30,13 @@ class CBRCompileTimeReporterTests: XCTestCase { var time = CACurrentMediaTime() override func setUp() { - Pixel.setUp() +// PixelKit.setUp() //TODO: errors? UserDefaultsWrapper.clearAll() } override func tearDown() { HTTPStubs.removeAllStubs() - Pixel.tearDown() +// PixelKit.tearDown() super.tearDown() } @@ -51,8 +52,8 @@ class CBRCompileTimeReporterTests: XCTestCase { @discardableResult func performTest(withOnboardingFinished onboardingFinished: Bool, waitTime: TimeInterval, - expectedWaitTime: Pixel.Event.CompileRulesWaitTime, - result: Pixel.Event.WaitResult, + expectedWaitTime: GeneralPixel.CompileRulesWaitTime, + result: GeneralPixel.WaitResult, runBeforeFinishing: ((Reporter) throws -> Void)? = nil) rethrows -> Reporter { HTTPStubs.removeAllStubs() @@ -60,7 +61,7 @@ class CBRCompileTimeReporterTests: XCTestCase { HTTPStubs.removeAllStubs() } let reporter = initReporter(onboardingFinished: onboardingFinished) - let pixel = Pixel.Event.compileRulesWait(onboardingShown: onboardingFinished ? .regularNavigation : .onboardingShown, + let pixel = GeneralPixel.compileRulesWait(onboardingShown: onboardingFinished ? .regularNavigation : .onboardingShown, waitTime: expectedWaitTime, result: result) @@ -98,7 +99,7 @@ class CBRCompileTimeReporterTests: XCTestCase { return reporter } - typealias Pair = (TimeInterval, Pixel.Event.CompileRulesWaitTime) + typealias Pair = (TimeInterval, GeneralPixel.CompileRulesWaitTime) let waitExpectationSeq: [Pair] = [(0, .noWait), (0.5, .lessThan1s), (1, .lessThan1s), diff --git a/UnitTests/Statistics/PixelTests.swift b/UnitTests/Statistics/PixelTests.swift index 1e580a0652..25b9de1126 100644 --- a/UnitTests/Statistics/PixelTests.swift +++ b/UnitTests/Statistics/PixelTests.swift @@ -66,7 +66,7 @@ class PixelTests: XCTestCase { return HTTPStubsResponse(data: Data(), statusCode: 200, headers: nil) } - Pixel.fire(.crash, withAdditionalParameters: params) + PixelKit.fire(GeneralPixel.crash, withAdditionalParameters: params) waitForExpectations(timeout: 1.0) } @@ -82,7 +82,7 @@ class PixelTests: XCTestCase { return HTTPStubsResponse(data: Data(), statusCode: 200, headers: nil) } - Pixel.fire(.debug(event: Pixel.Event.Debug.appOpenURLFailed, error: error)) + PixelKit.fire(DebugEvent( GeneralPixel.appOpenURLFailed, error: error)) waitForExpectations(timeout: 1.0) } @@ -107,7 +107,7 @@ class PixelTests: XCTestCase { return HTTPStubsResponse(data: Data(), statusCode: 200, headers: nil) } - Pixel.fire(.debug(event: Pixel.Event.Debug.appOpenURLFailed, error: error), withAdditionalParameters: params) + PixelKit.fire(DebugEvent( GeneralPixel.appOpenURLFailed, error: error), withAdditionalParameters: params) waitForExpectations(timeout: 1.0) } From 227c2b5367753e4ec1689a94d7bd222cc8d01e99 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 4 Apr 2024 17:20:47 +0100 Subject: [PATCH 03/31] all pixels migrated to PixelKit --- DuckDuckGo/SecureVault/SecureVaultErrorReporter.swift | 4 ++-- .../View/PasswordManagementViewController.swift | 10 +++++----- .../SecureVault/View/SaveIdentityViewController.swift | 2 +- .../View/SavePaymentMethodViewController.swift | 2 +- DuckDuckGo/Statistics/ATB/LocalStatisticsStore.swift | 2 +- DuckDuckGo/Statistics/GeneralPixel.swift | 1 - .../Views/WaitlistViewControllerPresenter.swift | 2 +- 7 files changed, 11 insertions(+), 12 deletions(-) diff --git a/DuckDuckGo/SecureVault/SecureVaultErrorReporter.swift b/DuckDuckGo/SecureVault/SecureVaultErrorReporter.swift index 2533f9c6ee..0a76643d88 100644 --- a/DuckDuckGo/SecureVault/SecureVaultErrorReporter.swift +++ b/DuckDuckGo/SecureVault/SecureVaultErrorReporter.swift @@ -30,9 +30,9 @@ final class SecureVaultErrorReporter: SecureVaultErrorReporting { switch error { case .initFailed, .failedToOpenDatabase: - PixelKit.fire(DebugEvent(GeneralPixel.secureVaultInitError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultInitError(error: error))) default: - PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } } diff --git a/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift b/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift index eb418179ba..efcb9dd56f 100644 --- a/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift +++ b/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift @@ -635,7 +635,7 @@ final class PasswordManagementViewController: NSViewController { self.requestSync() self.refreshData() } catch { - PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } default: @@ -658,7 +658,7 @@ final class PasswordManagementViewController: NSViewController { try self.secureVault?.deleteIdentityFor(identityId: id) self.refreshData() } catch { - PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } default: @@ -700,7 +700,7 @@ final class PasswordManagementViewController: NSViewController { try self.secureVault?.deleteCreditCardFor(cardId: id) self.refreshData() } catch { - PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } default: @@ -754,7 +754,7 @@ final class PasswordManagementViewController: NSViewController { self?.syncModelsOnNote(note) } } catch { - PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } } @@ -858,7 +858,7 @@ final class PasswordManagementViewController: NSViewController { items = cards.map(SecureVaultItem.card) } } catch { - PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } DispatchQueue.main.async { diff --git a/DuckDuckGo/SecureVault/View/SaveIdentityViewController.swift b/DuckDuckGo/SecureVault/View/SaveIdentityViewController.swift index d29870499b..88006e5096 100644 --- a/DuckDuckGo/SecureVault/View/SaveIdentityViewController.swift +++ b/DuckDuckGo/SecureVault/View/SaveIdentityViewController.swift @@ -75,7 +75,7 @@ final class SaveIdentityViewController: NSViewController { PixelKit.fire(GeneralPixel.autofillItemSaved(kind: .identity)) } catch { os_log("%s:%s: failed to store identity %s", type: .error, className, #function, error.localizedDescription) - PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } } diff --git a/DuckDuckGo/SecureVault/View/SavePaymentMethodViewController.swift b/DuckDuckGo/SecureVault/View/SavePaymentMethodViewController.swift index 83d28bd349..68275eeb5e 100644 --- a/DuckDuckGo/SecureVault/View/SavePaymentMethodViewController.swift +++ b/DuckDuckGo/SecureVault/View/SavePaymentMethodViewController.swift @@ -95,7 +95,7 @@ final class SavePaymentMethodViewController: NSViewController { try AutofillSecureVaultFactory.makeVault(errorReporter: SecureVaultErrorReporter.shared).storeCreditCard(paymentMethod) } catch { os_log("%s:%s: failed to store payment method %s", type: .error, className, #function, error.localizedDescription) - PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError, error: error)) + PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } } diff --git a/DuckDuckGo/Statistics/ATB/LocalStatisticsStore.swift b/DuckDuckGo/Statistics/ATB/LocalStatisticsStore.swift index 4885a848c6..82e9c241b3 100644 --- a/DuckDuckGo/Statistics/ATB/LocalStatisticsStore.swift +++ b/DuckDuckGo/Statistics/ATB/LocalStatisticsStore.swift @@ -79,7 +79,7 @@ final class LocalStatisticsStore: StatisticsStore { private let pixelDataStore: PixelDataStore - init(pixelDataStore: PixelDataStore = LocalPixelDataStore.shared) { + init(pixelDataStore: PixelDataStore = LocalPixelDataStore.shared) { // TODO: not used? investigate self.pixelDataStore = pixelDataStore var legacyStatisticsStore = LegacyStatisticsStore() diff --git a/DuckDuckGo/Statistics/GeneralPixel.swift b/DuckDuckGo/Statistics/GeneralPixel.swift index 862b56a8fb..8a06a48927 100644 --- a/DuckDuckGo/Statistics/GeneralPixel.swift +++ b/DuckDuckGo/Statistics/GeneralPixel.swift @@ -905,7 +905,6 @@ enum GeneralPixel: PixelKitEventV2 { self = .button default: - assertionFailure("AccessPoint: Unexpected type of sender: \(type(of: sender))") self = `default` } } diff --git a/DuckDuckGo/Waitlist/Views/WaitlistViewControllerPresenter.swift b/DuckDuckGo/Waitlist/Views/WaitlistViewControllerPresenter.swift index 22fcf9bbdb..31ddf12451 100644 --- a/DuckDuckGo/Waitlist/Views/WaitlistViewControllerPresenter.swift +++ b/DuckDuckGo/Waitlist/Views/WaitlistViewControllerPresenter.swift @@ -99,7 +99,7 @@ struct DataBrokerProtectionWaitlistViewControllerPresenter: WaitlistViewControll guard let windowController = WindowControllersManager.shared.lastKeyMainWindowController else { return } - DataBrokerProtectionExternalWaitlistPixels.fire(pixel: .dataBrokerProtectionWaitlistIntroDisplayed, frequency: .dailyAndCount) + DataBrokerProtectionExternalWaitlistPixels.fire(pixel: GeneralPixel.dataBrokerProtectionWaitlistIntroDisplayed, frequency: .dailyAndContinuous) // This is a hack to get around an issue with the waitlist notification screen showing the wrong state while it animates in, and then // jumping to the correct state as soon as the animation is complete. This works around that problem by providing the correct state up front, From afbb6a62d1d3d301170360ea855e1ddbb39b70d3 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 5 Apr 2024 10:40:56 +0100 Subject: [PATCH 04/31] more pixels migrated and unit tests now build --- DuckDuckGo.xcodeproj/project.pbxproj | 24 --------- DuckDuckGo/Application/AppDelegate.swift | 38 ++++++++------ .../DBP/DataBrokerProtectionAppEvents.swift | 8 +-- .../DataBrokerProtectionFeatureDisabler.swift | 2 +- .../DataBrokerProtectionLoginItemPixels.swift | 2 +- ...taBrokerProtectionLoginItemScheduler.swift | 4 +- .../Favicons/Services/FaviconStore.swift | 2 +- .../Services/DownloadListStore.swift | 2 +- .../Fire/ViewModel/FirePopoverViewModel.swift | 2 +- .../Services/EncryptedHistoryStore.swift | 2 +- .../Model/HomePageContinueSetUpModel.swift | 4 +- .../View/HomePageViewController.swift | 6 +-- DuckDuckGo/LoginItems/LoginItemsManager.swift | 2 +- .../MainWindow/MainViewController.swift | 2 +- .../NavigationBar/View/MoreOptionsMenu.swift | 4 +- .../View/NavigationBarViewController.swift | 4 +- .../EventMapping+NetworkProtectionError.swift | 4 +- .../NetworkProtectionNavBarButtonModel.swift | 2 +- .../NetworkProtectionTunnelController.swift | 8 +-- .../VPNLocation/VPNLocationViewModel.swift | 6 +-- .../MacPacketTunnelProvider.swift | 37 +++++++------- .../MacTransparentProxyProvider.swift | 1 - .../Permissions/Model/StoredPermission.swift | 2 +- .../Model/DefaultBrowserPreferences.swift | 11 +++-- .../View/PreferencesRootView.swift | 6 +-- .../SmarterEncryption/PrivacyFeatures.swift | 4 +- .../Statistics/ATB/StatisticsLoader.swift | 2 +- .../Experiment/PixelExperiment.swift | 13 +++-- DuckDuckGo/Statistics/GeneralPixel.swift | 38 ++++++++++++++ DuckDuckGo/Sync/SyncBookmarksAdapter.swift | 4 +- DuckDuckGo/Sync/SyncCredentialsAdapter.swift | 4 +- DuckDuckGo/Tab/Model/Tab.swift | 2 +- .../DuckPlayerTabExtension.swift | 2 +- .../SubscriptionAppStoreRestorer.swift | 4 +- .../SubscriptionErrorReporter.swift | 8 +-- .../SubscriptionPagesUserScript.swift | 26 +++++----- .../NetworkProtectionFeatureVisibility.swift | 2 +- .../WaitlistThankYouPromptPresenter.swift | 6 +-- .../WaitlistViewControllerPresenter.swift | 2 +- DuckDuckGo/Waitlist/Waitlist.swift | 4 +- ...tlistTermsAndConditionsActionHandler.swift | 8 +-- .../YoutubeOverlayUserScript.swift | 8 +-- ...kDuckGoDBPBackgroundAgentAppDelegate.swift | 1 - DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 3 +- .../PixelKit/Extensions/URL+PixelKit.swift | 2 +- .../PixelKit/Sources/PixelKit/PixelKit.swift | 49 ++++++++++--------- .../XCTestCase+PixelKit.swift | 1 - .../Tests/PixelKitTests/PixelKitTests.swift | 45 +++++++---------- .../BrokenSiteReportingReferenceTests.swift | 2 +- .../CBRCompileTimeReporterTests.swift | 9 ++-- .../{ => Legacy}/PixelArgumentsTests.swift | 30 ++++++------ .../{ => Legacy}/PixelEventTests.swift | 0 .../{ => Legacy}/PixelStoreTests.swift | 0 .../Statistics/{ => Legacy}/PixelTests.swift | 0 .../PixelExperimentTests.swift | 25 +++++++--- .../WebsiteBreakageReportTests.swift | 2 +- 56 files changed, 259 insertions(+), 232 deletions(-) rename UnitTests/Statistics/{ => Legacy}/PixelArgumentsTests.swift (79%) rename UnitTests/Statistics/{ => Legacy}/PixelEventTests.swift (100%) rename UnitTests/Statistics/{ => Legacy}/PixelStoreTests.swift (100%) rename UnitTests/Statistics/{ => Legacy}/PixelTests.swift (100%) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 69b82917ca..32fa6ba33c 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -749,7 +749,6 @@ 3706FDDF293F661700E42796 /* BookmarkSidebarTreeControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292B22667103000AD2C21 /* BookmarkSidebarTreeControllerTests.swift */; }; 3706FDE0293F661700E42796 /* TabIndexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D2377B287EBDA300BCE03B /* TabIndexTests.swift */; }; 3706FDE1293F661700E42796 /* AdjacentItemEnumeratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37534CA42811987D002621E7 /* AdjacentItemEnumeratorTests.swift */; }; - 3706FDE2293F661700E42796 /* PixelArgumentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44222616CABC00DD1EC2 /* PixelArgumentsTests.swift */; }; 3706FDE4293F661700E42796 /* TabLazyLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37534C9D28104D9B002621E7 /* TabLazyLoaderTests.swift */; }; 3706FDE5293F661700E42796 /* URLEventHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F1B0C825EF9759004792B6 /* URLEventHandlerTests.swift */; }; 3706FDE6293F661700E42796 /* BookmarkOutlineViewDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292B32667103000AD2C21 /* BookmarkOutlineViewDataSourceTests.swift */; }; @@ -802,7 +801,6 @@ 3706FE1A293F661700E42796 /* BrowserProfileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3F641D27A8D3BD00E0C118 /* BrowserProfileTests.swift */; }; 3706FE1B293F661700E42796 /* PermissionManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106B9F26A7BE0B0013B453 /* PermissionManagerTests.swift */; }; 3706FE1C293F661700E42796 /* ConnectBitwardenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B59CC8B290083240058F2F6 /* ConnectBitwardenViewModelTests.swift */; }; - 3706FE1D293F661700E42796 /* PixelStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B662D3D82755D7AD0035D4D6 /* PixelStoreTests.swift */; }; 3706FE1E293F661700E42796 /* GeolocationProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106BB426A809E60013B453 /* GeolocationProviderTests.swift */; }; 3706FE1F293F661700E42796 /* AppStateChangePublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A5A29F25B96E8300AA7ADA /* AppStateChangePublisherTests.swift */; }; 3706FE20293F661700E42796 /* CLLocationManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B63ED0E226B3E7FA00A9DAD1 /* CLLocationManagerMock.swift */; }; @@ -844,7 +842,6 @@ 3706FE45293F661700E42796 /* ProgressEstimationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AE74332609AFCE005B9B1A /* ProgressEstimationTests.swift */; }; 3706FE46293F661700E42796 /* EncryptedValueTransformerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BA1A6FD258C5C1300F6F690 /* EncryptedValueTransformerTests.swift */; }; 3706FE47293F661700E42796 /* URLExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F69B3B25EDE81F00978E59 /* URLExtensionTests.swift */; }; - 3706FE48293F661700E42796 /* PixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44102616C0FC00DD1EC2 /* PixelTests.swift */; }; 3706FE49293F661700E42796 /* BookmarkNodePathTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292B02667103000AD2C21 /* BookmarkNodePathTests.swift */; }; 3706FE4A293F661700E42796 /* BookmarkManagedObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292B62667103000AD2C21 /* BookmarkManagedObjectTests.swift */; }; 3706FE4B293F661700E42796 /* BookmarksHTMLImporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373A1AB128451ED400586521 /* BookmarksHTMLImporterTests.swift */; }; @@ -1014,7 +1011,6 @@ 376E2D2629428353001CD31B /* PrivacyReferenceTestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E163BC293A579E00963C10 /* PrivacyReferenceTestHelper.swift */; }; 376E2D2729428353001CD31B /* BrokenSiteReportingReferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E163B9293A56F400963C10 /* BrokenSiteReportingReferenceTests.swift */; }; 376E2D282942843D001CD31B /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = 31E163BF293A581900963C10 /* privacy-reference-tests */; }; - 376E2D29294286B8001CD31B /* PixelEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC2621C293996410087A482 /* PixelEventTests.swift */; }; 37716D8029707E5D00A9FC6D /* FireproofingReferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310E79BE294A19A8007C49E8 /* FireproofingReferenceTests.swift */; }; 3775912D29AAC72700E26367 /* SyncPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3775912C29AAC72700E26367 /* SyncPreferences.swift */; }; 3775912E29AAC72700E26367 /* SyncPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3775912C29AAC72700E26367 /* SyncPreferences.swift */; }; @@ -2089,7 +2085,6 @@ 4BBF0917282DD6EF00EE1418 /* TemporaryFileHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF0916282DD6EF00EE1418 /* TemporaryFileHandlerTests.swift */; }; 4BBF09232830812900EE1418 /* FileSystemDSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF09222830812900EE1418 /* FileSystemDSL.swift */; }; 4BBF0925283083EC00EE1418 /* FileSystemDSLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF0924283083EC00EE1418 /* FileSystemDSLTests.swift */; }; - 4BC2621D293996410087A482 /* PixelEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC2621C293996410087A482 /* PixelEventTests.swift */; }; 4BCBE4552BA7E16600FC75A1 /* NetworkProtectionSubscriptionEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2F565B2B38F93E001214C0 /* NetworkProtectionSubscriptionEventHandler.swift */; }; 4BCBE4562BA7E16900FC75A1 /* DataBrokerProtectionSubscriptionEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5789712B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift */; }; 4BCBE4582BA7E17800FC75A1 /* SubscriptionUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4BCBE4572BA7E17800FC75A1 /* SubscriptionUI */; }; @@ -2883,7 +2878,6 @@ B66260E629ACAE4B00E9E3EE /* NavigationHotkeyHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66260E529ACAE4B00E9E3EE /* NavigationHotkeyHandler.swift */; }; B66260E729ACAE4B00E9E3EE /* NavigationHotkeyHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66260E529ACAE4B00E9E3EE /* NavigationHotkeyHandler.swift */; }; B66260E829ACD0C900E9E3EE /* DuckPlayerTabExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C416A6294A4AE500C4F2E7 /* DuckPlayerTabExtension.swift */; }; - B662D3D92755D7AD0035D4D6 /* PixelStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B662D3D82755D7AD0035D4D6 /* PixelStoreTests.swift */; }; B662D3DE275613BB0035D4D6 /* EncryptionKeyStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B662D3DD275613BB0035D4D6 /* EncryptionKeyStoreMock.swift */; }; B662D3DF275616FF0035D4D6 /* EncryptionKeyStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B662D3DD275613BB0035D4D6 /* EncryptionKeyStoreMock.swift */; }; B6656E0D2B29C733008798A1 /* FileImportViewLocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6656E0C2B29C733008798A1 /* FileImportViewLocalizationTests.swift */; }; @@ -3114,11 +3108,9 @@ B6DA06E42913ECEE00225DE2 /* ContextMenuManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA06E32913ECEE00225DE2 /* ContextMenuManager.swift */; }; B6DA06E62913F39400225DE2 /* MenuItemSelectors.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA06E52913F39400225DE2 /* MenuItemSelectors.swift */; }; B6DA06E8291401D700225DE2 /* WKMenuItemIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA06E7291401D700225DE2 /* WKMenuItemIdentifier.swift */; }; - B6DA44112616C0FC00DD1EC2 /* PixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44102616C0FC00DD1EC2 /* PixelTests.swift */; }; B6DA44172616C13800DD1EC2 /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = B6DA44162616C13800DD1EC2 /* OHHTTPStubs */; }; B6DA44192616C13800DD1EC2 /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = B6DA44182616C13800DD1EC2 /* OHHTTPStubsSwift */; }; B6DA441E2616C84600DD1EC2 /* PixelStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA441D2616C84600DD1EC2 /* PixelStoreMock.swift */; }; - B6DA44232616CABC00DD1EC2 /* PixelArgumentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44222616CABC00DD1EC2 /* PixelArgumentsTests.swift */; }; B6DB3AEF278D5C370024C5C4 /* URLSessionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DB3AEE278D5C370024C5C4 /* URLSessionExtension.swift */; }; B6DB3AF6278EA0130024C5C4 /* BundleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106B9D26A565DA0013B453 /* BundleExtension.swift */; }; B6DB3CF926A00E2D00D459B7 /* AVCaptureDevice+SwizzledAuthState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DB3CF826A00E2D00D459B7 /* AVCaptureDevice+SwizzledAuthState.swift */; }; @@ -3961,7 +3953,6 @@ 4BBF0916282DD6EF00EE1418 /* TemporaryFileHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemporaryFileHandlerTests.swift; sourceTree = ""; }; 4BBF09222830812900EE1418 /* FileSystemDSL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSystemDSL.swift; sourceTree = ""; }; 4BBF0924283083EC00EE1418 /* FileSystemDSLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSystemDSLTests.swift; sourceTree = ""; }; - 4BC2621C293996410087A482 /* PixelEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelEventTests.swift; sourceTree = ""; }; 4BCF15D62ABB8A110083F6DF /* NetworkProtectionRemoteMessaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionRemoteMessaging.swift; sourceTree = ""; }; 4BCF15D82ABB8A7F0083F6DF /* NetworkProtectionRemoteMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionRemoteMessage.swift; sourceTree = ""; }; 4BCF15E42ABB98990083F6DF /* NetworkProtectionRemoteMessageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionRemoteMessageTests.swift; sourceTree = ""; }; @@ -4498,7 +4489,6 @@ B66260DC29AC5D4300E9E3EE /* NavigationProtectionTabExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationProtectionTabExtension.swift; sourceTree = ""; }; B66260DF29AC6EBD00E9E3EE /* HistoryTabExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryTabExtension.swift; sourceTree = ""; }; B66260E529ACAE4B00E9E3EE /* NavigationHotkeyHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationHotkeyHandler.swift; sourceTree = ""; }; - B662D3D82755D7AD0035D4D6 /* PixelStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelStoreTests.swift; sourceTree = ""; }; B662D3DD275613BB0035D4D6 /* EncryptionKeyStoreMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionKeyStoreMock.swift; sourceTree = ""; }; B6656E0C2B29C733008798A1 /* FileImportViewLocalizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileImportViewLocalizationTests.swift; sourceTree = ""; }; B6676BE02AA986A700525A21 /* AddressBarTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressBarTextEditor.swift; sourceTree = ""; }; @@ -4659,9 +4649,7 @@ B6DA06E7291401D700225DE2 /* WKMenuItemIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKMenuItemIdentifier.swift; sourceTree = ""; }; B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelDataStore.swift; sourceTree = ""; }; B6DA44072616B30600DD1EC2 /* PixelDataModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = PixelDataModel.xcdatamodel; sourceTree = ""; }; - B6DA44102616C0FC00DD1EC2 /* PixelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PixelTests.swift; sourceTree = ""; }; B6DA441D2616C84600DD1EC2 /* PixelStoreMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelStoreMock.swift; sourceTree = ""; }; - B6DA44222616CABC00DD1EC2 /* PixelArgumentsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelArgumentsTests.swift; sourceTree = ""; }; B6DB3AEE278D5C370024C5C4 /* URLSessionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionExtension.swift; sourceTree = ""; }; B6DB3CF826A00E2D00D459B7 /* AVCaptureDevice+SwizzledAuthState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVCaptureDevice+SwizzledAuthState.swift"; sourceTree = ""; }; B6DB3CFA26A17CB800D459B7 /* PermissionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionModel.swift; sourceTree = ""; }; @@ -8582,10 +8570,6 @@ children = ( 857E5AF72A79617100FC0FB4 /* PixelExperiment */, B69B50402726C3F400758A2B /* ATB */, - B6DA44102616C0FC00DD1EC2 /* PixelTests.swift */, - 4BC2621C293996410087A482 /* PixelEventTests.swift */, - B662D3D82755D7AD0035D4D6 /* PixelStoreTests.swift */, - B6DA44222616CABC00DD1EC2 /* PixelArgumentsTests.swift */, B6DA441D2616C84600DD1EC2 /* PixelStoreMock.swift */, 4B117F7C276C0CB5002F3D8C /* LocalStatisticsStoreTests.swift */, B610F2E327A8F37A00FCEBE9 /* CBRCompileTimeReporterTests.swift */, @@ -10976,7 +10960,6 @@ 3706FDE0293F661700E42796 /* TabIndexTests.swift in Sources */, 9F26060F2B85E17D00819292 /* AddEditBookmarkDialogCoordinatorViewModelTests.swift in Sources */, 3706FDE1293F661700E42796 /* AdjacentItemEnumeratorTests.swift in Sources */, - 3706FDE2293F661700E42796 /* PixelArgumentsTests.swift in Sources */, 4B9DB0572A983B55000927DB /* MockNotificationService.swift in Sources */, 3706FDE4293F661700E42796 /* TabLazyLoaderTests.swift in Sources */, 3706FDE5293F661700E42796 /* URLEventHandlerTests.swift in Sources */, @@ -11050,7 +11033,6 @@ 3706FE1B293F661700E42796 /* PermissionManagerTests.swift in Sources */, 3706FE1C293F661700E42796 /* ConnectBitwardenViewModelTests.swift in Sources */, 4B9DB0552A983B55000927DB /* MockWaitlistStorage.swift in Sources */, - 3706FE1D293F661700E42796 /* PixelStoreTests.swift in Sources */, 1D9FDEC72B9B64DB0040B78C /* PrivacyProtectionStatusTests.swift in Sources */, C13909F52B85FD79001626ED /* AutofillDeleteAllPasswordsExecutorTests.swift in Sources */, 857E44642A9F70F200ED77A7 /* CampaignVariantTests.swift in Sources */, @@ -11122,7 +11104,6 @@ 3706FE47293F661700E42796 /* URLExtensionTests.swift in Sources */, 1DB9617B29F1D06D00CF5568 /* InternalUserDeciderMock.swift in Sources */, 317295D52AF058D3002C3206 /* MockWaitlistFeatureSetupHandler.swift in Sources */, - 3706FE48293F661700E42796 /* PixelTests.swift in Sources */, 3706FE49293F661700E42796 /* BookmarkNodePathTests.swift in Sources */, 1DE03425298BC7F000CAB3D7 /* InternalUserDeciderStoreMock.swift in Sources */, 3706FE4A293F661700E42796 /* BookmarkManagedObjectTests.swift in Sources */, @@ -11151,7 +11132,6 @@ 3706FE5A293F661700E42796 /* GeolocationServiceMock.swift in Sources */, 3706FE5B293F661700E42796 /* FirefoxLoginReaderTests.swift in Sources */, 1D1C36E429FAE8DA001FA40C /* FaviconManagerTests.swift in Sources */, - 376E2D29294286B8001CD31B /* PixelEventTests.swift in Sources */, 37716D8029707E5D00A9FC6D /* FireproofingReferenceTests.swift in Sources */, B6AA64742994B43300D99CD6 /* FutureExtensionTests.swift in Sources */, 3706FE5C293F661700E42796 /* DuckPlayerPreferencesTests.swift in Sources */, @@ -13030,7 +13010,6 @@ 4B9DB05A2A983B55000927DB /* MockWaitlistRequest.swift in Sources */, 37D2377C287EBDA300BCE03B /* TabIndexTests.swift in Sources */, 37534CA52811987D002621E7 /* AdjacentItemEnumeratorTests.swift in Sources */, - B6DA44232616CABC00DD1EC2 /* PixelArgumentsTests.swift in Sources */, 56B234BF2A84EFD200F2A1CC /* NavigationBarUrlExtensionsTests.swift in Sources */, C13909F42B85FD79001626ED /* AutofillDeleteAllPasswordsExecutorTests.swift in Sources */, 37534C9E28104D9B002621E7 /* TabLazyLoaderTests.swift in Sources */, @@ -13113,7 +13092,6 @@ B6106BA026A7BE0B0013B453 /* PermissionManagerTests.swift in Sources */, 4B59CC8C290083240058F2F6 /* ConnectBitwardenViewModelTests.swift in Sources */, B68412202B6A30680092F66A /* StringExtensionTests.swift in Sources */, - B662D3D92755D7AD0035D4D6 /* PixelStoreTests.swift in Sources */, B6106BB526A809E60013B453 /* GeolocationProviderTests.swift in Sources */, B6E6BA232BA2EDDE008AA7E1 /* FileReadResult.swift in Sources */, B6A5A2A025B96E8300AA7ADA /* AppStateChangePublisherTests.swift in Sources */, @@ -13186,7 +13164,6 @@ C1E961EB2B879E79001760E1 /* MockAutofillActionPresenter.swift in Sources */, 4BA1A6FE258C5C1300F6F690 /* EncryptedValueTransformerTests.swift in Sources */, 85F69B3C25EDE81F00978E59 /* URLExtensionTests.swift in Sources */, - B6DA44112616C0FC00DD1EC2 /* PixelTests.swift in Sources */, 4B9292BA2667103100AD2C21 /* BookmarkNodePathTests.swift in Sources */, 4B9292C02667103100AD2C21 /* BookmarkManagedObjectTests.swift in Sources */, 373A1AB228451ED400586521 /* BookmarksHTMLImporterTests.swift in Sources */, @@ -13233,7 +13210,6 @@ B698E5042908011E00A746A8 /* AppKitPrivateMethodsAvailabilityTests.swift in Sources */, 56D145EE29E6DAD900E3488A /* DataImportProviderTests.swift in Sources */, 4BB99D0F26FE1A84001E4761 /* ChromiumBookmarksReaderTests.swift in Sources */, - 4BC2621D293996410087A482 /* PixelEventTests.swift in Sources */, 1D9FDEB72B9B5D150040B78C /* SearchPreferencesTests.swift in Sources */, 1D12F2E2298BC660009A65FD /* InternalUserDeciderStoreMock.swift in Sources */, 4BB99D1026FE1A84001E4761 /* FirefoxBookmarksReaderTests.swift in Sources */, diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 0ba9143e11..f218876e5c 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -97,6 +97,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate { var updateController: UpdateController! #endif + static private var aMonthAgo = Calendar.current.date(byAdding: .month, value: -1, to: Date())! // Temporary for init + @UserDefaultsWrapper(key: .firstLaunchDate, defaultValue: aMonthAgo) + static var firstLaunchDate: Date + + static var isNewUser: Bool { + let oneWeekAgo = Calendar.current.date(byAdding: .weekOfYear, value: -1, to: Date())! + return firstLaunchDate >= oneWeekAgo + } + // swiftlint:disable:next function_body_length override init() { do { @@ -225,10 +234,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate { if PixelExperiment.allocatedCohortDoesNotMatchCurrentCohorts { PixelExperiment.cleanup() } -// if LocalStatisticsStore().atb == nil { -// Pixel.firstLaunchDate = Date() // TODO: WTF is this? reimplement? -// // MARK: Enable pixel experiments here -// } + + if LocalStatisticsStore().atb == nil { + AppDelegate.firstLaunchDate = Date() + // MARK: Enable pixel experiments here + } AtbAndVariantCleanup.cleanup() DefaultVariantManager().assignVariantIfNeeded { _ in // MARK: perform first time launch logic here @@ -375,7 +385,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate { appVersion: AppVersion.shared.versionNumber, source: source, defaultHeaders: [:], - log: .networkProtectionPixel, defaults: .netP) { (pixelName: String, headers: [String: String], parameters: [String: String], _, _, onComplete: @escaping PixelKit.CompletionBlock) in let url = URL.pixelUrl(forPixelNamed: pixelName) @@ -434,7 +443,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { .filter { $0 } .asVoid() .sink { [weak syncService] in - PixelKit.fire(GeneralPixel.syncDaily, frequency: .dailyOnly) + PixelKit.fire(GeneralPixel.syncDaily, frequency: .daily) syncService?.syncDailyStats.sendStatsIfNeeded(handler: { params in PixelKit.fire(GeneralPixel.syncSuccessRateDaily, withAdditionalParameters: params) }) @@ -518,9 +527,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate { private func emailDidSignInNotification(_ notification: Notification) { PixelKit.fire(GeneralPixel.emailEnabled) -// if Pixel.isNewUser { // TODO: reimplement Pixel.isNewUser -// PixelKit.fire(GeneralPixel.emailEnabledInitial, frequency: .justOnce) -// } + if AppDelegate.isNewUser { + PixelKit.fire(GeneralPixel.emailEnabledInitial, frequency: .legacyInitial) + } if let object = notification.object as? EmailManager, let emailManager = syncDataProviders.settingsAdapter.emailManager, object !== emailManager { syncService?.scheduler.notifyDataChanged() @@ -535,11 +544,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate { } @objc private func dataImportCompleteNotification(_ notification: Notification) { -// if Pixel.isNewUser { // TODO: reimplement Pixel.isNewUser -// PixelKit.fire(GeneralPixel.importDataInitial, frequency: .justOnce) -// } + if AppDelegate.isNewUser { + PixelKit.fire(GeneralPixel.importDataInitial, frequency: .legacyInitial) + } } - } func updateSubscriptionStatus() { @@ -551,7 +559,7 @@ func updateSubscriptionStatus() { if case .success(let subscription) = await SubscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) { if subscription.isActive { - PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActive, frequency: .dailyOnly) + PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActive, frequency: .daily) } } @@ -578,7 +586,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate { #if NETWORK_PROTECTION if response.notification.request.identifier == NetworkProtectionWaitlist.notificationIdentifier { if NetworkProtectionWaitlist().readyToAcceptTermsAndConditions { - PixelKit.fire(GeneralPixel.networkProtectionWaitlistNotificationTapped, frequency: .dailyAndContinuous) + PixelKit.fire(GeneralPixel.networkProtectionWaitlistNotificationTapped, frequency: .dailyAndCount) NetworkProtectionWaitlistViewControllerPresenter.show() } } diff --git a/DuckDuckGo/DBP/DataBrokerProtectionAppEvents.swift b/DuckDuckGo/DBP/DataBrokerProtectionAppEvents.swift index f135cb8b50..d98257ed76 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionAppEvents.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionAppEvents.swift @@ -81,9 +81,9 @@ struct DataBrokerProtectionAppEvents { if DataBrokerProtectionWaitlist().readyToAcceptTermsAndConditions { switch source { case .cardUI: - DataBrokerProtectionExternalWaitlistPixels.fire(pixel: GeneralPixel.dataBrokerProtectionWaitlistCardUITapped, frequency: .dailyAndContinuous) + DataBrokerProtectionExternalWaitlistPixels.fire(pixel: GeneralPixel.dataBrokerProtectionWaitlistCardUITapped, frequency: .dailyAndCount) case .localPush: - DataBrokerProtectionExternalWaitlistPixels.fire(pixel: GeneralPixel.dataBrokerProtectionWaitlistNotificationTapped, frequency: .dailyAndContinuous) + DataBrokerProtectionExternalWaitlistPixels.fire(pixel: GeneralPixel.dataBrokerProtectionWaitlistNotificationTapped, frequency: .dailyAndCount) } DataBrokerProtectionWaitlistViewControllerPresenter.show() @@ -96,12 +96,12 @@ struct DataBrokerProtectionAppEvents { private func sendActiveDataBrokerProtectionWaitlistUserPixel() { if DefaultDataBrokerProtectionFeatureVisibility().waitlistIsOngoing { - DataBrokerProtectionExternalWaitlistPixels.fire(pixel: GeneralPixel.dataBrokerProtectionWaitlistUserActive, frequency: .dailyOnly) + DataBrokerProtectionExternalWaitlistPixels.fire(pixel: GeneralPixel.dataBrokerProtectionWaitlistUserActive, frequency: .daily) } } private func restartBackgroundAgent(loginItemsManager: LoginItemsManager) { - DataBrokerProtectionLoginItemPixels.fire(pixel: GeneralPixel.dataBrokerResetLoginItemDaily, frequency: .dailyOnly) + DataBrokerProtectionLoginItemPixels.fire(pixel: GeneralPixel.dataBrokerResetLoginItemDaily, frequency: .daily) loginItemsManager.disableLoginItems([LoginItem.dbpBackgroundAgent]) loginItemsManager.enableLoginItems([LoginItem.dbpBackgroundAgent], log: .dbp) diff --git a/DuckDuckGo/DBP/DataBrokerProtectionFeatureDisabler.swift b/DuckDuckGo/DBP/DataBrokerProtectionFeatureDisabler.swift index f1acba7569..1fd6a05b2d 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionFeatureDisabler.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionFeatureDisabler.swift @@ -48,7 +48,7 @@ struct DataBrokerProtectionFeatureDisabler: DataBrokerProtectionFeatureDisabling dataManager.removeAllData() - DataBrokerProtectionLoginItemPixels.fire(pixel: GeneralPixel.dataBrokerDisableAndDeleteDaily, frequency: .dailyOnly) + DataBrokerProtectionLoginItemPixels.fire(pixel: GeneralPixel.dataBrokerDisableAndDeleteDaily, frequency: .daily) NotificationCenter.default.post(name: .dbpWasDisabled, object: nil) } } diff --git a/DuckDuckGo/DBP/DataBrokerProtectionLoginItemPixels.swift b/DuckDuckGo/DBP/DataBrokerProtectionLoginItemPixels.swift index 84bdeba949..6b10effcf1 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionLoginItemPixels.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionLoginItemPixels.swift @@ -19,7 +19,7 @@ import Foundation import PixelKit -struct DataBrokerProtectionLoginItemPixels { // TODO: Remove struct and add private extension to PixelKit? +struct DataBrokerProtectionLoginItemPixels { static func fire(pixel: PixelKitEventV2, frequency: PixelKit.Frequency) { diff --git a/DuckDuckGo/DBP/DataBrokerProtectionLoginItemScheduler.swift b/DuckDuckGo/DBP/DataBrokerProtectionLoginItemScheduler.swift index cf8d1d82d9..c860b7a7fa 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionLoginItemScheduler.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionLoginItemScheduler.swift @@ -36,12 +36,12 @@ final class DataBrokerProtectionLoginItemScheduler { // MARK: - Login Item Management func disableLoginItem() { - DataBrokerProtectionLoginItemPixels.fire(pixel: GeneralPixel.dataBrokerDisableLoginItemDaily, frequency: .dailyOnly) + DataBrokerProtectionLoginItemPixels.fire(pixel: GeneralPixel.dataBrokerDisableLoginItemDaily, frequency: .daily) loginItemsManager.disableLoginItems([.dbpBackgroundAgent]) } func enableLoginItem() { - DataBrokerProtectionLoginItemPixels.fire(pixel: GeneralPixel.dataBrokerEnableLoginItemDaily, frequency: .dailyOnly) + DataBrokerProtectionLoginItemPixels.fire(pixel: GeneralPixel.dataBrokerEnableLoginItemDaily, frequency: .daily) loginItemsManager.enableLoginItems([.dbpBackgroundAgent], log: .dbp) } } diff --git a/DuckDuckGo/Favicons/Services/FaviconStore.swift b/DuckDuckGo/Favicons/Services/FaviconStore.swift index 0bfa5d0b32..2376d29162 100644 --- a/DuckDuckGo/Favicons/Services/FaviconStore.swift +++ b/DuckDuckGo/Favicons/Services/FaviconStore.swift @@ -231,7 +231,7 @@ fileprivate extension Favicon { let documentUrl = faviconMO.documentUrlEncrypted as? URL, let dateCreated = faviconMO.dateCreated, let relation = Favicon.Relation(rawValue: Int(faviconMO.relation)) else { - PixelKit.fire(DebugEvent(GeneralPixel.faviconDecryptionFailedUnique), frequency: .dailyOnly) + PixelKit.fire(DebugEvent(GeneralPixel.faviconDecryptionFailedUnique), frequency: .daily) assertionFailure("Favicon: Failed to init Favicon from FaviconManagedObject") return nil } diff --git a/DuckDuckGo/FileDownload/Services/DownloadListStore.swift b/DuckDuckGo/FileDownload/Services/DownloadListStore.swift index 6b884bea73..9af9ffb38a 100644 --- a/DuckDuckGo/FileDownload/Services/DownloadListStore.swift +++ b/DuckDuckGo/FileDownload/Services/DownloadListStore.swift @@ -192,7 +192,7 @@ extension DownloadListItem { let modified = managedObject.modified, let url = managedObject.urlEncrypted as? URL else { - PixelKit.fire(DebugEvent(GeneralPixel.downloadListItemDecryptionFailedUnique), frequency: .dailyOnly) + PixelKit.fire(DebugEvent(GeneralPixel.downloadListItemDecryptionFailedUnique), frequency: .daily) assertionFailure("DownloadListItem: Failed to init from ManagedObject") return nil } diff --git a/DuckDuckGo/Fire/ViewModel/FirePopoverViewModel.swift b/DuckDuckGo/Fire/ViewModel/FirePopoverViewModel.swift index 80d2470c45..1221497f5f 100644 --- a/DuckDuckGo/Fire/ViewModel/FirePopoverViewModel.swift +++ b/DuckDuckGo/Fire/ViewModel/FirePopoverViewModel.swift @@ -189,7 +189,7 @@ final class FirePopoverViewModel { // MARK: - Burning func burn() { - PixelKit.fire(GeneralPixel.fireButtonFirstBurn, frequency: .dailyOnly) + PixelKit.fire(GeneralPixel.fireButtonFirstBurn, frequency: .daily) switch (clearingOption, areAllSelected) { case (.currentTab, _): diff --git a/DuckDuckGo/History/Services/EncryptedHistoryStore.swift b/DuckDuckGo/History/Services/EncryptedHistoryStore.swift index 7e1c5984f0..fd6cb88790 100644 --- a/DuckDuckGo/History/Services/EncryptedHistoryStore.swift +++ b/DuckDuckGo/History/Services/EncryptedHistoryStore.swift @@ -344,7 +344,7 @@ fileprivate extension HistoryEntry { guard let url = historyEntryMO.urlEncrypted as? URL, let identifier = historyEntryMO.identifier, let lastVisit = historyEntryMO.lastVisit else { - PixelKit.fire(DebugEvent(GeneralPixel.historyEntryDecryptionFailedUnique), frequency: .dailyOnly) + PixelKit.fire(DebugEvent(GeneralPixel.historyEntryDecryptionFailedUnique), frequency: .daily) assertionFailure("HistoryEntry: Failed to init HistoryEntry from HistoryEntryManagedObject") return nil } diff --git a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift index d2da000baf..ddf078179d 100644 --- a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift +++ b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift @@ -251,13 +251,13 @@ extension HomePage.Models { for message in homePageRemoteMessaging.dataBrokerProtectionRemoteMessaging.presentableRemoteMessages() { features.append(.dataBrokerProtectionRemoteMessage(message)) - PixelKit.fire(GeneralPixel.dataBrokerProtectionRemoteMessageDisplayed(messageID: message.id), frequency: .dailyOnly) + PixelKit.fire(GeneralPixel.dataBrokerProtectionRemoteMessageDisplayed(messageID: message.id), frequency: .daily) } #endif #if NETWORK_PROTECTION for message in homePageRemoteMessaging.networkProtectionRemoteMessaging.presentableRemoteMessages() { - PixelKit.fire(GeneralPixel.networkProtectionRemoteMessageDisplayed(messageID: message.id), frequency: .dailyOnly) + PixelKit.fire(GeneralPixel.networkProtectionRemoteMessageDisplayed(messageID: message.id), frequency: .daily) } #endif diff --git a/DuckDuckGo/HomePage/View/HomePageViewController.swift b/DuckDuckGo/HomePage/View/HomePageViewController.swift index 457d3f4fd2..9769333182 100644 --- a/DuckDuckGo/HomePage/View/HomePageViewController.swift +++ b/DuckDuckGo/HomePage/View/HomePageViewController.swift @@ -107,9 +107,9 @@ final class HomePageViewController: NSViewController { override func viewWillAppear() { super.viewWillAppear() -// if OnboardingViewModel.isOnboardingFinished && Pixel.isNewUser { -// PixelKit.fire(GeneralPixel.newTabInitial, limitTo: .initial) -// } // TODO: reimplement Pixel.isNewUser + if OnboardingViewModel.isOnboardingFinished && AppDelegate.isNewUser { + PixelKit.fire(GeneralPixel.newTabInitial, frequency: .legacyInitial) + } subscribeToHistory() } diff --git a/DuckDuckGo/LoginItems/LoginItemsManager.swift b/DuckDuckGo/LoginItems/LoginItemsManager.swift index 2c0e4ab00f..0a0a4b4af4 100644 --- a/DuckDuckGo/LoginItems/LoginItemsManager.swift +++ b/DuckDuckGo/LoginItems/LoginItemsManager.swift @@ -71,7 +71,7 @@ final class LoginItemsManager { action: "enable", buildType: AppVersion.shared.buildType, osVersion: AppVersion.shared.osVersion) - PixelKit.fire(DebugEvent(event, error: error) , frequency: .dailyAndContinuous) + PixelKit.fire(DebugEvent(event, error: error) , frequency: .dailyAndCount) os_log("🔴 Could not enable %{public}@: %{public}@", item.debugDescription, error.debugDescription) } diff --git a/DuckDuckGo/MainWindow/MainViewController.swift b/DuckDuckGo/MainWindow/MainViewController.swift index ab264e8b49..b5736a6e79 100644 --- a/DuckDuckGo/MainWindow/MainViewController.swift +++ b/DuckDuckGo/MainWindow/MainViewController.swift @@ -454,7 +454,7 @@ final class MainViewController: NSViewController { #if NETWORK_PROTECTION private func sendActiveNetworkProtectionWaitlistUserPixel() { if DefaultNetworkProtectionVisibility().waitlistIsOngoing { - PixelKit.fire(GeneralPixel.networkProtectionWaitlistUserActive, frequency: .dailyOnly) + PixelKit.fire(GeneralPixel.networkProtectionWaitlistUserActive, frequency: .daily) } } #endif diff --git a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift index ad34551d95..c76e6bc011 100644 --- a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift +++ b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift @@ -366,7 +366,7 @@ final class MoreOptionsMenu: NSMenu { } #endif - PixelKit.fire(GeneralPixel.networkProtectionWaitlistEntryPointMenuItemDisplayed, frequency: .dailyAndContinuous, includeAppVersionParameter: true) + PixelKit.fire(GeneralPixel.networkProtectionWaitlistEntryPointMenuItemDisplayed, frequency: .dailyAndCount, includeAppVersionParameter: true) } else { networkProtectionFeatureVisibility.disableForWaitlistUsers() } @@ -399,7 +399,7 @@ final class MoreOptionsMenu: NSMenu { } #endif - DataBrokerProtectionExternalWaitlistPixels.fire(pixel: GeneralPixel.dataBrokerProtectionWaitlistEntryPointMenuItemDisplayed, frequency: .dailyAndContinuous) + DataBrokerProtectionExternalWaitlistPixels.fire(pixel: GeneralPixel.dataBrokerProtectionWaitlistEntryPointMenuItemDisplayed, frequency: .dailyAndCount) } else { DefaultDataBrokerProtectionFeatureVisibility().disableAndDeleteForWaitlistUsers() diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift index b2faf4a56e..294ca1f077 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift @@ -345,12 +345,12 @@ final class NavigationBarViewController: NSViewController { if NetworkProtectionWaitlist().shouldShowWaitlistViewController { NetworkProtectionWaitlistViewControllerPresenter.show() - PixelKit.fire(GeneralPixel.networkProtectionWaitlistIntroDisplayed, frequency: .dailyAndContinuous) + PixelKit.fire(GeneralPixel.networkProtectionWaitlistIntroDisplayed, frequency: .dailyAndCount) } else if NetworkProtectionKeychainTokenStore().isFeatureActivated { popovers.toggleNetworkProtectionPopover(usingView: networkProtectionButton, withDelegate: networkProtectionButtonModel) } else { NetworkProtectionWaitlistViewControllerPresenter.show() - PixelKit.fire(GeneralPixel.networkProtectionWaitlistIntroDisplayed, frequency: .dailyAndContinuous) + PixelKit.fire(GeneralPixel.networkProtectionWaitlistIntroDisplayed, frequency: .dailyAndCount) } } #endif diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/EventMapping+NetworkProtectionError.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/EventMapping+NetworkProtectionError.swift index 641c69c7f5..5a5d903aa4 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/EventMapping+NetworkProtectionError.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/EventMapping+NetworkProtectionError.swift @@ -64,10 +64,10 @@ extension EventMapping where Event == NetworkProtectionError { frequency = .standard case .failedToFetchLocationList(let error): domainEvent = .networkProtectionClientFailedToFetchLocations(error) - frequency = .dailyAndContinuous + frequency = .dailyAndCount case .failedToParseLocationListResponse(let error): domainEvent = .networkProtectionClientFailedToParseLocationsResponse(error) - frequency = .dailyAndContinuous + frequency = .dailyAndCount case .noServerRegistrationInfo, .couldNotSelectClosestServer, .couldNotGetPeerPublicKey, diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift index 88d8339c22..e498a18238 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift @@ -181,7 +181,7 @@ final class NetworkProtectionNavBarButtonModel: NSObject, ObservableObject { let networkProtectionVisibility = DefaultNetworkProtectionVisibility() if networkProtectionVisibility.isNetworkProtectionBetaVisible() { if NetworkProtectionWaitlist().readyToAcceptTermsAndConditions { - PixelKit.fire(GeneralPixel.networkProtectionWaitlistEntryPointToolbarButtonDisplayed, frequency: .dailyOnly, includeAppVersionParameter: true) + PixelKit.fire(GeneralPixel.networkProtectionWaitlistEntryPointToolbarButtonDisplayed, frequency: .daily, includeAppVersionParameter: true) showButton = true return } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift index 27833760c2..7be47f0865 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift @@ -487,7 +487,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr /// func start() async { PixelKit.fire(NetworkProtectionPixelEvent.networkProtectionControllerStartAttempt, - frequency: .dailyAndContinuous) + frequency: .dailyAndCount) controllerErrorStore.lastErrorMessage = nil do { @@ -528,11 +528,11 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr // in the packet tunnel provider side that can be used to debug additional logic. // PixelKit.fire(NetworkProtectionPixelEvent.networkProtectionControllerStartSuccess, - frequency: .dailyAndContinuous) + frequency: .dailyAndCount) } } catch { PixelKit.fire( - NetworkProtectionPixelEvent.networkProtectionControllerStartFailure(error), frequency: .dailyAndContinuous, includeAppVersionParameter: true + NetworkProtectionPixelEvent.networkProtectionControllerStartFailure(error), frequency: .dailyAndCount, includeAppVersionParameter: true ) await stop() @@ -584,7 +584,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionNewUser, - frequency: .justOnce, + frequency: .unique, includeAppVersionParameter: true) { [weak self] fired, error in guard let self, error == nil, fired else { return } self.defaults.vpnFirstEnabled = PixelKit.pixelLastFireDate(event: NetworkProtectionPixelEvent.networkProtectionNewUser) diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift index 8659580807..c362538f17 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift @@ -76,13 +76,13 @@ final class VPNLocationViewModel: ObservableObject { } func onNearestItemSelection() async { - PixelKit.fire(GeneralPixel.networkProtectionGeoswitchingSetNearest, frequency: .dailyAndContinuous) + PixelKit.fire(GeneralPixel.networkProtectionGeoswitchingSetNearest, frequency: .dailyAndCount) selectedLocation = .nearest await reloadList() } func onCountryItemSelection(id: String, cityId: String? = nil) async { - PixelKit.fire(GeneralPixel.networkProtectionGeoswitchingSetCustom, frequency: .dailyAndContinuous) + PixelKit.fire(GeneralPixel.networkProtectionGeoswitchingSetCustom, frequency: .dailyAndCount) let location = NetworkProtectionSelectedLocation(country: id, city: cityId) selectedLocation = .location(location) await reloadList() @@ -96,7 +96,7 @@ final class VPNLocationViewModel: ObservableObject { private func reloadList() async { guard let locations = try? await locationListRepository.fetchLocationList().sortedByName() else { return } if locations.isEmpty { - PixelKit.fire(GeneralPixel.networkProtectionGeoswitchingNoLocations, frequency: .dailyAndContinuous) + PixelKit.fire(GeneralPixel.networkProtectionGeoswitchingNoLocations, frequency: .dailyAndCount) } let isNearestSelected = selectedLocation == .nearest self.isNearestSelected = isNearestSelected diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index c9a8819faa..0922ec4a47 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -129,7 +129,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { return } - PixelKit.fire(domainEvent, frequency: .dailyAndContinuous, includeAppVersionParameter: true) + PixelKit.fire(domainEvent, frequency: .dailyAndCount, includeAppVersionParameter: true) } } @@ -150,7 +150,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { case .userBecameActive: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionActiveUser, - frequency: .dailyOnly, + frequency: .daily, withAdditionalParameters: ["cohort": PixelKit.dateString(for: defaults.vpnFirstEnabled)], includeAppVersionParameter: true) case .reportConnectionAttempt(attempt: let attempt): @@ -158,17 +158,17 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { case .connecting: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionEnableAttemptConnecting, - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) case .success: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionEnableAttemptSuccess, - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) case .failure: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionEnableAttemptFailure, - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) } case .reportTunnelFailure(result: let result): @@ -176,12 +176,12 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { case .failureDetected: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionTunnelFailureDetected, - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) case .failureRecovered: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionTunnelFailureRecovered, - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) case .networkPathChanged: break @@ -191,13 +191,13 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { case .error: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionLatencyError, - frequency: .dailyOnly, + frequency: .daily, includeAppVersionParameter: true) case .quality(let quality): guard quality != .unknown else { return } PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionLatency(quality: quality), - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) } case .rekeyAttempt(let step): @@ -205,17 +205,17 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { case .begin: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionRekeyAttempt, - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) case .failure(let error): PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionRekeyFailure(error), - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) case .success: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionRekeyCompleted, - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) } case .tunnelStartAttempt(let step): @@ -223,17 +223,17 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { case .begin: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionTunnelStartAttempt, - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) case .failure(let error): PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionTunnelStartFailure(error), - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) case .success: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionTunnelStartSuccess, - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) } case .tunnelUpdateAttempt(let step): @@ -241,17 +241,17 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { case .begin: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionTunnelUpdateAttempt, - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) case .failure(let error): PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionTunnelUpdateFailure(error), - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) case .success: PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionTunnelUpdateSuccess, - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, includeAppVersionParameter: true) } } @@ -474,7 +474,6 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { appVersion: AppVersion.shared.versionNumber, source: source, defaultHeaders: defaultHeaders, - log: .networkProtectionPixel, defaults: .netP) { (pixelName: String, headers: [String: String], parameters: [String: String], _, _, onComplete: @escaping PixelKit.CompletionBlock) in let url = URL.pixelUrl(forPixelNamed: pixelName) diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacTransparentProxyProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacTransparentProxyProvider.swift index d300309ec6..5d9b0c0fa4 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacTransparentProxyProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacTransparentProxyProvider.swift @@ -73,7 +73,6 @@ final class MacTransparentProxyProvider: TransparentProxyProvider { appVersion: AppVersion.shared.versionNumber, source: "vpnProxyExtension", defaultHeaders: [:], - log: .networkProtectionPixel, defaults: .netP) { (pixelName: String, headers: [String: String], parameters: [String: String], _, _, onComplete: @escaping PixelKit.CompletionBlock) in let url = URL.pixelUrl(forPixelNamed: pixelName) diff --git a/DuckDuckGo/Permissions/Model/StoredPermission.swift b/DuckDuckGo/Permissions/Model/StoredPermission.swift index b846eac0b5..6a73f71f1e 100644 --- a/DuckDuckGo/Permissions/Model/StoredPermission.swift +++ b/DuckDuckGo/Permissions/Model/StoredPermission.swift @@ -65,7 +65,7 @@ struct PermissionEntity: Equatable { guard let domain = managedObject.domainEncrypted as? String, let permissionTypeString = managedObject.permissionType, let permissionType = PermissionType(rawValue: permissionTypeString) else { - PixelKit.fire(DebugEvent(GeneralPixel.permissionDecryptionFailedUnique), frequency: .dailyOnly) + PixelKit.fire(DebugEvent(GeneralPixel.permissionDecryptionFailedUnique), frequency: .daily) assertionFailure("\(#file): Failed to create PermissionEntity from PermissionManagedObject") return nil } diff --git a/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift b/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift index fd09994721..95142d1891 100644 --- a/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift +++ b/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift @@ -20,6 +20,7 @@ import Combine import Common import Foundation import SwiftUI +import PixelKit protocol DefaultBrowserProvider { var bundleIdentifier: String { get } @@ -76,12 +77,14 @@ final class DefaultBrowserPreferences: ObservableObject { @Published private(set) var isDefault: Bool = false { didSet { // Temporary pixel for first time user import data + DispatchQueue.main.async { #if DEBUG - guard NSApp.runType.requiresEnvironment else { return } + guard NSApp.runType.requiresEnvironment else { return } #endif -// if Pixel.isNewUser && isDefault { // TODO: reimplement Pixel.isNewUser -// PixelKit.fire(GeneralPixel.setAsDefaultInitial, limitTo: .initial) -// } + if AppDelegate.isNewUser && self.isDefault { // TODO: reimplement Pixel.isNewUser + PixelKit.fire(GeneralPixel.setAsDefaultInitial, frequency: .legacyInitial) + } + } } } @Published private(set) var restorePreviousSession: Bool = false diff --git a/DuckDuckGo/Preferences/View/PreferencesRootView.swift b/DuckDuckGo/Preferences/View/PreferencesRootView.swift index 7c714ae842..452d870adc 100644 --- a/DuckDuckGo/Preferences/View/PreferencesRootView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesRootView.swift @@ -159,11 +159,11 @@ enum Preferences { case .iHaveASubscriptionClick: PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseClick) case .activateAddEmailClick: - PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailStart, frequency: .dailyAndContinuous) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailStart, frequency: .dailyAndCount) case .postSubscriptionAddEmailClick: - PixelKit.fire(PrivacyProPixel.privacyProWelcomeAddDevice, frequency: .justOnce) + PixelKit.fire(PrivacyProPixel.privacyProWelcomeAddDevice, frequency: .unique) case .restorePurchaseStoreClick: - PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreStart, frequency: .dailyAndContinuous) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreStart, frequency: .dailyAndCount) case .addToAnotherDeviceClick: PixelKit.fire(PrivacyProPixel.privacyProSettingsAddDevice) case .addDeviceEnterEmail: diff --git a/DuckDuckGo/SmarterEncryption/PrivacyFeatures.swift b/DuckDuckGo/SmarterEncryption/PrivacyFeatures.swift index 37067cdfc8..fbaecc8a21 100644 --- a/DuckDuckGo/SmarterEncryption/PrivacyFeatures.swift +++ b/DuckDuckGo/SmarterEncryption/PrivacyFeatures.swift @@ -57,14 +57,14 @@ final class AppPrivacyFeatures: PrivacyFeaturesProtocol { if dailyAndCount { PixelKit.fire(DebugEvent(domainEvent, error: error), - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, withAdditionalParameters: parameters ?? [:], includeAppVersionParameter: true) { success, error in onComplete(error) } } else { PixelKit.fire(DebugEvent(domainEvent, error: error), - frequency: .dailyAndContinuous, + frequency: .dailyAndCount, withAdditionalParameters: parameters ?? [:]) { success, error in onComplete(error) } diff --git a/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift b/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift index 073bf8ea41..cc1699d615 100644 --- a/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift +++ b/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift @@ -219,7 +219,7 @@ final class StatisticsLoader { DispatchQueue.global().asyncAfter(deadline: .now() + randomDelay) { PixelKit.fire(GeneralPixel.dailyOsVersionCounter, - frequency: .dailyOnly, + frequency: .daily, includeAppVersionParameter: false) } } diff --git a/DuckDuckGo/Statistics/Experiment/PixelExperiment.swift b/DuckDuckGo/Statistics/Experiment/PixelExperiment.swift index 7584feec0c..601a573799 100644 --- a/DuckDuckGo/Statistics/Experiment/PixelExperiment.swift +++ b/DuckDuckGo/Statistics/Experiment/PixelExperiment.swift @@ -129,7 +129,7 @@ final internal class PixelExperimentLogic { isInstalled = true } - // You'll need additional pixels for your experiment. Pass the cohort as a paramter. + // You'll need additional pixels for your experiment. Pass the cohort as a parameter. func fireEnrollmentPixel() { // You'll probably need this at least. } @@ -137,16 +137,19 @@ final internal class PixelExperimentLogic { // Often used func fireFirstSerpPixel() { guard allocatedCohort != nil, let cohort else { return } - PixelKit.fire(GeneralPixel.serpInitial(cohort: cohort.rawValue), frequency: .justOnce, includeAppVersionParameter: false) + PixelKit.fire(GeneralPixel.serpInitial(cohort: cohort.rawValue), frequency: .unique, includeAppVersionParameter: false) } // Often used for retention experiments func fireDay21To27SerpPixel() { guard allocatedCohort != nil, let cohort else { return } -// if now() >= Pixel.firstLaunchDate.adding(.days(21)) && now() <= Pixel.firstLaunchDate.adding(.days(27)) { // TODO: reimplement -// PixelKit.fire(GeneralPixel.serpDay21to27(cohort: cohort.rawValue), frequency: .justOnce, includeAppVersionParameter: false) -// } + DispatchQueue.main.async { + let now = self.now() + if now >= AppDelegate.firstLaunchDate.adding(.days(21)) && now <= AppDelegate.firstLaunchDate.adding(.days(27)) { + PixelKit.fire(GeneralPixel.serpDay21to27(cohort: cohort.rawValue), frequency: .unique, includeAppVersionParameter: false) + } + } } func cleanup() { diff --git a/DuckDuckGo/Statistics/GeneralPixel.swift b/DuckDuckGo/Statistics/GeneralPixel.swift index 8a06a48927..10c187d536 100644 --- a/DuckDuckGo/Statistics/GeneralPixel.swift +++ b/DuckDuckGo/Statistics/GeneralPixel.swift @@ -809,6 +809,44 @@ enum GeneralPixel: PixelKitEventV2 { switch self { case .loginItemUpdateError(let loginItemBundleID, let action, let buildType, let osVersion): return ["loginItemBundleID": loginItemBundleID, "action": action, "buildType": buildType, "macosVersion": osVersion] + + case .dataImportFailed(source: _, sourceVersion: let version, error: let error): + var params = error.pixelParameters + + if let version { + params[PixelKit.Parameters.sourceBrowserVersion] = version + } + return params + + case .launchInitial(let cohort): + return [PixelKit.Parameters.experimentCohort: cohort] + + case .serpInitial(let cohort): + return [PixelKit.Parameters.experimentCohort: cohort] + + case .serpDay21to27(let cohort): + return [PixelKit.Parameters.experimentCohort: cohort] + + case .dailyOsVersionCounter: + return [PixelKit.Parameters.osMajorVersion: "\(ProcessInfo.processInfo.operatingSystemVersion.majorVersion)"] + + case .dashboardProtectionAllowlistAdd(let triggerOrigin): + guard let trigger = triggerOrigin else { return nil } + return [PixelKit.Parameters.dashboardTriggerOrigin: trigger] + + case .dashboardProtectionAllowlistRemove(let triggerOrigin): + guard let trigger = triggerOrigin else { return nil } + return [PixelKit.Parameters.dashboardTriggerOrigin: trigger] + + case .syncSuccessRateDaily: + return nil + + case .vpnBreakageReport(let category, let description, let metadata): + return [ + PixelKit.Parameters.vpnBreakageCategory: category, + PixelKit.Parameters.vpnBreakageDescription: description, + PixelKit.Parameters.vpnBreakageMetadata: metadata + ] default: return nil } } diff --git a/DuckDuckGo/Sync/SyncBookmarksAdapter.swift b/DuckDuckGo/Sync/SyncBookmarksAdapter.swift index 97cc7db276..54dae6e41d 100644 --- a/DuckDuckGo/Sync/SyncBookmarksAdapter.swift +++ b/DuckDuckGo/Sync/SyncBookmarksAdapter.swift @@ -173,12 +173,12 @@ final class SyncBookmarksAdapter { case .unexpectedStatusCode(409): // If bookmarks count limit has been exceeded self?.isSyncBookmarksPaused = true - PixelKit.fire(GeneralPixel.syncBookmarksCountLimitExceededDaily, frequency: .dailyOnly) + PixelKit.fire(GeneralPixel.syncBookmarksCountLimitExceededDaily, frequency: .daily) self?.showSyncPausedAlert() case .unexpectedStatusCode(413): // If bookmarks request size limit has been exceeded self?.isSyncBookmarksPaused = true - PixelKit.fire(GeneralPixel.syncBookmarksRequestSizeLimitExceededDaily, frequency: .dailyOnly) + PixelKit.fire(GeneralPixel.syncBookmarksRequestSizeLimitExceededDaily, frequency: .daily) self?.showSyncPausedAlert() default: break diff --git a/DuckDuckGo/Sync/SyncCredentialsAdapter.swift b/DuckDuckGo/Sync/SyncCredentialsAdapter.swift index 979e8cee66..571fa21afb 100644 --- a/DuckDuckGo/Sync/SyncCredentialsAdapter.swift +++ b/DuckDuckGo/Sync/SyncCredentialsAdapter.swift @@ -91,12 +91,12 @@ final class SyncCredentialsAdapter { case .unexpectedStatusCode(409): // If credentials count limit has been exceeded self?.isSyncCredentialsPaused = true - PixelKit.fire(GeneralPixel.syncCredentialsCountLimitExceededDaily, frequency: .dailyOnly) + PixelKit.fire(GeneralPixel.syncCredentialsCountLimitExceededDaily, frequency: .daily) self?.showSyncPausedAlert() case .unexpectedStatusCode(413): // If credentials request size limit has been exceeded self?.isSyncCredentialsPaused = true - PixelKit.fire(GeneralPixel.syncCredentialsRequestSizeLimitExceededDaily, frequency: .dailyOnly) + PixelKit.fire(GeneralPixel.syncCredentialsRequestSizeLimitExceededDaily, frequency: .daily) self?.showSyncPausedAlert() default: break diff --git a/DuckDuckGo/Tab/Model/Tab.swift b/DuckDuckGo/Tab/Model/Tab.swift index 72f48916fa..7335ba7cb8 100644 --- a/DuckDuckGo/Tab/Model/Tab.swift +++ b/DuckDuckGo/Tab/Model/Tab.swift @@ -1468,7 +1468,7 @@ extension Tab/*: NavigationResponder*/ { // to be moved to Tab+Navigation.swift #if NETWORK_PROTECTION if navigation.url.isDuckDuckGoSearch, tunnelController?.isConnected == true { - PixelKit.fire(GeneralPixel.networkProtectionEnabledOnSearch, frequency: .dailyAndContinuous) + PixelKit.fire(GeneralPixel.networkProtectionEnabledOnSearch, frequency: .dailyAndCount) } #endif } diff --git a/DuckDuckGo/Tab/TabExtensions/DuckPlayerTabExtension.swift b/DuckDuckGo/Tab/TabExtensions/DuckPlayerTabExtension.swift index def1d649a2..f682f201b9 100644 --- a/DuckDuckGo/Tab/TabExtensions/DuckPlayerTabExtension.swift +++ b/DuckDuckGo/Tab/TabExtensions/DuckPlayerTabExtension.swift @@ -301,7 +301,7 @@ extension DuckPlayerTabExtension: NavigationResponder { return } if navigation.url.isDuckPlayer { - PixelKit.fire(GeneralPixel.duckPlayerDailyUniqueView, frequency: .dailyOnly) + PixelKit.fire(GeneralPixel.duckPlayerDailyUniqueView, frequency: .daily) } } diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift index 75d08cb0a1..35661ff131 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift @@ -67,12 +67,12 @@ struct SubscriptionAppStoreRestorer { switch result { case .success: - PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreSuccess, frequency: .dailyAndContinuous) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreSuccess, frequency: .dailyAndCount) case .failure(let error): switch error { case .missingAccountOrTransactions: break default: - PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreFailureOther, frequency: .dailyAndContinuous) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreFailureOther, frequency: .dailyAndCount) } switch error { diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionErrorReporter.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionErrorReporter.swift index 2a3de620ea..438997c0f7 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionErrorReporter.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionErrorReporter.swift @@ -61,7 +61,7 @@ struct SubscriptionErrorReporter { case .failedToRestorePastPurchase: isStoreError = true case .subscriptionNotFound: - PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreFailureNotFound, frequency: .dailyAndContinuous) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreFailureNotFound, frequency: .dailyAndCount) isStoreError = true case .subscriptionExpired: isStoreError = true @@ -70,17 +70,17 @@ struct SubscriptionErrorReporter { isBackendError = true case .cancelledByUser: break case .accountCreationFailed: - PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailureAccountNotCreated, frequency: .dailyAndContinuous) + PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailureAccountNotCreated, frequency: .dailyAndCount) case .activeSubscriptionAlreadyPresent: break case .generalError: break } if isStoreError { - PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailureStoreError, frequency: .dailyAndContinuous) + PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailureStoreError, frequency: .dailyAndCount) } if isBackendError { - PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailureBackendError, frequency: .dailyAndContinuous) + PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailureBackendError, frequency: .dailyAndCount) } } } diff --git a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift index afa8362a74..1b917bb9c6 100644 --- a/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift +++ b/DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionPagesUserScript.swift @@ -153,7 +153,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { func setSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { - PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailSuccess, frequency: .dailyAndContinuous) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailSuccess, frequency: .dailyAndCount) guard let subscriptionValues: SubscriptionValues = DecodableHelper.decode(from: params) else { assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") @@ -212,7 +212,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { // swiftlint:disable:next function_body_length cyclomatic_complexity func subscriptionSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { - PixelKit.fire(PrivacyProPixel.privacyProPurchaseAttempt, frequency: .dailyAndContinuous) + PixelKit.fire(PrivacyProPixel.privacyProPurchaseAttempt, frequency: .dailyAndCount) struct SubscriptionSelection: Decodable { let id: String } @@ -288,8 +288,8 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { switch await AppStorePurchaseFlow.completeSubscriptionPurchase(with: purchaseTransactionJWS, subscriptionAppGroup: subscriptionAppGroup) { case .success(let purchaseUpdate): os_log(.info, log: .subscription, "[Purchase] Purchase complete") - PixelKit.fire(PrivacyProPixel.privacyProPurchaseSuccess, frequency: .dailyAndContinuous) - PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActivated, frequency: .justOnce) + PixelKit.fire(PrivacyProPixel.privacyProPurchaseSuccess, frequency: .dailyAndCount) + PixelKit.fire(PrivacyProPixel.privacyProSubscriptionActivated, frequency: .unique) await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: purchaseUpdate) case .failure(let error): switch error { @@ -363,7 +363,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { }, uiActionHandler: { event in switch event { case .activateAddEmailClick: - PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailStart, frequency: .dailyAndContinuous) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseEmailStart, frequency: .dailyAndCount) default: break } @@ -400,14 +400,14 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { case .appTrackingProtection: NotificationCenter.default.post(name: .openAppTrackingProtection, object: self, userInfo: nil) case .vpn: - PixelKit.fire(PrivacyProPixel.privacyProWelcomeVPN, frequency: .justOnce) + PixelKit.fire(PrivacyProPixel.privacyProWelcomeVPN, frequency: .unique) NotificationCenter.default.post(name: .ToggleNetworkProtectionInMainWindow, object: self, userInfo: nil) case .personalInformationRemoval: - PixelKit.fire(PrivacyProPixel.privacyProWelcomePersonalInformationRemoval, frequency: .justOnce) + PixelKit.fire(PrivacyProPixel.privacyProWelcomePersonalInformationRemoval, frequency: .unique) NotificationCenter.default.post(name: .openPersonalInformationRemoval, object: self, userInfo: nil) await WindowControllersManager.shared.showTab(with: .dataBrokerProtection) case .identityTheftRestoration: - PixelKit.fire(PrivacyProPixel.privacyProWelcomeIdentityRestoration, frequency: .justOnce) + PixelKit.fire(PrivacyProPixel.privacyProWelcomeIdentityRestoration, frequency: .unique) await WindowControllersManager.shared.showTab(with: .identityTheftRestoration(.identityTheftRestoration)) } @@ -422,7 +422,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { await StripePurchaseFlow.completeSubscriptionPurchase(subscriptionAppGroup: subscriptionAppGroup) await mainViewController?.dismiss(progressViewController) - PixelKit.fire(PrivacyProPixel.privacyProPurchaseStripeSuccess, frequency: .dailyAndContinuous) + PixelKit.fire(PrivacyProPixel.privacyProPurchaseStripeSuccess, frequency: .dailyAndCount) return [String: String]() // cannot be nil, the web app expect something back before redirecting the user to the final page } @@ -444,12 +444,12 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature { } func subscriptionsAddEmailSuccess(params: Any, original: WKScriptMessage) async -> Encodable? { - PixelKit.fire(PrivacyProPixel.privacyProAddEmailSuccess, frequency: .justOnce) + PixelKit.fire(PrivacyProPixel.privacyProAddEmailSuccess, frequency: .unique) return nil } func subscriptionsWelcomeFaqClicked(params: Any, original: WKScriptMessage) async -> Encodable? { - PixelKit.fire(PrivacyProPixel.privacyProWelcomeFAQClick, frequency: .justOnce) + PixelKit.fire(PrivacyProPixel.privacyProWelcomeFAQClick, frequency: .unique) return nil } @@ -474,7 +474,7 @@ extension MainWindowController { @MainActor func showSomethingWentWrongAlert() { - PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailure, frequency: .dailyAndContinuous) + PixelKit.fire(PrivacyProPixel.privacyProPurchaseFailure, frequency: .dailyAndCount) guard let window else { return } window.show(.somethingWentWrongAlert()) @@ -510,7 +510,7 @@ extension MainWindowController { let result = await AppStoreRestoreFlow.restoreAccountFromPastPurchase(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) switch result { case .success: - PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreSuccess, frequency: .dailyAndContinuous) + PixelKit.fire(PrivacyProPixel.privacyProRestorePurchaseStoreSuccess, frequency: .dailyAndCount) case .failure: break } originalMessage.webView?.reload() diff --git a/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift b/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift index 96341cc2db..50ebf2fc03 100644 --- a/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift +++ b/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift @@ -215,7 +215,7 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { return false } - PixelKit.fire(VPNPrivacyProPixel.vpnBetaStoppedWhenPrivacyProEnabled, frequency: .dailyAndContinuous) + PixelKit.fire(VPNPrivacyProPixel.vpnBetaStoppedWhenPrivacyProEnabled, frequency: .dailyAndCount) defaults.vpnLegacyUserAccessDisabledOnce = true await featureDisabler.disable(keepAuthToken: true, uninstallSystemExtension: false) return true diff --git a/DuckDuckGo/Waitlist/Views/WaitlistThankYouPromptPresenter.swift b/DuckDuckGo/Waitlist/Views/WaitlistThankYouPromptPresenter.swift index 900de13ea8..4c895fcf36 100644 --- a/DuckDuckGo/Waitlist/Views/WaitlistThankYouPromptPresenter.swift +++ b/DuckDuckGo/Waitlist/Views/WaitlistThankYouPromptPresenter.swift @@ -56,7 +56,7 @@ final class WaitlistThankYouPromptPresenter { // Wiring this here since it's mostly useful for rolling out PrivacyPro, and should // go away once PPro is fully rolled out. if DefaultSubscriptionFeatureAvailability().isFeatureAvailable { - PixelKit.fire(PrivacyProPixel.privacyProFeatureEnabled, frequency: .dailyOnly) + PixelKit.fire(PrivacyProPixel.privacyProFeatureEnabled, frequency: .daily) } guard canShowPromptCheck() else { @@ -65,11 +65,11 @@ final class WaitlistThankYouPromptPresenter { if isPIRBetaTester() { saveDidShowPromptCheck() - PixelKit.fire(PrivacyProPixel.privacyProBetaUserThankYouDBP, frequency: .dailyAndContinuous) + PixelKit.fire(PrivacyProPixel.privacyProBetaUserThankYouDBP, frequency: .dailyAndCount) presentPIRThankYouPrompt(in: window) } else if isVPNBetaTester() { saveDidShowPromptCheck() - PixelKit.fire(PrivacyProPixel.privacyProBetaUserThankYouVPN, frequency: .dailyAndContinuous) + PixelKit.fire(PrivacyProPixel.privacyProBetaUserThankYouVPN, frequency: .dailyAndCount) presentVPNThankYouPrompt(in: window) } } diff --git a/DuckDuckGo/Waitlist/Views/WaitlistViewControllerPresenter.swift b/DuckDuckGo/Waitlist/Views/WaitlistViewControllerPresenter.swift index 31ddf12451..efc31d5a1d 100644 --- a/DuckDuckGo/Waitlist/Views/WaitlistViewControllerPresenter.swift +++ b/DuckDuckGo/Waitlist/Views/WaitlistViewControllerPresenter.swift @@ -99,7 +99,7 @@ struct DataBrokerProtectionWaitlistViewControllerPresenter: WaitlistViewControll guard let windowController = WindowControllersManager.shared.lastKeyMainWindowController else { return } - DataBrokerProtectionExternalWaitlistPixels.fire(pixel: GeneralPixel.dataBrokerProtectionWaitlistIntroDisplayed, frequency: .dailyAndContinuous) + DataBrokerProtectionExternalWaitlistPixels.fire(pixel: GeneralPixel.dataBrokerProtectionWaitlistIntroDisplayed, frequency: .dailyAndCount) // This is a hack to get around an issue with the waitlist notification screen showing the wrong state while it animates in, and then // jumping to the correct state as soon as the animation is complete. This works around that problem by providing the correct state up front, diff --git a/DuckDuckGo/Waitlist/Waitlist.swift b/DuckDuckGo/Waitlist/Waitlist.swift index 9102d43b6f..9d648f8691 100644 --- a/DuckDuckGo/Waitlist/Waitlist.swift +++ b/DuckDuckGo/Waitlist/Waitlist.swift @@ -224,7 +224,7 @@ struct NetworkProtectionWaitlist: Waitlist { try await networkProtectionCodeRedemption.redeem(inviteCode) NotificationCenter.default.post(name: .networkProtectionWaitlistAccessChanged, object: nil) sendInviteCodeAvailableNotification { - PixelKit.fire(GeneralPixel.networkProtectionWaitlistNotificationShown, frequency: .dailyAndContinuous) + PixelKit.fire(GeneralPixel.networkProtectionWaitlistNotificationShown, frequency: .dailyAndCount) } completion(nil) } catch { @@ -353,7 +353,7 @@ struct DataBrokerProtectionWaitlist: Waitlist { sendInviteCodeAvailableNotification { DispatchQueue.main.async { - DataBrokerProtectionExternalWaitlistPixels.fire(pixel: GeneralPixel.dataBrokerProtectionWaitlistNotificationShown, frequency: .dailyAndContinuous) + DataBrokerProtectionExternalWaitlistPixels.fire(pixel: GeneralPixel.dataBrokerProtectionWaitlistNotificationShown, frequency: .dailyAndCount) } } } diff --git a/DuckDuckGo/Waitlist/WaitlistTermsAndConditionsActionHandler.swift b/DuckDuckGo/Waitlist/WaitlistTermsAndConditionsActionHandler.swift index 7b3a90edcb..a77d50310c 100644 --- a/DuckDuckGo/Waitlist/WaitlistTermsAndConditionsActionHandler.swift +++ b/DuckDuckGo/Waitlist/WaitlistTermsAndConditionsActionHandler.swift @@ -37,7 +37,7 @@ struct NetworkProtectionWaitlistTermsAndConditionsActionHandler: WaitlistTermsAn var acceptedTermsAndConditions: Bool func didShow() { - PixelKit.fire(GeneralPixel.networkProtectionWaitlistTermsAndConditionsDisplayed, frequency: .dailyAndContinuous) + PixelKit.fire(GeneralPixel.networkProtectionWaitlistTermsAndConditionsDisplayed, frequency: .dailyAndCount) } mutating func didAccept() { @@ -45,7 +45,7 @@ struct NetworkProtectionWaitlistTermsAndConditionsActionHandler: WaitlistTermsAn // Remove delivered NetP notifications in case the user didn't click them. UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [NetworkProtectionWaitlist.notificationIdentifier]) - PixelKit.fire(GeneralPixel.networkProtectionWaitlistTermsAndConditionsAccepted, frequency: .dailyAndContinuous) + PixelKit.fire(GeneralPixel.networkProtectionWaitlistTermsAndConditionsAccepted, frequency: .dailyAndCount) } } @@ -58,14 +58,14 @@ struct DataBrokerProtectionWaitlistTermsAndConditionsActionHandler: WaitlistTerm var acceptedTermsAndConditions: Bool func didShow() { - PixelKit.fire(GeneralPixel.dataBrokerProtectionWaitlistTermsAndConditionsDisplayed, frequency: .dailyAndContinuous) + PixelKit.fire(GeneralPixel.dataBrokerProtectionWaitlistTermsAndConditionsDisplayed, frequency: .dailyAndCount) } mutating func didAccept() { acceptedTermsAndConditions = true // Remove delivered NetP notifications in case the user didn't click them. UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [DataBrokerProtectionWaitlist.notificationIdentifier]) - PixelKit.fire(GeneralPixel.dataBrokerProtectionWaitlistTermsAndConditionsAccepted, frequency: .dailyAndContinuous) + PixelKit.fire(GeneralPixel.dataBrokerProtectionWaitlistTermsAndConditionsAccepted, frequency: .dailyAndCount) } } diff --git a/DuckDuckGo/YoutubePlayer/YoutubeOverlayUserScript.swift b/DuckDuckGo/YoutubePlayer/YoutubeOverlayUserScript.swift index 46cb058dc4..8cdc69cec2 100644 --- a/DuckDuckGo/YoutubePlayer/YoutubeOverlayUserScript.swift +++ b/DuckDuckGo/YoutubePlayer/YoutubeOverlayUserScript.swift @@ -119,11 +119,11 @@ extension YoutubeOverlayUserScript { } // Temporary pixel for first time user uses Duck Player -// if !Pixel.isNewUser { // TODO: reimplement Pixel.isNewUser -// return nil -// } + if !AppDelegate.isNewUser { + return nil + } if pixelName == "play.use" { - PixelKit.fire(GeneralPixel.watchInDuckPlayerInitial, frequency: .justOnce) + PixelKit.fire(GeneralPixel.watchInDuckPlayerInitial, frequency: .unique) } return nil } diff --git a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift index 522bef3306..5e3bf42951 100644 --- a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift +++ b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift @@ -43,7 +43,6 @@ final class DuckDuckGoDBPBackgroundAgentApplication: NSApplication { appVersion: AppVersion.shared.versionNumber, source: "dbpBackgroundAgent", defaultHeaders: [:], - log: .dbpBackgroundAgentPixel, defaults: .standard) { (pixelName: String, headers: [String: String], parameters: [String: String], _, _, onComplete: @escaping (Bool, Error?) -> Void) in let url = URL.pixelUrl(forPixelNamed: pixelName) diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index 67d2baeb1d..c63440c515 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -303,7 +303,6 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { appVersion: AppVersion.shared.versionNumber, source: pixelSource, defaultHeaders: [:], - log: .networkProtectionPixel, defaults: .netP) { (pixelName: String, headers: [String: String], parameters: [String: String], _, _, onComplete: @escaping PixelKit.CompletionBlock) in let url = URL.pixelUrl(forPixelNamed: pixelName) @@ -372,7 +371,7 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { UserDefaults.netP.networkProtectionEntitlementsExpired = false case .invalidEntitlement: UserDefaults.netP.networkProtectionEntitlementsExpired = true - PixelKit.fire(VPNPrivacyProPixel.vpnAccessRevokedDialogShown, frequency: .dailyAndContinuous) + PixelKit.fire(VPNPrivacyProPixel.vpnAccessRevokedDialogShown, frequency: .dailyAndCount) guard let self else { return } Task { diff --git a/LocalPackages/PixelKit/Sources/PixelKit/Extensions/URL+PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKit/Extensions/URL+PixelKit.swift index 7b17318cb4..98bb77c2c4 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/Extensions/URL+PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/Extensions/URL+PixelKit.swift @@ -22,7 +22,7 @@ extension URL { static let pixelBase = ProcessInfo.processInfo.environment["PIXEL_BASE_URL", default: "https://improving.duckduckgo.com"] - public static func pixelUrl(forPixelNamed pixelName: String) -> URL { // TODO: make private or remove URL extension + public static func pixelUrl(forPixelNamed pixelName: String) -> URL { let urlString = "\(Self.pixelBase)/t/\(pixelName)" return URL(string: urlString)! } diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift index 01e848e56c..a2f66ad914 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift @@ -24,21 +24,23 @@ public final class PixelKit { public typealias CompletionBlock = (Bool, Error?) -> Void /// The frequency with which a pixel is sent to our endpoint. - /// public enum Frequency { /// The default frequency for pixels. This fires pixels with the event names as-is. case standard + /// Used in Pixel.fire(...) as unique but without the `_u` requirement in the name + case legacyInitial + /// Sent only once ever. The timestamp for this pixel is stored. Name for pixels of this type must end with `_u`. - case justOnce // TODO: Rename unique + case unique /// Sent once per day. The last timestamp for this pixel is stored and compared to the current date. Pixels of this type will have `_d` appended to their name. - case dailyOnly // TODO: Rename daily + case daily /// Sent once per day with a `_d` suffix, in addition to every time it is called with a `_c` suffix. /// This means a pixel will get sent twice the first time it is called per-day, and subsequent calls that day will only send the `_c` variant. /// This is useful in situations where pixels receive spikes in volume, as the daily pixel can be used to determine how many users are actually affected. - case dailyAndContinuous // TODO: Rename dailyAndCount + case dailyAndCount } public enum Header { @@ -51,7 +53,6 @@ public final class PixelKit { } /// A closure typealias to request sending pixels through the network. - /// public typealias FireRequest = ( _ pixelName: String, _ headers: [String: String], @@ -61,11 +62,11 @@ public final class PixelKit { _ onComplete: @escaping CompletionBlock) -> Void public typealias Event = PixelKitEvent - public static let duckDuckGoMorePrivacyInfo = URL(string: "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/atb/")! - private let defaults: UserDefaults + private let logger = Logger(subsystem: "com.duckduckgo.PixelKit", category: "PixelKit") + private static let defaultDailyPixelCalendar: Calendar = { var calendar = Calendar.current calendar.timeZone = TimeZone(secondsFromGMT: 0)! @@ -85,7 +86,6 @@ public final class PixelKit { public private(set) static var shared: PixelKit? private let appVersion: String private let defaultHeaders: [String: String] - private let log: OSLog private let fireRequest: FireRequest /// Sets up PixelKit for the entire app. @@ -98,14 +98,12 @@ public final class PixelKit { appVersion: String, source: String? = nil, defaultHeaders: [String: String], - log: OSLog, defaults: UserDefaults, fireRequest: @escaping FireRequest) { shared = PixelKit(dryRun: dryRun, appVersion: appVersion, source: source, defaultHeaders: defaultHeaders, - log: log, defaults: defaults, fireRequest: fireRequest) } @@ -122,7 +120,6 @@ public final class PixelKit { appVersion: String, source: String? = nil, defaultHeaders: [String: String], - log: OSLog, dailyPixelCalendar: Calendar? = nil, dateGenerator: @escaping () -> Date = Date.init, defaults: UserDefaults, @@ -132,14 +129,13 @@ public final class PixelKit { self.appVersion = appVersion self.source = source self.defaultHeaders = defaultHeaders - self.log = log self.pixelCalendar = dailyPixelCalendar ?? Self.defaultDailyPixelCalendar self.dateGenerator = dateGenerator self.defaults = defaults self.fireRequest = fireRequest } - // swiftlint:disable:next cyclomatic_complexity + // swiftlint:disable:next cyclomatic_complexity, function_body_length private func fire(pixelNamed pixelName: String, frequency: Frequency, withHeaders headers: [String: String]?, @@ -174,7 +170,14 @@ public final class PixelKit { switch frequency { case .standard: fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, onComplete) - case .justOnce: + case .legacyInitial: + if !pixelHasBeenFiredEver(pixelName) { + fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, onComplete) + updatePixelLastFireDate(pixelName: pixelName) + } else { + printDebugInfo(pixelName: pixelName, parameters: newParams, skipped: true) + } + case .unique: guard pixelName.hasSuffix("_u") else { assertionFailure("Unique pixel: must end with _u") return @@ -185,14 +188,14 @@ public final class PixelKit { } else { printDebugInfo(pixelName: pixelName, parameters: newParams, skipped: true) } - case .dailyOnly: + case .daily: if !pixelHasBeenFiredToday(pixelName) { fireRequestWrapper(pixelName + "_d", headers, newParams, allowedQueryReservedCharacters, true, onComplete) updatePixelLastFireDate(pixelName: pixelName) } else { printDebugInfo(pixelName: pixelName + "_d", parameters: newParams, skipped: true) } - case .dailyAndContinuous: + case .dailyAndCount: if !pixelHasBeenFiredToday(pixelName) { fireRequestWrapper(pixelName + "_d", headers, newParams, allowedQueryReservedCharacters, true, onComplete) updatePixelLastFireDate(pixelName: pixelName) @@ -204,11 +207,10 @@ public final class PixelKit { } } - private func printDebugInfo(pixelName: String, parameters: [String: String], skipped: Bool = false) { // TODO: re-do all logging with Logger -#if DEBUG + private func printDebugInfo(pixelName: String, parameters: [String: String], skipped: Bool = false) { let params = parameters.filter { key, _ in !["test"].contains(key) } - os_log(.debug, log: log, "👾 [%{public}@] %{public}@ %{public}@", skipped ? "SKIPPED" : "FIRED", pixelName.replacingOccurrences(of: "_", with: "."), params) -#endif + let pixelName = pixelName.replacingOccurrences(of: "_", with: ".") + logger.debug("👾 [\(skipped ? "SKIPPED" : "FIRED")] \(pixelName) \(params)") } private func fireRequestWrapper( @@ -256,10 +258,10 @@ public final class PixelKit { let pixelName = prefixedName(for: event) if !dryRun { - if frequency == .dailyOnly, pixelHasBeenFiredToday(pixelName) { + if frequency == .daily, pixelHasBeenFiredToday(pixelName) { onComplete(false, nil) return - } else if frequency == .justOnce, pixelHasBeenFiredEver(pixelName) { + } else if frequency == .unique, pixelHasBeenFiredEver(pixelName) { onComplete(false, nil) return } @@ -376,7 +378,8 @@ public final class PixelKit { } public static func clearFrequencyHistoryForAllPixels() { - //TODO: Implement changing storage approach + //TODO: Implement + assertionFailure("To be implemented") } /// Initially PixelKit was configured only for serving netP so these very specific keys were used, now PixelKit serves the entire app so we need to move away from them. diff --git a/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift index 9017d8774a..f6160daee0 100644 --- a/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift @@ -118,7 +118,6 @@ public extension XCTestCase { appVersion: "1.0.5", source: "test-app", defaultHeaders: [:], - log: .disabled, defaults: userDefaults) { firedPixelName, _, firedParameters, _, _, completion in callbackExecutedExpectation.fulfill() diff --git a/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift b/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift index b9576d6a6a..1651943470 100644 --- a/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift +++ b/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift @@ -63,11 +63,11 @@ final class PixelKitTests: XCTestCase { case .testEvent, .testEventWithoutParameters: return .standard case .uniqueEvent: - return .justOnce + return .unique case .dailyEvent, .dailyEventWithoutParameters: - return .dailyOnly + return .daily case .dailyAndContinuousEvent, .dailyAndContinuousEventWithoutParameters: - return .dailyAndContinuous + return .dailyAndCount } } } @@ -77,9 +77,8 @@ final class PixelKitTests: XCTestCase { func testDryRunWontExecuteCallback() async { let appVersion = "1.0.5" let headers: [String: String] = [:] - let log = OSLog.disabled - let pixelKit = PixelKit(dryRun: true, appVersion: appVersion, defaultHeaders: headers, log: log, dailyPixelCalendar: nil, defaults: userDefaults()) { _, _, _, _, _, _ in + let pixelKit = PixelKit(dryRun: true, appVersion: appVersion, defaultHeaders: headers, dailyPixelCalendar: nil, defaults: userDefaults()) { _, _, _, _, _, _ in XCTFail("This callback should not be executed when doing a dry run") } @@ -93,7 +92,6 @@ final class PixelKitTests: XCTestCase { // Prepare test parameters let appVersion = "1.0.5" let headers = ["a": "2", "b": "3", "c": "2000"] - let log = OSLog(subsystem: "TestSubsystem", category: "TestCategory") let event = TestEvent.testEvent let userDefaults = userDefaults() @@ -105,7 +103,6 @@ final class PixelKitTests: XCTestCase { let pixelKit = PixelKit(dryRun: false, appVersion: appVersion, defaultHeaders: headers, - log: log, dailyPixelCalendar: nil, defaults: userDefaults) { firedPixelName, firedHeaders, parameters, _, _, _ in @@ -139,7 +136,6 @@ final class PixelKitTests: XCTestCase { // Prepare test parameters let appVersion = "1.0.5" let headers = ["a": "2", "b": "3", "c": "2000"] - let log = OSLog(subsystem: "TestSubsystem", category: "TestCategory") let event = TestEvent.dailyEvent let userDefaults = userDefaults() @@ -152,7 +148,6 @@ final class PixelKitTests: XCTestCase { let pixelKit = PixelKit(dryRun: false, appVersion: appVersion, defaultHeaders: headers, - log: log, dailyPixelCalendar: nil, defaults: userDefaults) { firedPixelName, firedHeaders, parameters, _, _, _ in @@ -173,7 +168,7 @@ final class PixelKitTests: XCTestCase { } // Run test - pixelKit.fire(event, frequency: .dailyOnly) + pixelKit.fire(event, frequency: .daily) // Wait for expectations to be fulfilled wait(for: [fireCallbackCalled], timeout: 0.5) @@ -185,7 +180,6 @@ final class PixelKitTests: XCTestCase { // Prepare test parameters let appVersion = "1.0.5" let headers = ["a": "2", "b": "3", "c": "2000"] - let log = OSLog(subsystem: "TestSubsystem", category: "TestCategory") let event = TestEvent.dailyEvent let userDefaults = userDefaults() @@ -200,7 +194,6 @@ final class PixelKitTests: XCTestCase { let pixelKit = PixelKit(dryRun: false, appVersion: appVersion, defaultHeaders: headers, - log: log, dailyPixelCalendar: nil, defaults: userDefaults) { firedPixelName, firedHeaders, parameters, _, _, _ in @@ -221,8 +214,8 @@ final class PixelKitTests: XCTestCase { } // Run test - pixelKit.fire(event, frequency: .dailyOnly) - pixelKit.fire(event, frequency: .dailyOnly) + pixelKit.fire(event, frequency: .daily) + pixelKit.fire(event, frequency: .daily) // Wait for expectations to be fulfilled wait(for: [fireCallbackCalled], timeout: 0.5) @@ -233,7 +226,6 @@ final class PixelKitTests: XCTestCase { // Prepare test parameters let appVersion = "1.0.5" let headers = ["a": "2", "b": "3", "c": "2000"] - let log = OSLog(subsystem: "TestSubsystem", category: "TestCategory") let event = TestEvent.dailyEvent let userDefaults = userDefaults() @@ -248,7 +240,6 @@ final class PixelKitTests: XCTestCase { let pixelKit = PixelKit(dryRun: false, appVersion: appVersion, defaultHeaders: headers, - log: log, dailyPixelCalendar: nil, dateGenerator: timeMachine.now, defaults: userDefaults) { _, _, _, _, _, _ in @@ -256,19 +247,19 @@ final class PixelKitTests: XCTestCase { } // Run test - pixelKit.fire(event, frequency: .dailyOnly) // Fired + pixelKit.fire(event, frequency: .daily) // Fired timeMachine.travel(by: 60 * 60 * 2) - pixelKit.fire(event, frequency: .dailyOnly) // Skipped (2 hours since last fire) + pixelKit.fire(event, frequency: .daily) // Skipped (2 hours since last fire) timeMachine.travel(by: 60 * 60 * 24 + 1000) - pixelKit.fire(event, frequency: .dailyOnly) // Fired (24 hours + 1000 seconds since last fire) + pixelKit.fire(event, frequency: .daily) // Fired (24 hours + 1000 seconds since last fire) timeMachine.travel(by: 60 * 60 * 10) - pixelKit.fire(event, frequency: .dailyOnly) // Skipped (10 hours since last fire) + pixelKit.fire(event, frequency: .daily) // Skipped (10 hours since last fire) timeMachine.travel(by: 60 * 60 * 14) - pixelKit.fire(event, frequency: .dailyOnly) // Fired (24 hours since last fire) + pixelKit.fire(event, frequency: .daily) // Fired (24 hours since last fire) // Wait for expectations to be fulfilled wait(for: [fireCallbackCalled], timeout: 0.5) @@ -279,7 +270,6 @@ final class PixelKitTests: XCTestCase { // Prepare test parameters let appVersion = "1.0.5" let headers = ["a": "2", "b": "3", "c": "2000"] - let log = OSLog(subsystem: "TestSubsystem", category: "TestCategory") let event = TestEvent.uniqueEvent let userDefaults = userDefaults() @@ -293,7 +283,6 @@ final class PixelKitTests: XCTestCase { let pixelKit = PixelKit(dryRun: false, appVersion: appVersion, defaultHeaders: headers, - log: log, dailyPixelCalendar: nil, dateGenerator: timeMachine.now, defaults: userDefaults) { _, _, _, _, _, _ in @@ -301,19 +290,19 @@ final class PixelKitTests: XCTestCase { } // Run test - pixelKit.fire(event, frequency: .justOnce) // Fired + pixelKit.fire(event, frequency: .unique) // Fired timeMachine.travel(by: 60 * 60 * 2) - pixelKit.fire(event, frequency: .justOnce) // Skipped (already fired) + pixelKit.fire(event, frequency: .unique) // Skipped (already fired) timeMachine.travel(by: 60 * 60 * 24 + 1000) - pixelKit.fire(event, frequency: .justOnce) // Skipped (already fired) + pixelKit.fire(event, frequency: .unique) // Skipped (already fired) timeMachine.travel(by: 60 * 60 * 10) - pixelKit.fire(event, frequency: .justOnce) // Skipped (already fired) + pixelKit.fire(event, frequency: .unique) // Skipped (already fired) timeMachine.travel(by: 60 * 60 * 14) - pixelKit.fire(event, frequency: .justOnce) // Skipped (already fired) + pixelKit.fire(event, frequency: .unique) // Skipped (already fired) // Wait for expectations to be fulfilled wait(for: [fireCallbackCalled], timeout: 0.5) diff --git a/UnitTests/PrivacyReferenceTests/BrokenSiteReportingReferenceTests.swift b/UnitTests/PrivacyReferenceTests/BrokenSiteReportingReferenceTests.swift index bc52a33041..1d38801f44 100644 --- a/UnitTests/PrivacyReferenceTests/BrokenSiteReportingReferenceTests.swift +++ b/UnitTests/PrivacyReferenceTests/BrokenSiteReportingReferenceTests.swift @@ -51,7 +51,7 @@ final class BrokenSiteReportingReferenceTests: XCTestCase { APIRequest.Headers.setUserAgent("") var params = parameters params["test"] = "1" - let configuration = APIRequest.Configuration(url: URL.pixelUrl(forPixelNamed: Pixel.Event.brokenSiteReport.name), + let configuration = APIRequest.Configuration(url: URL.pixelUrl(forPixelNamed: GeneralPixel.brokenSiteReport.name), queryParameters: params, allowedQueryReservedCharacters: BrokenSiteReport.allowedQueryReservedCharacters) return configuration.request diff --git a/UnitTests/Statistics/CBRCompileTimeReporterTests.swift b/UnitTests/Statistics/CBRCompileTimeReporterTests.swift index ec435d6dae..62a2f2e77c 100644 --- a/UnitTests/Statistics/CBRCompileTimeReporterTests.swift +++ b/UnitTests/Statistics/CBRCompileTimeReporterTests.swift @@ -19,7 +19,8 @@ import XCTest import OHHTTPStubs import OHHTTPStubsSwift -//import PixelKit +import PixelKit + @testable import DuckDuckGo_Privacy_Browser class CBRCompileTimeReporterTests: XCTestCase { @@ -30,13 +31,15 @@ class CBRCompileTimeReporterTests: XCTestCase { var time = CACurrentMediaTime() override func setUp() { -// PixelKit.setUp() //TODO: errors? + PixelKit.setUp(appVersion: "", + defaultHeaders: [:], + defaults: UserDefaults()) { _, _, _, _, _, _ in } UserDefaultsWrapper.clearAll() } override func tearDown() { HTTPStubs.removeAllStubs() -// PixelKit.tearDown() + PixelKit.tearDown() super.tearDown() } diff --git a/UnitTests/Statistics/PixelArgumentsTests.swift b/UnitTests/Statistics/Legacy/PixelArgumentsTests.swift similarity index 79% rename from UnitTests/Statistics/PixelArgumentsTests.swift rename to UnitTests/Statistics/Legacy/PixelArgumentsTests.swift index 8347a25732..f5e812e079 100644 --- a/UnitTests/Statistics/PixelArgumentsTests.swift +++ b/UnitTests/Statistics/Legacy/PixelArgumentsTests.swift @@ -50,7 +50,7 @@ class PixelArgumentsTests: XCTestCase { // MARK: AccessPoint func testWhenInitWithButtonThenAccessPointIsButton() { - let ap = Pixel.Event.AccessPoint(sender: NSButton(), default: .tabMenu) + let ap = GeneralPixel.AccessPoint(sender: NSButton(), default: .tabMenu) XCTAssertEqual(ap, .button) } @@ -68,7 +68,7 @@ class PixelArgumentsTests: XCTestCase { func testWhenInitWithMainMenuItemThenAccessPointIsMenu() { let mainMenu = makeMenu() - let ap = Pixel.Event.AccessPoint(sender: mainMenu.item, default: .tabMenu) { $0 === mainMenu.menu } + let ap = GeneralPixel.AccessPoint(sender: mainMenu.item, default: .tabMenu) { $0 === mainMenu.menu } XCTAssertEqual(ap, .mainMenu) } @@ -79,7 +79,7 @@ class PixelArgumentsTests: XCTestCase { context: nil, characters: "x", charactersIgnoringModifiers: "", isARepeat: false, keyCode: UInt16(kVK_ANSI_X))! NSApp.setValue(event, forKey: "currentEvent") - let ap = Pixel.Event.AccessPoint(sender: mainMenu.item, default: .tabMenu) { $0 === mainMenu.menu } + let ap = GeneralPixel.AccessPoint(sender: mainMenu.item, default: .tabMenu) { $0 === mainMenu.menu } XCTAssertEqual(ap, .hotKey) } @@ -90,7 +90,7 @@ class PixelArgumentsTests: XCTestCase { context: nil, characters: "\n", charactersIgnoringModifiers: "", isARepeat: false, keyCode: UInt16(kVK_Return))! NSApp.setValue(event, forKey: "currentEvent") - let ap = Pixel.Event.AccessPoint(sender: mainMenu.item, default: .tabMenu) { $0 === mainMenu.menu } + let ap = GeneralPixel.AccessPoint(sender: mainMenu.item, default: .tabMenu) { $0 === mainMenu.menu } XCTAssertEqual(ap, .mainMenu) } @@ -101,30 +101,30 @@ class PixelArgumentsTests: XCTestCase { context: nil, characters: " ", charactersIgnoringModifiers: "", isARepeat: false, keyCode: UInt16(kVK_Space))! NSApp.setValue(event, forKey: "currentEvent") - let ap = Pixel.Event.AccessPoint(sender: mainMenu.item, default: .tabMenu) { $0 === mainMenu.menu } + let ap = GeneralPixel.AccessPoint(sender: mainMenu.item, default: .tabMenu) { $0 === mainMenu.menu } XCTAssertEqual(ap, .mainMenu) } func testWhenInitWithTabMenuItemThenAccessPointIsMenu() { let mainMenu = makeMenu() let tabMenu = makeMenu() - let ap = Pixel.Event.AccessPoint(sender: tabMenu.item, default: .tabMenu) { $0 === mainMenu.menu } + let ap = GeneralPixel.AccessPoint(sender: tabMenu.item, default: .tabMenu) { $0 === mainMenu.menu } XCTAssertEqual(ap, .tabMenu) } // MARK: Repetition func testWhenInitFirstTimeThenRepetitionIsInitial() { - let rep1 = Pixel.Event.Repetition(key: "test", store: pixelDataStore) - let rep2 = Pixel.Event.Repetition(key: "test2", store: pixelDataStore) + let rep1 = GeneralPixel.Repetition(key: "test", store: pixelDataStore) + let rep2 = GeneralPixel.Repetition(key: "test2", store: pixelDataStore) XCTAssertEqual(rep1, .initial) XCTAssertEqual(rep2, .initial) } func testWhenInitSecondTimeThenRepetitionIsRepetitive() { - _=Pixel.Event.Repetition(key: "test", store: pixelDataStore) - let rep1 = Pixel.Event.Repetition(key: "test", store: pixelDataStore) - let rep2 = Pixel.Event.Repetition(key: "test2", store: pixelDataStore) + _=GeneralPixel.Repetition(key: "test", store: pixelDataStore) + let rep1 = GeneralPixel.Repetition(key: "test", store: pixelDataStore) + let rep2 = GeneralPixel.Repetition(key: "test2", store: pixelDataStore) XCTAssertEqual(rep1, .repetitive) XCTAssertEqual(rep2, .initial) } @@ -135,10 +135,10 @@ class PixelArgumentsTests: XCTestCase { let tomorrow2 = now.addingTimeInterval(3600 * 24 + 1) let afterTomorrow = tomorrow.addingTimeInterval(3600 * 24) - _=Pixel.Event.Repetition(key: "test", store: pixelDataStore, now: now) - let rep1 = Pixel.Event.Repetition(key: "test", store: pixelDataStore, now: tomorrow) - let rep2 = Pixel.Event.Repetition(key: "test", store: pixelDataStore, now: tomorrow2) - let rep3 = Pixel.Event.Repetition(key: "test", store: pixelDataStore, now: afterTomorrow) + _=GeneralPixel.Repetition(key: "test", store: pixelDataStore, now: now) + let rep1 = GeneralPixel.Repetition(key: "test", store: pixelDataStore, now: tomorrow) + let rep2 = GeneralPixel.Repetition(key: "test", store: pixelDataStore, now: tomorrow2) + let rep3 = GeneralPixel.Repetition(key: "test", store: pixelDataStore, now: afterTomorrow) XCTAssertEqual(rep1, .dailyFirst) XCTAssertEqual(rep2, .repetitive) diff --git a/UnitTests/Statistics/PixelEventTests.swift b/UnitTests/Statistics/Legacy/PixelEventTests.swift similarity index 100% rename from UnitTests/Statistics/PixelEventTests.swift rename to UnitTests/Statistics/Legacy/PixelEventTests.swift diff --git a/UnitTests/Statistics/PixelStoreTests.swift b/UnitTests/Statistics/Legacy/PixelStoreTests.swift similarity index 100% rename from UnitTests/Statistics/PixelStoreTests.swift rename to UnitTests/Statistics/Legacy/PixelStoreTests.swift diff --git a/UnitTests/Statistics/PixelTests.swift b/UnitTests/Statistics/Legacy/PixelTests.swift similarity index 100% rename from UnitTests/Statistics/PixelTests.swift rename to UnitTests/Statistics/Legacy/PixelTests.swift diff --git a/UnitTests/Statistics/PixelExperiment/PixelExperimentTests.swift b/UnitTests/Statistics/PixelExperiment/PixelExperimentTests.swift index cb3953e760..0d79838480 100644 --- a/UnitTests/Statistics/PixelExperiment/PixelExperimentTests.swift +++ b/UnitTests/Statistics/PixelExperiment/PixelExperimentTests.swift @@ -17,6 +17,8 @@ // import XCTest +import PixelKit +import os.log @testable import DuckDuckGo_Privacy_Browser class PixelExperimentTests: XCTestCase { @@ -55,13 +57,16 @@ class PixelExperimentTests: XCTestCase { override func tearDown() { logic.cleanup() - Pixel.tearDown() + PixelKit.tearDown() _store = nil } func testWhenNotInstalledThenCohortIsNill() { XCTAssertNil(logic.cohort) - Pixel.setUp { _ in + + PixelKit.setUp(appVersion: "", + defaultHeaders: [:], + defaults: UserDefaults()) { _, _, _, _, _, _ in XCTFail("shouldn‘t fire pixels") } @@ -71,14 +76,18 @@ class PixelExperimentTests: XCTestCase { } func testWhenNoCohort_NoEnrollmentPixelFired() { - Pixel.firstLaunchDate = now - PixelExperiment.install() + DispatchQueue.main.async { + AppDelegate.firstLaunchDate = self.now + PixelExperiment.install() - Pixel.setUp(store: self.store) { _ in - XCTFail("shouldn‘t fire pixels") - } + PixelKit.setUp(appVersion: "", + defaultHeaders: [:], + defaults: UserDefaults()) { _, _, _, _, _, _ in + XCTFail("shouldn‘t fire pixels") + } - PixelExperiment.fireEnrollmentPixel() + PixelExperiment.fireEnrollmentPixel() + } } // See git history for fireFirstSerpPixel and fireDay21To27SerpPixel tests diff --git a/UnitTests/WebsiteBreakageReport/WebsiteBreakageReportTests.swift b/UnitTests/WebsiteBreakageReport/WebsiteBreakageReportTests.swift index c5c389feac..1103a8f2eb 100644 --- a/UnitTests/WebsiteBreakageReport/WebsiteBreakageReportTests.swift +++ b/UnitTests/WebsiteBreakageReport/WebsiteBreakageReportTests.swift @@ -129,7 +129,7 @@ class WebsiteBreakageReportTests: XCTestCase { APIRequest.Headers.setUserAgent("") var params = parameters params["test"] = "1" - let configuration = APIRequest.Configuration(url: URL.pixelUrl(forPixelNamed: Pixel.Event.brokenSiteReport.name), + let configuration = APIRequest.Configuration(url: URL.pixelUrl(forPixelNamed: GeneralPixel.brokenSiteReport.name), queryParameters: params, allowedQueryReservedCharacters: BrokenSiteReport.allowedQueryReservedCharacters) return configuration.request From c2b645309f5a368e738338404818f4e7a22a52b2 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 5 Apr 2024 12:54:17 +0100 Subject: [PATCH 05/31] Unit tests fixed and PixelExperiment removed --- DuckDuckGo.xcodeproj/project.pbxproj | 24 +- DuckDuckGo/Application/AppDelegate.swift | 6 +- .../Statistics/ATB/LocalStatisticsStore.swift | 2 +- .../Statistics/ATB/StatisticsLoader.swift | 4 +- .../Experiment/PixelExperiment.swift | 322 +++++++++--------- .../YoutubeOverlayUserScript.swift | 2 +- IntegrationTests/Tab/AddressBarTests.swift | 4 +- .../PixelKit/Sources/PixelKit/PixelKit.swift | 18 +- .../XCTestCase+PixelKit.swift | 13 +- .../ATB/StatisticsLoaderTests.swift | 10 + .../CBRCompileTimeReporterTests.swift | 13 +- .../PixelExperimentTests.swift | 176 +++++----- 12 files changed, 300 insertions(+), 294 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 32fa6ba33c..df3fb5f59d 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -3284,15 +3284,15 @@ F18826842BBEE31700D9AC4F /* PixelKit+Assertion.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */; }; F18826852BBEE31700D9AC4F /* PixelKit+Assertion.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */; }; F18826862BBEE31700D9AC4F /* PixelKit+Assertion.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */; }; - F18826872BBF01B600D9AC4F /* PixelDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */; }; - F18826882BBF01B600D9AC4F /* PixelDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */; }; - F18826892BBF01B800D9AC4F /* PixelDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */; }; - F188268A2BBF01BE00D9AC4F /* PixelDataRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */; }; - F188268B2BBF01BE00D9AC4F /* PixelDataRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */; }; - F188268C2BBF01C000D9AC4F /* PixelDataRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */; }; F188268D2BBF01C300D9AC4F /* PixelDataModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44062616B30600DD1EC2 /* PixelDataModel.xcdatamodeld */; }; F188268E2BBF01C400D9AC4F /* PixelDataModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44062616B30600DD1EC2 /* PixelDataModel.xcdatamodeld */; }; F188268F2BBF01C500D9AC4F /* PixelDataModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44062616B30600DD1EC2 /* PixelDataModel.xcdatamodeld */; }; + F18826902BC0105800D9AC4F /* PixelDataRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */; }; + F18826912BC0105800D9AC4F /* PixelDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */; }; + F18826922BC0105900D9AC4F /* PixelDataRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */; }; + F18826932BC0105900D9AC4F /* PixelDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */; }; + F18826942BC0105A00D9AC4F /* PixelDataRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */; }; + F18826952BC0105A00D9AC4F /* PixelDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */; }; F1B33DF22BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */; }; F1B33DF32BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */; }; F1B33DF42BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */; }; @@ -10322,7 +10322,6 @@ B6CC26692BAD959500F53F8D /* DownloadProgress.swift in Sources */, 3706FAE8293F65D500E42796 /* RecentlyClosedTab.swift in Sources */, 1D36E65C298ACD2900AA485D /* AppIconChanger.swift in Sources */, - F188268B2BBF01BE00D9AC4F /* PixelDataRecord.swift in Sources */, 3706FAE9293F65D500E42796 /* PDFSearchTextMenuItemHandler.swift in Sources */, 3706FAEB293F65D500E42796 /* HistoryMenu.swift in Sources */, 3706FAEC293F65D500E42796 /* ContentScopeFeatureFlagging.swift in Sources */, @@ -10539,6 +10538,8 @@ 37197EA42942441D00394917 /* NewWindowPolicy.swift in Sources */, 3706FB84293F65D500E42796 /* NSWindowExtension.swift in Sources */, 3706FB85293F65D500E42796 /* AddBookmarkPopover.swift in Sources */, + F18826922BC0105900D9AC4F /* PixelDataRecord.swift in Sources */, + F18826932BC0105900D9AC4F /* PixelDataStore.swift in Sources */, 7BBD45B22A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift in Sources */, 3706FB87293F65D500E42796 /* ProcessExtension.swift in Sources */, F18826812BBEB58100D9AC4F /* PrivacyProPixel.swift in Sources */, @@ -10665,7 +10666,6 @@ 3706FBDD293F65D500E42796 /* PaddedImageButton.swift in Sources */, 37CEFCAA2A6737A2001EF741 /* CredentialsCleanupErrorHandling.swift in Sources */, 3706FBDE293F65D500E42796 /* EncryptionKeyStoring.swift in Sources */, - F18826882BBF01B600D9AC4F /* PixelDataStore.swift in Sources */, 4B4D60E32A0C883A00BCD287 /* AppMain.swift in Sources */, 37197EA12942441700394917 /* Tab+UIDelegate.swift in Sources */, 56D6A3D729DB2BAB0055215A /* ContinueSetUpView.swift in Sources */, @@ -11433,7 +11433,6 @@ 4B9579552AC7AE700062CA31 /* Logging.swift in Sources */, 4B9579562AC7AE700062CA31 /* CrashReportPromptPresenter.swift in Sources */, 1DDC84F92B83558F00670238 /* PreferencesPrivateSearchView.swift in Sources */, - F18826892BBF01B800D9AC4F /* PixelDataStore.swift in Sources */, B6B4D1CD2B0C8C9200C26286 /* FirefoxCompatibilityPreferences.swift in Sources */, 9FA173ED2B7B232200EE4E6E /* AddEditBookmarkDialogView.swift in Sources */, 4B9579572AC7AE700062CA31 /* BWCredential.swift in Sources */, @@ -11676,6 +11675,8 @@ 4B957A272AC7AE700062CA31 /* PrivacyDashboardPopover.swift in Sources */, 4B957A282AC7AE700062CA31 /* TestsClosureNavigationResponder.swift in Sources */, 4B957A292AC7AE700062CA31 /* RootView.swift in Sources */, + F18826942BC0105A00D9AC4F /* PixelDataRecord.swift in Sources */, + F18826952BC0105A00D9AC4F /* PixelDataStore.swift in Sources */, 4B37EE7C2B4CFF8000A89A61 /* HomePageRemoteMessagingRequest.swift in Sources */, 4B957A2A2AC7AE700062CA31 /* AddressBarTextField.swift in Sources */, 4B957A2B2AC7AE700062CA31 /* FocusRingView.swift in Sources */, @@ -11841,7 +11842,6 @@ B6F9BDE62B45CD1900677B33 /* ModalView.swift in Sources */, 4B957AB32AC7AE700062CA31 /* NetworkProtectionControllerErrorStore.swift in Sources */, 4B957AB42AC7AE700062CA31 /* DataImport.swift in Sources */, - F188268C2BBF01C000D9AC4F /* PixelDataRecord.swift in Sources */, 4B957AB52AC7AE700062CA31 /* NetworkProtectionDebugMenu.swift in Sources */, 4B957AB62AC7AE700062CA31 /* FireproofDomains.xcdatamodeld in Sources */, 3158B14F2B0BF74F00AF130C /* DataBrokerProtectionManager.swift in Sources */, @@ -12310,7 +12310,6 @@ AAC30A26268DFEE200D2D9CD /* CrashReporter.swift in Sources */, B60D64492AAF1B7C00B26F50 /* AddressBarTextSelectionNavigation.swift in Sources */, 3184AC6D288F29D800C35E4B /* BadgeNotificationAnimationModel.swift in Sources */, - F18826872BBF01B600D9AC4F /* PixelDataStore.swift in Sources */, 857FFEC027D239DC00415E7A /* HyperLink.swift in Sources */, B65E5DAF2B74DE6D00480415 /* TrackerNetwork.swift in Sources */, 37445F992A1566420029F789 /* SyncDataProviders.swift in Sources */, @@ -12366,6 +12365,8 @@ 858A798326A8B75F00A75A42 /* CopyHandler.swift in Sources */, 1E7E2E9029029A2A00C01B54 /* ContentBlockingRulesUpdateObserver.swift in Sources */, 4B8AC93926B48A5100879451 /* FirefoxLoginReader.swift in Sources */, + F18826902BC0105800D9AC4F /* PixelDataRecord.swift in Sources */, + F18826912BC0105800D9AC4F /* PixelDataStore.swift in Sources */, B69B503E2726A12500758A2B /* AtbParser.swift in Sources */, 37F19A6528E1B3FB00740DC6 /* PreferencesDuckPlayerView.swift in Sources */, 4B92929E26670D2A00AD2C21 /* BookmarkSidebarTreeController.swift in Sources */, @@ -12918,7 +12919,6 @@ 31F28C5328C8EECA00119F70 /* DuckURLSchemeHandler.swift in Sources */, AA13DCB4271480B0006D48D3 /* FirePopoverViewModel.swift in Sources */, 1DDC84F72B83558F00670238 /* PreferencesPrivateSearchView.swift in Sources */, - F188268A2BBF01BE00D9AC4F /* PixelDataRecord.swift in Sources */, 1D43EB38292B636E0065E5D6 /* BWCommand.swift in Sources */, F41D174125CB131900472416 /* NSColorExtension.swift in Sources */, AAC5E4F625D6BF2C007F5990 /* AddressBarButtonsViewController.swift in Sources */, diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index f218876e5c..3644492b69 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -231,9 +231,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate { _ = RecentlyClosedCoordinator.shared // Clean up previous experiment - if PixelExperiment.allocatedCohortDoesNotMatchCurrentCohorts { - PixelExperiment.cleanup() - } +// if PixelExperiment.allocatedCohortDoesNotMatchCurrentCohorts { // Re-implement https://app.asana.com/0/0/1207002879349166/f +// PixelExperiment.cleanup() +// } if LocalStatisticsStore().atb == nil { AppDelegate.firstLaunchDate = Date() diff --git a/DuckDuckGo/Statistics/ATB/LocalStatisticsStore.swift b/DuckDuckGo/Statistics/ATB/LocalStatisticsStore.swift index 82e9c241b3..4885a848c6 100644 --- a/DuckDuckGo/Statistics/ATB/LocalStatisticsStore.swift +++ b/DuckDuckGo/Statistics/ATB/LocalStatisticsStore.swift @@ -79,7 +79,7 @@ final class LocalStatisticsStore: StatisticsStore { private let pixelDataStore: PixelDataStore - init(pixelDataStore: PixelDataStore = LocalPixelDataStore.shared) { // TODO: not used? investigate + init(pixelDataStore: PixelDataStore = LocalPixelDataStore.shared) { self.pixelDataStore = pixelDataStore var legacyStatisticsStore = LegacyStatisticsStore() diff --git a/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift b/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift index cc1699d615..cb2160e4fc 100644 --- a/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift +++ b/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift @@ -219,8 +219,8 @@ final class StatisticsLoader { DispatchQueue.global().asyncAfter(deadline: .now() + randomDelay) { PixelKit.fire(GeneralPixel.dailyOsVersionCounter, - frequency: .daily, - includeAppVersionParameter: false) + frequency: .daily, + includeAppVersionParameter: false) } } diff --git a/DuckDuckGo/Statistics/Experiment/PixelExperiment.swift b/DuckDuckGo/Statistics/Experiment/PixelExperiment.swift index 601a573799..4e2fa07614 100644 --- a/DuckDuckGo/Statistics/Experiment/PixelExperiment.swift +++ b/DuckDuckGo/Statistics/Experiment/PixelExperiment.swift @@ -1,162 +1,162 @@ +//// +//// PixelExperiment.swift +//// +//// Copyright © 2023 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. +//// // -// PixelExperiment.swift -// -// Copyright © 2023 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 -import PixelKit - -enum PixelExperiment: String, CaseIterable { - - static var logic = PixelExperimentLogic() - - /// When `cohort` is accessed for the first time after the experiment is installed with `install()`, - /// allocate and return a cohort. Subsequently, return the same cohort. - static var cohort: PixelExperiment? { - logic.cohort - } - - static var isExperimentInstalled: Bool { - return logic.isInstalled - } - - static var allocatedCohortDoesNotMatchCurrentCohorts: Bool { - guard let allocatedCohort = logic.allocatedCohort else { return false } - if PixelExperiment(rawValue: allocatedCohort) == nil { - return true - } - return false - } - - /// Enables this experiment for new users when called from the new installation path. - static func install() { - logic.install() - } - - static func cleanup() { - logic.cleanup() - } - - // These are the variants. Rename or add/remove them as needed. If you change the string value - // remember to keep it clear for privacy triage. - case control -} - -/// These functions contain the business logic for determining if the pixel should be fired or not. -extension PixelExperiment { - - static func fireEnrollmentPixel() { - logic.fireEnrollmentPixel() - } - - static func fireFirstSerpPixel() { - logic.fireFirstSerpPixel() - } - - static func fireDay21To27SerpPixel() { - logic.fireDay21To27SerpPixel() - } - -} - -final internal class PixelExperimentLogic { - - private let now: () -> Date - - var cohort: PixelExperiment? { - guard isInstalled else { return nil } - - if let allocatedCohort, - // if the stored cohort doesn't match, allocate a new one - let cohort = PixelExperiment(rawValue: allocatedCohort) { - return cohort - } - - // For now, just use equal distribution of all cohorts. - let cohort = PixelExperiment.allCases.randomElement()! - allocatedCohort = cohort.rawValue - enrollmentDate = now() - fireEnrollmentPixel() - return cohort - } - - @UserDefaultsWrapper(key: .pixelExperimentInstalled, defaultValue: false) - var isInstalled: Bool - - @UserDefaultsWrapper(key: .pixelExperimentCohort, defaultValue: nil) - var allocatedCohort: String? - - @UserDefaultsWrapper(key: .pixelExperimentEnrollmentDate, defaultValue: nil) - private var enrollmentDate: Date? - - private var daysSinceEnrollment: Int { - guard let enrollmentDate else { return 0 } - let diff = now().timeIntervalSince1970 - enrollmentDate.timeIntervalSince1970 - let days = Int(diff / 60 / 60 / 24) - return days - } - - @UserDefaultsWrapper(key: .pixelExperimentFiredPixels, defaultValue: []) - private var firedPixelsStorage: [String] - - private var firedPixels: Set { - get { - Set(firedPixelsStorage) - } - set { - firedPixelsStorage = Array(newValue) - } - } - - init(now: @escaping () -> Date = Date.init) { - self.now = now - } - - func install() { - isInstalled = true - } - - // You'll need additional pixels for your experiment. Pass the cohort as a parameter. - func fireEnrollmentPixel() { - // You'll probably need this at least. - } - - // Often used - func fireFirstSerpPixel() { - guard allocatedCohort != nil, let cohort else { return } - PixelKit.fire(GeneralPixel.serpInitial(cohort: cohort.rawValue), frequency: .unique, includeAppVersionParameter: false) - } - - // Often used for retention experiments - func fireDay21To27SerpPixel() { - guard allocatedCohort != nil, let cohort else { return } - - DispatchQueue.main.async { - let now = self.now() - if now >= AppDelegate.firstLaunchDate.adding(.days(21)) && now <= AppDelegate.firstLaunchDate.adding(.days(27)) { - PixelKit.fire(GeneralPixel.serpDay21to27(cohort: cohort.rawValue), frequency: .unique, includeAppVersionParameter: false) - } - } - } - - func cleanup() { - isInstalled = false - allocatedCohort = nil - enrollmentDate = nil - firedPixelsStorage = [] - } - -} +//import Foundation +//import PixelKit +// +//enum PixelExperiment: String, CaseIterable { +// +// static var logic = PixelExperimentLogic() +// +// /// When `cohort` is accessed for the first time after the experiment is installed with `install()`, +// /// allocate and return a cohort. Subsequently, return the same cohort. +// static var cohort: PixelExperiment? { +// logic.cohort +// } +// +// static var isExperimentInstalled: Bool { +// return logic.isInstalled +// } +// +// static var allocatedCohortDoesNotMatchCurrentCohorts: Bool { +// guard let allocatedCohort = logic.allocatedCohort else { return false } +// if PixelExperiment(rawValue: allocatedCohort) == nil { +// return true +// } +// return false +// } +// +// /// Enables this experiment for new users when called from the new installation path. +// static func install() { +// logic.install() +// } +// +// static func cleanup() { +// logic.cleanup() +// } +// +// // These are the variants. Rename or add/remove them as needed. If you change the string value +// // remember to keep it clear for privacy triage. +// case control +//} +// +//// These functions contain the business logic for determining if the pixel should be fired or not. +//extension PixelExperiment { +// +// static func fireEnrollmentPixel() { +// logic.fireEnrollmentPixel() +// } +// +// static func fireFirstSerpPixel() { +// logic.fireFirstSerpPixel() +// } +// +// static func fireDay21To27SerpPixel() { +// logic.fireDay21To27SerpPixel() +// } +// +//} +// +//final internal class PixelExperimentLogic { +// +// private let now: () -> Date +// +// var cohort: PixelExperiment? { +// guard isInstalled else { return nil } +// +// if let allocatedCohort, +// // if the stored cohort doesn't match, allocate a new one +// let cohort = PixelExperiment(rawValue: allocatedCohort) { +// return cohort +// } +// +// // For now, just use equal distribution of all cohorts. +// let cohort = PixelExperiment.allCases.randomElement()! +// allocatedCohort = cohort.rawValue +// enrollmentDate = now() +// fireEnrollmentPixel() +// return cohort +// } +// +// @UserDefaultsWrapper(key: .pixelExperimentInstalled, defaultValue: false) +// var isInstalled: Bool +// +// @UserDefaultsWrapper(key: .pixelExperimentCohort, defaultValue: nil) +// var allocatedCohort: String? +// +// @UserDefaultsWrapper(key: .pixelExperimentEnrollmentDate, defaultValue: nil) +// private var enrollmentDate: Date? +// +// private var daysSinceEnrollment: Int { +// guard let enrollmentDate else { return 0 } +// let diff = now().timeIntervalSince1970 - enrollmentDate.timeIntervalSince1970 +// let days = Int(diff / 60 / 60 / 24) +// return days +// } +// +// @UserDefaultsWrapper(key: .pixelExperimentFiredPixels, defaultValue: []) +// private var firedPixelsStorage: [String] +// +// private var firedPixels: Set { +// get { +// Set(firedPixelsStorage) +// } +// set { +// firedPixelsStorage = Array(newValue) +// } +// } +// +// init(now: @escaping () -> Date = Date.init) { +// self.now = now +// } +// +// func install() { +// isInstalled = true +// } +// +// // You'll need additional pixels for your experiment. Pass the cohort as a parameter. +// func fireEnrollmentPixel() { +// // You'll probably need this at least. +// } +// +// // Often used +// func fireFirstSerpPixel() { +// guard allocatedCohort != nil, let cohort else { return } +// PixelKit.fire(GeneralPixel.serpInitial(cohort: cohort.rawValue), frequency: .legacyInitial, includeAppVersionParameter: false) +// } +// +// // Often used for retention experiments +// func fireDay21To27SerpPixel() { +// guard allocatedCohort != nil, let cohort else { return } +// +// DispatchQueue.main.async { +// let now = self.now() +// if now >= AppDelegate.firstLaunchDate.adding(.days(21)) && now <= AppDelegate.firstLaunchDate.adding(.days(27)) { +// PixelKit.fire(GeneralPixel.serpDay21to27(cohort: cohort.rawValue), frequency: .legacyInitial, includeAppVersionParameter: false) +// } +// } +// } +// +// func cleanup() { +// isInstalled = false +// allocatedCohort = nil +// enrollmentDate = nil +// firedPixelsStorage = [] +// } +// +//} diff --git a/DuckDuckGo/YoutubePlayer/YoutubeOverlayUserScript.swift b/DuckDuckGo/YoutubePlayer/YoutubeOverlayUserScript.swift index 8cdc69cec2..e5b711db73 100644 --- a/DuckDuckGo/YoutubePlayer/YoutubeOverlayUserScript.swift +++ b/DuckDuckGo/YoutubePlayer/YoutubeOverlayUserScript.swift @@ -123,7 +123,7 @@ extension YoutubeOverlayUserScript { return nil } if pixelName == "play.use" { - PixelKit.fire(GeneralPixel.watchInDuckPlayerInitial, frequency: .unique) + PixelKit.fire(GeneralPixel.watchInDuckPlayerInitial, frequency: .legacyInitial) } return nil } diff --git a/IntegrationTests/Tab/AddressBarTests.swift b/IntegrationTests/Tab/AddressBarTests.swift index 22b6ce65aa..cc7794b86e 100644 --- a/IntegrationTests/Tab/AddressBarTests.swift +++ b/IntegrationTests/Tab/AddressBarTests.swift @@ -737,12 +737,12 @@ class AddressBarTests: XCTestCase { // when activaing a Pinned Tab in another window its Web View should become the first responder viewModel2.select(at: .pinned(0)) - try await Task.sleep(interval: 0.01) + try await Task.sleep(interval: 0.1) XCTAssertEqual(window.firstResponder, window) XCTAssertEqual(window2.firstResponder, tab.webView) window.makeKeyAndOrderFront(nil) - try await Task.sleep(interval: 0.01) + try await Task.sleep(interval: 0.1) XCTAssertEqual(window.firstResponder, tab.webView) XCTAssertEqual(window2.firstResponder, window2) diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift index a2f66ad914..d7d663995d 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift @@ -83,7 +83,7 @@ public final class PixelKit { private let dateGenerator: () -> Date - public private(set) static var shared: PixelKit? + public static var shared: PixelKit? private let appVersion: String private let defaultHeaders: [String: String] private let fireRequest: FireRequest @@ -116,14 +116,14 @@ public final class PixelKit { private let source: String? private let pixelCalendar: Calendar - init(dryRun: Bool, - appVersion: String, - source: String? = nil, - defaultHeaders: [String: String], - dailyPixelCalendar: Calendar? = nil, - dateGenerator: @escaping () -> Date = Date.init, - defaults: UserDefaults, - fireRequest: @escaping FireRequest) { + public init(dryRun: Bool, + appVersion: String, + source: String? = nil, + defaultHeaders: [String: String], + dailyPixelCalendar: Calendar? = nil, + dateGenerator: @escaping () -> Date = Date.init, + defaults: UserDefaults, + fireRequest: @escaping FireRequest) { self.dryRun = dryRun self.appVersion = appVersion diff --git a/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift index f6160daee0..fc4ac6d722 100644 --- a/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift @@ -25,9 +25,7 @@ public extension XCTestCase { // MARK: - Parameters /// List of standard pixel parameters. - /// /// This is useful to support filtering these parameters out if needed. - /// private static var standardPixelParameters = [ PixelKit.Parameters.appVersion, PixelKit.Parameters.pixelSource, @@ -35,21 +33,18 @@ public extension XCTestCase { ] /// List of errror pixel parameters - /// private static var errorPixelParameters = [ PixelKit.Parameters.errorCode, PixelKit.Parameters.errorDomain ] /// List of underlying error pixel parameters - /// private static var underlyingErrorPixelParameters = [ PixelKit.Parameters.underlyingErrorCode, PixelKit.Parameters.underlyingErrorDomain ] /// Filter out the standard parameters. - /// private static func filterStandardPixelParameters(from parameters: [String: String]) -> [String: String] { parameters.filter { element in !standardPixelParameters.contains(element.key) @@ -59,10 +54,8 @@ public extension XCTestCase { static var pixelPlatformPrefix: String { #if os(macOS) return "m_mac_" -#else - // Intentionally left blank for now because PixelKit currently doesn't support - // other platforms, but if we decide to implement another platform this'll fail - // and indicate that we need a value here. +#elseif os(iOS) + return "m_" #endif } @@ -71,7 +64,6 @@ public extension XCTestCase { /// They're not a complete list of parameters for the event, as the fire call may contain extra information /// that results in additional parameters. Ideally we want most (if not all) that information to eventually /// make part of the pixel definition. - /// func knownExpectedParameters(for event: PixelKitEventV2) -> [String: String] { var expectedParameters = [String: String]() @@ -104,7 +96,6 @@ public extension XCTestCase { /// Provides some snapshot of a fired pixel so that external libraries can validate all the expected info is included. /// /// This method also checks that there is internal consistency in the expected fields. - /// func verifyThat(_ event: PixelKitEventV2, meets expectations: PixelFireExpectations, file: StaticString, line: UInt) { let expectedPixelName = event.name.hasPrefix(Self.pixelPlatformPrefix) ? event.name : Self.pixelPlatformPrefix + event.name diff --git a/UnitTests/Statistics/ATB/StatisticsLoaderTests.swift b/UnitTests/Statistics/ATB/StatisticsLoaderTests.swift index bb572ecdf8..2ecc06ef8f 100644 --- a/UnitTests/Statistics/ATB/StatisticsLoaderTests.swift +++ b/UnitTests/Statistics/ATB/StatisticsLoaderTests.swift @@ -20,18 +20,28 @@ import XCTest import OHHTTPStubs import OHHTTPStubsSwift @testable import DuckDuckGo_Privacy_Browser +import PixelKit class StatisticsLoaderTests: XCTestCase { var mockStatisticsStore: StatisticsStore! var testee: StatisticsLoader! + let pixelKit = PixelKit(dryRun: false, + appVersion: "1.0.0", + defaultHeaders: [:], + defaults: UserDefaults(), + fireRequest: { _, _, _, _, _, _ in + + }) override func setUp() { + PixelKit.shared = pixelKit mockStatisticsStore = MockStatisticsStore() testee = StatisticsLoader(statisticsStore: mockStatisticsStore) } override func tearDown() { + PixelKit.tearDown() HTTPStubs.removeAllStubs() super.tearDown() } diff --git a/UnitTests/Statistics/CBRCompileTimeReporterTests.swift b/UnitTests/Statistics/CBRCompileTimeReporterTests.swift index 62a2f2e77c..347c162f11 100644 --- a/UnitTests/Statistics/CBRCompileTimeReporterTests.swift +++ b/UnitTests/Statistics/CBRCompileTimeReporterTests.swift @@ -19,7 +19,7 @@ import XCTest import OHHTTPStubs import OHHTTPStubsSwift -import PixelKit +@testable import PixelKit @testable import DuckDuckGo_Privacy_Browser @@ -29,11 +29,16 @@ class CBRCompileTimeReporterTests: XCTestCase { let host = "improving.duckduckgo.com" var tab: NSObject! = NSObject() var time = CACurrentMediaTime() + let pixelKit = PixelKit(dryRun: false, + appVersion: "1.0.0", + defaultHeaders: [:], + defaults: UserDefaults(), + fireRequest: { _, _, _, _, _, _ in + + }) override func setUp() { - PixelKit.setUp(appVersion: "", - defaultHeaders: [:], - defaults: UserDefaults()) { _, _, _, _, _, _ in } + PixelKit.shared = pixelKit UserDefaultsWrapper.clearAll() } diff --git a/UnitTests/Statistics/PixelExperiment/PixelExperimentTests.swift b/UnitTests/Statistics/PixelExperiment/PixelExperimentTests.swift index 0d79838480..36904e5476 100644 --- a/UnitTests/Statistics/PixelExperiment/PixelExperimentTests.swift +++ b/UnitTests/Statistics/PixelExperiment/PixelExperimentTests.swift @@ -1,94 +1,94 @@ +//// +//// PixelExperimentTests.swift +//// +//// Copyright © 2023 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. +//// // -// PixelExperimentTests.swift +//import XCTest +//import PixelKit +//import os.log +//@testable import DuckDuckGo_Privacy_Browser // -// Copyright © 2023 DuckDuckGo. All rights reserved. +//class PixelExperimentTests: XCTestCase { // -// 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 +// var now = Date() +// var logic: PixelExperimentLogic { +// PixelExperiment.logic +// } +// var cohort: PixelExperiment! { +// logic.cohort +// } // -// http://www.apache.org/licenses/LICENSE-2.0 +// lazy var container: NSPersistentContainer = { +// CoreData.createInMemoryPersistentContainer(modelName: "PixelDataModel", bundle: Bundle(for: PixelData.self)) +// }() +// var context: NSManagedObjectContext! +// private var _store: LocalPixelDataStore? +// var store: LocalPixelDataStore { +// if let store = _store { +// return store +// } +// context = container.newBackgroundContext() +// let store = LocalPixelDataStore(context: context, updateModel: PixelData.update) +// _store = store +// return store +// } // -// 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. +// override func setUp() { +// super.setUp() +// now = Date() +// PixelExperiment.logic = PixelExperimentLogic(now: { [unowned self] in +// self.now +// }) +// logic.cleanup() +// } // - -import XCTest -import PixelKit -import os.log -@testable import DuckDuckGo_Privacy_Browser - -class PixelExperimentTests: XCTestCase { - - var now = Date() - var logic: PixelExperimentLogic { - PixelExperiment.logic - } - var cohort: PixelExperiment! { - logic.cohort - } - - lazy var container: NSPersistentContainer = { - CoreData.createInMemoryPersistentContainer(modelName: "PixelDataModel", bundle: Bundle(for: PixelData.self)) - }() - var context: NSManagedObjectContext! - private var _store: LocalPixelDataStore? - var store: LocalPixelDataStore { - if let store = _store { - return store - } - context = container.newBackgroundContext() - let store = LocalPixelDataStore(context: context, updateModel: PixelData.update) - _store = store - return store - } - - override func setUp() { - super.setUp() - now = Date() - PixelExperiment.logic = PixelExperimentLogic(now: { [unowned self] in - self.now - }) - logic.cleanup() - } - - override func tearDown() { - logic.cleanup() - PixelKit.tearDown() - _store = nil - } - - func testWhenNotInstalledThenCohortIsNill() { - XCTAssertNil(logic.cohort) - - PixelKit.setUp(appVersion: "", - defaultHeaders: [:], - defaults: UserDefaults()) { _, _, _, _, _, _ in - XCTFail("shouldn‘t fire pixels") - } - - PixelExperiment.fireEnrollmentPixel() - PixelExperiment.fireFirstSerpPixel() - PixelExperiment.fireDay21To27SerpPixel() - } - - func testWhenNoCohort_NoEnrollmentPixelFired() { - DispatchQueue.main.async { - AppDelegate.firstLaunchDate = self.now - PixelExperiment.install() - - PixelKit.setUp(appVersion: "", - defaultHeaders: [:], - defaults: UserDefaults()) { _, _, _, _, _, _ in - XCTFail("shouldn‘t fire pixels") - } - - PixelExperiment.fireEnrollmentPixel() - } - } - - // See git history for fireFirstSerpPixel and fireDay21To27SerpPixel tests -} +// override func tearDown() { +// logic.cleanup() +// PixelKit.tearDown() +// _store = nil +// } +// +// func testWhenNotInstalledThenCohortIsNill() { +// XCTAssertNil(logic.cohort) +// +// PixelKit.setUp(appVersion: "", +// defaultHeaders: [:], +// defaults: UserDefaults()) { _, _, _, _, _, _ in +// XCTFail("shouldn‘t fire pixels") +// } +// +// PixelExperiment.fireEnrollmentPixel() +// PixelExperiment.fireFirstSerpPixel() +// PixelExperiment.fireDay21To27SerpPixel() +// } +// +// func testWhenNoCohort_NoEnrollmentPixelFired() { +// DispatchQueue.main.async { +// AppDelegate.firstLaunchDate = self.now +// PixelExperiment.install() +// +// PixelKit.setUp(appVersion: "", +// defaultHeaders: [:], +// defaults: UserDefaults()) { _, _, _, _, _, _ in +// XCTFail("shouldn‘t fire pixels") +// } +// +// PixelExperiment.fireEnrollmentPixel() +// } +// } +// +// // See git history for fireFirstSerpPixel and fireDay21To27SerpPixel tests +//} From 6eb1b9861a12c0eb6fd51ab67796d166905b62e0 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 5 Apr 2024 12:57:17 +0100 Subject: [PATCH 06/31] lint and comments added --- .../DuckDuckGo Privacy Browser.xcscheme | 3 + DuckDuckGo/Application/AppDelegate.swift | 2 +- DuckDuckGo/LoginItems/LoginItemsManager.swift | 2 +- .../View/PrivacyDashboardViewController.swift | 2 +- .../SmarterEncryption/PrivacyFeatures.swift | 6 +- .../Experiment/PixelExperiment.swift | 327 +++++++++--------- DuckDuckGo/Statistics/GeneralPixel.swift | 2 +- DuckDuckGo/Statistics/PrivacyProPixel.swift | 2 +- .../VPNFeedbackForm/VPNFeedbackSender.swift | 2 +- .../PixelKit/Sources/PixelKit/PixelKit.swift | 2 +- .../PixelExperimentTests.swift | 181 +++++----- 11 files changed, 272 insertions(+), 259 deletions(-) diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme index d683d41f9e..c65603ddc4 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme @@ -114,6 +114,9 @@ + + diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 3644492b69..f55848bb32 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -234,7 +234,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { // if PixelExperiment.allocatedCohortDoesNotMatchCurrentCohorts { // Re-implement https://app.asana.com/0/0/1207002879349166/f // PixelExperiment.cleanup() // } - + if LocalStatisticsStore().atb == nil { AppDelegate.firstLaunchDate = Date() // MARK: Enable pixel experiments here diff --git a/DuckDuckGo/LoginItems/LoginItemsManager.swift b/DuckDuckGo/LoginItems/LoginItemsManager.swift index 0a0a4b4af4..a4ccbba360 100644 --- a/DuckDuckGo/LoginItems/LoginItemsManager.swift +++ b/DuckDuckGo/LoginItems/LoginItemsManager.swift @@ -71,7 +71,7 @@ final class LoginItemsManager { action: "enable", buildType: AppVersion.shared.buildType, osVersion: AppVersion.shared.osVersion) - PixelKit.fire(DebugEvent(event, error: error) , frequency: .dailyAndCount) + PixelKit.fire(DebugEvent(event, error: error), frequency: .dailyAndCount) os_log("🔴 Could not enable %{public}@: %{public}@", item.debugDescription, error.debugDescription) } diff --git a/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift b/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift index ecb56bcdd2..a58ec40ba3 100644 --- a/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift +++ b/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift @@ -45,7 +45,7 @@ final class PrivacyDashboardViewController: NSViewController { private let brokenSiteReporter: BrokenSiteReporter = { BrokenSiteReporter(pixelHandler: { parameters in - PixelKit.fire(GeneralPixel.brokenSiteReport, + PixelKit.fire(GeneralPixel.brokenSiteReport, withAdditionalParameters: parameters, allowedQueryReservedCharacters: BrokenSiteReport.allowedQueryReservedCharacters) }, keyValueStoring: UserDefaults.standard) diff --git a/DuckDuckGo/SmarterEncryption/PrivacyFeatures.swift b/DuckDuckGo/SmarterEncryption/PrivacyFeatures.swift index fbaecc8a21..cb16b2bcef 100644 --- a/DuckDuckGo/SmarterEncryption/PrivacyFeatures.swift +++ b/DuckDuckGo/SmarterEncryption/PrivacyFeatures.swift @@ -56,16 +56,16 @@ final class AppPrivacyFeatures: PrivacyFeaturesProtocol { } if dailyAndCount { - PixelKit.fire(DebugEvent(domainEvent, error: error), + PixelKit.fire(DebugEvent(domainEvent, error: error), frequency: .dailyAndCount, withAdditionalParameters: parameters ?? [:], - includeAppVersionParameter: true) { success, error in + includeAppVersionParameter: true) { _, error in onComplete(error) } } else { PixelKit.fire(DebugEvent(domainEvent, error: error), frequency: .dailyAndCount, - withAdditionalParameters: parameters ?? [:]) { success, error in + withAdditionalParameters: parameters ?? [:]) { _, error in onComplete(error) } } diff --git a/DuckDuckGo/Statistics/Experiment/PixelExperiment.swift b/DuckDuckGo/Statistics/Experiment/PixelExperiment.swift index 4e2fa07614..eb0f07b592 100644 --- a/DuckDuckGo/Statistics/Experiment/PixelExperiment.swift +++ b/DuckDuckGo/Statistics/Experiment/PixelExperiment.swift @@ -1,162 +1,167 @@ -//// -//// PixelExperiment.swift -//// -//// Copyright © 2023 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 -//import PixelKit -// -//enum PixelExperiment: String, CaseIterable { -// -// static var logic = PixelExperimentLogic() -// -// /// When `cohort` is accessed for the first time after the experiment is installed with `install()`, -// /// allocate and return a cohort. Subsequently, return the same cohort. -// static var cohort: PixelExperiment? { -// logic.cohort -// } -// -// static var isExperimentInstalled: Bool { -// return logic.isInstalled -// } -// -// static var allocatedCohortDoesNotMatchCurrentCohorts: Bool { -// guard let allocatedCohort = logic.allocatedCohort else { return false } -// if PixelExperiment(rawValue: allocatedCohort) == nil { -// return true -// } -// return false -// } -// -// /// Enables this experiment for new users when called from the new installation path. -// static func install() { -// logic.install() -// } -// -// static func cleanup() { -// logic.cleanup() -// } -// -// // These are the variants. Rename or add/remove them as needed. If you change the string value -// // remember to keep it clear for privacy triage. -// case control -//} -// -//// These functions contain the business logic for determining if the pixel should be fired or not. -//extension PixelExperiment { -// -// static func fireEnrollmentPixel() { -// logic.fireEnrollmentPixel() -// } -// -// static func fireFirstSerpPixel() { -// logic.fireFirstSerpPixel() -// } -// -// static func fireDay21To27SerpPixel() { -// logic.fireDay21To27SerpPixel() -// } -// -//} -// -//final internal class PixelExperimentLogic { -// -// private let now: () -> Date -// -// var cohort: PixelExperiment? { -// guard isInstalled else { return nil } -// -// if let allocatedCohort, -// // if the stored cohort doesn't match, allocate a new one -// let cohort = PixelExperiment(rawValue: allocatedCohort) { -// return cohort -// } -// -// // For now, just use equal distribution of all cohorts. -// let cohort = PixelExperiment.allCases.randomElement()! -// allocatedCohort = cohort.rawValue -// enrollmentDate = now() -// fireEnrollmentPixel() -// return cohort -// } -// -// @UserDefaultsWrapper(key: .pixelExperimentInstalled, defaultValue: false) -// var isInstalled: Bool -// -// @UserDefaultsWrapper(key: .pixelExperimentCohort, defaultValue: nil) -// var allocatedCohort: String? -// -// @UserDefaultsWrapper(key: .pixelExperimentEnrollmentDate, defaultValue: nil) -// private var enrollmentDate: Date? -// -// private var daysSinceEnrollment: Int { -// guard let enrollmentDate else { return 0 } -// let diff = now().timeIntervalSince1970 - enrollmentDate.timeIntervalSince1970 -// let days = Int(diff / 60 / 60 / 24) -// return days -// } -// -// @UserDefaultsWrapper(key: .pixelExperimentFiredPixels, defaultValue: []) -// private var firedPixelsStorage: [String] -// -// private var firedPixels: Set { -// get { -// Set(firedPixelsStorage) -// } -// set { -// firedPixelsStorage = Array(newValue) -// } -// } -// -// init(now: @escaping () -> Date = Date.init) { -// self.now = now -// } -// -// func install() { -// isInstalled = true -// } -// -// // You'll need additional pixels for your experiment. Pass the cohort as a parameter. -// func fireEnrollmentPixel() { -// // You'll probably need this at least. -// } -// -// // Often used -// func fireFirstSerpPixel() { -// guard allocatedCohort != nil, let cohort else { return } -// PixelKit.fire(GeneralPixel.serpInitial(cohort: cohort.rawValue), frequency: .legacyInitial, includeAppVersionParameter: false) -// } -// -// // Often used for retention experiments -// func fireDay21To27SerpPixel() { -// guard allocatedCohort != nil, let cohort else { return } -// -// DispatchQueue.main.async { -// let now = self.now() -// if now >= AppDelegate.firstLaunchDate.adding(.days(21)) && now <= AppDelegate.firstLaunchDate.adding(.days(27)) { -// PixelKit.fire(GeneralPixel.serpDay21to27(cohort: cohort.rawValue), frequency: .legacyInitial, includeAppVersionParameter: false) -// } -// } -// } -// -// func cleanup() { -// isInstalled = false -// allocatedCohort = nil -// enrollmentDate = nil -// firedPixelsStorage = [] -// } -// -//} +// PixelExperiment.swift +// +// Copyright © 2023 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. +// + +/* + WARNING: This component doesn't work anymore, re-implementation needed + https://app.asana.com/0/0/1207002879349166/f + */ + +import Foundation +import PixelKit + +enum PixelExperiment: String, CaseIterable { + + static var logic = PixelExperimentLogic() + + /// When `cohort` is accessed for the first time after the experiment is installed with `install()`, + /// allocate and return a cohort. Subsequently, return the same cohort. + static var cohort: PixelExperiment? { + logic.cohort + } + + static var isExperimentInstalled: Bool { + return logic.isInstalled + } + + static var allocatedCohortDoesNotMatchCurrentCohorts: Bool { + guard let allocatedCohort = logic.allocatedCohort else { return false } + if PixelExperiment(rawValue: allocatedCohort) == nil { + return true + } + return false + } + + /// Enables this experiment for new users when called from the new installation path. + static func install() { + logic.install() + } + + static func cleanup() { + logic.cleanup() + } + + // These are the variants. Rename or add/remove them as needed. If you change the string value + // remember to keep it clear for privacy triage. + case control +} + +// These functions contain the business logic for determining if the pixel should be fired or not. +extension PixelExperiment { + + static func fireEnrollmentPixel() { + logic.fireEnrollmentPixel() + } + + static func fireFirstSerpPixel() { + logic.fireFirstSerpPixel() + } + + static func fireDay21To27SerpPixel() { + logic.fireDay21To27SerpPixel() + } + +} + +final internal class PixelExperimentLogic { + + private let now: () -> Date + + var cohort: PixelExperiment? { + guard isInstalled else { return nil } + + if let allocatedCohort, + // if the stored cohort doesn't match, allocate a new one + let cohort = PixelExperiment(rawValue: allocatedCohort) { + return cohort + } + + // For now, just use equal distribution of all cohorts. + let cohort = PixelExperiment.allCases.randomElement()! + allocatedCohort = cohort.rawValue + enrollmentDate = now() + fireEnrollmentPixel() + return cohort + } + + @UserDefaultsWrapper(key: .pixelExperimentInstalled, defaultValue: false) + var isInstalled: Bool + + @UserDefaultsWrapper(key: .pixelExperimentCohort, defaultValue: nil) + var allocatedCohort: String? + + @UserDefaultsWrapper(key: .pixelExperimentEnrollmentDate, defaultValue: nil) + private var enrollmentDate: Date? + + private var daysSinceEnrollment: Int { + guard let enrollmentDate else { return 0 } + let diff = now().timeIntervalSince1970 - enrollmentDate.timeIntervalSince1970 + let days = Int(diff / 60 / 60 / 24) + return days + } + + @UserDefaultsWrapper(key: .pixelExperimentFiredPixels, defaultValue: []) + private var firedPixelsStorage: [String] + + private var firedPixels: Set { + get { + Set(firedPixelsStorage) + } + set { + firedPixelsStorage = Array(newValue) + } + } + + init(now: @escaping () -> Date = Date.init) { + self.now = now + } + + func install() { + isInstalled = true + } + + // You'll need additional pixels for your experiment. Pass the cohort as a parameter. + func fireEnrollmentPixel() { + // You'll probably need this at least. + } + + // Often used + func fireFirstSerpPixel() { + guard allocatedCohort != nil, let cohort else { return } + PixelKit.fire(GeneralPixel.serpInitial(cohort: cohort.rawValue), frequency: .legacyInitial, includeAppVersionParameter: false) + } + + // Often used for retention experiments + func fireDay21To27SerpPixel() { + guard allocatedCohort != nil, let cohort else { return } + + DispatchQueue.main.async { + let now = self.now() + if now >= AppDelegate.firstLaunchDate.adding(.days(21)) && now <= AppDelegate.firstLaunchDate.adding(.days(27)) { + PixelKit.fire(GeneralPixel.serpDay21to27(cohort: cohort.rawValue), frequency: .legacyInitial, includeAppVersionParameter: false) + } + } + } + + func cleanup() { + isInstalled = false + allocatedCohort = nil + enrollmentDate = nil + firedPixelsStorage = [] + } + +} diff --git a/DuckDuckGo/Statistics/GeneralPixel.swift b/DuckDuckGo/Statistics/GeneralPixel.swift index 10c187d536..a3f27491d1 100644 --- a/DuckDuckGo/Statistics/GeneralPixel.swift +++ b/DuckDuckGo/Statistics/GeneralPixel.swift @@ -238,7 +238,7 @@ enum GeneralPixel: PixelKitEventV2 { case removedInvalidBookmarkManagedObjects case bitwardenNotResponding - case bitwardenRespondedCannotDecryptUnique //(repetition: Repetition = .init(key: "bitwardenRespondedCannotDecryptUnique")) // TODO: REIMPLEMENTATION?? + case bitwardenRespondedCannotDecryptUnique // (repetition: Repetition = .init(key: "bitwardenRespondedCannotDecryptUnique")) // TODO: REIMPLEMENTATION?? case bitwardenHandshakeFailed case bitwardenDecryptionOfSharedKeyFailed case bitwardenStoringOfTheSharedKeyFailed diff --git a/DuckDuckGo/Statistics/PrivacyProPixel.swift b/DuckDuckGo/Statistics/PrivacyProPixel.swift index 50f02bd016..d50f06656a 100644 --- a/DuckDuckGo/Statistics/PrivacyProPixel.swift +++ b/DuckDuckGo/Statistics/PrivacyProPixel.swift @@ -122,7 +122,7 @@ enum PrivacyProPixel: PixelKitEventV2 { return nil } - var parameters: [String : String]? { + var parameters: [String: String]? { return nil } } diff --git a/DuckDuckGo/VPNFeedbackForm/VPNFeedbackSender.swift b/DuckDuckGo/VPNFeedbackForm/VPNFeedbackSender.swift index cb65dd926a..83f83261bd 100644 --- a/DuckDuckGo/VPNFeedbackForm/VPNFeedbackSender.swift +++ b/DuckDuckGo/VPNFeedbackForm/VPNFeedbackSender.swift @@ -33,7 +33,7 @@ struct DefaultVPNFeedbackSender: VPNFeedbackSender { let pixelEvent = GeneralPixel.vpnBreakageReport(category: category.rawValue, description: encodedUserText, metadata: metadata.toBase64()) try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - PixelKit.fire(pixelEvent) { succes, error in + PixelKit.fire(pixelEvent) { _, error in if let error { continuation.resume(throwing: error) } else { diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift index d7d663995d..cdf613aa96 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift @@ -378,7 +378,7 @@ public final class PixelKit { } public static func clearFrequencyHistoryForAllPixels() { - //TODO: Implement + // TODO: Implement assertionFailure("To be implemented") } diff --git a/UnitTests/Statistics/PixelExperiment/PixelExperimentTests.swift b/UnitTests/Statistics/PixelExperiment/PixelExperimentTests.swift index 36904e5476..6215677a64 100644 --- a/UnitTests/Statistics/PixelExperiment/PixelExperimentTests.swift +++ b/UnitTests/Statistics/PixelExperiment/PixelExperimentTests.swift @@ -1,94 +1,99 @@ -//// -//// PixelExperimentTests.swift -//// -//// Copyright © 2023 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 XCTest -//import PixelKit -//import os.log -//@testable import DuckDuckGo_Privacy_Browser +// PixelExperimentTests.swift // -//class PixelExperimentTests: XCTestCase { +// Copyright © 2023 DuckDuckGo. All rights reserved. // -// var now = Date() -// var logic: PixelExperimentLogic { -// PixelExperiment.logic -// } -// var cohort: PixelExperiment! { -// logic.cohort -// } +// 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 // -// lazy var container: NSPersistentContainer = { -// CoreData.createInMemoryPersistentContainer(modelName: "PixelDataModel", bundle: Bundle(for: PixelData.self)) -// }() -// var context: NSManagedObjectContext! -// private var _store: LocalPixelDataStore? -// var store: LocalPixelDataStore { -// if let store = _store { -// return store -// } -// context = container.newBackgroundContext() -// let store = LocalPixelDataStore(context: context, updateModel: PixelData.update) -// _store = store -// return store -// } +// http://www.apache.org/licenses/LICENSE-2.0 // -// override func setUp() { -// super.setUp() -// now = Date() -// PixelExperiment.logic = PixelExperimentLogic(now: { [unowned self] in -// self.now -// }) -// logic.cleanup() -// } +// 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. // -// override func tearDown() { -// logic.cleanup() -// PixelKit.tearDown() -// _store = nil -// } -// -// func testWhenNotInstalledThenCohortIsNill() { -// XCTAssertNil(logic.cohort) -// -// PixelKit.setUp(appVersion: "", -// defaultHeaders: [:], -// defaults: UserDefaults()) { _, _, _, _, _, _ in -// XCTFail("shouldn‘t fire pixels") -// } -// -// PixelExperiment.fireEnrollmentPixel() -// PixelExperiment.fireFirstSerpPixel() -// PixelExperiment.fireDay21To27SerpPixel() -// } -// -// func testWhenNoCohort_NoEnrollmentPixelFired() { -// DispatchQueue.main.async { -// AppDelegate.firstLaunchDate = self.now -// PixelExperiment.install() -// -// PixelKit.setUp(appVersion: "", -// defaultHeaders: [:], -// defaults: UserDefaults()) { _, _, _, _, _, _ in -// XCTFail("shouldn‘t fire pixels") -// } -// -// PixelExperiment.fireEnrollmentPixel() -// } -// } -// -// // See git history for fireFirstSerpPixel and fireDay21To27SerpPixel tests -//} + +import XCTest +import PixelKit +import os.log +@testable import DuckDuckGo_Privacy_Browser + +/* + WARNING: This component doesn't work anymore, re-implementation needed + https://app.asana.com/0/0/1207002879349166/f + */ + +class PixelExperimentTests: XCTestCase { + + var now = Date() + var logic: PixelExperimentLogic { + PixelExperiment.logic + } + var cohort: PixelExperiment! { + logic.cohort + } + + lazy var container: NSPersistentContainer = { + CoreData.createInMemoryPersistentContainer(modelName: "PixelDataModel", bundle: Bundle(for: PixelData.self)) + }() + var context: NSManagedObjectContext! + private var _store: LocalPixelDataStore? + var store: LocalPixelDataStore { + if let store = _store { + return store + } + context = container.newBackgroundContext() + let store = LocalPixelDataStore(context: context, updateModel: PixelData.update) + _store = store + return store + } + + override func setUp() { + super.setUp() + now = Date() + PixelExperiment.logic = PixelExperimentLogic(now: { [unowned self] in + self.now + }) + logic.cleanup() + } + + override func tearDown() { + logic.cleanup() + PixelKit.tearDown() + _store = nil + } + + func testWhenNotInstalledThenCohortIsNill() { + XCTAssertNil(logic.cohort) + + PixelKit.setUp(appVersion: "", + defaultHeaders: [:], + defaults: UserDefaults()) { _, _, _, _, _, _ in + XCTFail("shouldn‘t fire pixels") + } + + PixelExperiment.fireEnrollmentPixel() + PixelExperiment.fireFirstSerpPixel() + PixelExperiment.fireDay21To27SerpPixel() + } + + func testWhenNoCohort_NoEnrollmentPixelFired() { + DispatchQueue.main.async { + AppDelegate.firstLaunchDate = self.now + PixelExperiment.install() + + PixelKit.setUp(appVersion: "", + defaultHeaders: [:], + defaults: UserDefaults()) { _, _, _, _, _, _ in + XCTFail("shouldn‘t fire pixels") + } + + PixelExperiment.fireEnrollmentPixel() + } + } + + // See git history for fireFirstSerpPixel and fireDay21To27SerpPixel tests +} From a8a62ac8f785367774c1f7f32b22a9f3291ba4d1 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 5 Apr 2024 13:51:11 +0100 Subject: [PATCH 07/31] PixelKit API cleanup --- DuckDuckGo.xcodeproj/project.pbxproj | 18 ---- .../Model/DefaultBrowserPreferences.swift | 2 +- .../PixelKit/Sources/PixelKit/PixelKit.swift | 86 ++++++++++++------- 3 files changed, 55 insertions(+), 51 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index df3fb5f59d..4e69bc887c 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -3823,7 +3823,6 @@ 4B67742A255DBEB800025BD8 /* httpsMobileV2FalsePositives.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = httpsMobileV2FalsePositives.json; sourceTree = ""; }; 4B677440255DBEEA00025BD8 /* Database.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Database.swift; sourceTree = ""; }; 4B677454255DC18000025BD8 /* Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Bridging.h; sourceTree = ""; }; - 4B67853E2AA7C726008A5004 /* DailyPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyPixel.swift; sourceTree = ""; }; 4B6785432AA8DE1F008A5004 /* NetworkProtectionFeatureDisabler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionFeatureDisabler.swift; sourceTree = ""; }; 4B6B64832BA930420009FF9F /* WaitlistThankYouPromptPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistThankYouPromptPresenter.swift; sourceTree = ""; }; 4B70BFFF27B0793D000386ED /* DuckDuckGo-ExampleCrash.ips */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "DuckDuckGo-ExampleCrash.ips"; sourceTree = ""; }; @@ -4577,11 +4576,7 @@ B6A60E4F2B73C3B800FD4968 /* WKURLSchemeTask+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WKURLSchemeTask+Private.h"; sourceTree = ""; }; B6A60E502B73C46B00FD4968 /* IntegrationTestsBridging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IntegrationTestsBridging.h; sourceTree = ""; }; B6A924D82664C72D001A28CA /* WebKitDownloadTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebKitDownloadTask.swift; sourceTree = ""; }; - B6A9E45226142B070067D1B9 /* Pixel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pixel.swift; sourceTree = ""; }; B6A9E46A2614618A0067D1B9 /* OperatingSystemVersionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperatingSystemVersionExtension.swift; sourceTree = ""; }; - B6A9E47626146A570067D1B9 /* PixelEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelEvent.swift; sourceTree = ""; }; - B6A9E47E26146A800067D1B9 /* PixelArguments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelArguments.swift; sourceTree = ""; }; - B6A9E48326146AAB0067D1B9 /* PixelParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelParameters.swift; sourceTree = ""; }; B6AA64722994B43300D99CD6 /* FutureExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FutureExtensionTests.swift; sourceTree = ""; }; B6AAAC2C260330580029438D /* PublishedAfter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishedAfter.swift; sourceTree = ""; }; B6AAAC3D26048F690029438D /* RandomAccessCollectionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomAccessCollectionExtension.swift; sourceTree = ""; }; @@ -8479,7 +8474,6 @@ children = ( B69B50332726A10700758A2B /* ATB */, 857E5AF32A79044900FC0FB4 /* Experiment */, - F18826712BBEAF9200D9AC4F /* Pixel Legacy */, B610F2BA27A145C500FCEBE9 /* RulesCompilationMonitor.swift */, F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */, F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */, @@ -8767,18 +8761,6 @@ path = JSAlert; sourceTree = ""; }; - F18826712BBEAF9200D9AC4F /* Pixel Legacy */ = { - isa = PBXGroup; - children = ( - B6A9E45226142B070067D1B9 /* Pixel.swift */, - 4B67853E2AA7C726008A5004 /* DailyPixel.swift */, - B6A9E47626146A570067D1B9 /* PixelEvent.swift */, - B6A9E47E26146A800067D1B9 /* PixelArguments.swift */, - B6A9E48326146AAB0067D1B9 /* PixelParameters.swift */, - ); - path = "Pixel Legacy"; - sourceTree = ""; - }; F1B33DF92BAD9C83001128B3 /* Subscription */ = { isa = PBXGroup; children = ( diff --git a/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift b/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift index 95142d1891..a5495827fe 100644 --- a/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift +++ b/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift @@ -81,7 +81,7 @@ final class DefaultBrowserPreferences: ObservableObject { #if DEBUG guard NSApp.runType.requiresEnvironment else { return } #endif - if AppDelegate.isNewUser && self.isDefault { // TODO: reimplement Pixel.isNewUser + if AppDelegate.isNewUser && self.isDefault { PixelKit.fire(GeneralPixel.setAsDefaultInitial, frequency: .legacyInitial) } } diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift index cdf613aa96..8edf2a6269 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift @@ -31,7 +31,8 @@ public final class PixelKit { /// Used in Pixel.fire(...) as unique but without the `_u` requirement in the name case legacyInitial - /// Sent only once ever. The timestamp for this pixel is stored. Name for pixels of this type must end with `_u`. + /// Sent only once ever. The timestamp for this pixel is stored. + /// Note: This is the only pixel that MUST end with `_u`, Name for pixels of this type must end with if it doesn't an assertion is fired. case unique /// Sent once per day. The last timestamp for this pixel is stored and compared to the current date. Pixels of this type will have `_d` appended to their name. @@ -41,6 +42,21 @@ public final class PixelKit { /// This means a pixel will get sent twice the first time it is called per-day, and subsequent calls that day will only send the `_c` variant. /// This is useful in situations where pixels receive spikes in volume, as the daily pixel can be used to determine how many users are actually affected. case dailyAndCount + + fileprivate var description: String { + switch self { + case .standard: + "Standard" + case .legacyInitial: + "Legacy Initial" + case .unique: + "Unique" + case .daily: + "Daily" + case .dailyAndCount: + "Daily and Count" + } + } } public enum Header { @@ -133,9 +149,18 @@ public final class PixelKit { self.dateGenerator = dateGenerator self.defaults = defaults self.fireRequest = fireRequest + + logger.debug(""" + PixelKit initialised: + dryRun: \(self.dryRun) + appVersion: \(self.appVersion) + source: \(self.source ?? "-") + defaultHeaders: \(self.defaultHeaders) + pixelCalendar: \(self.pixelCalendar) + """) } - // swiftlint:disable:next cyclomatic_complexity, function_body_length + // swiftlint:disable:next function_body_length cyclomatic_complexity private func fire(pixelNamed pixelName: String, frequency: Frequency, withHeaders headers: [String: String]?, @@ -146,18 +171,9 @@ public final class PixelKit { onComplete: @escaping CompletionBlock) { var newParams = params ?? [:] - - if includeAppVersionParameter { - newParams[Parameters.appVersion] = appVersion - } - - if let source { - newParams[Parameters.pixelSource] = source - } - - if let error { - newParams.appendErrorPixelParams(error: error) - } + if includeAppVersionParameter { newParams[Parameters.appVersion] = appVersion } + if let source { newParams[Parameters.pixelSource] = source } + if let error { newParams.appendErrorPixelParams(error: error) } #if DEBUG newParams[Parameters.test] = Values.test @@ -169,13 +185,13 @@ public final class PixelKit { switch frequency { case .standard: - fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, onComplete) + fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, onComplete, frequency) case .legacyInitial: if !pixelHasBeenFiredEver(pixelName) { - fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, onComplete) + fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, onComplete, frequency) updatePixelLastFireDate(pixelName: pixelName) } else { - printDebugInfo(pixelName: pixelName, parameters: newParams, skipped: true) + printDebugInfo(pixelName: pixelName, frequency: frequency, parameters: newParams, skipped: true) } case .unique: guard pixelName.hasSuffix("_u") else { @@ -183,34 +199,42 @@ public final class PixelKit { return } if !pixelHasBeenFiredEver(pixelName) { - fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, onComplete) + fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, onComplete, frequency) updatePixelLastFireDate(pixelName: pixelName) } else { - printDebugInfo(pixelName: pixelName, parameters: newParams, skipped: true) + printDebugInfo(pixelName: pixelName, frequency: frequency, parameters: newParams, skipped: true) } case .daily: + assertIf(name: pixelName, endsWith: "_d") if !pixelHasBeenFiredToday(pixelName) { - fireRequestWrapper(pixelName + "_d", headers, newParams, allowedQueryReservedCharacters, true, onComplete) + fireRequestWrapper(pixelName + "_d", headers, newParams, allowedQueryReservedCharacters, true, onComplete, frequency) updatePixelLastFireDate(pixelName: pixelName) } else { - printDebugInfo(pixelName: pixelName + "_d", parameters: newParams, skipped: true) + printDebugInfo(pixelName: pixelName + "_d", frequency: frequency, parameters: newParams, skipped: true) } case .dailyAndCount: + assertIf(name: pixelName, endsWith: "_c") + assertIf(name: pixelName, endsWith: "_d") if !pixelHasBeenFiredToday(pixelName) { - fireRequestWrapper(pixelName + "_d", headers, newParams, allowedQueryReservedCharacters, true, onComplete) + fireRequestWrapper(pixelName + "_d", headers, newParams, allowedQueryReservedCharacters, true, onComplete, frequency) updatePixelLastFireDate(pixelName: pixelName) } else { - printDebugInfo(pixelName: pixelName + "_d", parameters: newParams, skipped: true) + printDebugInfo(pixelName: pixelName + "_d", frequency: frequency, parameters: newParams, skipped: true) } - fireRequestWrapper(pixelName + "_c", headers, newParams, allowedQueryReservedCharacters, true, onComplete) + fireRequestWrapper(pixelName + "_c", headers, newParams, allowedQueryReservedCharacters, true, onComplete, frequency) + } + } + + func assertIf(name: String, endsWith forbiddenString: String) { + if name.hasSuffix(forbiddenString) { + assertionFailure("Pixel \(name) must not end with \(forbiddenString)") } } - private func printDebugInfo(pixelName: String, parameters: [String: String], skipped: Bool = false) { + private func printDebugInfo(pixelName: String, frequency: Frequency, parameters: [String: String], skipped: Bool = false) { let params = parameters.filter { key, _ in !["test"].contains(key) } - let pixelName = pixelName.replacingOccurrences(of: "_", with: ".") - logger.debug("👾 [\(skipped ? "SKIPPED" : "FIRED")] \(pixelName) \(params)") + logger.debug("👾[\(frequency.description)-\(skipped ? "Skipped" : "Fired")] \(pixelName) \(params)") } private func fireRequestWrapper( @@ -219,18 +243,16 @@ public final class PixelKit { _ parameters: [String: String], _ allowedQueryReservedCharacters: CharacterSet?, _ callBackOnMainThread: Bool, - _ onComplete: @escaping CompletionBlock) { + _ onComplete: @escaping CompletionBlock, + _ frequency: Frequency) { guard !dryRun else { - printDebugInfo(pixelName: pixelName, parameters: parameters) - + printDebugInfo(pixelName: pixelName, frequency: frequency, parameters: parameters, skipped: false) // simulate server response time for Dry Run mode DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { onComplete(true, nil) } - return } - fireRequest(pixelName, headers, parameters, allowedQueryReservedCharacters, callBackOnMainThread, onComplete) } From 04de21722ee6c3959e402f815a4bc26d03ba7dd3 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 5 Apr 2024 14:24:36 +0100 Subject: [PATCH 08/31] PixelKit safety checks --- .../PixelKit/Sources/PixelKit/PixelKit.swift | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift index 8edf2a6269..797208010b 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift @@ -185,8 +185,12 @@ public final class PixelKit { switch frequency { case .standard: + assertIf(name: pixelName, endsWith: "_u") + assertIf(name: pixelName, endsWith: "_d") fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, onComplete, frequency) case .legacyInitial: + assertIf(name: pixelName, endsWith: "_u") + assertIf(name: pixelName, endsWith: "_d") if !pixelHasBeenFiredEver(pixelName) { fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, onComplete, frequency) updatePixelLastFireDate(pixelName: pixelName) @@ -194,6 +198,7 @@ public final class PixelKit { printDebugInfo(pixelName: pixelName, frequency: frequency, parameters: newParams, skipped: true) } case .unique: + assertIf(name: pixelName, endsWith: "_d") guard pixelName.hasSuffix("_u") else { assertionFailure("Unique pixel: must end with _u") return @@ -205,7 +210,8 @@ public final class PixelKit { printDebugInfo(pixelName: pixelName, frequency: frequency, parameters: newParams, skipped: true) } case .daily: - assertIf(name: pixelName, endsWith: "_d") + assertIf(name: pixelName, endsWith: "_u") + assertIf(name: pixelName, endsWith: "_d") // Because is added automatically if !pixelHasBeenFiredToday(pixelName) { fireRequestWrapper(pixelName + "_d", headers, newParams, allowedQueryReservedCharacters, true, onComplete, frequency) updatePixelLastFireDate(pixelName: pixelName) @@ -213,8 +219,9 @@ public final class PixelKit { printDebugInfo(pixelName: pixelName + "_d", frequency: frequency, parameters: newParams, skipped: true) } case .dailyAndCount: - assertIf(name: pixelName, endsWith: "_c") - assertIf(name: pixelName, endsWith: "_d") + assertIf(name: pixelName, endsWith: "_u") + assertIf(name: pixelName, endsWith: "_d") // Because is added automatically + assertIf(name: pixelName, endsWith: "_c") // Because is added automatically if !pixelHasBeenFiredToday(pixelName) { fireRequestWrapper(pixelName + "_d", headers, newParams, allowedQueryReservedCharacters, true, onComplete, frequency) updatePixelLastFireDate(pixelName: pixelName) From 70849bcfdbfee5a218b80a863094c8a0d9d0abe9 Mon Sep 17 00:00:00 2001 From: Alessandro Boron Date: Mon, 8 Apr 2024 20:07:49 +1000 Subject: [PATCH 09/31] Migrate attribution pixel to pixel kit (#2564) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task/Issue URL: https://app.asana.com/0/0/1207002879349167/f **Description**: Migrates the Installation Attribution Pixel to use PixelKit. Screenshot 2024-04-08 at 4 07 47 PM **Steps to test this PR:** Download the Review App [here](https://github.com/duckduckgo/macos-browser/actions/runs/8595167779/artifacts/1393075355) Make sure to clean up the app's data directory by running sh clean-app.sh review. Run the App. Search for pixel:m.mac.install in Kibana. **Extra:** Create the Origin.txt file manually and add it to the Bundle of the App (.app/Contents/Resources) to test that the pixel fires and has the origin field set. This requires to re-sign the App again in order to run it. Steps to re-sign the App [here](https://github.com/duckduckgo/macos-browser/blob/988a3ef69aaf3c69fa2b56b274de5fa113d73809/.github/workflows/create_variants.yml#L142) In terminal: codesign -d --entitlements :- DuckDuckGo.app > entitlements.plist Copy result of security find-certificate -a -c "Developer ID Application" -Z | grep ^SHA-1 | cut -d " " -f3 | uniq Code sign the App using the below: --force \ --sign \ --options runtime \ --entitlements entitlements.plist \ --generate-entitlement-der “DuckDuckGo.app” Once the App is re-signed you need to open it by right-click on the icon and select Open from the context menu otherwise you won’t be able to open it. --- .../CrashReports/Model/CrashReporter.swift | 1 + .../InstallationAttributionPixelHandler.swift | 19 ++++++++++++------- .../Statistics/ATB/StatisticsLoader.swift | 11 +++++------ DuckDuckGo/Statistics/GeneralPixel.swift | 2 +- ...allationAttributionPixelHandlerTests.swift | 19 ++++++++++++------- .../ATB/StatisticsLoaderTests.swift | 1 + 6 files changed, 32 insertions(+), 21 deletions(-) diff --git a/DuckDuckGo/CrashReports/Model/CrashReporter.swift b/DuckDuckGo/CrashReports/Model/CrashReporter.swift index 00e8e69d63..df3d05fab4 100644 --- a/DuckDuckGo/CrashReports/Model/CrashReporter.swift +++ b/DuckDuckGo/CrashReports/Model/CrashReporter.swift @@ -17,6 +17,7 @@ // import Foundation +import PixelKit final class CrashReporter { diff --git a/DuckDuckGo/Statistics/ATB/InstallationAttributionPixelHandler.swift b/DuckDuckGo/Statistics/ATB/InstallationAttributionPixelHandler.swift index 9a0baf2bff..a14373e4b9 100644 --- a/DuckDuckGo/Statistics/ATB/InstallationAttributionPixelHandler.swift +++ b/DuckDuckGo/Statistics/ATB/InstallationAttributionPixelHandler.swift @@ -17,6 +17,7 @@ // import Foundation +import PixelKit /// A type that handles Pixels for acquisition attributions. protocol AttributionsPixelHandler: AnyObject { @@ -40,7 +41,7 @@ final class InstallationAttributionPixelHandler: AttributionsPixelHandler { /// - originProvider: A provider for the origin used to track the acquisition funnel. /// - locale: The locale of the device. init( - fireRequest: @escaping FireRequest = Pixel.fire, + fireRequest: @escaping FireRequest = PixelKit.fire, originProvider: AttributionOriginProvider = AttributionOriginFileProvider(), locale: Locale = .current ) { @@ -51,12 +52,14 @@ final class InstallationAttributionPixelHandler: AttributionsPixelHandler { func fireInstallationAttributionPixel() { fireRequest( - .installationAttribution, - .initial, + GeneralPixel.installationAttribution, + .standard, + [:], additionalParameters(origin: originProvider.origin, locale: locale.identifier), nil, + nil, true, - { _ in } + { _, _ in } ) } } @@ -77,11 +80,13 @@ private extension InstallationAttributionPixelHandler { extension InstallationAttributionPixelHandler { typealias FireRequest = ( - _ event: Pixel.Event, - _ repetition: Pixel.Event.Repetition, + _ event: PixelKit.Event, + _ frequency: PixelKit.Frequency, + _ headers: [String: String], _ parameters: [String: String]?, + _ error: Error?, _ allowedQueryReservedCharacters: CharacterSet?, _ includeAppVersionParameter: Bool, - _ onComplete: @escaping (Error?) -> Void + _ onComplete: @escaping (Bool, Error?) -> Void ) -> Void } diff --git a/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift b/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift index f67486649d..fd660d1a96 100644 --- a/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift +++ b/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift @@ -30,18 +30,18 @@ final class StatisticsLoader { private let statisticsStore: StatisticsStore private let emailManager: EmailManager -// private let attributionPixelHandler: AttributionsPixelHandler + private let attributionPixelHandler: AttributionsPixelHandler private let parser = AtbParser() private var isAppRetentionRequestInProgress = false init( statisticsStore: StatisticsStore = LocalStatisticsStore(), - emailManager: EmailManager = EmailManager() -// attributionPixelHandler: AttributionsPixelHandler = InstallationAttributionPixelHandler() + emailManager: EmailManager = EmailManager(), + attributionPixelHandler: AttributionsPixelHandler = InstallationAttributionPixelHandler() ) { self.statisticsStore = statisticsStore self.emailManager = emailManager -// self.attributionPixelHandler = attributionPixelHandler + self.attributionPixelHandler = attributionPixelHandler } func refreshRetentionAtb(isSearch: Bool, completion: @escaping Completion = {}) { @@ -101,8 +101,7 @@ final class StatisticsLoader { if let data = response?.data, let atb = try? self.parser.convert(fromJsonData: data) { self.requestExti(atb: atb, completion: completion) -// self.attributionPixelHandler.fireInstallationAttributionPixel() - PixelKit.fire(GeneralPixel.installationAttribution, frequency: .legacyInitial, withAdditionalParameters: []) + self.attributionPixelHandler.fireInstallationAttributionPixel() } else { completion() } diff --git a/DuckDuckGo/Statistics/GeneralPixel.swift b/DuckDuckGo/Statistics/GeneralPixel.swift index a3f8e19510..a60f6c298b 100644 --- a/DuckDuckGo/Statistics/GeneralPixel.swift +++ b/DuckDuckGo/Statistics/GeneralPixel.swift @@ -16,7 +16,7 @@ // limitations under the License. // -import Foundation +import AppKit import PixelKit import BrowserServicesKit import DDGSync diff --git a/UnitTests/Statistics/ATB/InstallationAttributionPixelHandlerTests.swift b/UnitTests/Statistics/ATB/InstallationAttributionPixelHandlerTests.swift index f94f1f402f..0d217cf769 100644 --- a/UnitTests/Statistics/ATB/InstallationAttributionPixelHandlerTests.swift +++ b/UnitTests/Statistics/ATB/InstallationAttributionPixelHandlerTests.swift @@ -17,6 +17,7 @@ // import XCTest +import PixelKit @testable import DuckDuckGo_Privacy_Browser final class InstallationAttributionPixelHandlerTests: XCTestCase { @@ -27,10 +28,12 @@ final class InstallationAttributionPixelHandlerTests: XCTestCase { override func setUpWithError() throws { try super.setUpWithError() capturedParams = CapturedParameters() - fireRequest = { event, repetition, parameters, reservedCharacters, includeAppVersion, onComplete in + fireRequest = { event, frequency, headers, parameters, error, reservedCharacters, includeAppVersion, onComplete in self.capturedParams.event = event - self.capturedParams.repetition = repetition + self.capturedParams.frequency = frequency + self.capturedParams.headers = headers self.capturedParams.parameters = parameters + self.capturedParams.error = error self.capturedParams.reservedCharacters = reservedCharacters self.capturedParams.includeAppVersion = includeAppVersion self.capturedParams.onComplete = onComplete @@ -97,7 +100,7 @@ final class InstallationAttributionPixelHandlerTests: XCTestCase { XCTAssertEqual(capturedParams?.parameters?[InstallationAttributionPixelHandler.Parameters.locale], "en-US") } - func testWhenPixelFiresThenAddAppVersionIsTrueAndRepetitionIsInitial() { + func testWhenPixelFiresThenAddAppVersionIsTrueAndFrequencyIsLegacyInitial() { // GIVEN sut = .init(fireRequest: fireRequest, originProvider: MockAttributionOriginProvider(), locale: .current) @@ -106,19 +109,21 @@ final class InstallationAttributionPixelHandlerTests: XCTestCase { // THEN XCTAssertEqual(capturedParams.includeAppVersion, true) - XCTAssertEqual(capturedParams.repetition, .initial) + XCTAssertEqual(capturedParams.frequency, .legacyInitial) } } extension InstallationAttributionPixelHandlerTests { struct CapturedParameters { - var event: Pixel.Event? - var repetition: Pixel.Event.Repetition = .repetitive + var event: PixelKit.Event? + var frequency: PixelKit.Frequency = .standard + var headers: [String: String] = [:] var parameters: [String: String]? + var error: Error? var reservedCharacters: CharacterSet? var includeAppVersion: Bool? - var onComplete: (Error?) -> Void = { _ in } + var onComplete: (Bool, Error?) -> Void = { _, _ in } } } diff --git a/UnitTests/Statistics/ATB/StatisticsLoaderTests.swift b/UnitTests/Statistics/ATB/StatisticsLoaderTests.swift index eb2772fd7c..4fa3f50a79 100644 --- a/UnitTests/Statistics/ATB/StatisticsLoaderTests.swift +++ b/UnitTests/Statistics/ATB/StatisticsLoaderTests.swift @@ -19,6 +19,7 @@ import XCTest import OHHTTPStubs import OHHTTPStubsSwift +import PixelKit @testable import DuckDuckGo_Privacy_Browser class StatisticsLoaderTests: XCTestCase { From 54c0cf06bb31effcf3a5682788e96ba74e6f22fe Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 8 Apr 2024 13:50:08 +0100 Subject: [PATCH 10/31] clearFrequencyHistoryForAllPixels implemented with new "reset Pixels Storage" debug menu --- DuckDuckGo/Menus/MainMenu.swift | 2 +- .../Bitwarden/Model/BWManager.swift | 5 +- DuckDuckGo/Statistics/GeneralPixel.swift | 6 +-- .../PixelKit/Sources/PixelKit/PixelKit.swift | 46 +++++++++++-------- 4 files changed, 33 insertions(+), 26 deletions(-) diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index 2e6c0d2639..c4f1248b7c 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -587,7 +587,7 @@ import SubscriptionUI NSMenuItem(title: "More Than 15 Days Ago", action: #selector(MainViewController.changeInstallDateToMoreThan15DaysAgo(_:))) } NSMenuItem(title: "Reset Email Protection InContext Signup Prompt", action: #selector(MainViewController.resetEmailProtectionInContextPrompt)) - NSMenuItem(title: "Reset Daily Pixels", action: #selector(MainViewController.resetDailyPixels)) + NSMenuItem(title: "Reset Pixels Storage", action: #selector(MainViewController.resetDailyPixels)) }.withAccessibilityIdentifier("MainMenu.resetData") NSMenuItem(title: "UI Triggers") { NSMenuItem(title: "Show Save Credentials Popover", action: #selector(MainViewController.showSaveCredentialsPopover)) diff --git a/DuckDuckGo/PasswordManager/Bitwarden/Model/BWManager.swift b/DuckDuckGo/PasswordManager/Bitwarden/Model/BWManager.swift index 1e03cf245a..cfc4a000dd 100644 --- a/DuckDuckGo/PasswordManager/Bitwarden/Model/BWManager.swift +++ b/DuckDuckGo/PasswordManager/Bitwarden/Model/BWManager.swift @@ -233,10 +233,7 @@ final class BWManager: BWManagement, ObservableObject { switch error { case "cannot-decrypt": logOrAssertionFailure("BWManagement: Bitwarden error - cannot decrypt") - -// if Pixel.Event.Repetition(key: "bitwardenRespondedCannotDecryptUnique", update: false) != .repetitive { // TODO: reimplement? -// PixelKit.fire(DebugEvent(GeneralPixel.bitwardenRespondedCannotDecryptUnique)) -// } + PixelKit.fire(DebugEvent(GeneralPixel.bitwardenRespondedCannotDecrypt), frequency: .daily) case "locked": if case let .connected(vault) = status { status = .connected(vault: vault.locked) diff --git a/DuckDuckGo/Statistics/GeneralPixel.swift b/DuckDuckGo/Statistics/GeneralPixel.swift index a60f6c298b..d57e63e4be 100644 --- a/DuckDuckGo/Statistics/GeneralPixel.swift +++ b/DuckDuckGo/Statistics/GeneralPixel.swift @@ -238,7 +238,7 @@ enum GeneralPixel: PixelKitEventV2 { case removedInvalidBookmarkManagedObjects case bitwardenNotResponding - case bitwardenRespondedCannotDecryptUnique // (repetition: Repetition = .init(key: "bitwardenRespondedCannotDecryptUnique")) // TODO: REIMPLEMENTATION?? + case bitwardenRespondedCannotDecrypt case bitwardenHandshakeFailed case bitwardenDecryptionOfSharedKeyFailed case bitwardenStoringOfTheSharedKeyFailed @@ -681,8 +681,8 @@ enum GeneralPixel: PixelKitEventV2 { case .bitwardenNotResponding: return "bitwarden_not_responding" - case .bitwardenRespondedCannotDecryptUnique: - return "bitwarden_responded_cannot_decrypt_unique" + case .bitwardenRespondedCannotDecrypt: + return "bitwarden_responded_cannot_decrypt_d" case .bitwardenHandshakeFailed: return "bitwarden_handshake_failed" case .bitwardenDecryptionOfSharedKeyFailed: diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift index 797208010b..13d27a6ee8 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift @@ -185,12 +185,12 @@ public final class PixelKit { switch frequency { case .standard: - assertIf(name: pixelName, endsWith: "_u") - assertIf(name: pixelName, endsWith: "_d") + reportErrorIf(pixel: pixelName, endsWith: "_u") + reportErrorIf(pixel: pixelName, endsWith: "_d") fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, onComplete, frequency) case .legacyInitial: - assertIf(name: pixelName, endsWith: "_u") - assertIf(name: pixelName, endsWith: "_d") + reportErrorIf(pixel: pixelName, endsWith: "_u") + reportErrorIf(pixel: pixelName, endsWith: "_d") if !pixelHasBeenFiredEver(pixelName) { fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, onComplete, frequency) updatePixelLastFireDate(pixelName: pixelName) @@ -198,7 +198,7 @@ public final class PixelKit { printDebugInfo(pixelName: pixelName, frequency: frequency, parameters: newParams, skipped: true) } case .unique: - assertIf(name: pixelName, endsWith: "_d") + reportErrorIf(pixel: pixelName, endsWith: "_d") guard pixelName.hasSuffix("_u") else { assertionFailure("Unique pixel: must end with _u") return @@ -210,8 +210,8 @@ public final class PixelKit { printDebugInfo(pixelName: pixelName, frequency: frequency, parameters: newParams, skipped: true) } case .daily: - assertIf(name: pixelName, endsWith: "_u") - assertIf(name: pixelName, endsWith: "_d") // Because is added automatically + reportErrorIf(pixel: pixelName, endsWith: "_u") + reportErrorIf(pixel: pixelName, endsWith: "_d") // Because is added automatically if !pixelHasBeenFiredToday(pixelName) { fireRequestWrapper(pixelName + "_d", headers, newParams, allowedQueryReservedCharacters, true, onComplete, frequency) updatePixelLastFireDate(pixelName: pixelName) @@ -219,9 +219,9 @@ public final class PixelKit { printDebugInfo(pixelName: pixelName + "_d", frequency: frequency, parameters: newParams, skipped: true) } case .dailyAndCount: - assertIf(name: pixelName, endsWith: "_u") - assertIf(name: pixelName, endsWith: "_d") // Because is added automatically - assertIf(name: pixelName, endsWith: "_c") // Because is added automatically + reportErrorIf(pixel: pixelName, endsWith: "_u") + reportErrorIf(pixel: pixelName, endsWith: "_d") // Because is added automatically + reportErrorIf(pixel: pixelName, endsWith: "_c") // Because is added automatically if !pixelHasBeenFiredToday(pixelName) { fireRequestWrapper(pixelName + "_d", headers, newParams, allowedQueryReservedCharacters, true, onComplete, frequency) updatePixelLastFireDate(pixelName: pixelName) @@ -233,9 +233,11 @@ public final class PixelKit { } } - func assertIf(name: String, endsWith forbiddenString: String) { - if name.hasSuffix(forbiddenString) { - assertionFailure("Pixel \(name) must not end with \(forbiddenString)") + /// If the pixel name ends with the forbiddenString then an error is logged or an assertion failure is fired in debug + func reportErrorIf(pixel: String, endsWith forbiddenString: String) { + if pixel.hasSuffix(forbiddenString) { + logger.error("Pixel \(pixel) must not end with \(forbiddenString)") + assertionFailure("Pixel \(pixel) must not end with \(forbiddenString)") } } @@ -407,20 +409,28 @@ public final class PixelKit { } public static func clearFrequencyHistoryForAllPixels() { - // TODO: Implement - assertionFailure("To be implemented") + guard let userDefaults = Self.shared?.defaults else { return } + for (key, _) in userDefaults.dictionaryRepresentation() { + if key.hasPrefix(storageKeyPrefixLegacy) || key.hasPrefix(storageKeyPrefix) { + userDefaults.removeObject(forKey: key) + PixelKit.shared?.logger.debug("🚮 Removing from storage \(key)") + } + } } + static let storageKeyPrefixLegacy = "com.duckduckgo.network-protection.pixel." + static let storageKeyPrefix = "com.duckduckgo.network-protection.pixel." + /// Initially PixelKit was configured only for serving netP so these very specific keys were used, now PixelKit serves the entire app so we need to move away from them. /// NOTE: I would remove this 6 months after release private func legacyUserDefaultsKeyName(forPixelName pixelName: String) -> String { dryRun - ? "com.duckduckgo.network-protection.pixel.\(pixelName).dry-run" - : "com.duckduckgo.network-protection.pixel.\(pixelName)" + ? "\(Self.storageKeyPrefixLegacy)\(pixelName).dry-run" + : "\(Self.storageKeyPrefixLegacy)\(pixelName)" } private func userDefaultsKeyName(forPixelName pixelName: String) -> String { - return "com.duckduckgo.pixel-date.\(pixelName)\( dryRun ? ".dry-run" : "" )" + return "\(Self.storageKeyPrefix)\(pixelName)\( dryRun ? ".dry-run" : "" )" } } From 1e15a9b7c98233e8d22157cb26739b5699e0d948 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 8 Apr 2024 15:13:18 +0100 Subject: [PATCH 11/31] Update sandbox-test-tool.xcscheme --- .../xcshareddata/xcschemes/sandbox-test-tool.xcscheme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme index 41730d7069..eb7e5e26bb 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme @@ -1,7 +1,7 @@ + version = "1.8"> Date: Mon, 8 Apr 2024 15:33:28 +0100 Subject: [PATCH 12/31] reverting unrelated changes --- DuckDuckGo/Common/Extensions/StringExtension.swift | 2 +- DuckDuckGo/Common/View/AppKit/HoverTrackingArea.swift | 2 +- DuckDuckGo/DataImport/ThirdPartyBrowser.swift | 2 +- DuckDuckGo/DataImport/View/DataImportProfilePicker.swift | 2 +- DuckDuckGo/DataImport/View/DataImportSummaryView.swift | 2 +- DuckDuckGo/DataImport/View/DataImportView.swift | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo/Common/Extensions/StringExtension.swift b/DuckDuckGo/Common/Extensions/StringExtension.swift index e117cde536..eeb8e5bedb 100644 --- a/DuckDuckGo/Common/Extensions/StringExtension.swift +++ b/DuckDuckGo/Common/Extensions/StringExtension.swift @@ -55,7 +55,7 @@ extension String { "^": "^", "`": "a", "{": "{", - "}": "}" + "}": "}", ] func escapedUnicodeHtmlString() -> String { var result = "" diff --git a/DuckDuckGo/Common/View/AppKit/HoverTrackingArea.swift b/DuckDuckGo/Common/View/AppKit/HoverTrackingArea.swift index 59b1ed25f2..718e7ea4c0 100644 --- a/DuckDuckGo/Common/View/AppKit/HoverTrackingArea.swift +++ b/DuckDuckGo/Common/View/AppKit/HoverTrackingArea.swift @@ -69,7 +69,7 @@ final class HoverTrackingArea: NSTrackingArea { owner.observe(\.backgroundInset) { [weak self] _, _ in self?.updateLayer() }, (owner as? NSControl)?.observe(\.isEnabled) { [weak self] _, _ in self?.updateLayer(animated: false) }, owner.observe(\.isMouseDown) { [weak self] _, _ in self?.mouseDownDidChange() }, - owner.observe(\.window) { [weak self] _, _ in self?.viewWindowDidChange() } + owner.observe(\.window) { [weak self] _, _ in self?.viewWindowDidChange() }, ].compactMap { $0 } } diff --git a/DuckDuckGo/DataImport/ThirdPartyBrowser.swift b/DuckDuckGo/DataImport/ThirdPartyBrowser.swift index e75589cb05..c749feeb1c 100644 --- a/DuckDuckGo/DataImport/ThirdPartyBrowser.swift +++ b/DuckDuckGo/DataImport/ThirdPartyBrowser.swift @@ -263,7 +263,7 @@ enum ThirdPartyBrowser: CaseIterable { applicationSupportURL.appendingPathComponent("Google/Chrome/"), applicationSupportURL.appendingPathComponent("Google/Chrome Beta/"), applicationSupportURL.appendingPathComponent("Google/Chrome Dev/"), - applicationSupportURL.appendingPathComponent("Google/Chrome Canary/") + applicationSupportURL.appendingPathComponent("Google/Chrome Canary/"), ] case .chromium: [applicationSupportURL.appendingPathComponent("Chromium/")] case .coccoc: [applicationSupportURL.appendingPathComponent("Coccoc/")] diff --git a/DuckDuckGo/DataImport/View/DataImportProfilePicker.swift b/DuckDuckGo/DataImport/View/DataImportProfilePicker.swift index a6d9257acf..98f87e6358 100644 --- a/DuckDuckGo/DataImport/View/DataImportProfilePicker.swift +++ b/DuckDuckGo/DataImport/View/DataImportProfilePicker.swift @@ -90,7 +90,7 @@ struct DataImportProfilePicker: View { .init(browser: .chrome, profileURL: URL(fileURLWithPath: "/Chrome Dev/Profile 1")), .init(browser: .chrome, - profileURL: URL(fileURLWithPath: "/Chrome Canary/Profile 2")) + profileURL: URL(fileURLWithPath: "/Chrome Canary/Profile 2")), ], validateProfileData: { _ in { .init(logins: .available, bookmarks: .available) } }), selectedProfile: Binding { .init(browser: .chrome, profileURL: URL(fileURLWithPath: "/test/Profile 1")) diff --git a/DuckDuckGo/DataImport/View/DataImportSummaryView.swift b/DuckDuckGo/DataImport/View/DataImportSummaryView.swift index 17205fe225..b385a90c66 100644 --- a/DuckDuckGo/DataImport/View/DataImportSummaryView.swift +++ b/DuckDuckGo/DataImport/View/DataImportSummaryView.swift @@ -162,7 +162,7 @@ private func skippedImage() -> some View { // .init(.bookmarks, .failure(DataImportViewModel.TestImportError(action: .bookmarks, errorType: .dataCorrupted))), // .init(.bookmarks, .failure(DataImportViewModel.TestImportError(action: .passwords, errorType: .keychainError))), .init(.passwords, .failure(DataImportViewModel.TestImportError(action: .passwords, errorType: .keychainError))), - .init(.passwords, .failure(DataImportViewModel.TestImportError(action: .passwords, errorType: .keychainError))) + .init(.passwords, .failure(DataImportViewModel.TestImportError(action: .passwords, errorType: .keychainError))), ])) .padding(EdgeInsets(top: 20, leading: 20, bottom: 16, trailing: 20)) Spacer() diff --git a/DuckDuckGo/DataImport/View/DataImportView.swift b/DuckDuckGo/DataImport/View/DataImportView.swift index 35f4969f89..2302b54421 100644 --- a/DuckDuckGo/DataImport/View/DataImportView.swift +++ b/DuckDuckGo/DataImport/View/DataImportView.swift @@ -491,7 +491,7 @@ extension DataImportViewModel.ButtonType { .init(browser: .chrome, profileURL: URL(fileURLWithPath: "/test/Profile 1")), .init(browser: .chrome, - profileURL: URL(fileURLWithPath: "/test/Profile 2")) + profileURL: URL(fileURLWithPath: "/test/Profile 2")), ], validateProfileData: { _ in { .init(logins: .available, bookmarks: .available) } // swiftlint:disable:this opening_brace }) } dataImporterFactory: { source, type, _, _ in From a452537cbde1d0bc27617c631d59b862596ef3fa Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 8 Apr 2024 15:57:56 +0100 Subject: [PATCH 13/31] pixel logs fired even in dry mode --- .../PixelKit/Sources/PixelKit/PixelKit.swift | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift index 8fa84c4713..517da3afc9 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift @@ -149,15 +149,7 @@ public final class PixelKit { self.dateGenerator = dateGenerator self.defaults = defaults self.fireRequest = fireRequest - - logger.debug(""" - PixelKit initialised: - dryRun: \(self.dryRun) - appVersion: \(self.appVersion) - source: \(self.source ?? "-") - defaultHeaders: \(self.defaultHeaders) - pixelCalendar: \(self.pixelCalendar) - """) + logger.debug("👾 PixelKit initialised: dryRun: \(self.dryRun) appVersion: \(self.appVersion) source: \(self.source ?? "-") defaultHeaders: \(self.defaultHeaders) pixelCalendar: \(self.pixelCalendar)") } // swiftlint:disable:next function_body_length cyclomatic_complexity @@ -254,16 +246,16 @@ public final class PixelKit { _ callBackOnMainThread: Bool, _ onComplete: @escaping CompletionBlock, _ frequency: Frequency) { - guard !dryRun else { printDebugInfo(pixelName: pixelName, frequency: frequency, parameters: parameters, skipped: false) - // simulate server response time for Dry Run mode - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - onComplete(true, nil) + guard !dryRun else { + // simulate server response time for Dry Run mode + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + onComplete(true, nil) + } + return } - return + fireRequest(pixelName, headers, parameters, allowedQueryReservedCharacters, callBackOnMainThread, onComplete) } - fireRequest(pixelName, headers, parameters, allowedQueryReservedCharacters, callBackOnMainThread, onComplete) - } private func prefixedName(for event: Event) -> String { if event.name.hasPrefix("m_mac_") { From 746a29d4130c4fbbefe6ec8b65a0bc587aedad23 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 8 Apr 2024 17:31:13 +0100 Subject: [PATCH 14/31] .legacyDaily pixel frequency added for support daily --- .../xcschemes/sandbox-test-tool.xcscheme | 2 +- .../MacPacketTunnelProvider.swift | 4 +- .../Statistics/ATB/StatisticsLoader.swift | 2 +- .../Pixels/VPNPrivacyProPixelTests.swift | 2 + .../PixelKit/Sources/PixelKit/PixelKit.swift | 16 +++++- .../XCTestCase+PixelKit.swift | 12 +++-- .../PixelKitParametersTests.swift | 1 + .../NetworkProtectionPixelEventTests.swift | 51 +++++++++++++++++++ 8 files changed, 81 insertions(+), 9 deletions(-) diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme index eb7e5e26bb..41730d7069 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme @@ -1,7 +1,7 @@ + version = "1.7"> Date: Tue, 9 Apr 2024 10:02:28 +0100 Subject: [PATCH 15/31] PixelKit Unit tests fixed --- .../NetworkProtectionPixelEvent.swift | 107 +----------------- .../InstallationAttributionPixelHandler.swift | 2 +- .../XCTestCase+PixelKit.swift | 40 +++++-- .../NetworkProtectionPixelEventTests.swift | 2 +- 4 files changed, 41 insertions(+), 110 deletions(-) diff --git a/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift b/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift index efa517356a..d69126d5f9 100644 --- a/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift +++ b/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift @@ -256,148 +256,51 @@ enum NetworkProtectionPixelEvent: PixelKitEventV2 { switch self { case .networkProtectionKeychainErrorFailedToCastKeychainValueToData(let field): return [PixelKit.Parameters.keychainFieldName: field] - case .networkProtectionKeychainReadError(let field, let status): return [ PixelKit.Parameters.keychainFieldName: field, PixelKit.Parameters.errorCode: String(status) ] - case .networkProtectionKeychainWriteError(let field, let status): return [ PixelKit.Parameters.keychainFieldName: field, PixelKit.Parameters.errorCode: String(status) ] - case .networkProtectionKeychainUpdateError(let field, let status): return [ PixelKit.Parameters.keychainFieldName: field, PixelKit.Parameters.errorCode: String(status) ] - case .networkProtectionKeychainDeleteError(let status): - return [ - PixelKit.Parameters.errorCode: String(status) - ] - + return [PixelKit.Parameters.errorCode: String(status)] case .networkProtectionClientFailedToFetchServerList(let error): return error?.pixelParameters - case .networkProtectionClientFailedToFetchRegisteredServers(let error): return error?.pixelParameters - case .networkProtectionClientFailedToRedeemInviteCode(let error): return error?.pixelParameters - case .networkProtectionClientFailedToFetchLocations(let error): return error?.pixelParameters - case .networkProtectionClientFailedToParseLocationsResponse(let error): return error?.pixelParameters - case .networkProtectionUnhandledError(let function, let line, let error): var parameters = error.pixelParameters parameters[PixelKit.Parameters.function] = function parameters[PixelKit.Parameters.line] = String(line) return parameters - case .networkProtectionWireguardErrorCannotSetNetworkSettings(let error): return error.pixelParameters - case .networkProtectionWireguardErrorCannotStartWireguardBackend(code: let code): - return [ - PixelKit.Parameters.errorCode: String(code) - ] - + return [PixelKit.Parameters.errorCode: String(code)] case .networkProtectionWireguardErrorInvalidState(reason: let reason): - return [ - PixelKit.Parameters.reason: reason - ] - - case .networkProtectionTunnelConfigurationNoServerRegistrationInfo, - .networkProtectionTunnelConfigurationCouldNotSelectClosestServer, - .networkProtectionTunnelConfigurationCouldNotGetPeerPublicKey, - .networkProtectionTunnelConfigurationCouldNotGetPeerHostName, - .networkProtectionTunnelConfigurationCouldNotGetInterfaceAddressRange, - .networkProtectionClientFailedToParseServerListResponse, - .networkProtectionClientFailedToEncodeRegisterKeyRequest, - .networkProtectionClientFailedToParseRegisteredServersResponse, - .networkProtectionClientFailedToParseRedeemResponse, - .networkProtectionClientInvalidInviteCode, - .networkProtectionClientFailedToEncodeRedeemRequest, - .networkProtectionClientInvalidAuthToken, - .networkProtectionNoAuthTokenFoundError, - .networkProtectionRekeyAttempt, - .networkProtectionRekeyCompleted, - .networkProtectionRekeyFailure, - .networkProtectionWireguardErrorCannotLocateTunnelFileDescriptor, - .networkProtectionWireguardErrorFailedDNSResolution, - .networkProtectionSystemExtensionActivationFailure, - .networkProtectionActiveUser, - .networkProtectionNewUser, - .networkProtectionEnableAttemptConnecting, - .networkProtectionEnableAttemptSuccess, - .networkProtectionEnableAttemptFailure, - .networkProtectionTunnelFailureDetected, - .networkProtectionTunnelFailureRecovered, - .networkProtectionLatency, - .networkProtectionLatencyError, - .networkProtectionControllerStartAttempt, - .networkProtectionControllerStartSuccess, - .networkProtectionControllerStartFailure, - .networkProtectionTunnelStartAttempt, - .networkProtectionTunnelStartSuccess, - .networkProtectionTunnelStartFailure, - .networkProtectionTunnelUpdateAttempt, - .networkProtectionTunnelUpdateSuccess, - .networkProtectionTunnelUpdateFailure: - + return [PixelKit.Parameters.reason: reason] + default: return nil } } var error: (any Error)? { switch self { - case .networkProtectionActiveUser, - .networkProtectionNewUser, - .networkProtectionControllerStartAttempt, - .networkProtectionControllerStartSuccess, - .networkProtectionTunnelStartAttempt, - .networkProtectionTunnelStartSuccess, - .networkProtectionTunnelUpdateAttempt, - .networkProtectionTunnelUpdateSuccess, - .networkProtectionEnableAttemptConnecting, - .networkProtectionEnableAttemptSuccess, - .networkProtectionEnableAttemptFailure, - .networkProtectionTunnelFailureDetected, - .networkProtectionTunnelFailureRecovered, - .networkProtectionLatencyError, - .networkProtectionLatency, - .networkProtectionTunnelConfigurationNoServerRegistrationInfo, - .networkProtectionTunnelConfigurationCouldNotGetPeerPublicKey, - .networkProtectionTunnelConfigurationCouldNotGetPeerHostName, - .networkProtectionTunnelConfigurationCouldNotGetInterfaceAddressRange, - .networkProtectionClientFailedToParseServerListResponse, - .networkProtectionClientFailedToEncodeRegisterKeyRequest, - .networkProtectionClientFailedToParseRegisteredServersResponse, - .networkProtectionTunnelConfigurationCouldNotSelectClosestServer, - .networkProtectionClientFailedToEncodeRedeemRequest, - .networkProtectionClientInvalidInviteCode, - .networkProtectionClientInvalidAuthToken, - .networkProtectionKeychainErrorFailedToCastKeychainValueToData, - .networkProtectionKeychainReadError, - .networkProtectionKeychainWriteError, - .networkProtectionKeychainUpdateError, - .networkProtectionKeychainDeleteError, - .networkProtectionWireguardErrorCannotLocateTunnelFileDescriptor, - .networkProtectionWireguardErrorInvalidState, - .networkProtectionWireguardErrorFailedDNSResolution, - .networkProtectionWireguardErrorCannotStartWireguardBackend, - .networkProtectionNoAuthTokenFoundError, - .networkProtectionRekeyAttempt, - .networkProtectionRekeyCompleted, - .networkProtectionSystemExtensionActivationFailure: - return nil case .networkProtectionClientFailedToRedeemInviteCode(let error), .networkProtectionClientFailedToFetchLocations(let error), .networkProtectionClientFailedToParseLocationsResponse(let error), @@ -412,6 +315,8 @@ enum NetworkProtectionPixelEvent: PixelKitEventV2 { .networkProtectionRekeyFailure(let error), .networkProtectionUnhandledError(_, _, let error): return error + default: + return nil } } } diff --git a/DuckDuckGo/Statistics/ATB/InstallationAttributionPixelHandler.swift b/DuckDuckGo/Statistics/ATB/InstallationAttributionPixelHandler.swift index a7d8dbe4b5..b26f411d9c 100644 --- a/DuckDuckGo/Statistics/ATB/InstallationAttributionPixelHandler.swift +++ b/DuckDuckGo/Statistics/ATB/InstallationAttributionPixelHandler.swift @@ -53,7 +53,7 @@ final class InstallationAttributionPixelHandler: AttributionsPixelHandler { func fireInstallationAttributionPixel() { fireRequest( GeneralPixel.installationAttribution, - .standard, + .legacyInitial, [:], additionalParameters(origin: originProvider.origin, locale: locale.identifier), nil, diff --git a/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift index 9f9d58d665..49546e9095 100644 --- a/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift @@ -101,11 +101,14 @@ public extension XCTestCase { meets expectations: PixelFireExpectations, file: StaticString, line: UInt) { - - let expectedPixelName = event.name.hasPrefix(Self.pixelPlatformPrefix) ? event.name : Self.pixelPlatformPrefix + event.name + let expectedPixelNames: [String] = expectedPixelNames(originalName: event.name, frequency: frequency) let knownExpectedParameters = knownExpectedParameters(for: event) let callbackExecutedExpectation = expectation(description: "The PixelKit callback has been executed") + if frequency == .dailyAndCount { + callbackExecutedExpectation.expectedFulfillmentCount = 2 + } + // Ensure PixelKit is torn down before setting it back up, avoiding unit test race conditions: PixelKit.tearDown() @@ -119,15 +122,16 @@ public extension XCTestCase { let firedParameters = Self.filterStandardPixelParameters(from: firedParameters) // Internal validations - - XCTAssertEqual(firedPixelName, expectedPixelName, file: file, line: line) - + XCTAssertTrue(expectedPixelNames.contains(firedPixelName), file: file, line: line) XCTAssertTrue(knownExpectedParameters.allSatisfy { (key, value) in firedParameters[key] == value }) - // Expectations - XCTAssertEqual(firedPixelName, expectations.pixelName) + if frequency == .dailyAndCount { + XCTAssertTrue(firedPixelName.hasSuffix("_c") || firedPixelName.hasSuffix("_d")) + } else { + XCTAssertEqual(expectations.pixelName, firedPixelName) + } XCTAssertEqual(firedParameters, expectations.parameters) completion(true, nil) @@ -136,4 +140,26 @@ public extension XCTestCase { PixelKit.fire(event, frequency: frequency) waitForExpectations(timeout: 0.1) } + + func expectedPixelNames(originalName: String, frequency: PixelKit.Frequency) -> [String] { + let expectedPixelNameWithoutSuffix = originalName.hasPrefix(Self.pixelPlatformPrefix) ? originalName : Self.pixelPlatformPrefix + originalName + var expectedPixelNames: [String] = [] + + switch frequency { + case .standard: + expectedPixelNames.append(expectedPixelNameWithoutSuffix) + case .legacyInitial: + expectedPixelNames.append(expectedPixelNameWithoutSuffix) + case .unique: + expectedPixelNames.append(expectedPixelNameWithoutSuffix) + case .legacyDaily: + expectedPixelNames.append(expectedPixelNameWithoutSuffix) + case .daily: + expectedPixelNames.append(expectedPixelNameWithoutSuffix.appending("_d")) + case .dailyAndCount: + expectedPixelNames.append(expectedPixelNameWithoutSuffix.appending("_d")) + expectedPixelNames.append(expectedPixelNameWithoutSuffix.appending("_c")) + } + return expectedPixelNames + } } diff --git a/UnitTests/NetworkProtection/NetworkProtectionPixelEventTests.swift b/UnitTests/NetworkProtection/NetworkProtectionPixelEventTests.swift index 57c0f9d63c..00bdd1e138 100644 --- a/UnitTests/NetworkProtection/NetworkProtectionPixelEventTests.swift +++ b/UnitTests/NetworkProtection/NetworkProtectionPixelEventTests.swift @@ -155,7 +155,7 @@ final class NetworkProtectionPixelEventTests: XCTestCase { file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionLatencyError, - frequency: .daily, + frequency: .legacyDaily, and: .expect(pixelName: "m_mac_netp_ev_latency_error"), file: #filePath, line: #line) From 88d0f27cde3990fc08b5e2c64afb51e39bc06af6 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 9 Apr 2024 15:12:28 +0100 Subject: [PATCH 16/31] unit tests fix --- .../Tests/PixelKitTests/PixelKitTests.swift | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift b/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift index 04d7f7c300..fed5b7789d 100644 --- a/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift +++ b/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift @@ -249,17 +249,17 @@ final class PixelKitTests: XCTestCase { // Run test pixelKit.fire(event, frequency: .daily) // Fired timeMachine.travel(by: .hour, value: 2) - pixelKit.fire(event, frequency: .dailyOnly) // Skipped + pixelKit.fire(event, frequency: .legacyDaily) // Skipped timeMachine.travel(by: .day, value: 1) timeMachine.travel(by: .hour, value: 2) - pixelKit.fire(event, frequency: .dailyOnly) // Fired + pixelKit.fire(event, frequency: .legacyDaily) // Fired timeMachine.travel(by: .hour, value: 10) - pixelKit.fire(event, frequency: .dailyOnly) // Skipped + pixelKit.fire(event, frequency: .legacyDaily) // Skipped timeMachine.travel(by: .day, value: 1) - pixelKit.fire(event, frequency: .dailyOnly) // Fired + pixelKit.fire(event, frequency: .legacyDaily) // Fired // Wait for expectations to be fulfilled wait(for: [fireCallbackCalled], timeout: 0.5) @@ -292,17 +292,17 @@ final class PixelKitTests: XCTestCase { // Run test pixelKit.fire(event, frequency: .unique) // Fired timeMachine.travel(by: .hour, value: 2) - pixelKit.fire(event, frequency: .justOnce) // Skipped (already fired) + pixelKit.fire(event, frequency: .unique) // Skipped (already fired) timeMachine.travel(by: .day, value: 1) timeMachine.travel(by: .hour, value: 2) - pixelKit.fire(event, frequency: .justOnce) // Skipped (already fired) + pixelKit.fire(event, frequency: .unique) // Skipped (already fired) timeMachine.travel(by: .hour, value: 10) - pixelKit.fire(event, frequency: .justOnce) // Skipped (already fired) + pixelKit.fire(event, frequency: .unique) // Skipped (already fired) timeMachine.travel(by: .day, value: 1) - pixelKit.fire(event, frequency: .justOnce) // Skipped (already fired) + pixelKit.fire(event, frequency: .unique) // Skipped (already fired) // Wait for expectations to be fulfilled wait(for: [fireCallbackCalled], timeout: 0.5) @@ -324,7 +324,6 @@ final class PixelKitTests: XCTestCase { PixelKit.setUp(appVersion: "test", defaultHeaders: [:], - log: .disabled, dailyPixelCalendar: calendar, dateGenerator: timeMachine.now, defaults: userDefaults()) { _, _, _, _, _, _ in } From 53bbb6b54fd8c729a7f05b5d4d31482ffb28cb38 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 10 Apr 2024 15:44:26 +0100 Subject: [PATCH 17/31] source change --- LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift index 14018a2ea5..0f96c2e944 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift @@ -73,6 +73,13 @@ public final class PixelKit { public static let client = "X-DuckDuckGo-Client" } + public enum Source: String { + case macStore = "browser-appstore" + case macDMG = "browser-dmg" + case iOS = "" + case iPadOS = "" + } + /// A closure typealias to request sending pixels through the network. public typealias FireRequest = ( _ pixelName: String, From 442c0b7125d7a84ac16e90bbd823d9f9ffef0889 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 11 Apr 2024 10:55:41 +0100 Subject: [PATCH 18/31] Diego's review comments improvements --- DuckDuckGo/Menus/MainMenuActions.swift | 8 +-- .../PixelKit/Sources/PixelKit/PixelKit.swift | 49 +++++++++++-------- .../ATB/StatisticsLoaderTests.swift | 4 +- .../CBRCompileTimeReporterTests.swift | 3 +- 4 files changed, 36 insertions(+), 28 deletions(-) diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index c1df096636..c94b028622 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -740,13 +740,13 @@ extension MainViewController { UserDefaults.netP.networkProtectionEntitlementsExpired = false // Clear pixel data - PixelKit.clearFrequencyHistoryFor(pixel: PrivacyProPixel.privacyProFeatureEnabled) - PixelKit.clearFrequencyHistoryFor(pixel: PrivacyProPixel.privacyProBetaUserThankYouDBP) - PixelKit.clearFrequencyHistoryFor(pixel: PrivacyProPixel.privacyProBetaUserThankYouVPN) + PixelKit.shared?.clearFrequencyHistoryFor(pixel: PrivacyProPixel.privacyProFeatureEnabled) + PixelKit.shared?.clearFrequencyHistoryFor(pixel: PrivacyProPixel.privacyProBetaUserThankYouDBP) + PixelKit.shared?.clearFrequencyHistoryFor(pixel: PrivacyProPixel.privacyProBetaUserThankYouVPN) } @objc func resetDailyPixels(_ sender: Any?) { - PixelKit.clearFrequencyHistoryForAllPixels() + PixelKit.shared?.clearFrequencyHistoryForAllPixels() } @objc func in10PercentSurveyOn(_ sender: Any?) { diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift index 0f96c2e944..9a4b501e0e 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift @@ -76,8 +76,8 @@ public final class PixelKit { public enum Source: String { case macStore = "browser-appstore" case macDMG = "browser-dmg" - case iOS = "" - case iPadOS = "" + case iOS = "phone" + case iPadOS = "tablet" } /// A closure typealias to request sending pixels through the network. @@ -105,7 +105,8 @@ public final class PixelKit { private let dateGenerator: () -> Date - public static var shared: PixelKit? + public private(set) static var shared: PixelKit? + private let appVersion: String private let defaultHeaders: [String: String] private let fireRequest: FireRequest @@ -189,12 +190,12 @@ public final class PixelKit { case .standard: reportErrorIf(pixel: pixelName, endsWith: "_u") reportErrorIf(pixel: pixelName, endsWith: "_d") - fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, onComplete, frequency) + fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, frequency, onComplete) case .legacyInitial: reportErrorIf(pixel: pixelName, endsWith: "_u") reportErrorIf(pixel: pixelName, endsWith: "_d") if !pixelHasBeenFiredEver(pixelName) { - fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, onComplete, frequency) + fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, frequency, onComplete) updatePixelLastFireDate(pixelName: pixelName) } else { printDebugInfo(pixelName: pixelName, frequency: frequency, parameters: newParams, skipped: true) @@ -206,7 +207,7 @@ public final class PixelKit { return } if !pixelHasBeenFiredEver(pixelName) { - fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, onComplete, frequency) + fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, frequency, onComplete) updatePixelLastFireDate(pixelName: pixelName) } else { printDebugInfo(pixelName: pixelName, frequency: frequency, parameters: newParams, skipped: true) @@ -215,7 +216,7 @@ public final class PixelKit { reportErrorIf(pixel: pixelName, endsWith: "_u") reportErrorIf(pixel: pixelName, endsWith: "_d") if !pixelHasBeenFiredToday(pixelName) { - fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, onComplete, frequency) + fireRequestWrapper(pixelName, headers, newParams, allowedQueryReservedCharacters, true, frequency, onComplete) updatePixelLastFireDate(pixelName: pixelName) } else { printDebugInfo(pixelName: pixelName, frequency: frequency, parameters: newParams, skipped: true) @@ -224,7 +225,7 @@ public final class PixelKit { reportErrorIf(pixel: pixelName, endsWith: "_u") reportErrorIf(pixel: pixelName, endsWith: "_d") // Because is added automatically if !pixelHasBeenFiredToday(pixelName) { - fireRequestWrapper(pixelName + "_d", headers, newParams, allowedQueryReservedCharacters, true, onComplete, frequency) + fireRequestWrapper(pixelName + "_d", headers, newParams, allowedQueryReservedCharacters, true, frequency, onComplete) updatePixelLastFireDate(pixelName: pixelName) } else { printDebugInfo(pixelName: pixelName + "_d", frequency: frequency, parameters: newParams, skipped: true) @@ -234,13 +235,13 @@ public final class PixelKit { reportErrorIf(pixel: pixelName, endsWith: "_d") // Because is added automatically reportErrorIf(pixel: pixelName, endsWith: "_c") // Because is added automatically if !pixelHasBeenFiredToday(pixelName) { - fireRequestWrapper(pixelName + "_d", headers, newParams, allowedQueryReservedCharacters, true, onComplete, frequency) + fireRequestWrapper(pixelName + "_d", headers, newParams, allowedQueryReservedCharacters, true, frequency, onComplete) updatePixelLastFireDate(pixelName: pixelName) } else { printDebugInfo(pixelName: pixelName + "_d", frequency: frequency, parameters: newParams, skipped: true) } - fireRequestWrapper(pixelName + "_c", headers, newParams, allowedQueryReservedCharacters, true, onComplete, frequency) + fireRequestWrapper(pixelName + "_c", headers, newParams, allowedQueryReservedCharacters, true, frequency, onComplete) } } @@ -263,8 +264,8 @@ public final class PixelKit { _ parameters: [String: String], _ allowedQueryReservedCharacters: CharacterSet?, _ callBackOnMainThread: Bool, - _ onComplete: @escaping CompletionBlock, - _ frequency: Frequency) { + _ frequency: Frequency, + _ onComplete: @escaping CompletionBlock) { printDebugInfo(pixelName: pixelName, frequency: frequency, parameters: parameters, skipped: false) guard !dryRun else { // simulate server response time for Dry Run mode @@ -422,19 +423,18 @@ public final class PixelKit { pixelLastFireDate(pixelName: name) != nil } - public static func clearFrequencyHistoryFor(pixel: PixelKitEventV2) { + public func clearFrequencyHistoryFor(pixel: PixelKitEventV2) { guard let name = Self.shared?.userDefaultsKeyName(forPixelName: pixel.name) else { return } - Self.shared?.defaults.removeObject(forKey: name) + self.defaults.removeObject(forKey: name) } - public static func clearFrequencyHistoryForAllPixels() { - guard let userDefaults = Self.shared?.defaults else { return } - for (key, _) in userDefaults.dictionaryRepresentation() { - if key.hasPrefix(storageKeyPrefixLegacy) || key.hasPrefix(storageKeyPrefix) { - userDefaults.removeObject(forKey: key) - PixelKit.shared?.logger.debug("🚮 Removing from storage \(key)") + public func clearFrequencyHistoryForAllPixels() { + for (key, _) in self.defaults.dictionaryRepresentation() { + if key.hasPrefix(Self.storageKeyPrefixLegacy) || key.hasPrefix(Self.storageKeyPrefix) { + self.defaults.removeObject(forKey: key) + self.logger.debug("🚮 Removing from storage \(key)") } } } @@ -463,3 +463,12 @@ extension Dictionary where Key == String, Value == String { } } } + +internal extension PixelKit { + + /// [USE ONLY FOR TESTS] Sets the shared PixelKit.shared singleton + /// - Parameter pixelkit: A custom instance of PixelKit + static func setSharedForTesting(pixelKit: PixelKit) { + Self.shared = pixelKit + } +} diff --git a/UnitTests/Statistics/ATB/StatisticsLoaderTests.swift b/UnitTests/Statistics/ATB/StatisticsLoaderTests.swift index 4fa3f50a79..aff70d88e6 100644 --- a/UnitTests/Statistics/ATB/StatisticsLoaderTests.swift +++ b/UnitTests/Statistics/ATB/StatisticsLoaderTests.swift @@ -19,7 +19,7 @@ import XCTest import OHHTTPStubs import OHHTTPStubsSwift -import PixelKit +@testable import PixelKit @testable import DuckDuckGo_Privacy_Browser class StatisticsLoaderTests: XCTestCase { @@ -36,7 +36,7 @@ class StatisticsLoaderTests: XCTestCase { }) override func setUp() { - PixelKit.shared = pixelKit + PixelKit.setSharedForTesting(pixelKit: pixelKit) mockAttributionsPixelHandler = MockAttributionsPixelHandler() mockStatisticsStore = MockStatisticsStore() diff --git a/UnitTests/Statistics/CBRCompileTimeReporterTests.swift b/UnitTests/Statistics/CBRCompileTimeReporterTests.swift index 347c162f11..8ba6037ae6 100644 --- a/UnitTests/Statistics/CBRCompileTimeReporterTests.swift +++ b/UnitTests/Statistics/CBRCompileTimeReporterTests.swift @@ -20,7 +20,6 @@ import XCTest import OHHTTPStubs import OHHTTPStubsSwift @testable import PixelKit - @testable import DuckDuckGo_Privacy_Browser class CBRCompileTimeReporterTests: XCTestCase { @@ -38,7 +37,7 @@ class CBRCompileTimeReporterTests: XCTestCase { }) override func setUp() { - PixelKit.shared = pixelKit + PixelKit.setSharedForTesting(pixelKit: pixelKit) UserDefaultsWrapper.clearAll() } From 1743e591cd95ec17cc42dbb4d1a7a99b0fd99092 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 15 Apr 2024 09:33:43 +0100 Subject: [PATCH 19/31] style fixed --- DuckDuckGo/Bookmarks/Services/ContextualMenu.swift | 2 +- DuckDuckGo/Bookmarks/View/BookmarkFolderPicker.swift | 2 +- .../View/BookmarkManagementDetailViewController.swift | 2 +- .../View/BookmarkManagementSidebarViewController.swift | 2 +- DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift | 4 ++-- DuckDuckGo/Bookmarks/View/BookmarkTableCellView.swift | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/DuckDuckGo/Bookmarks/Services/ContextualMenu.swift b/DuckDuckGo/Bookmarks/Services/ContextualMenu.swift index 016dbef1d2..eb77fd956f 100644 --- a/DuckDuckGo/Bookmarks/Services/ContextualMenu.swift +++ b/DuckDuckGo/Bookmarks/Services/ContextualMenu.swift @@ -136,7 +136,7 @@ private extension ContextualMenu { moveToEndMenuItem(entity: folder, parent: parent), NSMenuItem.separator(), addFolderMenuItem(folder: folder), - manageBookmarksMenuItem() + manageBookmarksMenuItem(), ] } diff --git a/DuckDuckGo/Bookmarks/View/BookmarkFolderPicker.swift b/DuckDuckGo/Bookmarks/View/BookmarkFolderPicker.swift index 2fe20be203..e7de655e8c 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkFolderPicker.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkFolderPicker.swift @@ -55,7 +55,7 @@ struct BookmarkFolderPicker: View { BookmarkFolderPicker(folders: [ FolderViewModel(entity: folder1, level: 0), FolderViewModel(entity: folder2, level: 1), - FolderViewModel(entity: folder3, level: 2) + FolderViewModel(entity: folder3, level: 2), ], selectedFolder: _selectedFolder.projectedValue) }.frame(width: 300) diff --git a/DuckDuckGo/Bookmarks/View/BookmarkManagementDetailViewController.swift b/DuckDuckGo/Bookmarks/View/BookmarkManagementDetailViewController.swift index 5e6ec25f06..837af92acd 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkManagementDetailViewController.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkManagementDetailViewController.swift @@ -199,7 +199,7 @@ final class BookmarkManagementDetailViewController: NSViewController, NSMenuItem emptyStateTitle.widthAnchor.constraint(equalToConstant: 192), emptyStateImageView.widthAnchor.constraint(equalToConstant: 128), - emptyStateImageView.heightAnchor.constraint(equalToConstant: 96) + emptyStateImageView.heightAnchor.constraint(equalToConstant: 96), ]) } diff --git a/DuckDuckGo/Bookmarks/View/BookmarkManagementSidebarViewController.swift b/DuckDuckGo/Bookmarks/View/BookmarkManagementSidebarViewController.swift index def195822e..f567a9f124 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkManagementSidebarViewController.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkManagementSidebarViewController.swift @@ -387,7 +387,7 @@ private let previewSize = NSSize(width: 400, height: 660) BookmarkFolder(id: "d", title: "Another Nested Folder", children: [ Bookmark(id: "z1", url: "a:b", title: "a", isFavorite: false), Bookmark(id: "z2", url: "a:b", title: "a", isFavorite: false), - Bookmark(id: "z3", url: "a:b", title: "a", isFavorite: false) + Bookmark(id: "z3", url: "a:b", title: "a", isFavorite: false), ]) ]) ]) diff --git a/DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift b/DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift index 87310ad6d9..813e1ff8ec 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift @@ -142,7 +142,7 @@ final class BookmarkOutlineCellView: NSTableCellView { favoriteImageView.leadingAnchor.constraint(greaterThanOrEqualTo: titleLabel.trailingAnchor, constant: 5), favoriteImageView.trailingAnchor.constraint(equalTo: menuButton.trailingAnchor), favoriteImageView.heightAnchor.constraint(equalToConstant: 15), - favoriteImageView.widthAnchor.constraint(equalToConstant: 15) + favoriteImageView.widthAnchor.constraint(equalToConstant: 15), ]) faviconImageView.setContentHuggingPriority(NSLayoutConstraint.Priority(rawValue: 251), for: .horizontal) @@ -214,7 +214,7 @@ extension BookmarkOutlineCellView { BookmarkOutlineCellView(identifier: .init("id")), BookmarkOutlineCellView(identifier: .init("id")), BookmarkOutlineCellView(identifier: .init("id")), - BookmarkOutlineCellView(identifier: .init("id")) + BookmarkOutlineCellView(identifier: .init("id")), ] let stackView = NSStackView(views: cells as [NSView]) diff --git a/DuckDuckGo/Bookmarks/View/BookmarkTableCellView.swift b/DuckDuckGo/Bookmarks/View/BookmarkTableCellView.swift index e442e8dcc8..3a3d3ea0d9 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkTableCellView.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkTableCellView.swift @@ -160,7 +160,7 @@ final class BookmarkTableCellView: NSTableCellView { titleLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 5), accessoryImageView.widthAnchor.constraint(equalToConstant: 22), - accessoryImageView.heightAnchor.constraint(equalToConstant: 32) + accessoryImageView.heightAnchor.constraint(equalToConstant: 32), ]) } From c46b890ccd2a3ed9b48c7dd94d67d82c9af6a0d9 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 15 Apr 2024 10:15:07 +0100 Subject: [PATCH 20/31] unit tests fixed after bad merge --- .../NetworkProtectionPixelEventTests.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/UnitTests/NetworkProtection/NetworkProtectionPixelEventTests.swift b/UnitTests/NetworkProtection/NetworkProtectionPixelEventTests.swift index 5c6f8b9077..cd60e9adf7 100644 --- a/UnitTests/NetworkProtection/NetworkProtectionPixelEventTests.swift +++ b/UnitTests/NetworkProtection/NetworkProtectionPixelEventTests.swift @@ -352,11 +352,11 @@ final class NetworkProtectionPixelEventTests: XCTestCase { underlyingErrors: [TestError.underlyingError]), file: #filePath, line: #line) - fire(NetworkProtectionPixelEvent.networkProtectionSystemExtensionActivationFailure, + fire(NetworkProtectionPixelEvent.networkProtectionSystemExtensionActivationFailure(TestError.testError), frequency: .dailyAndCount, - and: .expect(pixelName: "m_mac_netp_system_extension_activation_failure"), - error: TestError.testError, - underlyingErrors: [TestError.underlyingError]), + and: .expect(pixelName: "m_mac_netp_system_extension_activation_failure", + error: TestError.testError, + underlyingErrors: [TestError.underlyingError]), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionUnhandledError(function: "function", line: 1, error: TestError.testError), From b7d31a6cb61b50a3527380dcf4219647b608c0dd Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 15 Apr 2024 16:37:21 +0100 Subject: [PATCH 21/31] sandbox tool version reverted --- .../xcshareddata/xcschemes/sandbox-test-tool.xcscheme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme index 41730d7069..eb7e5e26bb 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme @@ -1,7 +1,7 @@ + version = "1.8"> Date: Mon, 15 Apr 2024 16:43:19 +0100 Subject: [PATCH 22/31] PR comments addressed --- .../PixelKit/Sources/PixelKit/PixelKitEvent.swift | 8 -------- 1 file changed, 8 deletions(-) diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEvent.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEvent.swift index 4f69e777b6..ca352f3347 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEvent.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEvent.swift @@ -31,7 +31,6 @@ public final class DebugEvent: PixelKitEvent { public enum EventType { case assertionFailure(message: String, file: StaticString, line: UInt) case custom(_ event: PixelKitEvent) - case customV2(_ event: PixelKitEventV2) } public let eventType: EventType @@ -47,19 +46,12 @@ public final class DebugEvent: PixelKitEvent { self.error = error } - public init(_ event: PixelKitEventV2) { - self.eventType = .customV2(event) - self.error = event.error - } - public var name: String { switch eventType { case .assertionFailure: return "assertion_failure" case .custom(let event): return event.name - case .customV2(let event): - return event.name } } From 541f86dc91430346714f91b81ea6779d8c7f6add Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 15 Apr 2024 16:56:13 +0100 Subject: [PATCH 23/31] switch default removed --- .../NetworkProtectionPixelEvent.swift | 77 ++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift b/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift index 1792a9b298..2446ab0ac5 100644 --- a/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift +++ b/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift @@ -292,7 +292,43 @@ enum NetworkProtectionPixelEvent: PixelKitEventV2 { return [PixelKit.Parameters.errorCode: String(code)] case .networkProtectionWireguardErrorInvalidState(reason: let reason): return [PixelKit.Parameters.reason: reason] - default: + case .networkProtectionActiveUser, + .networkProtectionNewUser, + .networkProtectionControllerStartAttempt, + .networkProtectionControllerStartSuccess, + .networkProtectionControllerStartFailure, + .networkProtectionTunnelStartAttempt, + .networkProtectionTunnelStartSuccess, + .networkProtectionTunnelStartFailure, + .networkProtectionTunnelUpdateAttempt, + .networkProtectionTunnelUpdateSuccess, + .networkProtectionTunnelUpdateFailure, + .networkProtectionEnableAttemptConnecting, + .networkProtectionEnableAttemptSuccess, + .networkProtectionEnableAttemptFailure, + .networkProtectionTunnelFailureDetected, + .networkProtectionTunnelFailureRecovered, + .networkProtectionLatency, + .networkProtectionLatencyError, + .networkProtectionTunnelConfigurationNoServerRegistrationInfo, + .networkProtectionTunnelConfigurationCouldNotSelectClosestServer, + .networkProtectionTunnelConfigurationCouldNotGetPeerPublicKey, + .networkProtectionTunnelConfigurationCouldNotGetPeerHostName, + .networkProtectionTunnelConfigurationCouldNotGetInterfaceAddressRange, + .networkProtectionClientFailedToParseServerListResponse, + .networkProtectionClientFailedToEncodeRegisterKeyRequest, + .networkProtectionClientFailedToParseRegisteredServersResponse, + .networkProtectionClientFailedToEncodeRedeemRequest, + .networkProtectionClientInvalidInviteCode, + .networkProtectionClientFailedToParseRedeemResponse, + .networkProtectionClientInvalidAuthToken, + .networkProtectionWireguardErrorCannotLocateTunnelFileDescriptor, + .networkProtectionWireguardErrorFailedDNSResolution, + .networkProtectionNoAuthTokenFoundError, + .networkProtectionRekeyAttempt, + .networkProtectionRekeyCompleted, + .networkProtectionRekeyFailure, + .networkProtectionSystemExtensionActivationFailure: return nil } } @@ -314,7 +350,44 @@ enum NetworkProtectionPixelEvent: PixelKitEventV2 { .networkProtectionUnhandledError(_, _, let error), .networkProtectionSystemExtensionActivationFailure(let error): return error - default: + case .networkProtectionActiveUser, + .networkProtectionNewUser, + .networkProtectionControllerStartAttempt, + .networkProtectionControllerStartSuccess, + .networkProtectionTunnelStartAttempt, + .networkProtectionTunnelStartSuccess, + .networkProtectionTunnelUpdateAttempt, + .networkProtectionTunnelUpdateSuccess, + .networkProtectionEnableAttemptConnecting, + .networkProtectionEnableAttemptSuccess, + .networkProtectionEnableAttemptFailure, + .networkProtectionTunnelFailureDetected, + .networkProtectionTunnelFailureRecovered, + .networkProtectionLatency, + .networkProtectionLatencyError, + .networkProtectionTunnelConfigurationNoServerRegistrationInfo, + .networkProtectionTunnelConfigurationCouldNotSelectClosestServer, + .networkProtectionTunnelConfigurationCouldNotGetPeerPublicKey, + .networkProtectionTunnelConfigurationCouldNotGetPeerHostName, + .networkProtectionTunnelConfigurationCouldNotGetInterfaceAddressRange, + .networkProtectionClientFailedToParseServerListResponse, + .networkProtectionClientFailedToEncodeRegisterKeyRequest, + .networkProtectionClientFailedToParseRegisteredServersResponse, + .networkProtectionClientFailedToEncodeRedeemRequest, + .networkProtectionClientInvalidInviteCode, + .networkProtectionClientInvalidAuthToken, + .networkProtectionKeychainErrorFailedToCastKeychainValueToData, + .networkProtectionKeychainReadError, + .networkProtectionKeychainWriteError, + .networkProtectionKeychainUpdateError, + .networkProtectionKeychainDeleteError, + .networkProtectionWireguardErrorCannotLocateTunnelFileDescriptor, + .networkProtectionWireguardErrorInvalidState, + .networkProtectionWireguardErrorFailedDNSResolution, + .networkProtectionWireguardErrorCannotStartWireguardBackend, + .networkProtectionNoAuthTokenFoundError, + .networkProtectionRekeyAttempt, + .networkProtectionRekeyCompleted: return nil } } From 12fd8b9c5917821dd030c8a43a0212f7610e0454 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 16 Apr 2024 15:49:36 +0100 Subject: [PATCH 24/31] logs params are now public --- LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift index 9a4b501e0e..be1be56922 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift @@ -160,7 +160,7 @@ public final class PixelKit { self.dateGenerator = dateGenerator self.defaults = defaults self.fireRequest = fireRequest - logger.debug("👾 PixelKit initialised: dryRun: \(self.dryRun) appVersion: \(self.appVersion) source: \(self.source ?? "-") defaultHeaders: \(self.defaultHeaders) pixelCalendar: \(self.pixelCalendar)") + logger.debug("👾 PixelKit initialised: dryRun: \(self.dryRun, privacy: .public) appVersion: \(self.appVersion, privacy: .public) source: \(self.source ?? "-", privacy: .public) defaultHeaders: \(self.defaultHeaders, privacy: .public) pixelCalendar: \(self.pixelCalendar, privacy: .public)") } // swiftlint:disable:next function_body_length cyclomatic_complexity @@ -248,14 +248,14 @@ public final class PixelKit { /// If the pixel name ends with the forbiddenString then an error is logged or an assertion failure is fired in debug func reportErrorIf(pixel: String, endsWith forbiddenString: String) { if pixel.hasSuffix(forbiddenString) { - logger.error("Pixel \(pixel) must not end with \(forbiddenString)") + logger.error("Pixel \(pixel, privacy: .public) must not end with \(forbiddenString, privacy: .public)") assertionFailure("Pixel \(pixel) must not end with \(forbiddenString)") } } private func printDebugInfo(pixelName: String, frequency: Frequency, parameters: [String: String], skipped: Bool = false) { let params = parameters.filter { key, _ in !["test"].contains(key) } - logger.debug("👾[\(frequency.description)-\(skipped ? "Skipped" : "Fired")] \(pixelName) \(params)") + logger.debug("👾[\(frequency.description, privacy: .public)-\(skipped ? "Skipped" : "Fired", privacy: .public)] \(pixelName, privacy: .public) \(params, privacy: .public)") } private func fireRequestWrapper( @@ -434,7 +434,7 @@ public final class PixelKit { for (key, _) in self.defaults.dictionaryRepresentation() { if key.hasPrefix(Self.storageKeyPrefixLegacy) || key.hasPrefix(Self.storageKeyPrefix) { self.defaults.removeObject(forKey: key) - self.logger.debug("🚮 Removing from storage \(key)") + self.logger.debug("🚮 Removing from storage \(key, privacy: .public)") } } } From 6ff3e5e0b0a72f0a4738a7d08fabdc2cff9751aa Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 17 Apr 2024 12:36:00 +0100 Subject: [PATCH 25/31] Date code duplication removed --- DuckDuckGo/Application/AppDelegate.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 05aea5a9f9..aacd98014a 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -95,13 +95,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate { var updateController: UpdateController! #endif - static private var aMonthAgo = Calendar.current.date(byAdding: .month, value: -1, to: Date())! // Temporary for init - @UserDefaultsWrapper(key: .firstLaunchDate, defaultValue: aMonthAgo) + @UserDefaultsWrapper(key: .firstLaunchDate, defaultValue: Date.monthAgo) static var firstLaunchDate: Date static var isNewUser: Bool { - let oneWeekAgo = Calendar.current.date(byAdding: .weekOfYear, value: -1, to: Date())! - return firstLaunchDate >= oneWeekAgo + return firstLaunchDate >= Date.weekAgo } // swiftlint:disable:next function_body_length From aa7adb4d243ee5b5d87b91740d1abeabe6d9af3b Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 17 Apr 2024 12:58:57 +0100 Subject: [PATCH 26/31] Legacy Pixel class and related files removed --- .../Statistics/Pixel Legacy/DailyPixel.swift | 85 -- .../Statistics/Pixel Legacy/Pixel.swift | 168 --- .../Pixel Legacy/PixelArguments.swift | 156 --- .../Statistics/Pixel Legacy/PixelEvent.swift | 967 ------------------ .../Pixel Legacy/PixelParameters.swift | 377 ------- 5 files changed, 1753 deletions(-) delete mode 100644 DuckDuckGo/Statistics/Pixel Legacy/DailyPixel.swift delete mode 100644 DuckDuckGo/Statistics/Pixel Legacy/Pixel.swift delete mode 100644 DuckDuckGo/Statistics/Pixel Legacy/PixelArguments.swift delete mode 100644 DuckDuckGo/Statistics/Pixel Legacy/PixelEvent.swift delete mode 100644 DuckDuckGo/Statistics/Pixel Legacy/PixelParameters.swift diff --git a/DuckDuckGo/Statistics/Pixel Legacy/DailyPixel.swift b/DuckDuckGo/Statistics/Pixel Legacy/DailyPixel.swift deleted file mode 100644 index e436d453ba..0000000000 --- a/DuckDuckGo/Statistics/Pixel Legacy/DailyPixel.swift +++ /dev/null @@ -1,85 +0,0 @@ -// -// DailyPixel.swift -// -// Copyright © 2023 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 -import Common - -final class DailyPixel { - - public enum PixelFrequency { - /// Sent once per day. The last timestamp for this pixel is stored and compared to the current date. Pixels of this type will have `_d` appended to their name. - case dailyOnly - - /// Sent once per day with a `_d` suffix, in addition to every time it is called with a `_c` suffix. - /// This means a pixel will get sent twice the first time it is called per-day, and subsequent calls that day will only send the `_c` variant. - /// This is useful in situations where pixels receive spikes in volume, as the daily pixel can be used to determine how many users are actually affected. - case dailyAndCount - } - - enum Error: Swift.Error { - case alreadyFired - } - - enum Constant { - static let dailyPixelStorageIdentifier = "com.duckduckgo.daily.pixel.storage" - } - - private static let storage: UserDefaults = UserDefaults(suiteName: Constant.dailyPixelStorageIdentifier)! - - static func fire(pixel: Pixel.Event, - frequency: PixelFrequency, - includeAppVersionParameter includeAppVersion: Bool = true, - withAdditionalParameters params: [String: String] = [:], - onComplete: @escaping (Swift.Error?) -> Void = { _ in }) { - switch frequency { - case .dailyOnly: - if !pixel.hasBeenFiredToday(dailyPixelStorage: storage) { - Pixel.shared?.fire(.dailyPixel(pixel, isFirst: true), withAdditionalParameters: params, includeAppVersionParameter: includeAppVersion) - updatePixelLastFireDate(pixel: pixel) - } - case .dailyAndCount: - if !pixel.hasBeenFiredToday(dailyPixelStorage: storage) { - Pixel.shared?.fire(.dailyPixel(pixel, isFirst: true), withAdditionalParameters: params, includeAppVersionParameter: includeAppVersion) - updatePixelLastFireDate(pixel: pixel) - } - - Pixel.shared?.fire(.dailyPixel(pixel, isFirst: false), withAdditionalParameters: params, includeAppVersionParameter: includeAppVersion) - } - } - - private static func updatePixelLastFireDate(pixel: Pixel.Event) { - storage.set(Date(), forKey: pixel.name) - } - - static func clearLastFireDate(pixel: Pixel.Event) { - storage.removeObject(forKey: pixel.name) - } - -} - -private extension Pixel.Event { - - func hasBeenFiredToday(dailyPixelStorage: UserDefaults) -> Bool { - if let lastFireDate = dailyPixelStorage.object(forKey: name) as? Date { - return Calendar.current.isDate(Date(), inSameDayAs: lastFireDate) - } - - return false - } - -} diff --git a/DuckDuckGo/Statistics/Pixel Legacy/Pixel.swift b/DuckDuckGo/Statistics/Pixel Legacy/Pixel.swift deleted file mode 100644 index aa30eace74..0000000000 --- a/DuckDuckGo/Statistics/Pixel Legacy/Pixel.swift +++ /dev/null @@ -1,168 +0,0 @@ -// -// Pixel.swift -// -// Copyright © 2018 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 -import Networking -import Common -import PixelKit - -final class Pixel { - - static private(set) var shared: Pixel? - - static func setUp(dryRun: Bool = false) { -#if DEBUG - if dryRun { - shared = Pixel(store: LocalPixelDataStore.shared) { event, params, _, _, onComplete in - let params = params.filter { key, _ in !["appVersion", "test"].contains(key) } - os_log(.debug, log: .pixel, "Pixel dry fire: %{public}@ %{public}@", event.name.replacingOccurrences(of: "_", with: "."), params.isEmpty ? "" : params.debugDescription) - // simulate server response time for Dry Run mode - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - onComplete(nil) - } - } - return - } -#endif - shared = Pixel(store: LocalPixelDataStore.shared, requestSender: Pixel.defaultRequestSender) - } - -#if DEBUG - static func setUp(store: @escaping @autoclosure () -> PixelDataStore = { fatalError("provide test store") }(), - pixelFired: @escaping (Pixel.Event) -> Void) { - shared = Pixel(store: store()) { event, _, _, _, onComplete in - pixelFired(event) - onComplete(nil) - } - } -#endif - - static func tearDown() { - shared?.store = nil - shared = nil - } - - typealias RequestSender = (Pixel.Event, [String: String], CharacterSet?, APIRequest.Headers, @escaping (Error?) -> Void) -> Void - private let sendRequest: RequestSender - - private var store: (() -> PixelDataStore)! - - static var isNewUser: Bool { - let oneWeekAgo = Calendar.current.date(byAdding: .weekOfYear, value: -1, to: Date())! - return firstLaunchDate >= oneWeekAgo - } - - static var defaultRequestSender: RequestSender { - { event, params, allowedQueryReservedCharacters, headers, onComplete in - let configuration = APIRequest.Configuration(url: URL.pixelUrl(forPixelNamed: event.name), - queryParameters: params, - allowedQueryReservedCharacters: allowedQueryReservedCharacters, - headers: headers) - let request = APIRequest(configuration: configuration, urlSession: URLSession.session(useMainThreadCallbackQueue: true)) - request.fetch { (_, error) in - onComplete(error) - } - } - } - - private let appVersion: String - - init(appVersion: String = AppVersion.shared.versionNumber, - store: @escaping @autoclosure () -> PixelDataStore, - requestSender: @escaping RequestSender) { - - self.appVersion = appVersion - self.store = store - self.sendRequest = requestSender - } - - private static let pixelRequestHeaders: HTTPHeaders = [ - APIRequest.HTTPHeaderField.moreInfo: "See " + URL.duckDuckGoMorePrivacyInfo.absoluteString, - "X-DuckDuckGo-Client": "macOS" - ] - - // Temporary for activation pixels - static private var aMonthAgo = Calendar.current.date(byAdding: .month, value: -1, to: Date())! - @UserDefaultsWrapper(key: .firstLaunchDate, defaultValue: aMonthAgo) - static var firstLaunchDate: Date - - func fire(_ event: Pixel.Event, - limitTo limit: Pixel.Event.Repetition = .repetitive, - withAdditionalParameters parameters: [String: String]? = nil, - allowedQueryReservedCharacters: CharacterSet? = nil, - includeAppVersionParameter: Bool = true, - withHeaders headers: APIRequest.Headers = APIRequest.Headers(additionalHeaders: pixelRequestHeaders), - onComplete: @escaping (Error?) -> Void = {_ in }) { - - func repetition() -> Event.Repetition { - Event.Repetition(key: event.name, store: self.store()) - } - switch limit { - case .initial: - if repetition() != .initial { return } - case .dailyFirst: - if repetition() == .repetitive { return } // Pixel already fired today - case .repetitive: break - } - - var newParams: [String: String] - switch (event.parameters, parameters) { - case (.some(let parameters), .none): - newParams = parameters - case (.none, .some(let parameters)): - newParams = parameters - case (.some(let params1), .some(let params2)): - newParams = params1.merging(params2) { $1 } - case (.none, .none): - newParams = [:] - } - - if includeAppVersionParameter { - newParams[PixelKit.Parameters.appVersion] = appVersion - } -#if DEBUG - newParams[PixelKit.Parameters.test] = PixelKit.Values.test -#endif - - sendRequest(event, newParams, allowedQueryReservedCharacters, headers, onComplete) - } - - static func fire(_ event: Pixel.Event, - limitTo limit: Pixel.Event.Repetition = .repetitive, - withAdditionalParameters parameters: [String: String]? = nil, - allowedQueryReservedCharacters: CharacterSet? = nil, - includeAppVersionParameter: Bool = true, - onComplete: @escaping (Error?) -> Void = {_ in }) { - - Self.shared?.fire(event, - limitTo: limit, - withAdditionalParameters: parameters, - allowedQueryReservedCharacters: allowedQueryReservedCharacters, - includeAppVersionParameter: includeAppVersionParameter, - onComplete: onComplete) - } - - func clearRepetitions(for event: Pixel.Event) { - store().removeValue(forKey: event.name) - } -} - -public func pixelAssertionFailure(_ message: @autoclosure () -> String = String(), file: StaticString = #fileID, line: UInt = #line) { - Pixel.fire(.debug(event: Pixel.Event.Debug.assertionFailure(message: message(), file: file, line: line))) - Swift.assertionFailure(message(), file: file, line: line) -} diff --git a/DuckDuckGo/Statistics/Pixel Legacy/PixelArguments.swift b/DuckDuckGo/Statistics/Pixel Legacy/PixelArguments.swift deleted file mode 100644 index 987491bcf7..0000000000 --- a/DuckDuckGo/Statistics/Pixel Legacy/PixelArguments.swift +++ /dev/null @@ -1,156 +0,0 @@ -// -// PixelArguments.swift -// -// Copyright © 2021 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 -import AppKit - -extension Pixel.Event { - - enum Repetition: String, CustomStringConvertible { - var description: String { rawValue } - - /// fires once ever - case initial = "initial" - - /// fires at most once per day - case dailyFirst = "first-in-a-day" - - /// fires every time - case repetitive = "repetitive" - - init(key: String, store: PixelDataStore = LocalPixelDataStore.shared, now: Date = Date(), update: Bool = true) { - defer { - if update { - store.set(now.daySinceReferenceDate, forKey: key) - } - } - - guard let lastUsedDay: Int = store.value(forKey: key) else { - self = .initial - return - } - if lastUsedDay == now.daySinceReferenceDate { - self = .repetitive - return - } - self = .dailyFirst - } - } - - enum AccessPoint: String, CustomStringConvertible { - var description: String { rawValue } - - case button = "source-button" - case mainMenu = "source-menu" - case tabMenu = "source-tab-menu" - case hotKey = "source-keyboard" - case moreMenu = "source-more-menu" - case newTab = "source-new-tab" - - init(sender: Any, default: AccessPoint, mainMenuCheck: (NSMenu?) -> Bool = { $0 is MainMenu }) { - switch sender { - case let menuItem as NSMenuItem: - if mainMenuCheck(menuItem.topMenu) { - if let event = NSApp.currentEvent, - case .keyDown = event.type, - event.characters == menuItem.keyEquivalent { - - self = .hotKey - } else { - self = .mainMenu - } - } else { - self = `default` - } - - case is NSButton: - self = .button - - default: - assertionFailure("AccessPoint: Unexpected type of sender: \(type(of: sender))") - self = `default` - } - } - - } - - enum FormAutofillKind: String, CustomStringConvertible { - var description: String { rawValue } - - case password - case card - case identity - } - - public enum CompileRulesListType: String, CustomStringConvertible { - - public var description: String { rawValue } - - case tds = "tracker_data" - case clickToLoad = "click_to_load" - case blockingAttribution = "blocking_attribution" - case attributed = "attributed" - case unknown = "unknown" - - } - - enum FireButtonOption: String, CustomStringConvertible { - var description: String { rawValue } - - case tab - case window - case allSites = "all-sites" - } -} - -extension DataImportAction: CustomStringConvertible { - var description: String { - switch self { - case .bookmarks: return "bookmarks" - case .passwords: return "logins" - case .favicons: return "favicons" - case .generic: return "generic" - } - } -} - -extension DataImport.Source: CustomStringConvertible { - var description: String { - switch self { - case .brave: return "source-brave" - case .chrome: return "source-chrome" - case .chromium: return "source-chromium" - case .coccoc: return "source-coccoc" - case .csv: return "source-csv" - case .bitwarden: return "source-bitwarden" - case .lastPass: return "source-lastpass" - case .onePassword7: return "source-1password" - case .onePassword8: return "source-1password-8" - case .edge: return "source-edge" - case .firefox: return "source-firefox" - case .safari: return "source-safari" - case .safariTechnologyPreview: return "source-safari-technology-preview" - case .bookmarksHTML: return "source-bookmarks-html" - case .opera: return "source-opera" - case .operaGX: return "source-operagx" - case .tor: return "source-tor" - case .vivaldi: return "source-vivaldi" - case .yandex: return "source-yandex" - } - } -} diff --git a/DuckDuckGo/Statistics/Pixel Legacy/PixelEvent.swift b/DuckDuckGo/Statistics/Pixel Legacy/PixelEvent.swift deleted file mode 100644 index 902e5b0b5a..0000000000 --- a/DuckDuckGo/Statistics/Pixel Legacy/PixelEvent.swift +++ /dev/null @@ -1,967 +0,0 @@ -// -// PixelEvent.swift -// -// Copyright © 2021 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 -import BrowserServicesKit -import Bookmarks -import Configuration -import DDGSync -import PixelKit - -extension Pixel { - - indirect enum Event { - /// This is a convenience pixel that allows us to fire `PixelKitEvents` using our - /// regular `Pixel.fire()` calls. This is a convenience intermediate step to help ensure - /// nothing breaks in the migration towards `PixelKit`. - case pixelKitEvent(_ event: PixelKitEvent) - - case crash - - case brokenSiteReport - - enum OnboardingShown: String, CustomStringConvertible { - var description: String { rawValue } - - init(_ value: Bool) { - if value { - self = .onboardingShown - } else { - self = .regularNavigation - } - } - case onboardingShown = "onboarding-shown" - case regularNavigation = "regular-nav" - } - - enum WaitResult: String, CustomStringConvertible { - var description: String { rawValue } - - case closed - case quit - case success - } - - enum CompileRulesWaitTime: String, CustomStringConvertible { - var description: String { rawValue } - - case noWait = "0" - case lessThan1s = "1" - case lessThan5s = "5" - case lessThan10s = "10" - case lessThan20s = "20" - case lessThan40s = "40" - case more = "more" - } - - case compileRulesWait(onboardingShown: OnboardingShown, waitTime: CompileRulesWaitTime, result: WaitResult) - static func compileRulesWait(onboardingShown: Bool, waitTime interval: TimeInterval, result: WaitResult) -> Event { - let waitTime: CompileRulesWaitTime - switch interval { - case 0: - waitTime = .noWait - case ...1: - waitTime = .lessThan1s - case ...5: - waitTime = .lessThan5s - case ...10: - waitTime = .lessThan10s - case ...20: - waitTime = .lessThan20s - case ...40: - waitTime = .lessThan40s - default: - waitTime = .more - } - return .compileRulesWait(onboardingShown: OnboardingShown(onboardingShown), - waitTime: waitTime, - result: result) - } - - case launchInitial(cohort: String) - - case serp - case serpInitial(cohort: String) - case serpDay21to27(cohort: String) - - case dailyOsVersionCounter - - case dataImportFailed(source: DataImport.Source, sourceVersion: String?, error: any DataImportError) - - case formAutofilled(kind: FormAutofillKind) - case autofillItemSaved(kind: FormAutofillKind) - - case autofillLoginsSaveLoginModalExcludeSiteConfirmed - case autofillLoginsSettingsResetExcludedDisplayed - case autofillLoginsSettingsResetExcludedConfirmed - case autofillLoginsSettingsResetExcludedDismissed - - case bitwardenPasswordAutofilled - case bitwardenPasswordSaved - - case ampBlockingRulesCompilationFailed - - case adClickAttributionDetected - case adClickAttributionActive - case adClickAttributionPageLoads - - case emailEnabled - case emailDisabled - case emailUserPressedUseAddress - case emailUserPressedUseAlias - case emailUserCreatedAlias - - case jsPixel(_ pixel: AutofillUserScript.JSPixel) - - case debug(event: Debug, error: Error? = nil) - - // Activation Points - case newTabInitial - case emailEnabledInitial - case watchInDuckPlayerInitial - case setAsDefaultInitial - case importDataInitial - - // New Tab section removed - case favoriteSectionHidden - case recentActivitySectionHidden - case continueSetUpSectionHidden - - // Fire Button - case fireButtonFirstBurn - case fireButton(option: FireButtonOption) - - // Duck Player - case duckPlayerDailyUniqueView - case duckPlayerViewFromYoutubeViaMainOverlay - case duckPlayerViewFromYoutubeViaHoverButton - case duckPlayerViewFromYoutubeAutomatic - case duckPlayerViewFromSERP - case duckPlayerViewFromOther - case duckPlayerSettingAlways - case duckPlayerSettingNever - case duckPlayerSettingBackToDefault - - // Dashboard - case dashboardProtectionAllowlistAdd(triggerOrigin: String?) - case dashboardProtectionAllowlistRemove(triggerOrigin: String?) - - // VPN - case vpnBreakageReport(category: String, description: String, metadata: String) - - // VPN - case networkProtectionRemoteMessageDisplayed(messageID: String) - case networkProtectionRemoteMessageDismissed(messageID: String) - case networkProtectionRemoteMessageOpened(messageID: String) - case networkProtectionEnabledOnSearch - case networkProtectionGeoswitchingOpened - case networkProtectionGeoswitchingSetNearest - case networkProtectionGeoswitchingSetCustom - case networkProtectionGeoswitchingNoLocations - - // Sync - case syncSignupDirect - case syncSignupConnect - case syncLogin - case syncDaily - case syncDuckAddressOverride - case syncSuccessRateDaily - case syncLocalTimestampResolutionTriggered(Feature) - case syncBookmarksCountLimitExceededDaily - case syncCredentialsCountLimitExceededDaily - case syncBookmarksRequestSizeLimitExceededDaily - case syncCredentialsRequestSizeLimitExceededDaily - - // DataBroker Protection Waitlist - case dataBrokerProtectionWaitlistUserActive - case dataBrokerProtectionWaitlistEntryPointMenuItemDisplayed - case dataBrokerProtectionWaitlistIntroDisplayed - case dataBrokerProtectionWaitlistNotificationShown - case dataBrokerProtectionWaitlistNotificationTapped - case dataBrokerProtectionWaitlistCardUITapped - case dataBrokerProtectionWaitlistTermsAndConditionsDisplayed - case dataBrokerProtectionWaitlistTermsAndConditionsAccepted - case dataBrokerProtectionRemoteMessageDisplayed(messageID: String) - case dataBrokerProtectionRemoteMessageDismissed(messageID: String) - case dataBrokerProtectionRemoteMessageOpened(messageID: String) - - // Login Item events - case dataBrokerEnableLoginItemDaily - case dataBrokerDisableLoginItemDaily - case dataBrokerResetLoginItemDaily - case dataBrokerDisableAndDeleteDaily - - // DataBrokerProtection Other - case dataBrokerProtectionErrorWhenFetchingSubscriptionAuthTokenAfterSignIn - - // Subscription - case privacyProFeatureEnabled - case privacyProBetaUserThankYouVPN - case privacyProBetaUserThankYouDBP - case privacyProSubscriptionActive - case privacyProOfferScreenImpression - case privacyProPurchaseAttempt - case privacyProPurchaseFailure - case privacyProPurchaseFailureStoreError - case privacyProPurchaseFailureBackendError - case privacyProPurchaseFailureAccountNotCreated - case privacyProPurchaseSuccess - case privacyProRestorePurchaseOfferPageEntry - case privacyProRestorePurchaseClick - case privacyProRestorePurchaseSettingsMenuEntry - case privacyProRestorePurchaseEmailStart - case privacyProRestorePurchaseStoreStart - case privacyProRestorePurchaseEmailSuccess - case privacyProRestorePurchaseStoreSuccess - case privacyProRestorePurchaseStoreFailureNotFound - case privacyProRestorePurchaseStoreFailureOther - case privacyProRestoreAfterPurchaseAttempt - case privacyProSubscriptionActivated - case privacyProWelcomeAddDevice - case privacyProSettingsAddDevice - case privacyProAddDeviceEnterEmail - case privacyProWelcomeVPN - case privacyProWelcomePersonalInformationRemoval - case privacyProWelcomeIdentityRestoration - case privacyProSubscriptionSettings - case privacyProVPNSettings - case privacyProPersonalInformationRemovalSettings - case privacyProIdentityRestorationSettings - case privacyProSubscriptionManagementEmail - case privacyProSubscriptionManagementPlanBilling - case privacyProSubscriptionManagementRemoval - case privacyProPurchaseStripeSuccess - // Web pixels - case privacyProOfferMonthlyPriceClick - case privacyProOfferYearlyPriceClick - case privacyProAddEmailSuccess - case privacyProWelcomeFAQClick - - case dailyPixel(Event, isFirst: Bool) - - // Default Browser - case defaultRequestedFromHomepage - case defaultRequestedFromHomepageSetupView - case defaultRequestedFromSettings - case defaultRequestedFromOnboarding - - case protectionToggledOffBreakageReport - case toggleProtectionsDailyCount - case toggleReportDoNotSend - case toggleReportDismiss - - // Password Import Keychain Prompt - case passwordImportKeychainPrompt - case passwordImportKeychainPromptDenied - - // Tracks installation without tracking retention. - case installationAttribution - - enum Debug { - /// This is a convenience pixel that allows us to fire `PixelKitEvents` using our - /// regular `Pixel.fire()` calls. This is a convenience intermediate step to help ensure - /// nothing breaks in the migration towards `PixelKit`. - case pixelKitEvent(_ event: PixelKitEvent) - - case assertionFailure(message: String, file: StaticString, line: UInt) - - case dbMakeDatabaseError - case dbContainerInitializationError - case dbInitializationError - case dbSaveExcludedHTTPSDomainsError - case dbSaveBloomFilterError - - case configurationFetchError - - case trackerDataParseFailed - case trackerDataReloadFailed - case trackerDataCouldNotBeLoaded - - case privacyConfigurationParseFailed - case privacyConfigurationReloadFailed - case privacyConfigurationCouldNotBeLoaded - - case fileStoreWriteFailed - case fileMoveToDownloadsFailed - case fileAccessRelatedItemFailed - case fileGetDownloadLocationFailed - case fileDownloadCreatePresentersFailed - case downloadResumeDataCodingFailed - - case suggestionsFetchFailed - case appOpenURLFailed - case appStateRestorationFailed - - case contentBlockingErrorReportingIssue - - case contentBlockingCompilationFailed(listType: CompileRulesListType, - component: ContentBlockerDebugEvents.Component) - - case contentBlockingCompilationTime - - case secureVaultInitError - case secureVaultError - - case feedbackReportingFailed - - case blankNavigationOnBurnFailed - - case historyRemoveFailed - case historyReloadFailed - case historyCleanEntriesFailed - case historyCleanVisitsFailed - case historySaveFailed - case historySaveFailedDaily - case historyInsertVisitFailed - case historyRemoveVisitsFailed - - case emailAutofillKeychainError - - case bookmarksStoreRootFolderMigrationFailed - case bookmarksStoreFavoritesFolderMigrationFailed - - case adAttributionCompilationFailedForAttributedRulesList - case adAttributionGlobalAttributedRulesDoNotExist - case adAttributionDetectionHeuristicsDidNotMatchDomain - case adAttributionLogicUnexpectedStateOnRulesCompiled - case adAttributionLogicUnexpectedStateOnInheritedAttribution - case adAttributionLogicUnexpectedStateOnRulesCompilationFailed - case adAttributionDetectionInvalidDomainInParameter - case adAttributionLogicRequestingAttributionTimedOut - case adAttributionLogicWrongVendorOnSuccessfulCompilation - case adAttributionLogicWrongVendorOnFailedCompilation - - case webKitDidTerminate - - case removedInvalidBookmarkManagedObjects - - case bitwardenNotResponding - case bitwardenRespondedCannotDecryptUnique(repetition: Repetition = .init(key: "bitwardenRespondedCannotDecryptUnique")) - case bitwardenHandshakeFailed - case bitwardenDecryptionOfSharedKeyFailed - case bitwardenStoringOfTheSharedKeyFailed - case bitwardenCredentialRetrievalFailed - case bitwardenCredentialCreationFailed - case bitwardenCredentialUpdateFailed - case bitwardenRespondedWithError - case bitwardenNoActiveVault - case bitwardenParsingFailed - case bitwardenStatusParsingFailed - case bitwardenHmacComparisonFailed - case bitwardenDecryptionFailed - case bitwardenSendingOfMessageFailed - case bitwardenSharedKeyInjectionFailed - - case updaterAborted - case userSelectedToSkipUpdate - case userSelectedToInstallUpdate - case userSelectedToDismissUpdate - - case faviconDecryptionFailedUnique - case downloadListItemDecryptionFailedUnique - case historyEntryDecryptionFailedUnique - case permissionDecryptionFailedUnique - - // Errors from Bookmarks Module - case missingParent - case bookmarksSaveFailed - case bookmarksSaveFailedOnImport - - case bookmarksCouldNotLoadDatabase - case bookmarksCouldNotPrepareDatabase - case bookmarksMigrationAlreadyPerformed - case bookmarksMigrationFailed - case bookmarksMigrationCouldNotPrepareDatabase - case bookmarksMigrationCouldNotPrepareDatabaseOnFailedMigration - case bookmarksMigrationCouldNotRemoveOldStore - case bookmarksMigrationCouldNotPrepareMultipleFavoriteFolders - - case syncSentUnauthenticatedRequest - case syncMetadataCouldNotLoadDatabase - case syncBookmarksProviderInitializationFailed - case syncBookmarksFailed - case syncCredentialsProviderInitializationFailed - case syncCredentialsFailed - case syncSettingsFailed - case syncSettingsMetadataUpdateFailed - case syncSignupError - case syncLoginError - case syncLogoutError - case syncUpdateDeviceError - case syncRemoveDeviceError - case syncDeleteAccountError - case syncLoginExistingAccountError - case syncCannotCreateRecoveryPDF - - case bookmarksCleanupFailed - case bookmarksCleanupAttemptedWhileSyncWasEnabled - case favoritesCleanupFailed - case bookmarksFaviconsFetcherStateStoreInitializationFailed - case bookmarksFaviconsFetcherFailed - - case credentialsDatabaseCleanupFailed - case credentialsCleanupAttemptedWhileSyncWasEnabled - - case invalidPayload(Configuration) - - case burnerTabMisplaced - - case networkProtectionRemoteMessageFetchingFailed - case networkProtectionRemoteMessageStorageFailed - case dataBrokerProtectionRemoteMessageFetchingFailed - case dataBrokerProtectionRemoteMessageStorageFailed - - case loginItemUpdateError(loginItemBundleID: String, action: String, buildType: String, osVersion: String) - } - } -} - -extension Pixel.Event { - - var name: String { - switch self { - case .pixelKitEvent(let event): - return event.name - - case .crash: - return "m_mac_crash" - - case .brokenSiteReport: - return "epbf_macos_desktop" - - case .compileRulesWait(onboardingShown: let onboardingShown, waitTime: let waitTime, result: let result): - return "m_mac_cbr-wait_\(onboardingShown)_\(waitTime)_\(result)" - - case .serp: - return "m_mac_navigation_search" - - case .dailyOsVersionCounter: - return "m_mac_daily-os-version-counter" - - case .dataImportFailed(source: let source, sourceVersion: _, error: let error) where error.action == .favicons: - return "m_mac_favicon-import-failed_\(source)" - case .dataImportFailed(source: let source, sourceVersion: _, error: let error): - return "m_mac_data-import-failed_\(error.action)_\(source)" - - case .formAutofilled(kind: let kind): - return "m_mac_autofill_\(kind)" - - case .autofillItemSaved(kind: let kind): - return "m_mac_save_\(kind)" - - case .autofillLoginsSaveLoginModalExcludeSiteConfirmed: - return "m_mac_autofill_logins_save_login_exclude_site_confirmed" - case .autofillLoginsSettingsResetExcludedDisplayed: - return "m_mac_autofill_settings_reset_excluded_displayed" - case .autofillLoginsSettingsResetExcludedConfirmed: - return "m_mac_autofill_settings_reset_excluded_confirmed" - case .autofillLoginsSettingsResetExcludedDismissed: - return "m_mac_autofill_settings_reset_excluded_dismissed" - - case .bitwardenPasswordAutofilled: - return "m_mac_bitwarden_autofill_password" - - case .bitwardenPasswordSaved: - return "m_mac_bitwarden_save_password" - - case .debug(event: let event, error: _): - return "m_mac_debug_\(event.name)" - - case .ampBlockingRulesCompilationFailed: - return "m_mac_amp_rules_compilation_failed" - - case .adClickAttributionDetected: - return "m_mac_ad_click_detected" - - case .adClickAttributionActive: - return "m_mac_ad_click_active" - - case .adClickAttributionPageLoads: - return "m_mac_ad_click_page_loads" - - // Deliberately omit the `m_mac_` prefix in order to format these pixels the same way as other platforms - case .emailEnabled: return "email_enabled_macos_desktop" - case .emailDisabled: return "email_disabled_macos_desktop" - case .emailUserPressedUseAddress: return "email_filled_main_macos_desktop" - case .emailUserPressedUseAlias: return "email_filled_random_macos_desktop" - case .emailUserCreatedAlias: return "email_generated_button_macos_desktop" - - case .jsPixel(let pixel): - // Email pixels deliberately avoid using the `m_mac_` prefix. - if pixel.isEmailPixel { - return "\(pixel.pixelName)_macos_desktop" - } else { - return "m_mac_\(pixel.pixelName)" - } - case .emailEnabledInitial: - return "m_mac.enable-email-protection.initial" - - case .watchInDuckPlayerInitial: - return "m_mac.watch-in-duckplayer.initial" - case .setAsDefaultInitial: - return "m_mac.set-as-default.initial" - case .importDataInitial: - return "m_mac.import-data.initial" - case .newTabInitial: - return "m_mac.new-tab-opened.initial" - case .favoriteSectionHidden: - return "m_mac.favorite-section-hidden" - case .recentActivitySectionHidden: - return "m_mac.recent-activity-section-hidden" - case .continueSetUpSectionHidden: - return "m_mac.continue-setup-section-hidden" - - // Fire Button - case .fireButtonFirstBurn: - return "m_mac_fire_button_first_burn" - case .fireButton(option: let option): - return "m_mac_fire_button_\(option)" - - case .duckPlayerDailyUniqueView: - return "m_mac_duck-player_daily-unique-view" - case .duckPlayerViewFromYoutubeViaMainOverlay: - return "m_mac_duck-player_view-from_youtube_main-overlay" - case .duckPlayerViewFromYoutubeViaHoverButton: - return "m_mac_duck-player_view-from_youtube_hover-button" - case .duckPlayerViewFromYoutubeAutomatic: - return "m_mac_duck-player_view-from_youtube_automatic" - case .duckPlayerViewFromSERP: - return "m_mac_duck-player_view-from_serp" - case .duckPlayerViewFromOther: - return "m_mac_duck-player_view-from_other" - case .duckPlayerSettingAlways: - return "m_mac_duck-player_setting_always" - case .duckPlayerSettingNever: - return "m_mac_duck-player_setting_never" - case .duckPlayerSettingBackToDefault: - return "m_mac_duck-player_setting_back-to-default" - - case .dashboardProtectionAllowlistAdd: - return "m_mac_mp_wla" - case .dashboardProtectionAllowlistRemove: - return "m_mac_mp_wlr" - - case .launchInitial: - return "m.mac.first-launch" - case .serpInitial: - return "m.mac.navigation.first-search" - case .serpDay21to27: - return "m.mac.search-day-21-27.initial" - - case .vpnBreakageReport: - return "m_mac_vpn_breakage_report" - - case .networkProtectionRemoteMessageDisplayed(let messageID): - return "m_mac_netp_remote_message_displayed_\(messageID)" - case .networkProtectionRemoteMessageDismissed(let messageID): - return "m_mac_netp_remote_message_dismissed_\(messageID)" - case .networkProtectionRemoteMessageOpened(let messageID): - return "m_mac_netp_remote_message_opened_\(messageID)" - case .networkProtectionEnabledOnSearch: - return "m_mac_netp_ev_enabled_on_search" - - // Sync - case .syncSignupDirect: - return "m_mac_sync_signup_direct" - case .syncSignupConnect: - return "m_mac_sync_signup_connect" - case .syncLogin: - return "m_mac_sync_login" - case .syncDaily: - return "m_mac_sync_daily" - case .syncDuckAddressOverride: - return "m_mac_sync_duck_address_override" - case .syncSuccessRateDaily: - return "m_mac_sync_success_rate_daily" - case .syncLocalTimestampResolutionTriggered(let feature): - return "m_mac_sync_\(feature.name)_local_timestamp_resolution_triggered" - case .syncBookmarksCountLimitExceededDaily: return "m_mac_sync_bookmarks_count_limit_exceeded_daily" - case .syncCredentialsCountLimitExceededDaily: return "m_mac_sync_credentials_count_limit_exceeded_daily" - case .syncBookmarksRequestSizeLimitExceededDaily: return "m_mac_sync_bookmarks_request_size_limit_exceeded_daily" - case .syncCredentialsRequestSizeLimitExceededDaily: return "m_mac_sync_credentials_request_size_limit_exceeded_daily" - - case .dataBrokerProtectionWaitlistUserActive: - return "m_mac_dbp_waitlist_user_active" - case .dataBrokerProtectionWaitlistEntryPointMenuItemDisplayed: - return "m_mac_dbp_imp_settings_entry_menu_item" - case .dataBrokerProtectionWaitlistIntroDisplayed: - return "m_mac_dbp_imp_intro_screen" - case .dataBrokerProtectionWaitlistNotificationShown: - return "m_mac_dbp_ev_waitlist_notification_shown" - case .dataBrokerProtectionWaitlistNotificationTapped: - return "m_mac_dbp_ev_waitlist_notification_launched" - case .dataBrokerProtectionWaitlistCardUITapped: - return "m_mac_dbp_ev_waitlist_card_ui_launched" - case .dataBrokerProtectionWaitlistTermsAndConditionsDisplayed: - return "m_mac_dbp_imp_terms" - case .dataBrokerProtectionWaitlistTermsAndConditionsAccepted: - return "m_mac_dbp_ev_terms_accepted" - case .dataBrokerProtectionErrorWhenFetchingSubscriptionAuthTokenAfterSignIn: - return "m_mac_dbp_error_when_fetching_subscription_auth_token_after_sign_in" - case .dataBrokerProtectionRemoteMessageDisplayed(let messageID): - return "m_mac_dbp_remote_message_displayed_\(messageID)" - case .dataBrokerProtectionRemoteMessageDismissed(let messageID): - return "m_mac_dbp_remote_message_dismissed_\(messageID)" - case .dataBrokerProtectionRemoteMessageOpened(let messageID): - return "m_mac_dbp_remote_message_opened_\(messageID)" - - case .dataBrokerEnableLoginItemDaily: return "m_mac_dbp_daily_login-item_enable" - case .dataBrokerDisableLoginItemDaily: return "m_mac_dbp_daily_login-item_disable" - case .dataBrokerResetLoginItemDaily: return "m_mac_dbp_daily_login-item_reset" - case .dataBrokerDisableAndDeleteDaily: return "m_mac_dbp_daily_disable-and-delete" - - case .dailyPixel(let pixel, isFirst: let isFirst): - return pixel.name + (isFirst ? "_d" : "_c") - case .networkProtectionGeoswitchingOpened: - return "m_mac_netp_imp_geoswitching_c" - case .networkProtectionGeoswitchingSetNearest: - return "m_mac_netp_ev_geoswitching_set_nearest" - case .networkProtectionGeoswitchingSetCustom: - return "m_mac_netp_ev_geoswitching_set_custom" - case .networkProtectionGeoswitchingNoLocations: - return "m_mac_netp_ev_geoswitching_no_locations" - - case .defaultRequestedFromHomepage: return "m_mac_default_requested_from_homepage" - case .defaultRequestedFromHomepageSetupView: return "m_mac_default_requested_from_homepage_setup_view" - case .defaultRequestedFromSettings: return "m_mac_default_requested_from_settings" - case .defaultRequestedFromOnboarding: return "m_mac_default_requested_from_onboarding" - - // MARK: - Subscription - case .privacyProFeatureEnabled: return - "m_mac_\(appDistribution)_privacy-pro_feature_enabled" - case .privacyProBetaUserThankYouVPN: return "m_mac_\(appDistribution)_privacy-pro_promotion-dialog_shown_vpn" - case .privacyProBetaUserThankYouDBP: return "m_mac_\(appDistribution)_privacy-pro_promotion-dialog_shown_dbp" - case .privacyProSubscriptionActive: return "m_mac_\(appDistribution)_privacy-pro_app_subscription_active" - case .privacyProOfferScreenImpression: return "m_mac_\(appDistribution)_privacy-pro_offer_screen_impression" - case .privacyProPurchaseAttempt: return "m_mac_\(appDistribution)_privacy-pro_terms-conditions_subscribe_click" - case .privacyProPurchaseFailure: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_failure_other" - case .privacyProPurchaseFailureStoreError: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_failure_store" - case .privacyProPurchaseFailureBackendError: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_failure_backend" - case .privacyProPurchaseFailureAccountNotCreated: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_failure_account-creation" - case .privacyProPurchaseSuccess: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_success" - case .privacyProRestorePurchaseOfferPageEntry: return "m_mac_\(appDistribution)_privacy-pro_offer_restore-purchase_click" - case .privacyProRestorePurchaseClick: return "m_mac_\(appDistribution)_privacy-pro_settings_restore-purchase_click" - case .privacyProRestorePurchaseSettingsMenuEntry: return "m_mac_\(appDistribution)_privacy-pro_settings_restore-purchase_click" - case .privacyProRestorePurchaseEmailStart: return "m_mac_\(appDistribution)_privacy-pro_activate-subscription_enter-email_click" - case .privacyProRestorePurchaseStoreStart: return "m_mac_\(appDistribution)_privacy-pro_activate-subscription_restore-purchase_click" - case .privacyProRestorePurchaseEmailSuccess: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-restore-using-email_success" - case .privacyProRestorePurchaseStoreSuccess: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-restore-using-store_success" - case .privacyProRestorePurchaseStoreFailureNotFound: return "m_mac_\(appDistribution)_privacy-pro_subscription-restore-using-store_failure_not-found" - case .privacyProRestorePurchaseStoreFailureOther: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-restore-using-store_failure_other" - case .privacyProRestoreAfterPurchaseAttempt: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-restore-after-purchase-attempt_success" - case .privacyProSubscriptionActivated: return "m_mac_\(appDistribution)_privacy-pro_app_subscription_activated_u" - case .privacyProWelcomeAddDevice: return "m_mac_\(appDistribution)_privacy-pro_welcome_add-device_click_u" - case .privacyProSettingsAddDevice: return "m_mac_\(appDistribution)_privacy-pro_settings_add-device_click" - case .privacyProAddDeviceEnterEmail: return "m_mac_\(appDistribution)_privacy-pro_add-device_enter-email_click" - case .privacyProWelcomeVPN: return "m_mac_\(appDistribution)_privacy-pro_welcome_vpn_click_u" - case .privacyProWelcomePersonalInformationRemoval: return "m_mac_\(appDistribution)_privacy-pro_welcome_personal-information-removal_click_u" - case .privacyProWelcomeIdentityRestoration: return "m_mac_\(appDistribution)_privacy-pro_welcome_identity-theft-restoration_click_u" - case .privacyProSubscriptionSettings: return "m_mac_\(appDistribution)_privacy-pro_settings_screen_impression" - case .privacyProVPNSettings: return "m_mac_\(appDistribution)_privacy-pro_settings_vpn_click" - case .privacyProPersonalInformationRemovalSettings: return "m_mac_\(appDistribution)_privacy-pro_settings_personal-information-removal_click" - case .privacyProIdentityRestorationSettings: return "m_mac_\(appDistribution)_privacy-pro_settings_identity-theft-restoration_click" - case .privacyProSubscriptionManagementEmail: return "m_mac_\(appDistribution)_privacy-pro_manage-email_edit_click" - case .privacyProSubscriptionManagementPlanBilling: return "m_mac_\(appDistribution)_privacy-pro_settings_change-plan-or-billing_click" - case .privacyProSubscriptionManagementRemoval: return "m_mac_\(appDistribution)_privacy-pro_settings_remove-from-device_click" - case .privacyProPurchaseStripeSuccess: return "m_mac_\(appDistribution)_privacy-pro_app_subscription-purchase_stripe_success" - // Web - case .privacyProOfferMonthlyPriceClick: return "m_mac_\(appDistribution)_privacy-pro_offer_monthly-price_click" - case .privacyProOfferYearlyPriceClick: return "m_mac_\(appDistribution)_privacy-pro_offer_yearly-price_click" - case .privacyProAddEmailSuccess: return "m_mac_\(appDistribution)_privacy-pro_app_add-email_success_u" - case .privacyProWelcomeFAQClick: return "m_mac_\(appDistribution)_privacy-pro_welcome_faq_click_u" - - case .protectionToggledOffBreakageReport: return "m_mac_protection-toggled-off-breakage-report" - case .toggleProtectionsDailyCount: return "m_mac_toggle-protections-daily-count" - case .toggleReportDoNotSend: return "m_mac_toggle-report-do-not-send" - case .toggleReportDismiss: return "m_mac_toggle-report-dismiss" - - // Password Import Keychain Prompt - case .passwordImportKeychainPrompt: return "m_mac_password_import_keychain_prompt" - case .passwordImportKeychainPromptDenied: return "m_mac_password_import_keychain_prompt_denied" - - // Installation Attribution - case .installationAttribution: return "m_mac_install" - } - } -} - -// swiftlint:disable private_over_fileprivate -#if APPSTORE -fileprivate let appDistribution = "store" -#else -fileprivate let appDistribution = "direct" -#endif -// swiftlint:enable private_over_fileprivate - -extension Pixel.Event: Equatable { - - static func == (lhs: Pixel.Event, rhs: Pixel.Event) -> Bool { - lhs.name == rhs.name && lhs.parameters == rhs.parameters - } - -} - -extension Pixel.Event.Debug { - - var name: String { - switch self { - case .pixelKitEvent(let event): - return event.name - - case .assertionFailure: - return "assertion_failure" - - case .dbMakeDatabaseError: - return "database_make_database_error" - case .dbContainerInitializationError: - return "database_container_error" - case .dbInitializationError: - return "dbie" - case .dbSaveExcludedHTTPSDomainsError: - return "dbsw" - case .dbSaveBloomFilterError: - return "dbsb" - - case .configurationFetchError: - return "cfgfetch" - - case .trackerDataParseFailed: - return "tds_p" - case .trackerDataReloadFailed: - return "tds_r" - case .trackerDataCouldNotBeLoaded: - return "tds_l" - - case .privacyConfigurationParseFailed: - return "pcf_p" - case .privacyConfigurationReloadFailed: - return "pcf_r" - case .privacyConfigurationCouldNotBeLoaded: - return "pcf_l" - - case .fileStoreWriteFailed: - return "fswf" - case .fileMoveToDownloadsFailed: - return "df" - case .fileGetDownloadLocationFailed: - return "dl" - case .fileAccessRelatedItemFailed: - return "dari" - case .fileDownloadCreatePresentersFailed: - return "dfpf" - case .downloadResumeDataCodingFailed: - return "drdc" - - case .suggestionsFetchFailed: - return "sgf" - case .appOpenURLFailed: - return "url" - case .appStateRestorationFailed: - return "srf" - - case .contentBlockingErrorReportingIssue: - return "content_blocking_error_reporting_issue" - - case .contentBlockingCompilationFailed(let listType, let component): - let componentString: String - switch component { - case .tds: - componentString = "fetched_tds" - case .allowlist: - componentString = "allow_list" - case .tempUnprotected: - componentString = "temp_list" - case .localUnprotected: - componentString = "unprotected_list" - case .fallbackTds: - componentString = "fallback_tds" - } - return "content_blocking_\(listType)_compilation_error_\(componentString)" - - case .contentBlockingCompilationTime: - return "content_blocking_compilation_time" - - case .secureVaultInitError: - return "secure_vault_init_error" - case .secureVaultError: - return "secure_vault_error" - - case .feedbackReportingFailed: - return "feedback_reporting_failed" - - case .blankNavigationOnBurnFailed: - return "blank_navigation_on_burn_failed" - - case .historyRemoveFailed: - return "history_remove_failed" - case .historyReloadFailed: - return "history_reload_failed" - case .historyCleanEntriesFailed: - return "history_clean_entries_failed" - case .historyCleanVisitsFailed: - return "history_clean_visits_failed" - case .historySaveFailed: - return "history_save_failed" - case .historySaveFailedDaily: - return "history_save_failed_daily" - case .historyInsertVisitFailed: - return "history_insert_visit_failed" - case .historyRemoveVisitsFailed: - return "history_remove_visits_failed" - - case .emailAutofillKeychainError: - return "email_autofill_keychain_error" - - case .bookmarksStoreRootFolderMigrationFailed: - return "bookmarks_store_root_folder_migration_failed" - case .bookmarksStoreFavoritesFolderMigrationFailed: - return "bookmarks_store_favorites_folder_migration_failed" - - case .adAttributionCompilationFailedForAttributedRulesList: - return "ad_attribution_compilation_failed_for_attributed_rules_list" - case .adAttributionGlobalAttributedRulesDoNotExist: - return "ad_attribution_global_attributed_rules_do_not_exist" - case .adAttributionDetectionHeuristicsDidNotMatchDomain: - return "ad_attribution_detection_heuristics_did_not_match_domain" - case .adAttributionLogicUnexpectedStateOnRulesCompiled: - return "ad_attribution_logic_unexpected_state_on_rules_compiled" - case .adAttributionLogicUnexpectedStateOnInheritedAttribution: - return "ad_attribution_logic_unexpected_state_on_inherited_attribution_2" - case .adAttributionLogicUnexpectedStateOnRulesCompilationFailed: - return "ad_attribution_logic_unexpected_state_on_rules_compilation_failed" - case .adAttributionDetectionInvalidDomainInParameter: - return "ad_attribution_detection_invalid_domain_in_parameter" - case .adAttributionLogicRequestingAttributionTimedOut: - return "ad_attribution_logic_requesting_attribution_timed_out" - case .adAttributionLogicWrongVendorOnSuccessfulCompilation: - return "ad_attribution_logic_wrong_vendor_on_successful_compilation" - case .adAttributionLogicWrongVendorOnFailedCompilation: - return "ad_attribution_logic_wrong_vendor_on_failed_compilation" - - case .webKitDidTerminate: - return "webkit_did_terminate" - - case .removedInvalidBookmarkManagedObjects: - return "removed_invalid_bookmark_managed_objects" - - case .bitwardenNotResponding: - return "bitwarden_not_responding" - case .bitwardenRespondedCannotDecryptUnique: - return "bitwarden_responded_cannot_decrypt_unique" - case .bitwardenHandshakeFailed: - return "bitwarden_handshake_failed" - case .bitwardenDecryptionOfSharedKeyFailed: - return "bitwarden_decryption_of_shared_key_failed" - case .bitwardenStoringOfTheSharedKeyFailed: - return "bitwarden_storing_of_the_shared_key_failed" - case .bitwardenCredentialRetrievalFailed: - return "bitwarden_credential_retrieval_failed" - case .bitwardenCredentialCreationFailed: - return "bitwarden_credential_creation_failed" - case .bitwardenCredentialUpdateFailed: - return "bitwarden_credential_update_failed" - case .bitwardenRespondedWithError: - return "bitwarden_responded_with_error" - case .bitwardenNoActiveVault: - return "bitwarden_no_active_vault" - case .bitwardenParsingFailed: - return "bitwarden_parsing_failed" - case .bitwardenStatusParsingFailed: - return "bitwarden_status_parsing_failed" - case .bitwardenHmacComparisonFailed: - return "bitwarden_hmac_comparison_failed" - case .bitwardenDecryptionFailed: - return "bitwarden_decryption_failed" - case .bitwardenSendingOfMessageFailed: - return "bitwarden_sending_of_message_failed" - case .bitwardenSharedKeyInjectionFailed: - return "bitwarden_shared_key_injection_failed" - - case .updaterAborted: - return "updater_aborted" - case .userSelectedToSkipUpdate: - return "user_selected_to_skip_update" - case .userSelectedToInstallUpdate: - return "user_selected_to_install_update" - case .userSelectedToDismissUpdate: - return "user_selected_to_dismiss_update" - - case .faviconDecryptionFailedUnique: - return "favicon_decryption_failed_unique" - case .downloadListItemDecryptionFailedUnique: - return "download_list_item_decryption_failed_unique" - case .historyEntryDecryptionFailedUnique: - return "history_entry_decryption_failed_unique" - case .permissionDecryptionFailedUnique: - return "permission_decryption_failed_unique" - - case .missingParent: return "bookmark_missing_parent" - case .bookmarksSaveFailed: return "bookmarks_save_failed" - case .bookmarksSaveFailedOnImport: return "bookmarks_save_failed_on_import" - - case .bookmarksCouldNotLoadDatabase: return "bookmarks_could_not_load_database" - case .bookmarksCouldNotPrepareDatabase: return "bookmarks_could_not_prepare_database" - case .bookmarksMigrationAlreadyPerformed: return "bookmarks_migration_already_performed" - case .bookmarksMigrationFailed: return "bookmarks_migration_failed" - case .bookmarksMigrationCouldNotPrepareDatabase: return "bookmarks_migration_could_not_prepare_database" - case .bookmarksMigrationCouldNotPrepareDatabaseOnFailedMigration: - return "bookmarks_migration_could_not_prepare_database_on_failed_migration" - case .bookmarksMigrationCouldNotRemoveOldStore: return "bookmarks_migration_could_not_remove_old_store" - case .bookmarksMigrationCouldNotPrepareMultipleFavoriteFolders: - return "bookmarks_migration_could_not_prepare_multiple_favorite_folders" - - case .syncSentUnauthenticatedRequest: return "sync_sent_unauthenticated_request" - case .syncMetadataCouldNotLoadDatabase: return "sync_metadata_could_not_load_database" - case .syncBookmarksProviderInitializationFailed: return "sync_bookmarks_provider_initialization_failed" - case .syncBookmarksFailed: return "sync_bookmarks_failed" - case .syncCredentialsProviderInitializationFailed: return "sync_credentials_provider_initialization_failed" - case .syncCredentialsFailed: return "sync_credentials_failed" - case .syncSettingsFailed: return "sync_settings_failed" - case .syncSettingsMetadataUpdateFailed: return "sync_settings_metadata_update_failed" - case .syncSignupError: return "sync_signup_error" - case .syncLoginError: return "sync_login_error" - case .syncLogoutError: return "sync_logout_error" - case .syncUpdateDeviceError: return "sync_update_device_error" - case .syncRemoveDeviceError: return "sync_remove_device_error" - case .syncDeleteAccountError: return "sync_delete_account_error" - case .syncLoginExistingAccountError: return "sync_login_existing_account_error" - case .syncCannotCreateRecoveryPDF: return "sync_cannot_create_recovery_pdf" - - case .bookmarksCleanupFailed: return "bookmarks_cleanup_failed" - case .bookmarksCleanupAttemptedWhileSyncWasEnabled: return "bookmarks_cleanup_attempted_while_sync_was_enabled" - case .favoritesCleanupFailed: return "favorites_cleanup_failed" - case .bookmarksFaviconsFetcherStateStoreInitializationFailed: return "bookmarks_favicons_fetcher_state_store_initialization_failed" - case .bookmarksFaviconsFetcherFailed: return "bookmarks_favicons_fetcher_failed" - - case .credentialsDatabaseCleanupFailed: return "credentials_database_cleanup_failed" - case .credentialsCleanupAttemptedWhileSyncWasEnabled: return "credentials_cleanup_attempted_while_sync_was_enabled" - - case .invalidPayload(let configuration): return "m_d_\(configuration.rawValue)_invalid_payload".lowercased() - - case .burnerTabMisplaced: return "burner_tab_misplaced" - - case .networkProtectionRemoteMessageFetchingFailed: return "netp_remote_message_fetching_failed" - case .networkProtectionRemoteMessageStorageFailed: return "netp_remote_message_storage_failed" - - case .dataBrokerProtectionRemoteMessageFetchingFailed: return "dbp_remote_message_fetching_failed" - case .dataBrokerProtectionRemoteMessageStorageFailed: return "dbp_remote_message_storage_failed" - - case .loginItemUpdateError: return "login-item_update-error" - } - } -} diff --git a/DuckDuckGo/Statistics/Pixel Legacy/PixelParameters.swift b/DuckDuckGo/Statistics/Pixel Legacy/PixelParameters.swift deleted file mode 100644 index 2fad773b42..0000000000 --- a/DuckDuckGo/Statistics/Pixel Legacy/PixelParameters.swift +++ /dev/null @@ -1,377 +0,0 @@ -// -// PixelParameters.swift -// -// Copyright © 2021 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 -import PixelKit - -extension Pixel.Event { - - var parameters: [String: String]? { - switch self { - case .pixelKitEvent(let event): - return event.parameters - - case .debug(event: let debugEvent, error: let error): - var params = error?.pixelParameters ?? [:] - - if let debugParams = debugEvent.parameters { - params.merge(debugParams) { (current, _) in current } - } - - if case let .assertionFailure(message, file, line) = debugEvent { - params[PixelKit.Parameters.assertionMessage] = message - params[PixelKit.Parameters.assertionFile] = String(file) - params[PixelKit.Parameters.assertionLine] = String(line) - } - - return params - - case .dataImportFailed(source: _, sourceVersion: let version, error: let error): - var params = error.pixelParameters - - if let version { - params[PixelKit.Parameters.sourceBrowserVersion] = version - } - return params - - case .launchInitial(let cohort): - return [PixelKit.Parameters.experimentCohort: cohort] - - case .serpInitial(let cohort): - return [PixelKit.Parameters.experimentCohort: cohort] - - case .serpDay21to27(let cohort): - return [PixelKit.Parameters.experimentCohort: cohort] - - case .dailyPixel(let pixel, isFirst: _): - return pixel.parameters - - case .dailyOsVersionCounter: - return [PixelKit.Parameters.osMajorVersion: "\(ProcessInfo.processInfo.operatingSystemVersion.majorVersion)"] - - case .dashboardProtectionAllowlistAdd(let triggerOrigin): - guard let trigger = triggerOrigin else { return nil } - return [PixelKit.Parameters.dashboardTriggerOrigin: trigger] - - case .dashboardProtectionAllowlistRemove(let triggerOrigin): - guard let trigger = triggerOrigin else { return nil } - return [PixelKit.Parameters.dashboardTriggerOrigin: trigger] - - case .syncSuccessRateDaily: - return nil - - case .vpnBreakageReport(let category, let description, let metadata): - return [ - PixelKit.Parameters.vpnBreakageCategory: category, - PixelKit.Parameters.vpnBreakageDescription: description, - PixelKit.Parameters.vpnBreakageMetadata: metadata - ] - - // Don't use default to force new items to be thought about - case .crash, - .brokenSiteReport, - .compileRulesWait, - .serp, - .formAutofilled, - .autofillItemSaved, - .autofillLoginsSaveLoginModalExcludeSiteConfirmed, - .autofillLoginsSettingsResetExcludedDisplayed, - .autofillLoginsSettingsResetExcludedConfirmed, - .autofillLoginsSettingsResetExcludedDismissed, - .bitwardenPasswordAutofilled, - .bitwardenPasswordSaved, - .ampBlockingRulesCompilationFailed, - .adClickAttributionDetected, - .adClickAttributionActive, - .adClickAttributionPageLoads, - .emailEnabled, - .emailDisabled, - .emailUserCreatedAlias, - .emailUserPressedUseAlias, - .emailUserPressedUseAddress, - .jsPixel, - .emailEnabledInitial, - .watchInDuckPlayerInitial, - .importDataInitial, - .newTabInitial, - .setAsDefaultInitial, - .favoriteSectionHidden, - .recentActivitySectionHidden, - .continueSetUpSectionHidden, - .fireButtonFirstBurn, - .fireButton, - .duckPlayerDailyUniqueView, - .duckPlayerViewFromYoutubeViaMainOverlay, - .duckPlayerViewFromYoutubeViaHoverButton, - .duckPlayerViewFromYoutubeAutomatic, - .duckPlayerViewFromSERP, - .duckPlayerViewFromOther, - .duckPlayerSettingAlways, - .duckPlayerSettingNever, - .duckPlayerSettingBackToDefault, - .networkProtectionRemoteMessageDisplayed, - .networkProtectionRemoteMessageDismissed, - .networkProtectionRemoteMessageOpened, - .networkProtectionEnabledOnSearch, - .networkProtectionGeoswitchingOpened, - .networkProtectionGeoswitchingSetNearest, - .networkProtectionGeoswitchingSetCustom, - .networkProtectionGeoswitchingNoLocations, - .syncSignupDirect, - .syncSignupConnect, - .syncLogin, - .syncDaily, - .syncDuckAddressOverride, - .syncLocalTimestampResolutionTriggered, - .syncBookmarksCountLimitExceededDaily, - .syncCredentialsCountLimitExceededDaily, - .syncBookmarksRequestSizeLimitExceededDaily, - .syncCredentialsRequestSizeLimitExceededDaily, - .dataBrokerProtectionWaitlistUserActive, - .dataBrokerProtectionWaitlistEntryPointMenuItemDisplayed, - .dataBrokerProtectionWaitlistIntroDisplayed, - .dataBrokerProtectionWaitlistNotificationShown, - .dataBrokerProtectionWaitlistNotificationTapped, - .dataBrokerProtectionWaitlistCardUITapped, - .dataBrokerProtectionWaitlistTermsAndConditionsDisplayed, - .dataBrokerProtectionWaitlistTermsAndConditionsAccepted, - .dataBrokerProtectionErrorWhenFetchingSubscriptionAuthTokenAfterSignIn, - .dataBrokerProtectionRemoteMessageOpened, - .dataBrokerProtectionRemoteMessageDisplayed, - .dataBrokerProtectionRemoteMessageDismissed, - .dataBrokerDisableAndDeleteDaily, - .dataBrokerEnableLoginItemDaily, - .dataBrokerDisableLoginItemDaily, - .dataBrokerResetLoginItemDaily, - .defaultRequestedFromHomepage, - .defaultRequestedFromHomepageSetupView, - .defaultRequestedFromSettings, - .defaultRequestedFromOnboarding, - .privacyProFeatureEnabled, - .privacyProBetaUserThankYouVPN, - .privacyProBetaUserThankYouDBP, - .privacyProSubscriptionActive, - .privacyProOfferScreenImpression, - .privacyProPurchaseAttempt, - .privacyProPurchaseFailure, - .privacyProPurchaseFailureStoreError, - .privacyProPurchaseFailureBackendError, - .privacyProPurchaseFailureAccountNotCreated, - .privacyProPurchaseSuccess, - .privacyProRestorePurchaseOfferPageEntry, - .privacyProRestorePurchaseSettingsMenuEntry, - .privacyProRestorePurchaseEmailStart, - .privacyProRestorePurchaseStoreStart, - .privacyProRestorePurchaseEmailSuccess, - .privacyProRestorePurchaseStoreSuccess, - .privacyProRestorePurchaseStoreFailureNotFound, - .privacyProRestorePurchaseStoreFailureOther, - .privacyProRestoreAfterPurchaseAttempt, - .privacyProSubscriptionActivated, - .privacyProWelcomeAddDevice, - .privacyProSettingsAddDevice, - .privacyProAddDeviceEnterEmail, - .privacyProWelcomeVPN, - .privacyProWelcomePersonalInformationRemoval, - .privacyProWelcomeIdentityRestoration, - .privacyProSubscriptionSettings, - .privacyProVPNSettings, - .privacyProPersonalInformationRemovalSettings, - .privacyProIdentityRestorationSettings, - .privacyProSubscriptionManagementEmail, - .privacyProSubscriptionManagementPlanBilling, - .privacyProSubscriptionManagementRemoval, - .privacyProRestorePurchaseClick, - .protectionToggledOffBreakageReport, - .toggleProtectionsDailyCount, - .toggleReportDoNotSend, - .toggleReportDismiss, - .privacyProOfferMonthlyPriceClick, - .privacyProOfferYearlyPriceClick, - .privacyProAddEmailSuccess, - .privacyProWelcomeFAQClick, - .privacyProPurchaseStripeSuccess, - .passwordImportKeychainPrompt, - .passwordImportKeychainPromptDenied, - .installationAttribution: - return nil - } - } - -} - -extension Pixel.Event.Debug { - - var parameters: [String: String]? { - switch self { - case .loginItemUpdateError(let loginItemBundleID, let action, let buildType, let osVersion): - return ["loginItemBundleID": loginItemBundleID, "action": action, "buildType": buildType, "macosVersion": osVersion] - case .pixelKitEvent, - .assertionFailure, - .dbMakeDatabaseError, - .dbContainerInitializationError, - .dbInitializationError, - .dbSaveExcludedHTTPSDomainsError, - .dbSaveBloomFilterError, - .configurationFetchError, - .trackerDataParseFailed, - .trackerDataReloadFailed, - .trackerDataCouldNotBeLoaded, - .privacyConfigurationParseFailed, - .privacyConfigurationReloadFailed, - .privacyConfigurationCouldNotBeLoaded, - .fileStoreWriteFailed, - .fileMoveToDownloadsFailed, - .fileGetDownloadLocationFailed, - .fileAccessRelatedItemFailed, - .fileDownloadCreatePresentersFailed, - .downloadResumeDataCodingFailed, - .suggestionsFetchFailed, - .appOpenURLFailed, - .appStateRestorationFailed, - .contentBlockingErrorReportingIssue, - .contentBlockingCompilationFailed, - .contentBlockingCompilationTime, - .secureVaultInitError, - .secureVaultError, - .feedbackReportingFailed, - .blankNavigationOnBurnFailed, - .historyRemoveFailed, - .historyReloadFailed, - .historyCleanEntriesFailed, - .historyCleanVisitsFailed, - .historySaveFailed, - .historySaveFailedDaily, - .historyInsertVisitFailed, - .historyRemoveVisitsFailed, - .emailAutofillKeychainError, - .bookmarksStoreRootFolderMigrationFailed, - .bookmarksStoreFavoritesFolderMigrationFailed, - .adAttributionCompilationFailedForAttributedRulesList, - .adAttributionGlobalAttributedRulesDoNotExist, - .adAttributionDetectionHeuristicsDidNotMatchDomain, - .adAttributionLogicUnexpectedStateOnRulesCompiled, - .adAttributionLogicUnexpectedStateOnInheritedAttribution, - .adAttributionLogicUnexpectedStateOnRulesCompilationFailed, - .adAttributionDetectionInvalidDomainInParameter, - .adAttributionLogicRequestingAttributionTimedOut, - .adAttributionLogicWrongVendorOnSuccessfulCompilation, - .adAttributionLogicWrongVendorOnFailedCompilation, - .webKitDidTerminate, - .removedInvalidBookmarkManagedObjects, - .bitwardenNotResponding, - .bitwardenRespondedCannotDecryptUnique, - .bitwardenHandshakeFailed, - .bitwardenDecryptionOfSharedKeyFailed, - .bitwardenStoringOfTheSharedKeyFailed, - .bitwardenCredentialRetrievalFailed, - .bitwardenCredentialCreationFailed, - .bitwardenCredentialUpdateFailed, - .bitwardenRespondedWithError, - .bitwardenNoActiveVault, - .bitwardenParsingFailed, - .bitwardenStatusParsingFailed, - .bitwardenHmacComparisonFailed, - .bitwardenDecryptionFailed, - .bitwardenSendingOfMessageFailed, - .bitwardenSharedKeyInjectionFailed, - .updaterAborted, - .userSelectedToSkipUpdate, - .userSelectedToInstallUpdate, - .userSelectedToDismissUpdate, - .faviconDecryptionFailedUnique, - .downloadListItemDecryptionFailedUnique, - .historyEntryDecryptionFailedUnique, - .permissionDecryptionFailedUnique, - .missingParent, - .bookmarksSaveFailed, - .bookmarksSaveFailedOnImport, - .bookmarksCouldNotLoadDatabase, - .bookmarksCouldNotPrepareDatabase, - .bookmarksMigrationAlreadyPerformed, - .bookmarksMigrationFailed, - .bookmarksMigrationCouldNotPrepareDatabase, - .bookmarksMigrationCouldNotPrepareDatabaseOnFailedMigration, - .bookmarksMigrationCouldNotRemoveOldStore, - .bookmarksMigrationCouldNotPrepareMultipleFavoriteFolders, - .syncSentUnauthenticatedRequest, - .syncMetadataCouldNotLoadDatabase, - .syncBookmarksProviderInitializationFailed, - .syncBookmarksFailed, - .syncCredentialsProviderInitializationFailed, - .syncCredentialsFailed, - .syncSettingsFailed, - .syncSettingsMetadataUpdateFailed, - .syncSignupError, - .syncLoginError, - .syncLogoutError, - .syncUpdateDeviceError, - .syncRemoveDeviceError, - .syncDeleteAccountError, - .syncLoginExistingAccountError, - .syncCannotCreateRecoveryPDF, - .bookmarksCleanupFailed, - .bookmarksCleanupAttemptedWhileSyncWasEnabled, - .favoritesCleanupFailed, - .bookmarksFaviconsFetcherStateStoreInitializationFailed, - .bookmarksFaviconsFetcherFailed, - .credentialsDatabaseCleanupFailed, - .credentialsCleanupAttemptedWhileSyncWasEnabled, - .invalidPayload, - .burnerTabMisplaced, - .networkProtectionRemoteMessageFetchingFailed, - .networkProtectionRemoteMessageStorageFailed, - .dataBrokerProtectionRemoteMessageFetchingFailed, - .dataBrokerProtectionRemoteMessageStorageFailed: - return nil - } - } - -} - -extension Error { - - var pixelParameters: [String: String] { - var params = [String: String]() - - if let errorWithUserInfo = self as? ErrorWithPixelParameters { - params = errorWithUserInfo.errorParameters - } - - let nsError = self as NSError - - params[PixelKit.Parameters.errorCode] = "\(nsError.code)" - params[PixelKit.Parameters.errorDomain] = nsError.domain - - if let underlyingError = nsError.userInfo[NSUnderlyingErrorKey] as? NSError { - params[PixelKit.Parameters.underlyingErrorCode] = "\(underlyingError.code)" - params[PixelKit.Parameters.underlyingErrorDomain] = underlyingError.domain - } - - if let sqlErrorCode = nsError.userInfo["SQLiteResultCode"] as? NSNumber { - params[PixelKit.Parameters.underlyingErrorSQLiteCode] = "\(sqlErrorCode.intValue)" - } - - if let sqlExtendedErrorCode = nsError.userInfo["SQLiteExtendedResultCode"] as? NSNumber { - params[PixelKit.Parameters.underlyingErrorSQLiteExtendedCode] = "\(sqlExtendedErrorCode.intValue)" - } - - return params - } - -} From 2367796212a7c4999b076daf40fa6fed98c466f5 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 17 Apr 2024 13:49:19 +0100 Subject: [PATCH 27/31] list style fixed --- .../xcshareddata/swiftpm/Package.resolved | 63 ------------------- .../xcschemes/sandbox-test-tool.xcscheme | 2 +- .../DBP/DataBrokerProtectionDebugMenu.swift | 2 +- DuckDuckGo/MainWindow/MainView.swift | 4 +- .../NetworkProtectionDebugMenu.swift | 2 +- 5 files changed, 5 insertions(+), 68 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 091262988c..50558ec859 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -9,15 +9,6 @@ "version" : "2.0.0" } }, - { - "identity" : "barebonesbrowser", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/BareBonesBrowser.git", - "state" : { - "revision" : "31e5bfedc3c2ca005640c4bf2b6959d69b0e18b9", - "version" : "0.1.0" - } - }, { "identity" : "bloom_cpp", "kind" : "remoteSourceControl", @@ -63,33 +54,6 @@ "version" : "2.3.0" } }, - { - "identity" : "lottie-spm", - "kind" : "remoteSourceControl", - "location" : "https://github.com/airbnb/lottie-spm.git", - "state" : { - "revision" : "3bd43e12d6fb54654366a61f7cfaca787318b8ce", - "version" : "4.4.1" - } - }, - { - "identity" : "ohhttpstubs", - "kind" : "remoteSourceControl", - "location" : "https://github.com/AliSoftware/OHHTTPStubs.git", - "state" : { - "revision" : "12f19662426d0434d6c330c6974d53e2eb10ecd9", - "version" : "9.1.0" - } - }, - { - "identity" : "openssl-xcframework", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/OpenSSL-XCFramework", - "state" : { - "revision" : "b75ab2c0405860bb2616db71b9a456acb118c21a", - "version" : "3.1.4000" - } - }, { "identity" : "privacy-dashboard", "kind" : "remoteSourceControl", @@ -108,15 +72,6 @@ "version" : "2.1.0" } }, - { - "identity" : "sparkle", - "kind" : "remoteSourceControl", - "location" : "https://github.com/sparkle-project/Sparkle.git", - "state" : { - "revision" : "0a4caaf7a81eea2cece651ef4b17331fa0634dff", - "version" : "2.6.0" - } - }, { "identity" : "swift-argument-parser", "kind" : "remoteSourceControl", @@ -126,15 +81,6 @@ "version" : "1.3.1" } }, - { - "identity" : "swift-snapshot-testing", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-snapshot-testing", - "state" : { - "revision" : "625ccca8570773dd84a34ee51a81aa2bc5a4f97a", - "version" : "1.16.0" - } - }, { "identity" : "swift-syntax", "kind" : "remoteSourceControl", @@ -144,15 +90,6 @@ "version" : "509.1.1" } }, - { - "identity" : "swifter", - "kind" : "remoteSourceControl", - "location" : "https://github.com/httpswift/swifter.git", - "state" : { - "revision" : "9483a5d459b45c3ffd059f7b55f9638e268632fd", - "version" : "1.5.0" - } - }, { "identity" : "sync_crypto", "kind" : "remoteSourceControl", diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme index eb7e5e26bb..41730d7069 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme @@ -1,7 +1,7 @@ + version = "1.7"> Date: Wed, 17 Apr 2024 14:03:51 +0100 Subject: [PATCH 28/31] pixels unit tests improved --- .../xcshareddata/swiftpm/Package.resolved | 63 +++++++++++++++++++ .../XCTestCase+PixelKit.swift | 4 ++ 2 files changed, 67 insertions(+) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 50558ec859..091262988c 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -9,6 +9,15 @@ "version" : "2.0.0" } }, + { + "identity" : "barebonesbrowser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/BareBonesBrowser.git", + "state" : { + "revision" : "31e5bfedc3c2ca005640c4bf2b6959d69b0e18b9", + "version" : "0.1.0" + } + }, { "identity" : "bloom_cpp", "kind" : "remoteSourceControl", @@ -54,6 +63,33 @@ "version" : "2.3.0" } }, + { + "identity" : "lottie-spm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/airbnb/lottie-spm.git", + "state" : { + "revision" : "3bd43e12d6fb54654366a61f7cfaca787318b8ce", + "version" : "4.4.1" + } + }, + { + "identity" : "ohhttpstubs", + "kind" : "remoteSourceControl", + "location" : "https://github.com/AliSoftware/OHHTTPStubs.git", + "state" : { + "revision" : "12f19662426d0434d6c330c6974d53e2eb10ecd9", + "version" : "9.1.0" + } + }, + { + "identity" : "openssl-xcframework", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/OpenSSL-XCFramework", + "state" : { + "revision" : "b75ab2c0405860bb2616db71b9a456acb118c21a", + "version" : "3.1.4000" + } + }, { "identity" : "privacy-dashboard", "kind" : "remoteSourceControl", @@ -72,6 +108,15 @@ "version" : "2.1.0" } }, + { + "identity" : "sparkle", + "kind" : "remoteSourceControl", + "location" : "https://github.com/sparkle-project/Sparkle.git", + "state" : { + "revision" : "0a4caaf7a81eea2cece651ef4b17331fa0634dff", + "version" : "2.6.0" + } + }, { "identity" : "swift-argument-parser", "kind" : "remoteSourceControl", @@ -81,6 +126,15 @@ "version" : "1.3.1" } }, + { + "identity" : "swift-snapshot-testing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-snapshot-testing", + "state" : { + "revision" : "625ccca8570773dd84a34ee51a81aa2bc5a4f97a", + "version" : "1.16.0" + } + }, { "identity" : "swift-syntax", "kind" : "remoteSourceControl", @@ -90,6 +144,15 @@ "version" : "509.1.1" } }, + { + "identity" : "swifter", + "kind" : "remoteSourceControl", + "location" : "https://github.com/httpswift/swifter.git", + "state" : { + "revision" : "9483a5d459b45c3ffd059f7b55f9638e268632fd", + "version" : "1.5.0" + } + }, { "identity" : "sync_crypto", "kind" : "remoteSourceControl", diff --git a/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift index 49546e9095..81232cd4bb 100644 --- a/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift @@ -128,7 +128,11 @@ public extension XCTestCase { }) if frequency == .dailyAndCount { + XCTAssertTrue(firedPixelName.hasPrefix(expectations.pixelName)) XCTAssertTrue(firedPixelName.hasSuffix("_c") || firedPixelName.hasSuffix("_d")) + XCTAssertEqual(firedPixelName.count, expectations.pixelName.count + 2) + let exp = self.expectedPixelNames(originalName: expectations.pixelName, frequency: frequency) + XCTAssertTrue(exp.contains(firedPixelName)) } else { XCTAssertEqual(expectations.pixelName, firedPixelName) } From 47bc21971da3ad3c03feedaa570e2776ced6edd9 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 17 Apr 2024 14:42:33 +0100 Subject: [PATCH 29/31] swift-snapshot-testing downgraded to previous version --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 378bcf928a..a53a9b2dd4 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -14532,7 +14532,7 @@ repositoryURL = "https://github.com/pointfreeco/swift-snapshot-testing"; requirement = { kind = exactVersion; - version = 1.16.0; + version = 1.15.4; }; }; B6DA44152616C13800DD1EC2 /* XCRemoteSwiftPackageReference "OHHTTPStubs" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 091262988c..05b5e0a844 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -131,8 +131,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-snapshot-testing", "state" : { - "revision" : "625ccca8570773dd84a34ee51a81aa2bc5a4f97a", - "version" : "1.16.0" + "revision" : "5b0c434778f2c1a4c9b5ebdb8682b28e84dd69bd", + "version" : "1.15.4" } }, { From 6bbd20965384f2e3422551ac73b199bd900ebee6 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 17 Apr 2024 15:49:46 +0100 Subject: [PATCH 30/31] unit tests fixed --- UnitTests/Permissions/PermissionModelTests.swift | 8 ++++++++ UnitTests/Permissions/PermissionStoreTests.swift | 10 ++++++++++ UnitTests/Statistics/ATB/StatisticsLoaderTests.swift | 12 +++++------- .../Statistics/CBRCompileTimeReporterTests.swift | 6 ++---- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/UnitTests/Permissions/PermissionModelTests.swift b/UnitTests/Permissions/PermissionModelTests.swift index 1357aec53a..1a31091936 100644 --- a/UnitTests/Permissions/PermissionModelTests.swift +++ b/UnitTests/Permissions/PermissionModelTests.swift @@ -21,6 +21,7 @@ import Combine import Foundation import WebKit import XCTest +@testable import PixelKit @testable import DuckDuckGo_Privacy_Browser @@ -31,6 +32,11 @@ final class PermissionModelTests: XCTestCase { var geolocationProviderMock: GeolocationProviderMock! let webView = WebViewMock() var model: PermissionModel! + let pixelKit = PixelKit(dryRun: true, + appVersion: "1.0.0", + defaultHeaders: [:], + defaults: UserDefaults(), + fireRequest: { _, _, _, _, _, _ in }) var securityOrigin: WKSecurityOrigin { WKSecurityOriginMock.new(url: .duckDuckGo) @@ -42,6 +48,8 @@ final class PermissionModelTests: XCTestCase { } override func setUp() { + PixelKit.setSharedForTesting(pixelKit: pixelKit) + webView.uiDelegate = self geolocationProviderMock = GeolocationProviderMock(geolocationService: geolocationServiceMock) diff --git a/UnitTests/Permissions/PermissionStoreTests.swift b/UnitTests/Permissions/PermissionStoreTests.swift index 4566288bf5..f95ff7f2ed 100644 --- a/UnitTests/Permissions/PermissionStoreTests.swift +++ b/UnitTests/Permissions/PermissionStoreTests.swift @@ -19,11 +19,21 @@ import Foundation import XCTest @testable import DuckDuckGo_Privacy_Browser +@testable import PixelKit final class PermissionStoreTests: XCTestCase { let container = CoreData.permissionContainer() lazy var store = LocalPermissionStore(context: container.viewContext) + let pixelKit = PixelKit(dryRun: true, + appVersion: "1.0.0", + defaultHeaders: [:], + defaults: UserDefaults(), + fireRequest: { _, _, _, _, _, _ in }) + + override func setUp() { + PixelKit.setSharedForTesting(pixelKit: pixelKit) + } func testWhenPermissionIsAddedThenItMustBeLoadedFromStore() throws { let stored1 = try store.add(domain: "duckduckgo.com", permissionType: .camera, decision: .allow) diff --git a/UnitTests/Statistics/ATB/StatisticsLoaderTests.swift b/UnitTests/Statistics/ATB/StatisticsLoaderTests.swift index aff70d88e6..eb6bbfaa2c 100644 --- a/UnitTests/Statistics/ATB/StatisticsLoaderTests.swift +++ b/UnitTests/Statistics/ATB/StatisticsLoaderTests.swift @@ -27,13 +27,11 @@ class StatisticsLoaderTests: XCTestCase { private var mockAttributionsPixelHandler: MockAttributionsPixelHandler! private var mockStatisticsStore: StatisticsStore! private var testee: StatisticsLoader! - let pixelKit = PixelKit(dryRun: false, - appVersion: "1.0.0", - defaultHeaders: [:], - defaults: UserDefaults(), - fireRequest: { _, _, _, _, _, _ in - - }) + let pixelKit = PixelKit(dryRun: true, + appVersion: "1.0.0", + defaultHeaders: [:], + defaults: UserDefaults(), + fireRequest: { _, _, _, _, _, _ in }) override func setUp() { PixelKit.setSharedForTesting(pixelKit: pixelKit) diff --git a/UnitTests/Statistics/CBRCompileTimeReporterTests.swift b/UnitTests/Statistics/CBRCompileTimeReporterTests.swift index 8ba6037ae6..f6e7aef1d2 100644 --- a/UnitTests/Statistics/CBRCompileTimeReporterTests.swift +++ b/UnitTests/Statistics/CBRCompileTimeReporterTests.swift @@ -28,13 +28,11 @@ class CBRCompileTimeReporterTests: XCTestCase { let host = "improving.duckduckgo.com" var tab: NSObject! = NSObject() var time = CACurrentMediaTime() - let pixelKit = PixelKit(dryRun: false, + let pixelKit = PixelKit(dryRun: true, appVersion: "1.0.0", defaultHeaders: [:], defaults: UserDefaults(), - fireRequest: { _, _, _, _, _, _ in - - }) + fireRequest: { _, _, _, _, _, _ in }) override func setUp() { PixelKit.setSharedForTesting(pixelKit: pixelKit) From 1c17e9a23b79fdece2e5cbae091ac608b0dcc9ba Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 17 Apr 2024 16:02:30 +0100 Subject: [PATCH 31/31] reverting xcode 15.3 changes --- .../xcshareddata/xcschemes/sandbox-test-tool.xcscheme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme index 41730d7069..eb7e5e26bb 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme @@ -1,7 +1,7 @@ + version = "1.8">