diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index 4798b176f9..1b5c68a3ee 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -22,6 +22,8 @@ import Common import WebKit import Configuration import History +import PixelKit +import VPNPrivacyPro // Actions are sent to objects of responder chain @@ -717,11 +719,19 @@ extension MainViewController { let state = internalUserDecider.isInternalUser internalUserDecider.debugSetInternalUserState(!state) - // Aid to transition VPN from waitlist to subscription - // by resetting this we allow users to go back to waitlist - // and re-test. + clearPrivacyProState() + } + + /// Clears the PrivacyPro state to make testing easier. + /// + private func clearPrivacyProState() { resetThankYouModalChecks(nil) UserDefaults.netP.networkProtectionEntitlementsExpired = false + + // Clear pixel data + DailyPixel.clearLastFireDate(pixel: .privacyProEnabled) + Pixel.shared?.clearRepetitions(for: .privacyProBetaUserThankYouDBP) + Pixel.shared?.clearRepetitions(for: .privacyProBetaUserThankYouVPN) } @objc func resetDailyPixels(_ sender: Any?) { diff --git a/DuckDuckGo/Statistics/DailyPixel.swift b/DuckDuckGo/Statistics/DailyPixel.swift index 9aade6d337..e436d453ba 100644 --- a/DuckDuckGo/Statistics/DailyPixel.swift +++ b/DuckDuckGo/Statistics/DailyPixel.swift @@ -66,6 +66,10 @@ final class DailyPixel { storage.set(Date(), forKey: pixel.name) } + static func clearLastFireDate(pixel: Pixel.Event) { + storage.removeObject(forKey: pixel.name) + } + } private extension Pixel.Event { diff --git a/DuckDuckGo/Statistics/Pixel.swift b/DuckDuckGo/Statistics/Pixel.swift index 4dddf4d740..30fde214bd 100644 --- a/DuckDuckGo/Statistics/Pixel.swift +++ b/DuckDuckGo/Statistics/Pixel.swift @@ -154,6 +154,9 @@ final class Pixel { 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) { diff --git a/DuckDuckGo/Statistics/PixelEvent.swift b/DuckDuckGo/Statistics/PixelEvent.swift index bd576d9bf0..518c133738 100644 --- a/DuckDuckGo/Statistics/PixelEvent.swift +++ b/DuckDuckGo/Statistics/PixelEvent.swift @@ -218,6 +218,9 @@ extension Pixel { case dataBrokerProtectionErrorWhenFetchingSubscriptionAuthTokenAfterSignIn // Subscription + case privacyProEnabled + case privacyProBetaUserThankYouVPN + case privacyProBetaUserThankYouDBP case privacyProSubscriptionActive case privacyProOfferScreenImpression case privacyProPurchaseAttempt @@ -651,6 +654,10 @@ extension Pixel.Event { case .defaultRequestedFromOnboarding: return "m_mac_default_requested_from_onboarding" // MARK: - Subscription + case .privacyProEnabled: return + "m_mac_\(appDistribution)_privacy-pro_enabled" + case .privacyProBetaUserThankYouVPN: return "m_mac_\(appDistribution)_privacy-pro_promotion-dialog_shown_vpn_u" + case .privacyProBetaUserThankYouDBP: return "m_mac_\(appDistribution)_privacy-pro_promotion-dialog_shown_dbp_u" 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" @@ -693,7 +700,6 @@ extension Pixel.Event { 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" - } } } diff --git a/DuckDuckGo/Statistics/PixelParameters.swift b/DuckDuckGo/Statistics/PixelParameters.swift index bca6af9060..1c2f4d7037 100644 --- a/DuckDuckGo/Statistics/PixelParameters.swift +++ b/DuckDuckGo/Statistics/PixelParameters.swift @@ -170,6 +170,9 @@ extension Pixel.Event { .defaultRequestedFromHomepageSetupView, .defaultRequestedFromSettings, .defaultRequestedFromOnboarding, + .privacyProEnabled, + .privacyProBetaUserThankYouVPN, + .privacyProBetaUserThankYouDBP, .privacyProSubscriptionActive, .privacyProOfferScreenImpression, .privacyProPurchaseAttempt, diff --git a/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift b/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift index 255dbd6dff..9d162e543a 100644 --- a/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift +++ b/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift @@ -25,9 +25,11 @@ import NetworkExtension import NetworkProtection import NetworkProtectionUI import LoginItems +import PixelKit #if SUBSCRIPTION import Subscription +import VPNPrivacyPro #endif protocol NetworkProtectionFeatureVisibility { @@ -173,6 +175,7 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { return false } + PixelKit.fire(VPNPrivacyProPixel.vpnBetaStoppedWhenPrivacyProEnabled, frequency: .dailyAndContinuous) 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 974a11191d..8fa599cbdc 100644 --- a/DuckDuckGo/Waitlist/Views/WaitlistThankYouPromptPresenter.swift +++ b/DuckDuckGo/Waitlist/Views/WaitlistThankYouPromptPresenter.swift @@ -18,8 +18,6 @@ import AppKit import Foundation -import PixelKit -import VPNPrivacyPro final class WaitlistThankYouPromptPresenter { @@ -53,23 +51,29 @@ final class WaitlistThankYouPromptPresenter { // If the user tested both, the PIR prompt will be displayed. @MainActor func presentThankYouPromptIfNecessary(in window: NSWindow) { + // Wiring this here since it's mostly useful for rolling out PrivacyPro, and should + // go away once PPro is fully rolled out. + if NSApp.delegateTyped.subscriptionFeatureAvailability.isFeatureAvailable { + DailyPixel.fire(pixel: .privacyProEnabled, frequency: .dailyOnly) + } + guard canShowPromptCheck() else { return } if isPIRBetaTester() { saveDidShowPromptCheck() + Pixel.fire(Pixel.Event.privacyProBetaUserThankYouDBP, limitTo: .initial) presentPIRThankYouPrompt(in: window) } else if isVPNBetaTester() { saveDidShowPromptCheck() + Pixel.fire(Pixel.Event.privacyProBetaUserThankYouVPN, limitTo: .initial) presentVPNThankYouPrompt(in: window) } } @MainActor func presentVPNThankYouPrompt(in window: NSWindow) { - PixelKit.fire(VPNPrivacyProPixel.vpnBetaThankYouShown) - let thankYouModalView = WaitlistBetaThankYouDialogViewController(copy: .vpn) let thankYouWindowController = thankYouModalView.wrappedInWindowController() if let thankYouWindow = thankYouWindowController.window { diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index c44f70bbd7..770a4d2e94 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -30,6 +30,7 @@ import PixelKit #if SUBSCRIPTION import Subscription +import VPNPrivacyPro #endif @objc(Application) @@ -376,6 +377,7 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { let isConnected = await self.tunnelController.isConnected if isConnected { await self.tunnelController.stop() + PixelKit.fire(VPNPrivacyProPixel.vpnAccessRevokedDialogShown, frequency: .dailyAndContinuous) DistributedNotificationCenter.default().post(.showExpiredEntitlementNotification) } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/VPNPrivacyPro/Pixels/VPNPrivacyProPixel.swift b/LocalPackages/NetworkProtectionMac/Sources/VPNPrivacyPro/Pixels/VPNPrivacyProPixel.swift index 0212687eb6..2bbbf3a464 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/VPNPrivacyPro/Pixels/VPNPrivacyProPixel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/VPNPrivacyPro/Pixels/VPNPrivacyProPixel.swift @@ -33,18 +33,12 @@ public enum VPNPrivacyProPixel: PixelKitEventV2 { /// case vpnBetaStoppedWhenPrivacyProEnabled - /// Fired only once when the thank you alert is shown. - /// - case vpnBetaThankYouShown - public var name: String { switch self { case .vpnAccessRevokedDialogShown: return "vpn_access_revoked_dialog_shown" case .vpnBetaStoppedWhenPrivacyProEnabled: return "vpn_beta_stopped_when_privacy_pro_enabled" - case .vpnBetaThankYouShown: - return "privacy_pro_promotion_dialog_shown" } } @@ -52,7 +46,7 @@ public enum VPNPrivacyProPixel: PixelKitEventV2 { nil } - public var parameters: [String : String]? { + public var parameters: [String: String]? { nil } } diff --git a/LocalPackages/NetworkProtectionMac/Tests/VPNPrivacyProTests/VPNPrivacyProPixelTests.swift b/LocalPackages/NetworkProtectionMac/Tests/VPNPrivacyProTests/VPNPrivacyProPixelTests.swift index 89cfb6ca75..edadd39f37 100644 --- a/LocalPackages/NetworkProtectionMac/Tests/VPNPrivacyProTests/VPNPrivacyProPixelTests.swift +++ b/LocalPackages/NetworkProtectionMac/Tests/VPNPrivacyProTests/VPNPrivacyProPixelTests.swift @@ -68,9 +68,5 @@ final class VPNPrivacyProPixelTests: XCTestCase { and: .expect(pixelName: "m_mac_vpn_beta_stopped_when_privacy_pro_enabled"), file: #filePath, line: #line) - fire(VPNPrivacyProPixel.vpnBetaThankYouShown, - and: .expect(pixelName: "m_mac_privacy_pro_promotion_dialog_shown"), - file: #filePath, - line: #line) } }