Skip to content

Commit c78edd0

Browse files
committed
bug 1955428 - Permit use of reserved metric category 'glean' only for 'glean.{attribution|distribution}.ext' object metrics
1 parent 9c033cb commit c78edd0

File tree

6 files changed

+245
-30
lines changed

6 files changed

+245
-30
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Permit object metrics named 'glean.attribution.ext' or 'glean.distribution.ext' even if allow_reserved is false ([bug 1955428](https://bugzilla.mozilla.org/show_bug.cgi?id=1955428))
6+
57
## 17.0.1
68

79
- BUGFIX: Fix missing `ping_arg` "`uploader_capabilities`" in util.py ([#786](https://github.com/mozilla/glean_parser/pull/786))

glean_parser/parser.py

Lines changed: 49 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -188,19 +188,32 @@ def _instantiate_metrics(
188188
if category_key == "no_lint":
189189
continue
190190
if not config.get("allow_reserved") and category_key.split(".")[0] == "glean":
191-
yield util.format_error(
192-
filepath,
193-
f"For category '{category_key}'",
194-
"Categories beginning with 'glean' are reserved for "
195-
"Glean internal use.",
196-
)
197-
continue
191+
if category_key not in ("glean.attribution", "glean.distribution"):
192+
yield util.format_error(
193+
filepath,
194+
f"For category '{category_key}'",
195+
"Categories beginning with 'glean' are reserved for "
196+
"Glean internal use.",
197+
)
198+
continue
198199
all_objects.setdefault(category_key, DictWrapper())
199200

200201
if not isinstance(category_val, dict):
201202
raise TypeError(f"Invalid content for {category_key}")
202203

203204
for metric_key, metric_val in sorted(category_val.items()):
205+
if (
206+
not config.get("allow_reserved")
207+
and category_key in ("glean.attribution", "glean.distribution")
208+
and metric_key != "ext"
209+
):
210+
yield util.format_error(
211+
filepath,
212+
f"For {category_key}.{metric_key}",
213+
f"May only use semi-reserved category {category_key} with metric name 'ext'",
214+
metric_val.defined_in["line"],
215+
)
216+
continue
204217
try:
205218
metric_obj = Metric.make_metric(
206219
category_key, metric_key, metric_val, validated=True, config=config
@@ -214,18 +227,28 @@ def _instantiate_metrics(
214227
)
215228
metric_obj = None
216229
else:
217-
if (
218-
not config.get("allow_reserved")
219-
and "all-pings" in metric_obj.send_in_pings
220-
):
221-
yield util.format_error(
222-
filepath,
223-
f"On instance {category_key}.{metric_key}",
224-
'Only internal metrics may specify "all-pings" '
225-
'in "send_in_pings"',
226-
metric_val.defined_in["line"],
227-
)
228-
metric_obj = None
230+
if not config.get("allow_reserved"):
231+
if "all-pings" in metric_obj.send_in_pings:
232+
yield util.format_error(
233+
filepath,
234+
f"On instance {category_key}.{metric_key}",
235+
'Only internal metrics may specify "all-pings" '
236+
'in "send_in_pings"',
237+
metric_val.defined_in["line"],
238+
)
239+
metric_obj = None
240+
elif (
241+
metric_obj.identifier()
242+
in ("glean.attribution.ext", "glean.distribution.ext")
243+
and metric_obj.type != "object"
244+
):
245+
yield util.format_error(
246+
filepath,
247+
f"On instance {category_key}.{metric_key}",
248+
"Extended attribution/distribution metrics must be of type 'object'",
249+
metric_val.defined_in["line"],
250+
)
251+
metric_obj = None
229252

230253
if metric_obj is not None:
231254
metric_obj.no_lint = sorted(set(metric_obj.no_lint + global_no_lint))
@@ -481,16 +504,16 @@ def parse_objects(
481504
raise TypeError(f"Invalid content for {filepath}")
482505

483506
for category_key, category_val in sorted(content.items()):
484-
if category_key.startswith("$"):
485-
continue
507+
if category_key.startswith("$"):
508+
continue
486509

487-
interesting_metrics_dict.setdefault(category_key, DictWrapper())
510+
interesting_metrics_dict.setdefault(category_key, DictWrapper())
488511

489-
if not isinstance(category_val, dict):
490-
raise TypeError(f"Invalid category_val for {category_key}")
512+
if not isinstance(category_val, dict):
513+
raise TypeError(f"Invalid category_val for {category_key}")
491514

492-
for metric_key, metric_val in sorted(category_val.items()):
493-
interesting_metrics_dict[category_key][metric_key] = metric_val
515+
for metric_key, metric_val in sorted(category_val.items()):
516+
interesting_metrics_dict[category_key][metric_key] = metric_val
494517

495518
for category_key, category_val in all_objects.items():
496519
if category_key == "tags":

tests/data/attribution.yaml

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Any copyright is dedicated to the Public Domain.
2+
# https://creativecommons.org/publicdomain/zero/1.0/
3+
4+
---
5+
$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
6+
7+
glean.attribution:
8+
ext:
9+
type: object
10+
lifetime: user
11+
# Permit long description lines
12+
# yamllint disable
13+
description: |
14+
Extended attribution information.
15+
Mapped to client_info.attribution.ext in datasets.
16+
* `experiment`: name/id of the enrolled funnel experiment
17+
* `ua`: identifier derived from the user agent downloading the installer
18+
e.g. chrome, Google Chrome 123
19+
* `dltoken`: Unique token created at Firefox download time.
20+
e.g. c18f86a3-f228-4d98-91bb-f90135c0aa9c
21+
* `msstoresignedin`: only present if the installation was done through the Microsoft Store,
22+
and was able to retrieve the "campaign ID" it was first installed with.
23+
This value is "true" if the user was signed into the Microsoft Store
24+
when they first installed, and false otherwise.
25+
* `dlsource`: identifier that indicate where installations of Firefox originate
26+
# yamllint enable
27+
bugs:
28+
- https://bugzilla.mozilla.org/1955428
29+
data_reviews:
30+
- http://example.com/reviews
31+
notification_emails:
32+
- CHANGE-ME@example.com
33+
send_in_pings:
34+
- metrics
35+
- baseline
36+
- events
37+
expires: never
38+
no_lint:
39+
- BASELINE_PING
40+
structure:
41+
type: object
42+
properties:
43+
experiment:
44+
type: string
45+
ua:
46+
type: string
47+
dltoken:
48+
type: string
49+
msstoresignedin:
50+
type: boolean
51+
dlsource:
52+
type: string
53+
54+
glean.distribution:
55+
ext:
56+
type: object
57+
lifetime: user
58+
description: |
59+
Extended distribution information.
60+
Mapped to client_info.distribution.ext in datasets.
61+
* `distributionVersion`: pref `distribution.version`, `null` on failure
62+
* `partnerId`: pref `mozilla.partner.id`, `null` on failure
63+
* `distributor`: pref `app.distributor`, `null` on failure
64+
* `distributorChannel`: pref `app.distributor.channel`, `null` on failure
65+
* `partnerNames`: list from prefs `app.partner.<name>=<name>`
66+
bugs:
67+
- https://bugzilla.mozilla.org/1955428
68+
data_reviews:
69+
- http://example.com/reviews
70+
notification_emails:
71+
- CHANGE-ME@example.com
72+
send_in_pings:
73+
- metrics
74+
- baseline
75+
- events
76+
expires: never
77+
no_lint:
78+
- BASELINE_PING
79+
structure:
80+
type: object
81+
properties:
82+
distributionVersion:
83+
type: string
84+
partnerId:
85+
type: string
86+
distributor:
87+
type: string
88+
distributorChannel:
89+
type: string
90+
partnerNames:
91+
type: array
92+
items:
93+
type: string

tests/data/bad_attribution.yamlx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Any copyright is dedicated to the Public Domain.
2+
# https://creativecommons.org/publicdomain/zero/1.0/
3+
4+
---
5+
$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
6+
7+
glean.attribution:
8+
not_ext:
9+
type: object
10+
description: |
11+
Can only use glean.attribution if our name is ext.
12+
bugs:
13+
- https://bugzilla.mozilla.org/1955428
14+
data_reviews:
15+
- http://example.com/reviews
16+
notification_emails:
17+
- CHANGE-ME@example.com
18+
expires: never
19+
structure:
20+
type: object
21+
properties:
22+
experiment:
23+
type: string
24+
25+
glean.distribution:
26+
ext:
27+
type: counter
28+
description: |
29+
Can only use glean.distribution.ext if our type is object.
30+
bugs:
31+
- https://bugzilla.mozilla.org/1955428
32+
data_reviews:
33+
- http://example.com/reviews
34+
notification_emails:
35+
- CHANGE-ME@example.com
36+
expires: never
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Any copyright is dedicated to the Public Domain.
2+
# https://creativecommons.org/publicdomain/zero/1.0/
3+
4+
---
5+
$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
6+
7+
glean:
8+
something:
9+
type: counter
10+
lifetime: ping
11+
description: |
12+
Just a test metric to fail.
13+
bugs:
14+
- https://bugzilla.mozilla.org/1955428
15+
data_reviews:
16+
- http://example.com/reviews
17+
notification_emails:
18+
- CHANGE-ME@example.com
19+
expires: never
20+
21+
glean.subcategory:
22+
someotherthing:
23+
type: counter
24+
lifetime: ping
25+
description: |
26+
Just a test metric to fail.
27+
bugs:
28+
- https://bugzilla.mozilla.org/1955428
29+
data_reviews:
30+
- http://example.com/reviews
31+
notification_emails:
32+
- CHANGE-ME@example.com
33+
expires: never

tests/test_parser.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -714,7 +714,7 @@ def test_interesting_disables_others():
714714
# But, only the metrics in metric-with-tags.yaml are marked as interesting
715715
# so every other metric will be marked as disabled.
716716
metrics_with_tags_yaml = ROOT / "data" / "metric-with-tags.yaml"
717-
interesting = [Path(path) for path in [ metrics_with_tags_yaml ]]
717+
interesting = [Path(path) for path in [metrics_with_tags_yaml]]
718718
config = {"interesting": interesting}
719719

720720
all_metrics = parser.parse_objects([all_metrics, metrics_with_tags_yaml], config)
@@ -728,14 +728,14 @@ def test_interesting_disables_others():
728728
metrics = all_metrics.value
729729
for category_key, category_val in metrics.items():
730730
if category_key == "tags":
731-
continue
731+
continue
732732

733733
for metric_key, metric_val in sorted(category_val.items()):
734734
cat = interesting_metrics.value.get(category_key)
735735
if cat and cat.get(metric_key):
736-
disabled = cat.get(metric_key).disabled
736+
disabled = cat.get(metric_key).disabled
737737
else:
738-
disabled = True
738+
disabled = True
739739
assert metrics[category_key][metric_key].disabled is disabled
740740

741741

@@ -1259,3 +1259,31 @@ def test_object_invalid():
12591259
errors = list(all_metrics)
12601260
assert len(errors) == 1
12611261
assert "invalid `type` in object structure." in errors[0]
1262+
1263+
1264+
def test_reserved_category():
1265+
all_metrics = parser.parse_objects(ROOT / "data" / "reserved_categories.yamlx")
1266+
1267+
errors = list(all_metrics)
1268+
assert len(errors) == 2
1269+
assert "Categories beginning with 'glean' are reserved" in errors[0]
1270+
assert "Categories beginning with 'glean' are reserved" in errors[1]
1271+
1272+
1273+
def test_attribution_distribution():
1274+
all_metrics = parser.parse_objects(ROOT / "data" / "attribution.yaml")
1275+
1276+
errors = list(all_metrics)
1277+
assert len(errors) == 0
1278+
1279+
1280+
def test_forbid_non_ext_non_object_attribution_distribution():
1281+
all_metrics = parser.parse_objects(ROOT / "data" / "bad_attribution.yamlx")
1282+
1283+
errors = list(all_metrics)
1284+
assert len(errors) == 2
1285+
assert "May only use semi-reserved category" in errors[0]
1286+
assert (
1287+
"Extended attribution/distribution metrics must be of type 'object'"
1288+
in errors[1]
1289+
)

0 commit comments

Comments
 (0)