[Instrumentation] Signin (#7742)

* first pass at instrumenting login

* round time taken
This commit is contained in:
Samuel Newman
2025-02-14 19:52:53 +00:00
committed by GitHub
parent 11616846ca
commit d793abf8cd
5 changed files with 85 additions and 26 deletions
+16
View File
@@ -51,6 +51,22 @@ export type LogEvents = {
} }
'signup:captchaSuccess': {} 'signup:captchaSuccess': {}
'signup:captchaFailure': {} 'signup:captchaFailure': {}
'signin:hostingProviderPressed': {
hostingProviderDidChange: boolean
}
'signin:hostingProviderFailedResolution': {}
'signin:success': {
failedAttemptsCount: number
isUsingCustomProvider: boolean
timeTakenSeconds: number
}
'signin:backPressed': {
failedAttemptsCount: number
}
'signin:forgotPasswordPressed': {}
'signin:passwordReset': {}
'signin:passwordResetSuccess': {}
'signin:passwordResetFailure': {}
'onboarding:interests:nextPressed': { 'onboarding:interests:nextPressed': {
selectedInterests: string[] selectedInterests: string[]
selectedInterestsLength: number selectedInterestsLength: number
+30 -22
View File
@@ -45,6 +45,8 @@ export const LoginForm = ({
onPressRetryConnect, onPressRetryConnect,
onPressBack, onPressBack,
onPressForgotPassword, onPressForgotPassword,
onAttemptSuccess,
onAttemptFailed,
}: { }: {
error: string error: string
serviceUrl: string serviceUrl: string
@@ -55,6 +57,8 @@ export const LoginForm = ({
onPressRetryConnect: () => void onPressRetryConnect: () => void
onPressBack: () => void onPressBack: () => void
onPressForgotPassword: () => void onPressForgotPassword: () => void
onAttemptSuccess: () => void
onAttemptFailed: () => void
}) => { }) => {
const t = useTheme() const t = useTheme()
const [isProcessing, setIsProcessing] = useState<boolean>(false) const [isProcessing, setIsProcessing] = useState<boolean>(false)
@@ -131,6 +135,7 @@ export const LoginForm = ({
}, },
'LoginForm', 'LoginForm',
) )
onAttemptSuccess()
setShowLoggedOut(false) setShowLoggedOut(false)
setHasCheckedForStarterPack(true) setHasCheckedForStarterPack(true)
requestNotificationsPermission('Login') requestNotificationsPermission('Login')
@@ -142,29 +147,32 @@ export const LoginForm = ({
e instanceof ComAtprotoServerCreateSession.AuthFactorTokenRequiredError e instanceof ComAtprotoServerCreateSession.AuthFactorTokenRequiredError
) { ) {
setIsAuthFactorTokenNeeded(true) setIsAuthFactorTokenNeeded(true)
} else if (errMsg.includes('Token is invalid')) {
logger.debug('Failed to login due to invalid 2fa token', {
error: errMsg,
})
setError(_(msg`Invalid 2FA confirmation code.`))
} else if (
errMsg.includes('Authentication Required') ||
errMsg.includes('Invalid identifier or password')
) {
logger.debug('Failed to login due to invalid credentials', {
error: errMsg,
})
setError(_(msg`Incorrect username or password`))
} else if (isNetworkError(e)) {
logger.warn('Failed to login due to network error', {error: errMsg})
setError(
_(
msg`Unable to contact your service. Please check your Internet connection.`,
),
)
} else { } else {
logger.warn('Failed to login', {error: errMsg}) onAttemptFailed()
setError(cleanError(errMsg)) if (errMsg.includes('Token is invalid')) {
logger.debug('Failed to login due to invalid 2fa token', {
error: errMsg,
})
setError(_(msg`Invalid 2FA confirmation code.`))
} else if (
errMsg.includes('Authentication Required') ||
errMsg.includes('Invalid identifier or password')
) {
logger.debug('Failed to login due to invalid credentials', {
error: errMsg,
})
setError(_(msg`Incorrect username or password`))
} else if (isNetworkError(e)) {
logger.warn('Failed to login due to network error', {error: errMsg})
setError(
_(
msg`Unable to contact your service. Please check your Internet connection.`,
),
)
} else {
logger.warn('Failed to login', {error: errMsg})
setError(cleanError(errMsg))
}
} }
} }
} }
+4
View File
@@ -4,6 +4,7 @@ import {BskyAgent} from '@atproto/api'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {logEvent} from '#/lib/statsig/statsig'
import {isNetworkError} from '#/lib/strings/errors' import {isNetworkError} from '#/lib/strings/errors'
import {cleanError} from '#/lib/strings/errors' import {cleanError} from '#/lib/strings/errors'
import {checkAndFormatResetCode} from '#/lib/strings/password' import {checkAndFormatResetCode} from '#/lib/strings/password'
@@ -48,6 +49,7 @@ export const SetNewPasswordForm = ({
msg`You have entered an invalid code. It should look like XXXXX-XXXXX.`, msg`You have entered an invalid code. It should look like XXXXX-XXXXX.`,
), ),
) )
logEvent('signin:passwordResetFailure', {})
return return
} }
@@ -67,9 +69,11 @@ export const SetNewPasswordForm = ({
password, password,
}) })
onPasswordSet() onPasswordSet()
logEvent('signin:passwordResetSuccess', {})
} catch (e: any) { } catch (e: any) {
const errMsg = e.toString() const errMsg = e.toString()
logger.warn('Failed to set new password', {error: e}) logger.warn('Failed to set new password', {error: e})
logEvent('signin:passwordResetFailure', {})
setIsProcessing(false) setIsProcessing(false)
if (isNetworkError(e)) { if (isNetworkError(e)) {
setError( setError(
+30 -3
View File
@@ -1,10 +1,11 @@
import React from 'react' import React, {useRef} from 'react'
import {KeyboardAvoidingView} from 'react-native' import {KeyboardAvoidingView} from 'react-native'
import {LayoutAnimationConfig} from 'react-native-reanimated' import {LayoutAnimationConfig} from 'react-native-reanimated'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {DEFAULT_SERVICE} from '#/lib/constants' import {DEFAULT_SERVICE} from '#/lib/constants'
import {logEvent} from '#/lib/statsig/statsig'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useServiceQuery} from '#/state/queries/service' import {useServiceQuery} from '#/state/queries/service'
import {SessionAccount, useSession} from '#/state/session' import {SessionAccount, useSession} from '#/state/session'
@@ -28,6 +29,8 @@ enum Forms {
export const Login = ({onPressBack}: {onPressBack: () => void}) => { export const Login = ({onPressBack}: {onPressBack: () => void}) => {
const {_} = useLingui() const {_} = useLingui()
const failedAttemptCountRef = useRef(0)
const startTimeRef = useRef(Date.now())
const {accounts} = useSession() const {accounts} = useSession()
const {requestedAccountSwitchTo} = useLoggedOutView() const {requestedAccountSwitchTo} = useLoggedOutView()
@@ -79,6 +82,7 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
logger.warn(`Failed to fetch service description for ${serviceUrl}`, { logger.warn(`Failed to fetch service description for ${serviceUrl}`, {
error: String(serviceError), error: String(serviceError),
}) })
logEvent('signin:hostingProviderFailedResolution', {})
} else { } else {
setError('') setError('')
} }
@@ -86,6 +90,27 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
const onPressForgotPassword = () => { const onPressForgotPassword = () => {
setCurrentForm(Forms.ForgotPassword) setCurrentForm(Forms.ForgotPassword)
logEvent('signin:forgotPasswordPressed', {})
}
const handlePressBack = () => {
onPressBack()
logEvent('signin:backPressed', {
failedAttemptsCount: failedAttemptCountRef.current,
})
}
const onAttemptSuccess = () => {
logEvent('signin:success', {
isUsingCustomProvider: serviceUrl !== DEFAULT_SERVICE,
timeTakenSeconds: Math.round((Date.now() - startTimeRef.current) / 1000),
failedAttemptsCount: failedAttemptCountRef.current,
})
setCurrentForm(Forms.Login)
}
const onAttemptFailed = () => {
failedAttemptCountRef.current += 1
} }
let content = null let content = null
@@ -103,9 +128,11 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
serviceDescription={serviceDescription} serviceDescription={serviceDescription}
initialHandle={initialHandle} initialHandle={initialHandle}
setError={setError} setError={setError}
onAttemptFailed={onAttemptFailed}
onAttemptSuccess={onAttemptSuccess}
setServiceUrl={setServiceUrl} setServiceUrl={setServiceUrl}
onPressBack={() => onPressBack={() =>
accounts.length ? gotoForm(Forms.ChooseAccount) : onPressBack() accounts.length ? gotoForm(Forms.ChooseAccount) : handlePressBack()
} }
onPressForgotPassword={onPressForgotPassword} onPressForgotPassword={onPressForgotPassword}
onPressRetryConnect={refetchService} onPressRetryConnect={refetchService}
@@ -118,7 +145,7 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
content = ( content = (
<ChooseAccountForm <ChooseAccountForm
onSelectAccount={onSelectAccount} onSelectAccount={onSelectAccount}
onPressBack={onPressBack} onPressBack={handlePressBack}
/> />
) )
break break
+5 -1
View File
@@ -5,6 +5,7 @@ import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {BSKY_SERVICE} from '#/lib/constants' import {BSKY_SERVICE} from '#/lib/constants'
import {logEvent} from '#/lib/statsig/statsig'
import * as persisted from '#/state/persisted' import * as persisted from '#/state/persisted'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {atoms as a, useBreakpoints, useTheme} from '#/alf'
@@ -39,7 +40,10 @@ export function ServerInputDialog({
setPreviousCustomAddress(result) setPreviousCustomAddress(result)
} }
} }
}, [onSelect]) logEvent('signin:hostingProviderPressed', {
hostingProviderDidChange: fixedOption !== BSKY_SERVICE,
})
}, [onSelect, fixedOption])
return ( return (
<Dialog.Outer <Dialog.Outer