Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show on App Launch: Add pixels #5040

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import androidx.lifecycle.viewModelScope
import com.duckduckgo.anvil.annotations.ContributesViewModel
import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption
import com.duckduckgo.app.generalsettings.showonapplaunch.store.ShowOnAppLaunchOptionDataStore
import com.duckduckgo.app.pixels.AppPixelName
import com.duckduckgo.app.pixels.AppPixelName.AUTOCOMPLETE_GENERAL_SETTINGS_TOGGLED_OFF
import com.duckduckgo.app.pixels.AppPixelName.AUTOCOMPLETE_GENERAL_SETTINGS_TOGGLED_ON
import com.duckduckgo.app.pixels.AppPixelName.AUTOCOMPLETE_RECENT_SITES_GENERAL_SETTINGS_TOGGLED_OFF
Expand Down Expand Up @@ -143,6 +144,7 @@ class GeneralSettingsViewModel @Inject constructor(

fun onShowOnAppLaunchButtonClick() {
sendCommand(Command.LaunchShowOnAppLaunchScreen)
pixel.fire(AppPixelName.SETTINGS_GENERAL_APP_LAUNCH_PRESSED)
}

private fun observeShowOnAppLaunchOption() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2024 DuckDuckGo
*
* 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.
*/

package com.duckduckgo.app.generalsettings.showonapplaunch

import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption
import com.duckduckgo.app.generalsettings.showonapplaunch.store.ShowOnAppLaunchOptionDataStore
import com.duckduckgo.app.statistics.api.BrowserFeatureStateReporterPlugin
import com.duckduckgo.app.statistics.pixels.Pixel.PixelParameter
import com.duckduckgo.common.utils.DispatcherProvider
import com.duckduckgo.di.scopes.AppScope
import com.squareup.anvil.annotations.ContributesBinding
import com.squareup.anvil.annotations.ContributesMultibinding
import javax.inject.Inject
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking

interface ShowOnAppLaunchReporterPlugin

@ContributesMultibinding(
scope = AppScope::class,
boundType = BrowserFeatureStateReporterPlugin::class,
)
@ContributesBinding(scope = AppScope::class, boundType = ShowOnAppLaunchReporterPlugin::class)
class ShowOnAppLaunchStateReporterPlugin
@Inject
constructor(
private val dispatcherProvider: DispatcherProvider,
private val showOnAppLaunchOptionDataStore: ShowOnAppLaunchOptionDataStore,
) : ShowOnAppLaunchReporterPlugin, BrowserFeatureStateReporterPlugin {

override fun featureStateParams(): Map<String, String> {
val option =
runBlocking(dispatcherProvider.io()) {
showOnAppLaunchOptionDataStore.optionFlow.first()
}
val dailyPixelValue = ShowOnAppLaunchOption.getDailyPixelValue(option)
return mapOf(PixelParameter.LAUNCH_SCREEN to dailyPixelValue)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import androidx.lifecycle.viewModelScope
import com.duckduckgo.anvil.annotations.ContributesViewModel
import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption
import com.duckduckgo.app.generalsettings.showonapplaunch.store.ShowOnAppLaunchOptionDataStore
import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.common.utils.DispatcherProvider
import com.duckduckgo.di.scopes.ActivityScope
import javax.inject.Inject
Expand All @@ -38,6 +39,7 @@ class ShowOnAppLaunchViewModel @Inject constructor(
private val dispatcherProvider: DispatcherProvider,
private val showOnAppLaunchOptionDataStore: ShowOnAppLaunchOptionDataStore,
private val urlConverter: UrlConverter,
private val pixel: Pixel,
) : ViewModel() {

data class ViewState(
Expand Down Expand Up @@ -65,6 +67,7 @@ class ShowOnAppLaunchViewModel @Inject constructor(
fun onShowOnAppLaunchOptionChanged(option: ShowOnAppLaunchOption) {
Timber.i("User changed show on app launch option to $option")
viewModelScope.launch(dispatcherProvider.io()) {
firePixel(option)
showOnAppLaunchOptionDataStore.setShowOnAppLaunchOption(option)
}
}
Expand All @@ -76,4 +79,9 @@ class ShowOnAppLaunchViewModel @Inject constructor(
showOnAppLaunchOptionDataStore.setSpecificPageUrl(convertedUrl)
}
}

private fun firePixel(option: ShowOnAppLaunchOption) {
val pixelName = ShowOnAppLaunchOption.getPixelName(option)
pixel.fire(pixelName)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

package com.duckduckgo.app.generalsettings.showonapplaunch.model

import com.duckduckgo.app.pixels.AppPixelName.SETTINGS_GENERAL_APP_LAUNCH_LAST_OPENED_TAB_SELECTED
import com.duckduckgo.app.pixels.AppPixelName.SETTINGS_GENERAL_APP_LAUNCH_NEW_TAB_PAGE_SELECTED
import com.duckduckgo.app.pixels.AppPixelName.SETTINGS_GENERAL_APP_LAUNCH_SPECIFIC_PAGE_SELECTED

sealed class ShowOnAppLaunchOption(val id: Int) {

data object LastOpenedTab : ShowOnAppLaunchOption(1)
Expand All @@ -30,5 +34,17 @@ sealed class ShowOnAppLaunchOption(val id: Int) {
3 -> SpecificPage("")
else -> throw IllegalArgumentException("Unknown id: $id")
}

fun getPixelName(option: ShowOnAppLaunchOption) = when (option) {
LastOpenedTab -> SETTINGS_GENERAL_APP_LAUNCH_LAST_OPENED_TAB_SELECTED
NewTabPage -> SETTINGS_GENERAL_APP_LAUNCH_NEW_TAB_PAGE_SELECTED
is SpecificPage -> SETTINGS_GENERAL_APP_LAUNCH_SPECIFIC_PAGE_SELECTED
}

fun getDailyPixelValue(option: ShowOnAppLaunchOption) = when (option) {
LastOpenedTab -> "last_opened_tab"
NewTabPage -> "new_tab_page"
is SpecificPage -> "specific_page"
}
}
}
4 changes: 4 additions & 0 deletions app/src/main/java/com/duckduckgo/app/pixels/AppPixelName.kt
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ enum class AppPixelName(override val pixelName: String) : Pixel.PixelName {
SETTINGS_PRIVATE_SEARCH_MORE_SEARCH_SETTINGS_PRESSED("ms_private_search_more_search_settings_pressed"),
SETTINGS_COOKIE_POPUP_PROTECTION_PRESSED("ms_cookie_popup_protection_setting_pressed"),
SETTINGS_FIRE_BUTTON_PRESSED("ms_fire_button_setting_pressed"),
SETTINGS_GENERAL_APP_LAUNCH_PRESSED("m_settings_general_app_launch_pressed"),
SETTINGS_GENERAL_APP_LAUNCH_LAST_OPENED_TAB_SELECTED("m_settings_general_app_launch_last_opened_tab_selected"),
SETTINGS_GENERAL_APP_LAUNCH_NEW_TAB_PAGE_SELECTED("m_settings_general_app_launch_new_tab_page_selected"),
SETTINGS_GENERAL_APP_LAUNCH_SPECIFIC_PAGE_SELECTED("m_settings_general_app_launch_specific_page_selected"),

SURVEY_CTA_SHOWN(pixelName = "mus_cs"),
SURVEY_CTA_DISMISSED(pixelName = "mus_cd"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchO
import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption.NewTabPage
import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption.SpecificPage
import com.duckduckgo.app.generalsettings.showonapplaunch.store.FakeShowOnAppLaunchOptionDataStore
import com.duckduckgo.app.pixels.AppPixelName.SETTINGS_GENERAL_APP_LAUNCH_PRESSED
import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.common.test.CoroutineTestRule
import com.duckduckgo.history.api.NavigationHistory
Expand Down Expand Up @@ -230,6 +231,13 @@ internal class GeneralSettingsViewModelTest {
}
}

@Test
fun whenShowOnAppLaunchClickedThenPixelFiredEmitted() = runTest {
testee.onShowOnAppLaunchButtonClick()

verify(mockPixel).fire(SETTINGS_GENERAL_APP_LAUNCH_PRESSED)
}

private fun defaultViewState() = GeneralSettingsViewModel.ViewState(
autoCompleteSuggestionsEnabled = true,
autoCompleteRecentlyVisitedSitesSuggestionsUserEnabled = true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright (c) 2024 DuckDuckGo
*
* 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.
*/

package com.duckduckgo.app.generalsettings.showonapplaunch

import androidx.test.ext.junit.runners.AndroidJUnit4
import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption
import com.duckduckgo.app.generalsettings.showonapplaunch.store.FakeShowOnAppLaunchOptionDataStore
import com.duckduckgo.app.generalsettings.showonapplaunch.store.ShowOnAppLaunchOptionDataStore
import com.duckduckgo.app.statistics.pixels.Pixel.PixelParameter
import com.duckduckgo.common.test.CoroutineTestRule
import com.duckduckgo.common.utils.DispatcherProvider
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class ShowOnAppLaunchReporterPluginTest {

@get:Rule val coroutineTestRule = CoroutineTestRule()

private val dispatcherProvider: DispatcherProvider = coroutineTestRule.testDispatcherProvider
private lateinit var testee: ShowOnAppLaunchStateReporterPlugin
private lateinit var fakeDataStore: ShowOnAppLaunchOptionDataStore

@Before
fun setup() {
fakeDataStore = FakeShowOnAppLaunchOptionDataStore(ShowOnAppLaunchOption.LastOpenedTab)

testee = ShowOnAppLaunchStateReporterPlugin(dispatcherProvider, fakeDataStore)
}

@Test
fun whenOptionIsSetToLastOpenedPageThenShouldReturnDailyPixelValue() = runTest {
fakeDataStore.setShowOnAppLaunchOption(ShowOnAppLaunchOption.LastOpenedTab)
val result = testee.featureStateParams()
assertEquals("last_opened_tab", result[PixelParameter.LAUNCH_SCREEN])
}

@Test
fun whenOptionIsSetToNewTabPageThenShouldReturnDailyPixelValue() = runTest {
fakeDataStore.setShowOnAppLaunchOption(ShowOnAppLaunchOption.NewTabPage)
val result = testee.featureStateParams()
assertEquals("new_tab_page", result[PixelParameter.LAUNCH_SCREEN])
}

@Test
fun whenOptionIsSetToSpecificPageThenShouldReturnDailyPixelValue() = runTest {
val specificPage = ShowOnAppLaunchOption.SpecificPage("example.com")
fakeDataStore.setShowOnAppLaunchOption(specificPage)
val result = testee.featureStateParams()
assertEquals("specific_page", result[PixelParameter.LAUNCH_SCREEN])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,14 @@ package com.duckduckgo.app.generalsettings.showonapplaunch
import app.cash.turbine.test
import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption.LastOpenedTab
import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption.NewTabPage
import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption.SpecificPage
import com.duckduckgo.app.generalsettings.showonapplaunch.store.FakeShowOnAppLaunchOptionDataStore
import com.duckduckgo.app.pixels.AppPixelName.SETTINGS_GENERAL_APP_LAUNCH_LAST_OPENED_TAB_SELECTED
import com.duckduckgo.app.pixels.AppPixelName.SETTINGS_GENERAL_APP_LAUNCH_NEW_TAB_PAGE_SELECTED
import com.duckduckgo.app.pixels.AppPixelName.SETTINGS_GENERAL_APP_LAUNCH_SPECIFIC_PAGE_SELECTED
import com.duckduckgo.common.test.CoroutineTestRule
import com.duckduckgo.common.utils.DispatcherProvider
import com.duckduckgo.fakes.FakePixel
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Before
Expand All @@ -35,12 +40,14 @@ class ShowOnAppLaunchViewModelTest {

private lateinit var testee: ShowOnAppLaunchViewModel
private lateinit var fakeDataStore: FakeShowOnAppLaunchOptionDataStore
private lateinit var fakePixel: FakePixel
private val dispatcherProvider: DispatcherProvider = coroutineTestRule.testDispatcherProvider

@Before
fun setup() {
fakeDataStore = FakeShowOnAppLaunchOptionDataStore(LastOpenedTab)
testee = ShowOnAppLaunchViewModel(dispatcherProvider, fakeDataStore, FakeUrlConverter())
fakePixel = FakePixel()
testee = ShowOnAppLaunchViewModel(dispatcherProvider, fakeDataStore, FakeUrlConverter(), fakePixel)
}

@Test
Expand Down Expand Up @@ -82,6 +89,28 @@ class ShowOnAppLaunchViewModelTest {
}
}

@Test
fun whenOptionChangedToLastOpenedPageThenLastOpenedPageIsFired() = runTest {
testee.onShowOnAppLaunchOptionChanged(NewTabPage)
testee.onShowOnAppLaunchOptionChanged(LastOpenedTab)
assertEquals(2, fakePixel.firedPixels.size)
assertEquals(SETTINGS_GENERAL_APP_LAUNCH_LAST_OPENED_TAB_SELECTED.pixelName, fakePixel.firedPixels.last())
}

@Test
fun whenOptionChangedToNewTabPageThenNewTabPagePixelIsFired() = runTest {
testee.onShowOnAppLaunchOptionChanged(NewTabPage)
assertEquals(1, fakePixel.firedPixels.size)
assertEquals(SETTINGS_GENERAL_APP_LAUNCH_NEW_TAB_PAGE_SELECTED.pixelName, fakePixel.firedPixels[0])
}

@Test
fun whenOptionChangedToSpecificPageThenSpecificPixelIsFired() = runTest {
testee.onShowOnAppLaunchOptionChanged(SpecificPage("https://example.com"))
assertEquals(1, fakePixel.firedPixels.size)
assertEquals(SETTINGS_GENERAL_APP_LAUNCH_SPECIFIC_PAGE_SELECTED.pixelName, fakePixel.firedPixels[0])
}

private class FakeUrlConverter : UrlConverter {

override fun convertUrl(url: String?): String {
Expand Down
60 changes: 60 additions & 0 deletions app/src/test/java/com/duckduckgo/fakes/FakePixel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright (c) 2024 DuckDuckGo
*
* 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.
*/

package com.duckduckgo.fakes

import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.app.statistics.pixels.Pixel.PixelName
import com.duckduckgo.app.statistics.pixels.Pixel.PixelType

internal class FakePixel : Pixel {

val firedPixels = mutableListOf<String>()

override fun fire(
pixel: PixelName,
parameters: Map<String, String>,
encodedParameters: Map<String, String>,
type: PixelType,
) {
firedPixels.add(pixel.pixelName)
}

override fun fire(
pixelName: String,
parameters: Map<String, String>,
encodedParameters: Map<String, String>,
type: PixelType,
) {
firedPixels.add(pixelName)
}

override fun enqueueFire(
pixel: PixelName,
parameters: Map<String, String>,
encodedParameters: Map<String, String>,
) {
firedPixels.add(pixel.pixelName)
}

override fun enqueueFire(
pixelName: String,
parameters: Map<String, String>,
encodedParameters: Map<String, String>,
) {
firedPixels.add(pixelName)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ interface Pixel {

// Loading Bar Experiment
const val LOADING_BAR_EXPERIMENT = "loading_bar_exp"
const val LAUNCH_SCREEN = "launch_screen"
}

object PixelValues {
Expand Down
Loading