From 8eb81afee574c015df3294e6a8a45d904795761c Mon Sep 17 00:00:00 2001 From: rahulsainani Date: Wed, 13 Sep 2023 12:28:12 +0200 Subject: [PATCH] Bug 1850407 - Add nimbus flag for review checker product recommendations --- app/.experimenter.yaml | 3 ++ app/nimbus.fml.yaml | 4 +++ .../ReviewQualityCheckPreferences.kt | 16 +++++++---- ...ReviewQualityCheckPreferencesMiddleware.kt | 10 +++++-- .../store/ReviewQualityCheckAction.kt | 6 +++- .../shopping/store/ReviewQualityCheckState.kt | 5 ++-- .../shopping/store/ReviewQualityCheckStore.kt | 2 +- .../fenix/shopping/ui/ProductAnalysis.kt | 2 +- .../fenix/shopping/ui/ProductAnalysisError.kt | 2 +- .../ui/ReviewQualityCheckSettingsCard.kt | 18 ++++++------ .../store/ReviewQualityCheckStoreTest.kt | 28 +++++++++++++++++-- 11 files changed, 72 insertions(+), 24 deletions(-) diff --git a/app/.experimenter.yaml b/app/.experimenter.yaml index 3cb3f80e43..83c5e8a3f1 100644 --- a/app/.experimenter.yaml +++ b/app/.experimenter.yaml @@ -199,6 +199,9 @@ shopping-experience: enabled: type: boolean description: "if true, the shopping experience feature is shown to the user." + product-recommendations: + type: boolean + description: "if true, recommended products feature is enabled to be shown to the user based on their preference." splash-screen: description: "A feature that extends splash screen duration, allowing additional data fetching time for the app's initial run." hasExposure: true diff --git a/app/nimbus.fml.yaml b/app/nimbus.fml.yaml index 9f764f7c2a..2147af07f7 100644 --- a/app/nimbus.fml.yaml +++ b/app/nimbus.fml.yaml @@ -346,6 +346,10 @@ features: description: if true, the shopping experience feature is shown to the user. type: Boolean default: false + product-recommendations: + description: if true, recommended products feature is enabled to be shown to the user based on their preference. + type: Boolean + default: false defaults: - channel: developer value: diff --git a/app/src/main/java/org/mozilla/fenix/shopping/middleware/ReviewQualityCheckPreferences.kt b/app/src/main/java/org/mozilla/fenix/shopping/middleware/ReviewQualityCheckPreferences.kt index 863e750309..279f3293db 100644 --- a/app/src/main/java/org/mozilla/fenix/shopping/middleware/ReviewQualityCheckPreferences.kt +++ b/app/src/main/java/org/mozilla/fenix/shopping/middleware/ReviewQualityCheckPreferences.kt @@ -6,6 +6,7 @@ package org.mozilla.fenix.shopping.middleware import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.utils.Settings /** @@ -18,9 +19,10 @@ interface ReviewQualityCheckPreferences { suspend fun enabled(): Boolean /** - * Returns true if the user has enabled product recommendations. + * Returns true if the user has turned on product recommendations, false if turned off by the + * user, null if the product recommendations feature is disabled. */ - suspend fun productRecommendationsEnabled(): Boolean + suspend fun productRecommendationsEnabled(): Boolean? /** * Sets whether the user has opted in to the review quality check feature. @@ -28,7 +30,7 @@ interface ReviewQualityCheckPreferences { suspend fun setEnabled(isEnabled: Boolean) /** - * Sets whether the user has enabled product recommendations. + * Sets user preference to turn on/off product recommendations. */ suspend fun setProductRecommendationsEnabled(isEnabled: Boolean) @@ -52,8 +54,12 @@ class ReviewQualityCheckPreferencesImpl( settings.isReviewQualityCheckEnabled } - override suspend fun productRecommendationsEnabled(): Boolean = withContext(Dispatchers.IO) { - settings.isReviewQualityCheckProductRecommendationsEnabled + override suspend fun productRecommendationsEnabled(): Boolean? = withContext(Dispatchers.IO) { + if (FxNimbus.features.shoppingExperience.value().productRecommendations) { + settings.isReviewQualityCheckProductRecommendationsEnabled + } else { + null + } } override suspend fun setEnabled(isEnabled: Boolean) { diff --git a/app/src/main/java/org/mozilla/fenix/shopping/middleware/ReviewQualityCheckPreferencesMiddleware.kt b/app/src/main/java/org/mozilla/fenix/shopping/middleware/ReviewQualityCheckPreferencesMiddleware.kt index fd10f4be54..34946272ee 100644 --- a/app/src/main/java/org/mozilla/fenix/shopping/middleware/ReviewQualityCheckPreferencesMiddleware.kt +++ b/app/src/main/java/org/mozilla/fenix/shopping/middleware/ReviewQualityCheckPreferencesMiddleware.kt @@ -86,9 +86,13 @@ class ReviewQualityCheckPreferencesMiddleware( ReviewQualityCheckAction.ToggleProductRecommendation -> { scope.launch { - reviewQualityCheckPreferences.setProductRecommendationsEnabled( - !reviewQualityCheckPreferences.productRecommendationsEnabled(), - ) + val productRecommendationsEnabled = + reviewQualityCheckPreferences.productRecommendationsEnabled() + if (productRecommendationsEnabled != null) { + reviewQualityCheckPreferences.setProductRecommendationsEnabled( + !productRecommendationsEnabled, + ) + } } } } diff --git a/app/src/main/java/org/mozilla/fenix/shopping/store/ReviewQualityCheckAction.kt b/app/src/main/java/org/mozilla/fenix/shopping/store/ReviewQualityCheckAction.kt index 8cdb7e8a89..f7ccc449c0 100644 --- a/app/src/main/java/org/mozilla/fenix/shopping/store/ReviewQualityCheckAction.kt +++ b/app/src/main/java/org/mozilla/fenix/shopping/store/ReviewQualityCheckAction.kt @@ -54,10 +54,14 @@ sealed interface ReviewQualityCheckAction : Action { /** * Triggered as a result of a [PreferencesMiddlewareAction] to update the state. + * + * @property hasUserOptedIn True when user has opted in for shopping experience. + * @property isProductRecommendationsEnabled Reflects the user preference update to display + * recommended product. Null when product recommendations feature is disabled. */ data class UpdateUserPreferences( val hasUserOptedIn: Boolean, - val isProductRecommendationsEnabled: Boolean, + val isProductRecommendationsEnabled: Boolean?, ) : UpdateAction /** diff --git a/app/src/main/java/org/mozilla/fenix/shopping/store/ReviewQualityCheckState.kt b/app/src/main/java/org/mozilla/fenix/shopping/store/ReviewQualityCheckState.kt index a24e8f1b2c..0ced6adda2 100644 --- a/app/src/main/java/org/mozilla/fenix/shopping/store/ReviewQualityCheckState.kt +++ b/app/src/main/java/org/mozilla/fenix/shopping/store/ReviewQualityCheckState.kt @@ -29,11 +29,12 @@ sealed interface ReviewQualityCheckState : State { * * @property productReviewState The state of the product the user is browsing. * @property productRecommendationsPreference User preference whether to show product - * recommendations. True if product recommendations should be shown. + * recommendations. True if product recommendations should be shown. Null indicates that product + * recommendations are disabled. */ data class OptedIn( val productReviewState: ProductReviewState = ProductReviewState.Loading, - val productRecommendationsPreference: Boolean, + val productRecommendationsPreference: Boolean?, ) : ReviewQualityCheckState { /** diff --git a/app/src/main/java/org/mozilla/fenix/shopping/store/ReviewQualityCheckStore.kt b/app/src/main/java/org/mozilla/fenix/shopping/store/ReviewQualityCheckStore.kt index 491d901a4b..50e040cc51 100644 --- a/app/src/main/java/org/mozilla/fenix/shopping/store/ReviewQualityCheckStore.kt +++ b/app/src/main/java/org/mozilla/fenix/shopping/store/ReviewQualityCheckStore.kt @@ -58,7 +58,7 @@ private fun mapStateForUpdateAction( } ReviewQualityCheckAction.ToggleProductRecommendation -> { - if (state is ReviewQualityCheckState.OptedIn) { + if (state is ReviewQualityCheckState.OptedIn && state.productRecommendationsPreference != null) { state.copy(productRecommendationsPreference = !state.productRecommendationsPreference) } else { state diff --git a/app/src/main/java/org/mozilla/fenix/shopping/ui/ProductAnalysis.kt b/app/src/main/java/org/mozilla/fenix/shopping/ui/ProductAnalysis.kt index dc55d459d1..99039e0821 100644 --- a/app/src/main/java/org/mozilla/fenix/shopping/ui/ProductAnalysis.kt +++ b/app/src/main/java/org/mozilla/fenix/shopping/ui/ProductAnalysis.kt @@ -58,7 +58,7 @@ import org.mozilla.fenix.theme.FirefoxTheme @Composable @Suppress("LongParameterList") fun ProductAnalysis( - productRecommendationsEnabled: Boolean, + productRecommendationsEnabled: Boolean?, productAnalysis: AnalysisPresent, onOptOutClick: () -> Unit, onReanalyzeClick: () -> Unit, diff --git a/app/src/main/java/org/mozilla/fenix/shopping/ui/ProductAnalysisError.kt b/app/src/main/java/org/mozilla/fenix/shopping/ui/ProductAnalysisError.kt index bd600c6535..ff5c1d05f5 100644 --- a/app/src/main/java/org/mozilla/fenix/shopping/ui/ProductAnalysisError.kt +++ b/app/src/main/java/org/mozilla/fenix/shopping/ui/ProductAnalysisError.kt @@ -29,7 +29,7 @@ import org.mozilla.fenix.theme.FirefoxTheme */ @Composable fun ProductAnalysisError( - productRecommendationsEnabled: Boolean, + productRecommendationsEnabled: Boolean?, onReviewGradeLearnMoreClick: (String) -> Unit, onOptOutClick: () -> Unit, onProductRecommendationsEnabledStateChange: (Boolean) -> Unit, diff --git a/app/src/main/java/org/mozilla/fenix/shopping/ui/ReviewQualityCheckSettingsCard.kt b/app/src/main/java/org/mozilla/fenix/shopping/ui/ReviewQualityCheckSettingsCard.kt index 7cb2e52a27..3ae841fc03 100644 --- a/app/src/main/java/org/mozilla/fenix/shopping/ui/ReviewQualityCheckSettingsCard.kt +++ b/app/src/main/java/org/mozilla/fenix/shopping/ui/ReviewQualityCheckSettingsCard.kt @@ -33,7 +33,7 @@ import org.mozilla.fenix.theme.FirefoxTheme */ @Composable fun ReviewQualityCheckSettingsCard( - productRecommendationsEnabled: Boolean, + productRecommendationsEnabled: Boolean?, onProductRecommendationsEnabledStateChange: (Boolean) -> Unit, onTurnOffReviewQualityCheckClick: () -> Unit, modifier: Modifier = Modifier, @@ -53,7 +53,7 @@ fun ReviewQualityCheckSettingsCard( @Composable private fun SettingsContent( - productRecommendationsEnabled: Boolean, + productRecommendationsEnabled: Boolean?, onProductRecommendationsEnabledStateChange: (Boolean) -> Unit, onTurnOffReviewQualityCheckClick: () -> Unit, modifier: Modifier = Modifier, @@ -61,13 +61,15 @@ private fun SettingsContent( Column(modifier = modifier) { Spacer(modifier = Modifier.height(8.dp)) - SwitchWithLabel( - checked = productRecommendationsEnabled, - onCheckedChange = onProductRecommendationsEnabledStateChange, - label = stringResource(R.string.review_quality_check_settings_recommended_products), - ) + if (productRecommendationsEnabled != null) { + SwitchWithLabel( + checked = productRecommendationsEnabled, + onCheckedChange = onProductRecommendationsEnabledStateChange, + label = stringResource(R.string.review_quality_check_settings_recommended_products), + ) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(16.dp)) + } SecondaryButton( text = stringResource(R.string.review_quality_check_settings_turn_off), diff --git a/app/src/test/java/org/mozilla/fenix/shopping/store/ReviewQualityCheckStoreTest.kt b/app/src/test/java/org/mozilla/fenix/shopping/store/ReviewQualityCheckStoreTest.kt index 8053c8e1ed..4b621a994d 100644 --- a/app/src/test/java/org/mozilla/fenix/shopping/store/ReviewQualityCheckStoreTest.kt +++ b/app/src/test/java/org/mozilla/fenix/shopping/store/ReviewQualityCheckStoreTest.kt @@ -90,6 +90,30 @@ class ReviewQualityCheckStoreTest { assertEquals(expected, tested.state) } + @Test + fun `GIVEN the user has opted in the feature and product recommendations feature is disabled THEN state should reflect that`() = + runTest { + val tested = ReviewQualityCheckStore( + middleware = provideReviewQualityCheckMiddleware( + reviewQualityCheckPreferences = FakeReviewQualityCheckPreferences( + isEnabled = true, + isProductRecommendationsEnabled = null, + ), + ), + ) + tested.waitUntilIdle() + dispatcher.scheduler.advanceUntilIdle() + tested.waitUntilIdle() + + val expected = ReviewQualityCheckState.OptedIn(productRecommendationsPreference = null) + assertEquals(expected, tested.state) + + // Even if toggle action is dispatched, state is not changed + tested.dispatch(ReviewQualityCheckAction.ToggleProductRecommendation).joinBlocking() + tested.waitUntilIdle() + assertEquals(expected, tested.state) + } + @Test fun `GIVEN the user has opted in the feature and product recommendations are off WHEN the user turns on product recommendations THEN state should reflect that`() = runTest { @@ -213,12 +237,12 @@ class ReviewQualityCheckStoreTest { private class FakeReviewQualityCheckPreferences( private val isEnabled: Boolean = false, - private val isProductRecommendationsEnabled: Boolean = false, + private val isProductRecommendationsEnabled: Boolean? = false, private val updateCFRCallback: () -> Unit = { }, ) : ReviewQualityCheckPreferences { override suspend fun enabled(): Boolean = isEnabled - override suspend fun productRecommendationsEnabled(): Boolean = isProductRecommendationsEnabled + override suspend fun productRecommendationsEnabled(): Boolean? = isProductRecommendationsEnabled override suspend fun setEnabled(isEnabled: Boolean) { }