feat(metrics): add "deeplink" 3rd party auth Glean events

This commit is contained in:
Barry Chen 2024-06-27 09:45:46 -05:00
Родитель 9d3ebf8392
Коммит 81718a0a6e
Не найден ключ, соответствующий данной подписи
7 изменённых файлов: 213 добавлений и 38 удалений

Просмотреть файл

@ -18,6 +18,7 @@ import { accountsEvents } from './pings';
import * as reg from './reg';
import { oauthClientId, service } from './relyingParty';
import { deviceType, entrypoint, flowId } from './session';
import * as thirdPartyAuth from './thirdPartyAuth';
import * as thirdPartyAuthSetPassword from './thirdPartyAuthSetPassword';
import * as utm from './utm';
@ -281,6 +282,12 @@ const recordEventMetric = (eventName: string, properties: EventProperties) => {
case 'third_party_auth_set_password_success':
thirdPartyAuthSetPassword.success.record();
break;
case 'google_deeplink':
thirdPartyAuth.googleDeeplink.record();
break;
case 'apple_deeplink':
thirdPartyAuth.appleDeeplink.record();
break;
}
};
@ -339,6 +346,25 @@ export const GleanMetrics = {
Glean.setUploadEnabled(gleanEnabled);
},
/**
* The ping calls are awaited internally for ease of use and that works in
* most cases. But in the scenario where we want to wait for the pings to
* finish before we unload the page, we are doing so, crudely, here. Do not
* emit more pings after calling this function.
*/
isDone: () =>
new Promise((resolve) => {
const checkForEmptyFnList = () => {
if (lambdas.length === 0) {
resolve(undefined);
} else {
setTimeout(checkForEmptyFnList, lambdas.length * 5);
}
};
checkForEmptyFnList();
}),
emailFirst: {
view: createEventFn('email_first_view'),
appleOauthStart: createEventFn('apple_oauth_email_first_start'),
@ -412,6 +438,10 @@ export const GleanMetrics = {
submit: createEventFn('third_party_auth_set_password_submit'),
success: createEventFn('third_party_auth_set_password_success'),
},
thirdPartyAuth: {
googleDeeplink: createEventFn('google_deeplink'),
appleDeeplink: createEventFn('apple_deeplink'),
},
};
export default GleanMetrics;

Просмотреть файл

@ -0,0 +1,41 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// AUTOGENERATED BY glean_parser v14.1.2. DO NOT EDIT. DO NOT COMMIT.
import EventMetricType from '@mozilla/glean/private/metrics/event';
/**
* User sees Apple link and bypasses Mozilla Accounts email first/login
* screens (relevant for Pocket)
*
* Generated from `third_party_auth.apple_deeplink`.
*/
export const appleDeeplink = new EventMetricType(
{
category: 'third_party_auth',
name: 'apple_deeplink',
sendInPings: ['events'],
lifetime: 'ping',
disabled: false,
},
[]
);
/**
* User sees Google link and bypasses Mozilla Accounts email first/login
* screens (relevant for Pocket)
*
* Generated from `third_party_auth.google_deeplink`.
*/
export const googleDeeplink = new EventMetricType(
{
category: 'third_party_auth',
name: 'google_deeplink',
sendInPings: ['events'],
lifetime: 'ping',
disabled: false,
},
[]
);

Просмотреть файл

@ -31,14 +31,22 @@ export default {
const params = new URLSearchParams(this.window.location.search);
if (params.get('deeplink') === 'googleLogin') {
this.logFlowEvent('google.deeplink');
return new Promise(() => {
this.googleSignIn();
});
GleanMetrics.thirdPartyAuth.googleDeeplink();
return GleanMetrics.isDone().then(
() =>
new Promise(() => {
this.googleSignIn();
})
);
} else if (params.get('deeplink') === 'appleLogin') {
this.logFlowEvent('apple.deeplink');
return new Promise(() => {
this.appleSignIn();
});
GleanMetrics.thirdPartyAuth.appleDeeplink();
return GleanMetrics.isDone().then(
() =>
new Promise(() => {
this.appleSignIn();
})
);
}
// Check to see if this page is being redirected to at the end of a

Просмотреть файл

@ -107,12 +107,14 @@ describe('lib/glean', () => {
});
it('does not submit a ping on an event', async () => {
await GleanMetrics.registration.view();
GleanMetrics.registration.view();
await GleanMetrics.isDone();
sinon.assert.notCalled(submitPingStub);
});
it('does not set the metrics values', async () => {
await GleanMetrics.registration.view();
GleanMetrics.registration.view();
await GleanMetrics.isDone();
sinon.assert.notCalled(setOauthClientIdStub);
sinon.assert.notCalled(setServiceStub);
@ -134,7 +136,8 @@ describe('lib/glean', () => {
const config = { ...mockConfig, enabled: true };
const initStub = sandbox.stub(Glean, 'initialize').throws();
GleanMetrics.initialize(config, { metrics, relier, user, userAgent });
await GleanMetrics.registration.view();
GleanMetrics.registration.view();
await GleanMetrics.isDone();
sinon.assert.calledOnce(initStub);
assert.isFalse(config.enabled);
// does not try to set a value since internal enabled state is false
@ -321,7 +324,8 @@ describe('lib/glean', () => {
describe('email first', () => {
it('submits a ping with the email_first_view event name', async () => {
await GleanMetrics.emailFirst.view();
GleanMetrics.emailFirst.view();
await GleanMetrics.isDone();
sinon.assert.calledOnce(setEventNameStub);
sinon.assert.calledWith(setEventNameStub, 'email_first_view');
});
@ -329,7 +333,8 @@ describe('lib/glean', () => {
describe('apple oauth email first', () => {
it('submits a ping with the apple_oauth_email_first_start event name', async () => {
await GleanMetrics.emailFirst.appleOauthStart();
GleanMetrics.emailFirst.appleOauthStart();
await GleanMetrics.isDone();
sinon.assert.calledOnce(setEventNameStub);
sinon.assert.calledWith(
setEventNameStub,
@ -340,7 +345,8 @@ describe('lib/glean', () => {
describe('google oauth email first', () => {
it('submits a ping with the google_oauth_email_first_start event name', async () => {
await GleanMetrics.emailFirst.googleOauthStart();
GleanMetrics.emailFirst.googleOauthStart();
await GleanMetrics.isDone();
sinon.assert.calledOnce(setEventNameStub);
sinon.assert.calledWith(
setEventNameStub,
@ -351,19 +357,22 @@ describe('lib/glean', () => {
describe('registration', () => {
it('submits a ping with the reg_view event name', async () => {
await GleanMetrics.registration.view();
GleanMetrics.registration.view();
await GleanMetrics.isDone();
sinon.assert.calledOnce(setEventNameStub);
sinon.assert.calledWith(setEventNameStub, 'reg_view');
});
it('submits a ping with the reg_submit event name', async () => {
await GleanMetrics.registration.submit();
GleanMetrics.registration.submit();
await GleanMetrics.isDone();
sinon.assert.calledOnce(setEventNameStub);
sinon.assert.calledWith(setEventNameStub, 'reg_submit');
});
it('submits a ping with the reg_submit_success event name', async () => {
await GleanMetrics.registration.success();
GleanMetrics.registration.success();
await GleanMetrics.isDone();
sinon.assert.calledOnce(setEventNameStub);
sinon.assert.calledWith(setEventNameStub, 'reg_submit_success');
});
@ -371,13 +380,15 @@ describe('lib/glean', () => {
describe('signup confirmation code', () => {
it('submits a ping with the reg_signup_code_view event name', async () => {
await GleanMetrics.signupConfirmation.view();
GleanMetrics.signupConfirmation.view();
await GleanMetrics.isDone();
sinon.assert.calledOnce(setEventNameStub);
sinon.assert.calledWith(setEventNameStub, 'reg_signup_code_view');
});
it('submits a ping with the reg_signup_code_submit event name', async () => {
await GleanMetrics.signupConfirmation.submit();
GleanMetrics.signupConfirmation.submit();
await GleanMetrics.isDone();
sinon.assert.calledOnce(setEventNameStub);
sinon.assert.calledWith(setEventNameStub, 'reg_signup_code_submit');
});
@ -385,7 +396,8 @@ describe('lib/glean', () => {
describe('loginConfirmation', () => {
it('submits a ping with the login_email_confirmation_view event name', async () => {
await GleanMetrics.loginConfirmation.view();
GleanMetrics.loginConfirmation.view();
await GleanMetrics.isDone();
sinon.assert.calledOnce(setEventNameStub);
sinon.assert.calledWith(
setEventNameStub,
@ -394,7 +406,8 @@ describe('lib/glean', () => {
});
it('submits a ping with the reg_submit event name', async () => {
await GleanMetrics.loginConfirmation.submit();
GleanMetrics.loginConfirmation.submit();
await GleanMetrics.isDone();
sinon.assert.calledOnce(setEventNameStub);
sinon.assert.calledWith(
setEventNameStub,
@ -405,19 +418,22 @@ describe('lib/glean', () => {
describe('totpForm', () => {
it('submits a ping with the login_totp_form_view event name', async () => {
await GleanMetrics.totpForm.view();
GleanMetrics.totpForm.view();
await GleanMetrics.isDone();
sinon.assert.calledOnce(setEventNameStub);
sinon.assert.calledWith(setEventNameStub, 'login_totp_form_view');
});
it('submits a ping with the login_totp_code_submit event name', async () => {
await GleanMetrics.totpForm.submit();
GleanMetrics.totpForm.submit();
await GleanMetrics.isDone();
sinon.assert.calledOnce(setEventNameStub);
sinon.assert.calledWith(setEventNameStub, 'login_totp_code_submit');
});
it('submits a ping with the login_totp_code_success_view event name', async () => {
await GleanMetrics.totpForm.success();
GleanMetrics.totpForm.success();
await GleanMetrics.isDone();
sinon.assert.calledOnce(setEventNameStub);
sinon.assert.calledWith(
setEventNameStub,
@ -426,27 +442,47 @@ describe('lib/glean', () => {
});
});
describe('thirdPartyAuth', () => {
it('submits a ping with the google_deeplink event name', async () => {
GleanMetrics.thirdPartyAuth.googleDeeplink();
await GleanMetrics.isDone();
sinon.assert.calledOnce(setEventNameStub);
sinon.assert.calledWith(setEventNameStub, 'google_deeplink');
});
it('submits a ping with the apple_deeplink event name', async () => {
GleanMetrics.thirdPartyAuth.appleDeeplink();
await GleanMetrics.isDone();
sinon.assert.calledOnce(setEventNameStub);
sinon.assert.calledWith(setEventNameStub, 'apple_deeplink');
});
});
describe('login', () => {
it('submits a ping with the login_view event name', () => {
it('submits a ping with the login_view event name', async () => {
GleanMetrics.login.view();
await GleanMetrics.isDone();
sinon.assert.calledOnce(setEventNameStub);
sinon.assert.calledWith(setEventNameStub, 'login_view');
});
it('submits a ping with the login_submit event name', () => {
it('submits a ping with the login_submit event name', async () => {
GleanMetrics.login.submit();
await GleanMetrics.isDone();
sinon.assert.calledOnce(setEventNameStub);
sinon.assert.calledWith(setEventNameStub, 'login_submit');
});
it('submits a ping with the login_submit_success event name', () => {
it('submits a ping with the login_submit_success event name', async () => {
GleanMetrics.login.success();
await GleanMetrics.isDone();
sinon.assert.calledOnce(setEventNameStub);
sinon.assert.calledWith(setEventNameStub, 'login_submit_success');
});
it('submits a ping with the login_submit_frontend_error event name and a reason', () => {
it('submits a ping with the login_submit_frontend_error event name and a reason', async () => {
GleanMetrics.login.error({ reason: 'quux' });
await GleanMetrics.isDone();
sinon.assert.calledOnce(setEventNameStub);
sinon.assert.calledWith(
setEventNameStub,
@ -459,7 +495,8 @@ describe('lib/glean', () => {
describe('third_party_auth_set_password', () => {
it('submits a ping with the third_party_auth_set_password_view event name', async () => {
await GleanMetrics.setPasswordThirdPartyAuth.view();
GleanMetrics.setPasswordThirdPartyAuth.view();
await GleanMetrics.isDone();
sinon.assert.calledOnce(setEventNameStub);
sinon.assert.calledWith(
setEventNameStub,
@ -468,7 +505,8 @@ describe('lib/glean', () => {
});
it('submits a ping with the third_party_auth_set_password_engage event name', async () => {
await GleanMetrics.setPasswordThirdPartyAuth.engage();
GleanMetrics.setPasswordThirdPartyAuth.engage();
await GleanMetrics.isDone();
sinon.assert.calledOnce(setEventNameStub);
sinon.assert.calledWith(
setEventNameStub,
@ -477,7 +515,8 @@ describe('lib/glean', () => {
});
it('submits a ping with the third_party_auth_set_password_submit event name', async () => {
await GleanMetrics.setPasswordThirdPartyAuth.submit();
GleanMetrics.setPasswordThirdPartyAuth.submit();
await GleanMetrics.isDone();
sinon.assert.calledOnce(setEventNameStub);
sinon.assert.calledWith(
setEventNameStub,
@ -486,7 +525,8 @@ describe('lib/glean', () => {
});
it('submits a ping with the third_party_auth_set_password_success event name', async () => {
await GleanMetrics.setPasswordThirdPartyAuth.success();
GleanMetrics.setPasswordThirdPartyAuth.success();
await GleanMetrics.isDone();
sinon.assert.calledOnce(setEventNameStub);
sinon.assert.calledWith(
setEventNameStub,

Просмотреть файл

@ -10,6 +10,7 @@ import sinon from 'sinon';
import ThirdPartyAuthMixin from 'views/mixins/third-party-auth-mixin';
import Notifier from 'lib/channels/notifier';
import Metrics from 'lib/metrics';
import GleanMetrics from '../../../../scripts/lib/glean';
import SentryMetrics from 'lib/sentry';
import WindowMock from '../../../mocks/window';
import Storage from 'lib/storage';
@ -83,9 +84,16 @@ describe('views/mixins/third-party-auth-mixin', function () {
flowBeginTime: '456',
deviceId: '789',
}));
sinon.stub(GleanMetrics.thirdPartyAuth, 'appleDeeplink');
sinon.stub(GleanMetrics.thirdPartyAuth, 'googleDeeplink');
await view.render();
});
afterEach(() => {
GleanMetrics.thirdPartyAuth.appleDeeplink.restore();
GleanMetrics.thirdPartyAuth.googleDeeplink.restore();
});
describe('beforeRender', () => {
beforeEach(() => {
sinon.spy(view, 'logViewEvent');
@ -124,16 +132,26 @@ describe('views/mixins/third-party-auth-mixin', function () {
it('google login deeplink', () => {
windowMock.location.search = '?deeplink=googleLogin';
view.beforeRender();
assert.isTrue(view.googleSignIn.calledOnce);
assert.isTrue(view.logFlowEvent.calledOnceWith('google.deeplink'));
view
.beforeRender()
.then(() => {
assert.isTrue(view.googleSignIn.calledOnce);
assert.isTrue(view.logFlowEvent.calledOnceWith('google.deeplink'));
assert.isTrue(GleanMetrics.thirdPartyAuth.googleDeeplink.calledOnce);
})
.catch(() => assert.fail());
});
it('apple login deeplink', () => {
windowMock.location.search = '?deeplink=appleLogin';
view.beforeRender();
assert.isTrue(view.appleSignIn.calledOnce);
assert.isTrue(view.logFlowEvent.calledOnceWith('apple.deeplink'));
view
.beforeRender()
.then(() => {
assert.isTrue(view.appleSignIn.calledOnce);
assert.isTrue(view.logFlowEvent.calledOnceWith('apple.deeplink'));
assert.isTrue(GleanMetrics.thirdPartyAuth.appleDeeplink.calledOnce);
})
.catch(() => assert.fail());
});
});

Просмотреть файл

@ -400,7 +400,7 @@ export const GleanMetrics: Pick<
if (lambdas.length === 0) {
resolve();
} else {
setTimeout(checkForEmptyFnList, 100);
setTimeout(checkForEmptyFnList, lambdas.length * 5);
}
};

Просмотреть файл

@ -1519,7 +1519,45 @@ cad:
expires: never
data_sensitivity:
- interaction
third_party_auth:
apple_deeplink:
type: event
description: |
User clicked Apple Signin link from an RP hosted site and is taken
directly to Apple authentication flow, bypassing Mozilla Accounts email
first page.
send_in_pings:
- events
notification_emails:
- vzare@mozilla.com
- fxa-staff@mozilla.com
bugs:
- https://mozilla-hub.atlassian.net/browse/FXA-9116
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1830504
- https://bugzilla.mozilla.org/show_bug.cgi?id=1844121
expires: never
data_sensitivity:
- interaction
google_deeplink:
type: event
description: |
User clicked Google Signin link from an RP hosted site and is taken
directly to Google authentication flow, bypassing Mozilla Accounts email
first page.
send_in_pings:
- events
notification_emails:
- vzare@mozilla.com
- fxa-staff@mozilla.com
bugs:
- https://mozilla-hub.atlassian.net/browse/FXA-9116
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1830504
- https://bugzilla.mozilla.org/show_bug.cgi?id=1844121
expires: never
data_sensitivity:
- interaction
third_party_auth_set_password:
view:
type: event