Skip to content

Commit

Permalink
Mitigate Onboarding pixels firing with empty atb (#3265)
Browse files Browse the repository at this point in the history
Task/Issue URL: https://app.asana.com/0/72649045549333/1208108104325249/f

**Description**:
This PR mitigates the issue that causes onboarding Pixels to fire with an empty ATB.
  • Loading branch information
alessandroboron authored Aug 23, 2024
1 parent 196f641 commit 75773ac
Show file tree
Hide file tree
Showing 23 changed files with 201 additions and 53 deletions.
8 changes: 6 additions & 2 deletions DuckDuckGo/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ import WebKit
private var didCrashDuringCrashHandlersSetUp: Bool

private let launchOptionsHandler = LaunchOptionsHandler()
private let onboardingPixelReporter = OnboardingPixelReporter()

override init() {
super.init()
Expand Down Expand Up @@ -322,6 +323,8 @@ import WebKit

presentInsufficientDiskSpaceAlert()
} else {
let daxDialogsFactory = ExperimentContextualDaxDialogsFactory(contextualOnboardingLogic: daxDialogs, contextualOnboardingPixelReporter: onboardingPixelReporter)
let contextualOnboardingPresenter = ContextualOnboardingPresenter(variantManager: variantManager, daxDialogsFactory: daxDialogsFactory)
let main = MainViewController(bookmarksDatabase: bookmarksDatabase,
bookmarksDatabaseCleaner: syncDataProviders.bookmarksAdapter.databaseCleaner,
historyManager: historyManager,
Expand All @@ -334,9 +337,9 @@ import WebKit
syncPausedStateManager: syncErrorHandler,
privacyProDataReporter: privacyProDataReporter,
variantManager: variantManager,
contextualOnboardingPresenter: ContextualOnboardingPresenter(variantManager: variantManager),
contextualOnboardingPresenter: contextualOnboardingPresenter,
contextualOnboardingLogic: daxDialogs,
contextualOnboardingPixelReporter: OnboardingPixelReporter())
contextualOnboardingPixelReporter: onboardingPixelReporter)

main.loadViewIfNeeded()
syncErrorHandler.alertPresenter = main
Expand Down Expand Up @@ -500,6 +503,7 @@ import WebKit
StatisticsLoader.shared.refreshAppRetentionAtb()
self.fireAppLaunchPixel()
self.reportAdAttribution()
self.onboardingPixelReporter.fireEnqueuedPixelsIfNeeded()
}

if appIsLaunching {
Expand Down
2 changes: 1 addition & 1 deletion DuckDuckGo/DaxOnboardingViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class DaxOnboardingViewController: UIViewController, Onboarding {

private let pixelReporting: OnboardingIntroImpressionReporting

init?(coder: NSCoder, pixelReporting: OnboardingIntroImpressionReporting = OnboardingPixelReporter()) {
init?(coder: NSCoder, pixelReporting: OnboardingIntroImpressionReporting) {
self.pixelReporting = pixelReporting
super.init(coder: coder)
}
Expand Down
4 changes: 2 additions & 2 deletions DuckDuckGo/MainViewController+Segues.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ extension MainViewController {
var controller: (Onboarding & UIViewController)?

if DefaultVariantManager().isSupported(feature: .newOnboardingIntro) {
controller = OnboardingIntroViewController()
controller = OnboardingIntroViewController(onboardingPixelReporter: contextualOnboardingPixelReporter)
} else {
let storyboard = UIStoryboard(name: "DaxOnboarding", bundle: nil)
controller = storyboard.instantiateInitialViewController(creator: { coder in
DaxOnboardingViewController(coder: coder)
DaxOnboardingViewController(coder: coder, pixelReporting: self.contextualOnboardingPixelReporter)
})
}

Expand Down
9 changes: 5 additions & 4 deletions DuckDuckGo/MainViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class MainViewController: UIViewController {
private let variantManager: VariantManager
private let tutorialSettings: TutorialSettings
private let contextualOnboardingLogic: ContextualOnboardingLogic
private let contextualOnboardingPixelReporter: OnboardingCustomInteractionPixelReporting
let contextualOnboardingPixelReporter: OnboardingPixelReporting
private let statisticsStore: StatisticsStore

@UserDefaultsWrapper(key: .syncDidShowSyncPausedByFeatureFlagAlert, defaultValue: false)
Expand Down Expand Up @@ -189,7 +189,7 @@ class MainViewController: UIViewController {
variantManager: VariantManager,
contextualOnboardingPresenter: ContextualOnboardingPresenting,
contextualOnboardingLogic: ContextualOnboardingLogic,
contextualOnboardingPixelReporter: OnboardingCustomInteractionPixelReporting,
contextualOnboardingPixelReporter: OnboardingPixelReporting,
tutorialSettings: TutorialSettings = DefaultTutorialSettings(),
statisticsStore: StatisticsStore = StatisticsUserDefaults()
) {
Expand All @@ -212,7 +212,8 @@ class MainViewController: UIViewController {
syncService: syncService,
privacyProDataReporter: privacyProDataReporter,
contextualOnboardingPresenter: contextualOnboardingPresenter,
contextualOnboardingLogic: contextualOnboardingLogic)
contextualOnboardingLogic: contextualOnboardingLogic,
onboardingPixelReporter: contextualOnboardingPixelReporter)
self.syncPausedStateManager = syncPausedStateManager
self.privacyProDataReporter = privacyProDataReporter
self.homeTabManager = NewTabPageManager()
Expand Down Expand Up @@ -778,7 +779,7 @@ class MainViewController: UIViewController {
fatalError("No tab model")
}

let newTabDaxDialogFactory = NewTabDaxDialogFactory(delegate: self, contextualOnboardingLogic: DaxDialogs.shared)
let newTabDaxDialogFactory = NewTabDaxDialogFactory(delegate: self, contextualOnboardingLogic: DaxDialogs.shared, onboardingPixelReporter: contextualOnboardingPixelReporter)
if homeTabManager.isNewTabPageSectionsEnabled {
let controller = NewTabPageViewController(tab: tabModel,
interactionModel: favoritesViewModel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,17 +194,17 @@ struct OnboardingFinalDialog: View {
// MARK: - Preview

#Preview("Try Search") {
OnboardingTrySearchDialog(viewModel: OnboardingSearchSuggestionsViewModel())
OnboardingTrySearchDialog(viewModel: OnboardingSearchSuggestionsViewModel(pixelReporter: OnboardingPixelReporter()))
.padding()
}

#Preview("Try Site Top") {
OnboardingTryVisitingSiteDialog(logoPosition: .top, viewModel: OnboardingSiteSuggestionsViewModel(title: UserText.DaxOnboardingExperiment.ContextualOnboarding.onboardingTryASiteTitle))
OnboardingTryVisitingSiteDialog(logoPosition: .top, viewModel: OnboardingSiteSuggestionsViewModel(title: UserText.DaxOnboardingExperiment.ContextualOnboarding.onboardingTryASiteTitle, pixelReporter: OnboardingPixelReporter()))
.padding()
}

#Preview("Try Site Left") {
OnboardingTryVisitingSiteDialog(logoPosition: .left, viewModel: OnboardingSiteSuggestionsViewModel(title: UserText.DaxOnboardingExperiment.ContextualOnboarding.onboardingTryASiteTitle))
OnboardingTryVisitingSiteDialog(logoPosition: .left, viewModel: OnboardingSiteSuggestionsViewModel(title: UserText.DaxOnboardingExperiment.ContextualOnboarding.onboardingTryASiteTitle, pixelReporter: OnboardingPixelReporter()))
.padding()
}

Expand All @@ -216,7 +216,7 @@ struct OnboardingFinalDialog: View {
}

#Preview("First Search Dialog") {
OnboardingFirstSearchDoneDialog(shouldFollowUp: true, viewModel: OnboardingSiteSuggestionsViewModel(title: UserText.DaxOnboardingExperiment.ContextualOnboarding.onboardingTryASiteTitle), gotItAction: {})
OnboardingFirstSearchDoneDialog(shouldFollowUp: true, viewModel: OnboardingSiteSuggestionsViewModel(title: UserText.DaxOnboardingExperiment.ContextualOnboarding.onboardingTryASiteTitle, pixelReporter: OnboardingPixelReporter()), gotItAction: {})
.padding()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ protocol NewTabDaxDialogProvider {
final class NewTabDaxDialogFactory: NewTabDaxDialogProvider {
private var delegate: OnboardingNavigationDelegate?
private let contextualOnboardingLogic: ContextualOnboardingLogic
private let onboardingPixelReporter: OnboardingScreenImpressionReporting
private let onboardingPixelReporter: OnboardingPixelReporting

init(
delegate: OnboardingNavigationDelegate?,
contextualOnboardingLogic: ContextualOnboardingLogic,
onboardingPixelReporter: OnboardingScreenImpressionReporting = OnboardingPixelReporter()
onboardingPixelReporter: OnboardingPixelReporting
) {
self.delegate = delegate
self.contextualOnboardingLogic = contextualOnboardingLogic
Expand All @@ -58,7 +58,7 @@ final class NewTabDaxDialogFactory: NewTabDaxDialogProvider {
}

private func createInitialDialog() -> some View {
let viewModel = OnboardingSearchSuggestionsViewModel(delegate: delegate)
let viewModel = OnboardingSearchSuggestionsViewModel(pixelReporter: onboardingPixelReporter, delegate: delegate)
return FadeInView {
OnboardingTrySearchDialog(viewModel: viewModel)
.onboardingDaxDialogStyle()
Expand All @@ -70,7 +70,7 @@ final class NewTabDaxDialogFactory: NewTabDaxDialogProvider {
}

private func createSubsequentDialog() -> some View {
let viewModel = OnboardingSiteSuggestionsViewModel(title: UserText.DaxOnboardingExperiment.ContextualOnboarding.onboardingTryASiteNTPTitle, delegate: delegate)
let viewModel = OnboardingSiteSuggestionsViewModel(title: UserText.DaxOnboardingExperiment.ContextualOnboarding.onboardingTryASiteNTPTitle, pixelReporter: onboardingPixelReporter, delegate: delegate)
return FadeInView {
OnboardingTryVisitingSiteDialog(logoPosition: .top, viewModel: viewModel)
.onboardingDaxDialogStyle()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ struct OnboardingSearchSuggestionsViewModel {
private let pixelReporter: OnboardingSearchSuggestionsPixelReporting

init(
pixelReporter: OnboardingSearchSuggestionsPixelReporting,
suggestedSearchesProvider: OnboardingSuggestionsItemsProviding = OnboardingSuggestedSearchesProvider(),
delegate: OnboardingNavigationDelegate? = nil,
pixelReporter: OnboardingSearchSuggestionsPixelReporting = OnboardingPixelReporter()
delegate: OnboardingNavigationDelegate? = nil
) {
self.suggestedSearchesProvider = suggestedSearchesProvider
self.delegate = delegate
Expand All @@ -56,9 +56,9 @@ struct OnboardingSiteSuggestionsViewModel {

init(
title: String,
pixelReporter: OnboardingSiteSuggestionsPixelReporting,
suggestedSitesProvider: OnboardingSuggestionsItemsProviding = OnboardingSuggestedSitesProvider(),
delegate: OnboardingNavigationDelegate? = nil,
pixelReporter: OnboardingSiteSuggestionsPixelReporting = OnboardingPixelReporter()
delegate: OnboardingNavigationDelegate? = nil
) {
self.title = title
self.suggestedSitesProvider = suggestedSitesProvider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ protocol ContextualDaxDialogsFactory {
final class ExperimentContextualDaxDialogsFactory: ContextualDaxDialogsFactory {
private let contextualOnboardingLogic: ContextualOnboardingLogic
private let contextualOnboardingSettings: ContextualOnboardingSettings
private let contextualOnboardingPixelReporter: OnboardingScreenImpressionReporting
private let contextualOnboardingPixelReporter: OnboardingPixelReporting

init(
contextualOnboardingLogic: ContextualOnboardingLogic,
contextualOnboardingSettings: ContextualOnboardingSettings = DefaultDaxDialogsSettings(),
contextualOnboardingPixelReporter: OnboardingScreenImpressionReporting = OnboardingPixelReporter()
contextualOnboardingPixelReporter: OnboardingPixelReporting
) {
self.contextualOnboardingSettings = contextualOnboardingSettings
self.contextualOnboardingLogic = contextualOnboardingLogic
Expand Down Expand Up @@ -103,8 +103,8 @@ final class ExperimentContextualDaxDialogsFactory: ContextualDaxDialogsFactory {
afterSearchPixelEvent: Pixel.Event,
onSizeUpdate: @escaping () -> Void
) -> some View {
let viewModel = OnboardingSiteSuggestionsViewModel(title: UserText.DaxOnboardingExperiment.ContextualOnboarding.onboardingTryASiteTitle, delegate: delegate)
let viewModel = OnboardingSiteSuggestionsViewModel(title: UserText.DaxOnboardingExperiment.ContextualOnboarding.onboardingTryASiteTitle, pixelReporter: contextualOnboardingPixelReporter, delegate: delegate)

// If should not show websites search after searching inform the delegate that the user dimissed the dialog, otherwise let the dialog handle it.
let gotItAction: () -> Void = if shouldFollowUpToWebsiteSearch {
{ [weak delegate, weak self] in
Expand All @@ -125,7 +125,7 @@ final class ExperimentContextualDaxDialogsFactory: ContextualDaxDialogsFactory {
}

private func tryVisitingSiteDialog(delegate: ContextualOnboardingDelegate) -> some View {
let viewModel = OnboardingSiteSuggestionsViewModel(title: UserText.DaxOnboardingExperiment.ContextualOnboarding.onboardingTryASiteTitle, delegate: delegate)
let viewModel = OnboardingSiteSuggestionsViewModel(title: UserText.DaxOnboardingExperiment.ContextualOnboarding.onboardingTryASiteTitle, pixelReporter: contextualOnboardingPixelReporter, delegate: delegate)
return OnboardingTryVisitingSiteDialog(logoPosition: .left, viewModel: viewModel)
.onFirstAppear { [weak self] in
self?.contextualOnboardingPixelReporter.trackScreenImpression(event: .onboardingContextualTryVisitSiteUnique)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ final class ContextualOnboardingPresenter: ContextualOnboardingPresenting {

init(
variantManager: VariantManager,
daxDialogsFactory: ContextualDaxDialogsFactory = ExperimentContextualDaxDialogsFactory(contextualOnboardingLogic: DaxDialogs.shared),
daxDialogsFactory: ContextualDaxDialogsFactory,
appSettings: AppSettings = AppUserDefaults()
) {
self.variantManager = variantManager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ final class OnboardingIntroViewController: UIHostingController<OnboardingView>,
weak var delegate: OnboardingDelegate?
private let viewModel: OnboardingIntroViewModel

init() {
viewModel = OnboardingIntroViewModel()
init(onboardingPixelReporter: OnboardingPixelReporting) {
viewModel = OnboardingIntroViewModel(pixelReporter: onboardingPixelReporter)
let rootView = OnboardingView(model: viewModel)
super.init(rootView: rootView)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ final class OnboardingIntroViewModel: ObservableObject {
private let pixelReporter: OnboardingIntroPixelReporting
private let urlOpener: URLOpener

init(pixelReporter: OnboardingIntroPixelReporting = OnboardingPixelReporter(), urlOpener: URLOpener = UIApplication.shared) {
init(pixelReporter: OnboardingIntroPixelReporting, urlOpener: URLOpener = UIApplication.shared) {
self.pixelReporter = pixelReporter
self.urlOpener = urlOpener
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,11 @@ private enum Metrics {
// MARK: - Preview

#Preview("Onboarding - Light") {
OnboardingView(model: .init())
OnboardingView(model: .init(pixelReporter: OnboardingPixelReporter()))
.preferredColorScheme(.light)
}

#Preview("Onboarding - Dark") {
OnboardingView(model: .init())
OnboardingView(model: .init(pixelReporter: OnboardingPixelReporter()))
.preferredColorScheme(.dark)
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ protocol OnboardingScreenImpressionReporting {
func trackScreenImpression(event: Pixel.Event)
}

typealias OnboardingPixelReporting = OnboardingIntroImpressionReporting & OnboardingIntroPixelReporting & OnboardingSearchSuggestionsPixelReporting & OnboardingSiteSuggestionsPixelReporting & OnboardingCustomInteractionPixelReporting & OnboardingScreenImpressionReporting

// MARK: - Implementation

final class OnboardingPixelReporter {
Expand All @@ -80,6 +82,8 @@ final class OnboardingPixelReporter {
private let userDefaults: UserDefaults
private let siteVisitedUserDefaultsKey = "com.duckduckgo.ios.site-visited"

private(set) var enqueuedPixels: [EnqueuedPixel] = []

init(
pixel: OnboardingPixelFiring.Type = Pixel.self,
uniquePixel: OnboardingPixelFiring.Type = UniquePixel.self,
Expand All @@ -97,6 +101,21 @@ final class OnboardingPixelReporter {
}

private func fire(event: Pixel.Event, unique: Bool, additionalParameters: [String: String] = [:], includedParameters: [Pixel.QueryParameters] = [.appVersion, .atb]) {

func enqueue(event: Pixel.Event, unique: Bool, additionalParameters: [String: String], includedParameters: [Pixel.QueryParameters]) {
enqueuedPixels.append(.init(event: event, unique: unique, additionalParameters: additionalParameters, includedParameters: includedParameters))
}

// If the Pixel needs the ATB and ATB is available, fire the Pixel immediately. Otherwise enqueue the pixel and process it once the ATB is available.
// If the Pixel does not need the ATB there's no need to wait for the ATB to become available.
if includedParameters.contains(.atb) && statisticsStore.atb == nil {
enqueue(event: event, unique: unique, additionalParameters: additionalParameters, includedParameters: includedParameters)
} else {
performFire(event: event, unique: unique, additionalParameters: additionalParameters, includedParameters: includedParameters)
}
}

private func performFire(event: Pixel.Event, unique: Bool, additionalParameters: [String: String], includedParameters: [Pixel.QueryParameters]) {
if unique {
uniquePixel.fire(pixel: event, withAdditionalParameters: additionalParameters, includedParameters: includedParameters)
} else {
Expand All @@ -106,6 +125,18 @@ final class OnboardingPixelReporter {

}

// MARK: - Fire Enqueued Pixels

extension OnboardingPixelReporter {

func fireEnqueuedPixelsIfNeeded() {
while !enqueuedPixels.isEmpty {
let event = enqueuedPixels.removeFirst()
performFire(event: event.event, unique: event.unique, additionalParameters: event.additionalParameters, includedParameters: event.includedParameters)
}
}
}

// MARK: - OnboardingPixelReporter + Intro

extension OnboardingPixelReporter: OnboardingIntroPixelReporting {
Expand Down Expand Up @@ -182,3 +213,10 @@ extension OnboardingPixelReporter: OnboardingScreenImpressionReporting {
}

}

struct EnqueuedPixel {
let event: Pixel.Event
let unique: Bool
let additionalParameters: [String: String]
let includedParameters: [Pixel.QueryParameters]
}
2 changes: 1 addition & 1 deletion DuckDuckGo/RootDebugViewController+Onboarding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import UIKit
extension RootDebugViewController {

func showOnboardingIntro() {
let controller = OnboardingIntroViewController()
let controller = OnboardingIntroViewController(onboardingPixelReporter: OnboardingPixelReporter())
controller.delegate = self
controller.modalPresentationStyle = .overFullScreen
present(controller: controller, fromView: self.view)
Expand Down
Loading

0 comments on commit 75773ac

Please sign in to comment.