Log additional context for post:* events (#11035)

This commit is contained in:
DS Boyce
2026-07-15 12:52:53 -07:00
committed by GitHub
parent a0c75944a3
commit 48fecae677
+102 -62
View File
@@ -14,7 +14,11 @@ import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {useOpenComposer} from '#/lib/hooks/useOpenComposer' import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking' import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking'
import {useFeedFeedback} from '#/state/feed-feedback' import {
FeedFeedbackProvider,
type StateContext as FeedFeedbackStateContext,
useFeedFeedback,
} from '#/state/feed-feedback'
import {type ThreadViewOption} from '#/state/queries/preferences/useThreadPreferences' import {type ThreadViewOption} from '#/state/queries/preferences/useThreadPreferences'
import { import {
PostThreadContextProvider, PostThreadContextProvider,
@@ -56,6 +60,9 @@ import {IS_NATIVE} from '#/env'
const PARENT_CHUNK_SIZE = IS_NATIVE ? 5 : 20 const PARENT_CHUNK_SIZE = IS_NATIVE ? 5 : 20
const CHILDREN_CHUNK_SIZE = 50 const CHILDREN_CHUNK_SIZE = 50
const analyticsOnlyOnItemSeen: FeedFeedbackStateContext['onItemSeen'] = () => {}
const analyticsOnlySendInteraction: FeedFeedbackStateContext['sendInteraction'] =
() => {}
export function PostThread({uri}: {uri: string}) { export function PostThread({uri}: {uri: string}) {
const ax = useAnalytics() const ax = useAnalytics()
@@ -569,68 +576,71 @@ export function PostThread({uri}: {uri: string}) {
onRetry={thread.actions.refetch} onRetry={thread.actions.refetch}
/> />
) : ( ) : (
<List <AnalyticsOnlyFeedFeedbackProvider
ref={listRef} feedDescriptor={feedFeedback.feedDescriptor}>
data={deferredSlices} <List
renderItem={renderItem} ref={listRef}
keyExtractor={keyExtractor} data={deferredSlices}
onContentSizeChange={platform({ renderItem={renderItem}
web: onContentSizeChangeWebOnly, keyExtractor={keyExtractor}
default: onContentSizeChangeNativeOnly, onContentSizeChange={platform({
})} web: onContentSizeChangeWebOnly,
onStartReached={onStartReached} default: onContentSizeChangeNativeOnly,
onEndReached={onEndReached} })}
onEndReachedThreshold={4} onStartReached={onStartReached}
onStartReachedThreshold={1} onEndReached={onEndReached}
onItemSeen={item => { onEndReachedThreshold={4}
// Track post:view for parent posts and replies (non-anchor posts) onStartReachedThreshold={1}
if (item.type === 'threadPost' && item.depth !== 0) { onItemSeen={item => {
trackThreadItemView(item.value.post) // Track post:view for parent posts and replies (non-anchor posts)
if (item.type === 'threadPost' && item.depth !== 0) {
trackThreadItemView(item.value.post)
}
}}
/**
* NATIVE ONLY
* {@link https://reactnative.dev/docs/scrollview#maintainvisiblecontentposition}
*/
maintainVisibleContentPosition={{minIndexForVisible: 0}}
desktopFixedHeight
sideBorders={false}
ListFooterComponent={
<ListFooter
/*
* On native, if `deferParents` is true, we need some extra buffer to
* account for the `on*ReachedThreshold` values.
*
* Otherwise, and on web, this value needs to be the height of
* the viewport _minus_ a sensible min-post height e.g. 200, so
* that there's enough scroll remaining to get the anchor post
* back to the top of the screen when handling scroll.
*/
height={platform({
web: defaultListFooterHeight,
default: deferParents
? windowHeight * 2
: defaultListFooterHeight,
})}
style={isTombstoneView ? {borderTopWidth: 0} : undefined}
/>
} }
}} initialNumToRender={initialNumToRender}
/** /**
* NATIVE ONLY * Default: 21
* {@link https://reactnative.dev/docs/scrollview#maintainvisiblecontentposition} *
*/ * Smaller for placeholder data so we don't waste time rendering skeletons
maintainVisibleContentPosition={{minIndexForVisible: 0}} */
desktopFixedHeight windowSize={thread.state.isPlaceholderData ? 1 : 7}
sideBorders={false} /**
ListFooterComponent={ * Default: 10
<ListFooter */
/* maxToRenderPerBatch={5}
* On native, if `deferParents` is true, we need some extra buffer to /**
* account for the `on*ReachedThreshold` values. * Default: 50
* */
* Otherwise, and on web, this value needs to be the height of updateCellsBatchingPeriod={100}
* the viewport _minus_ a sensible min-post height e.g. 200, so />
* that there's enough scroll remaining to get the anchor post </AnalyticsOnlyFeedFeedbackProvider>
* back to the top of the screen when handling scroll.
*/
height={platform({
web: defaultListFooterHeight,
default: deferParents
? windowHeight * 2
: defaultListFooterHeight,
})}
style={isTombstoneView ? {borderTopWidth: 0} : undefined}
/>
}
initialNumToRender={initialNumToRender}
/**
* Default: 21
*
* Smaller for placeholder data so we don't waste time rendering skeletons
*/
windowSize={thread.state.isPlaceholderData ? 1 : 7}
/**
* Default: 10
*/
maxToRenderPerBatch={5}
/**
* Default: 50
*/
updateCellsBatchingPeriod={100}
/>
)} )}
{!gtMobile && canReply && hasSession && ( {!gtMobile && canReply && hasSession && (
@@ -640,6 +650,36 @@ export function PostThread({uri}: {uri: string}) {
) )
} }
function AnalyticsOnlyFeedFeedbackProvider({
children,
feedDescriptor,
}: React.PropsWithChildren<{
feedDescriptor: FeedFeedbackStateContext['feedDescriptor']
}>) {
/*
* Non-anchor posts (parents and replies) are not the post the user tapped
* from a feed, so they should not report feed interactions. But they should
* still carry the originating feedDescriptor for analytics, so events like
* post:clickQuotePost can be attributed to the feed the thread was opened
* from. This inert context value provides feedDescriptor while keeping
* interaction reporting disabled (enabled: false makes sendInteraction and
* onItemSeen no-ops). The anchor renders its own full provider that nests
* inside and overrides this for its subtree.
*/
const value = useMemo<FeedFeedbackStateContext>(
() => ({
enabled: false,
onItemSeen: analyticsOnlyOnItemSeen,
sendInteraction: analyticsOnlySendInteraction,
feedDescriptor,
feedSourceInfo: undefined,
}),
[feedDescriptor],
)
return <FeedFeedbackProvider value={value}>{children}</FeedFeedbackProvider>
}
function MobileComposePrompt({onPressReply}: {onPressReply: () => unknown}) { function MobileComposePrompt({onPressReply}: {onPressReply: () => unknown}) {
const {footerHeight} = useShellLayout() const {footerHeight} = useShellLayout()