From e595243b1f9e9835fbe1114ea48354a44a68aedc Mon Sep 17 00:00:00 2001 From: DS Boyce <260543580+ds-boyce@users.noreply.github.com> Date: Fri, 4 Sep 2026 16:11:20 -0700 Subject: [PATCH] Add more data to trending metrics (#11565) --- src/analytics/metrics/types.ts | 2 ++ src/components/TrendingTopics.tsx | 21 ++++++++++++++-- .../interstitials/FeedTrendingTopics.tsx | 10 +++++++- .../Search/modules/ExploreTrendingTopics.tsx | 4 +++- src/state/feed-feedback.tsx | 24 ++++++++++--------- .../shell/desktop/SidebarTrendingTopics.tsx | 2 ++ 6 files changed, 48 insertions(+), 15 deletions(-) diff --git a/src/analytics/metrics/types.ts b/src/analytics/metrics/types.ts index 42e9ec2124..69e55f74e9 100644 --- a/src/analytics/metrics/types.ts +++ b/src/analytics/metrics/types.ts @@ -774,12 +774,14 @@ export type Events = { } 'trendingTopic:seen': { context: 'sidebar' | 'interstitial' | 'explore' + feedUri?: string recId?: string rank: number feedSliceIndex?: number } 'trendingTopic:click': { context: 'sidebar' | 'interstitial' | 'explore' + feedUri?: string recId?: string rank: number feedSliceIndex?: number diff --git a/src/components/TrendingTopics.tsx b/src/components/TrendingTopics.tsx index b55420a3d4..b6866f2a62 100644 --- a/src/components/TrendingTopics.tsx +++ b/src/components/TrendingTopics.tsx @@ -5,7 +5,7 @@ import {Trans, useLingui} from '@lingui/react/macro' import {PressableScale} from '#/lib/custom-animations/PressableScale' import {useCallOnce} from '#/lib/once' // import {makeProfileLink} from '#/lib/routes/links' -// import {feedUriToHref} from '#/lib/strings/url-helpers' +import {makeRecordUri} from '#/lib/strings/url-helpers' import {atoms as a, native, useTheme} from '#/alf' import {Link as InternalLink, type LinkProps} from '#/components/Link' import * as Prompt from '#/components/Prompt' @@ -72,7 +72,8 @@ export function TrendingTopicLink({ recId?: string } & Omit) { const topic = useTopic(raw) - useTrendingTopicSeen(metricContext, rank, recId) + const feedUri = getTrendingTopicFeedUri(raw) + useTrendingTopicSeen(metricContext, feedUri, rank, recId) return ( { ax.metric('trendingTopic:seen', { context, + feedUri, rank, feedSliceIndex, recId, @@ -106,6 +109,20 @@ export function useTrendingTopicSeen( }, [trackSeen]) } +export function getTrendingTopicFeedUri( + topic: app.bsky.unspecced.defs.TrendView, +): string | undefined { + const match = topic.link.match(/^\/profile\/([^/]+)\/feed\/([^/?#]+)/) + + if (!match) return undefined + + return makeRecordUri( + decodeURIComponent(match[1]), + 'app.bsky.feed.generator', + decodeURIComponent(match[2]), + ) +} + type ParsedTrendingTopic = | { type: 'topic' | 'tag' | 'starter-pack' | 'unknown' diff --git a/src/components/interstitials/FeedTrendingTopics.tsx b/src/components/interstitials/FeedTrendingTopics.tsx index e4e81287b9..5bac08d329 100644 --- a/src/components/interstitials/FeedTrendingTopics.tsx +++ b/src/components/interstitials/FeedTrendingTopics.tsx @@ -27,6 +27,7 @@ import {Link} from '#/components/Link' import * as Prompt from '#/components/Prompt' import {SubtleHover} from '#/components/SubtleHover' import { + getTrendingTopicFeedUri, TrendingTopicsPrompt, useTrendingTopicSeen, } from '#/components/TrendingTopics' @@ -154,6 +155,7 @@ function Inner({feedSliceIndex}: {feedSliceIndex: number}) { onPress={() => { ax.metric('trendingTopic:click', { context: 'interstitial', + feedUri: getTrendingTopicFeedUri(trend), rank, feedSliceIndex, recId: trending.recId, @@ -195,7 +197,13 @@ function TrendRow({ const actors = useModerateTrendingActors(trend.actors) const formattedPostCount = formatCount(i18n, trend.postCount) - useTrendingTopicSeen('interstitial', rank, recId, feedSliceIndex) + useTrendingTopicSeen( + 'interstitial', + getTrendingTopicFeedUri(trend), + rank, + recId, + feedSliceIndex, + ) return ( { ax.metric('trendingTopic:click', { context: 'explore', + feedUri: getTrendingTopicFeedUri(trend), rank, recId: trending.recId, }) @@ -134,7 +136,7 @@ export function TrendRow({ const actors = useModerateTrendingActors(trend.actors) const formattedPostCount = formatCount(i18n, trend.postCount) - useTrendingTopicSeen('explore', rank, recId) + useTrendingTopicSeen('explore', getTrendingTopicFeedUri(trend), rank, recId) const description = useMemo(() => { if (!trend.description) return diff --git a/src/state/feed-feedback.tsx b/src/state/feed-feedback.tsx index bcaa8d03c5..eaeaa67d19 100644 --- a/src/state/feed-feedback.tsx +++ b/src/state/feed-feedback.tsx @@ -10,12 +10,8 @@ import {AppState, type AppStateStatus} from 'react-native' import {type AtUriString, type DidString} from '@atproto/syntax' import throttle from 'lodash.throttle' -import {PROD_FEEDS, STAGING_FEEDS} from '#/lib/constants' -import { - type FeedSourceFeedInfo, - type FeedSourceInfo, - isFeedSourceFeedInfo, -} from '#/state/queries/feed' +import {PROD_FEEDS, STAGING_FEEDS, TRENDING_DID} from '#/lib/constants' +import {type FeedSourceInfo, isFeedSourceFeedInfo} from '#/state/queries/feed' import { type FeedDescriptor, type FeedPostSliceItem, @@ -144,7 +140,7 @@ export function useFeedFeedback( const interactionsToSend = interactions.filter( interaction => interaction.event && - isInteractionAllowed(enabled, feed, interaction.event), + isInteractionAllowed(enabled, feed?.feedDescriptor, interaction.event), ) if (interactionsToSend.length === 0) { @@ -263,22 +259,28 @@ export function useFeedFeedbackContext() { // TODO // We will introduce a permissions framework for 3p feeds to // take advantage of the feed feedback API. Until that's in -// place, we're hardcoding it to the discover feed. +// place, we're hardcoding it to the discover and trending feeds. // -prf export function isDiscoverFeed(feed?: FeedDescriptor) { return !!feed && FEEDBACK_FEEDS.includes(feed) } +export function isTrendingFeed(feed?: FeedDescriptor) { + return !!feed && feed.startsWith(`feedgen|at://${TRENDING_DID}/`) +} + function isInteractionAllowed( enabled: boolean, - feed: FeedSourceFeedInfo | undefined, + feed: FeedDescriptor | undefined, interaction: app.bsky.feed.defs.Interaction['event'], ) { if (!enabled || !feed) { return false } - const isDiscover = isDiscoverFeed(feed.feedDescriptor) - return isDiscover ? true : THIRD_PARTY_ALLOWED_INTERACTIONS.has(interaction) + if (isDiscoverFeed(feed) || isTrendingFeed(feed)) { + return true + } + return THIRD_PARTY_ALLOWED_INTERACTIONS.has(interaction) } function toString(interaction: app.bsky.feed.defs.Interaction): string { diff --git a/src/view/shell/desktop/SidebarTrendingTopics.tsx b/src/view/shell/desktop/SidebarTrendingTopics.tsx index cd07fa83ac..1196def9cc 100644 --- a/src/view/shell/desktop/SidebarTrendingTopics.tsx +++ b/src/view/shell/desktop/SidebarTrendingTopics.tsx @@ -17,6 +17,7 @@ import {Trending3_Stroke2_Corner1_Rounded as TrendingIcon} from '#/components/ic import {Link} from '#/components/Link' import * as Prompt from '#/components/Prompt' import { + getTrendingTopicFeedUri, TrendingTopicLink, TrendingTopicsPrompt, } from '#/components/TrendingTopics' @@ -133,6 +134,7 @@ function Inner() { onPress={() => { ax.metric('trendingTopic:click', { context: 'sidebar', + feedUri: getTrendingTopicFeedUri(topic), rank, recId: trending.recId, })