[Statsig] Send Discover aggregate interactions (#4599)
parent
7db8dd8980
commit
1715afd80e
|
@ -73,6 +73,22 @@ export type LogEvents = {
|
||||||
feedType: string
|
feedType: string
|
||||||
reason: 'pull-to-refresh' | 'soft-reset' | 'load-latest'
|
reason: 'pull-to-refresh' | 'soft-reset' | 'load-latest'
|
||||||
}
|
}
|
||||||
|
'discover:showMore': {
|
||||||
|
feedContext: string
|
||||||
|
}
|
||||||
|
'discover:showLess': {
|
||||||
|
feedContext: string
|
||||||
|
}
|
||||||
|
'discover:clickthrough:sampled': {
|
||||||
|
count: number
|
||||||
|
}
|
||||||
|
'discover:engaged:sampled': {
|
||||||
|
count: number
|
||||||
|
}
|
||||||
|
'discover:seen:sampled': {
|
||||||
|
count: number
|
||||||
|
}
|
||||||
|
|
||||||
'composer:gif:open': {}
|
'composer:gif:open': {}
|
||||||
'composer:gif:select': {}
|
'composer:gif:select': {}
|
||||||
|
|
||||||
|
|
|
@ -115,6 +115,9 @@ const DOWNSAMPLED_EVENTS: Set<keyof LogEvents> = new Set([
|
||||||
'home:feedDisplayed:sampled',
|
'home:feedDisplayed:sampled',
|
||||||
'feed:endReached:sampled',
|
'feed:endReached:sampled',
|
||||||
'feed:refresh:sampled',
|
'feed:refresh:sampled',
|
||||||
|
'discover:clickthrough:sampled',
|
||||||
|
'discover:engaged:sampled',
|
||||||
|
'discover:seen:sampled',
|
||||||
])
|
])
|
||||||
const isDownsampledSession = Math.random() < 0.9 // 90% likely
|
const isDownsampledSession = Math.random() < 0.9 // 90% likely
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {AppBskyFeedDefs, BskyAgent} from '@atproto/api'
|
||||||
import throttle from 'lodash.throttle'
|
import throttle from 'lodash.throttle'
|
||||||
|
|
||||||
import {PROD_DEFAULT_FEED} from '#/lib/constants'
|
import {PROD_DEFAULT_FEED} from '#/lib/constants'
|
||||||
|
import {logEvent} from '#/lib/statsig/statsig'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {
|
import {
|
||||||
FeedDescriptor,
|
FeedDescriptor,
|
||||||
|
@ -34,6 +35,16 @@ export function useFeedFeedback(feed: FeedDescriptor, hasSession: boolean) {
|
||||||
WeakSet<FeedPostSliceItem | AppBskyFeedDefs.Interaction>
|
WeakSet<FeedPostSliceItem | AppBskyFeedDefs.Interaction>
|
||||||
>(new WeakSet())
|
>(new WeakSet())
|
||||||
|
|
||||||
|
const aggregatedStats = React.useRef<AggregatedStats | null>(null)
|
||||||
|
const throttledFlushAggregatedStats = React.useMemo(
|
||||||
|
() =>
|
||||||
|
throttle(() => flushToStatsig(aggregatedStats.current), 45e3, {
|
||||||
|
leading: true, // The outer call is already throttled somewhat.
|
||||||
|
trailing: true,
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
const sendToFeedNoDelay = React.useCallback(() => {
|
const sendToFeedNoDelay = React.useCallback(() => {
|
||||||
const proxyAgent = agent.withProxy(
|
const proxyAgent = agent.withProxy(
|
||||||
// @ts-ignore TODO need to update withProxy() to support this key -prf
|
// @ts-ignore TODO need to update withProxy() to support this key -prf
|
||||||
|
@ -45,12 +56,20 @@ export function useFeedFeedback(feed: FeedDescriptor, hasSession: boolean) {
|
||||||
const interactions = Array.from(queue.current).map(toInteraction)
|
const interactions = Array.from(queue.current).map(toInteraction)
|
||||||
queue.current.clear()
|
queue.current.clear()
|
||||||
|
|
||||||
|
// Send to the feed
|
||||||
proxyAgent.app.bsky.feed
|
proxyAgent.app.bsky.feed
|
||||||
.sendInteractions({interactions})
|
.sendInteractions({interactions})
|
||||||
.catch((e: any) => {
|
.catch((e: any) => {
|
||||||
logger.warn('Failed to send feed interactions', {error: e})
|
logger.warn('Failed to send feed interactions', {error: e})
|
||||||
})
|
})
|
||||||
}, [agent])
|
|
||||||
|
// Send to Statsig
|
||||||
|
if (aggregatedStats.current === null) {
|
||||||
|
aggregatedStats.current = createAggregatedStats()
|
||||||
|
}
|
||||||
|
sendOrAggregateInteractionsForStats(aggregatedStats.current, interactions)
|
||||||
|
throttledFlushAggregatedStats()
|
||||||
|
}, [agent, throttledFlushAggregatedStats])
|
||||||
|
|
||||||
const sendToFeed = React.useMemo(
|
const sendToFeed = React.useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
@ -149,3 +168,89 @@ function toInteraction(str: string): AppBskyFeedDefs.Interaction {
|
||||||
const [item, event, feedContext] = str.split('|')
|
const [item, event, feedContext] = str.split('|')
|
||||||
return {item, event, feedContext}
|
return {item, event, feedContext}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AggregatedStats = {
|
||||||
|
clickthroughCount: number
|
||||||
|
engagedCount: number
|
||||||
|
seenCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAggregatedStats(): AggregatedStats {
|
||||||
|
return {
|
||||||
|
clickthroughCount: 0,
|
||||||
|
engagedCount: 0,
|
||||||
|
seenCount: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendOrAggregateInteractionsForStats(
|
||||||
|
stats: AggregatedStats,
|
||||||
|
interactions: AppBskyFeedDefs.Interaction[],
|
||||||
|
) {
|
||||||
|
for (let interaction of interactions) {
|
||||||
|
switch (interaction.event) {
|
||||||
|
// Pressing "Show more" / "Show less" is relatively uncommon so we won't aggregate them.
|
||||||
|
// This lets us send the feed context together with them.
|
||||||
|
case 'app.bsky.feed.defs#requestLess': {
|
||||||
|
logEvent('discover:showLess', {
|
||||||
|
feedContext: interaction.feedContext ?? '',
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'app.bsky.feed.defs#requestMore': {
|
||||||
|
logEvent('discover:showMore', {
|
||||||
|
feedContext: interaction.feedContext ?? '',
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// The rest of the events are aggregated and sent later in batches.
|
||||||
|
case 'app.bsky.feed.defs#clickthroughAuthor':
|
||||||
|
case 'app.bsky.feed.defs#clickthroughEmbed':
|
||||||
|
case 'app.bsky.feed.defs#clickthroughItem':
|
||||||
|
case 'app.bsky.feed.defs#clickthroughReposter': {
|
||||||
|
stats.clickthroughCount++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'app.bsky.feed.defs#interactionLike':
|
||||||
|
case 'app.bsky.feed.defs#interactionQuote':
|
||||||
|
case 'app.bsky.feed.defs#interactionReply':
|
||||||
|
case 'app.bsky.feed.defs#interactionRepost':
|
||||||
|
case 'app.bsky.feed.defs#interactionShare': {
|
||||||
|
stats.engagedCount++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'app.bsky.feed.defs#interactionSeen': {
|
||||||
|
stats.seenCount++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function flushToStatsig(stats: AggregatedStats | null) {
|
||||||
|
if (stats === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stats.clickthroughCount > 0) {
|
||||||
|
logEvent('discover:clickthrough:sampled', {
|
||||||
|
count: stats.clickthroughCount,
|
||||||
|
})
|
||||||
|
stats.clickthroughCount = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stats.engagedCount > 0) {
|
||||||
|
logEvent('discover:engaged:sampled', {
|
||||||
|
count: stats.engagedCount,
|
||||||
|
})
|
||||||
|
stats.engagedCount = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stats.seenCount > 0) {
|
||||||
|
logEvent('discover:seen:sampled', {
|
||||||
|
count: stats.seenCount,
|
||||||
|
})
|
||||||
|
stats.seenCount = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue