[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:
@@ -2,10 +2,11 @@ import {useCallback, useState} from 'react'
|
||||
import {LayoutAnimationConfig} from 'react-native-reanimated'
|
||||
import {SafeAreaView} from 'react-native-safe-area-context'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {useCallOnce} from '#/lib/once'
|
||||
import {FindContactsFlow} from '#/components/contacts/FindContactsFlow'
|
||||
import {type Action, type State} from '#/components/contacts/state'
|
||||
import {ScreenTransition} from '#/components/ScreenTransition'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {useOnboardingInternalState} from '../state'
|
||||
|
||||
export function StepFindContacts({
|
||||
@@ -16,6 +17,11 @@ export function StepFindContacts({
|
||||
flowDispatch: React.ActionDispatch<[Action]>
|
||||
}) {
|
||||
const {dispatch} = useOnboardingInternalState()
|
||||
const ax = useAnalytics()
|
||||
|
||||
useCallOnce(() => {
|
||||
ax.metric('onboarding:contacts:begin', {})
|
||||
})()
|
||||
|
||||
const [transitionDirection, setTransitionDirection] = useState<
|
||||
'Forward' | 'Backward'
|
||||
@@ -24,10 +30,10 @@ export function StepFindContacts({
|
||||
const isFinalStep = flowState.step === '4: view matches'
|
||||
const onSkip = useCallback(() => {
|
||||
if (!isFinalStep) {
|
||||
logger.metric('onboarding:contacts:skipPressed', {})
|
||||
ax.metric('onboarding:contacts:skipPressed', {})
|
||||
}
|
||||
dispatch({type: 'next'})
|
||||
}, [dispatch, isFinalStep])
|
||||
}, [dispatch, isFinalStep, ax])
|
||||
|
||||
const canGoBack = flowState.step === '2: verify number'
|
||||
const onBack = useCallback(() => {
|
||||
|
||||
@@ -5,11 +5,13 @@ import {useLingui} from '@lingui/react'
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
|
||||
import {urls} from '#/lib/constants'
|
||||
import {useCallOnce} from '#/lib/once'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Admonition} from '#/components/Admonition'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import {ContactsHeroImage} from '#/components/contacts/components/HeroImage'
|
||||
import {InlineLinkText} from '#/components/Link'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {
|
||||
OnboardingControls,
|
||||
OnboardingDescriptionText,
|
||||
@@ -19,9 +21,14 @@ import {
|
||||
import {useOnboardingInternalState} from '../state'
|
||||
|
||||
export function StepFindContactsIntro() {
|
||||
const ax = useAnalytics()
|
||||
const {_} = useLingui()
|
||||
const {dispatch} = useOnboardingInternalState()
|
||||
|
||||
useCallOnce(() => {
|
||||
ax.metric('onboarding:contacts:presented', {})
|
||||
})()
|
||||
|
||||
const {data: isAvailable, isSuccess} = useQuery({
|
||||
queryKey: ['contacts-available'],
|
||||
queryFn: async () => await Contacts.isAvailableAsync(),
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
VIDEO_SAVED_FEED,
|
||||
} from '#/lib/constants'
|
||||
import {useRequestNotificationsPermission} from '#/lib/notifications/notifications'
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
import {logger} from '#/logger'
|
||||
import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs'
|
||||
import {getAllListMembers} from '#/state/queries/list-members'
|
||||
@@ -46,12 +45,14 @@ import {atoms as a, useBreakpoints} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {ArrowRight_Stroke2_Corner0_Rounded as ArrowRight} from '#/components/icons/Arrow'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_WEB} from '#/env'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {ValuePropositionPager} from './ValuePropositionPager'
|
||||
|
||||
export function StepFinished() {
|
||||
const {state, dispatch} = useOnboardingInternalState()
|
||||
const ax = useAnalytics()
|
||||
const onboardDispatch = useOnboardingDispatch()
|
||||
const [saving, setSaving] = useState(false)
|
||||
const queryClient = useQueryClient()
|
||||
@@ -165,7 +166,7 @@ export function StepFinished() {
|
||||
return next
|
||||
})
|
||||
|
||||
logEvent('onboarding:finished:avatarResult', {
|
||||
ax.metric('onboarding:finished:avatarResult', {
|
||||
avatarResult: profileStepResults.isCreatedAvatar
|
||||
? 'created'
|
||||
: profileStepResults.image
|
||||
@@ -200,7 +201,7 @@ export function StepFinished() {
|
||||
startProgressGuide('follow-10')
|
||||
dispatch({type: 'finish'})
|
||||
onboardDispatch({type: 'finish'})
|
||||
logEvent('onboarding:finished:nextPressed', {
|
||||
ax.metric('onboarding:finished:nextPressed', {
|
||||
usedStarterPack: Boolean(starterPack),
|
||||
starterPackName:
|
||||
starterPack &&
|
||||
@@ -216,13 +217,14 @@ export function StepFinished() {
|
||||
feedsPinned: starterPack?.feeds?.length ?? 0,
|
||||
})
|
||||
if (starterPack && listItems?.length) {
|
||||
logEvent('starterPack:followAll', {
|
||||
ax.metric('starterPack:followAll', {
|
||||
logContext: 'Onboarding',
|
||||
starterPack: starterPack.uri,
|
||||
count: listItems?.length,
|
||||
})
|
||||
}
|
||||
}, [
|
||||
ax,
|
||||
queryClient,
|
||||
agent,
|
||||
dispatch,
|
||||
@@ -255,6 +257,7 @@ function ValueProposition({
|
||||
}) {
|
||||
const [subStep, setSubStep] = useState<0 | 1 | 2>(0)
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
|
||||
const onPress = () => {
|
||||
@@ -262,10 +265,10 @@ function ValueProposition({
|
||||
finishOnboarding() // has its own metrics
|
||||
} else if (subStep === 1) {
|
||||
setSubStep(2)
|
||||
logger.metric('onboarding:valueProp:stepTwo:nextPressed', {})
|
||||
ax.metric('onboarding:valueProp:stepTwo:nextPressed', {})
|
||||
} else if (subStep === 0) {
|
||||
setSubStep(1)
|
||||
logger.metric('onboarding:valueProp:stepOne:nextPressed', {})
|
||||
ax.metric('onboarding:valueProp:stepOne:nextPressed', {})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,7 +283,7 @@ function ValueProposition({
|
||||
size="small"
|
||||
label={_(msg`Skip introduction and start using your account`)}
|
||||
onPress={() => {
|
||||
logger.metric('onboarding:valueProp:skipPressed', {})
|
||||
ax.metric('onboarding:valueProp:skipPressed', {})
|
||||
finishOnboarding()
|
||||
}}
|
||||
style={[a.bg_transparent]}>
|
||||
|
||||
@@ -4,7 +4,6 @@ import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {interests, useInterestsDisplayNames} from '#/lib/interests'
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
import {capitalize} from '#/lib/strings/capitalize'
|
||||
import {logger} from '#/logger'
|
||||
import {
|
||||
@@ -19,9 +18,11 @@ import {atoms as a} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import * as Toggle from '#/components/forms/Toggle'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
|
||||
export function StepInterests() {
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const interestsDisplayNames = useInterestsDisplayNames()
|
||||
|
||||
const {state, dispatch} = useOnboardingInternalState()
|
||||
@@ -40,7 +41,7 @@ export function StepInterests() {
|
||||
selectedInterests,
|
||||
})
|
||||
dispatch({type: 'next'})
|
||||
logEvent('onboarding:interests:nextPressed', {
|
||||
ax.metric('onboarding:interests:nextPressed', {
|
||||
selectedInterests,
|
||||
selectedInterestsLength: selectedInterests.length,
|
||||
})
|
||||
@@ -48,7 +49,7 @@ export function StepInterests() {
|
||||
logger.info(`onboading: error saving interests`)
|
||||
logger.error(e)
|
||||
}
|
||||
}, [selectedInterests, setSaving, dispatch])
|
||||
}, [ax, selectedInterests, setSaving, dispatch])
|
||||
|
||||
return (
|
||||
<View style={[a.align_start, a.gap_sm]} testID="onboardingInterests">
|
||||
|
||||
@@ -14,7 +14,6 @@ import {compressIfNeeded} from '#/lib/media/manip'
|
||||
import {openCropper} from '#/lib/media/picker'
|
||||
import {getDataUriSize} from '#/lib/media/util'
|
||||
import {useRequestNotificationsPermission} from '#/lib/notifications/notifications'
|
||||
import {logEvent, useGate} from '#/lib/statsig/statsig'
|
||||
import {isCancelledError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {
|
||||
@@ -37,6 +36,7 @@ import * as Dialog from '#/components/Dialog'
|
||||
import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper'
|
||||
import {CircleInfo_Stroke2_Corner0_Rounded} from '#/components/icons/CircleInfo'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_NATIVE, IS_WEB} from '#/env'
|
||||
import {type AvatarColor, avatarColors, type Emoji, emojiItems} from './types'
|
||||
|
||||
@@ -66,11 +66,11 @@ const randomColor =
|
||||
avatarColors[Math.floor(Math.random() * avatarColors.length)]
|
||||
|
||||
export function StepProfile() {
|
||||
const ax = useAnalytics()
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
|
||||
const gate = useGate()
|
||||
const requestNotificationsPermission = useRequestNotificationsPermission()
|
||||
|
||||
const creatorControl = Dialog.useDialogControl()
|
||||
@@ -89,7 +89,7 @@ export function StepProfile() {
|
||||
|
||||
React.useEffect(() => {
|
||||
requestNotificationsPermission('StartOnboarding')
|
||||
}, [gate, requestNotificationsPermission])
|
||||
}, [requestNotificationsPermission])
|
||||
|
||||
const sheetWrapper = useSheetWrapper()
|
||||
const openPicker = React.useCallback(
|
||||
@@ -156,8 +156,8 @@ export function StepProfile() {
|
||||
}
|
||||
|
||||
dispatch({type: 'next'})
|
||||
logEvent('onboarding:profile:nextPressed', {})
|
||||
}, [avatar, dispatch])
|
||||
ax.metric('onboarding:profile:nextPressed', {})
|
||||
}, [ax, avatar, dispatch])
|
||||
|
||||
const onDoneCreating = React.useCallback(() => {
|
||||
setAvatar(prev => ({
|
||||
|
||||
@@ -9,7 +9,6 @@ import * as bcp47Match from 'bcp-47-match'
|
||||
import {wait} from '#/lib/async/wait'
|
||||
import {popularInterests, useInterestsDisplayNames} from '#/lib/interests'
|
||||
import {isBlockedOrBlocking, isMuted} from '#/lib/moderation/blocked-and-muted'
|
||||
import {logger} from '#/logger'
|
||||
import {updateProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {useLanguagePrefs} from '#/state/preferences'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
@@ -30,12 +29,14 @@ import {boostInterests, InterestTabs} from '#/components/InterestTabs'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import * as ProfileCard from '#/components/ProfileCard'
|
||||
import * as toast from '#/components/Toast'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_WEB} from '#/env'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
import {bulkWriteFollows} from '../util'
|
||||
|
||||
export function StepSuggestedAccounts() {
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const t = useTheme()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const moderationOpts = useModerationOpts()
|
||||
@@ -92,7 +93,7 @@ export function StepSuggestedAccounts() {
|
||||
|
||||
const {mutate: followAll, isPending: isFollowingAll} = useMutation({
|
||||
onMutate: () => {
|
||||
logger.metric('onboarding:suggestedAccounts:followAllPressed', {
|
||||
ax.metric('onboarding:suggestedAccounts:followAllPressed', {
|
||||
tab: selectedInterest ?? 'all',
|
||||
numAccounts: followableDids.length,
|
||||
})
|
||||
@@ -132,20 +133,16 @@ export function StepSuggestedAccounts() {
|
||||
(did: string, position: number) => {
|
||||
if (!seenProfilesRef.current.has(did)) {
|
||||
seenProfilesRef.current.add(did)
|
||||
logger.metric(
|
||||
'suggestedUser:seen',
|
||||
{
|
||||
logContext: 'Onboarding',
|
||||
recId: undefined,
|
||||
position,
|
||||
suggestedDid: did,
|
||||
category: selectedInterest,
|
||||
},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('suggestedUser:seen', {
|
||||
logContext: 'Onboarding',
|
||||
recId: undefined,
|
||||
position,
|
||||
suggestedDid: did,
|
||||
category: selectedInterest,
|
||||
})
|
||||
}
|
||||
},
|
||||
[selectedInterest],
|
||||
[ax, selectedInterest],
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -297,6 +294,7 @@ function TabBar({
|
||||
defaultTabLabel?: string
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const interestsDisplayNames = useInterestsDisplayNames()
|
||||
const interests = Object.keys(interestsDisplayNames)
|
||||
.sort(boostInterests(popularInterests))
|
||||
@@ -309,11 +307,7 @@ function TabBar({
|
||||
selectedInterest || (hideDefaultTab ? interests[0] : 'all')
|
||||
}
|
||||
onSelectTab={tab => {
|
||||
logger.metric(
|
||||
'onboarding:suggestedAccounts:tabPressed',
|
||||
{tab: tab},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('onboarding:suggestedAccounts:tabPressed', {tab: tab})
|
||||
onSelectInterest(tab === 'all' ? null : tab)
|
||||
}}
|
||||
interestsDisplayNames={
|
||||
@@ -343,6 +337,7 @@ function SuggestedProfileCard({
|
||||
onSeen: (did: string, position: number) => void
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const ax = useAnalytics()
|
||||
const cardRef = useRef<View>(null)
|
||||
const hasTrackedRef = useRef(false)
|
||||
|
||||
@@ -403,18 +398,14 @@ function SuggestedProfileCard({
|
||||
withIcon={false}
|
||||
logContext="OnboardingSuggestedAccounts"
|
||||
onFollow={() => {
|
||||
logger.metric(
|
||||
'suggestedUser:follow',
|
||||
{
|
||||
logContext: 'Onboarding',
|
||||
location: 'Card',
|
||||
recId: undefined,
|
||||
position,
|
||||
suggestedDid: profile.did,
|
||||
category,
|
||||
},
|
||||
{statsig: true},
|
||||
)
|
||||
ax.metric('suggestedUser:follow', {
|
||||
logContext: 'Onboarding',
|
||||
location: 'Card',
|
||||
recId: undefined,
|
||||
position,
|
||||
suggestedDid: profile.did,
|
||||
category,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</ProfileCard.Header>
|
||||
|
||||
@@ -19,6 +19,7 @@ import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Che
|
||||
import {Loader} from '#/components/Loader'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
const IGNORED_ACCOUNT = 'did:plc:pifkcjimdcfwaxkanzhwxufp'
|
||||
@@ -30,6 +31,7 @@ export function StarterPackCard({
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const {currentAccount} = useSession()
|
||||
const {gtPhone} = useBreakpoints()
|
||||
const agent = useAgent()
|
||||
@@ -89,7 +91,7 @@ export function StarterPackCard({
|
||||
}
|
||||
})
|
||||
Toast.show(_(msg`All accounts have been followed!`), {type: 'success'})
|
||||
logger.metric('starterPack:followAll', {
|
||||
ax.metric('starterPack:followAll', {
|
||||
logContext: 'Onboarding',
|
||||
starterPack: view.uri,
|
||||
count: dids.length,
|
||||
|
||||
@@ -3,7 +3,6 @@ import {View} from 'react-native'
|
||||
import * as bcp47Match from 'bcp-47-match'
|
||||
|
||||
import {useEnableKeyboardControllerScreen} from '#/lib/hooks/useEnableKeyboardController'
|
||||
import {useGate} from '#/lib/statsig/statsig'
|
||||
import {useLanguagePrefs} from '#/state/preferences'
|
||||
import {
|
||||
Layout,
|
||||
@@ -23,6 +22,7 @@ import {useIsFindContactsFeatureEnabledBasedOnGeolocation} from '#/components/co
|
||||
import {useFindContactsFlowState} from '#/components/contacts/state'
|
||||
import {Portal} from '#/components/Portal'
|
||||
import {ScreenTransition} from '#/components/ScreenTransition'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {ENV, IS_NATIVE} from '#/env'
|
||||
import {StepFindContacts} from './StepFindContacts'
|
||||
import {StepFindContactsIntro} from './StepFindContactsIntro'
|
||||
@@ -31,7 +31,7 @@ import {StepSuggestedStarterpacks} from './StepSuggestedStarterpacks'
|
||||
|
||||
export function Onboarding() {
|
||||
const t = useTheme()
|
||||
const gate = useGate()
|
||||
const ax = useAnalytics()
|
||||
|
||||
const {contentLanguages} = useLanguagePrefs()
|
||||
const probablySpeaksEnglish = useMemo(() => {
|
||||
@@ -48,7 +48,7 @@ export function Onboarding() {
|
||||
ENV !== 'e2e' &&
|
||||
IS_NATIVE &&
|
||||
findContactsEnabled &&
|
||||
!gate('disable_onboarding_find_contacts')
|
||||
!ax.features.enabled(ax.features.DisableOnboardingFindContacts)
|
||||
|
||||
const [state, dispatch] = useReducer(
|
||||
reducer,
|
||||
|
||||
@@ -172,15 +172,6 @@ export function reducer(
|
||||
}
|
||||
}
|
||||
|
||||
if (a.type === 'next') {
|
||||
if (next.activeStep === 'find-contacts-intro') {
|
||||
logger.metric('onboarding:contacts:presented', {})
|
||||
}
|
||||
if (next.activeStep === 'find-contacts') {
|
||||
logger.metric('onboarding:contacts:begin', {})
|
||||
}
|
||||
}
|
||||
|
||||
const state = {
|
||||
...next,
|
||||
hasPrev: next.activeStep !== 'profile',
|
||||
|
||||
Reference in New Issue
Block a user