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

mozilla / relman-auto-nag / #5679

20 Nov 2025 06:20PM UTC coverage: 20.774% (+0.01%) from 20.764%
#5679

push

coveralls-python

web-flow
Add a needinfo even when there are multiple regressors (#2766)

426 of 3036 branches covered (14.03%)

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

1 existing line in 1 file now uncovered.

1953 of 9401 relevant lines covered (20.77%)

0.21 hits per line

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

0.0
/bugbot/rules/needinfo_regression_author.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

5
import collections
×
6

7
from libmozdata.bugzilla import Bugzilla
×
8

9
from bugbot import logger, utils
×
10
from bugbot.bzcleaner import BzCleaner
×
11
from bugbot.user_activity import UserActivity, UserStatus
×
12

13

14
class NeedinfoRegressionAuthor(BzCleaner):
×
15
    def __init__(self):
×
16
        super().__init__()
×
17
        self.extra_ni = {}
×
18
        self.private_regressor_ids: set[str] = set()
×
19

20
    def description(self):
×
21
        return "Unassigned regressions with non-empty Regressed By field"
×
22

23
    def handle_bug(self, bug, data):
×
NEW
24
        if not bug["regressed_by"]:
×
UNCOV
25
            return
×
26

27
        data[str(bug["id"])] = {
×
28
            "creator": bug["creator"],
29
            "regressor_id": max(bug["regressed_by"]),
30
            "severity": bug["severity"],
31
        }
32

33
        return bug
×
34

35
    def get_extra_for_needinfo_template(self):
×
36
        return self.extra_ni
×
37

38
    def get_autofix_change(self):
×
39
        return {
×
40
            "keywords": {"add": ["regression"]},
41
        }
42

43
    def set_autofix(self, bugs):
×
44
        for bugid, info in bugs.items():
×
45
            self.extra_ni[bugid] = {
×
46
                "regressor_id": str(info["regressor_id"]),
47
                "suggest_set_severity": info["suggest_set_severity"],
48
            }
49
            self.add_auto_ni(
×
50
                bugid,
51
                {
52
                    "mail": info["regressor_author_email"],
53
                    "nickname": info["regressor_author_nickname"],
54
                },
55
            )
56

57
    def get_bz_params(self, date):
×
58
        start_date, _ = self.get_dates(date)
×
59

60
        fields = [
×
61
            "id",
62
            "creator",
63
            "regressed_by",
64
            "assigned_to",
65
            "severity",
66
        ]
67

68
        # Find all bugs with regressed_by information which were open after start_date or
69
        # whose regressed_by field was set after start_date.
70
        params = {
×
71
            "include_fields": fields,
72
            "f1": "OP",
73
            "j1": "OR",
74
            "f2": "creation_ts",
75
            "o2": "greaterthan",
76
            "v2": start_date,
77
            "f3": "regressed_by",
78
            "o3": "changedafter",
79
            "v3": start_date,
80
            "f4": "CP",
81
            "f5": "regressed_by",
82
            "o5": "isnotempty",
83
            "n6": 1,
84
            "f6": "longdesc",
85
            "o6": "casesubstring",
86
            "v6": "since you are the author of the regressor",
87
            "f7": "flagtypes.name",
88
            "o7": "notsubstring",
89
            "v7": "needinfo?",
90
            "status": ["UNCONFIRMED", "NEW", "REOPENED"],
91
            "resolution": ["---"],
92
        }
93

94
        utils.get_empty_assignees(params)
×
95

96
        return params
×
97

98
    def retrieve_regressors(self, bugs):
×
99
        regressor_to_bugs = collections.defaultdict(list)
×
100
        for bug in bugs.values():
×
101
            regressor_to_bugs[bug["regressor_id"]].append(bug)
×
102

103
        def bug_handler(regressor_bug):
×
104
            if regressor_bug.get("groups"):
×
105
                regressor_bug_id = str(regressor_bug["id"])
×
106
                self.private_regressor_ids.add(regressor_bug_id)
×
107

108
            for bug in regressor_to_bugs[regressor_bug["id"]]:
×
109
                bug["regressor_author_email"] = regressor_bug["assigned_to"]
×
110
                bug["regressor_author_nickname"] = regressor_bug["assigned_to_detail"][
×
111
                    "nick"
112
                ]
113

114
        Bugzilla(
×
115
            bugids={bug["regressor_id"] for bug in bugs.values()},
116
            bughandler=bug_handler,
117
            include_fields=["id", "assigned_to", "groups"],
118
        ).get_data().wait()
119

120
    def filter_bugs(self, bugs):
×
121
        # Exclude bugs whose regressor author is nobody.
122
        for bug in list(bugs.values()):
×
123
            if utils.is_no_assignee(bug["regressor_author_email"]):
×
124
                logger.warning(
×
125
                    "Bug {}, regressor of bug {}, doesn't have an author".format(
126
                        bug["regressor_id"], bug["id"]
127
                    )
128
                )
129
                del bugs[bug["id"]]
×
130

131
        # Exclude bugs whose creator is the regressor author.
132
        bugs = {
×
133
            bug["id"]: bug
134
            for bug in bugs.values()
135
            if bug["creator"] != bug["regressor_author_email"]
136
        }
137

138
        # Exclude bugs where a commentor is the regressor author.
139
        def comment_handler(bug, bug_id):
×
140
            if any(
×
141
                comment["creator"] == bugs[bug_id]["regressor_author_email"]
142
                for comment in bug["comments"]
143
            ):
144
                del bugs[str(bug_id)]
×
145

146
        # Exclude bugs where the regressor author is inactive or blocked needinfo.
147
        # TODO: We can drop this when https://github.com/mozilla/bugbot/issues/1465 is implemented.
148
        users_info = UserActivity(include_fields=["groups", "requests"]).check_users(
×
149
            set(bug["regressor_author_email"] for bug in bugs.values()),
150
            keep_active=True,
151
            fetch_employee_info=True,
152
        )
153

154
        for bug_id, bug in list(bugs.items()):
×
155
            user_info = users_info[bug["regressor_author_email"]]
×
156
            if (
×
157
                user_info["status"] != UserStatus.ACTIVE
158
                or user_info["requests"]["needinfo"]["blocked"]
159
            ):
160
                del bugs[bug_id]
×
161
            else:
162
                bug["suggest_set_severity"] = bug["severity"] in (
×
163
                    "--",
164
                    "n/a",
165
                ) and user_info.get("is_employee")
166

167
        Bugzilla(
×
168
            bugids=self.get_list_bugs(bugs),
169
            commenthandler=comment_handler,
170
            comment_include_fields=["creator"],
171
        ).get_data().wait()
172

173
        return bugs
×
174

175
    def get_bugs(self, *args, **kwargs):
×
176
        bugs = super().get_bugs(*args, **kwargs)
×
177
        self.retrieve_regressors(bugs)
×
178
        bugs = self.filter_bugs(bugs)
×
179
        self.set_autofix(bugs)
×
180
        return bugs
×
181

182
    def set_needinfo(self):
×
183
        res = super().set_needinfo()
×
184
        for bug_id, needinfo_action in res.items():
×
185
            needinfo_action["comment"]["is_private"] = (
×
186
                bug_id in self.private_regressor_ids
187
            )
188

189
        return res
×
190

191

192
if __name__ == "__main__":
×
193
    NeedinfoRegressionAuthor().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

© 2025 Coveralls, Inc