[APP-1782] Analytics migration (#9734)
* WIP * Clean up growthbook code, integrate into init and sessions * Move everything out of React * Add metrics client * Move to separate file * Shared metadata cache * Ensure we update metadata when session ID changes * Ensure userMetadata is cleared when logging out * WIP revamp * Integrate feature gates into analytics context * Clean up old code * Fix useMeta util * Some comments and cleanup * Add logger to base analytics context * Refactor current route handling * Rip out LogEvent from navigation * Update tracking endpoint * Migrate toClout * Clear out statsig client * Add todo, reset logger readme * Ope fix statsig noop * Refactor logging in feed-feedback, add debug logging to metrics client * Remove LogEvents alias for Metrics * Prefer root package export * Remove Metrics alias from logger * [APP-1782] Migrate to new analytics APIs (#9735) * Migrate logEvent to useAnalytics * Migrate logger.metric to useAnalytics * Migrate tricky spot, fix types * Migrate remaining tricky spot * Missed one * Remove metric() from logger * Migrate useGate to useAnalytics * Remove all other StatSig mentions * Update event payload * Update logger tests * Mock expo method * Fix session ID bug * Add session ID test * Add test for metrics client * Clarify intent * Clean up core analytics file * Clean up the call once utils * Fix TODO * Fix TODO * Fix TODO * Fix TODO * Fix TODO * Remove debug code * Fix navigation context * OK nav context is not working, todo * Checkpoint: works but feels hacky * Fix navigation context issue * Improve feature API * Improve metric logging * Update logger tests
This commit is contained in:
@@ -7,8 +7,8 @@ import {
|
||||
PUBLIC_BSKY_SERVICE,
|
||||
} from '#/lib/constants'
|
||||
import {createFullHandle} from '#/lib/strings/handles'
|
||||
import {logger} from '#/logger'
|
||||
import {useDebouncedValue} from '#/components/live/utils'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {Agent} from '../session/agent'
|
||||
|
||||
@@ -36,6 +36,7 @@ export function useHandleAvailabilityQuery(
|
||||
},
|
||||
debounceDelayMs = 500,
|
||||
) {
|
||||
const ax = useAnalytics()
|
||||
const name = username.trim()
|
||||
const debouncedHandle = useDebouncedValue(name, debounceDelayMs)
|
||||
|
||||
@@ -51,11 +52,16 @@ export function useHandleAvailabilityQuery(
|
||||
),
|
||||
queryFn: async () => {
|
||||
const handle = createFullHandle(name, serviceDomain)
|
||||
return await checkHandleAvailability(handle, serviceDid, {
|
||||
const res = await checkHandleAvailability(handle, serviceDid, {
|
||||
email,
|
||||
birthDate,
|
||||
typeahead: true,
|
||||
})
|
||||
if (res.available) {
|
||||
ax.metric('signup:handleAvailable', {typeahead: true})
|
||||
} else {
|
||||
ax.metric('signup:handleTaken', {typeahead: true})
|
||||
}
|
||||
return res
|
||||
},
|
||||
}),
|
||||
}
|
||||
@@ -67,11 +73,9 @@ export async function checkHandleAvailability(
|
||||
{
|
||||
email,
|
||||
birthDate,
|
||||
typeahead,
|
||||
}: {
|
||||
email?: string
|
||||
birthDate?: string
|
||||
typeahead?: boolean
|
||||
},
|
||||
) {
|
||||
if (serviceDid === BSKY_SERVICE_DID) {
|
||||
@@ -89,8 +93,6 @@ export async function checkHandleAvailability(
|
||||
ComAtprotoTempCheckHandleAvailability.isResultAvailable,
|
||||
)
|
||||
) {
|
||||
logger.metric('signup:handleAvailable', {typeahead}, {statsig: true})
|
||||
|
||||
return {available: true} as const
|
||||
} else if (
|
||||
bsky.dangerousIsType<ComAtprotoTempCheckHandleAvailability.ResultUnavailable>(
|
||||
@@ -98,7 +100,6 @@ export async function checkHandleAvailability(
|
||||
ComAtprotoTempCheckHandleAvailability.isResultUnavailable,
|
||||
)
|
||||
) {
|
||||
logger.metric('signup:handleTaken', {typeahead}, {statsig: true})
|
||||
return {
|
||||
available: false,
|
||||
suggestions: data.result.suggestions,
|
||||
@@ -117,11 +118,9 @@ export async function checkHandleAvailability(
|
||||
})
|
||||
|
||||
if (res.data.did) {
|
||||
logger.metric('signup:handleTaken', {typeahead}, {statsig: true})
|
||||
return {available: false} as const
|
||||
}
|
||||
} catch {}
|
||||
logger.metric('signup:handleAvailable', {typeahead}, {statsig: true})
|
||||
return {available: true} as const
|
||||
}
|
||||
}
|
||||
|
||||
+16
-14
@@ -3,12 +3,12 @@ import {type AppBskyActorDefs, type AppBskyFeedDefs, AtUri} from '@atproto/api'
|
||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
|
||||
import {type LogEvents, toClout} from '#/lib/statsig/statsig'
|
||||
import {logger} from '#/logger'
|
||||
import {updatePostShadow} from '#/state/cache/post-shadow'
|
||||
import {type Shadow} from '#/state/cache/types'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import * as userActionHistory from '#/state/userActionHistory'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {type Metrics, toClout} from '#/analytics/metrics'
|
||||
import {useIsThreadMuted, useSetThreadMute} from '../cache/thread-mutes'
|
||||
import {findProfileQueryData} from './profile'
|
||||
|
||||
@@ -103,8 +103,7 @@ export function usePostLikeMutationQueue(
|
||||
post: Shadow<AppBskyFeedDefs.PostView>,
|
||||
viaRepost: {uri: string; cid: string} | undefined,
|
||||
feedDescriptor: string | undefined,
|
||||
logContext: LogEvents['post:like']['logContext'] &
|
||||
LogEvents['post:unlike']['logContext'],
|
||||
logContext: Metrics['post:like']['logContext'],
|
||||
) {
|
||||
const queryClient = useQueryClient()
|
||||
const postUri = post.uri
|
||||
@@ -164,13 +163,14 @@ export function usePostLikeMutationQueue(
|
||||
|
||||
function usePostLikeMutation(
|
||||
feedDescriptor: string | undefined,
|
||||
logContext: LogEvents['post:like']['logContext'],
|
||||
logContext: Metrics['post:like']['logContext'],
|
||||
post: Shadow<AppBskyFeedDefs.PostView>,
|
||||
) {
|
||||
const {currentAccount} = useSession()
|
||||
const queryClient = useQueryClient()
|
||||
const postAuthor = post.author
|
||||
const agent = useAgent()
|
||||
const ax = useAnalytics()
|
||||
return useMutation<
|
||||
{uri: string}, // responds with the uri of the like
|
||||
Error,
|
||||
@@ -181,7 +181,7 @@ function usePostLikeMutation(
|
||||
if (currentAccount) {
|
||||
ownProfile = findProfileQueryData(queryClient, currentAccount.did)
|
||||
}
|
||||
logger.metric('post:like', {
|
||||
ax.metric('post:like', {
|
||||
uri,
|
||||
authorDid: postAuthor.did,
|
||||
logContext,
|
||||
@@ -207,13 +207,14 @@ function usePostLikeMutation(
|
||||
|
||||
function usePostUnlikeMutation(
|
||||
feedDescriptor: string | undefined,
|
||||
logContext: LogEvents['post:unlike']['logContext'],
|
||||
logContext: Metrics['post:unlike']['logContext'],
|
||||
post: Shadow<AppBskyFeedDefs.PostView>,
|
||||
) {
|
||||
const agent = useAgent()
|
||||
const ax = useAnalytics()
|
||||
return useMutation<void, Error, {postUri: string; likeUri: string}>({
|
||||
mutationFn: ({postUri, likeUri}) => {
|
||||
logger.metric('post:unlike', {
|
||||
ax.metric('post:unlike', {
|
||||
uri: postUri,
|
||||
authorDid: post.author.did,
|
||||
logContext,
|
||||
@@ -228,8 +229,7 @@ export function usePostRepostMutationQueue(
|
||||
post: Shadow<AppBskyFeedDefs.PostView>,
|
||||
viaRepost: {uri: string; cid: string} | undefined,
|
||||
feedDescriptor: string | undefined,
|
||||
logContext: LogEvents['post:repost']['logContext'] &
|
||||
LogEvents['post:unrepost']['logContext'],
|
||||
logContext: Metrics['post:repost']['logContext'],
|
||||
) {
|
||||
const queryClient = useQueryClient()
|
||||
const postUri = post.uri
|
||||
@@ -291,17 +291,18 @@ export function usePostRepostMutationQueue(
|
||||
|
||||
function usePostRepostMutation(
|
||||
feedDescriptor: string | undefined,
|
||||
logContext: LogEvents['post:repost']['logContext'],
|
||||
logContext: Metrics['post:repost']['logContext'],
|
||||
post: Shadow<AppBskyFeedDefs.PostView>,
|
||||
) {
|
||||
const agent = useAgent()
|
||||
const ax = useAnalytics()
|
||||
return useMutation<
|
||||
{uri: string}, // responds with the uri of the repost
|
||||
Error,
|
||||
{uri: string; cid: string; via?: {uri: string; cid: string}} // the post's uri and cid, and the repost uri/cid if present
|
||||
>({
|
||||
mutationFn: ({uri, cid, via}) => {
|
||||
logger.metric('post:repost', {
|
||||
ax.metric('post:repost', {
|
||||
uri,
|
||||
authorDid: post.author.did,
|
||||
logContext,
|
||||
@@ -314,13 +315,14 @@ function usePostRepostMutation(
|
||||
|
||||
function usePostUnrepostMutation(
|
||||
feedDescriptor: string | undefined,
|
||||
logContext: LogEvents['post:unrepost']['logContext'],
|
||||
logContext: Metrics['post:unrepost']['logContext'],
|
||||
post: Shadow<AppBskyFeedDefs.PostView>,
|
||||
) {
|
||||
const agent = useAgent()
|
||||
const ax = useAnalytics()
|
||||
return useMutation<void, Error, {postUri: string; repostUri: string}>({
|
||||
mutationFn: ({postUri, repostUri}) => {
|
||||
logger.metric('post:unrepost', {
|
||||
ax.metric('post:unrepost', {
|
||||
uri: postUri,
|
||||
authorDid: post.author.did,
|
||||
logContext,
|
||||
|
||||
@@ -9,7 +9,6 @@ import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
import {PROD_DEFAULT_FEED} from '#/lib/constants'
|
||||
import {replaceEqualDeep} from '#/lib/functions'
|
||||
import {getAge} from '#/lib/strings/time'
|
||||
import {logger} from '#/logger'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {
|
||||
DEFAULT_HOME_FEED_PREFS,
|
||||
@@ -24,6 +23,7 @@ import {useAgent} from '#/state/session'
|
||||
import {saveLabelers} from '#/state/session/agent-config'
|
||||
import {useAgeAssurance} from '#/ageAssurance'
|
||||
import {makeAgeRestrictedModerationPrefs} from '#/ageAssurance/util'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
|
||||
export * from '#/state/queries/preferences/const'
|
||||
export * from '#/state/queries/preferences/moderation'
|
||||
@@ -110,6 +110,7 @@ export function useClearPreferencesMutation() {
|
||||
}
|
||||
|
||||
export function usePreferencesSetContentLabelMutation() {
|
||||
const ax = useAnalytics()
|
||||
const agent = useAgent()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
@@ -120,11 +121,7 @@ export function usePreferencesSetContentLabelMutation() {
|
||||
>({
|
||||
mutationFn: async ({label, visibility, labelerDid}) => {
|
||||
await agent.setContentLabelPref(label, visibility, labelerDid)
|
||||
logger.metric(
|
||||
'moderation:changeLabelPreference',
|
||||
{preference: visibility},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('moderation:changeLabelPreference', {preference: visibility})
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: preferencesQueryKey,
|
||||
@@ -417,6 +414,7 @@ export function useSetActiveProgressGuideMutation() {
|
||||
}
|
||||
|
||||
export function useSetVerificationPrefsMutation() {
|
||||
const ax = useAnalytics()
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
|
||||
@@ -424,9 +422,9 @@ export function useSetVerificationPrefsMutation() {
|
||||
mutationFn: async prefs => {
|
||||
await agent.setVerificationPrefs(prefs)
|
||||
if (prefs.hideBadges) {
|
||||
logger.metric('verification:settings:hideBadges', {}, {statsig: true})
|
||||
ax.metric('verification:settings:hideBadges', {})
|
||||
} else {
|
||||
logger.metric('verification:settings:unHideBadges', {}, {statsig: true})
|
||||
ax.metric('verification:settings:unHideBadges', {})
|
||||
}
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
|
||||
@@ -2,13 +2,13 @@ import {useCallback, useMemo, useRef, useState} from 'react'
|
||||
import {type AppBskyUnspeccedGetPostThreadV2} from '@atproto/api'
|
||||
import debounce from 'lodash.debounce'
|
||||
|
||||
import {OnceKey, useCallOnce} from '#/lib/hooks/useCallOnce'
|
||||
import {logger} from '#/logger'
|
||||
import {useCallOnce} from '#/lib/once'
|
||||
import {
|
||||
usePreferencesQuery,
|
||||
useSetThreadViewPreferencesMutation,
|
||||
} from '#/state/queries/preferences'
|
||||
import {type ThreadViewPreferences} from '#/state/queries/preferences/types'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {type Literal} from '#/types/utils'
|
||||
|
||||
export type ThreadSortOption = Literal<
|
||||
@@ -28,9 +28,10 @@ export type ThreadPreferences = {
|
||||
export function useThreadPreferences({
|
||||
save,
|
||||
}: {save?: boolean} = {}): ThreadPreferences {
|
||||
const ax = useAnalytics()
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
const serverPrefs = preferences?.threadViewPrefs
|
||||
const once = useCallOnce(OnceKey.PreferencesThread)
|
||||
const once = useCallOnce()
|
||||
|
||||
/*
|
||||
* Create local state representations of server state
|
||||
@@ -61,7 +62,7 @@ export function useThreadPreferences({
|
||||
)
|
||||
|
||||
once(() => {
|
||||
logger.metric('thread:preferences:load', {
|
||||
ax.metric('thread:preferences:load', {
|
||||
sort: serverPrefs.sort,
|
||||
view: serverPrefs.lab_treeViewEnabled ? 'tree' : 'linear',
|
||||
})
|
||||
@@ -76,12 +77,12 @@ export function useThreadPreferences({
|
||||
try {
|
||||
setIsSaving(true)
|
||||
await mutateAsync(prefs)
|
||||
logger.metric('thread:preferences:update', {
|
||||
ax.metric('thread:preferences:update', {
|
||||
sort: prefs.sort,
|
||||
view: prefs.lab_treeViewEnabled ? 'tree' : 'linear',
|
||||
})
|
||||
} catch (e) {
|
||||
logger.error('useThreadPreferences failed to save', {
|
||||
ax.logger.error('useThreadPreferences failed to save', {
|
||||
safeMessage: e,
|
||||
})
|
||||
} finally {
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
import {uploadBlob} from '#/lib/api'
|
||||
import {until} from '#/lib/async/until'
|
||||
import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
|
||||
import {logEvent, type LogEvents, toClout} from '#/lib/statsig/statsig'
|
||||
import {updateProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {type Shadow} from '#/state/cache/types'
|
||||
import {type ImageMeta} from '#/state/gallery'
|
||||
@@ -36,6 +35,8 @@ import {
|
||||
import {useUpdateProfileVerificationCache} from '#/state/queries/verification/useUpdateProfileVerificationCache'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import * as userActionHistory from '#/state/userActionHistory'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {type Metrics, toClout} from '#/analytics/metrics'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
import {
|
||||
ProgressGuideAction,
|
||||
@@ -243,8 +244,7 @@ export function useProfileUpdateMutation() {
|
||||
|
||||
export function useProfileFollowMutationQueue(
|
||||
profile: Shadow<bsky.profile.AnyProfileView>,
|
||||
logContext: LogEvents['profile:follow']['logContext'] &
|
||||
LogEvents['profile:follow']['logContext'],
|
||||
logContext: Metrics['profile:follow']['logContext'],
|
||||
position?: number,
|
||||
contextProfileDid?: string,
|
||||
) {
|
||||
@@ -364,11 +364,12 @@ export function useProfileFollowMutationQueue(
|
||||
}
|
||||
|
||||
function useProfileFollowMutation(
|
||||
logContext: LogEvents['profile:follow']['logContext'],
|
||||
logContext: Metrics['profile:follow']['logContext'],
|
||||
profile: Shadow<bsky.profile.AnyProfileView>,
|
||||
position?: number,
|
||||
contextProfileDid?: string,
|
||||
) {
|
||||
const ax = useAnalytics()
|
||||
const {currentAccount} = useSession()
|
||||
const agent = useAgent()
|
||||
const queryClient = useQueryClient()
|
||||
@@ -381,7 +382,7 @@ function useProfileFollowMutation(
|
||||
ownProfile = findProfileQueryData(queryClient, currentAccount.did)
|
||||
}
|
||||
captureAction(ProgressGuideAction.Follow)
|
||||
logEvent('profile:follow', {
|
||||
ax.metric('profile:follow', {
|
||||
logContext,
|
||||
didBecomeMutual: profile.viewer
|
||||
? Boolean(profile.viewer.followedBy)
|
||||
@@ -401,12 +402,13 @@ function useProfileFollowMutation(
|
||||
}
|
||||
|
||||
function useProfileUnfollowMutation(
|
||||
logContext: LogEvents['profile:unfollow']['logContext'],
|
||||
logContext: Metrics['profile:unfollow']['logContext'],
|
||||
) {
|
||||
const ax = useAnalytics()
|
||||
const agent = useAgent()
|
||||
return useMutation<void, Error, {did: string; followUri: string}>({
|
||||
mutationFn: async ({followUri}) => {
|
||||
logEvent('profile:unfollow', {logContext})
|
||||
ax.metric('profile:unfollow', {logContext})
|
||||
return await agent.deleteFollow(followUri)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -2,12 +2,13 @@ import {type AppBskyActorGetProfile} from '@atproto/api'
|
||||
import {useMutation} from '@tanstack/react-query'
|
||||
|
||||
import {until} from '#/lib/async/until'
|
||||
import {logger} from '#/logger'
|
||||
import {useUpdateProfileVerificationCache} from '#/state/queries/verification/useUpdateProfileVerificationCache'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
|
||||
export function useVerificationCreateMutation() {
|
||||
const ax = useAnalytics()
|
||||
const agent = useAgent()
|
||||
const {currentAccount} = useSession()
|
||||
const updateProfileVerificationCache = useUpdateProfileVerificationCache()
|
||||
@@ -46,7 +47,7 @@ export function useVerificationCreateMutation() {
|
||||
)
|
||||
},
|
||||
async onSuccess(_, {profile}) {
|
||||
logger.metric('verification:create', {}, {statsig: true})
|
||||
ax.metric('verification:create', {})
|
||||
await updateProfileVerificationCache({profile})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -6,12 +6,13 @@ import {
|
||||
import {useMutation} from '@tanstack/react-query'
|
||||
|
||||
import {until} from '#/lib/async/until'
|
||||
import {logger} from '#/logger'
|
||||
import {useUpdateProfileVerificationCache} from '#/state/queries/verification/useUpdateProfileVerificationCache'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
|
||||
export function useVerificationsRemoveMutation() {
|
||||
const ax = useAnalytics()
|
||||
const agent = useAgent()
|
||||
const {currentAccount} = useSession()
|
||||
const updateProfileVerificationCache = useUpdateProfileVerificationCache()
|
||||
@@ -56,7 +57,7 @@ export function useVerificationsRemoveMutation() {
|
||||
)
|
||||
},
|
||||
async onSuccess(_, {profile}) {
|
||||
logger.metric('verification:revoke', {}, {statsig: true})
|
||||
ax.metric('verification:revoke', {})
|
||||
await updateProfileVerificationCache({profile})
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user