Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* [#647](https://github.com/mozilla/glean.js/pull/647): Implement the Text metric type.
* [#681](https://github.com/mozilla/glean.js/pull/681): BUGFIX: Fix error in scanning events database upon initialization on Qt/QML.
* This bug prevents the changes introduced in [#526](https://github.com/mozilla/glean.js/pull/526) from working properly.
* [#614](https://github.com/mozilla/glean.js/pull/614): Implement the String List metric type.

# v0.18.1 (2021-07-22)

Expand Down
166 changes: 166 additions & 0 deletions glean/src/core/metrics/types/string_list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/* 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 { MetricType } from "../index.js";
import { Context } from "../../context.js";
import { Metric } from "../metric.js";
import { isString, truncateStringAtBoundaryWithError } from "../../utils.js";
import type { JSONValue } from "../../utils.js";
import { ErrorType } from "../../error/error_type.js";

export const MAX_LIST_LENGTH = 20;
export const MAX_STRING_LENGTH = 50;

export class StringListMetric extends Metric<string[], string[]> {
constructor(v: unknown) {
super(v);
}

validate(v: unknown): v is string[] {
if (!Array.isArray(v)) {
return false;
}

if (v.length > MAX_LIST_LENGTH) {
return false;
}

for (const s of v) {
if (!isString(s) || s.length > MAX_STRING_LENGTH) {
return false;
}
}

return true;
}

payload(): string[] {
return this._inner;
}
}

/**
* A string list metric.
*
* This allows appending a string value with arbitrary content to a list.
* The list is length-limited to `MAX_LIST_LENGTH`.
* Strings are length-limited to `MAX_STRING_LENGTH` characters.
*/
class StringListMetricType extends MetricType {
constructor(meta: CommonMetricData) {
super("string_list", meta);
}

/**
* Sets to the specified string list value.
*
* # Note
*
* Truncates the list if it is longer than `MAX_LIST_LENGTH` and records an error.
*
* Truncates the value if it is longer than `MAX_STRING_LENGTH` characters
* and records an error.
*
* @param value The list of strings to set the metric to.
*/
set(value: string[]): void {
Context.dispatcher.launch(async () => {
if (!this.shouldRecord(Context.uploadEnabled)) {
return;
}

const truncatedList: string[] = [];
if (value.length > MAX_LIST_LENGTH) {
await Context.errorManager.record(
this,
ErrorType.InvalidValue,
`String list length of ${value.length} exceeds maximum of ${MAX_LIST_LENGTH}.`
);
}

for (let i = 0; i < Math.min(value.length, MAX_LIST_LENGTH); ++i) {
const truncatedString = await truncateStringAtBoundaryWithError(this, value[i], MAX_STRING_LENGTH);
truncatedList.push(truncatedString);
}
const metric = new StringListMetric(truncatedList);
await Context.metricsDatabase.record(this, metric);
});
}

/**
* Adds a new string `value` to the list.
*
* # Note
*
* - If the list is already of length `MAX_LIST_LENGTH`, record an error.
* - Truncates the value if it is longer than `MAX_STRING_LENGTH` characters
* and records an error.
*
* @param value The string to add.
*/
add(value: string): void {
Context.dispatcher.launch(async () => {
if (!this.shouldRecord(Context.uploadEnabled)) {
return;
}

const truncatedValue = await truncateStringAtBoundaryWithError(this, value, MAX_STRING_LENGTH);
let currentLen = 0;

const transformFn = ((value) => {
return (v?: JSONValue): StringListMetric => {
let metric: StringListMetric;
let result: string[];
try {
metric = new StringListMetric(v);
result = metric.get();
currentLen = result.length;
if (result.length < MAX_LIST_LENGTH) {
result.push(value);
}
} catch {
metric = new StringListMetric([value]);
result = [value];
}
metric.set(result);
return metric;
};
})(truncatedValue);

await Context.metricsDatabase.transform(this, transformFn);

if (currentLen >= MAX_LIST_LENGTH) {
await Context.errorManager.record(
this,
ErrorType.InvalidValue,
`String list length of ${currentLen+1} exceeds maximum of ${MAX_LIST_LENGTH}.`
);
}
});
}

/**
* Test-only API**
*
* Gets the currently stored value as a string array.
*
* 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 StringListMetricType;
2 changes: 2 additions & 0 deletions glean/src/core/metrics/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { StringListMetric } from "./types/string_list.js";
import { TextMetric } from "./types/text.js";
import { TimespanMetric } from "./types/timespan.js";
import { UrlMetric } from "./types/url.js";
Expand All @@ -31,6 +32,7 @@ const METRIC_MAP: {
"labeled_string": LabeledMetric,
"quantity": QuantityMetric,
"string": StringMetric,
"string_list": StringListMetric,
"text": TextMetric,
"timespan": TimespanMetric,
"url": UrlMetric,
Expand Down
2 changes: 2 additions & 0 deletions glean/src/index/qt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 StringListMetricType from "../core/metrics/types/string_list.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";
Expand Down Expand Up @@ -124,6 +125,7 @@ export default {
LabeledMetricType,
QuantityMetricType,
StringMetricType,
StringListMetricType,
TimespanMetricType,
TextMetricType,
UUIDMetricType,
Expand Down
13 changes: 13 additions & 0 deletions glean/tests/integration/schema/metrics.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,19 @@ for_testing:
expires: never
send_in_pings:
- testing
string_list:
type: string_list
description: |
Sample string list 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
timespan:
type: timespan
description: |
Expand Down
1 change: 1 addition & 0 deletions glean/tests/integration/schema/schema.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ describe("schema", function() {
metrics.labeledString["a_label"].set("ho");
metrics.quantity.set(42);
metrics.string.set("let's go");
metrics.stringList.set(["let's go"]);
metrics.text.set("let's gooooooooo");
metrics.timespan.setRawNanos(10 * 10**6);
metrics.uuid.generateAndSet();
Expand Down
Loading