Skip to content

Commit

Permalink
Experiment: Removing Browser Onboarding CTAs (#4642)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
nalcalag authored Jun 14, 2024
1 parent e9ab49c commit 9704bfd
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,6 @@ class BrowserTabViewModelTest {
subscriptions = subscriptions,
sslCertificatesFeature = mockSSLCertificatesFeature,
bypassedSSLCertificatesRepository = mockBypassedSSLCertificatesRepository,
extendedOnboardingFeatureToggles = mockExtendedOnboardingFeatureToggles,
userBrowserProperties = mockUserBrowserProperties,
history = mockNavigationHistory,
)
Expand Down Expand Up @@ -5456,6 +5455,7 @@ class BrowserTabViewModelTest {
}

private suspend fun givenFireButtonPulsing() {
whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockEnabledToggle)
whenever(mockUserStageStore.getUserAppStage()).thenReturn(AppStage.DAX_ONBOARDING)
dismissedCtaDaoChannel.send(listOf(DismissedCta(CtaId.DAX_DIALOG_TRACKERS_FOUND)))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ class CtaViewModelTest {
private lateinit var testee: CtaViewModel

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

@Before
fun before() {
Expand All @@ -138,8 +139,8 @@ class CtaViewModelTest {
.allowMainThreadQueries()
.build()

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

@Test
fun givenNoBrowserCtasExperimentWhenRefreshCtaOnHomeTabThenSkipOnboardingHomeCtas() = runTest {
givenDaxOnboardingActive()
whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockEnabledToggle)
whenever(mockDismissedCtaDao.exists(CtaId.DAX_INTRO)).thenReturn(false)
whenever(mockWidgetCapabilities.supportsAutomaticWidgetAdd).thenReturn(true)

val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = false)
assertFalse(value is DaxBubbleCta.DaxIntroSearchOptionsCta)
}

@Test
fun givenNoBrowserCtasExperimentWhenFirstRefreshCtaOnHomeTabThenDontReturnWidgetCta() = runTest {
givenDaxOnboardingActive()
whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockEnabledToggle)
whenever(mockDismissedCtaDao.exists(CtaId.DAX_INTRO)).thenReturn(false)

val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = false)
assertFalse(value is HomePanelCta.AddWidgetAuto)
}

@Test
fun givenNoBrowserCtasExperimentWhenRefreshCtaOnHomeTabAndIntroShownThenReturnWidgetCta() = runTest {
givenDaxOnboardingActive()
whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockEnabledToggle)
whenever(mockDismissedCtaDao.exists(CtaId.DAX_INTRO)).thenReturn(true)
whenever(mockWidgetCapabilities.supportsAutomaticWidgetAdd).thenReturn(true)

val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = false)
assertTrue(value is HomePanelCta.AddWidgetAuto)
}

@Test
fun givenNoBrowserCtasExperimentWhenRefreshCtaWhileBrowsingThenReturnNull() = runTest {
givenDaxOnboardingActive()
whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockEnabledToggle)
val site = site(url = "http://www.facebook.com", entity = TestEntity("Facebook", "Facebook", 9.0))

val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = true, site = site)
assertNull(value)
}

private suspend fun givenDaxOnboardingActive() {
whenever(mockUserStageStore.getUserAppStage()).thenReturn(AppStage.DAX_ONBOARDING)
}
Expand Down
42 changes: 17 additions & 25 deletions app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,6 @@ import com.duckduckgo.app.global.model.domainMatchesUrl
import com.duckduckgo.app.location.GeoLocationPermissions
import com.duckduckgo.app.location.data.LocationPermissionType
import com.duckduckgo.app.location.data.LocationPermissionsRepository
import com.duckduckgo.app.onboarding.ui.page.extendedonboarding.ExtendedOnboardingFeatureToggles
import com.duckduckgo.app.onboarding.ui.page.extendedonboarding.OnboardingExperimentPixel
import com.duckduckgo.app.pixels.AppPixelName
import com.duckduckgo.app.pixels.AppPixelName.AUTOCOMPLETE_BANNER_DISMISSED
Expand Down Expand Up @@ -264,7 +263,6 @@ class BrowserTabViewModel @Inject constructor(
private val privacyProtectionsToggleUsageListener: PrivacyProtectionsToggleUsageListener,
private val privacyProtectionsPopupExperimentExternalPixels: PrivacyProtectionsPopupExperimentExternalPixels,
private val faviconsFetchingPrompt: FaviconsFetchingPrompt,
private val extendedOnboardingFeatureToggles: ExtendedOnboardingFeatureToggles,
private val subscriptions: Subscriptions,
private val sslCertificatesFeature: SSLCertificatesFeature,
private val bypassedSSLCertificatesRepository: BypassedSSLCertificatesRepository,
Expand Down Expand Up @@ -3174,12 +3172,10 @@ class BrowserTabViewModel @Inject constructor(
return when (onboardingCta) {
is OnboardingDaxDialogCta.DaxSerpCta -> {
viewModelScope.launch {
if (extendedOnboardingFeatureToggles.aestheticUpdates().isEnabled()) {
val cta = withContext(dispatchers.io()) { ctaViewModel.getSiteSuggestionsDialogCta() }
ctaViewState.value = currentCtaViewState().copy(cta = cta)
if (cta == null) {
command.value = HideOnboardingDaxDialog(onboardingCta)
}
val cta = withContext(dispatchers.io()) { ctaViewModel.getSiteSuggestionsDialogCta() }
ctaViewState.value = currentCtaViewState().copy(cta = cta)
if (cta == null) {
command.value = HideOnboardingDaxDialog(onboardingCta)
}
}
null
Expand All @@ -3193,12 +3189,10 @@ class BrowserTabViewModel @Inject constructor(
browserViewState.value = currentBrowserViewState().copy(showPrivacyShield = HighlightableButton.Visible(highlighted = false))
}
viewModelScope.launch {
if (extendedOnboardingFeatureToggles.aestheticUpdates().isEnabled()) {
val cta = withContext(dispatchers.io()) { ctaViewModel.getFireDialogCta() }
ctaViewState.value = currentCtaViewState().copy(cta = cta)
if (cta == null) {
command.value = HideOnboardingDaxDialog(onboardingCta)
}
val cta = withContext(dispatchers.io()) { ctaViewModel.getFireDialogCta() }
ctaViewState.value = currentCtaViewState().copy(cta = cta)
if (cta == null) {
command.value = HideOnboardingDaxDialog(onboardingCta)
}
}
null
Expand All @@ -3218,22 +3212,20 @@ class BrowserTabViewModel @Inject constructor(
}

fun onFireMenuSelected() {
if (extendedOnboardingFeatureToggles.aestheticUpdates().isEnabled()) {
val cta = currentCtaViewState().cta
if (cta is OnboardingDaxDialogCta.DaxFireButtonCta) {
onUserDismissedCta()
command.value = HideOnboardingDaxDialog(cta)
}
if (currentBrowserViewState().fireButton.isHighlighted()) {
viewModelScope.launch {
ctaViewModel.dismissPulseAnimation()
}
val cta = currentCtaViewState().cta
if (cta is OnboardingDaxDialogCta.DaxFireButtonCta) {
onUserDismissedCta()
command.value = HideOnboardingDaxDialog(cta)
}
if (currentBrowserViewState().fireButton.isHighlighted()) {
viewModelScope.launch {
ctaViewModel.dismissPulseAnimation()
}
}
}

fun onPrivacyShieldSelected() {
if (extendedOnboardingFeatureToggles.aestheticUpdates().isEnabled() && currentBrowserViewState().showPrivacyShield.isHighlighted()) {
if (currentBrowserViewState().showPrivacyShield.isHighlighted()) {
browserViewState.value = currentBrowserViewState().copy(showPrivacyShield = HighlightableButton.Visible(highlighted = false))
pixel.fire(
pixel = PrivacyDashboardPixels.PRIVACY_DASHBOARD_FIRST_TIME_OPENED,
Expand Down
75 changes: 41 additions & 34 deletions app/src/main/java/com/duckduckgo/app/cta/ui/CtaViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -195,17 +195,20 @@ class CtaViewModel @Inject constructor(
}

private suspend fun getHomeCta(): Cta? {
val onboardingEnabled = extendedOnboardingFeatureToggles.aestheticUpdates().isEnabled()
return when {
canShowDaxIntroCta() && onboardingEnabled -> {
canShowDaxIntroCta() && extendedOnboardingFeatureToggles.noBrowserCtas().isEnabled() -> {
dismissedCtaDao.insert(DismissedCta(CtaId.DAX_INTRO))
null
}
canShowDaxIntroCta() && !extendedOnboardingFeatureToggles.noBrowserCtas().isEnabled() -> {
DaxBubbleCta.DaxIntroSearchOptionsCta(onboardingStore, appInstallStore)
}

canShowDaxIntroVisitSiteCta() && onboardingEnabled -> {
canShowDaxIntroVisitSiteCta() && !extendedOnboardingFeatureToggles.noBrowserCtas().isEnabled() -> {
DaxBubbleCta.DaxIntroVisitSiteOptionsCta(onboardingStore, appInstallStore)
}

canShowDaxCtaEndOfJourney() && onboardingEnabled -> {
canShowDaxCtaEndOfJourney() && !extendedOnboardingFeatureToggles.noBrowserCtas().isEnabled() -> {
DaxBubbleCta.DaxEndCta(onboardingStore, appInstallStore)
}

Expand Down Expand Up @@ -247,10 +250,15 @@ class CtaViewModel @Inject constructor(
(daxDialogNetworkShown() || daxDialogOtherShown() || daxDialogSerpShown() || daxDialogTrackersFoundShown())

private suspend fun canShowDaxDialogCta(): Boolean {
if (!daxOnboardingActive() || hideTips()) {
return false
return when {
!daxOnboardingActive() || hideTips() -> false
extendedOnboardingFeatureToggles.noBrowserCtas().isEnabled() -> {
settingsDataStore.hideTips = true
userStageStore.stageCompleted(AppStage.DAX_ONBOARDING)
false
}
else -> true
}
return true
}

@WorkerThread
Expand All @@ -269,40 +277,39 @@ class CtaViewModel @Inject constructor(

if (!canShowDaxDialogCta()) return null

if (extendedOnboardingFeatureToggles.aestheticUpdates().isEnabled()) {
// Trackers blocked
if (!daxDialogTrackersFoundShown() && !isSerpUrl(it.url) && it.orderedTrackerBlockedEntities().isNotEmpty()) {
return OnboardingDaxDialogCta.DaxTrackersBlockedCta(
onboardingStore,
appInstallStore,
it.orderedTrackerBlockedEntities(),
)
}
// Trackers blocked
if (!daxDialogTrackersFoundShown() && !isSerpUrl(it.url) && it.orderedTrackerBlockedEntities().isNotEmpty()) {
return OnboardingDaxDialogCta.DaxTrackersBlockedCta(
onboardingStore,
appInstallStore,
it.orderedTrackerBlockedEntities(),
)
}

// Is major network
if (it.entity != null) {
it.entity?.let { entity ->
if (!daxDialogNetworkShown() && OnboardingDaxDialogCta.mainTrackerNetworks.contains(entity.displayName)) {
return OnboardingDaxDialogCta.DaxMainNetworkCta(onboardingStore, appInstallStore, entity.displayName, host)
}
// Is major network
if (it.entity != null) {
it.entity?.let { entity ->
if (!daxDialogNetworkShown() && OnboardingDaxDialogCta.mainTrackerNetworks.contains(entity.displayName)) {
return OnboardingDaxDialogCta.DaxMainNetworkCta(onboardingStore, appInstallStore, entity.displayName, host)
}
}
}

// SERP
if (isSerpUrl(it.url) && !daxDialogSerpShown()) {
return OnboardingDaxDialogCta.DaxSerpCta(onboardingStore, appInstallStore)
}
// SERP
if (isSerpUrl(it.url) && !daxDialogSerpShown()) {
return OnboardingDaxDialogCta.DaxSerpCta(onboardingStore, appInstallStore)
}

// No trackers blocked
if (!isSerpUrl(it.url) && !daxDialogOtherShown() && !daxDialogTrackersFoundShown() && !daxDialogNetworkShown()) {
return OnboardingDaxDialogCta.DaxNoTrackersCta(onboardingStore, appInstallStore)
}
// No trackers blocked
if (!isSerpUrl(it.url) && !daxDialogOtherShown() && !daxDialogTrackersFoundShown() && !daxDialogNetworkShown()) {
return OnboardingDaxDialogCta.DaxNoTrackersCta(onboardingStore, appInstallStore)
}

// End
if (canShowDaxCtaEndOfJourney() && daxDialogFireEducationShown()) {
return OnboardingDaxDialogCta.DaxEndCta(onboardingStore, appInstallStore)
}
// End
if (canShowDaxCtaEndOfJourney() && daxDialogFireEducationShown()) {
return OnboardingDaxDialogCta.DaxEndCta(onboardingStore, appInstallStore)
}

return null
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.duckduckgo.app.onboarding.ui.page.extendedonboarding
import com.duckduckgo.anvil.annotations.ContributesRemoteFeature
import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.feature.toggles.api.Toggle
import com.duckduckgo.feature.toggles.api.Toggle.Experiment

@ContributesRemoteFeature(
scope = AppScope::class,
Expand All @@ -31,4 +32,8 @@ interface ExtendedOnboardingFeatureToggles {

@Toggle.DefaultValue(true)
fun aestheticUpdates(): Toggle

@Toggle.DefaultValue(false)
@Experiment
fun noBrowserCtas(): Toggle
}

0 comments on commit 9704bfd

Please sign in to comment.