Profile follow client events (#9385)
* Add parameters to profile:follow Track who was followed, whose profile generated the follow, and the position of the person who was followed in the list * Add profileCard:seen event * Don't send "profileCard:seen" event to Statsig * Clean up * prevent overzealous clearing --------- Co-authored-by: Samuel Newman <mozzius@protonmail.com>
This commit is contained in:
@@ -48,11 +48,15 @@ export function Default({
|
||||
moderationOpts,
|
||||
logContext = 'ProfileCard',
|
||||
testID,
|
||||
position,
|
||||
contextProfileDid,
|
||||
}: {
|
||||
profile: bsky.profile.AnyProfileView
|
||||
moderationOpts: ModerationOpts
|
||||
logContext?: 'ProfileCard' | 'StarterPackProfilesList'
|
||||
testID?: string
|
||||
position?: number
|
||||
contextProfileDid?: string
|
||||
}) {
|
||||
return (
|
||||
<Link testID={testID} profile={profile}>
|
||||
@@ -60,6 +64,8 @@ export function Default({
|
||||
profile={profile}
|
||||
moderationOpts={moderationOpts}
|
||||
logContext={logContext}
|
||||
position={position}
|
||||
contextProfileDid={contextProfileDid}
|
||||
/>
|
||||
</Link>
|
||||
)
|
||||
@@ -69,10 +75,14 @@ export function Card({
|
||||
profile,
|
||||
moderationOpts,
|
||||
logContext = 'ProfileCard',
|
||||
position,
|
||||
contextProfileDid,
|
||||
}: {
|
||||
profile: bsky.profile.AnyProfileView
|
||||
moderationOpts: ModerationOpts
|
||||
logContext?: 'ProfileCard' | 'StarterPackProfilesList'
|
||||
position?: number
|
||||
contextProfileDid?: string
|
||||
}) {
|
||||
return (
|
||||
<Outer>
|
||||
@@ -83,6 +93,8 @@ export function Card({
|
||||
profile={profile}
|
||||
moderationOpts={moderationOpts}
|
||||
logContext={logContext}
|
||||
position={position}
|
||||
contextProfileDid={contextProfileDid}
|
||||
/>
|
||||
</Header>
|
||||
|
||||
@@ -437,6 +449,8 @@ export type FollowButtonProps = {
|
||||
colorInverted?: boolean
|
||||
onFollow?: () => void
|
||||
withIcon?: boolean
|
||||
position?: number
|
||||
contextProfileDid?: string
|
||||
} & Partial<ButtonProps>
|
||||
|
||||
export function FollowButton(props: FollowButtonProps) {
|
||||
@@ -453,6 +467,8 @@ export function FollowButtonInner({
|
||||
onFollow,
|
||||
colorInverted,
|
||||
withIcon = true,
|
||||
position,
|
||||
contextProfileDid,
|
||||
...rest
|
||||
}: FollowButtonProps) {
|
||||
const {_} = useLingui()
|
||||
@@ -461,6 +477,8 @@ export function FollowButtonInner({
|
||||
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
|
||||
profile,
|
||||
logContext,
|
||||
position,
|
||||
contextProfileDid,
|
||||
)
|
||||
const isRound = Boolean(rest.shape && rest.shape === 'round')
|
||||
|
||||
|
||||
@@ -256,9 +256,12 @@ export type MetricEvents = {
|
||||
'bookmarks:view': {}
|
||||
'bookmarks:post-clicked': {}
|
||||
'profile:follow': {
|
||||
contextProfileDid?: string
|
||||
didBecomeMutual: boolean | undefined
|
||||
followeeClout: number | undefined
|
||||
followeeDid: string
|
||||
followerClout: number | undefined
|
||||
position?: number
|
||||
logContext:
|
||||
| 'RecommendedFollowsItem'
|
||||
| 'PostThreadItem'
|
||||
@@ -276,6 +279,11 @@ export type MetricEvents = {
|
||||
| 'ExploreSuggestedAccounts'
|
||||
| 'OnboardingSuggestedAccounts'
|
||||
}
|
||||
'profileCard:seen': {
|
||||
contextProfileDid?: string
|
||||
profileDid: string
|
||||
position?: number
|
||||
}
|
||||
'suggestedUser:follow': {
|
||||
logContext:
|
||||
| 'Explore'
|
||||
|
||||
@@ -242,12 +242,19 @@ export function useProfileFollowMutationQueue(
|
||||
profile: Shadow<bsky.profile.AnyProfileView>,
|
||||
logContext: LogEvents['profile:follow']['logContext'] &
|
||||
LogEvents['profile:follow']['logContext'],
|
||||
position?: number,
|
||||
contextProfileDid?: string,
|
||||
) {
|
||||
const agent = useAgent()
|
||||
const queryClient = useQueryClient()
|
||||
const did = profile.did
|
||||
const initialFollowingUri = profile.viewer?.following
|
||||
const followMutation = useProfileFollowMutation(logContext, profile)
|
||||
const followMutation = useProfileFollowMutation(
|
||||
logContext,
|
||||
profile,
|
||||
position,
|
||||
contextProfileDid,
|
||||
)
|
||||
const unfollowMutation = useProfileUnfollowMutation(logContext)
|
||||
|
||||
const queueToggle = useToggleMutationQueue({
|
||||
@@ -314,6 +321,8 @@ export function useProfileFollowMutationQueue(
|
||||
function useProfileFollowMutation(
|
||||
logContext: LogEvents['profile:follow']['logContext'],
|
||||
profile: Shadow<bsky.profile.AnyProfileView>,
|
||||
position?: number,
|
||||
contextProfileDid?: string,
|
||||
) {
|
||||
const {currentAccount} = useSession()
|
||||
const agent = useAgent()
|
||||
@@ -336,7 +345,10 @@ function useProfileFollowMutation(
|
||||
'followersCount' in profile
|
||||
? toClout(profile.followersCount)
|
||||
: undefined,
|
||||
followeeDid: did,
|
||||
followerClout: toClout(ownProfile?.followersCount),
|
||||
position,
|
||||
contextProfileDid,
|
||||
})
|
||||
return await agent.follow(did)
|
||||
},
|
||||
|
||||
@@ -9,10 +9,14 @@ export function ProfileCardWithFollowBtn({
|
||||
profile,
|
||||
noBorder,
|
||||
logContext = 'ProfileCard',
|
||||
position,
|
||||
contextProfileDid,
|
||||
}: {
|
||||
profile: AppBskyActorDefs.ProfileView
|
||||
noBorder?: boolean
|
||||
logContext?: 'ProfileCard' | 'StarterPackProfilesList'
|
||||
position?: number
|
||||
contextProfileDid?: string
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const moderationOpts = useModerationOpts()
|
||||
@@ -30,6 +34,8 @@ export function ProfileCardWithFollowBtn({
|
||||
profile={profile}
|
||||
moderationOpts={moderationOpts}
|
||||
logContext={logContext}
|
||||
position={position}
|
||||
contextProfileDid={contextProfileDid}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
|
||||
@@ -16,15 +16,19 @@ import {ProfileCardWithFollowBtn} from './ProfileCard'
|
||||
function renderItem({
|
||||
item,
|
||||
index,
|
||||
contextProfileDid,
|
||||
}: {
|
||||
item: ActorDefs.ProfileView
|
||||
index: number
|
||||
contextProfileDid: string | undefined
|
||||
}) {
|
||||
return (
|
||||
<ProfileCardWithFollowBtn
|
||||
key={item.did}
|
||||
profile={item}
|
||||
noBorder={index === 0}
|
||||
position={index + 1}
|
||||
contextProfileDid={contextProfileDid}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -83,6 +87,40 @@ export function ProfileFollowers({name}: {name: string}) {
|
||||
}
|
||||
}, [isFetchingNextPage, hasNextPage, error, fetchNextPage])
|
||||
|
||||
const renderItemWithContext = React.useCallback(
|
||||
({item, index}: {item: ActorDefs.ProfileView; index: number}) =>
|
||||
renderItem({item, index, contextProfileDid: resolvedDid}),
|
||||
[resolvedDid],
|
||||
)
|
||||
|
||||
// track seen items
|
||||
const seenItemsRef = React.useRef<Set<string>>(new Set())
|
||||
React.useEffect(() => {
|
||||
seenItemsRef.current.clear()
|
||||
}, [resolvedDid])
|
||||
const onItemSeen = React.useCallback(
|
||||
(item: ActorDefs.ProfileView) => {
|
||||
if (seenItemsRef.current.has(item.did)) {
|
||||
return
|
||||
}
|
||||
seenItemsRef.current.add(item.did)
|
||||
const position = followers.findIndex(p => p.did === item.did) + 1
|
||||
if (position === 0) {
|
||||
return
|
||||
}
|
||||
logger.metric(
|
||||
'profileCard:seen',
|
||||
{
|
||||
profileDid: item.did,
|
||||
position,
|
||||
...(resolvedDid !== undefined && {contextProfileDid: resolvedDid}),
|
||||
},
|
||||
{statsig: false},
|
||||
)
|
||||
},
|
||||
[followers, resolvedDid],
|
||||
)
|
||||
|
||||
if (followers.length < 1) {
|
||||
return (
|
||||
<ListMaybePlaceholder
|
||||
@@ -104,12 +142,13 @@ export function ProfileFollowers({name}: {name: string}) {
|
||||
return (
|
||||
<List
|
||||
data={followers}
|
||||
renderItem={renderItem}
|
||||
renderItem={renderItemWithContext}
|
||||
keyExtractor={keyExtractor}
|
||||
refreshing={isPTRing}
|
||||
onRefresh={onRefresh}
|
||||
onEndReached={onEndReached}
|
||||
onEndReachedThreshold={4}
|
||||
onItemSeen={onItemSeen}
|
||||
ListFooterComponent={
|
||||
<ListFooter
|
||||
isFetchingNextPage={isFetchingNextPage}
|
||||
|
||||
@@ -16,15 +16,19 @@ import {ProfileCardWithFollowBtn} from './ProfileCard'
|
||||
function renderItem({
|
||||
item,
|
||||
index,
|
||||
contextProfileDid,
|
||||
}: {
|
||||
item: ActorDefs.ProfileView
|
||||
index: number
|
||||
contextProfileDid: string | undefined
|
||||
}) {
|
||||
return (
|
||||
<ProfileCardWithFollowBtn
|
||||
key={item.did}
|
||||
profile={item}
|
||||
noBorder={index === 0}
|
||||
position={index + 1}
|
||||
contextProfileDid={contextProfileDid}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -83,6 +87,40 @@ export function ProfileFollows({name}: {name: string}) {
|
||||
}
|
||||
}, [error, fetchNextPage, hasNextPage, isFetchingNextPage])
|
||||
|
||||
const renderItemWithContext = React.useCallback(
|
||||
({item, index}: {item: ActorDefs.ProfileView; index: number}) =>
|
||||
renderItem({item, index, contextProfileDid: resolvedDid}),
|
||||
[resolvedDid],
|
||||
)
|
||||
|
||||
// track seen items
|
||||
const seenItemsRef = React.useRef<Set<string>>(new Set())
|
||||
React.useEffect(() => {
|
||||
seenItemsRef.current.clear()
|
||||
}, [resolvedDid])
|
||||
const onItemSeen = React.useCallback(
|
||||
(item: ActorDefs.ProfileView) => {
|
||||
if (seenItemsRef.current.has(item.did)) {
|
||||
return
|
||||
}
|
||||
seenItemsRef.current.add(item.did)
|
||||
const position = follows.findIndex(p => p.did === item.did) + 1
|
||||
if (position === 0) {
|
||||
return
|
||||
}
|
||||
logger.metric(
|
||||
'profileCard:seen',
|
||||
{
|
||||
profileDid: item.did,
|
||||
position,
|
||||
...(resolvedDid !== undefined && {contextProfileDid: resolvedDid}),
|
||||
},
|
||||
{statsig: false},
|
||||
)
|
||||
},
|
||||
[follows, resolvedDid],
|
||||
)
|
||||
|
||||
if (follows.length < 1) {
|
||||
return (
|
||||
<ListMaybePlaceholder
|
||||
@@ -104,12 +142,13 @@ export function ProfileFollows({name}: {name: string}) {
|
||||
return (
|
||||
<List
|
||||
data={follows}
|
||||
renderItem={renderItem}
|
||||
renderItem={renderItemWithContext}
|
||||
keyExtractor={keyExtractor}
|
||||
refreshing={isPTRing}
|
||||
onRefresh={onRefresh}
|
||||
onEndReached={onEndReached}
|
||||
onEndReachedThreshold={4}
|
||||
onItemSeen={onItemSeen}
|
||||
ListFooterComponent={
|
||||
<ListFooter
|
||||
isFetchingNextPage={isFetchingNextPage}
|
||||
|
||||
Reference in New Issue
Block a user