Skip to content

Commit e768bbe

Browse files
committed
Allow numbers and booleans values as event extras
1 parent c7c1438 commit e768bbe

File tree

5 files changed

+67
-31
lines changed

5 files changed

+67
-31
lines changed

glean/src/core/metrics/events_database/index.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import type Store from "../../storage/index.js";
66
import type { JSONArray, JSONObject, JSONValue } from "../../utils.js";
7+
import { isString } from "../../utils.js";
78
import type { StorageBuilder } from "../../../platform/index.js";
89
import { isUndefined } from "../../utils.js";
910
import EventMetricType from "../types/event.js";
@@ -12,6 +13,7 @@ import CounterMetricType from "../types/counter.js";
1213
import { Lifetime } from "../lifetime.js";
1314
import { Context } from "../../context.js";
1415
import { generateReservedMetricIdentifiers } from "../database.js";
16+
import type { ExtraValues} from "./recorded_event.js";
1517
import { RecordedEvent } from "./recorded_event.js";
1618
import {
1719
GLEAN_EXECUTION_COUNTER_EXTRA_KEY,
@@ -29,7 +31,11 @@ const LOG_TAG = "core.Metric.EventsDatabase";
2931
* @param str The string to generate a date from.
3032
* @returns The Date object created.
3133
*/
32-
function createDateObject(str = ""): Date {
34+
function createDateObject(str?: ExtraValues): Date {
35+
if (!isString(str)) {
36+
str = "";
37+
}
38+
3339
const date = new Date(str);
3440
// Date object will not throw errors when we attempt to create an invalid date.
3541
if (isNaN(date.getTime())) {
@@ -158,7 +164,7 @@ class EventsDatabase {
158164
for (const ping of metric.sendInPings) {
159165
const executionCounter = getExecutionCounterMetric([ping]);
160166

161-
let currentExecutionCount = await Context.metricsDatabase.getMetric(ping, executionCounter);
167+
let currentExecutionCount = await Context.metricsDatabase.getMetric<number>(ping, executionCounter);
162168
// There might not be an execution counter stored in case:
163169
//
164170
// 1. The ping was already sent during this session and the events storage was cleared;
@@ -171,8 +177,7 @@ class EventsDatabase {
171177
// this must **always** be the first event of any events list.
172178
await recordGleanRestartedEvent([ping], new Date());
173179
}
174-
// TODO (bug 1693487): Remove the stringification once the new events API is implemented.
175-
value.addExtra(GLEAN_EXECUTION_COUNTER_EXTRA_KEY, currentExecutionCount.toString());
180+
value.addExtra(GLEAN_EXECUTION_COUNTER_EXTRA_KEY, currentExecutionCount);
176181

177182
const transformFn = (v?: JSONValue): JSONArray => {
178183
const existing: JSONArray = (v as JSONArray) ?? [];
@@ -285,7 +290,6 @@ class EventsDatabase {
285290
): Promise<JSONArray> {
286291
// Sort events by execution counter and by timestamp.
287292
const sortedEvents = pingData.sort((a, b) => {
288-
// TODO (bug 1693487): Remove the number casting once the new events API is implemented.
289293
const executionCounterA = Number(a.extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY]);
290294
const executionCounterB = Number(b.extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY]);
291295
// Sort by execution counter, in case they are different.

glean/src/core/metrics/events_database/recorded_event.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
import { GLEAN_RESERVED_EXTRA_KEYS } from "../../constants.js";
66
import type { JSONObject } from "../../utils.js";
77

8-
// An helper type for the 'extra' map.
9-
export type ExtraMap = Record<string, string>;
8+
export type ExtraValues = string | boolean | number;
9+
// A helper type for the 'extra' map.
10+
export type ExtraMap = Record<string, ExtraValues>;
1011

1112
// Represents the recorded data for a single event.
1213
export class RecordedEvent {
@@ -53,7 +54,7 @@ export class RecordedEvent {
5354
* @param key The key to add.
5455
* @param value The value of the key.
5556
*/
56-
addExtra(key: string, value: string): void {
57+
addExtra(key: string, value: ExtraValues): void {
5758
if (!this.extra) {
5859
this.extra = {};
5960
}

glean/src/core/metrics/types/event.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { CommonMetricData } from "../index.js";
66
import type { ExtraMap } from "../events_database/recorded_event.js";
77
import { MetricType } from "../index.js";
88
import { RecordedEvent } from "../events_database/recorded_event.js";
9-
import { getMonotonicNow, truncateStringAtBoundaryWithError } from "../../utils.js";
9+
import { getMonotonicNow, isString, truncateStringAtBoundaryWithError } from "../../utils.js";
1010
import { Context } from "../../context.js";
1111
import { ErrorType } from "../../error/error_type.js";
1212

@@ -15,7 +15,7 @@ const MAX_LENGTH_EXTRA_KEY_VALUE = 100;
1515
/**
1616
* An event metric.
1717
*/
18-
class EventMetricType extends MetricType {
18+
class EventMetricType<SpecificExtraMap extends ExtraMap = ExtraMap> extends MetricType {
1919
private allowedExtraKeys?: string[];
2020

2121
constructor(meta: CommonMetricData, allowedExtraKeys?: string[]) {
@@ -33,10 +33,8 @@ class EventMetricType extends MetricType {
3333
* that the recording of the metric will happen in order with other Glean API calls.
3434
*
3535
* @param instance The metric instance to record to.
36-
* @param extra optional. This is a map, both keys and values need to be
37-
* strings, keys are identifiers. This is used for events where
38-
* additional richer context is needed.
39-
* The maximum length for values is 100 bytes.
36+
* @param extra optional. Used for events where additional richer context is needed.
37+
* The maximum length for string values is 100 bytes.
4038
* @param timestamp The event timestamp, defaults to now.
4139
* @returns A promise that resolves once the event is recorded.
4240
*/
@@ -55,7 +53,11 @@ class EventMetricType extends MetricType {
5553
truncatedExtra = {};
5654
for (const [name, value] of Object.entries(extra)) {
5755
if (instance.allowedExtraKeys.includes(name)) {
58-
truncatedExtra[name] = await truncateStringAtBoundaryWithError(instance, value, MAX_LENGTH_EXTRA_KEY_VALUE);
56+
if (isString(value)) {
57+
truncatedExtra[name] = await truncateStringAtBoundaryWithError(instance, value, MAX_LENGTH_EXTRA_KEY_VALUE);
58+
} else {
59+
truncatedExtra[name] = value;
60+
}
5961
} else {
6062
await Context.errorManager.record(instance, ErrorType.InvalidValue, `Invalid key index: ${name}`);
6163
continue;
@@ -77,12 +79,10 @@ class EventMetricType extends MetricType {
7779
/**
7880
* Record an event.
7981
*
80-
* @param extra optional. This is a map, both keys and values need to be
81-
* strings, keys are identifiers. This is used for events where
82-
* additional richer context is needed.
83-
* The maximum length for values is 100 bytes.
82+
* @param extra optional. Used for events where additional richer context is needed.
83+
* The maximum length for string values is 100 bytes.
8484
*/
85-
record(extra?: ExtraMap): void {
85+
record(extra?: SpecificExtraMap): void {
8686
const timestamp = getMonotonicNow();
8787

8888
Context.dispatcher.launch(async () => {

glean/tests/unit/core/metrics/event.spec.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,4 +234,35 @@ describe("EventMetric", function() {
234234
event.extra["truncatedExtra"]
235235
);
236236
});
237+
238+
it("all types of event keys are recorded correctly", async function () {
239+
const event = new EventMetricType<{
240+
"string": string,
241+
"boolean": boolean,
242+
"number": number
243+
}>({
244+
category: "test",
245+
name: "event",
246+
sendInPings: ["store1"],
247+
lifetime: Lifetime.Ping,
248+
disabled: false
249+
}, ["string", "boolean", "number"]);
250+
251+
event.record({
252+
"string": "aString",
253+
"boolean": false,
254+
"number": 42
255+
});
256+
257+
const snapshot = await event.testGetValue();
258+
assert.ok(snapshot);
259+
assert.strictEqual(snapshot.length, 1);
260+
261+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
262+
assert.strictEqual(snapshot[0].extra!["string"], "aString");
263+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
264+
assert.strictEqual(snapshot[0].extra!["boolean"], false);
265+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
266+
assert.strictEqual(snapshot[0].extra!["number"], 42);
267+
});
237268
});

glean/tests/unit/core/metrics/events_database.spec.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ describe("EventsDatabase", function() {
199199
// We need to use `getAndValidatePingData` here,
200200
// because the public function will strip reserved extra keys.
201201
const rawRecordedEvent = (await db["getAndValidatePingData"](ping))[0];
202-
assert.strictEqual(rawRecordedEvent.extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY], "1");
202+
assert.strictEqual(rawRecordedEvent.extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY], 1);
203203
}
204204
});
205205

@@ -271,8 +271,8 @@ describe("EventsDatabase", function() {
271271
// We expect only two events here, restarted and the above. Execution counter 1.
272272
const rawRecordedEvents1 = (await db["getAndValidatePingData"]("aPing"));
273273
assert.strictEqual(rawRecordedEvents1.length, 2);
274-
assert.strictEqual(rawRecordedEvents1[0].extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY], "1");
275-
assert.strictEqual(rawRecordedEvents1[1].extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY], "1");
274+
assert.strictEqual(rawRecordedEvents1[0].extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY], 1);
275+
assert.strictEqual(rawRecordedEvents1[1].extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY], 1);
276276

277277
// Fake restart Glean and record a new event.
278278
const restartedDb = new EventsDatabase(Glean.platform.Storage);
@@ -288,15 +288,15 @@ describe("EventsDatabase", function() {
288288
// the next two events are this run's restart event + the event we just recorded and both are execution counter 2.
289289
const rawRecordedEvents2 = (await db["getAndValidatePingData"]("aPing"))
290290
.sort((a, b) => {
291-
const executionCounterA = parseInt(a.extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY] || "0");
292-
const executionCounterB = parseInt(b.extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY] || "0");
291+
const executionCounterA = a.extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY] as number;
292+
const executionCounterB = b.extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY] as number;
293293
return executionCounterA - executionCounterB;
294294
});
295295
assert.strictEqual(rawRecordedEvents2.length, 4);
296-
assert.strictEqual(rawRecordedEvents2[0].extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY], "1");
297-
assert.strictEqual(rawRecordedEvents2[1].extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY], "1");
298-
assert.strictEqual(rawRecordedEvents2[2].extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY], "2");
299-
assert.strictEqual(rawRecordedEvents2[3].extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY], "2");
296+
assert.strictEqual(rawRecordedEvents2[0].extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY], 1);
297+
assert.strictEqual(rawRecordedEvents2[1].extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY], 1);
298+
assert.strictEqual(rawRecordedEvents2[2].extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY], 2);
299+
assert.strictEqual(rawRecordedEvents2[3].extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY], 2);
300300

301301
ping.submit();
302302
// Sanity check that the execution counter was cleared.
@@ -311,8 +311,8 @@ describe("EventsDatabase", function() {
311311
// We expect only two events again, the other have been cleared, execution counter 1.
312312
const rawRecordedEvents3 = (await db["getAndValidatePingData"]("aPing"));
313313
assert.strictEqual(rawRecordedEvents3.length, 2);
314-
assert.strictEqual(rawRecordedEvents3[0].extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY], "1");
315-
assert.strictEqual(rawRecordedEvents3[1].extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY], "1");
314+
assert.strictEqual(rawRecordedEvents3[0].extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY], 1);
315+
assert.strictEqual(rawRecordedEvents3[1].extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY], 1);
316316
});
317317

318318
it("reserved extra properties are removed from the recorded events", async function () {

0 commit comments

Comments
 (0)