Hook up suggestedUser:seen client events (#9468)
* Hook up suggestedUser:seen client events * Fix crash when clicking "find people to follow" * While we're at it, fix the position of the X button on the "find people to follow" modal * Add suggestedDid and category attributes to suggestedUser client events --------- Co-authored-by: Eric Bailey <git@esb.lol>
This commit is contained in:
@@ -1,4 +1,11 @@
|
||||
import {useContext, useMemo, useState} from 'react'
|
||||
import {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {type ModerationOpts} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
@@ -123,6 +130,28 @@ export function StepSuggestedAccounts() {
|
||||
|
||||
const canFollowAll = followableDids.length > 0 && !isFollowingAll
|
||||
|
||||
// Track seen profiles - shared ref across all cards
|
||||
const seenProfilesRef = useRef<Set<string>>(new Set())
|
||||
const onProfileSeen = useCallback(
|
||||
(did: string, position: number) => {
|
||||
if (!seenProfilesRef.current.has(did)) {
|
||||
seenProfilesRef.current.add(did)
|
||||
logger.metric(
|
||||
'suggestedUser:seen',
|
||||
{
|
||||
logContext: 'Onboarding',
|
||||
recId: undefined,
|
||||
position,
|
||||
suggestedDid: did,
|
||||
category: selectedInterest,
|
||||
},
|
||||
{statsig: true},
|
||||
)
|
||||
}
|
||||
},
|
||||
[selectedInterest],
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={[a.align_start]} testID="onboardingInterests">
|
||||
<Text style={[a.font_bold, a.text_3xl]}>
|
||||
@@ -193,6 +222,8 @@ export function StepSuggestedAccounts() {
|
||||
profile={user}
|
||||
moderationOpts={moderationOpts}
|
||||
position={index}
|
||||
category={selectedInterest}
|
||||
onSeen={onProfileSeen}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
@@ -303,14 +334,52 @@ function SuggestedProfileCard({
|
||||
profile,
|
||||
moderationOpts,
|
||||
position,
|
||||
category,
|
||||
onSeen,
|
||||
}: {
|
||||
profile: bsky.profile.AnyProfileView
|
||||
moderationOpts: ModerationOpts
|
||||
position: number
|
||||
category: string | null
|
||||
onSeen: (did: string, position: number) => void
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const cardRef = useRef<View>(null)
|
||||
const hasTrackedRef = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
const node = cardRef.current
|
||||
if (!node || hasTrackedRef.current) return
|
||||
|
||||
if (isWeb && typeof IntersectionObserver !== 'undefined') {
|
||||
const observer = new IntersectionObserver(
|
||||
entries => {
|
||||
if (entries[0]?.isIntersecting && !hasTrackedRef.current) {
|
||||
hasTrackedRef.current = true
|
||||
onSeen(profile.did, position)
|
||||
observer.disconnect()
|
||||
}
|
||||
},
|
||||
{threshold: 0.5},
|
||||
)
|
||||
// @ts-ignore - web only
|
||||
observer.observe(node)
|
||||
return () => observer.disconnect()
|
||||
} else {
|
||||
// Native: use a short delay to account for initial layout
|
||||
const timeout = setTimeout(() => {
|
||||
if (!hasTrackedRef.current) {
|
||||
hasTrackedRef.current = true
|
||||
onSeen(profile.did, position)
|
||||
}
|
||||
}, 500)
|
||||
return () => clearTimeout(timeout)
|
||||
}
|
||||
}, [onSeen, profile.did, position])
|
||||
|
||||
return (
|
||||
<View
|
||||
ref={cardRef}
|
||||
style={[
|
||||
a.w_full,
|
||||
a.py_lg,
|
||||
@@ -342,6 +411,8 @@ function SuggestedProfileCard({
|
||||
location: 'Card',
|
||||
recId: undefined,
|
||||
position,
|
||||
suggestedDid: profile.did,
|
||||
category,
|
||||
},
|
||||
{statsig: true},
|
||||
)
|
||||
|
||||
@@ -47,6 +47,7 @@ export function AnimatedProfileHeaderSuggestedFollows({
|
||||
recId={data.recId}
|
||||
error={error}
|
||||
viewContext="profileHeader"
|
||||
isVisible={isExpanded}
|
||||
/>
|
||||
</AccordionAnimation>
|
||||
)
|
||||
|
||||
@@ -1030,26 +1030,48 @@ export function Explore({
|
||||
|
||||
// track headers and report module viewability
|
||||
const alreadyReportedRef = useRef<Map<string, string>>(new Map())
|
||||
const onItemSeen = useCallback((item: ExploreScreenItems) => {
|
||||
let module: MetricEvents['explore:module:seen']['module']
|
||||
if (item.type === 'trendingTopics' || item.type === 'trendingVideos') {
|
||||
module = item.type
|
||||
} else if (item.type === 'profile') {
|
||||
module = 'suggestedAccounts'
|
||||
} else if (item.type === 'feed') {
|
||||
module = 'suggestedFeeds'
|
||||
} else if (item.type === 'starterPack') {
|
||||
module = 'suggestedStarterPacks'
|
||||
} else if (item.type === 'preview:sliceItem') {
|
||||
module = `feed:feedgen|${item.feed.uri}`
|
||||
} else {
|
||||
return
|
||||
}
|
||||
if (!alreadyReportedRef.current.has(module)) {
|
||||
alreadyReportedRef.current.set(module, module)
|
||||
logger.metric('explore:module:seen', {module}, {statsig: false})
|
||||
}
|
||||
}, [])
|
||||
const seenProfilesRef = useRef<Set<string>>(new Set())
|
||||
const onItemSeen = useCallback(
|
||||
(item: ExploreScreenItems) => {
|
||||
let module: MetricEvents['explore:module:seen']['module']
|
||||
if (item.type === 'trendingTopics' || item.type === 'trendingVideos') {
|
||||
module = item.type
|
||||
} else if (item.type === 'profile') {
|
||||
module = 'suggestedAccounts'
|
||||
// Track individual profile seen events
|
||||
if (!seenProfilesRef.current.has(item.profile.did)) {
|
||||
seenProfilesRef.current.add(item.profile.did)
|
||||
const position = suggestedFollowsModule.findIndex(
|
||||
i => i.type === 'profile' && i.profile.did === item.profile.did,
|
||||
)
|
||||
logger.metric(
|
||||
'suggestedUser:seen',
|
||||
{
|
||||
logContext: 'Explore',
|
||||
recId: item.recId,
|
||||
position: position !== -1 ? position - 1 : 0, // -1 to account for header
|
||||
suggestedDid: item.profile.did,
|
||||
category: null,
|
||||
},
|
||||
{statsig: true},
|
||||
)
|
||||
}
|
||||
} else if (item.type === 'feed') {
|
||||
module = 'suggestedFeeds'
|
||||
} else if (item.type === 'starterPack') {
|
||||
module = 'suggestedStarterPacks'
|
||||
} else if (item.type === 'preview:sliceItem') {
|
||||
module = `feed:feedgen|${item.feed.uri}`
|
||||
} else {
|
||||
return
|
||||
}
|
||||
if (!alreadyReportedRef.current.has(module)) {
|
||||
alreadyReportedRef.current.set(module, module)
|
||||
logger.metric('explore:module:seen', {module}, {statsig: false})
|
||||
}
|
||||
},
|
||||
[suggestedFollowsModule],
|
||||
)
|
||||
|
||||
return (
|
||||
<List
|
||||
|
||||
@@ -123,6 +123,8 @@ let SuggestedProfileCard = ({
|
||||
logContext: 'Explore',
|
||||
recId,
|
||||
position,
|
||||
suggestedDid: profile.did,
|
||||
category: null,
|
||||
},
|
||||
{statsig: true},
|
||||
)
|
||||
@@ -162,6 +164,8 @@ let SuggestedProfileCard = ({
|
||||
location: 'Card',
|
||||
recId,
|
||||
position,
|
||||
suggestedDid: profile.did,
|
||||
category: null,
|
||||
},
|
||||
{statsig: true},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user