Conversation
Because: - Users reach the /pair choice screen from several flows (password sign-in, registration, email OTP sign-in, passkey sign-in), and cad_firefox.choice_view carries no way to tell them apart. - Splitting that funnel by originating flow needs the attribution on the view event itself, rather than inferred by joining across other events. This commit: - Adds a `reason` extra key to cad_firefox.choice_view and regenerates the Glean bindings. The permitted values live in one place, fxa-shared/metrics/glean/pair-reasons.ts, which also exports the runtime guard both /pair implementations validate with. - Derives the reason in getSyncNavigate. `passwordCreationReason` is the primary signal: Sync always requires encryption keys, so every passwordless OTP or passkey sign-in is routed through /post_verify/set_password before it can reach /pair, making that page the only place that still knows how the session was established. The raw session flags (isPasskeySession, isPasswordlessOtpSignin) cover the flows that skip it. - Resolves the reason before the interstitial early returns so it rides along on /signup_confirmed_sync and /inline_recovery_key_setup, which reach /pair via hardNavigate('/pair', {}, true). Without this the reason was lost for every flow that stops at an interstitial, and password_reg was unreachable outside Send Tab. - Declares origin: 'signup' at the sign-up callers that set showSignupConfirmedSync, which otherwise fell through to password_login. - Reports no reason for third-party auth rather than folding it into password_login; it has no bucket of its own in FXA-14133. - Reads the value in React /pair from router state with a query-param fallback for the interstitial hand-off, validating both. The reason is deliberately kept out of the soft-nav URL so an account-level attribute is not written to history or server access logs on every pair visit. - Validates the Backbone query param against the shared allowlist before recording it, since that value arrives from the URL. - Adds handleNavigation-level tests asserting the reason that actually reaches /pair. The pass-through had no coverage, so the derivation could be wired to signals no Sync flow carries and the suite would still pass. - Parses app/scripts/lib/glean/*.ts with @typescript-eslint/parser in fxa-content-server (precedent: fxa-profile-server). That directory is TypeScript while the app is otherwise babel-eslint, so the dispatcher could not be linted at all; scoped to that directory to avoid surfacing pre-existing violations in unrelated .ts files.
| if (isSyncDesktopV3Integration(integration)) { | ||
| const { to } = getSyncNavigate(location.search, { | ||
| showSignupConfirmedSync: true, | ||
| origin: 'signup', |
There was a problem hiding this comment.
I think this is acting like a sinup action... but let's double check...
There was a problem hiding this comment.
Looks like it is, I see the isSignup check above it that we pass in when it's a signup code.
|
|
||
| expect(hardNavigateSpy).toHaveBeenCalledWith( | ||
| '/pair?showSuccessMessage=true' | ||
| '/pair?showSuccessMessage=true&pairReason=password_login' |
There was a problem hiding this comment.
The only reason you would need this would be for Backbone pairing. I don't see any changes in fxa-content-server (and don't think we need them) so, remove?
There was a problem hiding this comment.
That makes sense. Initially Claude also included content server here, but I rolled those back and this fell through the cracks.
There was a problem hiding this comment.
Ah... I see what's happening here. There's a code a path where can hard navigate. Are you suggesting that path should be removed entirely? If not, then this needs to change, cause we will be passing along the pairReason query parameter by default. The hard navigate is based on the to value returned by getSyncNavigate, which with changes will return password_login.
There was a problem hiding this comment.
@dschom it looks like we never check the config there. We should have been checking, "if react pair is on from the config then normal navigate". I think it's fine to just remove at this point.
LZoog
left a comment
There was a problem hiding this comment.
@dschom This is a big diff for something I would've expected we could mostly get at least? looking at flow ID + other Glean events, but I can see why we'd want it.
I tested the password sign up and password sign in flows and checked the Glean pings and they looked right. I'll go ahead and r+ not to block you 👍
|
|
||
| // The dispatcher case arm is the only thing deciding what reaches Glean | ||
| // for this event, so both the populated and empty reason are pinned here | ||
| // (FXA-14133). |
There was a problem hiding this comment.
Not sure this comment is adding much, at least remove ticket reference?
| handleFxaOAuthLogin: true, | ||
| showSignupConfirmedSync: true, | ||
| origin: 'post-verify-set-password', | ||
| // Sync needs keys, so every passwordless OTP/passkey sign-in lands |
There was a problem hiding this comment.
nit: non-PRF passkey sign-in
Though how you have it is true today
|
|
||
| expect(hardNavigateSpy).toHaveBeenCalledWith( | ||
| '/pair?showSuccessMessage=true' | ||
| '/pair?showSuccessMessage=true&pairReason=password_login' |
There was a problem hiding this comment.
@dschom it looks like we never check the config there. We should have been checking, "if react pair is on from the config then normal navigate". I think it's fine to just remove at this point.
| // (FXA-14133). | ||
| it('submits a ping with the cad_firefox_choice_view event name and a reason', async () => { | ||
| GleanMetrics.cadFireFox.choiceView({ | ||
| event: { reason: 'otp_login' }, |
There was a problem hiding this comment.
This is just a nit but otp_login really made me think passwordless OTP login at first. otp_confirmation or some other name would make it more clear but I know these were set by Product so just a suggestion.
| if (isSyncDesktopV3Integration(integration)) { | ||
| const { to } = getSyncNavigate(location.search, { | ||
| showSignupConfirmedSync: true, | ||
| origin: 'signup', |
There was a problem hiding this comment.
Looks like it is, I see the isSignup check above it that we pass in when it's a signup code.
| ); | ||
| }); | ||
|
|
||
| // This is the only producer of isPasskeySession, and therefore the only |
There was a problem hiding this comment.
Remove comment? This could be stale later
| 'password_login', | ||
| 'password_reg', | ||
| 'otp_login', | ||
| 'passkey_login', |
There was a problem hiding this comment.
I guess if a user accesses this directly from the Firefox browser menu, Product can tell by checking the entrypoint on the param and check that reason is blank. Going to firefox.com/pair directly (like we tell people to do to pair) and clicking on "Connect another device" from the browser both record a blank reason. Expected I think but just noting.
Because
This pull request
reasonextra key to cad_firefox.choice_view and regenerates the Glean bindings. The permitted values live in one place, fxa-shared/metrics/glean/pair-reasons.ts, which also exports the runtime guard both /pair implementations validate with.passwordCreationReasonis the primary signal: Sync always requires encryption keys, so every passwordless OTP or passkey sign-in is routed through /post_verify/set_password before it can reach /pair, making that page the only place that still knows how the session was established. The raw session flags (isPasskeySession, isPasswordlessOtpSignin) cover the flows that skip it.Issue that this pull request solves
Closes: FXA-14133
Checklist
Put an
xin the boxes that applyHow to review (Optional)
Screenshots (Optional)
Please attach the screenshots of the changes made in case of change in user interface.
Other information (Optional)
Any other information that is important to this pull request.