Skip to content

Commit 030d850

Browse files
authored
Merge pull request #647 from badboy/text-metric
Bug 1726327 - Implement the new text metric type
2 parents b9ac92a + f99cac3 commit 030d850

File tree

10 files changed

+222
-2
lines changed

10 files changed

+222
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
* Only up to 15 ping submissions every 60 seconds are now allowed.
1212
* [#658](https://github.com/mozilla/glean.js/pull/658): BUGFIX: Unblock ping uploading jobs after the maximum of upload failures are hit for a given uploading window.
1313
* [#661](https://github.com/mozilla/glean.js/pull/661): Include unminified version of library on Qt/QML builds.
14+
* [#647](https://github.com/mozilla/glean.js/pull/647): Implement the Text metric type.
1415

1516
# v0.18.1 (2021-07-22)
1617

benchmarks/size/webext/max.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import EventMetricType from "@mozilla/glean/webext/private/metrics/event";
1111
import LabeledMetricType from "@mozilla/glean/webext/private/metrics/labeled";
1212
import QuantityMetricType from "@mozilla/glean/webext/private/metrics/quantity";
1313
import StringMetricType from "@mozilla/glean/webext/private/metrics/string";
14+
import TextMetricType from "@mozilla/glean/webext/private/metrics/text";
1415
import TimespanMetricType from "@mozilla/glean/webext/private/metrics/timespan";
1516
import UUIDMetricType from "@mozilla/glean/webext/private/metrics/uuid";
1617
import URLMetricType from "@mozilla/glean/webext/private/metrics/url";
@@ -28,6 +29,7 @@ console.log(
2829
JSON.stringify(LabeledMetricType),
2930
JSON.stringify(QuantityMetricType),
3031
JSON.stringify(StringMetricType),
32+
JSON.stringify(TextMetricType),
3133
JSON.stringify(TimespanMetricType),
3234
JSON.stringify(UUIDMetricType),
3335
JSON.stringify(URLMetricType),

glean/src/cli.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const LOG_TAG = "CLI";
1919
const VIRTUAL_ENVIRONMENT_DIR = ".venv";
2020

2121
// The version of glean_parser to install from PyPI.
22-
const GLEAN_PARSER_VERSION = "3.8.0";
22+
const GLEAN_PARSER_VERSION = "4.0.0";
2323

2424
// This script runs a given Python module as a "main" module, like
2525
// `python -m module`. However, it first checks that the installed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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 type { CommonMetricData } from "../index.js";
6+
import { isString, truncateStringAtBoundaryWithError } from "../../utils.js";
7+
import { MetricType } from "../index.js";
8+
import { Context } from "../../context.js";
9+
import { Metric } from "../metric.js";
10+
11+
// The maximum number of characters for text.
12+
export const TEXT_MAX_LENGTH = 200 * 1024;
13+
14+
export class TextMetric extends Metric<string, string> {
15+
constructor(v: unknown) {
16+
super(v);
17+
}
18+
19+
/**
20+
* Validates that a given value is within bounds.
21+
*
22+
* @param v The value to validate.
23+
* @returns Whether or not v is valid text data.
24+
*/
25+
validate(v: unknown): v is string {
26+
if (!isString(v)) {
27+
return false;
28+
}
29+
30+
if (v.length > TEXT_MAX_LENGTH) {
31+
return false;
32+
}
33+
34+
return true;
35+
}
36+
37+
payload(): string {
38+
return this._inner;
39+
}
40+
}
41+
42+
/**
43+
* A text metric.
44+
*/
45+
class TextMetricType extends MetricType {
46+
constructor(meta: CommonMetricData) {
47+
super("text", meta);
48+
}
49+
50+
/**
51+
* Sets to a specified value.
52+
*
53+
* @param text the value to set.
54+
*/
55+
set(text: string): void {
56+
Context.dispatcher.launch(async () => {
57+
if (!this.shouldRecord(Context.uploadEnabled)) {
58+
return;
59+
}
60+
61+
const truncatedValue = await truncateStringAtBoundaryWithError(this, text, TEXT_MAX_LENGTH);
62+
const metric = new TextMetric(truncatedValue);
63+
await Context.metricsDatabase.record(this, metric);
64+
});
65+
}
66+
67+
/**
68+
* Test-only API.**
69+
*
70+
* Gets the currently stored value as a string.
71+
*
72+
* This doesn't clear the stored value.
73+
*
74+
* TODO: Only allow this function to be called on test mode (depends on Bug 1682771).
75+
*
76+
* @param ping the ping from which we want to retrieve this metrics value from.
77+
* Defaults to the first value in `sendInPings`.
78+
* @returns The value found in storage or `undefined` if nothing was found.
79+
*/
80+
async testGetValue(ping: string = this.sendInPings[0]): Promise<string | undefined> {
81+
let metric: string | undefined;
82+
await Context.dispatcher.testLaunch(async () => {
83+
metric = await Context.metricsDatabase.getMetric<string>(ping, this);
84+
});
85+
return metric;
86+
}
87+
}
88+
89+
export default TextMetricType;

glean/src/core/metrics/utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { CounterMetric } from "./types/counter.js";
1212
import { DatetimeMetric } from "./types/datetime.js";
1313
import { QuantityMetric } from "./types/quantity.js";
1414
import { StringMetric } from "./types/string.js";
15+
import { TextMetric } from "./types/text.js";
1516
import { TimespanMetric } from "./types/timespan.js";
1617
import { UrlMetric } from "./types/url.js";
1718
import { UUIDMetric } from "./types/uuid.js";
@@ -30,6 +31,7 @@ const METRIC_MAP: {
3031
"labeled_string": LabeledMetric,
3132
"quantity": QuantityMetric,
3233
"string": StringMetric,
34+
"text": TextMetric,
3335
"timespan": TimespanMetric,
3436
"url": UrlMetric,
3537
"uuid": UUIDMetric,

glean/src/index/qt.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import EventMetricType from "../core/metrics/types/event.js";
1717
import LabeledMetricType from "../core/metrics/types/labeled.js";
1818
import QuantityMetricType from "../core/metrics/types/quantity.js";
1919
import StringMetricType from "../core/metrics/types/string.js";
20+
import TextMetricType from "../core/metrics/types/text.js";
2021
import TimespanMetricType from "../core/metrics/types/timespan.js";
2122
import UUIDMetricType from "../core/metrics/types/uuid.js";
2223
import URLMetricType from "../core/metrics/types/url.js";
@@ -124,6 +125,7 @@ export default {
124125
QuantityMetricType,
125126
StringMetricType,
126127
TimespanMetricType,
128+
TextMetricType,
127129
UUIDMetricType,
128130
URLMetricType
129131
}

glean/tests/integration/schema/metrics.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,18 @@ for_testing:
175175
expires: never
176176
send_in_pings:
177177
- testing
178+
text:
179+
type: text
180+
description: |
181+
Sample text metric.
182+
bugs:
183+
- https://bugzilla.mozilla.org/000000
184+
data_reviews:
185+
- https://bugzilla.mozilla.org/show_bug.cgi?id=000000#c3
186+
notification_emails:
187+
- me@mozilla.com
188+
expires: never
189+
send_in_pings:
190+
- testing
191+
data_sensitivity:
192+
- highly_sensitive

glean/tests/integration/schema/schema.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ describe("schema", function() {
7070
metrics.labeledString["a_label"].set("ho");
7171
metrics.quantity.set(42);
7272
metrics.string.set("let's go");
73+
metrics.text.set("let's gooooooooo");
7374
metrics.timespan.setRawNanos(10 * 10**6);
7475
metrics.uuid.generateAndSet();
7576
metrics.url.set("glean://test");
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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 assert from "assert";
6+
import { Context } from "../../../../src/core/context";
7+
import { ErrorType } from "../../../../src/core/error/error_type";
8+
9+
import Glean from "../../../../src/core/glean";
10+
import { Lifetime } from "../../../../src/core/metrics/lifetime";
11+
import TextMetricType, { TEXT_MAX_LENGTH } from "../../../../src/core/metrics/types/text";
12+
13+
describe("TextMetric", function() {
14+
const testAppId = `gleanjs.test.${this.title}`;
15+
16+
beforeEach(async function() {
17+
await Glean.testResetGlean(testAppId);
18+
});
19+
20+
it("attempting to get the value of a metric that hasn't been recorded doesn't error", async function() {
21+
const metric = new TextMetricType({
22+
category: "aCategory",
23+
name: "aTextMetric",
24+
sendInPings: ["aPing", "twoPing", "threePing"],
25+
lifetime: Lifetime.Ping,
26+
disabled: false
27+
});
28+
29+
assert.strictEqual(await metric.testGetValue("aPing"), undefined);
30+
});
31+
32+
it("attempting to set when glean upload is disabled is a no-op", async function() {
33+
Glean.setUploadEnabled(false);
34+
35+
const metric = new TextMetricType({
36+
category: "aCategory",
37+
name: "aTextMetric",
38+
sendInPings: ["aPing", "twoPing", "threePing"],
39+
lifetime: Lifetime.Ping,
40+
disabled: false
41+
});
42+
43+
metric.set("some value");
44+
assert.strictEqual(await metric.testGetValue("aPing"), undefined);
45+
});
46+
47+
it("ping payload is correct", async function() {
48+
const metric = new TextMetricType({
49+
category: "aCategory",
50+
name: "aTextMetric",
51+
sendInPings: ["aPing"],
52+
lifetime: Lifetime.Ping,
53+
disabled: false
54+
});
55+
56+
const validValues = [
57+
"some value",
58+
"<html><head><title>Website</title></head><body><h1>Text</h1></body>",
59+
"some longer text\nwith newlines\nand also some quotes: \"once upon a time ...\"",
60+
];
61+
62+
for (const value of validValues) {
63+
metric.set(value);
64+
assert.strictEqual(await metric.testGetValue("aPing"), value);
65+
66+
const snapshot = await Context.metricsDatabase.getPingMetrics("aPing", true);
67+
assert.deepStrictEqual(snapshot, {
68+
"text": {
69+
"aCategory.aTextMetric": value
70+
}
71+
});
72+
}
73+
});
74+
75+
it("set properly sets the value in all pings", async function() {
76+
const metric = new TextMetricType({
77+
category: "aCategory",
78+
name: "aTextMetric",
79+
sendInPings: ["aPing", "twoPing", "threePing"],
80+
lifetime: Lifetime.Ping,
81+
disabled: false
82+
});
83+
84+
metric.set("some value");
85+
assert.strictEqual(await metric.testGetValue("aPing"), "some value");
86+
assert.strictEqual(await metric.testGetValue("twoPing"), "some value");
87+
assert.strictEqual(await metric.testGetValue("threePing"), "some value");
88+
});
89+
90+
it("truncates when text exceeds maximum length and records errors", async function () {
91+
const metric = new TextMetricType({
92+
category: "aCategory",
93+
name: "aTextMetric",
94+
sendInPings: ["aPing"],
95+
lifetime: Lifetime.Ping,
96+
disabled: false
97+
});
98+
99+
const testText = `some value ${"a".repeat(TEXT_MAX_LENGTH)}`;
100+
metric.set(testText);
101+
const truncated = testText.substr(0, TEXT_MAX_LENGTH);
102+
103+
assert.strictEqual(await metric.testGetValue("aPing"), truncated);
104+
assert.strictEqual(
105+
await metric.testGetNumRecordedErrors(ErrorType.InvalidOverflow), 1
106+
);
107+
});
108+
});
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
PySide2==5.15.2
22
shiboken2==5.15.2
3-
glean_parser==3.8.0
3+
glean_parser==4.0.0

0 commit comments

Comments
 (0)