Skip to content

Commit ee299ca

Browse files
author
Beatriz Rizental
authored
Bug 1679378 - Implement the string metric type (#13)
1 parent d400397 commit ee299ca

File tree

11 files changed

+298
-98
lines changed

11 files changed

+298
-98
lines changed

src/database.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,28 @@ class Database {
129129
}
130130

131131
/**
132-
* Gets the persisted payload of a given metric in a given ping.
132+
* Gets and validates the persisted payload of a given metric in a given ping.
133+
*
134+
* If the persisted value is invalid for the metric we are attempting to retrieve,
135+
* the persisted value is deleted and `undefined is returned.
136+
*
137+
* This behaviour is not consistent with what the Glean SDK does, but this is on purpose.
138+
* On the Glean SDK we panic when we can't serialize the persisted value,
139+
* that is because this is an extremely unlikely situation for that environment.
140+
*
141+
* Since Glean.js will run on the browser, it is easy for a consumers / developers
142+
* to mess with the storage which makes this sort of errors plausible.
143+
* That is why we choose to not panic and simply delete the corrupted data here.
144+
*
145+
* Note: This is not a strong guard against consumers / developers messing with the storage on their own.
146+
* Currently Glean.js does not include mechanisms to reliably prevent that.
133147
*
134148
* @param ping The ping from which we want to retrieve the given metric.
135-
* @param validateFn A validation function to verify if persisted payload is of the correct type.
149+
* @param validateFn A validation function to verify if persisted payload is in the correct format.
136150
* @param metric An object containing the information about the metric to retrieve.
137151
*
138152
* @returns The payload persisted for the given metric,
139-
* `undefined` in case the metric has not been recorded yet.
153+
* `undefined` in case the metric has not been recorded yet or the found valus in invalid.
140154
*/
141155
async getMetric<T>(
142156
ping: string,
@@ -147,13 +161,6 @@ class Database {
147161
const storageKey = metric.identifier;
148162
const value = await store.get([ping, metric.type, storageKey]);
149163
if (!isUndefined(value) && !validateFn(value)) {
150-
// The following behaviour is not consistent with what the Glean SDK does, but this is on purpose.
151-
// On the Glean SDK we panic when we can't serialize the given,
152-
// that is because this is a extremely unlikely situation for that environment.
153-
//
154-
// Since Glean.js will run on the browser, it is easy for a user to mess with the persisted data
155-
// which makes this sort of errors plausible. That is why we choose to not panic and
156-
// simply delete the corrupted data here.
157164
console.error(`Unexpected value found for metric ${metric.identifier}: ${JSON.stringify(value)}. Clearing.`);
158165
await store.delete([ping, metric.type, storageKey]);
159166
return;

src/glean.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,20 @@ class Glean {
4747
static set uploadEnabled(value: boolean) {
4848
Glean.instance._uploadEnabled = value;
4949
}
50+
51+
/**
52+
* **Test-only API**
53+
*
54+
* Resets the Glean singleton to its initial state.
55+
*
56+
* TODO: Only allow this function to be called on test mode (depends on Bug 1682771).
57+
*/
58+
static async resetGlean(): Promise<void> {
59+
// Reset upload enabled state, not to inerfere with other tests.
60+
Glean.uploadEnabled = true;
61+
// Clear the database.
62+
await Glean.db.clearAll();
63+
}
5064
}
5165

5266
export default Glean;

src/metrics/boolean.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ import Glean from "glean";
77
import { isBoolean } from "utils";
88

99
export type BooleanMetricPayload = boolean;
10+
1011
/**
1112
* Checks whether or not `v` is a valid boolean metric payload.
1213
*
1314
* @param v The value to verify.
1415
*
1516
* @returns A special Typescript value (which compiles down to a boolean)
16-
* stating wether `v` is a valid boolean metric payload.
17+
* stating whether `v` is a valid boolean metric payload.
1718
*/
1819
export function isBooleanMetricPayload(v: unknown): v is BooleanMetricPayload {
1920
return isBoolean(v);
@@ -38,7 +39,7 @@ class BooleanMetric extends Metric {
3839
}
3940

4041
/**
41-
* **Test-only API (exported for FFI purposes).**
42+
* **Test-only API**
4243
*
4344
* Gets the currently stored value as a boolean.
4445
*

src/metrics/payload.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44

55
import { BooleanMetricPayload, isBooleanMetricPayload } from "metrics/boolean";
6-
import { isString } from "utils";
6+
import { StringMetricPayload, isStringMetricPayload } from "metrics/string";
77

88
/**
99
* Validates that a given value is the correct type of payload for a metric of a given type.
@@ -17,11 +17,15 @@ export function isMetricPayload(type: string, v: unknown): v is MetricPayload {
1717
switch (type) {
1818
case "boolean":
1919
return isBooleanMetricPayload(v);
20+
case "string":
21+
return isStringMetricPayload(v);
2022
default:
21-
return isString(v);
23+
return false;
2224
}
2325
}
2426

2527
// Leaving the `string` as a valid metric payload here so that tests keep working for now.
26-
export type MetricPayload = BooleanMetricPayload | string;
28+
export type MetricPayload =
29+
BooleanMetricPayload |
30+
StringMetricPayload;
2731

src/metrics/string.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
import Metric, { CommonMetricData } from "metrics";
6+
import Glean from "glean";
7+
import { isString } from "utils";
8+
9+
export const MAX_LENGTH_VALUE = 100;
10+
11+
export type StringMetricPayload = string;
12+
13+
/**
14+
* Checks whether or not `v` is a valid string metric payload.
15+
*
16+
* # Note
17+
*
18+
* Not only will this verify if `v` is a string,
19+
* it will also check if its length is less than `MAX_LENGTH_VALUE`.
20+
*
21+
* @param v The value to verify.
22+
*
23+
* @returns A special Typescript value (which compiles down to a boolean)
24+
* stating wether `v` is a valid string metric payload.
25+
*/
26+
export function isStringMetricPayload(v: unknown): v is StringMetricPayload {
27+
if (!isString(v)) {
28+
return false;
29+
}
30+
31+
if (v.length > MAX_LENGTH_VALUE) {
32+
return false;
33+
}
34+
35+
return true;
36+
}
37+
38+
class StringMetric extends Metric {
39+
constructor(meta: CommonMetricData) {
40+
super("string", meta);
41+
}
42+
43+
/**
44+
* Sets to the specified string value.
45+
*
46+
* # Note
47+
*
48+
* Truncates the value if it is longer than `MAX_STRING_LENGTH` bytes
49+
* and logs an error.
50+
*
51+
* @param value the value to set.
52+
*/
53+
async set(value: string): Promise<void> {
54+
if (!this.shouldRecord()) {
55+
return;
56+
}
57+
58+
if (value.length > MAX_LENGTH_VALUE) {
59+
// TODO: record error once Bug 1682574 is resolved.
60+
console.warn(`String ${value} is longer than ${MAX_LENGTH_VALUE} chars. Truncating.`);
61+
}
62+
63+
await Glean.db.record(this, value.substring(0, MAX_LENGTH_VALUE));
64+
}
65+
66+
/**
67+
* **Test-only API**
68+
*
69+
* Gets the currently stored value as a string.
70+
*
71+
* This doesn't clear the stored value.
72+
*
73+
* TODO: Only allow this function to be called on test mode (depends on Bug 1682771).
74+
*
75+
* @param ping the ping from which we want to retrieve this metrics value from.
76+
*
77+
* @returns The value found in storage or `undefined` if nothing was found.
78+
*/
79+
async testGetValue(ping: string): Promise<StringMetricPayload | undefined> {
80+
return Glean.db.getMetric(ping, isStringMetricPayload, this);
81+
}
82+
}
83+
84+
export default StringMetric;

src/storage/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export interface StorageObject {
3939
* @param v The value to verify
4040
*
4141
* @returns A special Typescript value (which compiles down to a boolean)
42-
* stating wether `v` is a valid StorageValue.
42+
* stating whether `v` is a valid StorageValue.
4343
*/
4444
export function isStorageValue(v: unknown): v is StorageValue {
4545
if (isUndefined(v) || isString(v) || isBoolean(v)) {

src/utils.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* @param v The value to verify.
99
*
1010
* @returns A special Typescript value (which compiles down to a boolean)
11-
* stating wether `v` is a valid data object.
11+
* stating whether `v` is a valid data object.
1212
*/
1313
export function isObject(v: unknown): v is Record<string | number | symbol, unknown> {
1414
return (typeof v === "object" && v !== null && v.constructor === Object);
@@ -20,7 +20,7 @@ export function isObject(v: unknown): v is Record<string | number | symbol, unkn
2020
* @param v The value to verify.
2121
*
2222
* @returns A special Typescript value (which compiles down to a boolean)
23-
* stating wether `v` is undefined.
23+
* stating whether `v` is undefined.
2424
*/
2525
export function isUndefined(v: unknown): v is undefined {
2626
return typeof v === "undefined";
@@ -32,7 +32,7 @@ export function isUndefined(v: unknown): v is undefined {
3232
* @param v The value to verify.
3333
*
3434
* @returns A special Typescript value (which compiles down to a boolean)
35-
* stating wether `v` is a string.
35+
* stating whether `v` is a string.
3636
*/
3737
export function isString(v: unknown): v is string {
3838
return (typeof v === "string" || (typeof v === "object" && v !== null && v.constructor === String));
@@ -44,7 +44,7 @@ export function isString(v: unknown): v is string {
4444
* @param v The value to verify.
4545
*
4646
* @returns A special Typescript value (which compiles down to a boolean)
47-
* stating wether `v` is a boolean.
47+
* stating whether `v` is a boolean.
4848
*/
4949
export function isBoolean(v: unknown): v is string {
5050
return (typeof v === "boolean" || (typeof v === "object" && v !== null && v.constructor === Boolean));

0 commit comments

Comments
 (0)