• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mozilla / relman-auto-nag / #5558

03 Sep 2025 02:15PM UTC coverage: 20.835% (-0.2%) from 21.001%
#5558

push

coveralls-python

web-flow
Add a rule to add See Also links to bugs tagged with web-features (#2675)

426 of 3016 branches covered (14.12%)

0 of 70 new or added lines in 1 file covered. (0.0%)

1 existing line in 1 file now uncovered.

1942 of 9321 relevant lines covered (20.83%)

0.21 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/bugbot/rules/web_platform_features.py
1
# This Source Code Form is subject to the terms of the Mozilla Public
2
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
3
# You can obtain one at http://mozilla.org/MPL/2.0/.
4

NEW
5
import urllib
×
NEW
6
from dataclasses import dataclass
×
NEW
7
from typing import Any, Iterable, Mapping, Optional
×
8

NEW
9
from bugbot import gcp
×
NEW
10
from bugbot.bzcleaner import BzCleaner
×
11

12

NEW
13
@dataclass
×
NEW
14
class FeatureUrls:
×
NEW
15
    feature: str
×
NEW
16
    spec_url: Optional[list[str]]
×
NEW
17
    feature_url: str
×
NEW
18
    sp_url: Optional[str]
×
19

20

NEW
21
def url_keys(urls: Iterable[str]) -> Mapping[tuple[str, str], str]:
×
NEW
22
    rv = {}
×
NEW
23
    for url in urls:
×
NEW
24
        try:
×
NEW
25
            parsed = urllib.parse.urlparse(url)
×
NEW
26
            if parsed.hostname is None:
×
NEW
27
                continue
×
NEW
28
            rv[(parsed.hostname, parsed.path)] = url
×
NEW
29
        except ValueError:
×
NEW
30
            pass
×
NEW
31
    return rv
×
32

33

NEW
34
class WebPlatformFeatures(BzCleaner):
×
NEW
35
    def __init__(self) -> None:
×
NEW
36
        super().__init__()
×
NEW
37
        self.feature_bugs: Mapping[int, FeatureUrls] = {}
×
38

NEW
39
    def description(self) -> str:
×
NEW
40
        return "Update See Also for web-features bugs"
×
41

NEW
42
    def filter_no_nag_keyword(self) -> bool:
×
NEW
43
        return False
×
44

NEW
45
    def has_default_products(self) -> bool:
×
NEW
46
        return False
×
47

NEW
48
    def columns(self) -> list[str]:
×
NEW
49
        return ["id", "summary", "added"]
×
50

NEW
51
    def handle_bug(
×
52
        self, bug: dict[str, Any], data: dict[str, Any]
53
    ) -> Optional[dict[str, Any]]:
NEW
54
        features_key = bug["id"]
×
NEW
55
        bug_id = str(bug["id"])
×
56

NEW
57
        changes = {}
×
NEW
58
        if bug_id not in data:
×
NEW
59
            data[bug_id] = {}
×
NEW
60
        data[bug_id]["added"] = []
×
NEW
61
        if features_key in self.feature_bugs:
×
NEW
62
            see_also_keys = url_keys(bug["see_also"])
×
63

NEW
64
            feature_urls = self.feature_bugs[features_key]
×
NEW
65
            expected_urls = [feature_urls.feature_url]
×
NEW
66
            if feature_urls.sp_url is not None:
×
NEW
67
                expected_urls.append(feature_urls.sp_url)
×
NEW
68
            if feature_urls.spec_url is not None:
×
NEW
69
                expected_urls.extend(feature_urls.spec_url)
×
NEW
70
            expected_keys = url_keys(expected_urls)
×
NEW
71
            add_urls = [
×
72
                url for key, url in expected_keys.items() if key not in see_also_keys
73
            ]
NEW
74
            if add_urls:
×
NEW
75
                changes["see_also"] = bug["see_also"] + add_urls
×
NEW
76
                data[bug_id]["added"] = add_urls
×
77

NEW
78
        if changes:
×
NEW
79
            self.autofix_changes[bug_id] = changes
×
NEW
80
            return bug
×
81

NEW
82
        return None
×
83

NEW
84
    def get_bz_params(self, date) -> dict[str, str | int | list[str] | list[int]]:
×
NEW
85
        fields = ["id", "see_also"]
×
NEW
86
        self.feature_bugs = self.get_feature_bugs()
×
NEW
87
        print(self.feature_bugs)
×
NEW
88
        return {"include_fields": fields, "id": list(self.feature_bugs.keys())}
×
89

NEW
90
    def get_feature_bugs(self) -> Mapping[int, FeatureUrls]:
×
NEW
91
        project = "moz-fx-dev-dschubert-wckb"
×
92

NEW
93
        client = gcp.get_bigquery_client(project, ["cloud-platform", "drive"])
×
NEW
94
        query = f"""
×
95
WITH feature_bugs as (
96
  SELECT
97
    bugs.number,
98
    feature,
99
    bugs.see_also,
100
    web_features.spec as spec_url,
101
    concat("https://web-platform-dx.github.io/web-features-explorer/features/", feature, "/") as feature_url,
102
    concat("https://github.com/mozilla/standards-positions/issues/", sp_mozilla.issue) as sp_url
103
  FROM `{project}.webcompat_knowledge_base.bugzilla_bugs` AS bugs
104
  JOIN `{project}.web_features.features_latest` AS web_features
105
    ON web_features.feature IN UNNEST(`{project}.webcompat_knowledge_base.EXTRACT_ARRAY`(bugs.user_story, "$.web-feature"))
106
  LEFT JOIN `{project}.standards_positions.mozilla_standards_positions` AS sp_mozilla
107
    ON `{project}.webcompat_knowledge_base.BUG_ID_FROM_BUGZILLA_URL`(sp_mozilla.bug) = bugs.number
108
)
109

110
SELECT number, feature, spec_url, feature_url, sp_url FROM feature_bugs
111
WHERE
112
  NOT EXISTS(SELECT 1 FROM feature_bugs.spec_url WHERE spec_url NOT IN UNNEST(see_also))
113
  OR feature_url NOT IN UNNEST(see_also)
114
  OR sp_url NOT IN UNNEST(see_also)
115
"""
116

NEW
117
        return {
×
118
            row["number"]: FeatureUrls(
119
                feature=row["feature"],
120
                spec_url=row["spec_url"],
121
                feature_url=row["feature_url"],
122
                sp_url=row["sp_url"],
123
            )
124
            for row in client.query(query).result()
125
        }
126

127

NEW
128
if __name__ == "__main__":
×
NEW
129
    WebPlatformFeatures().run()
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc