Implement the new text metric type

The new text metric type is really just a long string,
but only allowed under specific circumstances.
This commit is contained in:
Jan-Erik Rediger 2021-08-19 17:16:51 +02:00
Родитель b9ac92a85c
Коммит e4cb123994
8 изменённых файлов: 220 добавлений и 0 удалений

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

@ -11,6 +11,7 @@
* Only up to 15 ping submissions every 60 seconds are now allowed.
* [#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.
* [#661](https://github.com/mozilla/glean.js/pull/661): Include unminified version of library on Qt/QML builds.
* [#647](https://github.com/mozilla/glean.js/pull/647): Implement the Text metric type.
# v0.18.1 (2021-07-22)

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

@ -11,6 +11,7 @@ import EventMetricType from "@mozilla/glean/webext/private/metrics/event";
import LabeledMetricType from "@mozilla/glean/webext/private/metrics/labeled";
import QuantityMetricType from "@mozilla/glean/webext/private/metrics/quantity";
import StringMetricType from "@mozilla/glean/webext/private/metrics/string";
import TextMetricType from "@mozilla/glean/webext/private/metrics/text";
import TimespanMetricType from "@mozilla/glean/webext/private/metrics/timespan";
import UUIDMetricType from "@mozilla/glean/webext/private/metrics/uuid";
import URLMetricType from "@mozilla/glean/webext/private/metrics/url";
@ -28,6 +29,7 @@ console.log(
JSON.stringify(LabeledMetricType),
JSON.stringify(QuantityMetricType),
JSON.stringify(StringMetricType),
JSON.stringify(TextMetricType),
JSON.stringify(TimespanMetricType),
JSON.stringify(UUIDMetricType),
JSON.stringify(URLMetricType),

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

@ -0,0 +1,89 @@
/* 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/. */
import type { CommonMetricData } from "../index.js";
import { isString, truncateStringAtBoundaryWithError } from "../../utils.js";
import { MetricType } from "../index.js";
import { Context } from "../../context.js";
import { Metric } from "../metric.js";
// The maximum number of characters for text.
export const TEXT_MAX_LENGTH = 200 * 1024;
export class TextMetric extends Metric<string, string> {
constructor(v: unknown) {
super(v);
}
/**
* Validates that a given value is within bounds.
*
* @param v The value to validate.
* @returns Whether or not v is valid text data.
*/
validate(v: unknown): v is string {
if (!isString(v)) {
return false;
}
if (v.length > TEXT_MAX_LENGTH) {
return false;
}
return true;
}
payload(): string {
return this._inner;
}
}
/**
* A text metric.
*/
class TextMetricType extends MetricType {
constructor(meta: CommonMetricData) {
super("text", meta);
}
/**
* Sets to a specified value.
*
* @param text the value to set.
*/
set(text: string): void {
Context.dispatcher.launch(async () => {
if (!this.shouldRecord(Context.uploadEnabled)) {
return;
}
const truncatedValue = await truncateStringAtBoundaryWithError(this, text, TEXT_MAX_LENGTH);
const metric = new TextMetric(truncatedValue);
await Context.metricsDatabase.record(this, metric);
});
}
/**
* Test-only API.**
*
* Gets the currently stored value as a string.
*
* This doesn't clear the stored value.
*
* TODO: Only allow this function to be called on test mode (depends on Bug 1682771).
*
* @param ping the ping from which we want to retrieve this metrics value from.
* Defaults to the first value in `sendInPings`.
* @returns The value found in storage or `undefined` if nothing was found.
*/
async testGetValue(ping: string = this.sendInPings[0]): Promise<string | undefined> {
let metric: string | undefined;
await Context.dispatcher.testLaunch(async () => {
metric = await Context.metricsDatabase.getMetric<string>(ping, this);
});
return metric;
}
}
export default TextMetricType;

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

@ -12,6 +12,7 @@ import { CounterMetric } from "./types/counter.js";
import { DatetimeMetric } from "./types/datetime.js";
import { QuantityMetric } from "./types/quantity.js";
import { StringMetric } from "./types/string.js";
import { TextMetric } from "./types/text.js";
import { TimespanMetric } from "./types/timespan.js";
import { UrlMetric } from "./types/url.js";
import { UUIDMetric } from "./types/uuid.js";
@ -30,6 +31,7 @@ const METRIC_MAP: {
"labeled_string": LabeledMetric,
"quantity": QuantityMetric,
"string": StringMetric,
"text": TextMetric,
"timespan": TimespanMetric,
"url": UrlMetric,
"uuid": UUIDMetric,

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

@ -17,6 +17,7 @@ import EventMetricType from "../core/metrics/types/event.js";
import LabeledMetricType from "../core/metrics/types/labeled.js";
import QuantityMetricType from "../core/metrics/types/quantity.js";
import StringMetricType from "../core/metrics/types/string.js";
import TextMetricType from "../core/metrics/types/text.js";
import TimespanMetricType from "../core/metrics/types/timespan.js";
import UUIDMetricType from "../core/metrics/types/uuid.js";
import URLMetricType from "../core/metrics/types/url.js";
@ -124,6 +125,7 @@ export default {
QuantityMetricType,
StringMetricType,
TimespanMetricType,
TextMetricType,
UUIDMetricType,
URLMetricType
}

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

@ -175,3 +175,18 @@ for_testing:
expires: never
send_in_pings:
- testing
text:
type: text
description: |
Sample text metric.
bugs:
- https://bugzilla.mozilla.org/000000
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=000000#c3
notification_emails:
- me@mozilla.com
expires: never
send_in_pings:
- testing
data_sensitivity:
- highly_sensitive

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

@ -70,6 +70,7 @@ describe("schema", function() {
metrics.labeledString["a_label"].set("ho");
metrics.quantity.set(42);
metrics.string.set("let's go");
metrics.text.set("let's gooooooooo");
metrics.timespan.setRawNanos(10 * 10**6);
metrics.uuid.generateAndSet();
metrics.url.set("glean://test");

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

@ -0,0 +1,108 @@
/* 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/. */
import assert from "assert";
import { Context } from "../../../../src/core/context";
import { ErrorType } from "../../../../src/core/error/error_type";
import Glean from "../../../../src/core/glean";
import { Lifetime } from "../../../../src/core/metrics/lifetime";
import TextMetricType, { TEXT_MAX_LENGTH } from "../../../../src/core/metrics/types/text";
describe("TextMetric", function() {
const testAppId = `gleanjs.test.${this.title}`;
beforeEach(async function() {
await Glean.testResetGlean(testAppId);
});
it("attempting to get the value of a metric that hasn't been recorded doesn't error", async function() {
const metric = new TextMetricType({
category: "aCategory",
name: "aTextMetric",
sendInPings: ["aPing", "twoPing", "threePing"],
lifetime: Lifetime.Ping,
disabled: false
});
assert.strictEqual(await metric.testGetValue("aPing"), undefined);
});
it("attempting to set when glean upload is disabled is a no-op", async function() {
Glean.setUploadEnabled(false);
const metric = new TextMetricType({
category: "aCategory",
name: "aTextMetric",
sendInPings: ["aPing", "twoPing", "threePing"],
lifetime: Lifetime.Ping,
disabled: false
});
metric.set("some value");
assert.strictEqual(await metric.testGetValue("aPing"), undefined);
});
it("ping payload is correct", async function() {
const metric = new TextMetricType({
category: "aCategory",
name: "aTextMetric",
sendInPings: ["aPing"],
lifetime: Lifetime.Ping,
disabled: false
});
const validValues = [
"some value",
"<html><head><title>Website</title></head><body><h1>Text</h1></body>",
"some longer text\nwith newlines\nand also some quotes: \"once upon a time ...\"",
];
for (const value of validValues) {
metric.set(value);
assert.strictEqual(await metric.testGetValue("aPing"), value);
const snapshot = await Context.metricsDatabase.getPingMetrics("aPing", true);
assert.deepStrictEqual(snapshot, {
"text": {
"aCategory.aTextMetric": value
}
});
}
});
it("set properly sets the value in all pings", async function() {
const metric = new TextMetricType({
category: "aCategory",
name: "aTextMetric",
sendInPings: ["aPing", "twoPing", "threePing"],
lifetime: Lifetime.Ping,
disabled: false
});
metric.set("some value");
assert.strictEqual(await metric.testGetValue("aPing"), "some value");
assert.strictEqual(await metric.testGetValue("twoPing"), "some value");
assert.strictEqual(await metric.testGetValue("threePing"), "some value");
});
it("truncates when text exceeds maximum length and records errors", async function () {
const metric = new TextMetricType({
category: "aCategory",
name: "aTextMetric",
sendInPings: ["aPing"],
lifetime: Lifetime.Ping,
disabled: false
});
const testText = `some value ${"a".repeat(TEXT_MAX_LENGTH)}`;
metric.set(testText);
const truncated = testText.substr(0, TEXT_MAX_LENGTH);
assert.strictEqual(await metric.testGetValue("aPing"), truncated);
assert.strictEqual(
await metric.testGetNumRecordedErrors(ErrorType.InvalidOverflow), 1
);
});
});