diff --git a/mobile/android/fenix/app/metrics.yaml b/mobile/android/fenix/app/metrics.yaml index fc798f3fc81d7..e96bd214e0951 100644 --- a/mobile/android/fenix/app/metrics.yaml +++ b/mobile/android/fenix/app/metrics.yaml @@ -9305,6 +9305,12 @@ top_sites: type: event description: | A user opened a default top site + extra_keys: + source: &top_sites_source + type: string + description: | + The surface where the top site interaction occurred. + One of "homepage" or "shortcuts_library". bugs: - https://github.com/mozilla-mobile/fenix/issues/8125 data_reviews: @@ -9325,6 +9331,8 @@ top_sites: type: event description: | A user opened the google top site + extra_keys: + source: *top_sites_source bugs: - https://github.com/mozilla-mobile/fenix/issues/17418 data_reviews: @@ -9365,6 +9373,8 @@ top_sites: type: event description: | A user opened a frecency top site + extra_keys: + source: *top_sites_source bugs: - https://github.com/mozilla-mobile/fenix/issues/14565 data_reviews: @@ -9385,6 +9395,8 @@ top_sites: type: event description: | A user opened a pinned top site + extra_keys: + source: *top_sites_source bugs: - https://github.com/mozilla-mobile/fenix/issues/14565 data_reviews: @@ -9436,6 +9448,7 @@ top_sites: description: | The type of top site. Options are: "FRECENCY", "DEFAULT", "PINNED", or "PROVIDED" + source: *top_sites_source bugs: - https://github.com/mozilla-mobile/fenix/issues/14565 - https://github.com/mozilla-mobile/fenix/issues/23526 @@ -9458,6 +9471,8 @@ top_sites: type: event description: | A user opens a new tab based on a top site item + extra_keys: + source: *top_sites_source bugs: - https://github.com/mozilla-mobile/fenix/issues/6757 data_reviews: @@ -9478,6 +9493,8 @@ top_sites: type: event description: | A user opens a new private tab based on a top site item + extra_keys: + source: *top_sites_source bugs: - https://github.com/mozilla-mobile/fenix/issues/6757 data_reviews: @@ -9498,6 +9515,8 @@ top_sites: type: event description: | A user removes a top site item + extra_keys: + source: *top_sites_source bugs: - https://github.com/mozilla-mobile/fenix/issues/6757 data_reviews: @@ -9518,6 +9537,8 @@ top_sites: type: event description: | A user removed the default Google top site + extra_keys: + source: *top_sites_source bugs: - https://github.com/mozilla-mobile/fenix/issues/21841 data_reviews: @@ -9550,6 +9571,8 @@ top_sites: type: event description: | A user has opened a contile top site. + extra_keys: + source: *top_sites_source bugs: - https://github.com/mozilla-mobile/fenix/issues/23526 data_reviews: @@ -9566,6 +9589,8 @@ top_sites: description: | A user has opened a contile top site in a private tab via the long press context menu. + extra_keys: + source: *top_sites_source bugs: - https://github.com/mozilla-mobile/fenix/issues/23526 data_reviews: @@ -9582,6 +9607,8 @@ top_sites: description: | A user has selected "Settings" via the contile top sites longpress context menu. + extra_keys: + source: *top_sites_source bugs: - https://github.com/mozilla-mobile/fenix/issues/23526 data_reviews: @@ -9598,6 +9625,8 @@ top_sites: description: | A user has selected "Our sponsors & your privacy" via the contile top sites longpress context menu. + extra_keys: + source: *top_sites_source bugs: - https://github.com/mozilla-mobile/fenix/issues/23526 data_reviews: @@ -9702,7 +9731,9 @@ top_sites: A user saw a Contile top site extra_keys: source: - description: The source of the event, example "newtab", "urlbar" + description: | + The surface where the Contile top site was shown. + One of "homepage" or "shortcuts_library". type: string position: description: The tile placement (1-based) @@ -9729,7 +9760,9 @@ top_sites: A user clicked a Contile top site extra_keys: source: - description: The source of the event, example "newtab", "urlbar" + description: | + The surface where the Contile top site was clicked. + One of "homepage" or "shortcuts_library". type: string position: description: The tile placement (1-based) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 5f08570d5dc66..66df2a5975151 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -139,6 +139,7 @@ import org.mozilla.fenix.home.toolbar.HomeToolbarComposable.Companion.DirectToSe import org.mozilla.fenix.home.topsites.DefaultTopSitesView import org.mozilla.fenix.home.topsites.TopSitesBinding import org.mozilla.fenix.home.topsites.controller.DefaultTopSiteController +import org.mozilla.fenix.home.topsites.controller.TopSitesSource import org.mozilla.fenix.home.topsites.getTopSitesConfig import org.mozilla.fenix.home.ui.Homepage import org.mozilla.fenix.home.ui.WallpaperBackground @@ -1335,6 +1336,7 @@ class HomeFragment : Fragment() { topSitesUseCases = requireComponents.useCases.topSitesUseCase, mozAdsUseCases = requireComponents.useCases.mozAdsUseCases, viewLifecycleScope = viewLifecycleOwner.lifecycleScope, + source = TopSitesSource.HOMEPAGE, ) private fun initController() { diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/topsites/ShortcutsFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/topsites/ShortcutsFragment.kt index 3fdd90f4a7eaf..80e0f3c040723 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/topsites/ShortcutsFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/topsites/ShortcutsFragment.kt @@ -19,6 +19,7 @@ import org.mozilla.fenix.e2e.SystemInsetsPaddedFragment import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.home.topsites.controller.DefaultTopSiteController import org.mozilla.fenix.home.topsites.controller.TopSiteController +import org.mozilla.fenix.home.topsites.controller.TopSitesSource import org.mozilla.fenix.home.topsites.interactor.DefaultTopSiteInteractor import org.mozilla.fenix.home.topsites.interactor.TopSiteInteractor import org.mozilla.fenix.home.topsites.middleware.ShortcutsMiddleware @@ -69,6 +70,7 @@ class ShortcutsFragment : Fragment(), SystemInsetsPaddedFragment { topSitesUseCases = requireComponents.useCases.topSitesUseCase, mozAdsUseCases = requireComponents.useCases.mozAdsUseCases, viewLifecycleScope = viewLifecycleOwner.lifecycleScope, + source = TopSitesSource.SHORTCUTS_LIBRARY, ) interactor = DefaultTopSiteInteractor( diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/topsites/controller/TopSiteController.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/topsites/controller/TopSiteController.kt index 4845a5b910c19..2c21faaf62e6e 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/topsites/controller/TopSiteController.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/topsites/controller/TopSiteController.kt @@ -52,6 +52,15 @@ import java.lang.ref.WeakReference import androidx.appcompat.R as appcompatR import mozilla.components.ui.icons.R as iconsR +/** + * The surface where a top sites / shortcuts interaction occurred. Used as the `source` extra on + * the `top_sites` Glean events. + */ +enum class TopSitesSource(val sourceName: String) { + HOMEPAGE("homepage"), + SHORTCUTS_LIBRARY("shortcuts_library"), +} + /** * An interface that handles the view manipulation of the top sites triggered by the Interactor. */ @@ -127,6 +136,7 @@ class DefaultTopSiteController( private val topSitesUseCases: TopSitesUseCases, private val mozAdsUseCases: MozAdsUseCases, private val viewLifecycleScope: CoroutineScope, + private val source: TopSitesSource, ) : TopSiteController { private val activity: Activity @@ -137,9 +147,13 @@ class DefaultTopSiteController( override fun handleOpenInPrivateTabClicked(topSite: TopSite) { if (topSite is TopSite.Provided) { - TopSites.openContileInPrivateTab.record(NoExtras()) + TopSites.openContileInPrivateTab.record( + TopSites.OpenContileInPrivateTabExtra(source = source.sourceName), + ) } else { - TopSites.openInPrivateTab.record(NoExtras()) + TopSites.openInPrivateTab.record( + TopSites.OpenInPrivateTabExtra(source = source.sourceName), + ) } activity.components.appStore.dispatch( @@ -235,10 +249,12 @@ class DefaultTopSiteController( } override fun handleRemoveTopSiteClicked(topSite: TopSite) { - TopSites.remove.record(NoExtras()) + TopSites.remove.record(TopSites.RemoveExtra(source = source.sourceName)) when (topSite.url) { - SupportUtils.GOOGLE_URL -> TopSites.googleTopSiteRemoved.record(NoExtras()) + SupportUtils.GOOGLE_URL -> TopSites.googleTopSiteRemoved.record( + TopSites.GoogleTopSiteRemovedExtra(source = source.sourceName), + ) } viewLifecycleScope.launch { @@ -250,20 +266,30 @@ class DefaultTopSiteController( override fun handleSelectTopSite(topSite: TopSite, position: Int) { when (topSite) { - is TopSite.Default -> TopSites.openDefault.record(NoExtras()) - is TopSite.Frecent -> TopSites.openFrecency.record(NoExtras()) - is TopSite.Pinned -> TopSites.openPinned.record(NoExtras()) + is TopSite.Default -> TopSites.openDefault.record( + TopSites.OpenDefaultExtra(source = source.sourceName), + ) + is TopSite.Frecent -> TopSites.openFrecency.record( + TopSites.OpenFrecencyExtra(source = source.sourceName), + ) + is TopSite.Pinned -> TopSites.openPinned.record( + TopSites.OpenPinnedExtra(source = source.sourceName), + ) is TopSite.Provided -> { sendMozAdsClickInteraction(clickUrl = topSite.clickUrl) - TopSites.openContileTopSite.record(NoExtras()).also { + TopSites.openContileTopSite.record( + TopSites.OpenContileTopSiteExtra(source = source.sourceName), + ).also { recordTopSitesClickTelemetry(topSite, position) } } } when (topSite.url) { - SupportUtils.GOOGLE_URL -> TopSites.openGoogleSearchAttribution.record(NoExtras()) + SupportUtils.GOOGLE_URL -> TopSites.openGoogleSearchAttribution.record( + TopSites.OpenGoogleSearchAttributionExtra(source = source.sourceName), + ) } val availableEngines: List = getAvailableSearchEngines() @@ -296,7 +322,9 @@ class DefaultTopSiteController( } if (existingTabForUrl == null) { - TopSites.openInNewTab.record(NoExtras()) + TopSites.openInNewTab.record( + TopSites.OpenInNewTabExtra(source = source.sourceName), + ) addTabUseCase.invoke( url = appendSearchAttributionToUrlIfNeeded(topSite.url), @@ -320,7 +348,7 @@ class DefaultTopSiteController( TopSites.contileClick.record( TopSites.ContileClickExtra( position = position + 1, - source = "newtab", + source = source.sourceName, ), ) @@ -336,7 +364,7 @@ class DefaultTopSiteController( TopSites.contileImpression.record( TopSites.ContileImpressionExtra( position = position + 1, - source = "newtab", + source = source.sourceName, ), ) @@ -359,12 +387,16 @@ class DefaultTopSiteController( } override fun handleTopSiteSettingsClicked() { - TopSites.contileSettings.record(NoExtras()) + TopSites.contileSettings.record( + TopSites.ContileSettingsExtra(source = source.sourceName), + ) navController.navigate(R.id.homeSettingsFragment) } override fun handleSponsorPrivacyClicked() { - TopSites.contileSponsorsAndPrivacy.record(NoExtras()) + TopSites.contileSponsorsAndPrivacy.record( + TopSites.ContileSponsorsAndPrivacyExtra(source = source.sourceName), + ) if (navController.currentDestination?.id == R.id.shortcutsFragment) { navController.navigate(ShortcutsFragmentDirections.actionShortcutsFragmentToBrowserFragment()) @@ -380,7 +412,9 @@ class DefaultTopSiteController( } override fun handleTopSiteLongClicked(topSite: TopSite) { - TopSites.longPress.record(TopSites.LongPressExtra(topSite.type)) + TopSites.longPress.record( + TopSites.LongPressExtra(type = topSite.type, source = source.sourceName), + ) } override fun handleShowAllTopSitesClicked() { diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/home/topsites/controller/DefaultTopSiteControllerTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/home/topsites/controller/DefaultTopSiteControllerTest.kt index 2a8c61182ad83..6709af2ed5d0a 100644 --- a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/home/topsites/controller/DefaultTopSiteControllerTest.kt +++ b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/home/topsites/controller/DefaultTopSiteControllerTest.kt @@ -13,7 +13,6 @@ import io.mockk.mockk import io.mockk.spyk import io.mockk.verify import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.action.SearchAction import mozilla.components.browser.state.search.RegionState @@ -126,11 +125,11 @@ class DefaultTopSiteControllerTest { assertNotNull(TopSites.openInNewTab.testGetValue()) assertEquals(1, TopSites.openInNewTab.testGetValue()!!.size) - assertNull(TopSites.openInNewTab.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openInNewTab.testGetValue()!!.single().extra!!["source"]) assertNotNull(TopSites.openDefault.testGetValue()) assertEquals(1, TopSites.openDefault.testGetValue()!!.size) - assertNull(TopSites.openDefault.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openDefault.testGetValue()!!.single().extra!!["source"]) verify { tabsUseCases.addTab.invoke( @@ -158,7 +157,7 @@ class DefaultTopSiteControllerTest { assertNotNull(TopSites.openInNewTab.testGetValue()) assertEquals(1, TopSites.openInNewTab.testGetValue()!!.size) - assertNull(TopSites.openInNewTab.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openInNewTab.testGetValue()!!.single().extra!!["source"]) verify { tabsUseCases.addTab.invoke( @@ -207,7 +206,7 @@ class DefaultTopSiteControllerTest { url = "mozilla.org", createdAt = 0, ) - val controller = spyk(createController(this)) + val controller = spyk(createController(this, source = TopSitesSource.SHORTCUTS_LIBRARY)) every { controller.getAvailableSearchEngines() } returns listOf(searchEngine) @@ -223,6 +222,33 @@ class DefaultTopSiteControllerTest { } } + @Test + fun `GIVEN the shortcuts library source WHEN a top site is selected THEN record telemetry with the shortcuts library source`() = runTest { + val topSite = TopSite.Default( + id = 1L, + title = "Mozilla", + url = "mozilla.org", + createdAt = 0, + ) + val controller = spyk(createController(this, source = TopSitesSource.SHORTCUTS_LIBRARY)) + + every { controller.getAvailableSearchEngines() } returns listOf(searchEngine) + + controller.handleSelectTopSite(topSite, position = 0) + + assertNotNull(TopSites.openInNewTab.testGetValue()) + assertEquals( + "shortcuts_library", + TopSites.openInNewTab.testGetValue()!!.single().extra!!["source"], + ) + + assertNotNull(TopSites.openDefault.testGetValue()) + assertEquals( + "shortcuts_library", + TopSites.openDefault.testGetValue()!!.single().extra!!["source"], + ) + } + @Test fun `GIVEN existing tab for url WHEN Default TopSite selected THEN open new tab`() = runTest { val url = "mozilla.org" @@ -251,11 +277,11 @@ class DefaultTopSiteControllerTest { assertNotNull(TopSites.openInNewTab.testGetValue()) assertEquals(1, TopSites.openInNewTab.testGetValue()!!.size) - assertNull(TopSites.openInNewTab.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openInNewTab.testGetValue()!!.single().extra!!["source"]) assertNotNull(TopSites.openDefault.testGetValue()) assertEquals(1, TopSites.openDefault.testGetValue()!!.size) - assertNull(TopSites.openDefault.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openDefault.testGetValue()!!.single().extra!!["source"]) verify { tabsUseCases.addTab.invoke( @@ -299,11 +325,11 @@ class DefaultTopSiteControllerTest { assertNotNull(TopSites.openInNewTab.testGetValue()) assertEquals(1, TopSites.openInNewTab.testGetValue()!!.size) - assertNull(TopSites.openInNewTab.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openInNewTab.testGetValue()!!.single().extra!!["source"]) assertNotNull(TopSites.openContileTopSite.testGetValue()) assertEquals(1, TopSites.openContileTopSite.testGetValue()!!.size) - assertNull(TopSites.openContileTopSite.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openContileTopSite.testGetValue()!!.single().extra!!["source"]) verify { tabsUseCases.addTab.invoke( @@ -346,7 +372,7 @@ class DefaultTopSiteControllerTest { assertNotNull(TopSites.openFrecency.testGetValue()) assertEquals(1, TopSites.openFrecency.testGetValue()!!.size) - assertNull(TopSites.openFrecency.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openFrecency.testGetValue()!!.single().extra!!["source"]) verify { selectTabUseCase.invoke(existingTabForUrl.id) @@ -384,7 +410,7 @@ class DefaultTopSiteControllerTest { assertNotNull(TopSites.openPinned.testGetValue()) assertEquals(1, TopSites.openPinned.testGetValue()!!.size) - assertNull(TopSites.openPinned.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openPinned.testGetValue()!!.single().extra!!["source"]) verify { selectTabUseCase.invoke(existingTabForUrl.id) @@ -410,15 +436,18 @@ class DefaultTopSiteControllerTest { assertNotNull(TopSites.openInNewTab.testGetValue()) assertEquals(1, TopSites.openInNewTab.testGetValue()!!.size) - assertNull(TopSites.openInNewTab.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openInNewTab.testGetValue()!!.single().extra!!["source"]) assertNotNull(TopSites.openDefault.testGetValue()) assertEquals(1, TopSites.openDefault.testGetValue()!!.size) - assertNull(TopSites.openDefault.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openDefault.testGetValue()!!.single().extra!!["source"]) assertNotNull(TopSites.openGoogleSearchAttribution.testGetValue()) assertEquals(1, TopSites.openGoogleSearchAttribution.testGetValue()!!.size) - assertNull(TopSites.openGoogleSearchAttribution.testGetValue()!!.single().extra) + assertEquals( + "homepage", + TopSites.openGoogleSearchAttribution.testGetValue()!!.single().extra!!["source"], + ) verify { tabsUseCases.addTab.invoke( @@ -448,15 +477,18 @@ class DefaultTopSiteControllerTest { assertNotNull(TopSites.openInNewTab.testGetValue()) assertEquals(1, TopSites.openInNewTab.testGetValue()!!.size) - assertNull(TopSites.openInNewTab.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openInNewTab.testGetValue()!!.single().extra!!["source"]) assertNotNull(TopSites.openDefault.testGetValue()) assertEquals(1, TopSites.openDefault.testGetValue()!!.size) - assertNull(TopSites.openDefault.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openDefault.testGetValue()!!.single().extra!!["source"]) assertNotNull(TopSites.openGoogleSearchAttribution.testGetValue()) assertEquals(1, TopSites.openGoogleSearchAttribution.testGetValue()!!.size) - assertNull(TopSites.openGoogleSearchAttribution.testGetValue()!!.single().extra) + assertEquals( + "homepage", + TopSites.openGoogleSearchAttribution.testGetValue()!!.single().extra!!["source"], + ) verify { tabsUseCases.addTab.invoke( @@ -488,11 +520,14 @@ class DefaultTopSiteControllerTest { assertNotNull(TopSites.openDefault.testGetValue()) assertEquals(1, TopSites.openDefault.testGetValue()!!.size) - assertNull(TopSites.openDefault.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openDefault.testGetValue()!!.single().extra!!["source"]) assertNotNull(TopSites.openGoogleSearchAttribution.testGetValue()) assertEquals(1, TopSites.openGoogleSearchAttribution.testGetValue()!!.size) - assertNull(TopSites.openGoogleSearchAttribution.testGetValue()!!.single().extra) + assertEquals( + "homepage", + TopSites.openGoogleSearchAttribution.testGetValue()!!.single().extra!!["source"], + ) } @Test @@ -535,15 +570,18 @@ class DefaultTopSiteControllerTest { assertNotNull(TopSites.openInNewTab.testGetValue()) assertEquals(1, TopSites.openInNewTab.testGetValue()!!.size) - assertNull(TopSites.openInNewTab.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openInNewTab.testGetValue()!!.single().extra!!["source"]) assertNotNull(TopSites.openPinned.testGetValue()) assertEquals(1, TopSites.openPinned.testGetValue()!!.size) - assertNull(TopSites.openPinned.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openPinned.testGetValue()!!.single().extra!!["source"]) assertNotNull(TopSites.openGoogleSearchAttribution.testGetValue()) assertEquals(1, TopSites.openGoogleSearchAttribution.testGetValue()!!.size) - assertNull(TopSites.openGoogleSearchAttribution.testGetValue()!!.single().extra) + assertEquals( + "homepage", + TopSites.openGoogleSearchAttribution.testGetValue()!!.single().extra!!["source"], + ) verify { tabsUseCases.addTab.invoke( @@ -573,15 +611,18 @@ class DefaultTopSiteControllerTest { assertNotNull(TopSites.openInNewTab.testGetValue()) assertEquals(1, TopSites.openInNewTab.testGetValue()!!.size) - assertNull(TopSites.openInNewTab.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openInNewTab.testGetValue()!!.single().extra!!["source"]) assertNotNull(TopSites.openPinned.testGetValue()) assertEquals(1, TopSites.openPinned.testGetValue()!!.size) - assertNull(TopSites.openPinned.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openPinned.testGetValue()!!.single().extra!!["source"]) assertNotNull(TopSites.openGoogleSearchAttribution.testGetValue()) assertEquals(1, TopSites.openGoogleSearchAttribution.testGetValue()!!.size) - assertNull(TopSites.openGoogleSearchAttribution.testGetValue()!!.single().extra) + assertEquals( + "homepage", + TopSites.openGoogleSearchAttribution.testGetValue()!!.single().extra!!["source"], + ) verify { tabsUseCases.addTab.invoke( @@ -611,15 +652,18 @@ class DefaultTopSiteControllerTest { assertNotNull(TopSites.openInNewTab.testGetValue()) assertEquals(1, TopSites.openInNewTab.testGetValue()!!.size) - assertNull(TopSites.openInNewTab.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openInNewTab.testGetValue()!!.single().extra!!["source"]) assertNotNull(TopSites.openFrecency.testGetValue()) assertEquals(1, TopSites.openFrecency.testGetValue()!!.size) - assertNull(TopSites.openFrecency.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openFrecency.testGetValue()!!.single().extra!!["source"]) assertNotNull(TopSites.openGoogleSearchAttribution.testGetValue()) assertEquals(1, TopSites.openGoogleSearchAttribution.testGetValue()!!.size) - assertNull(TopSites.openGoogleSearchAttribution.testGetValue()!!.single().extra) + assertEquals( + "homepage", + TopSites.openGoogleSearchAttribution.testGetValue()!!.single().extra!!["source"], + ) verify { tabsUseCases.addTab.invoke( @@ -649,15 +693,18 @@ class DefaultTopSiteControllerTest { assertNotNull(TopSites.openInNewTab.testGetValue()) assertEquals(1, TopSites.openInNewTab.testGetValue()!!.size) - assertNull(TopSites.openInNewTab.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openInNewTab.testGetValue()!!.single().extra!!["source"]) assertNotNull(TopSites.openFrecency.testGetValue()) assertEquals(1, TopSites.openFrecency.testGetValue()!!.size) - assertNull(TopSites.openFrecency.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openFrecency.testGetValue()!!.single().extra!!["source"]) assertNotNull(TopSites.openGoogleSearchAttribution.testGetValue()) assertEquals(1, TopSites.openGoogleSearchAttribution.testGetValue()!!.size) - assertNull(TopSites.openGoogleSearchAttribution.testGetValue()!!.single().extra) + assertEquals( + "homepage", + TopSites.openGoogleSearchAttribution.testGetValue()!!.single().extra!!["source"], + ) verify { tabsUseCases.addTab.invoke( @@ -689,11 +736,11 @@ class DefaultTopSiteControllerTest { assertNotNull(TopSites.openInNewTab.testGetValue()) assertEquals(1, TopSites.openInNewTab.testGetValue()!!.size) - assertNull(TopSites.openInNewTab.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openInNewTab.testGetValue()!!.single().extra!!["source"]) assertNotNull(TopSites.openContileTopSite.testGetValue()) assertEquals(1, TopSites.openContileTopSite.testGetValue()!!.size) - assertNull(TopSites.openContileTopSite.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.openContileTopSite.testGetValue()!!.single().extra!!["source"]) verify { tabsUseCases.addTab.invoke( @@ -736,6 +783,48 @@ class DefaultTopSiteControllerTest { controller.handleSelectTopSite(topSite, position) + val event = TopSites.contileClick.testGetValue()!! + + assertEquals(1, event.size) + assertEquals("top_sites", event[0].category) + assertEquals("contile_click", event[0].name) + assertEquals("1", event[0].extra!!["position"]) + assertEquals("homepage", event[0].extra!!["source"]) + + job.join() + assertTrue(topSiteImpressionPinged) + } + + @Ignore("Bug 2016888 - passes on individual test run, fails when running entire app test suite.") + @Test + fun `GIVEN Ads client is enabled WHEN the provided top site is clicked THEN send a click callback request`() = runTest { + val controller = spyk(createController(this)) + val topSite = TopSite.Provided( + id = 3, + title = "Mozilla", + url = "https://mozilla.com", + clickUrl = "https://mozilla.com/click", + imageUrl = "https://test.com/image2.jpg", + impressionUrl = "https://mozilla.com/impression", + createdAt = 3, + ) + val position = 0 + + every { controller.getAvailableSearchEngines() } returns listOf(searchEngine) + + assertNull(TopSites.contileClick.testGetValue()) + + var topSiteImpressionPinged = false + val job = Pings.topsitesImpression.testBeforeNextSubmit { + assertEquals(3L, TopSites.contileTileId.testGetValue()) + assertEquals("mozilla", TopSites.contileAdvertiser.testGetValue()) + assertNull(TopSites.contileReportingUrl.testGetValue()) + + topSiteImpressionPinged = true + } + + controller.handleSelectTopSite(topSite, position) + coVerify { mozAdsUseCases.recordClickInteraction(clickUrl = topSite.clickUrl) } val event = TopSites.contileClick.testGetValue()!! @@ -744,7 +833,7 @@ class DefaultTopSiteControllerTest { assertEquals("top_sites", event[0].category) assertEquals("contile_click", event[0].name) assertEquals("1", event[0].extra!!["position"]) - assertEquals("newtab", event[0].extra!!["source"]) + assertEquals("homepage", event[0].extra!!["source"]) job.join() assertTrue(topSiteImpressionPinged) @@ -780,6 +869,48 @@ class DefaultTopSiteControllerTest { controller.handleTopSiteImpression(topSite, position) + val event = TopSites.contileImpression.testGetValue()!! + + assertEquals(1, event.size) + assertEquals("top_sites", event[0].category) + assertEquals("contile_impression", event[0].name) + assertEquals("1", event[0].extra!!["position"]) + assertEquals("homepage", event[0].extra!!["source"]) + + job.join() + assertTrue(topSiteImpressionSubmitted) + } + + @Ignore("Bug 2016888 - passes on individual test run, fails when running entire app test suite.") + @Test + fun `GIVEN Ads client is enabled WHEN the provided top site is seen THEN send a impression callback request`() = runTest { + val controller = spyk(createController(this)) + val topSite = TopSite.Provided( + id = 3, + title = "Mozilla", + url = "https://mozilla.com", + clickUrl = "https://mozilla.com/click", + imageUrl = "https://test.com/image2.jpg", + impressionUrl = "https://mozilla.com/impression", + createdAt = 3, + ) + val position = 0 + + every { controller.getAvailableSearchEngines() } returns listOf(searchEngine) + + assertNull(TopSites.contileImpression.testGetValue()) + + var topSiteImpressionSubmitted = false + val job = Pings.topsitesImpression.testBeforeNextSubmit { + assertEquals(3L, TopSites.contileTileId.testGetValue()) + assertEquals("mozilla", TopSites.contileAdvertiser.testGetValue()) + assertNull(TopSites.contileReportingUrl.testGetValue()) + + topSiteImpressionSubmitted = true + } + + controller.handleTopSiteImpression(topSite, position) + coVerify { mozAdsUseCases.recordImpressionInteraction(impressionUrl = topSite.impressionUrl) } val event = TopSites.contileImpression.testGetValue()!! @@ -788,12 +919,58 @@ class DefaultTopSiteControllerTest { assertEquals("top_sites", event[0].category) assertEquals("contile_impression", event[0].name) assertEquals("1", event[0].extra!!["position"]) - assertEquals("newtab", event[0].extra!!["source"]) + assertEquals("homepage", event[0].extra!!["source"]) job.join() assertTrue(topSiteImpressionSubmitted) } + @Test + fun `GIVEN the shortcuts library source WHEN a provided top site is clicked THEN record contile_click with the shortcuts library source`() = runTest { + val controller = spyk(createController(this, source = TopSitesSource.SHORTCUTS_LIBRARY)) + val topSite = TopSite.Provided( + id = 3, + title = "Mozilla", + url = "https://mozilla.com", + clickUrl = "https://mozilla.com/click", + imageUrl = "https://test.com/image2.jpg", + impressionUrl = "https://mozilla.com/impression", + createdAt = 3, + ) + + every { controller.getAvailableSearchEngines() } returns listOf(searchEngine) + + assertNull(TopSites.contileClick.testGetValue()) + + controller.handleSelectTopSite(topSite, position = 0) + + val event = TopSites.contileClick.testGetValue()!! + assertEquals(1, event.size) + assertEquals("shortcuts_library", event[0].extra!!["source"]) + } + + @Test + fun `GIVEN the shortcuts library source WHEN a provided top site is seen THEN record contile_impression with the shortcuts library source`() = runTest { + val controller = spyk(createController(this, source = TopSitesSource.SHORTCUTS_LIBRARY)) + val topSite = TopSite.Provided( + id = 3, + title = "Mozilla", + url = "https://mozilla.com", + clickUrl = "https://mozilla.com/click", + imageUrl = "https://test.com/image2.jpg", + impressionUrl = "https://mozilla.com/impression", + createdAt = 3, + ) + + assertNull(TopSites.contileImpression.testGetValue()) + + controller.handleTopSiteImpression(topSite, position = 0) + + val event = TopSites.contileImpression.testGetValue()!! + assertEquals(1, event.size) + assertEquals("shortcuts_library", event[0].extra!!["source"]) + } + @Test fun `WHEN the default Google top site is removed THEN the correct metric is recorded`() = runTest { val controller = spyk(createController(this)) @@ -810,11 +987,14 @@ class DefaultTopSiteControllerTest { assertNotNull(TopSites.googleTopSiteRemoved.testGetValue()) assertEquals(1, TopSites.googleTopSiteRemoved.testGetValue()!!.size) - assertNull(TopSites.googleTopSiteRemoved.testGetValue()!!.single().extra) + assertEquals( + "homepage", + TopSites.googleTopSiteRemoved.testGetValue()!!.single().extra!!["source"], + ) assertNotNull(TopSites.remove.testGetValue()) assertEquals(1, TopSites.remove.testGetValue()!!.size) - assertNull(TopSites.remove.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.remove.testGetValue()!!.single().extra!!["source"]) } @Test @@ -877,7 +1057,7 @@ class DefaultTopSiteControllerTest { assertNotNull(TopSites.contileSettings.testGetValue()) assertEquals(1, TopSites.contileSettings.testGetValue()!!.size) - assertNull(TopSites.contileSettings.testGetValue()!!.single().extra) + assertEquals("homepage", TopSites.contileSettings.testGetValue()!!.single().extra!!["source"]) verify { navController.navigate(R.id.homeSettingsFragment) } } @@ -888,7 +1068,10 @@ class DefaultTopSiteControllerTest { assertNotNull(TopSites.contileSponsorsAndPrivacy.testGetValue()) assertEquals(1, TopSites.contileSponsorsAndPrivacy.testGetValue()!!.size) - assertNull(TopSites.contileSponsorsAndPrivacy.testGetValue()!!.single().extra) + assertEquals( + "homepage", + TopSites.contileSponsorsAndPrivacy.testGetValue()!!.single().extra!!["source"], + ) verify { navController.navigate(R.id.browserFragment) @@ -906,11 +1089,14 @@ class DefaultTopSiteControllerTest { every { id } returns R.id.shortcutsFragment } - createController(this).handleSponsorPrivacyClicked() + createController(this, source = TopSitesSource.SHORTCUTS_LIBRARY).handleSponsorPrivacyClicked() assertNotNull(TopSites.contileSponsorsAndPrivacy.testGetValue()) assertEquals(1, TopSites.contileSponsorsAndPrivacy.testGetValue()!!.size) - assertNull(TopSites.contileSponsorsAndPrivacy.testGetValue()!!.single().extra) + assertEquals( + "shortcuts_library", + TopSites.contileSponsorsAndPrivacy.testGetValue()!!.single().extra!!["source"], + ) verify { navController.navigate(ShortcutsFragmentDirections.actionShortcutsFragmentToBrowserFragment()) @@ -939,6 +1125,7 @@ class DefaultTopSiteControllerTest { createController(this).handleTopSiteLongClicked(topSite) assertEquals(topSite.type, TopSites.longPress.testGetValue()!!.single().extra!!["type"]) + assertEquals("homepage", TopSites.longPress.testGetValue()!!.single().extra!!["source"]) } @Test @@ -956,7 +1143,10 @@ class DefaultTopSiteControllerTest { assertNotNull(TopSites.openContileInPrivateTab.testGetValue()) assertEquals(1, TopSites.openContileInPrivateTab.testGetValue()!!.size) - assertNull(TopSites.openContileInPrivateTab.testGetValue()!!.single().extra) + assertEquals( + "homepage", + TopSites.openContileInPrivateTab.testGetValue()!!.single().extra!!["source"], + ) verify { navController.navigate(R.id.browserFragment) @@ -983,11 +1173,14 @@ class DefaultTopSiteControllerTest { impressionUrl = "", createdAt = 0, ) - createController(this).handleOpenInPrivateTabClicked(topSite) + createController(this, source = TopSitesSource.SHORTCUTS_LIBRARY).handleOpenInPrivateTabClicked(topSite) assertNotNull(TopSites.openContileInPrivateTab.testGetValue()) assertEquals(1, TopSites.openContileInPrivateTab.testGetValue()!!.size) - assertNull(TopSites.openContileInPrivateTab.testGetValue()!!.single().extra) + assertEquals( + "shortcuts_library", + TopSites.openContileInPrivateTab.testGetValue()!!.single().extra!!["source"], + ) verify { navController.navigate(ShortcutsFragmentDirections.actionShortcutsFragmentToBrowserFragment()) @@ -1016,7 +1209,10 @@ class DefaultTopSiteControllerTest { assertNotNull(TopSites.openContileInPrivateTab.testGetValue()) assertEquals(1, TopSites.openContileInPrivateTab.testGetValue()!!.size) - assertNull(TopSites.openContileInPrivateTab.testGetValue()!!.single().extra) + assertEquals( + "homepage", + TopSites.openContileInPrivateTab.testGetValue()!!.single().extra!!["source"], + ) verify { navController.navigate(R.id.browserFragment) @@ -1058,7 +1254,7 @@ class DefaultTopSiteControllerTest { assertNotNull(TopSites.openInPrivateTab.testGetValue()) assertEquals(3, TopSites.openInPrivateTab.testGetValue()!!.size) for (event in TopSites.openInPrivateTab.testGetValue()!!) { - assertNull(event.extra) + assertEquals("homepage", event.extra!!["source"]) } } @@ -1069,7 +1265,10 @@ class DefaultTopSiteControllerTest { assertNotNull(ShortcutsLibrary.viewed.testGetValue()) } - private fun createController(scope: CoroutineScope): DefaultTopSiteController = + private fun createController( + scope: CoroutineScope, + source: TopSitesSource = TopSitesSource.HOMEPAGE, + ): DefaultTopSiteController = DefaultTopSiteController( activityRef = WeakReference(activity), navControllerRef = WeakReference(navController), @@ -1081,5 +1280,6 @@ class DefaultTopSiteControllerTest { topSitesUseCases = topSitesUseCases, mozAdsUseCases = mozAdsUseCases, viewLifecycleScope = scope, + source = source, ) }