[fenix] Tab group count telemetry (https://github.com/mozilla-mobile/fenix/pull/22479)
* For https://github.com/mozilla-mobile/fenix/issues/22410 - Refactored tab sorter metrics into a middleware * For https://github.com/mozilla-mobile/fenix/issues/22410 - Created distribution metric for tab group sizes * For https://github.com/mozilla-mobile/fenix/issues/22410 - Created tests for tabs tray middleware * For https://github.com/mozilla-mobile/fenix/issues/22410 - Merge fixes * For https://github.com/mozilla-mobile/fenix/issues/22410 - Added PR number to metric * For https://github.com/mozilla-mobile/fenix/issues/22410 - Fixed unit tests post merge. Added waitUntilIdle to new tests. * For https://github.com/mozilla-mobile/fenix/issues/22410 - Added missing line to middleware to have the Store process actions * For https://github.com/mozilla-mobile/fenix/issues/22410 - Updated metric expiration to December * For https://github.com/mozilla-mobile/fenix/issues/22410 - PR Feedback * For https://github.com/mozilla-mobile/fenix/issues/22410 - Removed else from middleware whenpull/600/head
parent
2f8ad2cd2a
commit
97e59a9717
@ -0,0 +1,71 @@
|
|||||||
|
/* 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.tabstray
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import mozilla.components.lib.state.Middleware
|
||||||
|
import mozilla.components.lib.state.MiddlewareContext
|
||||||
|
import org.mozilla.fenix.components.metrics.Event
|
||||||
|
import org.mozilla.fenix.components.metrics.MetricController
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Middleware] that reacts to various [TabsTrayAction]s.
|
||||||
|
*
|
||||||
|
* @property metrics reference to the configured [MetricController] to record general page load events.
|
||||||
|
*/
|
||||||
|
class TabsTrayMiddleware(
|
||||||
|
private val metrics: MetricController
|
||||||
|
) : Middleware<TabsTrayState, TabsTrayAction> {
|
||||||
|
|
||||||
|
private var shouldReportInactiveTabMetrics: Boolean = true
|
||||||
|
private var shouldReportSearchGroupMetrics: Boolean = true
|
||||||
|
|
||||||
|
override fun invoke(
|
||||||
|
context: MiddlewareContext<TabsTrayState, TabsTrayAction>,
|
||||||
|
next: (TabsTrayAction) -> Unit,
|
||||||
|
action: TabsTrayAction
|
||||||
|
) {
|
||||||
|
next(action)
|
||||||
|
|
||||||
|
when (action) {
|
||||||
|
is TabsTrayAction.UpdateInactiveTabs -> {
|
||||||
|
if (shouldReportInactiveTabMetrics) {
|
||||||
|
shouldReportInactiveTabMetrics = false
|
||||||
|
metrics.track(Event.InactiveTabsCountUpdate(action.tabs.size))
|
||||||
|
metrics.track(Event.TabsTrayHasInactiveTabs(action.tabs.size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is TabsTrayAction.UpdateSearchGroupTabs -> {
|
||||||
|
if (shouldReportSearchGroupMetrics) {
|
||||||
|
shouldReportSearchGroupMetrics = false
|
||||||
|
|
||||||
|
metrics.track(Event.SearchTermGroupCount(action.groups.size))
|
||||||
|
|
||||||
|
if (action.groups.isNotEmpty()) {
|
||||||
|
val tabsPerGroup = action.groups.map { it.tabs.size }
|
||||||
|
val averageTabsPerGroup = tabsPerGroup.average()
|
||||||
|
metrics.track(Event.AverageTabsPerSearchTermGroup(averageTabsPerGroup))
|
||||||
|
|
||||||
|
val tabGroupSizeMapping = tabsPerGroup.map { generateTabGroupSizeMappedValue(it) }
|
||||||
|
metrics.track(Event.SearchTermGroupSizeDistribution(tabGroupSizeMapping))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MagicNumber")
|
||||||
|
@VisibleForTesting
|
||||||
|
/**
|
||||||
|
* This follows the logic outlined in metrics.yaml for "search_terms.group_size_distribution"
|
||||||
|
*/
|
||||||
|
internal fun generateTabGroupSizeMappedValue(size: Int): Long =
|
||||||
|
when (size) {
|
||||||
|
2 -> 1L
|
||||||
|
in 3..5 -> 2L
|
||||||
|
in 6..10 -> 3L
|
||||||
|
else -> 4L
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,107 @@
|
|||||||
|
/* 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.tabstray
|
||||||
|
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import mozilla.components.support.test.libstate.ext.waitUntilIdle
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mozilla.fenix.components.metrics.Event
|
||||||
|
import org.mozilla.fenix.components.metrics.MetricController
|
||||||
|
import org.mozilla.fenix.tabstray.browser.TabGroup
|
||||||
|
|
||||||
|
class TabsTrayMiddlewareTest {
|
||||||
|
|
||||||
|
private lateinit var store: TabsTrayStore
|
||||||
|
private lateinit var tabsTrayMiddleware: TabsTrayMiddleware
|
||||||
|
private lateinit var metrics: MetricController
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
metrics = mockk(relaxed = true)
|
||||||
|
tabsTrayMiddleware = TabsTrayMiddleware(
|
||||||
|
metrics
|
||||||
|
)
|
||||||
|
store = TabsTrayStore(
|
||||||
|
middlewares = listOf(tabsTrayMiddleware),
|
||||||
|
initialState = TabsTrayState()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN search term groups are updated AND there is at least one group THEN report the average tabs per group`() {
|
||||||
|
store.dispatch(TabsTrayAction.UpdateSearchGroupTabs(generateSearchTermTabGroupsForAverage()))
|
||||||
|
store.waitUntilIdle()
|
||||||
|
verify { metrics.track(Event.AverageTabsPerSearchTermGroup(5.0)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN search term groups are updated AND there is at least one group THEN report the distribution of tab sizes`() {
|
||||||
|
store.dispatch(TabsTrayAction.UpdateSearchGroupTabs(generateSearchTermTabGroupsForDistribution()))
|
||||||
|
store.waitUntilIdle()
|
||||||
|
verify { metrics.track(Event.SearchTermGroupSizeDistribution(listOf(3L, 2L, 1L, 4L))) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN search term groups are updated THEN report the count of search term tab groups`() {
|
||||||
|
store.dispatch(TabsTrayAction.UpdateSearchGroupTabs(emptyList()))
|
||||||
|
store.waitUntilIdle()
|
||||||
|
verify { metrics.track(Event.SearchTermGroupCount(0)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN inactive tabs are updated THEN report the count of inactive tabs`() {
|
||||||
|
store.dispatch(TabsTrayAction.UpdateInactiveTabs(emptyList()))
|
||||||
|
store.waitUntilIdle()
|
||||||
|
verify { metrics.track(Event.TabsTrayHasInactiveTabs(0)) }
|
||||||
|
verify { metrics.track(Event.InactiveTabsCountUpdate(0)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testGenerateTabGroupSizeMappedValue() {
|
||||||
|
assertEquals(1L, tabsTrayMiddleware.generateTabGroupSizeMappedValue(2))
|
||||||
|
assertEquals(2L, tabsTrayMiddleware.generateTabGroupSizeMappedValue(3))
|
||||||
|
assertEquals(2L, tabsTrayMiddleware.generateTabGroupSizeMappedValue(4))
|
||||||
|
assertEquals(2L, tabsTrayMiddleware.generateTabGroupSizeMappedValue(5))
|
||||||
|
assertEquals(3L, tabsTrayMiddleware.generateTabGroupSizeMappedValue(6))
|
||||||
|
assertEquals(3L, tabsTrayMiddleware.generateTabGroupSizeMappedValue(7))
|
||||||
|
assertEquals(3L, tabsTrayMiddleware.generateTabGroupSizeMappedValue(8))
|
||||||
|
assertEquals(3L, tabsTrayMiddleware.generateTabGroupSizeMappedValue(9))
|
||||||
|
assertEquals(3L, tabsTrayMiddleware.generateTabGroupSizeMappedValue(10))
|
||||||
|
assertEquals(4L, tabsTrayMiddleware.generateTabGroupSizeMappedValue(11))
|
||||||
|
assertEquals(4L, tabsTrayMiddleware.generateTabGroupSizeMappedValue(12))
|
||||||
|
assertEquals(4L, tabsTrayMiddleware.generateTabGroupSizeMappedValue(20))
|
||||||
|
assertEquals(4L, tabsTrayMiddleware.generateTabGroupSizeMappedValue(50))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateSearchTermTabGroupsForAverage(): List<TabGroup> {
|
||||||
|
val group1 = TabGroup("", mockk(relaxed = true), 0L)
|
||||||
|
val group2 = TabGroup("", mockk(relaxed = true), 0L)
|
||||||
|
val group3 = TabGroup("", mockk(relaxed = true), 0L)
|
||||||
|
|
||||||
|
every { group1.tabs.size } returns 8
|
||||||
|
every { group2.tabs.size } returns 4
|
||||||
|
every { group3.tabs.size } returns 3
|
||||||
|
|
||||||
|
return listOf(group1, group2, group3)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateSearchTermTabGroupsForDistribution(): List<TabGroup> {
|
||||||
|
val group1 = TabGroup("", mockk(relaxed = true), 0L)
|
||||||
|
val group2 = TabGroup("", mockk(relaxed = true), 0L)
|
||||||
|
val group3 = TabGroup("", mockk(relaxed = true), 0L)
|
||||||
|
val group4 = TabGroup("", mockk(relaxed = true), 0L)
|
||||||
|
|
||||||
|
every { group1.tabs.size } returns 8
|
||||||
|
every { group2.tabs.size } returns 4
|
||||||
|
every { group3.tabs.size } returns 2
|
||||||
|
every { group4.tabs.size } returns 12
|
||||||
|
|
||||||
|
return listOf(group1, group2, group3, group4)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue