Skip to content

Commit

Permalink
Onboarding Highlights - Experiment Setup (#3390)
Browse files Browse the repository at this point in the history
Task/Issue URL:
https://app.asana.com/0/1206329551987282/1208084960727003/f

**Description**:
Setup 3 branches experiment for Onboarding according to [Asana comment](https://app.asana.com/0/0/1208314441219794/1208151149115582/f).
  • Loading branch information
alessandroboron authored Sep 26, 2024
1 parent c49f188 commit 9bacb29
Show file tree
Hide file tree
Showing 21 changed files with 479 additions and 60 deletions.
10 changes: 10 additions & 0 deletions .maestro/shared/onboarding.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,13 @@ appId: com.duckduckgo.mobile.ios
# - assertVisible: "Make DuckDuckGo your default browser."
- tapOn:
text: "Skip"

- runFlow:
when:
visible: "Which color looks best on me?"
commands:
- assertVisible: "Next"
- tapOn: "Next"
- assertVisible: "Where should I put your address bar?"
- assertVisible: "Next"
- tapOn: "Next"
7 changes: 5 additions & 2 deletions Core/DefaultVariantManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ extension FeatureName {
// public static let experimentalFeature = FeatureName(rawValue: "experimentalFeature")

public static let newOnboardingIntro = FeatureName(rawValue: "newOnboardingIntro")
public static let newOnboardingIntroHighlights = FeatureName(rawValue: "newOnboardingIntroHighlights")
public static let contextualDaxDialogs = FeatureName(rawValue: "contextualDaxDialogs")
}

public struct VariantIOS: Variant {
Expand Down Expand Up @@ -56,8 +58,9 @@ public struct VariantIOS: Variant {
VariantIOS(name: "sd", weight: doNotAllocate, isIncluded: When.always, features: []),
VariantIOS(name: "se", weight: doNotAllocate, isIncluded: When.always, features: []),

VariantIOS(name: "ma", weight: 1, isIncluded: When.always, features: []),
VariantIOS(name: "mb", weight: 1, isIncluded: When.always, features: [.newOnboardingIntro]),
VariantIOS(name: "ms", weight: 1, isIncluded: When.always, features: [.newOnboardingIntro]),
VariantIOS(name: "mu", weight: 1, isIncluded: When.always, features: [.newOnboardingIntro, .contextualDaxDialogs]),
VariantIOS(name: "mx", weight: 1, isIncluded: When.always, features: [.newOnboardingIntroHighlights, .contextualDaxDialogs]),

returningUser
]
Expand Down
8 changes: 8 additions & 0 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,8 @@
98F6EA472863124100720957 /* ContentBlockerRulesLists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F6EA462863124100720957 /* ContentBlockerRulesLists.swift */; };
98F78B8E22419093007CACF4 /* ThemableNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F78B8D22419093007CACF4 /* ThemableNavigationController.swift */; };
9F16230B2CA0F0190093C4FC /* DebouncerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F16230A2CA0F0190093C4FC /* DebouncerTests.swift */; };
9F1061652C9C013F008DD5A0 /* DefaultVariantManager+Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1061642C9C013F008DD5A0 /* DefaultVariantManager+Onboarding.swift */; };
9F1623092C9D14F10093C4FC /* DefaultVariantManagerOnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1623082C9D14F10093C4FC /* DefaultVariantManagerOnboardingTests.swift */; };
9F23B8012C2BC94400950875 /* OnboardingBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F23B8002C2BC94400950875 /* OnboardingBackground.swift */; };
9F23B8032C2BCD0000950875 /* DaxDialogStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F23B8022C2BCD0000950875 /* DaxDialogStyles.swift */; };
9F23B8062C2BE22700950875 /* OnboardingIntroViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F23B8052C2BE22700950875 /* OnboardingIntroViewModelTests.swift */; };
Expand Down Expand Up @@ -2506,6 +2508,8 @@
98F6EA462863124100720957 /* ContentBlockerRulesLists.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentBlockerRulesLists.swift; sourceTree = "<group>"; };
98F78B8D22419093007CACF4 /* ThemableNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemableNavigationController.swift; sourceTree = "<group>"; };
9F16230A2CA0F0190093C4FC /* DebouncerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebouncerTests.swift; sourceTree = "<group>"; };
9F1061642C9C013F008DD5A0 /* DefaultVariantManager+Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DefaultVariantManager+Onboarding.swift"; sourceTree = "<group>"; };
9F1623082C9D14F10093C4FC /* DefaultVariantManagerOnboardingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultVariantManagerOnboardingTests.swift; sourceTree = "<group>"; };
9F23B8002C2BC94400950875 /* OnboardingBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingBackground.swift; sourceTree = "<group>"; };
9F23B8022C2BCD0000950875 /* DaxDialogStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaxDialogStyles.swift; sourceTree = "<group>"; };
9F23B8052C2BE22700950875 /* OnboardingIntroViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingIntroViewModelTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4777,6 +4781,7 @@
9F7CFF7C2C89B69A0012833E /* AppIconPickerViewModelTests.swift */,
9FDEC7B32C8FD62F00C7A692 /* OnboardingAddressBarPositionPickerViewModelTests.swift */,
9FDEC7B92C9006E000C7A692 /* BrowserComparisonModelTests.swift */,
9F1623082C9D14F10093C4FC /* DefaultVariantManagerOnboardingTests.swift */,
);
name = Onboarding;
sourceTree = "<group>";
Expand Down Expand Up @@ -4902,6 +4907,7 @@
9F23B7FF2C2BABE000950875 /* OnboardingIntro */,
9F5E5AAA2C3D0FAA00165F54 /* ContextualOnboarding */,
9FCFCD842C75C91A006EB7A0 /* ProgressBarView.swift */,
9F1061642C9C013F008DD5A0 /* DefaultVariantManager+Onboarding.swift */,
);
path = OnboardingExperiment;
sourceTree = "<group>";
Expand Down Expand Up @@ -7434,6 +7440,7 @@
6FBF0F8B2BD7C0A900136CF0 /* AllProtectedCell.swift in Sources */,
9F4CC5242C4A4F0D006A96EB /* SwiftUITestUtilities.swift in Sources */,
6FDC64032C92F4D600DB71B3 /* NewTabPageSettingsPersistentStore.swift in Sources */,
9F1061652C9C013F008DD5A0 /* DefaultVariantManager+Onboarding.swift in Sources */,
1E4F4A5A297193DE00625985 /* MainViewController+CookiesManaged.swift in Sources */,
C12324C32C4697C900FBB26B /* AutofillBreakageReportTableViewCell.swift in Sources */,
8586A10D24CBA7070049720E /* FindInPageActivity.swift in Sources */,
Expand Down Expand Up @@ -7954,6 +7961,7 @@
5694372B2BE3F2D900C0881B /* SyncErrorHandlerTests.swift in Sources */,
987130C7294AAB9F00AB05E0 /* MenuBookmarksViewModelTests.swift in Sources */,
858650D32469BFAD00C36F8A /* DaxDialogTests.swift in Sources */,
9F1623092C9D14F10093C4FC /* DefaultVariantManagerOnboardingTests.swift in Sources */,
31C138B227A4097800FFD4B2 /* DownloadTestsHelper.swift in Sources */,
1E1D8B5D2994FFE100C96994 /* AutoconsentMessageProtocolTests.swift in Sources */,
85C11E532090B23A00BFFEB4 /* UserDefaultsHomeRowReminderStorageTests.swift in Sources */,
Expand Down
3 changes: 2 additions & 1 deletion DuckDuckGo/DaxDialogs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ final class DaxDialogs: NewTabDialogSpecProvider, ContextualOnboardingLogic {
}

private var isNewOnboarding: Bool {
variantManager.isSupported(feature: .newOnboardingIntro)
variantManager.isContextualDaxDialogsEnabled
}

private var firstBrowsingMessageSeen: Bool {
Expand Down Expand Up @@ -279,6 +279,7 @@ final class DaxDialogs: NewTabDialogSpecProvider, ContextualOnboardingLogic {
var isEnabled: Bool {
// skip dax dialogs in integration tests
guard ProcessInfo.processInfo.environment["DAXDIALOGS"] != "false" else { return false }
guard variantManager.shouldShowDaxDialogs else { return false }
return !settings.isDismissed
}

Expand Down
8 changes: 5 additions & 3 deletions DuckDuckGo/HomeViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ class HomeViewController: UIViewController, NewTabPage {

func openedAsNewTab(allowingKeyboard: Bool) {
collectionView.openedAsNewTab(allowingKeyboard: allowingKeyboard)
if !variantManager.isSupported(feature: .newOnboardingIntro) {
if !variantManager.isContextualDaxDialogsEnabled {
// In the new onboarding this gets called twice (viewDidAppear in Tab) which then reset the spec to nil.
presentNextDaxDialog()
}
Expand Down Expand Up @@ -258,15 +258,17 @@ class HomeViewController: UIViewController, NewTabPage {
}

func presentNextDaxDialog() {
if variantManager.isSupported(feature: .newOnboardingIntro) {
guard variantManager.shouldShowDaxDialogs else { return }

if variantManager.isContextualDaxDialogsEnabled {
showNextDaxDialogNew(dialogProvider: newTabDialogTypeProvider, factory: newTabDialogFactory)
} else {
showNextDaxDialog(dialogProvider: newTabDialogTypeProvider)
}
}

func showNextDaxDialog() {
showNextDaxDialog(dialogProvider: newTabDialogTypeProvider)
presentNextDaxDialog()
}

func reloadFavorites() {
Expand Down
2 changes: 1 addition & 1 deletion DuckDuckGo/MainViewController+Segues.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ extension MainViewController {

var controller: (Onboarding & UIViewController)?

if DefaultVariantManager().isSupported(feature: .newOnboardingIntro) {
if DefaultVariantManager().isNewIntroFlow {
controller = OnboardingIntroViewController(onboardingPixelReporter: contextualOnboardingPixelReporter)
} else {
let storyboard = UIStoryboard(name: "DaxOnboarding", bundle: nil)
Expand Down
11 changes: 4 additions & 7 deletions DuckDuckGo/MainViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -852,7 +852,7 @@ class MainViewController: UIViewController {
hideNotificationBarIfBrokenSitePromptShown()
wakeLazyFireButtonAnimator()

if DefaultVariantManager().isSupported(feature: .newOnboardingIntro) {
if variantManager.isContextualDaxDialogsEnabled {
// Dismiss dax dialog and pulse animation when the user taps on the Fire Button.
currentTab?.dismissContextualDaxFireDialog()
ViewHighlighter.hideAll()
Expand Down Expand Up @@ -2418,10 +2418,6 @@ extension MainViewController: TabDelegate {
}
}

func tabDidRequestForgetAll(tab: TabViewController) {
forgetAllWithAnimation(showNextDaxDialog: true)
}

func tabDidRequestFireButtonPulse(tab: TabViewController) {
showFireButtonPulse()
}
Expand Down Expand Up @@ -2741,7 +2737,8 @@ extension MainViewController: AutoClearWorker {
self.privacyProDataReporter.saveFireCount()

// Ideally this should happen once data clearing has finished AND the animation is finished
if showNextDaxDialog {
// `showNextDaxDialog: true` only set from old onboarding FireDialog ActionSheet
if showNextDaxDialog && self.variantManager.shouldShowDaxDialogs {
self.homeController?.showNextDaxDialog()
} else if KeyboardSettings().onNewTab {
let showKeyboardAfterFireButton = DispatchWorkItem {
Expand All @@ -2751,7 +2748,7 @@ extension MainViewController: AutoClearWorker {
self.showKeyboardAfterFireButton = showKeyboardAfterFireButton
}

if self.variantManager.isSupported(feature: .newOnboardingIntro) {
if self.variantManager.isContextualDaxDialogsEnabled {
DaxDialogs.shared.clearedBrowserData()
}
}
Expand Down
6 changes: 4 additions & 2 deletions DuckDuckGo/NewTabPageViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,9 @@ final class NewTabPageViewController: UIHostingController<NewTabPageView<Favorit
}

func showNextDaxDialog() {
showNextDaxDialogNew(dialogProvider: newTabDialogTypeProvider, factory: newTabDialogFactory)
if variantManager.shouldShowDaxDialogs {
showNextDaxDialogNew(dialogProvider: newTabDialogTypeProvider, factory: newTabDialogFactory)
}
}

func onboardingCompleted() {
Expand All @@ -179,7 +181,7 @@ final class NewTabPageViewController: UIHostingController<NewTabPageView<Favorit
// MARK: - Onboarding

private func presentNextDaxDialog() {
if variantManager.isSupported(feature: .newOnboardingIntro) {
if variantManager.isContextualDaxDialogsEnabled {
showNextDaxDialogNew(dialogProvider: newTabDialogTypeProvider, factory: newTabDialogFactory)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,18 @@ final class ContextualOnboardingPresenter: ContextualOnboardingPresenting {
}

func presentContextualOnboarding(for spec: DaxDialogs.BrowsingSpec, in vc: TabViewOnboardingDelegate) {
if variantManager.isSupported(feature: .newOnboardingIntro) {
// Extra safety net
guard variantManager.shouldShowDaxDialogs else { return }

if variantManager.isContextualDaxDialogsEnabled {
presentExperimentContextualOnboarding(for: spec, in: vc)
} else {
presentControlContextualOnboarding(for: spec, in: vc)
}
}

func dismissContextualOnboardingIfNeeded(from vc: TabViewOnboardingDelegate) {
guard variantManager.isSupported(feature: .newOnboardingIntro), let daxContextualOnboarding = vc.daxContextualOnboardingController else { return }
guard variantManager.isContextualDaxDialogsEnabled, let daxContextualOnboarding = vc.daxContextualOnboardingController else { return }
remove(daxController: daxContextualOnboarding, fromParent: vc)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// DefaultVariantManager+Onboarding.swift
// DuckDuckGo
//
// 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 BrowserServicesKit

extension VariantManager {

var isNewIntroFlow: Bool {
isSupported(feature: .newOnboardingIntro) || isSupported(feature: .newOnboardingIntroHighlights)
}

var isOnboardingHighlightsExperiment: Bool {
isSupported(feature: .newOnboardingIntroHighlights)
}

var shouldShowDaxDialogs: Bool {
// Disable Dax Dialogs if only feature supported is .newOnboardingIntro
guard let features = currentVariant?.features else { return true }
return !(features.count == 1 && features.contains(.newOnboardingIntro))
}

var isContextualDaxDialogsEnabled: Bool {
isSupported(feature: .contextualDaxDialogs)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,20 @@ protocol OnboardingHighlightsDebugging: OnboardingHighlightsManaging {
final class OnboardingManager: OnboardingHighlightsManaging, OnboardingHighlightsDebugging {
private var appDefaults: AppDebugSettings
private let featureFlagger: FeatureFlagger
private let variantManager: VariantManager

init(
appDefaults: AppDebugSettings = AppDependencyProvider.shared.appSettings,
featureFlagger: FeatureFlagger = AppDependencyProvider.shared.featureFlagger
featureFlagger: FeatureFlagger = AppDependencyProvider.shared.featureFlagger,
variantManager: VariantManager = DefaultVariantManager()
) {
self.appDefaults = appDefaults
self.featureFlagger = featureFlagger
self.variantManager = variantManager
}

var isOnboardingHighlightsEnabled: Bool {
isLocalFlagEnabled && isFeatureFlagEnabled
variantManager.isOnboardingHighlightsExperiment || (isLocalFlagEnabled && isFeatureFlagEnabled)
}

var isLocalFlagEnabled: Bool {
Expand Down
2 changes: 0 additions & 2 deletions DuckDuckGo/TabDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ protocol TabDelegate: AnyObject {

func tabContentProcessDidTerminate(tab: TabViewController)

func tabDidRequestForgetAll(tab: TabViewController)

func tabDidRequestFireButtonPulse(tab: TabViewController)

func tabDidRequestPrivacyDashboardButtonPulse(tab: TabViewController, animated: Bool)
Expand Down
31 changes: 21 additions & 10 deletions DuckDuckGo/TabSwitcherViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,27 @@ class TabSwitcherViewController: UIViewController {
}

@IBAction func onFirePressed(sender: AnyObject) {

func presentForgetDataAlert() {
let alert = ForgetDataAlert.buildAlert(forgetTabsAndDataHandler: { [weak self] in
self?.forgetAll()
})

if let anchor = sender as? UIView {
self.present(controller: alert, fromView: anchor)
} else {
self.present(controller: alert, fromView: toolbar)
}
}

Pixel.fire(pixel: .forgetAllPressedTabSwitching)
let isNewOnboarding = DefaultVariantManager().isSupported(feature: .newOnboardingIntro)
let variantManager = DefaultVariantManager()
let isNewOnboarding = variantManager.isContextualDaxDialogsEnabled

guard variantManager.shouldShowDaxDialogs else {
presentForgetDataAlert()
return
}

if !isNewOnboarding
&& DaxDialogs.shared.shouldShowFireButtonPulse {
Expand All @@ -328,15 +347,7 @@ class TabSwitcherViewController: UIViewController {
if isNewOnboarding {
ViewHighlighter.hideAll()
}
let alert = ForgetDataAlert.buildAlert(forgetTabsAndDataHandler: { [weak self] in
self?.forgetAll()
})

if let anchor = sender as? UIView {
self.present(controller: alert, fromView: anchor)
} else {
self.present(controller: alert, fromView: toolbar)
}
presentForgetDataAlert()
}
}

Expand Down
2 changes: 1 addition & 1 deletion DuckDuckGo/TabViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1508,7 +1508,7 @@ extension TabViewController: WKNavigationDelegate {
return
}

if !DefaultVariantManager().isSupported(feature: .newOnboardingIntro) {
if !DefaultVariantManager().isContextualDaxDialogsEnabled {
isShowingFullScreenDaxDialog = true
}
scheduleTrackerNetworksAnimation(collapsing: !spec.highlightAddressBar)
Expand Down
11 changes: 9 additions & 2 deletions DuckDuckGo/TabsBarViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,14 @@ class TabsBarViewController: UIViewController {
self.present(controller: alert, fromView: fireButton)
}

if DefaultVariantManager().isSupported(feature: .newOnboardingIntro) {
let variantManager = DefaultVariantManager()

guard variantManager.shouldShowDaxDialogs else {
showClearDataAlert()
return
}

if variantManager.isContextualDaxDialogsEnabled {
delegate?.tabsBarDidRequestFireEducationDialog(self)
showClearDataAlert()
} else {
Expand Down Expand Up @@ -322,7 +329,7 @@ extension MainViewController: TabsBarDelegate {
}

func tabsBarDidRequestFireEducationDialog(_ controller: TabsBarViewController) {
if DefaultVariantManager().isSupported(feature: .newOnboardingIntro) {
if DefaultVariantManager().isContextualDaxDialogsEnabled {
currentTab?.dismissContextualDaxFireDialog()
ViewHighlighter.hideAll()
} else {
Expand Down
Loading

0 comments on commit 9bacb29

Please sign in to comment.