diff --git a/app/metrics.yaml b/app/metrics.yaml index d3757f7e283b..f3df51f04ba9 100644 --- a/app/metrics.yaml +++ b/app/metrics.yaml @@ -5864,6 +5864,26 @@ pocket: metadata: tags: - PocketIntegration + spoc_clicked: + type: text + description: | + Shim data of the just clicked Pocket sponsored story. + The shim is a unique base64 string identifying each story and + type of user interaction. + bugs: + - https://github.com/mozilla-mobile/fenix/issues/27549 + data_reviews: + - ?????? + data_sensitivity: + - web_activity + notification_emails: + - android-probes@mozilla.com + expires: 119 + send_in_pings: + - spoc + metadata: + tags: + - PocketIntegration home_recs_spoc_shown: type: event description: | @@ -5892,6 +5912,27 @@ pocket: metadata: tags: - PocketIntegration + spoc_shown: + type: text + description: | + Shim data of the shown Pocket sponsored. + The shim is a unique base64 string identifying each story and + type of user interaction. + For this to be recorded the story should be >50% visible. + bugs: + - https://github.com/mozilla-mobile/fenix/issues/27549 + data_reviews: + - ????? + data_sensitivity: + - web_activity + notification_emails: + - android-probes@mozilla.com + expires: 119 + send_in_pings: + - spoc + metadata: + tags: + - PocketIntegration home_recs_category_clicked: type: event description: | diff --git a/app/pings.yaml b/app/pings.yaml index ef205112c9a6..acbb549100fa 100644 --- a/app/pings.yaml +++ b/app/pings.yaml @@ -43,3 +43,14 @@ topsites-impression: - https://github.com/mozilla-mobile/fenix/pull/23945 notification_emails: - android-probes@mozilla.com + +spoc: + description: | + Contains data about user's interactions with Pocket sponsored stories. + include_client_id: false + bugs: + - https://github.com/mozilla-mobile/fenix/issues/27549 + data_reviews: + - ??? + notification_emails: + - android-probes@mozilla.com diff --git a/app/src/main/java/org/mozilla/fenix/home/pocket/PocketStoriesController.kt b/app/src/main/java/org/mozilla/fenix/home/pocket/PocketStoriesController.kt index 0bb1618fd3f5..d010e2f6e20d 100644 --- a/app/src/main/java/org/mozilla/fenix/home/pocket/PocketStoriesController.kt +++ b/app/src/main/java/org/mozilla/fenix/home/pocket/PocketStoriesController.kt @@ -12,6 +12,7 @@ import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory import mozilla.components.service.pocket.PocketStory.PocketSponsoredStory import mozilla.components.service.pocket.ext.getCurrentFlightImpressions import org.mozilla.fenix.BrowserDirection +import org.mozilla.fenix.GleanMetrics.Pings import org.mozilla.fenix.GleanMetrics.Pocket import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R @@ -93,6 +94,8 @@ internal class DefaultPocketStoriesController( timesShown = storyShown.getCurrentFlightImpressions().size.inc().toString(), ), ) + Pocket.spocShown.set(storyShown.shim.impression) + Pings.spoc.submit() } else -> { // no-op @@ -169,6 +172,8 @@ internal class DefaultPocketStoriesController( timesShown = storyClicked.getCurrentFlightImpressions().size.inc().toString(), ), ) + Pocket.spocClicked.set(storyClicked.shim.click) + Pings.spoc.submit() } } } diff --git a/app/src/test/java/org/mozilla/fenix/home/pocket/DefaultPocketStoriesControllerTest.kt b/app/src/test/java/org/mozilla/fenix/home/pocket/DefaultPocketStoriesControllerTest.kt index fd6acd75dd5c..dde790cab784 100644 --- a/app/src/test/java/org/mozilla/fenix/home/pocket/DefaultPocketStoriesControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/home/pocket/DefaultPocketStoriesControllerTest.kt @@ -7,12 +7,14 @@ package org.mozilla.fenix.home.pocket import androidx.navigation.NavController import io.mockk.every import io.mockk.mockk +import io.mockk.mockkStatic import io.mockk.spyk import io.mockk.verify import io.mockk.verifyOrder import mozilla.components.service.pocket.PocketStory import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory import mozilla.components.service.pocket.PocketStory.PocketSponsoredStory +import mozilla.components.service.pocket.ext.getCurrentFlightImpressions import mozilla.components.support.test.robolectric.testContext import mozilla.telemetry.glean.testing.GleanTestRule import org.junit.Assert.assertEquals @@ -23,6 +25,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mozilla.fenix.BrowserDirection +import org.mozilla.fenix.GleanMetrics.Pings import org.mozilla.fenix.GleanMetrics.Pocket import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R @@ -173,13 +176,30 @@ class DefaultPocketStoriesControllerTest { fun `WHEN a new sponsored story is shown THEN update the State and record telemetry`() { val store = spyk(AppStore()) val controller = DefaultPocketStoriesController(mockk(), store, mockk()) - val storyShown: PocketSponsoredStory = mockk(relaxed = true) - val storyGridLocation = 1 to 2 - - controller.handleStoryShown(storyShown, storyGridLocation) - - verify { store.dispatch(AppAction.PocketStoriesShown(listOf(storyShown))) } - assertNotNull(Pocket.homeRecsSpocShown.testGetValue()) + val storyShown: PocketSponsoredStory = mockk { + every { shim.click } returns "testClickShim" + every { shim.impression } returns "testImpressionShim" + } + var wasPingSent = false + mockkStatic("mozilla.components.service.pocket.ext.PocketStoryKt") { + // Simulate that the story was already shown 3 times. + every { storyShown.getCurrentFlightImpressions() } returns listOf(2L, 3L, 7L) + // Test that the spoc ping is immediately sent with the needed data. + Pings.spoc.testBeforeNextSubmit { + assertEquals(storyShown.shim.impression, Pocket.spocShown.testGetValue()) + wasPingSent = true + } + + controller.handleStoryShown(storyShown, 1 to 2) + + verify { store.dispatch(AppAction.PocketStoriesShown(listOf(storyShown))) } + assertNotNull(Pocket.homeRecsSpocShown.testGetValue()) + assertEquals(1, Pocket.homeRecsSpocShown.testGetValue()!!.size) + val data = Pocket.homeRecsSpocShown.testGetValue()!!.single().extra + assertEquals("1x2", data?.entries?.first { it.key == "position" }?.value) + assertEquals("4", data?.entries?.first { it.key == "times_shown" }?.value) + assertTrue(wasPingSent) + } } @Test @@ -227,24 +247,42 @@ class DefaultPocketStoriesControllerTest { @Test fun `WHEN a sponsored story is clicked THEN open that story's url using HomeActivity and record telemetry`() { - val story = PocketSponsoredStory( + val storyClicked = PocketSponsoredStory( id = 7, title = "", url = "testLink", imageUrl = "", sponsor = "", - shim = mockk(), + shim = mockk { + every { click } returns "testClickShim" + every { impression } returns "testImpressionShim" + }, priority = 3, caps = mockk(relaxed = true), ) val homeActivity: HomeActivity = mockk(relaxed = true) val controller = DefaultPocketStoriesController(homeActivity, mockk(), mockk(relaxed = true)) + var wasPingSent = false assertNull(Pocket.homeRecsSpocClicked.testGetValue()) - - controller.handleStoryClicked(story, 1 to 2) - - verify { homeActivity.openToBrowserAndLoad(story.url, true, BrowserDirection.FromHome) } - assertNull(Pocket.homeRecsStoryClicked.testGetValue()) + mockkStatic("mozilla.components.service.pocket.ext.PocketStoryKt") { + // Simulate that the story was already shown 2 times. + every { storyClicked.getCurrentFlightImpressions() } returns listOf(2L, 3L) + // Test that the spoc ping is immediately sent with the needed data. + Pings.spoc.testBeforeNextSubmit { + assertEquals(storyClicked.shim.click, Pocket.spocClicked.testGetValue()) + wasPingSent = true + } + + controller.handleStoryClicked(storyClicked, 2 to 3) + + verify { homeActivity.openToBrowserAndLoad(storyClicked.url, true, BrowserDirection.FromHome) } + assertNotNull(Pocket.homeRecsSpocClicked.testGetValue()) + assertEquals(1, Pocket.homeRecsSpocClicked.testGetValue()!!.size) + val data = Pocket.homeRecsSpocClicked.testGetValue()!!.single().extra + assertEquals("2x3", data?.entries?.first { it.key == "position" }?.value) + assertEquals("3", data?.entries?.first { it.key == "times_shown" }?.value) + assertTrue(wasPingSent) + } } @Test