Bug 1854501 - Add telemetry to count Fakespot exposures

Added a new probe `product_page_visits` which counts the number of
visits to a supported retailer product page.

(cherry picked from commit fbd860cb37334374806b835ad548b82e050c820a)
fenix/120.0
DreVla 12 months ago committed by mergify[bot]
parent 55eba1d604
commit 87bde8192c

@ -10773,6 +10773,26 @@ shopping:
metadata: metadata:
tags: tags:
- Shopping - Shopping
product_page_visits:
type: counter
description: |
Counts number of visits to a supported retailer product page
while enrolled in either the control or treatment branches
of the shopping experiment.
send_in_pings:
- metrics
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1854501
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/4120#issuecomment-1768423370
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: never
metadata:
tags:
- Shopping
shopping.settings: shopping.settings:
component_opted_out: component_opted_out:

@ -288,7 +288,7 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
shoppingExperienceFeature = DefaultShoppingExperienceFeature( shoppingExperienceFeature = DefaultShoppingExperienceFeature(
settings = requireContext().settings(), settings = requireContext().settings(),
), ),
onAvailabilityChange = { onIconVisibilityChange = {
if (!reviewQualityCheckAvailable && it) { if (!reviewQualityCheckAvailable && it) {
Shopping.addressBarIconDisplayed.record() Shopping.addressBarIconDisplayed.record()
} }
@ -298,6 +298,9 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
onBottomSheetStateChange = { onBottomSheetStateChange = {
reviewQualityCheck.setSelected(selected = it, notifyListener = false) reviewQualityCheck.setSelected(selected = it, notifyListener = false)
}, },
onProductPageDetected = {
Shopping.productPageVisits.add()
},
), ),
owner = this, owner = this,
view = view, view = view,

@ -27,37 +27,41 @@ private const val DEBOUNCE_TIMEOUT_MILLIS = 200L
* @property appStore Reference to the application's [AppStore]. * @property appStore Reference to the application's [AppStore].
* @property browserStore Reference to the application's [BrowserStore]. * @property browserStore Reference to the application's [BrowserStore].
* @property shoppingExperienceFeature Reference to the [ShoppingExperienceFeature]. * @property shoppingExperienceFeature Reference to the [ShoppingExperienceFeature].
* @property onAvailabilityChange Invoked when availability of this feature changes based on feature * @property onIconVisibilityChange Invoked when shopping icon visibility changes based on feature
* flag and when the loaded page is a supported product page. * flag and when the loaded page is a supported product page.
* @property onBottomSheetStateChange Invoked when the bottom sheet is collapsed or expanded. * @property onBottomSheetStateChange Invoked when the bottom sheet is collapsed or expanded.
* @property debounceTimeoutMillis Function that returns the debounce timeout in milliseconds. This * @property debounceTimeoutMillis Function that returns the debounce timeout in milliseconds. This
* make it possible to wait till [ContentState.isProductUrl] is stable before invoking * make it possible to wait till [ContentState.isProductUrl] is stable before invoking
* [onAvailabilityChange]. * [onIconVisibilityChange].
* @property onProductPageDetected Invoked when a product page is detected and loaded. Used to
* detect when to send telemetry for shopping.product_page_visits.
*/ */
@OptIn(FlowPreview::class) @OptIn(FlowPreview::class)
class ReviewQualityCheckFeature( class ReviewQualityCheckFeature(
private val appStore: AppStore, private val appStore: AppStore,
private val browserStore: BrowserStore, private val browserStore: BrowserStore,
private val shoppingExperienceFeature: ShoppingExperienceFeature, private val shoppingExperienceFeature: ShoppingExperienceFeature,
private val onAvailabilityChange: (isAvailable: Boolean) -> Unit, private val onIconVisibilityChange: (isAvailable: Boolean) -> Unit,
private val onBottomSheetStateChange: (isExpanded: Boolean) -> Unit, private val onBottomSheetStateChange: (isExpanded: Boolean) -> Unit,
private val debounceTimeoutMillis: (Boolean) -> Long = { if (it) DEBOUNCE_TIMEOUT_MILLIS else 0 }, private val debounceTimeoutMillis: (Boolean) -> Long = { if (it) DEBOUNCE_TIMEOUT_MILLIS else 0 },
private val onProductPageDetected: () -> Unit,
) : LifecycleAwareFeature { ) : LifecycleAwareFeature {
private var scope: CoroutineScope? = null private var scope: CoroutineScope? = null
private var appStoreScope: CoroutineScope? = null private var appStoreScope: CoroutineScope? = null
override fun start() { override fun start() {
if (!shoppingExperienceFeature.isEnabled) {
onAvailabilityChange(false)
return
}
scope = browserStore.flowScoped { flow -> scope = browserStore.flowScoped { flow ->
flow.mapNotNull { it.selectedTab } flow.mapNotNull { it.selectedTab }
.map { it.content.isProductUrl && !it.content.loading } .map { it.content.isProductUrl && !it.content.loading }
.distinctUntilChanged() .distinctUntilChanged()
.debounce(debounceTimeoutMillis) .debounce(debounceTimeoutMillis)
.collect(onAvailabilityChange) .collect {
if (it) {
onProductPageDetected()
}
onIconVisibilityChange(shoppingExperienceFeature.isEnabled && it)
}
} }
appStoreScope = appStore.flowScoped { flow -> appStoreScope = appStore.flowScoped { flow ->

@ -30,20 +30,34 @@ class ReviewQualityCheckFeatureTest {
val coroutinesTestRule = MainCoroutineRule() val coroutinesTestRule = MainCoroutineRule()
@Test @Test
fun `WHEN feature is not enabled THEN callback returns false`() { fun `WHEN feature is not enabled THEN callback returns false`() = runTest {
var availability: Boolean? = null var availability: Boolean? = null
val tab = createTab(
url = "https://www.mozilla.org",
id = "test-tab",
isProductUrl = true,
)
val browserState = BrowserState(
tabs = listOf(tab),
selectedTabId = tab.id,
)
val tested = ReviewQualityCheckFeature( val tested = ReviewQualityCheckFeature(
appStore = AppStore(), appStore = AppStore(),
browserStore = BrowserStore(), browserStore = BrowserStore(
initialState = browserState,
),
shoppingExperienceFeature = FakeShoppingExperienceFeature(enabled = false), shoppingExperienceFeature = FakeShoppingExperienceFeature(enabled = false),
onAvailabilityChange = { onIconVisibilityChange = {
availability = it availability = it
}, },
onBottomSheetStateChange = {}, onBottomSheetStateChange = {},
onProductPageDetected = {},
) )
tested.start() tested.start()
testScheduler.advanceTimeBy(250)
assertFalse(availability!!) assertFalse(availability!!)
} }
@ -66,10 +80,11 @@ class ReviewQualityCheckFeatureTest {
initialState = browserState, initialState = browserState,
), ),
shoppingExperienceFeature = FakeShoppingExperienceFeature(), shoppingExperienceFeature = FakeShoppingExperienceFeature(),
onAvailabilityChange = { onIconVisibilityChange = {
availability = it availability = it
}, },
onBottomSheetStateChange = {}, onBottomSheetStateChange = {},
onProductPageDetected = {},
) )
tested.start() tested.start()
@ -99,10 +114,11 @@ class ReviewQualityCheckFeatureTest {
initialState = browserState, initialState = browserState,
), ),
shoppingExperienceFeature = FakeShoppingExperienceFeature(), shoppingExperienceFeature = FakeShoppingExperienceFeature(),
onAvailabilityChange = { onIconVisibilityChange = {
availability = it availability = it
}, },
onBottomSheetStateChange = {}, onBottomSheetStateChange = {},
onProductPageDetected = {},
) )
tested.start() tested.start()
@ -129,11 +145,12 @@ class ReviewQualityCheckFeatureTest {
initialState = browserState, initialState = browserState,
), ),
shoppingExperienceFeature = FakeShoppingExperienceFeature(), shoppingExperienceFeature = FakeShoppingExperienceFeature(),
onAvailabilityChange = { onIconVisibilityChange = {
availability = it availability = it
}, },
onBottomSheetStateChange = {}, onBottomSheetStateChange = {},
debounceTimeoutMillis = { 0 }, debounceTimeoutMillis = { 0 },
onProductPageDetected = {},
) )
tested.start() tested.start()
@ -165,11 +182,12 @@ class ReviewQualityCheckFeatureTest {
appStore = AppStore(), appStore = AppStore(),
browserStore = browserStore, browserStore = browserStore,
shoppingExperienceFeature = FakeShoppingExperienceFeature(), shoppingExperienceFeature = FakeShoppingExperienceFeature(),
onAvailabilityChange = { onIconVisibilityChange = {
availability = it availability = it
}, },
onBottomSheetStateChange = {}, onBottomSheetStateChange = {},
debounceTimeoutMillis = { 0 }, debounceTimeoutMillis = { 0 },
onProductPageDetected = {},
) )
tested.start() tested.start()
@ -204,11 +222,12 @@ class ReviewQualityCheckFeatureTest {
appStore = AppStore(), appStore = AppStore(),
browserStore = browserStore, browserStore = browserStore,
shoppingExperienceFeature = FakeShoppingExperienceFeature(), shoppingExperienceFeature = FakeShoppingExperienceFeature(),
onAvailabilityChange = { onIconVisibilityChange = {
availability = it availability = it
}, },
onBottomSheetStateChange = {}, onBottomSheetStateChange = {},
debounceTimeoutMillis = { 0 }, debounceTimeoutMillis = { 0 },
onProductPageDetected = {},
) )
tested.start() tested.start()
@ -238,10 +257,11 @@ class ReviewQualityCheckFeatureTest {
appStore = AppStore(), appStore = AppStore(),
browserStore = browserStore, browserStore = browserStore,
shoppingExperienceFeature = FakeShoppingExperienceFeature(), shoppingExperienceFeature = FakeShoppingExperienceFeature(),
onAvailabilityChange = { onIconVisibilityChange = {
availability.add(it) availability.add(it)
}, },
onBottomSheetStateChange = {}, onBottomSheetStateChange = {},
onProductPageDetected = {},
) )
tested.start() tested.start()
@ -301,11 +321,12 @@ class ReviewQualityCheckFeatureTest {
appStore = AppStore(), appStore = AppStore(),
browserStore = browserStore, browserStore = browserStore,
shoppingExperienceFeature = FakeShoppingExperienceFeature(), shoppingExperienceFeature = FakeShoppingExperienceFeature(),
onAvailabilityChange = { onIconVisibilityChange = {
availability = it availability = it
availabilityCount++ availabilityCount++
}, },
onBottomSheetStateChange = {}, onBottomSheetStateChange = {},
onProductPageDetected = {},
) )
tested.start() tested.start()
@ -329,10 +350,11 @@ class ReviewQualityCheckFeatureTest {
appStore = appStore, appStore = appStore,
browserStore = BrowserStore(), browserStore = BrowserStore(),
shoppingExperienceFeature = FakeShoppingExperienceFeature(), shoppingExperienceFeature = FakeShoppingExperienceFeature(),
onAvailabilityChange = {}, onIconVisibilityChange = {},
onBottomSheetStateChange = { onBottomSheetStateChange = {
isExpanded = it isExpanded = it
}, },
onProductPageDetected = {},
) )
tested.start() tested.start()
@ -354,10 +376,11 @@ class ReviewQualityCheckFeatureTest {
appStore = appStore, appStore = appStore,
browserStore = BrowserStore(), browserStore = BrowserStore(),
shoppingExperienceFeature = FakeShoppingExperienceFeature(), shoppingExperienceFeature = FakeShoppingExperienceFeature(),
onAvailabilityChange = {}, onIconVisibilityChange = {},
onBottomSheetStateChange = { onBottomSheetStateChange = {
isExpanded = it isExpanded = it
}, },
onProductPageDetected = {},
) )
tested.start() tested.start()
@ -379,10 +402,11 @@ class ReviewQualityCheckFeatureTest {
appStore = appStore, appStore = appStore,
browserStore = BrowserStore(), browserStore = BrowserStore(),
shoppingExperienceFeature = FakeShoppingExperienceFeature(), shoppingExperienceFeature = FakeShoppingExperienceFeature(),
onAvailabilityChange = {}, onIconVisibilityChange = {},
onBottomSheetStateChange = { onBottomSheetStateChange = {
isExpanded = it isExpanded = it
}, },
onProductPageDetected = {},
) )
tested.start() tested.start()
@ -394,4 +418,140 @@ class ReviewQualityCheckFeatureTest {
tested.start() tested.start()
assertFalse(isExpanded!!) assertFalse(isExpanded!!)
} }
@Test
fun `GIVEN feature is enabled WHEN non product url accessed THEN callback not called`() {
runTest {
var invokedCounter = 0
val tab = createTab(
url = "https://www.mozilla.org",
id = "test-tab",
isProductUrl = false,
)
val browserState = BrowserState(
tabs = listOf(tab),
selectedTabId = tab.id,
)
val tested = ReviewQualityCheckFeature(
appStore = AppStore(),
browserStore = BrowserStore(
initialState = browserState,
),
shoppingExperienceFeature = FakeShoppingExperienceFeature(),
onIconVisibilityChange = {},
onBottomSheetStateChange = {},
debounceTimeoutMillis = { 0 },
onProductPageDetected = {
invokedCounter++
},
)
tested.start()
assertEquals(invokedCounter, 0)
}
}
@Test
fun `GIVEN feature is enabled WHEN product url accessed THEN callback called`() {
runTest {
var invokedCounter = 0
val tab = createTab(
url = "https://www.shopping.org",
id = "test-tab",
isProductUrl = true,
).let {
it.copy(content = it.content.copy(loading = false))
}
val browserState = BrowserState(
tabs = listOf(tab),
selectedTabId = tab.id,
)
val tested = ReviewQualityCheckFeature(
appStore = AppStore(),
browserStore = BrowserStore(
initialState = browserState,
),
shoppingExperienceFeature = FakeShoppingExperienceFeature(),
onIconVisibilityChange = {},
onBottomSheetStateChange = {},
debounceTimeoutMillis = { 0 },
onProductPageDetected = {
invokedCounter++
},
)
tested.start()
assertEquals(invokedCounter, 1)
}
}
@Test
fun `GIVEN feature is disabled WHEN non product url accessed THEN callback not called`() {
runTest {
var invokedCounter = 0
val tab = createTab(
url = "https://www.mozilla.org",
id = "test-tab",
isProductUrl = false,
)
val browserState = BrowserState(
tabs = listOf(tab),
selectedTabId = tab.id,
)
val tested = ReviewQualityCheckFeature(
appStore = AppStore(),
browserStore = BrowserStore(
initialState = browserState,
),
shoppingExperienceFeature = FakeShoppingExperienceFeature(enabled = false),
onIconVisibilityChange = {},
onBottomSheetStateChange = {},
debounceTimeoutMillis = { 0 },
onProductPageDetected = {
invokedCounter++
},
)
tested.start()
assertEquals(invokedCounter, 0)
}
}
@Test
fun `GIVEN feature is disabled WHEN product url accessed THEN callback called`() {
runTest {
var invokedCounter = 0
val tab = createTab(
url = "https://www.mozilla.org",
id = "test-tab",
isProductUrl = true,
).let {
it.copy(content = it.content.copy(loading = false))
}
val browserState = BrowserState(
tabs = listOf(tab),
selectedTabId = tab.id,
)
val tested = ReviewQualityCheckFeature(
appStore = AppStore(),
browserStore = BrowserStore(
initialState = browserState,
),
shoppingExperienceFeature = FakeShoppingExperienceFeature(enabled = false),
onIconVisibilityChange = {},
onBottomSheetStateChange = {},
debounceTimeoutMillis = { 0 },
onProductPageDetected = {
invokedCounter++
},
)
tested.start()
assertEquals(invokedCounter, 1)
}
}
} }

Loading…
Cancel
Save