Bug 1843508 - Part 1: Add review quality check feature telemetry events

fenix/120.0
Alexandru2909 8 months ago committed by mergify[bot]
parent 65f42ae719
commit 1ec1a4fd11

@ -10401,3 +10401,406 @@ sync:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: never
shopping:
address_bar_icon_displayed:
type: event
description: |
The shopping icon was displayed in the address bar.
send_in_pings:
- events
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1843508
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/3619
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: 131
metadata:
tags:
- Shopping
address_bar_icon_clicked:
type: event
description: |
The shopping icon from the address bar was clicked.
send_in_pings:
- events
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1843508
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/3619
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: 131
metadata:
tags:
- Shopping
address_bar_feature_callout_displayed:
type: event
description: |
The shopping CFR was displayed to the user.
send_in_pings:
- events
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1843508
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/3619
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: 131
metadata:
tags:
- Shopping
surface_closed:
type: event
description: |
The shopping bottom sheet was closed.
send_in_pings:
- events
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1843508
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/3619
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: 131
metadata:
tags:
- Shopping
surface_displayed:
type: event
description: |
The shopping bottom sheet was displayed.
send_in_pings:
- events
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1843508
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/3619
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: 131
metadata:
tags:
- Shopping
surface_onboarding_displayed:
type: event
description: |
The shopping contextual onboarding card was displayed.
send_in_pings:
- events
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1843508
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/3619
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: 131
metadata:
tags:
- Shopping
surface_review_quality_explainer_url_clicked:
type: event
description: |
User interacted with the learn more link from the shopping explanation card.
send_in_pings:
- events
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1843508
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/3619
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: 131
metadata:
tags:
- Shopping
surface_show_terms_clicked:
type: event
description: |
User interacted with the terms and conditions link from the shopping contextual onboarding card.
send_in_pings:
- events
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1843508
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/3619
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: 131
metadata:
tags:
- Shopping
surface_show_privacy_policy_clicked:
type: event
description: |
User interacted with the privacy policy link from the shopping contextual onboarding card.
send_in_pings:
- events
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1843508
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/3619
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: 131
metadata:
tags:
- Shopping
surface_not_now_clicked:
type: event
description: |
User interacted with the opt out button from the shopping contextual onboarding card.
send_in_pings:
- events
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1843508
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/3619
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: 131
metadata:
tags:
- Shopping
surface_opt_in_accepted:
type: event
description: |
User interacted with the opt in button from the shopping contextual onboarding card.
send_in_pings:
- events
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1843508
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/3619
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: 131
metadata:
tags:
- Shopping
surface_show_more_recent_reviews_clicked:
type: event
description: |
User interacted with the expand "show more" button from the shopping highlights card.
send_in_pings:
- events
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1843508
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/3619
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: 131
metadata:
tags:
- Shopping
surface_learn_more_clicked:
type: event
description: |
The user clicked on learn more link from the onboarding card.
send_in_pings:
- events
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1843508
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/3619
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: 131
metadata:
tags:
- Shopping
surface_expand_settings:
type: event
description: |
Settings card from the bottom sheet was expanded.
send_in_pings:
- events
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1843508
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/3619
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: 131
metadata:
tags:
- Shopping
surface_no_review_reliability_available:
type: event
description: |
No analysis card was displayed to the user.
send_in_pings:
- events
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1843508
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/3619
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: 131
metadata:
tags:
- Shopping
surface_analyze_reviews_none_available_clicked:
type: event
description: |
User clicked on the launch analyzer link from the no analysis card.
send_in_pings:
- events
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1843508
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/3619
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: 131
metadata:
tags:
- Shopping
surface_reanalyze_clicked:
type: event
description: |
User interacted with the launch analyzer link from the "New info to check" warning card.
send_in_pings:
- events
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1843508
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/3619
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: 131
metadata:
tags:
- Shopping
surface_reactivated_button_clicked:
type: event
description: |
User interacted with the button to report a product is back in stock.
send_in_pings:
- events
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1843508
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/3619
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: 131
metadata:
tags:
- Shopping
shopping.settings:
component_opted_out:
type: boolean
description: |
Whether or not the user opted out of review quality check feature.
send_in_pings:
- metrics
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1843508
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/3619
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: 131
metadata:
tags:
- Shopping
nimbus_disabled_shopping:
type: boolean
description: |
Whether or not Nimbus has disabled the use of the shopping component.
send_in_pings:
- metrics
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1843508
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/3619
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: 131
metadata:
tags:
- Shopping
user_has_onboarded:
type: boolean
description: |
Whether or the user has completed the review quality check onboarding.
send_in_pings:
- metrics
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1843508
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/3619
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: 131
metadata:
tags:
- Shopping

@ -82,6 +82,7 @@ import org.mozilla.fenix.GleanMetrics.Metrics
import org.mozilla.fenix.GleanMetrics.PerfStartup
import org.mozilla.fenix.GleanMetrics.Preferences
import org.mozilla.fenix.GleanMetrics.SearchDefaultEngine
import org.mozilla.fenix.GleanMetrics.ShoppingSettings
import org.mozilla.fenix.GleanMetrics.TopSites
import org.mozilla.fenix.components.Components
import org.mozilla.fenix.components.Core
@ -861,6 +862,12 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
logger.error("Failed to fetch list of logins", e)
}
}
with(ShoppingSettings) {
componentOptedOut.set(!settings.isReviewQualityCheckEnabled)
nimbusDisabledShopping.set(!FxNimbus.features.shoppingExperience.value().enabled)
userHasOnboarded.set(settings.reviewQualityCheckOptInTimeInMillis != 0L)
}
}
@VisibleForTesting

@ -38,6 +38,7 @@ import mozilla.components.support.base.feature.UserInteractionHandler
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import org.mozilla.fenix.GleanMetrics.ReaderMode
import org.mozilla.fenix.GleanMetrics.Shopping
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar
@ -240,6 +241,7 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
findNavController().navigate(
BrowserFragmentDirections.actionBrowserFragmentToReviewQualityCheckDialogFragment(),
)
Shopping.addressBarIconClicked.record()
},
)
@ -253,6 +255,9 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
settings = requireContext().settings(),
),
onAvailabilityChange = {
if (!reviewQualityCheckAvailable && it) {
Shopping.addressBarIconDisplayed.record()
}
reviewQualityCheckAvailable = it
safeInvalidateBrowserToolbarView()
},

@ -36,6 +36,7 @@ import mozilla.components.compose.cfr.CFRPopup.PopupAlignment.INDICATOR_CENTERED
import mozilla.components.compose.cfr.CFRPopupProperties
import mozilla.components.lib.state.ext.flowScoped
import mozilla.components.service.glean.private.NoExtras
import org.mozilla.fenix.GleanMetrics.Shopping
import org.mozilla.fenix.GleanMetrics.TrackingProtection
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
@ -366,6 +367,7 @@ class BrowserToolbarCFRPresenter(
}
},
).run {
Shopping.addressBarFeatureCalloutDisplayed.record()
popup = this
show()
}

@ -21,6 +21,7 @@ import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import org.mozilla.fenix.components.appstate.AppAction
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.shopping.di.ReviewQualityCheckMiddlewareProvider
import org.mozilla.fenix.shopping.store.ReviewQualityCheckAction
import org.mozilla.fenix.shopping.store.ReviewQualityCheckStore
import org.mozilla.fenix.shopping.ui.ReviewQualityCheckBottomSheet
import org.mozilla.fenix.theme.FirefoxTheme
@ -51,6 +52,7 @@ class ReviewQualityCheckFragment : BottomSheetDialogFragment() {
findViewById<View?>(com.google.android.material.R.id.design_bottom_sheet)
bottomSheet?.setBackgroundResource(android.R.color.transparent)
behavior = BottomSheetBehavior.from(bottomSheet)
store.dispatch(ReviewQualityCheckAction.BottomSheetDisplayed)
}
}
@ -65,6 +67,7 @@ class ReviewQualityCheckFragment : BottomSheetDialogFragment() {
store = store,
onRequestDismiss = {
behavior?.state = BottomSheetBehavior.STATE_HIDDEN
store.dispatch(ReviewQualityCheckAction.BottomSheetClosed)
},
modifier = Modifier.nestedScroll(rememberNestedScrollInteropConnection()),
)

@ -15,6 +15,7 @@ import org.mozilla.fenix.shopping.middleware.DefaultReviewQualityCheckVendorsSer
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckNavigationMiddleware
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckNetworkMiddleware
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckPreferencesMiddleware
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckTelemetryMiddleware
import org.mozilla.fenix.shopping.store.ReviewQualityCheckMiddleware
import org.mozilla.fenix.utils.Settings
@ -41,6 +42,7 @@ object ReviewQualityCheckMiddlewareProvider {
providePreferencesMiddleware(settings, browserStore, scope),
provideNetworkMiddleware(browserStore, context, scope),
provideNavigationMiddleware(TabsUseCases.SelectOrAddUseCase(browserStore), context),
provideTelemetryMiddleware(),
)
private fun providePreferencesMiddleware(
@ -70,4 +72,6 @@ object ReviewQualityCheckMiddlewareProvider {
selectOrAddUseCase = selectOrAddUseCase,
context = context,
)
private fun provideTelemetryMiddleware() = ReviewQualityCheckTelemetryMiddleware()
}

@ -59,7 +59,7 @@ class ReviewQualityCheckNetworkMiddleware(
store.updateProductReviewState(productReviewState)
}
ReviewQualityCheckAction.ReanalyzeProduct -> {
ReviewQualityCheckAction.ReanalyzeProduct, ReviewQualityCheckAction.AnalyzeProduct -> {
val reanalysis = reviewQualityCheckService.reanalyzeProduct()
if (reanalysis == null) {

@ -0,0 +1,97 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.shopping.middleware
import mozilla.components.lib.state.MiddlewareContext
import org.mozilla.fenix.GleanMetrics.Shopping
import org.mozilla.fenix.GleanMetrics.ShoppingSettings
import org.mozilla.fenix.shopping.store.ReviewQualityCheckAction
import org.mozilla.fenix.shopping.store.ReviewQualityCheckMiddleware
import org.mozilla.fenix.shopping.store.ReviewQualityCheckState
/**
* Middleware that captures telemetry events for the review quality check feature.
*/
class ReviewQualityCheckTelemetryMiddleware : ReviewQualityCheckMiddleware {
override fun invoke(
context: MiddlewareContext<ReviewQualityCheckState, ReviewQualityCheckAction>,
next: (ReviewQualityCheckAction) -> Unit,
action: ReviewQualityCheckAction,
) {
next(action)
when (action) {
is ReviewQualityCheckAction.TelemetryAction -> processAction(action)
else -> {
// no-op
}
}
}
private fun processAction(
action: ReviewQualityCheckAction.TelemetryAction,
) = when (action) {
is ReviewQualityCheckAction.OptIn -> {
Shopping.surfaceOptInAccepted.record()
ShoppingSettings.userHasOnboarded.set(true)
}
is ReviewQualityCheckAction.OptOut -> {
ShoppingSettings.componentOptedOut.set(true)
}
is ReviewQualityCheckAction.BottomSheetClosed -> Shopping.surfaceClosed.record()
is ReviewQualityCheckAction.BottomSheetDisplayed -> Shopping.surfaceDisplayed.record()
is ReviewQualityCheckAction.OpenExplainerLearnMoreLink -> {
Shopping.surfaceReviewQualityExplainerUrlClicked.record()
}
is ReviewQualityCheckAction.OpenOnboardingLearnMoreLink -> {
Shopping.surfaceLearnMoreClicked.record()
}
is ReviewQualityCheckAction.OpenOnboardingPrivacyPolicyLink -> {
Shopping.surfaceShowPrivacyPolicyClicked.record()
}
is ReviewQualityCheckAction.OpenOnboardingTermsLink -> {
Shopping.surfaceShowTermsClicked.record()
}
is ReviewQualityCheckAction.NotNowClicked -> {
Shopping.surfaceNotNowClicked.record()
}
is ReviewQualityCheckAction.ShowMoreRecentReviewsClicked -> {
Shopping.surfaceShowMoreRecentReviewsClicked.record()
}
is ReviewQualityCheckAction.ExpandSettingsClicked -> {
Shopping.surfaceExpandSettings.record()
}
is ReviewQualityCheckAction.NoAnalysisDisplayed -> {
Shopping.surfaceNoReviewReliabilityAvailable.record()
}
is ReviewQualityCheckAction.AnalyzeProduct -> {
Shopping.surfaceAnalyzeReviewsNoneAvailableClicked.record()
}
is ReviewQualityCheckAction.ReanalyzeProduct -> {
Shopping.surfaceReanalyzeClicked.record()
}
is ReviewQualityCheckAction.ReportProductBackInStock -> {
Shopping.surfaceReactivatedButtonClicked.record()
}
is ReviewQualityCheckAction.OptOutCompleted -> {
Shopping.surfaceOnboardingDisplayed.record()
}
}
}

@ -32,6 +32,11 @@ sealed interface ReviewQualityCheckAction : Action {
*/
sealed interface NetworkAction : ReviewQualityCheckAction
/**
* Actions related to telemetry events.
*/
sealed interface TelemetryAction : ReviewQualityCheckAction
/**
* Triggered when the store is initialized.
*/
@ -40,12 +45,12 @@ sealed interface ReviewQualityCheckAction : Action {
/**
* Triggered when the user has opted in to the review quality check feature.
*/
object OptIn : PreferencesMiddlewareAction
object OptIn : PreferencesMiddlewareAction, TelemetryAction
/**
* Triggered when the user has opted out of the review quality check feature.
*/
object OptOut : PreferencesMiddlewareAction, UpdateAction
object OptOut : PreferencesMiddlewareAction, UpdateAction, TelemetryAction
/**
* Triggered when the user has enabled or disabled product recommendations.
@ -71,7 +76,7 @@ sealed interface ReviewQualityCheckAction : Action {
*/
data class OptOutCompleted(
val productVendors: List<ReviewQualityCheckState.ProductVendor>,
) : UpdateAction
) : UpdateAction, TelemetryAction
/**
* Triggered as a result of a [NetworkAction] to update the state.
@ -91,12 +96,17 @@ sealed interface ReviewQualityCheckAction : Action {
/**
* Triggered when the user triggers product re-analysis.
*/
object ReanalyzeProduct : NetworkAction, UpdateAction
object ReanalyzeProduct : NetworkAction, UpdateAction, TelemetryAction
/**
* Triggered when the user clicks on the analyze button
*/
object AnalyzeProduct : NetworkAction, UpdateAction, TelemetryAction
/**
* Triggered when the user clicks on learn more link on the explainer card.
*/
object OpenExplainerLearnMoreLink : NavigationMiddlewareAction
object OpenExplainerLearnMoreLink : NavigationMiddlewareAction, TelemetryAction
/**
* Triggered when the user clicks on the "Powered by" link in the footer.
@ -106,15 +116,50 @@ sealed interface ReviewQualityCheckAction : Action {
/**
* Triggered when the user clicks on learn more link on the opt in card.
*/
object OpenOnboardingLearnMoreLink : NavigationMiddlewareAction
object OpenOnboardingLearnMoreLink : NavigationMiddlewareAction, TelemetryAction
/**
* Triggered when the user clicks on terms and conditions link on the opt in card.
*/
object OpenOnboardingTermsLink : NavigationMiddlewareAction
object OpenOnboardingTermsLink : NavigationMiddlewareAction, TelemetryAction
/**
* Triggered when the user clicks on privacy policy link on the opt in card.
*/
object OpenOnboardingPrivacyPolicyLink : NavigationMiddlewareAction
object OpenOnboardingPrivacyPolicyLink : NavigationMiddlewareAction, TelemetryAction
/**
* Triggered when the bottom sheet is closed.
*/
object BottomSheetClosed : TelemetryAction
/**
* Triggered when the bottom sheet is opened.
*/
object BottomSheetDisplayed : TelemetryAction
/**
* Triggered when the user clicks on the "Not now" button from the contextual onboarding card.
*/
object NotNowClicked : TelemetryAction
/**
* Triggered when the user expands the recent reviews card.
*/
object ShowMoreRecentReviewsClicked : TelemetryAction
/**
* Triggered when the user expands the settings card.
*/
object ExpandSettingsClicked : TelemetryAction
/**
* Triggered when the No analysis card is displayed to the user.
*/
object NoAnalysisDisplayed : TelemetryAction
/**
* Triggered when the user reports a product is back in stock.
*/
object ReportProductBackInStock : TelemetryAction
}

@ -79,7 +79,7 @@ private fun mapStateForUpdateAction(
}
}
ReviewQualityCheckAction.ReanalyzeProduct -> {
ReviewQualityCheckAction.ReanalyzeProduct, ReviewQualityCheckAction.AnalyzeProduct -> {
state.mapIfOptedIn {
when (it.productReviewState) {
is ProductReviewState.AnalysisPresent -> {

@ -46,6 +46,7 @@ import org.mozilla.fenix.theme.FirefoxTheme
* @param onOptOutClick Invoked when the user opts out of the review quality check feature.
* @param onProductRecommendationsEnabledStateChange Invoked when the user changes the product
* recommendations toggle state.
* @param onExpandSettings Invoked when the user expands the settings card.
* @param modifier Modifier to be applied to the composable.
*/
@Suppress("LongParameterList")
@ -58,6 +59,7 @@ fun NoAnalysis(
onReviewGradeLearnMoreClick: () -> Unit,
onOptOutClick: () -> Unit,
onProductRecommendationsEnabledStateChange: (Boolean) -> Unit,
onExpandSettings: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
@ -75,6 +77,7 @@ fun NoAnalysis(
productRecommendationsEnabled = productRecommendationsEnabled,
onProductRecommendationsEnabledStateChange = onProductRecommendationsEnabledStateChange,
onTurnOffReviewQualityCheckClick = onOptOutClick,
onExpandSettings = onExpandSettings,
modifier = Modifier.fillMaxWidth(),
)
}
@ -183,6 +186,7 @@ private fun NoAnalysisPreview() {
onReviewGradeLearnMoreClick = {},
onOptOutClick = {},
onProductRecommendationsEnabledStateChange = {},
onExpandSettings = {},
modifier = Modifier.fillMaxWidth(),
)
}

@ -58,6 +58,8 @@ import java.util.SortedMap
* recommendations toggle state.
* @param onReviewGradeLearnMoreClick Invoked when the user clicks to learn more about review grades.
* @param onFooterLinkClick Invoked when the user clicks on the footer link.
* @param onShowMoreRecentReviewsClicked Invoked when the user clicks to show more recent reviews.
* @param onExpandSettings Invoked when the user expands the settings card.
* @param modifier The modifier to be applied to the Composable.
*/
@Composable
@ -71,6 +73,8 @@ fun ProductAnalysis(
onProductRecommendationsEnabledStateChange: (Boolean) -> Unit,
onReviewGradeLearnMoreClick: () -> Unit,
onFooterLinkClick: () -> Unit,
onShowMoreRecentReviewsClicked: () -> Unit,
onExpandSettings: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
@ -119,6 +123,7 @@ fun ProductAnalysis(
highlights = productAnalysis.highlights,
highlightsFadeVisible = productAnalysis.highlightsFadeVisible,
showMoreButtonVisible = productAnalysis.showMoreButtonVisible,
onShowMoreRecentReviewsClicked = onShowMoreRecentReviewsClicked,
modifier = Modifier.fillMaxWidth(),
)
}
@ -133,6 +138,7 @@ fun ProductAnalysis(
productRecommendationsEnabled = productRecommendationsEnabled,
onProductRecommendationsEnabledStateChange = onProductRecommendationsEnabledStateChange,
onTurnOffReviewQualityCheckClick = onOptOutClick,
onExpandSettings = onExpandSettings,
modifier = Modifier.fillMaxWidth(),
)
@ -218,11 +224,13 @@ private fun AdjustedProductRatingCard(
}
}
@Suppress("LongMethod")
@Composable
private fun HighlightsCard(
highlights: Map<HighlightType, List<String>>,
highlightsFadeVisible: Boolean,
showMoreButtonVisible: Boolean,
onShowMoreRecentReviewsClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
ReviewQualityCheckCard(modifier = modifier) {
@ -301,7 +309,12 @@ private fun HighlightsCard(
} else {
stringResource(R.string.review_quality_check_highlights_show_more)
},
onClick = { isExpanded = isExpanded.not() },
onClick = {
if (!isExpanded) {
onShowMoreRecentReviewsClicked()
}
isExpanded = isExpanded.not()
},
)
}
}
@ -484,6 +497,8 @@ private fun ProductAnalysisPreview(
},
onReviewGradeLearnMoreClick = {},
onFooterLinkClick = {},
onShowMoreRecentReviewsClicked = {},
onExpandSettings = {},
)
}
}

@ -32,6 +32,7 @@ import org.mozilla.fenix.theme.FirefoxTheme
* @param onProductRecommendationsEnabledStateChange Invoked when the user changes the product
* recommendations toggle state.
* @param onFooterLinkClick Invoked when the user clicks on the footer link.
* @param onExpandSettings Invoked when the user expands the settings card.
* @param modifier Modifier to apply to the layout.
*/
@Composable
@ -44,6 +45,7 @@ fun ProductAnalysisError(
onOptOutClick: () -> Unit,
onProductRecommendationsEnabledStateChange: (Boolean) -> Unit,
onFooterLinkClick: () -> Unit,
onExpandSettings: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
@ -96,6 +98,7 @@ fun ProductAnalysisError(
productRecommendationsEnabled = productRecommendationsEnabled,
onProductRecommendationsEnabledStateChange = onProductRecommendationsEnabledStateChange,
onTurnOffReviewQualityCheckClick = onOptOutClick,
onExpandSettings = onExpandSettings,
modifier = Modifier.fillMaxWidth(),
)
@ -123,6 +126,7 @@ private fun ProductAnalysisErrorPreview() {
onOptOutClick = {},
onProductRecommendationsEnabledStateChange = {},
onFooterLinkClick = {},
onExpandSettings = {},
modifier = Modifier.fillMaxWidth(),
)
}

@ -25,6 +25,7 @@ import org.mozilla.fenix.shopping.store.ReviewQualityCheckStore
* @param onRequestDismiss Invoked when a user action requests dismissal of the bottom sheet.
* @param modifier The modifier to be applied to the Composable.
*/
@Suppress("LongMethod")
@Composable
fun ReviewQualityCheckBottomSheet(
store: ReviewQualityCheckStore,
@ -58,7 +59,10 @@ fun ReviewQualityCheckBottomSheet(
onRequestDismiss()
store.dispatch(ReviewQualityCheckAction.OpenOnboardingTermsLink)
},
onSecondaryButtonClick = onRequestDismiss,
onSecondaryButtonClick = {
onRequestDismiss()
store.dispatch(ReviewQualityCheckAction.NotNowClicked)
},
)
}
@ -69,6 +73,9 @@ fun ReviewQualityCheckBottomSheet(
onRequestDismiss()
store.dispatch(ReviewQualityCheckAction.OptOut)
},
onAnalyzeClick = {
store.dispatch(ReviewQualityCheckAction.AnalyzeProduct)
},
onReanalyzeClick = {
store.dispatch(ReviewQualityCheckAction.ReanalyzeProduct)
},
@ -83,6 +90,15 @@ fun ReviewQualityCheckBottomSheet(
onRequestDismiss()
store.dispatch(ReviewQualityCheckAction.OpenPoweredByLink)
},
onExpandSettings = {
store.dispatch(ReviewQualityCheckAction.ExpandSettingsClicked)
},
onNoAnalysisPresent = {
store.dispatch(ReviewQualityCheckAction.NoAnalysisDisplayed)
},
onShowMoreRecentReviewsClicked = {
store.dispatch(ReviewQualityCheckAction.ShowMoreRecentReviewsClicked)
},
)
}
@ -102,8 +118,12 @@ fun ReviewQualityCheckBottomSheet(
private fun ProductReview(
state: ReviewQualityCheckState.OptedIn,
onOptOutClick: () -> Unit,
onAnalyzeClick: () -> Unit,
onReanalyzeClick: () -> Unit,
onProductRecommendationsEnabledStateChange: (Boolean) -> Unit,
onShowMoreRecentReviewsClicked: () -> Unit,
onNoAnalysisPresent: () -> Unit,
onExpandSettings: () -> Unit,
onReviewGradeLearnMoreClick: () -> Unit,
onFooterLinkClick: () -> Unit,
) {
@ -120,6 +140,8 @@ private fun ProductReview(
onOptOutClick = onOptOutClick,
onReanalyzeClick = onReanalyzeClick,
onProductRecommendationsEnabledStateChange = onProductRecommendationsEnabledStateChange,
onShowMoreRecentReviewsClicked = onShowMoreRecentReviewsClicked,
onExpandSettings = onExpandSettings,
onReviewGradeLearnMoreClick = onReviewGradeLearnMoreClick,
onFooterLinkClick = onFooterLinkClick,
)
@ -134,6 +156,7 @@ private fun ProductReview(
onOptOutClick = onOptOutClick,
onProductRecommendationsEnabledStateChange = onProductRecommendationsEnabledStateChange,
onFooterLinkClick = onFooterLinkClick,
onExpandSettings = onExpandSettings,
modifier = Modifier.fillMaxWidth(),
)
}
@ -143,14 +166,19 @@ private fun ProductReview(
}
is ReviewQualityCheckState.OptedIn.ProductReviewState.NoAnalysisPresent -> {
LaunchedEffect(Unit) {
onNoAnalysisPresent()
}
NoAnalysis(
isAnalyzing = productReviewState.isReanalyzing,
productRecommendationsEnabled = state.productRecommendationsPreference,
productVendor = state.productVendor,
onAnalyzeClick = onReanalyzeClick,
onAnalyzeClick = onAnalyzeClick,
onReviewGradeLearnMoreClick = onReviewGradeLearnMoreClick,
onOptOutClick = onOptOutClick,
onProductRecommendationsEnabledStateChange = onProductRecommendationsEnabledStateChange,
onExpandSettings = onExpandSettings,
modifier = Modifier.fillMaxWidth(),
)
}

@ -44,12 +44,14 @@ private val defaultCardContentPadding = 16.dp
*
* @param title The title of the card.
* @param modifier Modifier to be applied to the card.
* @param onExpandToggleClick Callback invoked when card is collapsed or expanded.
* @param content The content of the card.
*/
@Composable
fun ReviewQualityCheckExpandableCard(
title: String,
modifier: Modifier = Modifier,
onExpandToggleClick: (isExpanded: Boolean) -> Unit = {},
content: @Composable () -> Unit,
) {
ReviewQualityCheckCard(
@ -64,6 +66,7 @@ fun ReviewQualityCheckExpandableCard(
.fillMaxWidth()
.clickable {
isExpanded = isExpanded.not()
onExpandToggleClick(isExpanded)
}
.padding(defaultCardContentPadding),
verticalAlignment = Alignment.CenterVertically,

@ -29,6 +29,7 @@ import org.mozilla.fenix.theme.FirefoxTheme
* @param onProductRecommendationsEnabledStateChange Invoked when the user changes the product
* recommendations toggle state.
* @param onTurnOffReviewQualityCheckClick Invoked when the user opts out of the review quality check feature.
* @param onExpandSettings Invoked when the user expands the settings card.
* @param modifier Modifier to apply to the layout.
*/
@Composable
@ -36,11 +37,17 @@ fun ReviewQualityCheckSettingsCard(
productRecommendationsEnabled: Boolean?,
onProductRecommendationsEnabledStateChange: (Boolean) -> Unit,
onTurnOffReviewQualityCheckClick: () -> Unit,
onExpandSettings: () -> Unit,
modifier: Modifier = Modifier,
) {
ReviewQualityCheckExpandableCard(
modifier = modifier,
title = stringResource(R.string.review_quality_check_settings_title),
onExpandToggleClick = { isExpanded ->
if (isExpanded) {
onExpandSettings()
}
},
) {
SettingsContent(
productRecommendationsEnabled = productRecommendationsEnabled,
@ -92,6 +99,7 @@ private fun ReviewQualityCheckSettingsCardPreview() {
productRecommendationsEnabled = true,
onProductRecommendationsEnabledStateChange = {},
onTurnOffReviewQualityCheckClick = {},
onExpandSettings = {},
modifier = Modifier.fillMaxWidth(),
)
}

@ -1,3 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.shopping.middleware
import mozilla.components.browser.state.store.BrowserStore

@ -0,0 +1,158 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.shopping.middleware
import mozilla.components.service.glean.testing.GleanTestRule
import mozilla.components.support.test.ext.joinBlocking
import mozilla.components.support.test.libstate.ext.waitUntilIdle
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.GleanMetrics.Shopping
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.shopping.store.ReviewQualityCheckAction
import org.mozilla.fenix.shopping.store.ReviewQualityCheckStore
@RunWith(FenixRobolectricTestRunner::class)
class ReviewQualityCheckTelemetryMiddlewareTest {
@get:Rule
val gleanTestRule = GleanTestRule(testContext)
private lateinit var store: ReviewQualityCheckStore
@Before
fun setup() {
store = ReviewQualityCheckStore(
middleware = listOf(
ReviewQualityCheckTelemetryMiddleware(),
),
)
store.waitUntilIdle()
}
@Test
fun `WHEN the user opts in the feature THEN the opt in event is recorded`() {
store.dispatch(ReviewQualityCheckAction.OptIn).joinBlocking()
store.waitUntilIdle()
assertNotNull(Shopping.surfaceOptInAccepted.testGetValue())
}
@Test
fun `WHEN the bottom sheet is closed THEN the bottom sheet closed event is recorded`() {
store.dispatch(ReviewQualityCheckAction.BottomSheetClosed).joinBlocking()
store.waitUntilIdle()
assertNotNull(Shopping.surfaceClosed.testGetValue())
}
@Test
fun `WHEN the bottom sheet is displayed THEN the bottom sheet displayed event is recorded`() {
store.dispatch(ReviewQualityCheckAction.BottomSheetDisplayed).joinBlocking()
store.waitUntilIdle()
assertNotNull(Shopping.surfaceDisplayed.testGetValue())
}
@Test
fun `WHEN the learn more link from the explainer card is clicked THEN the explainer learn more event is recorded`() {
store.dispatch(ReviewQualityCheckAction.OpenExplainerLearnMoreLink).joinBlocking()
store.waitUntilIdle()
assertNotNull(Shopping.surfaceReviewQualityExplainerUrlClicked.testGetValue())
}
@Test
fun `WHEN the terms and conditions link from the onboarding card is clicked THEN the terms and conditions event is recorded`() {
store.dispatch(ReviewQualityCheckAction.OpenOnboardingTermsLink).joinBlocking()
store.waitUntilIdle()
assertNotNull(Shopping.surfaceShowTermsClicked.testGetValue())
}
@Test
fun `WHEN the privacy policy link from the onboarding card is clicked THEN the privacy policy event is recorded`() {
store.dispatch(ReviewQualityCheckAction.OpenOnboardingPrivacyPolicyLink).joinBlocking()
store.waitUntilIdle()
assertNotNull(Shopping.surfaceShowPrivacyPolicyClicked.testGetValue())
}
@Test
fun `WHEN the learn more link from the onboarding card is clicked THEN the onboarding learn more event is recorded`() {
store.dispatch(ReviewQualityCheckAction.OpenOnboardingLearnMoreLink).joinBlocking()
store.waitUntilIdle()
assertNotNull(Shopping.surfaceLearnMoreClicked.testGetValue())
}
@Test
fun `WHEN the not now button from the onboarding card is clicked THEN the not now event is recorded`() {
store.dispatch(ReviewQualityCheckAction.NotNowClicked).joinBlocking()
store.waitUntilIdle()
assertNotNull(Shopping.surfaceNotNowClicked.testGetValue())
}
@Test
fun `WHEN the expand button from the highlights card is clicked THEN the show more recent reviews event is recorded`() {
store.dispatch(ReviewQualityCheckAction.ShowMoreRecentReviewsClicked).joinBlocking()
store.waitUntilIdle()
assertNotNull(Shopping.surfaceShowMoreRecentReviewsClicked.testGetValue())
}
@Test
fun `WHEN the expand button from the settings card is clicked THEN the settings expand event is recorded`() {
store.dispatch(ReviewQualityCheckAction.ExpandSettingsClicked).joinBlocking()
store.waitUntilIdle()
assertNotNull(Shopping.surfaceExpandSettings.testGetValue())
}
@Test
fun `WHEN no analysis is present THEN the no analysis event is recorded`() {
store.dispatch(ReviewQualityCheckAction.NoAnalysisDisplayed).joinBlocking()
store.waitUntilIdle()
assertNotNull(Shopping.surfaceNoReviewReliabilityAvailable.testGetValue())
}
@Test
fun `WHEN analyze button is clicked THEN the analyze reviews event is recorded`() {
store.dispatch(ReviewQualityCheckAction.AnalyzeProduct).joinBlocking()
store.waitUntilIdle()
assertNotNull(Shopping.surfaceAnalyzeReviewsNoneAvailableClicked.testGetValue())
}
@Test
fun `WHEN reanalyze button is clicked THEN the reanalyze event is recorded`() {
store.dispatch(ReviewQualityCheckAction.ReanalyzeProduct).joinBlocking()
store.waitUntilIdle()
assertNotNull(Shopping.surfaceReanalyzeClicked.testGetValue())
}
@Test
fun `WHEN back in stock button is clicked THEN the reactivate event is recorded`() {
store.dispatch(ReviewQualityCheckAction.ReportProductBackInStock).joinBlocking()
store.waitUntilIdle()
assertNotNull(Shopping.surfaceReactivatedButtonClicked.testGetValue())
}
@Test
fun `WHEN the user is opted out after initializing the feature after THEN the onboarding displayed event is recorded`() {
store.dispatch(ReviewQualityCheckAction.OptOutCompleted(emptyList())).joinBlocking()
store.waitUntilIdle()
assertNotNull(Shopping.surfaceOnboardingDisplayed.testGetValue())
}
}

@ -171,6 +171,8 @@ Settings:
Sharing:
description: Corresponds to the [Feature:Sharing](https://github.com/mozilla-mobile/fenix/issues?q=label%3AFeature%3ASharing)
label on GitHub.
Shopping:
description: Corresponds to the review quality check feature.
Shortcuts:
description: Top Sites/Topsites on the Firefox home page. Corresponds to the [Feature:Shortcuts](https://github.com/mozilla-mobile/fenix/issues?q=label%3AFeature%3AShortcuts)
label on GitHub.

Loading…
Cancel
Save