[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:
+38
-40
@@ -11,7 +11,6 @@ import {type AppBskyFeedDefs} from '@atproto/api'
|
||||
import throttle from 'lodash.throttle'
|
||||
|
||||
import {PROD_FEEDS, STAGING_FEEDS} from '#/lib/constants'
|
||||
import {Logger} from '#/logger'
|
||||
import {
|
||||
type FeedSourceFeedInfo,
|
||||
type FeedSourceInfo,
|
||||
@@ -22,6 +21,7 @@ import {
|
||||
type FeedPostSliceItem,
|
||||
} from '#/state/queries/post-feed'
|
||||
import {getItemsForFeedback} from '#/view/com/posts/PostFeed'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {useAgent} from './session'
|
||||
|
||||
export const FEEDBACK_FEEDS = [...PROD_FEEDS, ...STAGING_FEEDS]
|
||||
@@ -42,8 +42,6 @@ export const THIRD_PARTY_ALLOWED_INTERACTIONS = new Set<
|
||||
'app.bsky.feed.defs#interactionSeen',
|
||||
])
|
||||
|
||||
const logger = Logger.create(Logger.Context.FeedFeedback)
|
||||
|
||||
export type StateContext = {
|
||||
enabled: boolean
|
||||
onItemSeen: (item: any) => void
|
||||
@@ -65,6 +63,8 @@ export function useFeedFeedback(
|
||||
feedSourceInfo: FeedSourceInfo | undefined,
|
||||
hasSession: boolean,
|
||||
) {
|
||||
const ax = useAnalytics()
|
||||
const logger = ax.logger.useChild(ax.logger.Context.FeedFeedback)
|
||||
const agent = useAgent()
|
||||
|
||||
const feed =
|
||||
@@ -85,12 +85,45 @@ export function useFeedFeedback(
|
||||
WeakSet<FeedPostSliceItem | AppBskyFeedDefs.Interaction>
|
||||
>(new WeakSet())
|
||||
|
||||
const flushEvents = useCallback(
|
||||
(stats: AggregatedStats | null, feedDescriptor: string) => {
|
||||
if (stats === null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (stats.clickthroughCount > 0) {
|
||||
ax.metric('feed:clickthrough', {
|
||||
count: stats.clickthroughCount,
|
||||
feed: feedDescriptor,
|
||||
})
|
||||
stats.clickthroughCount = 0
|
||||
}
|
||||
|
||||
if (stats.engagedCount > 0) {
|
||||
ax.metric('feed:engaged', {
|
||||
count: stats.engagedCount,
|
||||
feed: feedDescriptor,
|
||||
})
|
||||
stats.engagedCount = 0
|
||||
}
|
||||
|
||||
if (stats.seenCount > 0) {
|
||||
ax.metric('feed:seen', {
|
||||
count: stats.seenCount,
|
||||
feed: feedDescriptor,
|
||||
})
|
||||
stats.seenCount = 0
|
||||
}
|
||||
},
|
||||
[ax],
|
||||
)
|
||||
|
||||
const aggregatedStats = useRef<AggregatedStats | null>(null)
|
||||
const throttledFlushAggregatedStats = useMemo(
|
||||
() =>
|
||||
throttle(
|
||||
() =>
|
||||
flushToStatsig(
|
||||
flushEvents(
|
||||
aggregatedStats.current,
|
||||
feed?.feedDescriptor ?? 'unknown',
|
||||
),
|
||||
@@ -100,7 +133,7 @@ export function useFeedFeedback(
|
||||
trailing: true,
|
||||
},
|
||||
),
|
||||
[feed?.feedDescriptor],
|
||||
[feed?.feedDescriptor, flushEvents],
|
||||
)
|
||||
|
||||
const sendToFeedNoDelay = useCallback(() => {
|
||||
@@ -130,7 +163,6 @@ export function useFeedFeedback(
|
||||
)
|
||||
.catch(() => {}) // ignore upstream errors
|
||||
|
||||
// Send to Statsig
|
||||
if (aggregatedStats.current === null) {
|
||||
aggregatedStats.current = createAggregatedStats()
|
||||
}
|
||||
@@ -299,37 +331,3 @@ function sendOrAggregateInteractionsForStats(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function flushToStatsig(stats: AggregatedStats | null, feedDescriptor: string) {
|
||||
if (stats === null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (stats.clickthroughCount > 0) {
|
||||
logger.metric('feed:clickthrough', {
|
||||
count: stats.clickthroughCount,
|
||||
feed: feedDescriptor,
|
||||
})
|
||||
stats.clickthroughCount = 0
|
||||
}
|
||||
|
||||
if (stats.engagedCount > 0) {
|
||||
logger.metric('feed:engaged', {
|
||||
count: stats.engagedCount,
|
||||
feed: feedDescriptor,
|
||||
})
|
||||
stats.engagedCount = 0
|
||||
}
|
||||
|
||||
if (stats.seenCount > 0) {
|
||||
logger.metric(
|
||||
'feed:seen',
|
||||
{
|
||||
count: stats.seenCount,
|
||||
feed: feedDescriptor,
|
||||
},
|
||||
{statsig: false},
|
||||
)
|
||||
stats.seenCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import {type ChatBskyConvoDefs} from '@atproto/api'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {useAppState} from '#/lib/hooks/useAppState'
|
||||
import {useAppState} from '#/lib/appState'
|
||||
import {Convo} from '#/state/messages/convo/agent'
|
||||
import {
|
||||
type ConvoParams,
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react'
|
||||
|
||||
import {type AppLanguage} from '#/locale/languages'
|
||||
import * as persisted from '#/state/persisted'
|
||||
import {AnalyticsContext, utils} from '#/analytics'
|
||||
|
||||
type SetStateCb = (
|
||||
s: persisted.Schema['languagePrefs'],
|
||||
@@ -124,7 +125,17 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
|
||||
return (
|
||||
<stateContext.Provider value={state}>
|
||||
<apiContext.Provider value={api}>{children}</apiContext.Provider>
|
||||
<apiContext.Provider value={api}>
|
||||
<AnalyticsContext
|
||||
metadata={utils.useMeta({
|
||||
preferences: {
|
||||
appLanguage: state.appLanguage,
|
||||
contentLanguages: state.contentLanguages,
|
||||
},
|
||||
})}>
|
||||
{children}
|
||||
</AnalyticsContext>
|
||||
</apiContext.Provider>
|
||||
</stateContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {createContext, useContext, useMemo} from 'react'
|
||||
|
||||
import {useGate} from '#/lib/statsig/statsig'
|
||||
import {useLanguagePrefs} from '#/state/preferences/languages'
|
||||
import {useServiceConfigQuery} from '#/state/queries/service-config'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_DEV} from '#/env'
|
||||
import {device} from '#/storage'
|
||||
|
||||
@@ -52,10 +52,6 @@ export function Provider({children}: {children: React.ReactNode}) {
|
||||
return {enabled: Boolean(cachedEnabled)}
|
||||
}
|
||||
|
||||
/*
|
||||
* Doing an extra check here to reduce hits to statsig. If it's disabled on
|
||||
* the server, we can exit early.
|
||||
*/
|
||||
const enabled = Boolean(config?.topicsEnabled)
|
||||
|
||||
// update cache
|
||||
@@ -107,10 +103,10 @@ export function useLiveNowConfig(): LiveNowConfig {
|
||||
}
|
||||
|
||||
export function useCanGoLive() {
|
||||
const gate = useGate()
|
||||
const ax = useAnalytics()
|
||||
const {hasSession} = useSession()
|
||||
if (!hasSession) return false
|
||||
return IS_DEV ? true : !gate('disable_live_now_beta')
|
||||
return IS_DEV ? true : !ax.features.enabled(ax.features.DisableLiveNowBeta)
|
||||
}
|
||||
|
||||
export function useCheckEmailConfirmed() {
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
PUBLIC_BSKY_SERVICE,
|
||||
TIMELINE_SAVED_FEED,
|
||||
} from '#/lib/constants'
|
||||
import {tryFetchGates} from '#/lib/statsig/statsig'
|
||||
import {getAge} from '#/lib/strings/time'
|
||||
import {logger} from '#/logger'
|
||||
import {snoozeBirthdateUpdateAllowedForDid} from '#/state/birthdate'
|
||||
@@ -32,6 +31,7 @@ import {
|
||||
setBirthdateForDid,
|
||||
setCreatedAtForDid,
|
||||
} from '#/ageAssurance/data'
|
||||
import {features} from '#/analytics'
|
||||
import {emitNetworkConfirmed, emitNetworkLost} from '../events'
|
||||
import {addSessionErrorLog} from './logging'
|
||||
import {
|
||||
@@ -63,7 +63,9 @@ export async function createAgentAndResume(
|
||||
if (storedAccount.pdsUrl) {
|
||||
agent.sessionManager.pdsUrl = new URL(storedAccount.pdsUrl)
|
||||
}
|
||||
const gates = tryFetchGates(storedAccount.did, 'prefer-low-latency')
|
||||
const gates = features.refresh({
|
||||
strategy: 'prefer-low-latency',
|
||||
})
|
||||
const moderation = configureModerationForAccount(agent, storedAccount)
|
||||
const prevSession: AtpSessionData = sessionAccountToSession(storedAccount)
|
||||
if (isSessionExpired(storedAccount)) {
|
||||
@@ -123,7 +125,7 @@ export async function createAgentAndLogin(
|
||||
})
|
||||
|
||||
const account = agentToSessionAccountOrThrow(agent)
|
||||
const gates = tryFetchGates(account.did, 'prefer-fresh-gates')
|
||||
const gates = features.refresh({strategy: 'prefer-fresh-gates'})
|
||||
const moderation = configureModerationForAccount(agent, account)
|
||||
const aa = prefetchAgeAssuranceData({agent})
|
||||
|
||||
@@ -171,7 +173,7 @@ export async function createAgentAndCreateAccount(
|
||||
verificationCode,
|
||||
})
|
||||
const account = agentToSessionAccountOrThrow(agent)
|
||||
const gates = tryFetchGates(account.did, 'prefer-fresh-gates')
|
||||
const gates = features.refresh({strategy: 'prefer-fresh-gates'})
|
||||
const moderation = configureModerationForAccount(agent, account)
|
||||
|
||||
const createdAt = new Date().toISOString()
|
||||
@@ -322,7 +324,7 @@ export function agentToSessionAccount(
|
||||
return undefined
|
||||
}
|
||||
return {
|
||||
service: agent.service.toString(),
|
||||
service: agent.serviceUrl.toString(),
|
||||
did: agent.session.did,
|
||||
handle: agent.session.handle,
|
||||
email: agent.session.email,
|
||||
@@ -332,7 +334,7 @@ export function agentToSessionAccount(
|
||||
accessJwt: agent.session.accessJwt,
|
||||
signupQueued: isSignupQueued(agent.session.accessJwt),
|
||||
active: agent.session.active,
|
||||
status: agent.session.status as SessionAccount['status'],
|
||||
status: agent.session.status,
|
||||
pdsUrl: agent.pdsUrl?.toString(),
|
||||
isSelfHosted: !agent.serviceUrl.toString().startsWith(BSKY_SERVICE),
|
||||
}
|
||||
|
||||
+38
-14
@@ -4,6 +4,7 @@ import {type AtpSessionEvent, type BskyAgent} from '@atproto/api'
|
||||
import * as persisted from '#/state/persisted'
|
||||
import {useCloseAllActiveElements} from '#/state/util'
|
||||
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
|
||||
import {AnalyticsContext, useAnalyticsBase, utils} from '#/analytics'
|
||||
import {IS_WEB} from '#/env'
|
||||
import {emitSessionDropped} from '../events'
|
||||
import {
|
||||
@@ -15,11 +16,9 @@ import {
|
||||
sessionAccountToSession,
|
||||
} from './agent'
|
||||
import {type Action, getInitialState, reducer, type State} from './reducer'
|
||||
|
||||
export {isSignupQueued} from './util'
|
||||
import {addSessionDebugLog} from './logging'
|
||||
export type {SessionAccount} from '#/state/session/types'
|
||||
import {logger} from '#/logger'
|
||||
import {
|
||||
type SessionApiContext,
|
||||
type SessionStateContext,
|
||||
@@ -93,6 +92,7 @@ class SessionStore {
|
||||
}
|
||||
|
||||
export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
const ax = useAnalyticsBase()
|
||||
const cancelPendingTask = useOneTaskAtATime()
|
||||
const [store] = React.useState(() => new SessionStore())
|
||||
const state = React.useSyncExternalStore(store.subscribe, store.getState)
|
||||
@@ -119,7 +119,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
async (params, metrics) => {
|
||||
addSessionDebugLog({type: 'method:start', method: 'createAccount'})
|
||||
const signal = cancelPendingTask()
|
||||
logger.metric('account:create:begin', {}, {statsig: true})
|
||||
ax.metric('account:create:begin', {})
|
||||
const {agent, account} = await createAgentAndCreateAccount(
|
||||
params,
|
||||
onAgentSessionChange,
|
||||
@@ -133,10 +133,12 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
newAgent: agent,
|
||||
newAccount: account,
|
||||
})
|
||||
logger.metric('account:create:success', metrics, {statsig: true})
|
||||
ax.metric('account:create:success', metrics, {
|
||||
session: utils.accountToSessionMetadata(account),
|
||||
})
|
||||
addSessionDebugLog({type: 'method:end', method: 'createAccount', account})
|
||||
},
|
||||
[store, onAgentSessionChange, cancelPendingTask],
|
||||
[ax, store, onAgentSessionChange, cancelPendingTask],
|
||||
)
|
||||
|
||||
const login = React.useCallback<SessionApiContext['login']>(
|
||||
@@ -156,14 +158,14 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
newAgent: agent,
|
||||
newAccount: account,
|
||||
})
|
||||
logger.metric(
|
||||
ax.metric(
|
||||
'account:loggedIn',
|
||||
{logContext, withPassword: true},
|
||||
{statsig: true},
|
||||
{session: utils.accountToSessionMetadata(account)},
|
||||
)
|
||||
addSessionDebugLog({type: 'method:end', method: 'login', account})
|
||||
},
|
||||
[store, onAgentSessionChange, cancelPendingTask],
|
||||
[ax, store, onAgentSessionChange, cancelPendingTask],
|
||||
)
|
||||
|
||||
const logoutCurrentAccount = React.useCallback<
|
||||
@@ -176,10 +178,16 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
store.dispatch({
|
||||
type: 'logged-out-current-account',
|
||||
})
|
||||
logger.metric(
|
||||
ax.metric(
|
||||
'account:loggedOut',
|
||||
{logContext, scope: 'current'},
|
||||
{statsig: true},
|
||||
{
|
||||
session: utils.accountToSessionMetadata(
|
||||
prevState.accounts.find(
|
||||
a => a.did === prevState.currentAgentState.did,
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
addSessionDebugLog({type: 'method:end', method: 'logout'})
|
||||
if (prevState.currentAgentState.did) {
|
||||
@@ -188,7 +196,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
// reset onboarding flow on logout
|
||||
onboardingDispatch({type: 'skip'})
|
||||
},
|
||||
[store, cancelPendingTask, onboardingDispatch],
|
||||
[ax, store, cancelPendingTask, onboardingDispatch],
|
||||
)
|
||||
|
||||
const logoutEveryAccount = React.useCallback<
|
||||
@@ -197,13 +205,20 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
logContext => {
|
||||
addSessionDebugLog({type: 'method:start', method: 'logout'})
|
||||
cancelPendingTask()
|
||||
const prevState = store.getState()
|
||||
store.dispatch({
|
||||
type: 'logged-out-every-account',
|
||||
})
|
||||
logger.metric(
|
||||
ax.metric(
|
||||
'account:loggedOut',
|
||||
{logContext, scope: 'every'},
|
||||
{statsig: true},
|
||||
{
|
||||
session: utils.accountToSessionMetadata(
|
||||
prevState.accounts.find(
|
||||
a => a.did === prevState.currentAgentState.did,
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
addSessionDebugLog({type: 'method:end', method: 'logout'})
|
||||
clearAgeAssuranceData()
|
||||
@@ -359,7 +374,16 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
return (
|
||||
<AgentContext.Provider value={agent}>
|
||||
<StateContext.Provider value={stateContext}>
|
||||
<ApiContext.Provider value={api}>{children}</ApiContext.Provider>
|
||||
<ApiContext.Provider value={api}>
|
||||
<AnalyticsContext
|
||||
metadata={utils.useMeta({
|
||||
session: utils.accountToSessionMetadata(
|
||||
stateContext.currentAccount,
|
||||
),
|
||||
})}>
|
||||
{children}
|
||||
</AnalyticsContext>
|
||||
</ApiContext.Provider>
|
||||
</StateContext.Provider>
|
||||
</AgentContext.Provider>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {type LogEvents} from '#/lib/statsig/statsig'
|
||||
import {type PersistedAccount} from '#/state/persisted'
|
||||
import {type Metrics} from '#/analytics/metrics'
|
||||
|
||||
export type SessionAccount = PersistedAccount
|
||||
|
||||
@@ -21,7 +21,7 @@ export type SessionApiContext = {
|
||||
verificationPhone?: string
|
||||
verificationCode?: string
|
||||
},
|
||||
metrics: LogEvents['account:create:success'],
|
||||
metrics: Metrics['account:create:success'],
|
||||
) => Promise<void>
|
||||
login: (
|
||||
props: {
|
||||
@@ -30,13 +30,13 @@ export type SessionApiContext = {
|
||||
password: string
|
||||
authFactorToken?: string | undefined
|
||||
},
|
||||
logContext: LogEvents['account:loggedIn']['logContext'],
|
||||
logContext: Metrics['account:loggedIn']['logContext'],
|
||||
) => Promise<void>
|
||||
logoutCurrentAccount: (
|
||||
logContext: LogEvents['account:loggedOut']['logContext'],
|
||||
logContext: Metrics['account:loggedOut']['logContext'],
|
||||
) => void
|
||||
logoutEveryAccount: (
|
||||
logContext: LogEvents['account:loggedOut']['logContext'],
|
||||
logContext: Metrics['account:loggedOut']['logContext'],
|
||||
) => void
|
||||
resumeSession: (
|
||||
account: SessionAccount,
|
||||
|
||||
@@ -2,11 +2,11 @@ import React, {useMemo} from 'react'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
import {
|
||||
ProgressGuideToast,
|
||||
type ProgressGuideToastRef,
|
||||
} from '#/components/ProgressGuide/Toast'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {
|
||||
usePreferencesQuery,
|
||||
useSetActiveProgressGuideMutation,
|
||||
@@ -71,6 +71,7 @@ export function useProgressGuideControls() {
|
||||
}
|
||||
|
||||
export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
const ax = useAnalytics()
|
||||
const {_} = useLingui()
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
const {mutateAsync, variables, isPending} =
|
||||
@@ -140,7 +141,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
endProgressGuide() {
|
||||
setLocalGuideState(undefined)
|
||||
mutateAsync(undefined)
|
||||
logEvent('progressGuide:hide', {})
|
||||
ax.metric('progressGuide:hide', {})
|
||||
},
|
||||
|
||||
captureAction(action: ProgressGuideAction, count = 1) {
|
||||
@@ -202,7 +203,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
mutateAsync(guide?.isComplete ? undefined : guide)
|
||||
},
|
||||
}
|
||||
}, [activeProgressGuide, mutateAsync, setLocalGuideState])
|
||||
}, [ax, activeProgressGuide, mutateAsync, setLocalGuideState])
|
||||
|
||||
return (
|
||||
<ProgressGuideContext.Provider value={localGuideState}>
|
||||
|
||||
Reference in New Issue
Block a user