Fire impressions on vis change

This commit is contained in:
Eric Bailey
2026-06-08 10:50:06 -05:00
parent c22cc90565
commit 5c551e1ad9
2 changed files with 50 additions and 26 deletions
+1 -15
View File
@@ -1,4 +1,4 @@
import {useEffect, useRef} from 'react'
import {useRef} from 'react'
import {InteractionManager, View} from 'react-native'
import {type AnimatedRef} from 'react-native-reanimated'
import {Image} from 'expo-image'
@@ -57,20 +57,6 @@ export function ImageEmbed({
? {layout, ...postContext}
: undefined
// Impression: one per mount of a post photo embed. Covers all three layouts
// identically so the opens/impressions CTR is unbiased by layout.
useEffect(() => {
if (images.length > 0 && postContext) {
ax.metric('post:photoEmbed:impression', {
layout,
totalImages: images.length,
...postContext,
})
}
// Fire once per mount; intentionally not reactive to post context changes.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
// Captured from AutoSizedImage so the peek-commit handler can reuse the same
// ref + dims that a tap would — keeps the lightbox's return animation intact.
const singleContainerRef = useRef<AnimatedRef<any> | null>(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,
uri: post.uri,
authorDid: 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++) {