Add suggestedDid and category attributes to suggestedUser client events

This commit is contained in:
Alex Benzer
2025-12-03 12:51:17 -08:00
parent d78eb5d3bb
commit f7e1d886cf
6 changed files with 83 additions and 11 deletions
+6
View File
@@ -288,6 +288,8 @@ export function ProfileGrid({
logContext, logContext,
recId, recId,
position: index, position: index,
suggestedDid: profile.did,
category: null,
}, },
{statsig: true}, {statsig: true},
) )
@@ -368,6 +370,8 @@ export function ProfileGrid({
: 'InterstitialProfile', : 'InterstitialProfile',
recId, recId,
position: index, position: index,
suggestedDid: profile.did,
category: null,
}) })
}} }}
style={[ style={[
@@ -428,6 +432,8 @@ export function ProfileGrid({
location: 'Card', location: 'Card',
recId, recId,
position: index, position: index,
suggestedDid: profile.did,
category: null,
}) })
}} }}
/> />
@@ -236,6 +236,8 @@ function DialogInner({guide}: {guide: Follow10ProgressGuide}) {
const seenProfilesRef = useRef<Set<string>>(new Set()) const seenProfilesRef = useRef<Set<string>>(new Set())
const itemsRef = useRef(items) const itemsRef = useRef(items)
itemsRef.current = items itemsRef.current = items
const selectedInterestRef = useRef(selectedInterest)
selectedInterestRef.current = selectedInterest
const onViewableItemsChanged = useRef( const onViewableItemsChanged = useRef(
({viewableItems}: {viewableItems: ViewToken[]}) => { ({viewableItems}: {viewableItems: ViewToken[]}) => {
@@ -253,6 +255,8 @@ function DialogInner({guide}: {guide: Follow10ProgressGuide}) {
logContext: 'ProgressGuide', logContext: 'ProgressGuide',
recId: undefined, recId: undefined,
position: position !== -1 ? position : 0, position: position !== -1 ? position : 0,
suggestedDid: item.profile.did,
category: selectedInterestRef.current,
}, },
{statsig: true}, {statsig: true},
) )
+6
View File
@@ -311,6 +311,8 @@ export type MetricEvents = {
location: 'Card' | 'Profile' location: 'Card' | 'Profile'
recId?: number recId?: number
position: number position: number
suggestedDid: string
category: string | null
} }
'suggestedUser:press': { 'suggestedUser:press': {
logContext: logContext:
@@ -320,6 +322,8 @@ export type MetricEvents = {
| 'Onboarding' | 'Onboarding'
recId?: number recId?: number
position: number position: number
suggestedDid: string
category: string | null
} }
'suggestedUser:seen': { 'suggestedUser:seen': {
logContext: logContext:
@@ -331,6 +335,8 @@ export type MetricEvents = {
| 'ProgressGuide' | 'ProgressGuide'
recId?: number recId?: number
position: number position: number
suggestedDid: string
category: string | null
} }
'suggestedUser:seeMore': { 'suggestedUser:seeMore': {
logContext: logContext:
@@ -1,4 +1,11 @@
import {useContext, useEffect, useMemo, useRef, useState} from 'react' import {
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {type ModerationOpts} from '@atproto/api' import {type ModerationOpts} from '@atproto/api'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
@@ -123,26 +130,27 @@ export function StepSuggestedAccounts() {
const canFollowAll = followableDids.length > 0 && !isFollowingAll const canFollowAll = followableDids.length > 0 && !isFollowingAll
// Track seen profiles // Track seen profiles - shared ref across all cards
const seenProfilesRef = useRef<Set<string>>(new Set()) const seenProfilesRef = useRef<Set<string>>(new Set())
useEffect(() => { const onProfileSeen = useCallback(
if (isLoading || !moderationOpts || !suggestedUsers?.actors.length) return (did: string, position: number) => {
if (!seenProfilesRef.current.has(did)) {
suggestedUsers.actors.forEach((profile, index) => { seenProfilesRef.current.add(did)
if (!seenProfilesRef.current.has(profile.did)) {
seenProfilesRef.current.add(profile.did)
logger.metric( logger.metric(
'suggestedUser:seen', 'suggestedUser:seen',
{ {
logContext: 'Onboarding', logContext: 'Onboarding',
recId: undefined, recId: undefined,
position: index, position,
suggestedDid: did,
category: selectedInterest,
}, },
{statsig: true}, {statsig: true},
) )
} }
}) },
}, [isLoading, moderationOpts, suggestedUsers]) [selectedInterest],
)
return ( return (
<View style={[a.align_start]} testID="onboardingInterests"> <View style={[a.align_start]} testID="onboardingInterests">
@@ -214,6 +222,8 @@ export function StepSuggestedAccounts() {
profile={user} profile={user}
moderationOpts={moderationOpts} moderationOpts={moderationOpts}
position={index} position={index}
category={selectedInterest}
onSeen={onProfileSeen}
/> />
))} ))}
</View> </View>
@@ -324,14 +334,52 @@ function SuggestedProfileCard({
profile, profile,
moderationOpts, moderationOpts,
position, position,
category,
onSeen,
}: { }: {
profile: bsky.profile.AnyProfileView profile: bsky.profile.AnyProfileView
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
position: number position: number
category: string | null
onSeen: (did: string, position: number) => void
}) { }) {
const t = useTheme() 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 ( return (
<View <View
ref={cardRef}
style={[ style={[
a.flex_1, a.flex_1,
a.w_full, a.w_full,
@@ -364,6 +412,8 @@ function SuggestedProfileCard({
location: 'Card', location: 'Card',
recId: undefined, recId: undefined,
position, position,
suggestedDid: profile.did,
category,
}, },
{statsig: true}, {statsig: true},
) )
+2
View File
@@ -1050,6 +1050,8 @@ export function Explore({
logContext: 'Explore', logContext: 'Explore',
recId: item.recId, recId: item.recId,
position: position !== -1 ? position - 1 : 0, // -1 to account for header position: position !== -1 ? position - 1 : 0, // -1 to account for header
suggestedDid: item.profile.did,
category: null,
}, },
{statsig: true}, {statsig: true},
) )
@@ -123,6 +123,8 @@ let SuggestedProfileCard = ({
logContext: 'Explore', logContext: 'Explore',
recId, recId,
position, position,
suggestedDid: profile.did,
category: null,
}, },
{statsig: true}, {statsig: true},
) )
@@ -162,6 +164,8 @@ let SuggestedProfileCard = ({
location: 'Card', location: 'Card',
recId, recId,
position, position,
suggestedDid: profile.did,
category: null,
}, },
{statsig: true}, {statsig: true},
) )