[Statsig] Track like/follow metadata (#3435)
* Track becoming mutuals * Track poster/liker status * Track post and followee clout * Track follower and liker clout * Extract utilityzio/stable
parent
8188f61e7d
commit
887fedabea
|
@ -67,6 +67,10 @@ export type LogEvents = {
|
||||||
logContext: 'Composer'
|
logContext: 'Composer'
|
||||||
}
|
}
|
||||||
'post:like': {
|
'post:like': {
|
||||||
|
doesLikerFollowPoster: boolean | undefined
|
||||||
|
doesPosterFollowLiker: boolean | undefined
|
||||||
|
likerClout: number | undefined
|
||||||
|
postClout: number | undefined
|
||||||
logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
|
logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
|
||||||
}
|
}
|
||||||
'post:repost': {
|
'post:repost': {
|
||||||
|
@ -79,6 +83,9 @@ export type LogEvents = {
|
||||||
logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
|
logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
|
||||||
}
|
}
|
||||||
'profile:follow': {
|
'profile:follow': {
|
||||||
|
didBecomeMutual: boolean | undefined
|
||||||
|
followeeClout: number | undefined
|
||||||
|
followerClout: number | undefined
|
||||||
logContext:
|
logContext:
|
||||||
| 'RecommendedFollowsItem'
|
| 'RecommendedFollowsItem'
|
||||||
| 'PostThreadItem'
|
| 'PostThreadItem'
|
||||||
|
|
|
@ -43,6 +43,14 @@ export function attachRouteToLogEvents(
|
||||||
getCurrentRouteName = getRouteName
|
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<E extends keyof LogEvents>(
|
export function logEvent<E extends keyof LogEvents>(
|
||||||
eventName: E & string,
|
eventName: E & string,
|
||||||
rawMetadata: LogEvents[E] & FlatJSONRecord,
|
rawMetadata: LogEvents[E] & FlatJSONRecord,
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import {useCallback} from 'react'
|
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 {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {track} from '#/lib/analytics/analytics'
|
import {track} from '#/lib/analytics/analytics'
|
||||||
import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
|
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 {updatePostShadow} from '#/state/cache/post-shadow'
|
||||||
import {Shadow} from '#/state/cache/types'
|
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'
|
const RQKEY_ROOT = 'post'
|
||||||
export const RQKEY = (postUri: string) => [RQKEY_ROOT, postUri]
|
export const RQKEY = (postUri: string) => [RQKEY_ROOT, postUri]
|
||||||
|
@ -68,7 +69,7 @@ export function usePostLikeMutationQueue(
|
||||||
const postUri = post.uri
|
const postUri = post.uri
|
||||||
const postCid = post.cid
|
const postCid = post.cid
|
||||||
const initialLikeUri = post.viewer?.like
|
const initialLikeUri = post.viewer?.like
|
||||||
const likeMutation = usePostLikeMutation(logContext)
|
const likeMutation = usePostLikeMutation(logContext, post)
|
||||||
const unlikeMutation = usePostUnlikeMutation(logContext)
|
const unlikeMutation = usePostUnlikeMutation(logContext)
|
||||||
|
|
||||||
const queueToggle = useToggleMutationQueue({
|
const queueToggle = useToggleMutationQueue({
|
||||||
|
@ -117,15 +118,40 @@ export function usePostLikeMutationQueue(
|
||||||
return [queueLike, queueUnlike]
|
return [queueLike, queueUnlike]
|
||||||
}
|
}
|
||||||
|
|
||||||
function usePostLikeMutation(logContext: LogEvents['post:like']['logContext']) {
|
function usePostLikeMutation(
|
||||||
|
logContext: LogEvents['post:like']['logContext'],
|
||||||
|
post: Shadow<AppBskyFeedDefs.PostView>,
|
||||||
|
) {
|
||||||
|
const {currentAccount} = useSession()
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
const postAuthor = post.author
|
||||||
return useMutation<
|
return useMutation<
|
||||||
{uri: string}, // responds with the uri of the like
|
{uri: string}, // responds with the uri of the like
|
||||||
Error,
|
Error,
|
||||||
{uri: string; cid: string} // the post's uri and cid
|
{uri: string; cid: string} // the post's uri and cid
|
||||||
>({
|
>({
|
||||||
mutationFn: post => {
|
mutationFn: ({uri, cid}) => {
|
||||||
logEvent('post:like', {logContext})
|
let ownProfile: AppBskyActorDefs.ProfileViewDetailed | undefined
|
||||||
return getAgent().like(post.uri, post.cid)
|
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() {
|
onSuccess() {
|
||||||
track('Post:Like')
|
track('Post:Like')
|
||||||
|
|
|
@ -20,7 +20,7 @@ import {track} from '#/lib/analytics/analytics'
|
||||||
import {uploadBlob} from '#/lib/api'
|
import {uploadBlob} from '#/lib/api'
|
||||||
import {until} from '#/lib/async/until'
|
import {until} from '#/lib/async/until'
|
||||||
import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
|
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 {Shadow} from '#/state/cache/types'
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
import {resetProfilePostsQueries} from '#/state/queries/post-feed'
|
import {resetProfilePostsQueries} from '#/state/queries/post-feed'
|
||||||
|
@ -202,7 +202,7 @@ export function useProfileFollowMutationQueue(
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const did = profile.did
|
const did = profile.did
|
||||||
const initialFollowingUri = profile.viewer?.following
|
const initialFollowingUri = profile.viewer?.following
|
||||||
const followMutation = useProfileFollowMutation(logContext)
|
const followMutation = useProfileFollowMutation(logContext, profile)
|
||||||
const unfollowMutation = useProfileUnfollowMutation(logContext)
|
const unfollowMutation = useProfileUnfollowMutation(logContext)
|
||||||
|
|
||||||
const queueToggle = useToggleMutationQueue({
|
const queueToggle = useToggleMutationQueue({
|
||||||
|
@ -252,10 +252,24 @@ export function useProfileFollowMutationQueue(
|
||||||
|
|
||||||
function useProfileFollowMutation(
|
function useProfileFollowMutation(
|
||||||
logContext: LogEvents['profile:follow']['logContext'],
|
logContext: LogEvents['profile:follow']['logContext'],
|
||||||
|
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>,
|
||||||
) {
|
) {
|
||||||
|
const {currentAccount} = useSession()
|
||||||
|
const queryClient = useQueryClient()
|
||||||
return useMutation<{uri: string; cid: string}, Error, {did: string}>({
|
return useMutation<{uri: string; cid: string}, Error, {did: string}>({
|
||||||
mutationFn: async ({did}) => {
|
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)
|
return await getAgent().follow(did)
|
||||||
},
|
},
|
||||||
onSuccess(data, variables) {
|
onSuccess(data, variables) {
|
||||||
|
@ -530,3 +544,12 @@ export function* findAllProfilesInQueryData(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function findProfileQueryData(
|
||||||
|
queryClient: QueryClient,
|
||||||
|
did: string,
|
||||||
|
): AppBskyActorDefs.ProfileViewDetailed | undefined {
|
||||||
|
return queryClient.getQueryData<AppBskyActorDefs.ProfileViewDetailed>(
|
||||||
|
RQKEY(did),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue