Skip to content

Commit a95506f

Browse files
committed
Merge branch 'release-v0.5.0' into release
# Conflicts: # CHANGELOG.md # glean/package-lock.json # glean/package.json # glean/src/core/constants.ts
2 parents 4ed10c7 + 54afe86 commit a95506f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1707
-214
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,6 @@ web-ext-artifacts/
121121

122122
# This is the name of the folder we will add the glean generated files in for our samples.
123123
generated/
124+
125+
# This is the name of the Glean virtual environment
126+
.venv

CHANGELOG.md

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
# Unreleased changes
22

3-
[Full changelog](https://github.com/mozilla/glean.js/compare/v0.4.0...main)
3+
[Full changelog](https://github.com/mozilla/glean.js/compare/v0.5.0...main)
4+
5+
# v0.5.0 (2021-03-18)
6+
7+
[Full changelog](https://github.com/mozilla/glean.js/compare/v0.4.0...v0.5.0)
8+
9+
* [#96](https://github.com/mozilla/glean.js/pull/96): Provide a ping encryption plugin.
10+
* This plugin listens to the `afterPingCollection` event. It receives the collected payload of a ping and returns an encrypted version of it using a JWK provided upon instantiation.
11+
* [#95](https://github.com/mozilla/glean.js/pull/95): Add a `plugins` property to the configuration options and create an event abstraction for triggering internal Glean events.
12+
* The only internal event triggered at this point is the `afterPingCollection` event, which is triggered after ping collection and logging, and before ping storing.
13+
* Plugins are built to listen to a specific Glean event. Each plugin must define an `action`, which is executed everytime the event they are listening to is triggered.
14+
* [#101](https://github.com/mozilla/glean.js/pull/101): BUGFIX: Only validate Debug View Tag and Source Tags when they are present.
15+
* [#101](https://github.com/mozilla/glean.js/pull/101): BUGFIX: Only validate Debug View Tag and Source Tags when they are present.
16+
* [#102](https://github.com/mozilla/glean.js/pull/102): BUGFIX: Include a Glean User-Agent header in all pings.
17+
* [#97](https://github.com/mozilla/glean.js/pull/97): Add support for labeled metric types (string, boolean and counter).
18+
* [#105](https://github.com/mozilla/glean.js/pull/105): Introduce and publish the `glean` command for using the `glean-parser` in a virtual environment.
419

520
# v0.4.0 (2021-03-10)
621

docs/adding_a_new_metric_type.md

+226
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
# Adding a new metric type
2+
3+
This document covers how to add new metric types to Glean.js. New metric types should only be implemented
4+
after they were proposed and approved through Glean's metric type request process. If Glean is missing a
5+
metric type you need, start by filing a request as described in the [process documentation](https://wiki.mozilla.org/Glean/Adding_or_changing_Glean_metric_types).
6+
7+
## Glossary
8+
9+
For the purposes of this document, when we say:
10+
11+
- **"Metric Type"** we mean an object that represents one of the metrics a user defined in their
12+
`metric.yaml` file. This object holds all the metadata related to a user defined metric as
13+
well as exposes the testing and recording APIs of the metric type. Example:
14+
15+
```ts
16+
export const enlightening = new StringMetricType({
17+
category: "example",
18+
name: "enlightening",
19+
sendInPings: ["custom"],
20+
lifetime: "ping",
21+
disabled: false,
22+
});
23+
```
24+
25+
- **"Metric"** a wrapper object around a concrete value to record for a metric type. Example:
26+
27+
```ts
28+
// Under the hood, Glean.js will use the value passed to `set` to instantiate a new StringMetric.
29+
enlightening.set("a-ha");
30+
```
31+
32+
## Implementation guide
33+
34+
Glean.js' metric type code lives on the `glean/src/core/metrics/types` folder. Once your metric type
35+
request is approved, create a new file on that folder to accomodate your new metric type.
36+
37+
### The `Metric` class
38+
39+
Inside the file you just created, define a class that extends the
40+
[`Metric`](https://github.com/mozilla/glean.js/blob/main/glean/src/core/metrics/index.ts#L20)
41+
abstract class.
42+
43+
This class will be used for instantiating metrics of your new type. Whenever a user
44+
uses a metric type's recording APIs, a metric instance is created using the given value and
45+
that is what Glean.js will record.
46+
47+
It expects two type arguments:
48+
49+
- `InternalRepresentation`: the representation in which this metric will be stored in memory.
50+
- `PayloadRepresentation`: the representation in which this metric will be added to the ping payload.
51+
52+
For most metrics, both representations will be same, but some metrics may need extra information
53+
on their internal representation in order to assist when deserializing for testing purposes
54+
(e.g. the [`DatetimeMetric`](https://github.com/mozilla/glean.js/blob/main/glean/src/core/metrics/types/datetime.ts#L27-L156)).
55+
56+
The `PayloadRepresentation` must match _exactly_ the representation of this metric on
57+
[Glean's ping payload schema](https://github.com/mozilla-services/mozilla-pipeline-schemas/blob/master/schemas/glean/glean/glean.1.schema.json).
58+
However you define this representation will be the representation used by Glean.js'
59+
when building ping payloads.
60+
61+
This subclass requires the you implement these two methods:
62+
63+
- [`validate`](https://github.com/mozilla/glean.js/blob/main/glean/src/core/metrics/index.ts#L67):
64+
Which validates that an `unknown` value is in the correct internal representation for the current metric.
65+
This method will be used to validate values retrieved from the metrics database.
66+
67+
- [`payload`](https://github.com/mozilla/glean.js/blob/main/glean/src/core/metrics/index.ts#L74):
68+
Which returns the metric in it's `PayloadRepresentation`. This method will be used for building
69+
ping payloads.
70+
71+
Let's look at an example:
72+
73+
```ts
74+
// The string metric will be stored as a string and included in the ping payload as a string,
75+
// so its internal and payload representation are of the same type.
76+
class StringMetric extends Metric<string, string> {
77+
constructor(v: unknown) {
78+
// Calling `super` will call `validate` to check that `v` is in the correct type.
79+
// In case it is not, this function will throw and no metric will be created.
80+
super(v);
81+
}
82+
83+
validate(v: unknown): v is string {
84+
return typeof v === "string";
85+
}
86+
87+
payload(): number {
88+
return this._inner;
89+
}
90+
}
91+
```
92+
93+
Oce you have your `Metric` subclass, include it in Glean.js'
94+
[`METRIC_MAP`](https://github.com/mozilla/glean.js/blob/main/glean/src/core/metrics/utils.ts#L17).
95+
This map will be used as a template for creating metric instances from the metrics database.
96+
97+
### The `MetricType` class
98+
99+
Now you are ready to implement the `MetricType` class for your new metric type.
100+
This class will hold all the metadata related to a specific user defined metric and
101+
expose the recording and testing APIs of your new metric type.
102+
103+
This class extends the [`MetricType`](https://github.com/mozilla/glean.js/blob/main/glean/src/core/metrics/index.ts#L110) abstract class. Different from the `Metric` class, this class does not have
104+
any required methods to implement. Each metric type class will have a different API.
105+
This API's design should have been discussed and decided upon during the metric type request process.
106+
107+
Still, metric type classes will always have at least one recording function and one testing function.
108+
109+
> **Note** The `type` property on the `MetricType` subclass is a constant. It will be used
110+
> to determine in which section of the ping the recorded metrics for this type should be placed.
111+
> It's value is the name of the section for this metric type on the ping payload.
112+
> Make sure that, when you included your `Metric` class on the `METRIC_MAP` the property has the
113+
> same value as the `type` property on the corresponding `MetricType`.
114+
115+
#### Recording functions
116+
117+
_Functions that call Glean.js' database and store concrete values of a metric type._
118+
119+
Database calls are all asynchronous, but Glean.js' external API must **never** return promises.
120+
Therefore, Glean.js has an internal dispatcher. Asynchronous tasks are dispatched and the dispatcher
121+
will guarantee that they are executed in order without the user having to worry about
122+
awaiting or callbacks.
123+
124+
This is to say: any recording action must be wrapped in a `Glean.dispatcher.launch` block.
125+
126+
Continuing on the String metric type example,
127+
let's look at how a simple string recording function will look like.
128+
129+
```ts
130+
function set(value: string): void {
131+
Glean.dispatcher.launch(async () => {
132+
// !IMPORTANT! Always check whether or not metrics should be recorded before recording.
133+
//
134+
// Metrics must not be recorded in case: upload is disabled or the metric is expired.
135+
if (!this.shouldRecord()) {
136+
return;
137+
}
138+
139+
await Glean.metricsDatabase.record(this, value);
140+
});
141+
}
142+
```
143+
144+
#### Testing functions
145+
146+
_Functions that allow users to check what was recorded for the current metric type instance._
147+
148+
Because all recording actions are dispatched, testing actions must also be dispatched so that they
149+
are guaranteed to run _after_ recording is finished. We cannot use the usual `Glean.dispatcher.launch`
150+
function in this case though, because we cannot await on our actions completion when we use it.
151+
152+
Instead we will use the `Glean.dispatcher.testLaunch` API which let's us await on the launched function.
153+
154+
Again on the String metric type example:
155+
156+
```ts
157+
async function testGetValue(ping: string = this.sendInPings[0]): Promise<string | undefined> {
158+
let metric: string | undefined;
159+
await Glean.dispatcher.testLaunch(async () => {
160+
metric = await Glean.metricsDatabase.getMetric<string>(ping, this);
161+
});
162+
return metric;
163+
}
164+
```
165+
166+
> **Note**: All testing functions must start with the prefix `test`.
167+
168+
## Testing
169+
170+
Tests for metric type implementations live under the `glean/tests/core/metrics/types` folder. Create a new
171+
file with the same name as the one you created in `glean/src/core/metrics/types` to accomodate your
172+
metric type tests.
173+
174+
Make sure your tests cover at least your metric types basic functionality:
175+
176+
- The metric returns the correct value when it has no value;
177+
- The metric correctly reports errors;
178+
- The metric returns the correct value when it has value.
179+
180+
## Documentation
181+
182+
Glean.js' has linter rules that enforce [JSDoc](https://jsdoc.app/) strings on every public function.
183+
184+
Moreover, once a new metric type is added to Glean.js, a new documentation page must be added to the user
185+
facing documentation on [the Glean book](https://mozilla.github.io/glean/book/index.html).
186+
187+
Source code for the Glean book lives on the [`mozilla/glean`](https://github.com/mozilla/glean) repository.
188+
189+
Once you are on that repository:
190+
191+
- Add a new file for your new metric in `docs/user/user/metrics/`.
192+
Its contents should follow the form and content of the other examples in that folder.
193+
- Reference that file in `docs/user/SUMMARY.md` so it will be included in the build.
194+
- Follow the [Documentation Contribution Guide](https://mozilla.github.io/glean/dev/docs.html).
195+
196+
## Other
197+
198+
Even after your are done with all the above steps, you still need to prepare other parts of the Glean
199+
ecosystem in order for you to be done implementing your new metric type.
200+
201+
### glean_parser
202+
203+
New metric types need to be added to `glean_parser` so that they can be generated
204+
from users `.yaml` files.
205+
206+
Please refer to the[`glean_parser` documentation](https://mozilla.github.io/glean_parser/contributing.html)
207+
on how to do that.
208+
209+
### mozilla-pipeline-schemas
210+
211+
New metrics types must also be added to the Glean schema on the [`mozilla-pipeline-schemas`](https://github.com/mozilla-services/mozilla-pipeline-schemas/blob/master/schemas/glean/glean/glean.1.schema.json).
212+
This step makes the Glean pipeline aware of the new metric type, before it is completed all ping
213+
payloads containing the new metric type will be rejected as a schema error.
214+
215+
Please refer to the [mozilla-pipeline-schemas documentation](https://github.com/mozilla-services/mozilla-pipeline-schemas#contributions) on how to do that.
216+
217+
### The Glean SDK
218+
219+
When adding a new metric type to Glean.js you may also want to add it to the Glean SDK.
220+
221+
If that is the case, please refer to
222+
[the Glean SDK's developer documentation](https://mozilla.github.io/glean/dev/core/new-metric-type.html)
223+
on adding new metric types.
224+
225+
> **Note**: This step is not mandatory. If a metric type is only implemented in Glean.js you may
226+
> start to use it in production given that all the other above steps were completed.

glean/.eslintrc

+2-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@
4545
],
4646
"rules": {
4747
"mocha/no-skipped-tests": "off",
48-
"mocha/no-pending-tests": "off"
48+
"mocha/no-pending-tests": "off",
49+
"mocha/no-setup-in-describe": "off"
4950
}
5051
},
5152
{

glean/package-lock.json

+19-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

glean/package.json

+17-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@mozilla/glean",
3-
"version": "0.4.0",
3+
"version": "0.5.0",
44
"description": "An implementation of the Glean SDK, a modern cross-platform telemetry client, for Javascript environments.",
55
"exports": {
66
"./package.json": "./package.json",
@@ -18,6 +18,11 @@
1818
"browser": "./dist/webext/browser/core/pings/index.js",
1919
"import": "./dist/webext/esm/core/pings/index.js",
2020
"require": "./dist/webext/cjs/core/pings/index.js"
21+
},
22+
"./webext/plugins/*": {
23+
"browser": "./dist/webext/browser/plugins/*.js",
24+
"import": "./dist/webext/esm/plugins/*.js",
25+
"require": "./dist/webext/cjs/plugins/*.js"
2126
}
2227
},
2328
"typesVersions": {
@@ -30,6 +35,9 @@
3035
],
3136
"webext/private/metrics/*": [
3237
"./dist/webext/types/core/metrics/types/*"
38+
],
39+
"webext/plugins/*": [
40+
"./dist/webext/types/plugins/*"
3341
]
3442
}
3543
},
@@ -38,20 +46,25 @@
3846
"package.json",
3947
"dist/**/*"
4048
],
49+
"bin": {
50+
"glean": "./dist/cli/cli.js"
51+
},
4152
"scripts": {
42-
"test": "npm run test:core && npm run test:platform",
53+
"test": "npm run test:core && npm run test:platform && npm run test:plugins",
4354
"test:core": "ts-mocha \"tests/core/**/*.spec.ts\" --recursive",
55+
"test:plugins": "ts-mocha \"tests/plugins/**/*.spec.ts\" --recursive",
4456
"test:platform": "npm run build:test-webext && ts-mocha \"tests/platform/**/*.spec.ts\" --recursive --timeout 0",
4557
"build:test-webext": "cd tests/platform/utils/webext/sample/ && npm install && npm run build:xpi",
4658
"lint": "eslint . --ext .ts,.js,.json --max-warnings=0",
4759
"fix": "eslint . --ext .ts,.js,.json --fix",
60+
"build:cli": "tsc -p ./tsconfig/cli.json",
4861
"build:webext:lib:esm": "tsc -p ./tsconfig/webext/esm.json",
4962
"build:webext:lib:cjs": "tsc -p ./tsconfig/webext/cjs.json",
5063
"build:webext:lib:browser": "tsc -p ./tsconfig/webext/browser.json",
5164
"build:webext:types": "tsc -p ./tsconfig/webext/types.json",
5265
"build:webext": "rm -rf dist/webext && npm run build:webext:lib:esm && npm run build:webext:lib:cjs && npm run build:webext:lib:browser && npm run build:webext:types",
5366
"build:qt": "webpack --config webpack.config.qt.js --mode production",
54-
"prepublishOnly": "cp ../README.md ./README.md && npm run build:webext",
67+
"prepublishOnly": "cp ../README.md ./README.md && npm run build:cli && npm run build:webext",
5568
"postpublish": "rm ./README.md"
5669
},
5770
"repository": {
@@ -96,6 +109,7 @@
96109
"webpack-cli": "^4.5.0"
97110
},
98111
"dependencies": {
112+
"jose": "^3.7.0",
99113
"uuid": "^8.3.2"
100114
}
101115
}

0 commit comments

Comments
 (0)