for #24549: add telemetry for recent synced tab

pull/543/head
MatthewTighe 2 years ago committed by mergify[bot]
parent 9f1e1b49b5
commit 414a54ed02

@ -3957,6 +3957,20 @@ tabs_tray:
tab_count:
description: The number of selected tabs added to colelction.
type: quantity
access_point:
type: labeled_counter
description: |
The area that the tabs tray was accessed from.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/24549
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/24671
notification_emails:
- android-probes@mozilla.com
data_sensitivity:
- interaction
expires: 114
collections:
renamed:
@ -7570,3 +7584,74 @@ downloads:
metadata:
tags:
- Download
recent_synced_tabs:
recent_synced_tab_shown:
type: labeled_counter
description: |
Counts impressions of a recent synced tab on the homepage, labeled by the
device type the tab originates from.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/24549
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/24671
notification_emails:
- android-probes@mozilla.com
data_sensitivity:
- interaction
expires: 114
recent_synced_tab_time_to_load:
type: timing_distribution
time_unit: millisecond
description: |
Measures the amount of time between the beginning of a sync and the end.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/24549
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/24671
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 114
recent_synced_tab_opened:
type: labeled_counter
description: |
Counts the number of times a recent synced tab is opened, labeled by the
device type the tab originates from.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/24549
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/24671
notification_emails:
- android-probes@mozilla.com
data_sensitivity:
- interaction
expires: 114
show_all_synced_tabs_clicked:
type: counter
description: |
Counts how many times "show all synced tabs" button has been clicked.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/24549
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/24671
notification_emails:
- android-probes@mozilla.com
data_sensitivity:
- interaction
expires: 114
latest_synced_tab_is_stale:
type: counter
description: |
Counts how often the loading placeholder is shown and the resulting tab
is the same as it was before the load.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/24549
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/24671
notification_emails:
- android-probes@mozilla.com
data_sensitivity:
- interaction
expires: 114

@ -123,6 +123,7 @@ import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.settings.SupportUtils.SumoTopic.HELP
import org.mozilla.fenix.settings.deletebrowsingdata.deleteAndQuit
import org.mozilla.fenix.tabstray.TabsTrayAccessPoint
import org.mozilla.fenix.theme.ThemeManager
import org.mozilla.fenix.utils.Settings.Companion.TOP_SITES_PROVIDER_MAX_THRESHOLD
import org.mozilla.fenix.utils.ToolbarPopupWindow
@ -175,7 +176,7 @@ class HomeFragment : Fragment() {
context = requireContext(),
storage = requireComponents.backgroundServices.syncedTabsStorage,
accountManager = requireComponents.backgroundServices.accountManager,
lifecycleOwner = viewLifecycleOwner
lifecycleOwner = viewLifecycleOwner,
)
}
@ -364,6 +365,7 @@ class HomeFragment : Fragment() {
recentSyncedTabController = DefaultRecentSyncedTabController(
addNewTabUseCase = requireComponents.useCases.tabsUseCases.addTab,
navController = findNavController(),
accessPoint = TabsTrayAccessPoint.HomeRecentSyncedTab,
),
recentBookmarksController = DefaultRecentBookmarksController(
activity = activity,

@ -7,6 +7,7 @@ package org.mozilla.fenix.home.recentsyncedtabs
import android.content.Context
import androidx.lifecycle.LifecycleOwner
import mozilla.components.browser.storage.sync.SyncedDeviceTabs
import mozilla.components.concept.sync.DeviceType
import mozilla.components.feature.syncedtabs.SyncedTabsFeature
import mozilla.components.feature.syncedtabs.storage.SyncedTabsStorage
import mozilla.components.feature.syncedtabs.view.SyncedTabsView
@ -14,6 +15,8 @@ import mozilla.components.service.fxa.manager.FxaAccountManager
import mozilla.components.support.base.feature.LifecycleAwareFeature
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.appstate.AppAction
import mozilla.telemetry.glean.GleanTimerId
import org.mozilla.fenix.GleanMetrics.RecentSyncedTabs
/**
* Delegate to handle layout updates and dispatch actions related to the recent synced tab.
@ -45,7 +48,12 @@ class RecentSyncedTabFeature(
override var listener: SyncedTabsView.Listener? = null
private var syncStartId: GleanTimerId? = null
private var lastSyncedTab: RecentSyncedTab? = null
override fun startLoading() {
syncStartId?.let { RecentSyncedTabs.recentSyncedTabTimeToLoad.cancel(it) }
syncStartId = RecentSyncedTabs.recentSyncedTabTimeToLoad.start()
store.dispatch(
AppAction.RecentSyncedTabStateChange(RecentSyncedTabState.Loading)
)
@ -59,14 +67,17 @@ class RecentSyncedTabFeature(
val tab = it.tabs.firstOrNull()?.active() ?: return
RecentSyncedTab(
deviceDisplayName = it.device.displayName,
deviceType = it.device.deviceType,
title = tab.title,
url = tab.url,
iconUrl = tab.iconUrl
)
} ?: return
recordMetrics(syncedTab, lastSyncedTab, syncStartId)
store.dispatch(
AppAction.RecentSyncedTabStateChange(RecentSyncedTabState.Success(syncedTab))
)
lastSyncedTab = syncedTab
}
// UI will either not be displayed if not authenticated (DefaultPresenter.start),
@ -84,6 +95,18 @@ class RecentSyncedTabFeature(
override fun stop() {
syncedTabsFeature.stop()
}
private fun recordMetrics(
tab: RecentSyncedTab,
lastSyncedTab: RecentSyncedTab?,
syncStartId: GleanTimerId?
) {
RecentSyncedTabs.recentSyncedTabShown[tab.deviceType.name].add()
RecentSyncedTabs.recentSyncedTabTimeToLoad.stopAndAccumulate(syncStartId)
if (tab == lastSyncedTab) {
RecentSyncedTabs.latestSyncedTabIsStale.add()
}
}
}
/**
@ -116,6 +139,7 @@ sealed class RecentSyncedTabState {
*/
data class RecentSyncedTab(
val deviceDisplayName: String,
val deviceType: DeviceType,
val title: String,
val url: String,
val iconUrl: String?,

@ -6,11 +6,13 @@ package org.mozilla.fenix.home.recentsyncedtabs.controller
import androidx.navigation.NavController
import mozilla.components.feature.tabs.TabsUseCases
import org.mozilla.fenix.GleanMetrics.RecentSyncedTabs
import org.mozilla.fenix.R
import org.mozilla.fenix.home.HomeFragmentDirections
import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTab
import org.mozilla.fenix.home.recentsyncedtabs.interactor.RecentSyncedTabInteractor
import org.mozilla.fenix.tabstray.Page
import org.mozilla.fenix.tabstray.TabsTrayAccessPoint
/**
* An interface that handles the view manipulation of the recent synced tabs in the Home screen.
@ -36,15 +38,21 @@ interface RecentSyncedTabController {
class DefaultRecentSyncedTabController(
private val addNewTabUseCase: TabsUseCases.AddNewTabUseCase,
private val navController: NavController,
private val accessPoint: TabsTrayAccessPoint,
) : RecentSyncedTabController {
override fun handleRecentSyncedTabClick(tab: RecentSyncedTab) {
RecentSyncedTabs.recentSyncedTabOpened[tab.deviceType.name].add()
addNewTabUseCase.invoke(tab.url)
navController.navigate(R.id.browserFragment)
}
override fun handleSyncedTabShowAllClicked() {
RecentSyncedTabs.showAllSyncedTabsClicked.add()
navController.navigate(
HomeFragmentDirections.actionGlobalTabsTrayFragment(page = Page.SyncedTabs)
HomeFragmentDirections.actionGlobalTabsTrayFragment(
page = Page.SyncedTabs,
accessPoint = accessPoint
)
)
}
}

@ -35,6 +35,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import mozilla.components.concept.sync.DeviceType
import org.mozilla.fenix.R
import org.mozilla.fenix.compose.PrimaryText
import org.mozilla.fenix.compose.SecondaryText
@ -206,6 +207,7 @@ private fun TextLinePlaceHolder() {
private fun LoadedRecentSyncedTab() {
val tab = RecentSyncedTab(
deviceDisplayName = "Firefox on MacBook",
deviceType = DeviceType.DESKTOP,
title = "This is a long site title",
url = "https://mozilla.org",
iconUrl = "https://mozilla.org",

@ -66,6 +66,14 @@ import org.mozilla.fenix.tabstray.syncedtabs.SyncedTabsIntegration
import org.mozilla.fenix.utils.allowUndo
import kotlin.math.max
/**
* The action or screen that was used to navigate to the Tabs Tray.
*/
enum class TabsTrayAccessPoint {
None,
HomeRecentSyncedTab
}
@Suppress("TooManyFunctions", "LargeClass")
class TabsTrayFragment : AppCompatDialogFragment() {
@ -127,6 +135,9 @@ class TabsTrayFragment : AppCompatDialogFragment() {
)
val args by navArgs<TabsTrayFragmentArgs>()
args.accessPoint.takeIf { it != TabsTrayAccessPoint.None }?.let {
TabsTray.accessPoint[it.name].add()
}
val initialMode = if (args.enterMultiselect) {
TabsTrayState.Mode.Select(emptySet())
} else {

@ -165,8 +165,11 @@
<argument
android:name="page"
android:defaultValue="NormalTabs"
app:argType="org.mozilla.fenix.tabstray.Page"
/>
app:argType="org.mozilla.fenix.tabstray.Page" />
<argument
android:name="accessPoint"
android:defaultValue="None"
app:argType="org.mozilla.fenix.tabstray.TabsTrayAccessPoint" />
</dialog>
<fragment

@ -10,6 +10,7 @@ import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.verify
import kotlinx.coroutines.runBlocking
import mozilla.components.concept.sync.DeviceType
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.top.sites.TopSite
import mozilla.components.service.fxa.manager.FxaAccountManager
@ -164,7 +165,7 @@ class AppStoreTest {
appStore.dispatch(AppAction.RecentSyncedTabStateChange(loading)).join()
assertEquals(loading, appStore.state.recentSyncedTabState)
val recentSyncedTab = RecentSyncedTab("device name", "title", "url", null)
val recentSyncedTab = RecentSyncedTab("device name", DeviceType.DESKTOP, "title", "url", null)
val success = RecentSyncedTabState.Success(recentSyncedTab)
appStore.dispatch(AppAction.RecentSyncedTabStateChange(success)).join()
assertEquals(success, appStore.state.recentSyncedTabState)

@ -4,6 +4,7 @@
package org.mozilla.fenix.home.recentsyncedtabs
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
@ -14,12 +15,25 @@ import mozilla.components.concept.sync.Device
import mozilla.components.concept.sync.DeviceType
import mozilla.components.feature.syncedtabs.view.SyncedTabsView
import mozilla.components.service.fxa.manager.FxaAccountManager
import mozilla.components.service.glean.testing.GleanTestRule
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.GleanMetrics.RecentSyncedTabs
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.appstate.AppAction
@RunWith(AndroidJUnit4::class)
class RecentSyncedTabFeatureTest {
@get:Rule
val gleanTestRule = GleanTestRule(testContext)
private val earliestTime = 100L
private val earlierTime = 250L
private val timeNow = 500L
@ -168,6 +182,46 @@ class RecentSyncedTabFeatureTest {
verify { store.dispatch(AppAction.RecentSyncedTabStateChange(RecentSyncedTabState.None)) }
}
@Test
fun `WHEN synced tab displayed THEN labeled counter metric recorded with device type`() {
val tab = SyncedDeviceTabs(deviceAccessed1, listOf(createActiveTab()))
feature.displaySyncedTabs(listOf(tab))
assertEquals(1, RecentSyncedTabs.recentSyncedTabShown[deviceAccessed1.deviceType.name].testGetValue())
}
@Test
fun `GIVEN that tab previously started loading WHEN synced tab displayed THEN load time metric recorded`() {
val tab = SyncedDeviceTabs(deviceAccessed1, listOf(createActiveTab()))
feature.startLoading()
feature.displaySyncedTabs(listOf(tab))
assertTrue(RecentSyncedTabs.recentSyncedTabTimeToLoad.testHasValue())
}
@Test
fun `GIVEN that the displayed tab was the last displayed tab WHEN displayed THEN recorded as stale`() {
val tab = SyncedDeviceTabs(deviceAccessed1, listOf(createActiveTab()))
feature.displaySyncedTabs(listOf(tab))
feature.displaySyncedTabs(listOf(tab))
assertEquals(1, RecentSyncedTabs.latestSyncedTabIsStale.testGetValue())
}
@Test
fun `GIVEN that the displayed tab was not the last displayed tab WHEN displayed THEN not recorded as stale`() {
val tab1 = SyncedDeviceTabs(deviceAccessed1, listOf(createActiveTab()))
val tab2 = SyncedDeviceTabs(deviceAccessed2, listOf(createActiveTab()))
feature.displaySyncedTabs(listOf(tab1))
feature.displaySyncedTabs(listOf(tab2))
assertFalse(RecentSyncedTabs.latestSyncedTabIsStale.testHasValue())
}
private fun createActiveTab(
title: String = "title",
url: String = "url",
@ -181,6 +235,7 @@ class RecentSyncedTabFeatureTest {
private fun Tab.toRecentSyncedTab(device: Device) = RecentSyncedTab(
deviceDisplayName = device.displayName,
deviceType = device.deviceType,
title = this.active().title,
url = this.active().url,
iconUrl = this.active().iconUrl

@ -6,29 +6,47 @@ package org.mozilla.fenix.home.recentsyncedtabs.controller
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.runs
import io.mockk.verify
import mozilla.components.concept.sync.DeviceType
import mozilla.components.feature.tabs.TabsUseCases
import mozilla.components.support.test.robolectric.testContext
import mozilla.telemetry.glean.testing.GleanTestRule
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.GleanMetrics.RecentSyncedTabs
import org.mozilla.fenix.R
import org.mozilla.fenix.home.HomeFragmentDirections
import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTab
import org.mozilla.fenix.tabstray.Page
import org.mozilla.fenix.tabstray.TabsTrayAccessPoint
@RunWith(AndroidJUnit4::class)
class DefaultRecentSyncedTabControllerTest {
@get:Rule
val gleanTestRule = GleanTestRule(testContext)
private val addTabUseCase: TabsUseCases.AddNewTabUseCase = mockk()
private val navController: NavController = mockk()
private val accessPoint = TabsTrayAccessPoint.HomeRecentSyncedTab
private lateinit var controller: RecentSyncedTabController
@Before
fun setup() {
controller = DefaultRecentSyncedTabController(addTabUseCase, navController)
controller = DefaultRecentSyncedTabController(
addNewTabUseCase = addTabUseCase,
navController = navController,
accessPoint = accessPoint,
)
}
@Test
@ -36,6 +54,7 @@ class DefaultRecentSyncedTabControllerTest {
val url = "https://mozilla.org"
val tab = RecentSyncedTab(
deviceDisplayName = "display",
deviceType = DeviceType.DESKTOP,
title = "title",
url = url,
iconUrl = null
@ -58,8 +77,40 @@ class DefaultRecentSyncedTabControllerTest {
verify {
navController.navigate(
HomeFragmentDirections.actionGlobalTabsTrayFragment(page = Page.SyncedTabs)
HomeFragmentDirections.actionGlobalTabsTrayFragment(
page = Page.SyncedTabs,
accessPoint = accessPoint
)
)
}
}
@Test
fun `WHEN synced tab clicked THEN metric counter labeled by device type is incremented`() {
val url = "https://mozilla.org"
val deviceType = DeviceType.DESKTOP
val tab = RecentSyncedTab(
deviceDisplayName = "display",
deviceType = deviceType,
title = "title",
url = url,
iconUrl = null
)
every { addTabUseCase.invoke(any()) } just runs
every { navController.navigate(any<Int>()) } just runs
controller.handleRecentSyncedTabClick(tab)
assertEquals(1, RecentSyncedTabs.recentSyncedTabOpened[deviceType.name].testGetValue())
}
@Test
fun `WHEN synced tab show all clicked THEN metric counter is incremented`() {
every { navController.navigate(any<NavDirections>()) } just runs
controller.handleSyncedTabShowAllClicked()
assertEquals(1, RecentSyncedTabs.showAllSyncedTabsClicked.testGetValue())
}
}

Loading…
Cancel
Save