Skip to content

Commit 9704bfd

Browse files
authored
Experiment: Removing Browser Onboarding CTAs (#4642)
Task/Issue URL: https://app.asana.com/0/1201807753394693/1207385522349489/f ### Description Retention Experiment where the experimental variant won't see onboarding Dax dialogs in the browser. - _mp_: Control variant - _mo_: Experimental variant ### Steps to test this PR **_Pre steps_** - [ ] Set `PRIVACY_REMOTE_CONFIG_URL = https://jsonblob.com/api/1250458078377009152` in `PrivacyFeatureName.kt` **_Control variant_** - [ ] Set the experimental variant weight to 0 and the control variant weight to 1 in the [JSON Blob link](https://jsonblob.com/1250458078377009152) ``` { "desc": "Control group for not showing onboarding CTAs experiment", "variantKey": "mp", "weight": 1 }, { "desc": "Dax dialogs removal during onboarding experimental group", "variantKey": "mo", "weight": 0 } ``` - [x] Delete `DuckDuckGo` directory from Downloads folder in your device to take part of the experiment - [x] Fresh install - [x] Go to browser - [x] Check Dax dialog (search suggestions) is shown in home page - [x] Perform a search - [x] Check SERP Dax dialog is shown on top of the site - [x] Go to `bbc.co.uk` - [x] Check TrackersBlocked Dax dialog is shown - [x] Dismiss dialog - [x] Open new tab - [x] Check End Dax dialog is shown in new tab page **_Experimental variant_** - [x] Set the experimental variant weight to 0 and the control variant weight to 1 in the [JSON Blob link](https://jsonblob.com/1250458078377009152) ``` { "desc": "Control group for not showing onboarding CTAs experiment", "variantKey": "mp", "weight": 0 }, { "desc": "Dax dialogs removal during onboarding experimental group", "variantKey": "mo", "weight": 1 } ``` - [x] Delete `DuckDuckGo` directory from Downloads folder in your device to take part of the experiment - [x] Fresh install - [x] Go to browser - [x] Check Dax dialog is **not** shown in home page. You will see Dax Icon instead - [x] Perform a search - [x] Check Dax dialog is **not** shown - [x] Go to `bbc.co.uk` - [x] Check Dax dialog is **not** shown - [x] Open new tab - [x] Check Dax dialog is **not** shown in home page. You will see Dax Icon instead ### No UI changes
1 parent e9ab49c commit 9704bfd

File tree

5 files changed

+109
-62
lines changed

5 files changed

+109
-62
lines changed

app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,6 @@ class BrowserTabViewModelTest {
621621
subscriptions = subscriptions,
622622
sslCertificatesFeature = mockSSLCertificatesFeature,
623623
bypassedSSLCertificatesRepository = mockBypassedSSLCertificatesRepository,
624-
extendedOnboardingFeatureToggles = mockExtendedOnboardingFeatureToggles,
625624
userBrowserProperties = mockUserBrowserProperties,
626625
history = mockNavigationHistory,
627626
)
@@ -5456,6 +5455,7 @@ class BrowserTabViewModelTest {
54565455
}
54575456

54585457
private suspend fun givenFireButtonPulsing() {
5458+
whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockEnabledToggle)
54595459
whenever(mockUserStageStore.getUserAppStage()).thenReturn(AppStage.DAX_ONBOARDING)
54605460
dismissedCtaDaoChannel.send(listOf(DismissedCta(CtaId.DAX_DIALOG_TRACKERS_FOUND)))
54615461
}

app/src/androidTest/java/com/duckduckgo/app/cta/ui/CtaViewModelTest.kt

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ class CtaViewModelTest {
130130
private lateinit var testee: CtaViewModel
131131

132132
val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
133+
val mockEnabledToggle: Toggle = mock { on { it.isEnabled() } doReturn true }
133134

134135
@Before
135136
fun before() {
@@ -138,8 +139,8 @@ class CtaViewModelTest {
138139
.allowMainThreadQueries()
139140
.build()
140141

141-
val mockToggle: Toggle = mock { on { it.isEnabled() } doReturn true }
142-
whenever(mockExtendedOnboardingFeatureToggles.aestheticUpdates()).thenReturn(mockToggle)
142+
val mockDisabledToggle: Toggle = mock { on { it.isEnabled() } doReturn false }
143+
whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockDisabledToggle)
143144
whenever(mockAppInstallStore.installTimestamp).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1))
144145
whenever(mockUserAllowListRepository.isDomainInUserAllowList(any())).thenReturn(false)
145146
whenever(mockDismissedCtaDao.dismissedCtas()).thenReturn(db.dismissedCtaDao().dismissedCtas())
@@ -718,6 +719,48 @@ class CtaViewModelTest {
718719
verify(mockDismissedCtaDao).insert(DismissedCta(CtaId.DAX_END))
719720
}
720721

722+
@Test
723+
fun givenNoBrowserCtasExperimentWhenRefreshCtaOnHomeTabThenSkipOnboardingHomeCtas() = runTest {
724+
givenDaxOnboardingActive()
725+
whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockEnabledToggle)
726+
whenever(mockDismissedCtaDao.exists(CtaId.DAX_INTRO)).thenReturn(false)
727+
whenever(mockWidgetCapabilities.supportsAutomaticWidgetAdd).thenReturn(true)
728+
729+
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = false)
730+
assertFalse(value is DaxBubbleCta.DaxIntroSearchOptionsCta)
731+
}
732+
733+
@Test
734+
fun givenNoBrowserCtasExperimentWhenFirstRefreshCtaOnHomeTabThenDontReturnWidgetCta() = runTest {
735+
givenDaxOnboardingActive()
736+
whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockEnabledToggle)
737+
whenever(mockDismissedCtaDao.exists(CtaId.DAX_INTRO)).thenReturn(false)
738+
739+
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = false)
740+
assertFalse(value is HomePanelCta.AddWidgetAuto)
741+
}
742+
743+
@Test
744+
fun givenNoBrowserCtasExperimentWhenRefreshCtaOnHomeTabAndIntroShownThenReturnWidgetCta() = runTest {
745+
givenDaxOnboardingActive()
746+
whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockEnabledToggle)
747+
whenever(mockDismissedCtaDao.exists(CtaId.DAX_INTRO)).thenReturn(true)
748+
whenever(mockWidgetCapabilities.supportsAutomaticWidgetAdd).thenReturn(true)
749+
750+
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = false)
751+
assertTrue(value is HomePanelCta.AddWidgetAuto)
752+
}
753+
754+
@Test
755+
fun givenNoBrowserCtasExperimentWhenRefreshCtaWhileBrowsingThenReturnNull() = runTest {
756+
givenDaxOnboardingActive()
757+
whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockEnabledToggle)
758+
val site = site(url = "http://www.facebook.com", entity = TestEntity("Facebook", "Facebook", 9.0))
759+
760+
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = true, site = site)
761+
assertNull(value)
762+
}
763+
721764
private suspend fun givenDaxOnboardingActive() {
722765
whenever(mockUserStageStore.getUserAppStage()).thenReturn(AppStage.DAX_ONBOARDING)
723766
}

app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,6 @@ import com.duckduckgo.app.global.model.domainMatchesUrl
125125
import com.duckduckgo.app.location.GeoLocationPermissions
126126
import com.duckduckgo.app.location.data.LocationPermissionType
127127
import com.duckduckgo.app.location.data.LocationPermissionsRepository
128-
import com.duckduckgo.app.onboarding.ui.page.extendedonboarding.ExtendedOnboardingFeatureToggles
129128
import com.duckduckgo.app.onboarding.ui.page.extendedonboarding.OnboardingExperimentPixel
130129
import com.duckduckgo.app.pixels.AppPixelName
131130
import com.duckduckgo.app.pixels.AppPixelName.AUTOCOMPLETE_BANNER_DISMISSED
@@ -264,7 +263,6 @@ class BrowserTabViewModel @Inject constructor(
264263
private val privacyProtectionsToggleUsageListener: PrivacyProtectionsToggleUsageListener,
265264
private val privacyProtectionsPopupExperimentExternalPixels: PrivacyProtectionsPopupExperimentExternalPixels,
266265
private val faviconsFetchingPrompt: FaviconsFetchingPrompt,
267-
private val extendedOnboardingFeatureToggles: ExtendedOnboardingFeatureToggles,
268266
private val subscriptions: Subscriptions,
269267
private val sslCertificatesFeature: SSLCertificatesFeature,
270268
private val bypassedSSLCertificatesRepository: BypassedSSLCertificatesRepository,
@@ -3174,12 +3172,10 @@ class BrowserTabViewModel @Inject constructor(
31743172
return when (onboardingCta) {
31753173
is OnboardingDaxDialogCta.DaxSerpCta -> {
31763174
viewModelScope.launch {
3177-
if (extendedOnboardingFeatureToggles.aestheticUpdates().isEnabled()) {
3178-
val cta = withContext(dispatchers.io()) { ctaViewModel.getSiteSuggestionsDialogCta() }
3179-
ctaViewState.value = currentCtaViewState().copy(cta = cta)
3180-
if (cta == null) {
3181-
command.value = HideOnboardingDaxDialog(onboardingCta)
3182-
}
3175+
val cta = withContext(dispatchers.io()) { ctaViewModel.getSiteSuggestionsDialogCta() }
3176+
ctaViewState.value = currentCtaViewState().copy(cta = cta)
3177+
if (cta == null) {
3178+
command.value = HideOnboardingDaxDialog(onboardingCta)
31833179
}
31843180
}
31853181
null
@@ -3193,12 +3189,10 @@ class BrowserTabViewModel @Inject constructor(
31933189
browserViewState.value = currentBrowserViewState().copy(showPrivacyShield = HighlightableButton.Visible(highlighted = false))
31943190
}
31953191
viewModelScope.launch {
3196-
if (extendedOnboardingFeatureToggles.aestheticUpdates().isEnabled()) {
3197-
val cta = withContext(dispatchers.io()) { ctaViewModel.getFireDialogCta() }
3198-
ctaViewState.value = currentCtaViewState().copy(cta = cta)
3199-
if (cta == null) {
3200-
command.value = HideOnboardingDaxDialog(onboardingCta)
3201-
}
3192+
val cta = withContext(dispatchers.io()) { ctaViewModel.getFireDialogCta() }
3193+
ctaViewState.value = currentCtaViewState().copy(cta = cta)
3194+
if (cta == null) {
3195+
command.value = HideOnboardingDaxDialog(onboardingCta)
32023196
}
32033197
}
32043198
null
@@ -3218,22 +3212,20 @@ class BrowserTabViewModel @Inject constructor(
32183212
}
32193213

32203214
fun onFireMenuSelected() {
3221-
if (extendedOnboardingFeatureToggles.aestheticUpdates().isEnabled()) {
3222-
val cta = currentCtaViewState().cta
3223-
if (cta is OnboardingDaxDialogCta.DaxFireButtonCta) {
3224-
onUserDismissedCta()
3225-
command.value = HideOnboardingDaxDialog(cta)
3226-
}
3227-
if (currentBrowserViewState().fireButton.isHighlighted()) {
3228-
viewModelScope.launch {
3229-
ctaViewModel.dismissPulseAnimation()
3230-
}
3215+
val cta = currentCtaViewState().cta
3216+
if (cta is OnboardingDaxDialogCta.DaxFireButtonCta) {
3217+
onUserDismissedCta()
3218+
command.value = HideOnboardingDaxDialog(cta)
3219+
}
3220+
if (currentBrowserViewState().fireButton.isHighlighted()) {
3221+
viewModelScope.launch {
3222+
ctaViewModel.dismissPulseAnimation()
32313223
}
32323224
}
32333225
}
32343226

32353227
fun onPrivacyShieldSelected() {
3236-
if (extendedOnboardingFeatureToggles.aestheticUpdates().isEnabled() && currentBrowserViewState().showPrivacyShield.isHighlighted()) {
3228+
if (currentBrowserViewState().showPrivacyShield.isHighlighted()) {
32373229
browserViewState.value = currentBrowserViewState().copy(showPrivacyShield = HighlightableButton.Visible(highlighted = false))
32383230
pixel.fire(
32393231
pixel = PrivacyDashboardPixels.PRIVACY_DASHBOARD_FIRST_TIME_OPENED,

app/src/main/java/com/duckduckgo/app/cta/ui/CtaViewModel.kt

Lines changed: 41 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -195,17 +195,20 @@ class CtaViewModel @Inject constructor(
195195
}
196196

197197
private suspend fun getHomeCta(): Cta? {
198-
val onboardingEnabled = extendedOnboardingFeatureToggles.aestheticUpdates().isEnabled()
199198
return when {
200-
canShowDaxIntroCta() && onboardingEnabled -> {
199+
canShowDaxIntroCta() && extendedOnboardingFeatureToggles.noBrowserCtas().isEnabled() -> {
200+
dismissedCtaDao.insert(DismissedCta(CtaId.DAX_INTRO))
201+
null
202+
}
203+
canShowDaxIntroCta() && !extendedOnboardingFeatureToggles.noBrowserCtas().isEnabled() -> {
201204
DaxBubbleCta.DaxIntroSearchOptionsCta(onboardingStore, appInstallStore)
202205
}
203206

204-
canShowDaxIntroVisitSiteCta() && onboardingEnabled -> {
207+
canShowDaxIntroVisitSiteCta() && !extendedOnboardingFeatureToggles.noBrowserCtas().isEnabled() -> {
205208
DaxBubbleCta.DaxIntroVisitSiteOptionsCta(onboardingStore, appInstallStore)
206209
}
207210

208-
canShowDaxCtaEndOfJourney() && onboardingEnabled -> {
211+
canShowDaxCtaEndOfJourney() && !extendedOnboardingFeatureToggles.noBrowserCtas().isEnabled() -> {
209212
DaxBubbleCta.DaxEndCta(onboardingStore, appInstallStore)
210213
}
211214

@@ -247,10 +250,15 @@ class CtaViewModel @Inject constructor(
247250
(daxDialogNetworkShown() || daxDialogOtherShown() || daxDialogSerpShown() || daxDialogTrackersFoundShown())
248251

249252
private suspend fun canShowDaxDialogCta(): Boolean {
250-
if (!daxOnboardingActive() || hideTips()) {
251-
return false
253+
return when {
254+
!daxOnboardingActive() || hideTips() -> false
255+
extendedOnboardingFeatureToggles.noBrowserCtas().isEnabled() -> {
256+
settingsDataStore.hideTips = true
257+
userStageStore.stageCompleted(AppStage.DAX_ONBOARDING)
258+
false
259+
}
260+
else -> true
252261
}
253-
return true
254262
}
255263

256264
@WorkerThread
@@ -269,40 +277,39 @@ class CtaViewModel @Inject constructor(
269277

270278
if (!canShowDaxDialogCta()) return null
271279

272-
if (extendedOnboardingFeatureToggles.aestheticUpdates().isEnabled()) {
273-
// Trackers blocked
274-
if (!daxDialogTrackersFoundShown() && !isSerpUrl(it.url) && it.orderedTrackerBlockedEntities().isNotEmpty()) {
275-
return OnboardingDaxDialogCta.DaxTrackersBlockedCta(
276-
onboardingStore,
277-
appInstallStore,
278-
it.orderedTrackerBlockedEntities(),
279-
)
280-
}
280+
// Trackers blocked
281+
if (!daxDialogTrackersFoundShown() && !isSerpUrl(it.url) && it.orderedTrackerBlockedEntities().isNotEmpty()) {
282+
return OnboardingDaxDialogCta.DaxTrackersBlockedCta(
283+
onboardingStore,
284+
appInstallStore,
285+
it.orderedTrackerBlockedEntities(),
286+
)
287+
}
281288

282-
// Is major network
283-
if (it.entity != null) {
284-
it.entity?.let { entity ->
285-
if (!daxDialogNetworkShown() && OnboardingDaxDialogCta.mainTrackerNetworks.contains(entity.displayName)) {
286-
return OnboardingDaxDialogCta.DaxMainNetworkCta(onboardingStore, appInstallStore, entity.displayName, host)
287-
}
289+
// Is major network
290+
if (it.entity != null) {
291+
it.entity?.let { entity ->
292+
if (!daxDialogNetworkShown() && OnboardingDaxDialogCta.mainTrackerNetworks.contains(entity.displayName)) {
293+
return OnboardingDaxDialogCta.DaxMainNetworkCta(onboardingStore, appInstallStore, entity.displayName, host)
288294
}
289295
}
296+
}
290297

291-
// SERP
292-
if (isSerpUrl(it.url) && !daxDialogSerpShown()) {
293-
return OnboardingDaxDialogCta.DaxSerpCta(onboardingStore, appInstallStore)
294-
}
298+
// SERP
299+
if (isSerpUrl(it.url) && !daxDialogSerpShown()) {
300+
return OnboardingDaxDialogCta.DaxSerpCta(onboardingStore, appInstallStore)
301+
}
295302

296-
// No trackers blocked
297-
if (!isSerpUrl(it.url) && !daxDialogOtherShown() && !daxDialogTrackersFoundShown() && !daxDialogNetworkShown()) {
298-
return OnboardingDaxDialogCta.DaxNoTrackersCta(onboardingStore, appInstallStore)
299-
}
303+
// No trackers blocked
304+
if (!isSerpUrl(it.url) && !daxDialogOtherShown() && !daxDialogTrackersFoundShown() && !daxDialogNetworkShown()) {
305+
return OnboardingDaxDialogCta.DaxNoTrackersCta(onboardingStore, appInstallStore)
306+
}
300307

301-
// End
302-
if (canShowDaxCtaEndOfJourney() && daxDialogFireEducationShown()) {
303-
return OnboardingDaxDialogCta.DaxEndCta(onboardingStore, appInstallStore)
304-
}
308+
// End
309+
if (canShowDaxCtaEndOfJourney() && daxDialogFireEducationShown()) {
310+
return OnboardingDaxDialogCta.DaxEndCta(onboardingStore, appInstallStore)
305311
}
312+
306313
return null
307314
}
308315
}

app/src/main/java/com/duckduckgo/app/onboarding/ui/page/extendedonboarding/ExtendedOnboardingFeatureToggles.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package com.duckduckgo.app.onboarding.ui.page.extendedonboarding
1919
import com.duckduckgo.anvil.annotations.ContributesRemoteFeature
2020
import com.duckduckgo.di.scopes.AppScope
2121
import com.duckduckgo.feature.toggles.api.Toggle
22+
import com.duckduckgo.feature.toggles.api.Toggle.Experiment
2223

2324
@ContributesRemoteFeature(
2425
scope = AppScope::class,
@@ -31,4 +32,8 @@ interface ExtendedOnboardingFeatureToggles {
3132

3233
@Toggle.DefaultValue(true)
3334
fun aestheticUpdates(): Toggle
35+
36+
@Toggle.DefaultValue(false)
37+
@Experiment
38+
fun noBrowserCtas(): Toggle
3439
}

0 commit comments

Comments
 (0)