From 887fedabeaa3470615199642870494df2784b280 Mon Sep 17 00:00:00 2001 From: dan Date: Mon, 8 Apr 2024 18:38:51 +0100 Subject: [PATCH] [Statsig] Track like/follow metadata (#3435) * Track becoming mutuals * Track poster/liker status * Track post and followee clout * Track follower and liker clout * Extract utility --- src/lib/statsig/events.ts | 7 ++++++ src/lib/statsig/statsig.tsx | 8 +++++++ src/state/queries/post.ts | 42 +++++++++++++++++++++++++++++------- src/state/queries/profile.ts | 29 ++++++++++++++++++++++--- 4 files changed, 75 insertions(+), 11 deletions(-) diff --git a/src/lib/statsig/events.ts b/src/lib/statsig/events.ts index 98d633194d..73e9876ac8 100644 --- a/src/lib/statsig/events.ts +++ b/src/lib/statsig/events.ts @@ -67,6 +67,10 @@ export type LogEvents = { logContext: 'Composer' } 'post:like': { + doesLikerFollowPoster: boolean | undefined + doesPosterFollowLiker: boolean | undefined + likerClout: number | undefined + postClout: number | undefined logContext: 'FeedItem' | 'PostThreadItem' | 'Post' } 'post:repost': { @@ -79,6 +83,9 @@ export type LogEvents = { logContext: 'FeedItem' | 'PostThreadItem' | 'Post' } 'profile:follow': { + didBecomeMutual: boolean | undefined + followeeClout: number | undefined + followerClout: number | undefined logContext: | 'RecommendedFollowsItem' | 'PostThreadItem' diff --git a/src/lib/statsig/statsig.tsx b/src/lib/statsig/statsig.tsx index d0ef2408e9..c164616217 100644 --- a/src/lib/statsig/statsig.tsx +++ b/src/lib/statsig/statsig.tsx @@ -43,6 +43,14 @@ export function attachRouteToLogEvents( getCurrentRouteName = getRouteName } +export function toClout(n: number | null | undefined): number | undefined { + if (n == null) { + return undefined + } else { + return Math.max(0, Math.round(Math.log(n))) + } +} + export function logEvent( eventName: E & string, rawMetadata: LogEvents[E] & FlatJSONRecord, diff --git a/src/state/queries/post.ts b/src/state/queries/post.ts index 746dedad27..77497f6bab 100644 --- a/src/state/queries/post.ts +++ b/src/state/queries/post.ts @@ -1,13 +1,14 @@ import {useCallback} from 'react' -import {AppBskyFeedDefs, AtUri} from '@atproto/api' +import {AppBskyActorDefs, AppBskyFeedDefs, AtUri} from '@atproto/api' import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' import {track} from '#/lib/analytics/analytics' import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue' -import {logEvent, LogEvents} from '#/lib/statsig/statsig' +import {logEvent, LogEvents, toClout} from '#/lib/statsig/statsig' import {updatePostShadow} from '#/state/cache/post-shadow' import {Shadow} from '#/state/cache/types' -import {getAgent} from '#/state/session' +import {getAgent, useSession} from '#/state/session' +import {findProfileQueryData} from './profile' const RQKEY_ROOT = 'post' export const RQKEY = (postUri: string) => [RQKEY_ROOT, postUri] @@ -68,7 +69,7 @@ export function usePostLikeMutationQueue( const postUri = post.uri const postCid = post.cid const initialLikeUri = post.viewer?.like - const likeMutation = usePostLikeMutation(logContext) + const likeMutation = usePostLikeMutation(logContext, post) const unlikeMutation = usePostUnlikeMutation(logContext) const queueToggle = useToggleMutationQueue({ @@ -117,15 +118,40 @@ export function usePostLikeMutationQueue( return [queueLike, queueUnlike] } -function usePostLikeMutation(logContext: LogEvents['post:like']['logContext']) { +function usePostLikeMutation( + logContext: LogEvents['post:like']['logContext'], + post: Shadow, +) { + const {currentAccount} = useSession() + const queryClient = useQueryClient() + const postAuthor = post.author return useMutation< {uri: string}, // responds with the uri of the like Error, {uri: string; cid: string} // the post's uri and cid >({ - mutationFn: post => { - logEvent('post:like', {logContext}) - return getAgent().like(post.uri, post.cid) + mutationFn: ({uri, cid}) => { + let ownProfile: AppBskyActorDefs.ProfileViewDetailed | undefined + if (currentAccount) { + ownProfile = findProfileQueryData(queryClient, currentAccount.did) + } + logEvent('post:like', { + logContext, + doesPosterFollowLiker: postAuthor.viewer + ? Boolean(postAuthor.viewer.followedBy) + : undefined, + doesLikerFollowPoster: postAuthor.viewer + ? Boolean(postAuthor.viewer.following) + : undefined, + likerClout: toClout(ownProfile?.followersCount), + postClout: + post.likeCount != null && + post.repostCount != null && + post.replyCount != null + ? toClout(post.likeCount + post.repostCount + post.replyCount) + : undefined, + }) + return getAgent().like(uri, cid) }, onSuccess() { track('Post:Like') diff --git a/src/state/queries/profile.ts b/src/state/queries/profile.ts index 2094e0c3a2..a962fecff7 100644 --- a/src/state/queries/profile.ts +++ b/src/state/queries/profile.ts @@ -20,7 +20,7 @@ import {track} from '#/lib/analytics/analytics' import {uploadBlob} from '#/lib/api' import {until} from '#/lib/async/until' import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue' -import {logEvent, LogEvents} from '#/lib/statsig/statsig' +import {logEvent, LogEvents, toClout} from '#/lib/statsig/statsig' import {Shadow} from '#/state/cache/types' import {STALE} from '#/state/queries' import {resetProfilePostsQueries} from '#/state/queries/post-feed' @@ -202,7 +202,7 @@ export function useProfileFollowMutationQueue( const queryClient = useQueryClient() const did = profile.did const initialFollowingUri = profile.viewer?.following - const followMutation = useProfileFollowMutation(logContext) + const followMutation = useProfileFollowMutation(logContext, profile) const unfollowMutation = useProfileUnfollowMutation(logContext) const queueToggle = useToggleMutationQueue({ @@ -252,10 +252,24 @@ export function useProfileFollowMutationQueue( function useProfileFollowMutation( logContext: LogEvents['profile:follow']['logContext'], + profile: Shadow, ) { + const {currentAccount} = useSession() + const queryClient = useQueryClient() return useMutation<{uri: string; cid: string}, Error, {did: string}>({ mutationFn: async ({did}) => { - logEvent('profile:follow', {logContext}) + let ownProfile: AppBskyActorDefs.ProfileViewDetailed | undefined + if (currentAccount) { + ownProfile = findProfileQueryData(queryClient, currentAccount.did) + } + logEvent('profile:follow', { + logContext, + didBecomeMutual: profile.viewer + ? Boolean(profile.viewer.followedBy) + : undefined, + followeeClout: toClout(profile.followersCount), + followerClout: toClout(ownProfile?.followersCount), + }) return await getAgent().follow(did) }, onSuccess(data, variables) { @@ -530,3 +544,12 @@ export function* findAllProfilesInQueryData( } } } + +export function findProfileQueryData( + queryClient: QueryClient, + did: string, +): AppBskyActorDefs.ProfileViewDetailed | undefined { + return queryClient.getQueryData( + RQKEY(did), + ) +}