Skip to content

Commit a74a6c1

Browse files
author
Beatriz Rizental
authored
Bug 1692157 - Update Javascript templates and provide Typescript template (#288)
1 parent 782ec70 commit a74a6c1

File tree

5 files changed

+182
-55
lines changed

5 files changed

+182
-55
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44

55
- Add parser and object model support for `rate` metric type. ([bug 1645166](https://bugzilla.mozilla.org/show_bug.cgi?id=1645166))
66
- Add parser and object model support for telemetry_mirror property. ([bug 1685406](https://bugzilla.mozilla.org/show_bug.cgi?id=1685406))
7+
- Update the Javascript template to match Glean.js expectations. ([bug 1693516](https://bugzilla.mozilla.org/show_bug.cgi?id=1693516))
8+
- Glean.js has updated it's export strategy. It will now export each metric type as an independent module;
9+
- Glean.js has dropped support for non ES6 modules.
10+
- Add support for generating Typescript code. ([bug 1692157](https://bugzilla.mozilla.org/show_bug.cgi?id=1692157))
11+
- The templates added generate metrics and pings code for Glean.js.
712

813
## 2.4.0 (2021-02-18)
914

glean_parser/javascript.py

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,19 @@ def class_name(obj_type: str) -> str:
4141
else:
4242
class_name = util.Camelize(obj_type) + "MetricType"
4343

44-
return "Glean._private." + class_name
44+
return class_name
45+
46+
47+
def import_path(obj_type: str) -> str:
48+
"""
49+
Returns the import path of the given object inside the @mozilla/glean package.
50+
"""
51+
if obj_type == "ping":
52+
import_path = "ping"
53+
else:
54+
import_path = "metrics/" + util.camelize(obj_type)
55+
56+
return import_path
4557

4658

4759
def args(obj_type: str) -> Dict[str, object]:
@@ -54,12 +66,16 @@ def args(obj_type: str) -> Dict[str, object]:
5466
return {"common": util.common_metric_args, "extra": util.extra_metric_args}
5567

5668

57-
def output_javascript(
58-
objs: metrics.ObjectTree, output_dir: Path, options: Optional[Dict[str, Any]] = None
69+
def output(
70+
lang: str,
71+
objs: metrics.ObjectTree,
72+
output_dir: Path,
73+
options: Optional[Dict[str, Any]] = None,
5974
) -> None:
6075
"""
61-
Given a tree of objects, output Javascript code to `output_dir`.
76+
Given a tree of objects, output Javascript or Typescript code to `output_dir`.
6277
78+
:param lang: Either "javascript" or "typescript";
6379
:param objects: A tree of objects (metrics and pings) as returned from
6480
`parser.parse_objects`.
6581
:param output_dir: Path to an output directory to write to.
@@ -82,15 +98,18 @@ def output_javascript(
8298
"javascript.jinja2",
8399
filters=(
84100
("class_name", class_name),
101+
("import_path", import_path),
85102
("js", javascript_datatypes_filter),
86103
("args", args),
87104
),
88105
)
89106

90107
for category_key, category_val in objs.items():
91-
filename = util.camelize(category_key) + ".js"
108+
extension = ".js" if lang == "javascript" else ".ts"
109+
filename = util.camelize(category_key) + extension
92110
filepath = output_dir / filename
93111

112+
types = set([util.camelize(obj.type) for obj in category_val.values()])
94113
with filepath.open("w", encoding="utf-8") as fd:
95114
fd.write(
96115
template.render(
@@ -99,7 +118,56 @@ def output_javascript(
99118
extra_args=util.extra_args,
100119
namespace=namespace,
101120
glean_namespace=glean_namespace,
121+
types=types,
122+
lang=lang,
102123
)
103124
)
104125
# Jinja2 squashes the final newline, so we explicitly add it
105126
fd.write("\n")
127+
128+
129+
def output_javascript(
130+
objs: metrics.ObjectTree, output_dir: Path, options: Optional[Dict[str, Any]] = None
131+
) -> None:
132+
"""
133+
Given a tree of objects, output Javascript code to `output_dir`.
134+
135+
:param objects: A tree of objects (metrics and pings) as returned from
136+
`parser.parse_objects`.
137+
:param output_dir: Path to an output directory to write to.
138+
:param options: options dictionary, with the following optional keys:
139+
140+
- `namespace`: The identifier of the global variable to assign to.
141+
This will only have and effect for Qt and static web sites.
142+
Default is `gleanAssets`.
143+
- `glean_namespace`: Which version of the `@mozilla/glean` package to import,
144+
options are `webext` or `qt`. Default is `webext`.
145+
"""
146+
147+
output("javascript", objs, output_dir, options)
148+
149+
150+
def output_typescript(
151+
objs: metrics.ObjectTree, output_dir: Path, options: Optional[Dict[str, Any]] = None
152+
) -> None:
153+
"""
154+
Given a tree of objects, output Typescript code to `output_dir`.
155+
156+
# Note
157+
158+
The only difference between the typescript and javascript templates,
159+
currently is the file extension.
160+
161+
:param objects: A tree of objects (metrics and pings) as returned from
162+
`parser.parse_objects`.
163+
:param output_dir: Path to an output directory to write to.
164+
:param options: options dictionary, with the following optional keys:
165+
166+
- `namespace`: The identifier of the global variable to assign to.
167+
This will only have and effect for Qt and static web sites.
168+
Default is `gleanAssets`.
169+
- `glean_namespace`: Which version of the `@mozilla/glean` package to import,
170+
options are `webext` or `qt`. Default is `webext`.
171+
"""
172+
173+
output("typescript", objs, output_dir, options)
Lines changed: 21 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,33 @@
1-
{# The final Javascript code is autogenerated, but this
1+
{# The final Javascript/Typescript code is autogenerated, but this
22
Jinja2 template is not. Please file bugs! #}
33
{% macro obj_declaration(obj) %}
44
new {{ obj.type|class_name }}({
5-
{% for arg_name in (obj.type|args).common if obj[arg_name] is defined %}
6-
{{ arg_name|camelize }}: {{ obj[arg_name]|js }},
7-
{% endfor %}
8-
}, {% for arg_name in (obj.type|args).extra if obj[arg_name] is defined %}{{ obj[arg_name]|js }}{% endfor %}){% endmacro %}
5+
{% for arg_name in (obj.type|args).common if obj[arg_name] is defined %}
6+
{{ arg_name|camelize }}: {{ obj[arg_name]|js }},
7+
{% endfor %}
8+
}{% for arg_name in (obj.type|args).extra if obj[arg_name] is defined %}, {{ obj[arg_name]|js }}{% endfor %}){% endmacro %}
99
/* eslint-disable */
1010

1111
/* This Source Code Form is subject to the terms of the Mozilla Public
1212
* License, v. 2.0. If a copy of the MPL was not distributed with this
1313
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
1414

15-
"use strict";
16-
1715
// AUTOGENERATED BY glean_parser. DO NOT EDIT.
1816

19-
// Global variable to use when in Qt/QML environments,
20-
// where do not have access to the global object (i.e. window);
21-
var {{ category_name|camelize }};
17+
{% for type in types %}
18+
import {{ type|class_name }} from "@mozilla/glean/{{ glean_namespace }}/private/{{ type|import_path }}";
19+
{% endfor %}
20+
{% if lang == "javascript" %}
21+
22+
"use strict"
23+
{% endif %}
24+
25+
{% for obj in objs.values() %}
26+
/**
27+
* {{ obj.description|wordwrap() | replace("\n", "\n * ") }}
28+
*
29+
* Generated from `{{ obj.identifier() }}`.
30+
*/
31+
export const {{ obj.name|camelize }} = {{ obj_declaration(obj) }};
2232

23-
// Universal Module Definition (UMD) template based on:
24-
// https://github.com/umdjs/umd/blob/master/templates/returnExports.js
25-
(function (root, factory) {
26-
if (typeof define === "function" && define.amd) {
27-
// AMD. Register as an anonymous module.
28-
define(["@mozilla/glean/{{ glean_namespace }}"], factory);
29-
} else if (typeof module === "object" && module.exports) {
30-
// Node. Does not work with strict CommonJS, but
31-
// only CommonJS-like environments that support module.exports, like Node.
32-
module.exports = factory(require("@mozilla/glean/{{ glean_namespace }}"));
33-
} else if (typeof root === "undefined") {
34-
// In Qt/QML environments we can't change the global object from Javascript.
35-
// We will simply assing to a global variable in this case.
36-
{{ category_name|camelize }} = factory({{ glean_namespace }}.Glean);
37-
} else {
38-
// Browser globals (root is window)
39-
if (!root["{{ namespace }}"]) {
40-
root["{{ namespace }}"] = {};
41-
}
42-
root["{{ namespace }}"]["{{ category_name|camelize }}"] = factory(root.{{ glean_namespace }});
43-
}
44-
})(typeof self !== "undefined" ? self : this, function(Glean) {
45-
{% if objs %}
46-
return {
47-
{% for obj in objs.values() %}
48-
/**
49-
* {{ obj.description|wordwrap() | replace("\n", "\n * ") }}
50-
*
51-
* Generated from `{{ obj.identifier() }}`.
52-
*/
53-
{{ obj.name|camelize }}: {{ obj_declaration(obj) }},
54-
{% endfor %}
55-
};
56-
{% endif %}
57-
});
33+
{% endfor %}

glean_parser/translate.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def __init__(
5454
OUTPUTTERS = {
5555
"csharp": Outputter(csharp.output_csharp, ["*.cs"]),
5656
"javascript": Outputter(javascript.output_javascript, []),
57+
"typescript": Outputter(javascript.output_typescript, []),
5758
"kotlin": Outputter(kotlin.output_kotlin, ["*.kt"]),
5859
"markdown": Outputter(markdown.output_markdown, []),
5960
"swift": Outputter(swift.output_swift, ["*.swift"]),

tests/test_javascript.py

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
ROOT = Path(__file__).parent
1515

1616

17-
def test_parser(tmpdir):
17+
def test_parser_js(tmpdir):
1818
"""Test translating metrics to Javascript files."""
1919
tmpdir = Path(str(tmpdir))
2020

@@ -39,14 +39,56 @@ def test_parser(tmpdir):
3939
# Make sure descriptions made it in
4040
with (tmpdir / "corePing.js").open("r", encoding="utf-8") as fd:
4141
content = fd.read()
42+
assert "use strict" in content
4243
assert "True if the user has set Firefox as the default browser." in content
4344

4445
with (tmpdir / "telemetry.js").open("r", encoding="utf-8") as fd:
4546
content = fd.read()
47+
assert "use strict" in content
4648
assert "جمع 搜集" in content
4749

4850
with (tmpdir / "gleanInternalMetrics.js").open("r", encoding="utf-8") as fd:
4951
content = fd.read()
52+
assert "use strict" in content
53+
assert 'category: ""' in content
54+
55+
56+
def test_parser_ts(tmpdir):
57+
"""Test translating metrics to Typescript files."""
58+
tmpdir = Path(str(tmpdir))
59+
60+
translate.translate(
61+
ROOT / "data" / "core.yaml",
62+
"typescript",
63+
tmpdir,
64+
None,
65+
{"allow_reserved": True},
66+
)
67+
68+
assert set(x.name for x in tmpdir.iterdir()) == set(
69+
[
70+
"corePing.ts",
71+
"telemetry.ts",
72+
"environment.ts",
73+
"dottedCategory.ts",
74+
"gleanInternalMetrics.ts",
75+
]
76+
)
77+
78+
# Make sure descriptions made it in
79+
with (tmpdir / "corePing.ts").open("r", encoding="utf-8") as fd:
80+
content = fd.read()
81+
assert "use strict" not in content
82+
assert "True if the user has set Firefox as the default browser." in content
83+
84+
with (tmpdir / "telemetry.ts").open("r", encoding="utf-8") as fd:
85+
content = fd.read()
86+
assert "use strict" not in content
87+
assert "جمع 搜集" in content
88+
89+
with (tmpdir / "gleanInternalMetrics.ts").open("r", encoding="utf-8") as fd:
90+
content = fd.read()
91+
assert "use strict" not in content
5092
assert 'category: ""' in content
5193

5294

@@ -87,7 +129,42 @@ def test_metric_class_name():
87129
extra_keys={"my_extra": {"description": "an extra"}},
88130
)
89131

90-
assert javascript.class_name(event.type) == "Glean._private.EventMetricType"
132+
assert javascript.class_name(event.type) == "EventMetricType"
133+
134+
boolean = metrics.Boolean(
135+
type="boolean",
136+
category="category",
137+
name="metric",
138+
bugs=["http://bugzilla.mozilla.com/12345"],
139+
notification_emails=["nobody@example.com"],
140+
description="description...",
141+
expires="never",
142+
)
143+
assert javascript.class_name(boolean.type) == "BooleanMetricType"
144+
145+
ping = pings.Ping(
146+
name="custom",
147+
description="description...",
148+
include_client_id=True,
149+
bugs=["http://bugzilla.mozilla.com/12345"],
150+
notification_emails=["nobody@nowhere.com"],
151+
)
152+
assert javascript.class_name(ping.type) == "PingType"
153+
154+
155+
def test_import_path():
156+
event = metrics.Event(
157+
type="event",
158+
category="category",
159+
name="metric",
160+
bugs=["http://bugzilla.mozilla.com/12345"],
161+
notification_emails=["nobody@example.com"],
162+
description="description...",
163+
expires="never",
164+
extra_keys={"my_extra": {"description": "an extra"}},
165+
)
166+
167+
assert javascript.import_path(event.type) == "metrics/event"
91168

92169
boolean = metrics.Boolean(
93170
type="boolean",
@@ -98,7 +175,7 @@ def test_metric_class_name():
98175
description="description...",
99176
expires="never",
100177
)
101-
assert javascript.class_name(boolean.type) == "Glean._private.BooleanMetricType"
178+
assert javascript.import_path(boolean.type) == "metrics/boolean"
102179

103180
ping = pings.Ping(
104181
name="custom",
@@ -107,7 +184,7 @@ def test_metric_class_name():
107184
bugs=["http://bugzilla.mozilla.com/12345"],
108185
notification_emails=["nobody@nowhere.com"],
109186
)
110-
assert javascript.class_name(ping.type) == "Glean._private.PingType"
187+
assert javascript.import_path(ping.type) == "ping"
111188

112189

113190
# TODO: Activate once Glean.js adds support for labeled metric types in Bug 1682573.
@@ -189,5 +266,5 @@ def test_arguments_are_generated_in_deterministic_order(tmpdir):
189266
with (tmpdir / "event.js").open("r", encoding="utf-8") as fd:
190267
content = fd.read()
191268
content = " ".join(content.split())
192-
expected = 'new Glean._private.EventMetricType({ category: "event", name: "example", sendInPings: ["events"], lifetime: "ping", disabled: false, }, ["alice", "bob", "charlie"]),' # noqa
269+
expected = 'export const example = new EventMetricType({ category: "event", name: "example", sendInPings: ["events"], lifetime: "ping", disabled: false, }, ["alice", "bob", "charlie"]);' # noqa
193270
assert expected in content

0 commit comments

Comments
 (0)