diff --git a/app/src/main/java/org/mozilla/fenix/nimbus/NimbusBranchesFragment.kt b/app/src/main/java/org/mozilla/fenix/nimbus/NimbusBranchesFragment.kt index 45a6d5d90..e98ffb85c 100644 --- a/app/src/main/java/org/mozilla/fenix/nimbus/NimbusBranchesFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/nimbus/NimbusBranchesFragment.kt @@ -10,6 +10,7 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -47,6 +48,8 @@ class NimbusBranchesFragment : Fragment() { } controller = NimbusBranchesController( + context = requireContext(), + navController = findNavController(), nimbusBranchesStore = nimbusBranchesStore, experiments = requireContext().components.analytics.experiments, experimentId = args.experimentId diff --git a/app/src/main/java/org/mozilla/fenix/nimbus/controller/NimbusBranchesController.kt b/app/src/main/java/org/mozilla/fenix/nimbus/controller/NimbusBranchesController.kt index 46ae9479f..fda8e2b82 100644 --- a/app/src/main/java/org/mozilla/fenix/nimbus/controller/NimbusBranchesController.kt +++ b/app/src/main/java/org/mozilla/fenix/nimbus/controller/NimbusBranchesController.kt @@ -4,15 +4,22 @@ package org.mozilla.fenix.nimbus.controller +import android.content.Context +import androidx.navigation.NavController import mozilla.components.service.nimbus.NimbusApi import mozilla.components.service.nimbus.ui.NimbusBranchesAdapterDelegate import org.mozilla.experiments.nimbus.Branch +import org.mozilla.fenix.R +import org.mozilla.fenix.components.FenixSnackbar +import org.mozilla.fenix.ext.getRootView +import org.mozilla.fenix.ext.settings import org.mozilla.fenix.nimbus.NimbusBranchesAction +import org.mozilla.fenix.nimbus.NimbusBranchesFragmentDirections import org.mozilla.fenix.nimbus.NimbusBranchesStore /** * [NimbusBranchesFragment] controller. This implements [NimbusBranchesAdapterDelegate] to handle - * interactions with a Nimbus branch item. + * interactions with a Nimbus branch. * * @param nimbusBranchesStore An instance of [NimbusBranchesStore] for dispatching * [NimbusBranchesAction]s. @@ -20,12 +27,41 @@ import org.mozilla.fenix.nimbus.NimbusBranchesStore * @param experimentId The string experiment-id or "slug" for a Nimbus experiment. */ class NimbusBranchesController( + private val context: Context, + private val navController: NavController, private val nimbusBranchesStore: NimbusBranchesStore, private val experiments: NimbusApi, private val experimentId: String ) : NimbusBranchesAdapterDelegate { override fun onBranchItemClicked(branch: Branch) { + val telemetryEnabled = context.settings().isTelemetryEnabled + val experimentsEnabled = context.settings().isExperimentationEnabled + + updateOptInState(branch) + + if (!telemetryEnabled && !experimentsEnabled) { + val snackbarText = context.getString(R.string.experiments_snackbar) + val buttonText = context.getString(R.string.experiments_snackbar_button) + context.getRootView()?.let { v -> + FenixSnackbar.make( + view = v, + FenixSnackbar.LENGTH_LONG, + isDisplayedWithBrowserToolbar = false + ) + .setText(snackbarText) + .setAction(buttonText) { + navController.navigate( + NimbusBranchesFragmentDirections + .actionNimbusBranchesFragmentToDataChoicesFragment() + ) + } + .show() + } + } + } + + private fun updateOptInState(branch: Branch) { nimbusBranchesStore.dispatch( if (experiments.getExperimentBranch(experimentId) != branch.slug) { experiments.optInWithBranch(experimentId, branch.slug) diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 7052780db..db415a858 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -1160,6 +1160,14 @@ + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 27c843137..f75ec348b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1763,4 +1763,9 @@ Part of the Firefox family. %s Learn more + + + Enable telemetry to send data. + + Go to settings diff --git a/app/src/test/java/org/mozilla/fenix/nimbus/NimbusBranchesControllerTest.kt b/app/src/test/java/org/mozilla/fenix/nimbus/NimbusBranchesControllerTest.kt index 6f436f4b3..233c4d3d1 100644 --- a/app/src/test/java/org/mozilla/fenix/nimbus/NimbusBranchesControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/nimbus/NimbusBranchesControllerTest.kt @@ -4,32 +4,196 @@ package org.mozilla.fenix.nimbus +import android.R +import android.app.Activity +import android.content.Context +import android.view.View +import android.view.ViewGroup +import androidx.navigation.NavController +import io.mockk.every import io.mockk.mockk +import io.mockk.mockkObject import io.mockk.verify +import io.mockk.verifyAll import mozilla.components.service.nimbus.NimbusApi import mozilla.components.support.test.libstate.ext.waitUntilIdle import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mozilla.experiments.nimbus.Branch +import org.mozilla.fenix.components.Components +import org.mozilla.fenix.components.FenixSnackbar +import org.mozilla.fenix.components.metrics.MetricController +import org.mozilla.fenix.ext.components +import org.mozilla.fenix.ext.getRootView +import org.mozilla.fenix.ext.settings +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.nimbus.controller.NimbusBranchesController +import org.mozilla.fenix.utils.Settings +@RunWith(FenixRobolectricTestRunner::class) class NimbusBranchesControllerTest { private val experiments: NimbusApi = mockk(relaxed = true) private val experimentId = "id" private lateinit var controller: NimbusBranchesController + private lateinit var navController: NavController private lateinit var nimbusBranchesStore: NimbusBranchesStore + private lateinit var settings: Settings + private lateinit var activity: Context + private lateinit var components: Components + private lateinit var metrics: MetricController + private lateinit var snackbar: FenixSnackbar + private lateinit var rootView: View @Before fun setup() { + components = mockk(relaxed = true) + settings = mockk(relaxed = true) + metrics = mockk(relaxed = true) + snackbar = mockk(relaxed = true) + navController = mockk(relaxed = true) + + rootView = mockk(relaxed = true) + activity = mockk(relaxed = true) { + every { findViewById(R.id.content) } returns rootView + every { getRootView() } returns rootView + } + + mockkObject(FenixSnackbar) + every { FenixSnackbar.make(any(), any(), any(), any()) } returns snackbar + + every { activity.settings() } returns settings + every { activity.components.analytics.metrics } returns metrics + + every { navController.currentDestination } returns mockk { + every { id } returns org.mozilla.fenix.R.id.nimbusBranchesFragment + } + nimbusBranchesStore = NimbusBranchesStore(NimbusBranchesState(emptyList())) - controller = NimbusBranchesController(nimbusBranchesStore, experiments, experimentId) + controller = NimbusBranchesController( + activity, + navController, + nimbusBranchesStore, + experiments, + experimentId + ) } @Test fun `WHEN branch item is clicked THEN branch is opted into and selectedBranch state is updated`() { + every { settings.isTelemetryEnabled } returns true + every { settings.isExperimentationEnabled } returns true + + val branch = Branch( + slug = "slug", + ratio = 1 + ) + + controller.onBranchItemClicked(branch) + + nimbusBranchesStore.waitUntilIdle() + + verify { + experiments.optInWithBranch(experimentId, branch.slug) + } + + assertEquals(branch.slug, nimbusBranchesStore.state.selectedBranch) + } + + @Test + fun `WHEN branch item is clicked THEN branch is opted out and selectedBranch state is updated`() { + every { settings.isTelemetryEnabled } returns true + every { settings.isExperimentationEnabled } returns true + every { experiments.getExperimentBranch(experimentId) } returns "slug" + + val branch = Branch( + slug = "slug", + ratio = 1 + ) + + controller.onBranchItemClicked(branch) + + nimbusBranchesStore.waitUntilIdle() + + verify { + experiments.optOut(experimentId) + } + } + + @Test + fun `WHEN studies and telemetry are ON and item is clicked THEN branch is opted in`() { + every { settings.isTelemetryEnabled } returns true + every { settings.isExperimentationEnabled } returns true + + val branch = Branch( + slug = "slug", + ratio = 1 + ) + + controller.onBranchItemClicked(branch) + + nimbusBranchesStore.waitUntilIdle() + + verify { + experiments.optInWithBranch(experimentId, branch.slug) + } + + assertEquals(branch.slug, nimbusBranchesStore.state.selectedBranch) + } + + @Test + fun `WHEN studies and telemetry are Off THEN branch is opted in AND data is not sent`() { + every { settings.isTelemetryEnabled } returns false + every { settings.isExperimentationEnabled } returns false + every { activity.getString(any()) } returns "hello" + + val branch = Branch( + slug = "slug", + ratio = 1 + ) + + controller.onBranchItemClicked(branch) + + nimbusBranchesStore.waitUntilIdle() + + verifyAll { + experiments.getExperimentBranch(experimentId) + experiments.optInWithBranch(experimentId, branch.slug) + snackbar.setText("hello") + } + + assertEquals(branch.slug, nimbusBranchesStore.state.selectedBranch) + } + + @Test + fun `WHEN studies are ON and telemetry Off THEN branch is opted in`() { + every { settings.isExperimentationEnabled } returns true + every { settings.isTelemetryEnabled } returns false + + val branch = Branch( + slug = "slug", + ratio = 1 + ) + + controller.onBranchItemClicked(branch) + + nimbusBranchesStore.waitUntilIdle() + + verify { + experiments.optInWithBranch(experimentId, branch.slug) + } + + assertEquals(branch.slug, nimbusBranchesStore.state.selectedBranch) + } + + @Test + fun `WHEN studies are OFF and telemetry ON THEN branch is opted in`() { + every { settings.isExperimentationEnabled } returns false + every { settings.isTelemetryEnabled } returns true + val branch = Branch( slug = "slug", ratio = 1