feat(metrics): set available ping metrics during Glean init

Because:
 - we want to send known metric values in the automatic page load events

This commit:
 - set the available metrics when initializing Glean, before any page
   load events
This commit is contained in:
Barry Chen 2024-09-17 07:50:08 -05:00
Родитель 1328ec8597
Коммит 5a80a63555
Не найден ключ, соответствующий данной подписи
8 изменённых файлов: 81 добавлений и 41 удалений

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

@ -172,7 +172,7 @@ Start.prototype = {
},
initializeGlean() {
GleanMetrics.initialize(this._config.glean, {
return GleanMetrics.initialize(this._config.glean, {
metrics: this._metrics,
relier: this._relier,
user: this._user,

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

@ -87,7 +87,7 @@ const hashUid = async (uid) => {
return hex;
};
const populateMetrics = async (properties: EventProperties = {}) => {
const initMetrics = async () => {
const account = gleanMetricsContext.user.getSignedInAccount();
// the "signed in" account could just be the most recently used account from
@ -103,10 +103,6 @@ const populateMetrics = async (properties: EventProperties = {}) => {
userIdSha256.set('');
}
for (const n of eventPropertyNames) {
event[n].set(properties[n] || '');
}
const flowEventMetadata = gleanMetricsContext.metrics.getFlowEventMetadata();
oauthClientId.set(gleanMetricsContext.relier.get('clientId') || '');
@ -126,6 +122,13 @@ const populateMetrics = async (properties: EventProperties = {}) => {
entrypointQuery.variation.set(flowEventMetadata.entrypointVariation || '');
};
const populateMetrics = async (properties: EventProperties = {}) => {
await initMetrics();
for (const n of eventPropertyNames) {
event[n].set(properties[n] || '');
}
};
const recordEventMetric = (eventName: string, properties: EventProperties) => {
switch (eventName) {
case 'email_first_view':
@ -349,7 +352,10 @@ const createEventFn =
};
export const GleanMetrics = {
initialize: (config: GleanMetricsConfig, context: GleanMetricsContext) => {
initialize: async (
config: GleanMetricsConfig,
context: GleanMetricsContext
) => {
// https://bugzilla.mozilla.org/show_bug.cgi?id=1859629
// Starting with glean.js v2, accessing localStorage during
// initialization could cause an error
@ -373,6 +379,7 @@ export const GleanMetrics = {
gleanMetricsContext = context;
}
GleanMetrics.setEnabled(config.enabled);
await initMetrics();
} catch (_) {
// set some states so we won't try to do anything with glean.js later
config.enabled = false;

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

@ -187,7 +187,8 @@ describe('lib/glean', () => {
});
it('submits a ping on an event', async () => {
await GleanMetrics.registration.view();
GleanMetrics.registration.view();
await GleanMetrics.isDone();
sinon.assert.calledOnce(submitPingStub);
});

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

@ -81,6 +81,7 @@ jest.mock('../../lib/glean', () => ({
default: {
initialize: jest.fn(),
getEnabled: jest.fn(),
useGlean: jest.fn().mockReturnValue({ enabled: true }),
accountPref: { view: jest.fn(), promoMonitorView: jest.fn() },
pageLoad: jest.fn(),
},

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

@ -161,6 +161,11 @@ export const App = ({
return;
}
// This is in a useMemo because we want Glean to be initialized _before_
// other components are rendered. `useEffect` is called after a component
// is rendered, which for this means _after_ all the children components
// are rendered and _their `useEffect` hooks are called_.
GleanMetrics.initialize(
{
...config.glean,
@ -297,10 +302,11 @@ const AuthAndAccountSetupRoutes = ({
// TODO: MozServices / string discrepancy, FXA-6802
const serviceName = integration.getServiceName() as MozServices;
const location = useLocation();
const { enabled: gleanEnabled } = GleanMetrics.useGlean();
useEffect(() => {
GleanMetrics.pageLoad(location.pathname);
}, [location.pathname]);
gleanEnabled && GleanMetrics.pageLoad(location.pathname);
}, [location.pathname, gleanEnabled]);
return (
<Router>

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

@ -73,10 +73,11 @@ export const Settings = ({
}, [account, session]);
const { loading, error } = useInitialSettingsState();
const { enabled: gleanEnabled } = GleanMetrics.useGlean();
useEffect(() => {
!loading && GleanMetrics.pageLoad(location.pathname);
}, [loading, location.pathname]);
!loading && gleanEnabled && GleanMetrics.pageLoad(location.pathname);
}, [loading, location.pathname, gleanEnabled]);
if (loading) {
return <LoadingSpinner fullScreen />;

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

@ -166,6 +166,10 @@ describe('lib/glean', () => {
sinon.assert.notCalled(setEntrypointExperimentStub);
sinon.assert.notCalled(setEntrypointVariationStub);
});
it('returns false for enabed in useGlean', () => {
expect(GleanMetrics.useGlean().enabled).toBe(false);
});
});
describe('initialization error', () => {
@ -211,6 +215,10 @@ describe('lib/glean', () => {
sinon.assert.calledWith(setEnabledSpy, true);
});
it('returns true for enabed in useGlean', () => {
expect(GleanMetrics.useGlean().enabled).toBe(true);
});
it('submits a ping on an event', async () => {
GleanMetrics.registration.view();
await GleanMetrics.isDone();

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

@ -66,6 +66,8 @@ type GleanMetricsT = {
isDone: () => Promise<void>;
pageLoad: (url?: string) => void;
handleClickEvent(event: Event): void;
useGlean: () => { enabled: boolean };
} & {
[k in EventMapKeys]: { [eventKey in keyof EventsMap[k]]: PingFn };
};
@ -127,34 +129,7 @@ const getDeviceType: () => DeviceTypes | void = () => {
}
};
const populateMetrics = async (gleanPingMetrics: GleanPingMetrics) => {
if (gleanPingMetrics?.event) {
// The event here is the Glean `event` metric type, not an "metrics event" in
// a more general sense
for (const name of booleanEventPropertyNames) {
const eventValue = gleanPingMetrics.event[name];
if (eventValue) {
event[name].set(eventValue);
}
}
for (const name of stringEventPropertyNames) {
event[name].set(gleanPingMetrics.event[name] || '');
}
}
if (gleanPingMetrics?.sync?.cwts) {
Object.entries(gleanPingMetrics.sync.cwts).forEach(([k, v]) => {
sync.cwts[k].set(v);
});
}
if (gleanPingMetrics?.standard?.marketing) {
Object.entries(gleanPingMetrics.standard.marketing).forEach(([k, v]) => {
standard.marketing[k].set(v);
});
}
const initMetrics = async () => {
userId.set('');
userIdSha256.set('');
try {
@ -187,6 +162,37 @@ const populateMetrics = async (gleanPingMetrics: GleanPingMetrics) => {
);
};
const populateMetrics = async (gleanPingMetrics: GleanPingMetrics) => {
await initMetrics();
if (gleanPingMetrics?.event) {
// The event here is the Glean `event` metric type, not an "metrics event" in
// a more general sense
for (const name of booleanEventPropertyNames) {
const eventValue = gleanPingMetrics.event[name];
if (eventValue) {
event[name].set(eventValue);
}
}
for (const name of stringEventPropertyNames) {
event[name].set(gleanPingMetrics.event[name] || '');
}
}
if (gleanPingMetrics?.sync?.cwts) {
Object.entries(gleanPingMetrics.sync.cwts).forEach(([k, v]) => {
sync.cwts[k].set(v);
});
}
if (gleanPingMetrics?.standard?.marketing) {
Object.entries(gleanPingMetrics.standard.marketing).forEach(([k, v]) => {
standard.marketing[k].set(v);
});
}
};
const recordEventMetric = (
eventName: string,
gleanPingMetrics: GleanPingMetrics
@ -532,11 +538,15 @@ export const GleanMetrics: Pick<
| 'initialize'
| 'setEnabled'
| 'getEnabled'
| 'useGlean'
| 'isDone'
| 'pageLoad'
| 'handleClickEvent'
> = {
initialize: (config: GleanMetricsConfig, context: GleanMetricsContext) => {
initialize: async (
config: GleanMetricsConfig,
context: GleanMetricsContext
) => {
// https://bugzilla.mozilla.org/show_bug.cgi?id=1859629
// Starting with glean.js v2, accessing localStorage during
// initialization could cause an error
@ -557,6 +567,10 @@ export const GleanMetrics: Pick<
GleanMetrics.setEnabled(config.enabled);
metricsContext = context;
ua = null;
// try to initialize any available metrics prior to any automatic Glean
// events
await initMetrics();
} catch (_) {
// set some states so we won't try to do anything with glean.js later
config.enabled = false;
@ -573,6 +587,8 @@ export const GleanMetrics: Pick<
return gleanEnabled;
},
useGlean: () => ({ enabled: GleanMetrics.getEnabled() }),
pageLoad: (url?: string) => {
if (url) {
GleanMetricsAPI.pageLoad({ url });