Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions mobile/android/fenix/app/metrics.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9756,6 +9756,12 @@ pocket:
type: event
description: |
The Pocket recommended stories are shown on the home screen.
extra_keys:
source:
type: string
description: |
Where the stories impression was recorded.
One of "homepage" or "stories_screen".
bugs:
- https://github.com/mozilla-mobile/fenix/issues/21593
data_reviews:
Expand Down Expand Up @@ -9784,6 +9790,11 @@ pocket:
description: |
Position of the clicked story in the list shown.
Uses the [row x column] matrix notation.
source:
type: string
description: |
Where the clicked story was shown.
One of "homepage" or "stories_screen".
bugs:
- https://github.com/mozilla-mobile/fenix/issues/21593
data_reviews:
Expand Down Expand Up @@ -15379,6 +15390,11 @@ home.content.article:
topic: &topic
description: The topic of the recommendation. Like "entertainment".
type: string
source: &source_pocket
description: >
The surface where the article was shown.
One of "homepage" or "stories_screen".
type: string
send_in_pings:
- home

Expand All @@ -15404,6 +15420,7 @@ home.content.article:
received_rank: *received_rank
recommended_at: *recommended_at
topic: *topic
source: *source_pocket
send_in_pings:
- home

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import org.mozilla.fenix.home.bookmarks.Bookmark
import org.mozilla.fenix.home.pocket.PocketImpression
import org.mozilla.fenix.home.pocket.PocketRecommendedStoriesCategory
import org.mozilla.fenix.home.pocket.PocketRecommendedStoriesSelectedCategory
import org.mozilla.fenix.home.pocket.controller.StoriesImpressionSource
import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTab
import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTabState
import org.mozilla.fenix.home.recenttabs.RecentTab
Expand Down Expand Up @@ -598,10 +599,12 @@ sealed class AppAction : Action {
*
* @property recommendation The [ContentRecommendation] that was clicked.
* @property position The position (0-index) of the [ContentRecommendation].
* @property source The surface where the clicked recommendation was shown.
*/
data class ContentRecommendationClicked(
val recommendation: ContentRecommendation,
val position: Int,
val source: StoriesImpressionSource,
) : ContentRecommendationsAction()

/**
Expand All @@ -621,9 +624,12 @@ sealed class AppAction : Action {
*
* @property impressions A list of [PocketImpression]s detailing the story shown and
* their respective position.
* @property source The surface where the stories were shown.
*/
data class PocketStoriesShown(val impressions: List<PocketImpression>) :
ContentRecommendationsAction()
data class PocketStoriesShown(
val impressions: List<PocketImpression>,
val source: StoriesImpressionSource,
) : ContentRecommendationsAction()

/**
* Cleans all in-memory data about Pocket stories and categories.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import org.mozilla.fenix.home.collections.CollectionsState
import org.mozilla.fenix.home.interactor.HomepageInteractor
import org.mozilla.fenix.home.pocket.PocketRecommendedStoriesCategory
import org.mozilla.fenix.home.pocket.PocketState
import org.mozilla.fenix.home.pocket.controller.StoriesImpressionSource
import org.mozilla.fenix.home.pocket.interactor.PocketStoriesInteractor
import org.mozilla.fenix.home.privatebrowsing.interactor.PrivateBrowsingInteractor
import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTab
Expand Down Expand Up @@ -140,13 +141,17 @@ internal object FakeHomepagePreview {
storyPosition: Triple<Int, Int, Int>,
) { /* no op */ }

override fun onStoriesShown(storiesShown: List<PocketStory>) { /* no op */ }
override fun onStoriesShown(
storiesShown: List<PocketStory>,
source: StoriesImpressionSource,
) { /* no op */ }

override fun onCategoryClicked(categoryClicked: PocketRecommendedStoriesCategory) { /* no op */ }

override fun onStoryClicked(
storyClicked: PocketStory,
storyPosition: Triple<Int, Int, Int>,
source: StoriesImpressionSource,
) { /* no op */ }

override fun onDiscoverMoreClicked() { /* no op */ }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class HomeTelemetryMiddleware : Middleware<AppState, AppAction> {
scheduledCorpusItemId = recommendation.scheduledCorpusItemId,
tileId = recommendation.tileId.toInt(),
topic = recommendation.topic,
source = action.source.sourceName,
),
)

Expand All @@ -60,6 +61,7 @@ class HomeTelemetryMiddleware : Middleware<AppState, AppAction> {
scheduledCorpusItemId = story.scheduledCorpusItemId,
tileId = story.tileId.toInt(),
topic = story.topic,
source = action.source.sourceName,
),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ import java.lang.ref.WeakReference

private const val POCKET_CATEGORIES_SELECTED_AT_A_TIME_COUNT = 8

/**
* The surface where a Pocket stories impression was recorded. Used as the `source` extra on the
* `pocket.home_recs_shown` Glean event.
*/
enum class StoriesImpressionSource(val sourceName: String) {
HOMEPAGE("homepage"),
STORIES_SCREEN("stories_screen"),
}

/**
* Contract for how all user interactions with the Pocket stories feature are to be handled.
*/
Expand All @@ -49,8 +58,9 @@ interface PocketStoriesController {
* Callback to decide what should happen as an effect of a new list of stories being shown.
*
* @param storiesShown the new list of [PocketStory]es shown to the user.
* @param source The surface where the stories were shown.
*/
fun handleStoriesShown(storiesShown: List<PocketStory>)
fun handleStoriesShown(storiesShown: List<PocketStory>, source: StoriesImpressionSource)

/**
* Callback allowing to handle a specific [PocketRecommendedStoriesCategory] being clicked by the user.
Expand All @@ -65,8 +75,13 @@ interface PocketStoriesController {
* @param storyClicked The just clicked [PocketStory].
* @param storyPosition `row x column x index` matrix representing the grid and index position
* of the clicked story.
* @param source The surface where the clicked story was shown.
*/
fun handleStoryClicked(storyClicked: PocketStory, storyPosition: Triple<Int, Int, Int>)
fun handleStoryClicked(
storyClicked: PocketStory,
storyPosition: Triple<Int, Int, Int>,
source: StoriesImpressionSource,
)

/**
* Callback for when the user clicks on the "Discover more" button.
Expand Down Expand Up @@ -114,6 +129,7 @@ internal class DefaultPocketStoriesController(
position = storyPosition.third,
),
),
source = StoriesImpressionSource.HOMEPAGE,
),
)

Expand All @@ -138,18 +154,24 @@ internal class DefaultPocketStoriesController(
}
}

override fun handleStoriesShown(storiesShown: List<PocketStory>) {
override fun handleStoriesShown(
storiesShown: List<PocketStory>,
source: StoriesImpressionSource,
) {
// Only report here the impressions for recommended stories.
// Sponsored stories use a different API for more accurate tracking.
appStore.dispatch(
ContentRecommendationsAction.PocketStoriesShown(
impressions = storiesShown
.filter { it is ContentRecommendation || it is PocketRecommendedStory }
.map { PocketImpression(story = it, position = storiesShown.indexOf(it)) },
source = source,
),
)

Pocket.homeRecsShown.record(NoExtras())
Pocket.homeRecsShown.record(
Pocket.HomeRecsShownExtra(source = source.sourceName),
)
}

override fun handleCategoryClick(categoryClicked: PocketRecommendedStoriesCategory) {
Expand Down Expand Up @@ -196,6 +218,7 @@ internal class DefaultPocketStoriesController(
override fun handleStoryClicked(
storyClicked: PocketStory,
storyPosition: Triple<Int, Int, Int>,
source: StoriesImpressionSource,
) {
val storyUrl = when (navController.currentDestination?.id) {
R.id.storiesFragment -> storyClicked.url.markAsOpenedFromStoriesScreen()
Expand All @@ -216,6 +239,7 @@ internal class DefaultPocketStoriesController(
Pocket.HomeRecsStoryClickedExtra(
position = "${storyPosition.first}x${storyPosition.second}",
timesShown = storyClicked.timesShown.inc().toString(),
source = source.sourceName,
),
)
}
Expand All @@ -225,6 +249,7 @@ internal class DefaultPocketStoriesController(
ContentRecommendationsAction.ContentRecommendationClicked(
recommendation = storyClicked,
position = storyPosition.third,
source = source,
),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import mozilla.components.service.pocket.PocketStory
import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory
import org.mozilla.fenix.home.pocket.PocketRecommendedStoriesCategory
import org.mozilla.fenix.home.pocket.controller.PocketStoriesController
import org.mozilla.fenix.home.pocket.controller.StoriesImpressionSource

/**
* Contract for all possible user interactions with the Pocket recommended stories feature.
Expand All @@ -26,8 +27,9 @@ interface PocketStoriesInteractor {
* Callback for then new stories are shown to the user.
*
* @param storiesShown The new list of [PocketRecommendedStory]es shown to the user.
* @param source The surface where the stories were shown.
*/
fun onStoriesShown(storiesShown: List<PocketStory>)
fun onStoriesShown(storiesShown: List<PocketStory>, source: StoriesImpressionSource)

/**
* Callback for when the user clicks a specific category.
Expand All @@ -42,8 +44,13 @@ interface PocketStoriesInteractor {
* @param storyClicked The just clicked [PocketStory].
* @param storyPosition `row x column x index` matrix representing the grid and index position
* of the clicked story.
* @param source The surface where the clicked story was shown.
*/
fun onStoryClicked(storyClicked: PocketStory, storyPosition: Triple<Int, Int, Int>)
fun onStoryClicked(
storyClicked: PocketStory,
storyPosition: Triple<Int, Int, Int>,
source: StoriesImpressionSource,
)

/**
* Callback when an user clicks on the "Discover more" nutton for stories on the homepage.
Expand All @@ -66,16 +73,20 @@ class DefaultPocketStoriesInteractor(
controller.handleStoryShown(storyShown, storyPosition)
}

override fun onStoriesShown(storiesShown: List<PocketStory>) {
controller.handleStoriesShown(storiesShown)
override fun onStoriesShown(storiesShown: List<PocketStory>, source: StoriesImpressionSource) {
controller.handleStoriesShown(storiesShown, source)
}

override fun onCategoryClicked(categoryClicked: PocketRecommendedStoriesCategory) {
controller.handleCategoryClick(categoryClicked)
}

override fun onStoryClicked(storyClicked: PocketStory, storyPosition: Triple<Int, Int, Int>) {
controller.handleStoryClicked(storyClicked, storyPosition)
override fun onStoryClicked(
storyClicked: PocketStory,
storyPosition: Triple<Int, Int, Int>,
source: StoriesImpressionSource,
) {
controller.handleStoryClicked(storyClicked, storyPosition, source)
}

override fun onDiscoverMoreClicked() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.compose.home.HomeSectionHeader
import org.mozilla.fenix.home.fake.FakeHomepagePreview
import org.mozilla.fenix.home.pocket.PocketState
import org.mozilla.fenix.home.pocket.controller.StoriesImpressionSource
import org.mozilla.fenix.home.pocket.interactor.PocketStoriesInteractor
import org.mozilla.fenix.theme.FirefoxTheme
import org.mozilla.fenix.wallpapers.WallpaperState
Expand All @@ -45,7 +46,10 @@ fun PocketSection(
// We should report back when a certain story is actually being displayed.
// Cannot do it reliably so for now we'll just mass report everything as being displayed.
state.stories.let {
interactor.onStoriesShown(storiesShown = it)
interactor.onStoriesShown(
storiesShown = it,
source = StoriesImpressionSource.HOMEPAGE,
)
}
}

Expand All @@ -65,7 +69,9 @@ fun PocketSection(
contentPadding = horizontalPadding,
backgroundColor = cardBackgroundColor,
onStoryShown = interactor::onStoryShown,
onStoryClicked = interactor::onStoryClicked,
onStoryClicked = { story, position ->
interactor.onStoryClicked(story, position, StoriesImpressionSource.HOMEPAGE)
},
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import mozilla.components.compose.base.utils.BackInvokedHandler
import org.mozilla.fenix.R
import org.mozilla.fenix.components.appstate.recommendations.ContentRecommendationsState
import org.mozilla.fenix.home.fake.FakeHomepagePreview
import org.mozilla.fenix.home.pocket.controller.StoriesImpressionSource
import org.mozilla.fenix.home.pocket.interactor.PocketStoriesInteractor
import org.mozilla.fenix.home.ui.LeftChevronPillButton
import org.mozilla.fenix.theme.FirefoxTheme
Expand All @@ -54,6 +55,15 @@ fun StoriesScreen(
interactor.onDiscoverMoreScreenViewed()
}

// We should report back when a certain story is actually being displayed.
// Cannot do it reliably so for now we'll just mass report everything as being displayed.
LaunchedEffect(state.pocketStories) {
interactor.onStoriesShown(
storiesShown = state.pocketStories,
source = StoriesImpressionSource.STORIES_SCREEN,
)
}

BackInvokedHandler {
onNavigationIconClick()
}
Expand Down Expand Up @@ -155,7 +165,13 @@ private fun Stories(
itemsIndexed(state.pocketStories) { index, story ->
StoryCard(
story = story,
onClick = interactor::onStoryClicked,
onClick = { clickedStory, position ->
interactor.onStoryClicked(
clickedStory,
position,
StoriesImpressionSource.STORIES_SCREEN,
)
},
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import org.mozilla.fenix.home.logo.LogoController
import org.mozilla.fenix.home.logo.TrackingProtectionController
import org.mozilla.fenix.home.pocket.PocketRecommendedStoriesCategory
import org.mozilla.fenix.home.pocket.controller.PocketStoriesController
import org.mozilla.fenix.home.pocket.controller.StoriesImpressionSource
import org.mozilla.fenix.home.privatebrowsing.controller.PrivateBrowsingController
import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTab
import org.mozilla.fenix.home.recentsyncedtabs.controller.RecentSyncedTabController
Expand Down Expand Up @@ -381,16 +382,20 @@ class SessionControlInteractor(
pocketStoriesController.handleStoryShown(storyShown, storyPosition)
}

override fun onStoriesShown(storiesShown: List<PocketStory>) {
pocketStoriesController.handleStoriesShown(storiesShown)
override fun onStoriesShown(storiesShown: List<PocketStory>, source: StoriesImpressionSource) {
pocketStoriesController.handleStoriesShown(storiesShown, source)
}

override fun onCategoryClicked(categoryClicked: PocketRecommendedStoriesCategory) {
pocketStoriesController.handleCategoryClick(categoryClicked)
}

override fun onStoryClicked(storyClicked: PocketStory, storyPosition: Triple<Int, Int, Int>) {
pocketStoriesController.handleStoryClicked(storyClicked, storyPosition)
override fun onStoryClicked(
storyClicked: PocketStory,
storyPosition: Triple<Int, Int, Int>,
source: StoriesImpressionSource,
) {
pocketStoriesController.handleStoryClicked(storyClicked, storyPosition, source)
}

override fun onDiscoverMoreClicked() {
Expand Down
Loading