Fire impressions on vis change
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import {useEffect, useRef} from 'react'
|
import {useRef} from 'react'
|
||||||
import {InteractionManager, View} from 'react-native'
|
import {InteractionManager, View} from 'react-native'
|
||||||
import {type AnimatedRef} from 'react-native-reanimated'
|
import {type AnimatedRef} from 'react-native-reanimated'
|
||||||
import {Image} from 'expo-image'
|
import {Image} from 'expo-image'
|
||||||
@@ -57,20 +57,6 @@ export function ImageEmbed({
|
|||||||
? {layout, ...postContext}
|
? {layout, ...postContext}
|
||||||
: undefined
|
: 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
|
// 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.
|
// ref + dims that a tap would — keeps the lightbox's return animation intact.
|
||||||
const singleContainerRef = useRef<AnimatedRef<any> | null>(null)
|
const singleContainerRef = useRef<AnimatedRef<any> | null>(null)
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import {
|
|||||||
import {
|
import {
|
||||||
type AppBskyActorDefs,
|
type AppBskyActorDefs,
|
||||||
AppBskyEmbedExternal,
|
AppBskyEmbedExternal,
|
||||||
|
AppBskyEmbedGallery,
|
||||||
|
AppBskyEmbedImages,
|
||||||
AppBskyEmbedVideo,
|
AppBskyEmbedVideo,
|
||||||
type AppBskyFeedDefs,
|
type AppBskyFeedDefs,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
@@ -907,7 +909,9 @@ let PostFeed = ({
|
|||||||
|
|
||||||
const seenActorWithStatusRef = useRef<Set<string>>(new Set())
|
const seenActorWithStatusRef = useRef<Set<string>>(new Set())
|
||||||
const seenPostUrisRef = 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)
|
// Helper to calculate position in feed (count only root posts, not interstitials or thread replies)
|
||||||
const getPostPosition = useNonReactiveCallback(
|
const getPostPosition = useNonReactiveCallback(
|
||||||
@@ -940,6 +944,48 @@ let PostFeed = ({
|
|||||||
(item: FeedRow) => {
|
(item: FeedRow) => {
|
||||||
feedFeedback.onItemSeen(item)
|
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
|
// Track post:view events
|
||||||
if (item.type === 'sliceItem') {
|
if (item.type === 'sliceItem') {
|
||||||
const slice = item.slice
|
const slice = item.slice
|
||||||
@@ -947,6 +993,8 @@ let PostFeed = ({
|
|||||||
const postItem = slice.items[indexInSlice]
|
const postItem = slice.items[indexInSlice]
|
||||||
const post = postItem.post
|
const post = postItem.post
|
||||||
|
|
||||||
|
onPostSeen(post)
|
||||||
|
|
||||||
// Only track the root post of each slice (index 0) to avoid double-counting thread items
|
// Only track the root post of each slice (index 0) to avoid double-counting thread items
|
||||||
if (indexInSlice === 0 && !seenPostUrisRef.current.has(post.uri)) {
|
if (indexInSlice === 0 && !seenPostUrisRef.current.has(post.uri)) {
|
||||||
seenPostUrisRef.current.add(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') {
|
} else if (item.type === 'videoGridRow') {
|
||||||
// Track each video in the grid row
|
// Track each video in the grid row
|
||||||
for (let i = 0; i < item.items.length; i++) {
|
for (let i = 0; i < item.items.length; i++) {
|
||||||
|
|||||||
Reference in New Issue
Block a user