Refactor photo embed analytics to post:photoEmbed:* namespace (#10784)

Co-authored-by: Eric Bailey <git@esb.lol>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Spence Pope
2026-06-08 12:44:59 -04:00
committed by GitHub
parent c4f3a2cb89
commit 0090285fc0
13 changed files with 164 additions and 26 deletions
+1
View File
@@ -255,6 +255,7 @@ function PostInner({
embed={post.embed}
moderation={moderation}
viewContext={PostEmbedViewContext.Feed}
post={post}
/>
</View>
) : null}
+49 -11
View File
@@ -13,6 +13,8 @@ import {
import {
type AppBskyActorDefs,
AppBskyEmbedExternal,
AppBskyEmbedGallery,
AppBskyEmbedImages,
AppBskyEmbedVideo,
type AppBskyFeedDefs,
} from '@atproto/api'
@@ -907,7 +909,9 @@ let PostFeed = ({
const seenActorWithStatusRef = useRef<Set<string>>(new Set())
const seenPostUrisRef = useRef<Set<string>>(new Set())
const seenStandardSiteUrisRef = useRef<Set<string>>(new Set())
// Tracks every post we've seen so we can fire per-post events exactly once,
// regardless of the post's position within its slice.
const seenPerPostUrisRef = useRef<Set<string>>(new Set())
// Helper to calculate position in feed (count only root posts, not interstitials or thread replies)
const getPostPosition = useNonReactiveCallback(
@@ -940,6 +944,48 @@ let PostFeed = ({
(item: FeedRow) => {
feedFeedback.onItemSeen(item)
// Events that should fire exactly once for every new post, regardless of
// its position within a slice or video grid row.
const onPostSeen = (post: AppBskyFeedDefs.PostView) => {
if (seenPerPostUrisRef.current.has(post.uri)) return
seenPerPostUrisRef.current.add(post.uri)
// Standard site embed view tracking
if (
AppBskyEmbedExternal.isView(post.embed) &&
isStandardSiteEmbed(post.embed.external)
) {
ax.metric('embed:standardSite:view', {url: post.embed.external.uri})
}
// Photo embed impression tracking
if (
AppBskyEmbedImages.isView(post.embed) ||
AppBskyEmbedGallery.isView(post.embed)
) {
const totalImages = AppBskyEmbedGallery.isView(post.embed)
? post.embed.items.filter(AppBskyEmbedGallery.isViewImage).length
: post.embed.images.length
const useExpandedLayout = AppBskyEmbedGallery.isView(post.embed)
? totalImages > 4
: ax.features.enabled(ax.features.PostGalleryEmbedEnable)
const layout =
totalImages === 1
? 'single'
: useExpandedLayout
? 'carousel'
: 'grid'
ax.metric('post:photoEmbed:impression', {
layout,
totalImages,
postUri: post.uri,
postAuthorDid: post.author.did,
feedDescriptor: feedFeedback.feedDescriptor || feed,
})
}
}
// Track post:view events
if (item.type === 'sliceItem') {
const slice = item.slice
@@ -947,6 +993,8 @@ let PostFeed = ({
const postItem = slice.items[indexInSlice]
const post = postItem.post
onPostSeen(post)
// Only track the root post of each slice (index 0) to avoid double-counting thread items
if (indexInSlice === 0 && !seenPostUrisRef.current.has(post.uri)) {
seenPostUrisRef.current.add(post.uri)
@@ -977,16 +1025,6 @@ let PostFeed = ({
})
}
}
// Standard site embed view tracking
if (
AppBskyEmbedExternal.isView(post.embed) &&
isStandardSiteEmbed(post.embed.external) &&
!seenStandardSiteUrisRef.current.has(post.embed.external.uri)
) {
seenStandardSiteUrisRef.current.add(post.embed.external.uri)
ax.metric('embed:standardSite:view', {url: post.embed.external.uri})
}
} else if (item.type === 'videoGridRow') {
// Track each video in the grid row
for (let i = 0; i < item.items.length; i++) {
+5
View File
@@ -429,6 +429,7 @@ let FeedItemInner = ({
onOpenEmbed={onOpenEmbed}
post={post}
additionalPostAlerts={additionalPostAlerts}
feedDescriptor={feedDescriptor}
/>
<PostControls
post={post}
@@ -460,6 +461,7 @@ let PostContent = ({
postAuthor,
onOpenEmbed,
additionalPostAlerts,
feedDescriptor,
}: {
moderation: ModerationDecision
richText: RichTextAPI
@@ -468,6 +470,7 @@ let PostContent = ({
onOpenEmbed: () => void
post: AppBskyFeedDefs.PostView
additionalPostAlerts?: AppModerationCause[]
feedDescriptor?: string
}): React.ReactNode => {
const [limitLines, setLimitLines] = useState(
() => countLines(richText.text) >= MAX_POST_LINES,
@@ -528,6 +531,8 @@ let PostContent = ({
moderation={moderation}
onOpen={onOpenEmbed}
viewContext={PostEmbedViewContext.Feed}
post={post}
feedDescriptor={feedDescriptor}
/>
</View>
) : null}