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:
@@ -255,6 +255,7 @@ function PostInner({
|
||||
embed={post.embed}
|
||||
moderation={moderation}
|
||||
viewContext={PostEmbedViewContext.Feed}
|
||||
post={post}
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user