From 19b6e5390ad8a7057637d069d371ff081de7b17e Mon Sep 17 00:00:00 2001 From: Suhaib Mujahid Date: Mon, 18 Jul 2022 22:22:16 -0400 Subject: [PATCH] Support the new `triaged` keyword --- auto_nag/components.py | 13 ++ auto_nag/scripts/configs/tools.json | 21 --- auto_nag/scripts/to_triage.py | 5 +- auto_nag/scripts/workflow/multi_nag.py | 15 +- .../{no_severity.py => not_triaged.py} | 88 ++++++--- .../scripts/workflow/triaged_no_severity.py | 170 ++++++++++++++++++ auto_nag/utils.py | 22 +++ .../{no_severity.html => not_triaged.html} | 14 +- ..._needinfo.txt => not_triaged_needinfo.txt} | 2 +- templates/triaged_no_severity.html | 36 ++++ templates/triaged_no_severity_needinfo.txt | 4 + 11 files changed, 325 insertions(+), 65 deletions(-) rename auto_nag/scripts/workflow/{no_severity.py => not_triaged.py} (79%) create mode 100644 auto_nag/scripts/workflow/triaged_no_severity.py rename templates/{no_severity.html => not_triaged.html} (50%) rename templates/{no_severity_needinfo.txt => not_triaged_needinfo.txt} (60%) create mode 100644 templates/triaged_no_severity.html create mode 100644 templates/triaged_no_severity_needinfo.txt diff --git a/auto_nag/components.py b/auto_nag/components.py index f58f9ad01..532efbfea 100644 --- a/auto_nag/components.py +++ b/auto_nag/components.py @@ -26,6 +26,19 @@ def from_str(cls, pc: str) -> "ComponentName": return cls(*splitted_name) + @classmethod + def from_bug(cls, bug: dict) -> "ComponentName": + """Create an instance from a bug dictionary. + + Args: + bug: a dictionary that have product and component keys + + Returns: + An instance from the ComponentName class based on the provided bug. + """ + + return cls(bug["product"], bug["component"]) + class Components: """Bugzilla components""" diff --git a/auto_nag/scripts/configs/tools.json b/auto_nag/scripts/configs/tools.json index 43b00939e..c50dcf10c 100644 --- a/auto_nag/scripts/configs/tools.json +++ b/auto_nag/scripts/configs/tools.json @@ -270,27 +270,6 @@ ], "supervisor_skiplist": ["dcamp@mozilla.com"] }, - "no_severity": { - "max-years": 1, - "first-step": 2, - "second-step": 4, - "escalation-first": { - "default": { - "[0;+∞[": { - "supervisor": "self", - "days": ["Mon", "Tue", "Wed", "Thu", "Fri"] - } - } - }, - "escalation-second": { - "default": { - "[0;+∞[": { - "supervisor": "n+1", - "days": ["Mon", "Thu"] - } - } - } - }, "p3_p4_p5": { "months_lookup": 6 }, diff --git a/auto_nag/scripts/to_triage.py b/auto_nag/scripts/to_triage.py index d3768f837..de7851ea0 100644 --- a/auto_nag/scripts/to_triage.py +++ b/auto_nag/scripts/to_triage.py @@ -79,7 +79,7 @@ def get_bz_params(self, date): "include_fields": fields, "product": list(prods), "component": list(comps), - "keywords": "intermittent-failure", + "keywords": ["intermittent-failure", "triaged"], "keywords_type": "nowords", "email2": "wptsync@mozilla.bugs", "emailreporter2": "1", @@ -91,9 +91,6 @@ def get_bz_params(self, date): "f2": "flagtypes.name", "o2": "notsubstring", "v2": "needinfo?", - "f3": "bug_severity", - "o3": "anyexact", - "v3": "--, n/a", } return params diff --git a/auto_nag/scripts/workflow/multi_nag.py b/auto_nag/scripts/workflow/multi_nag.py index 81ea575c1..a6b515ccf 100644 --- a/auto_nag/scripts/workflow/multi_nag.py +++ b/auto_nag/scripts/workflow/multi_nag.py @@ -4,9 +4,9 @@ from auto_nag.erroneous_bzmail import check_erroneous_bzmail from auto_nag.multinaggers import MultiNaggers - -from .no_severity import NoSeverity -from .p1_no_assignee import P1NoAssignee +from auto_nag.scripts.workflow.not_triaged import NotTriaged +from auto_nag.scripts.workflow.p1_no_assignee import P1NoAssignee +from auto_nag.scripts.workflow.triaged_no_severity import TriagedNoSeverity # from .p1_no_activity import P1NoActivity # from .p2_no_activity import P2NoActivity @@ -16,8 +16,9 @@ class WorkflowMultiNag(MultiNaggers): def __init__(self): super(WorkflowMultiNag, self).__init__( - NoSeverity("first"), - NoSeverity("second"), + TriagedNoSeverity(), + NotTriaged("first"), + NotTriaged("second"), # P1NoActivity(), P1NoAssignee(), # P2NoActivity(), @@ -27,9 +28,7 @@ def description(self): return "Bugs requiring special attention to help release management" def title(self): - return "{} -- Severity and Priority Flags Alert".format( - self.date.strftime("%A %b %d") - ) + return "{} -- Triage Alert".format(self.date.strftime("%A %b %d")) if __name__ == "__main__": diff --git a/auto_nag/scripts/workflow/no_severity.py b/auto_nag/scripts/workflow/not_triaged.py similarity index 79% rename from auto_nag/scripts/workflow/no_severity.py rename to auto_nag/scripts/workflow/not_triaged.py index 85208b354..49d613dbe 100644 --- a/auto_nag/scripts/workflow/no_severity.py +++ b/auto_nag/scripts/workflow/not_triaged.py @@ -2,17 +2,48 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. +from datetime import datetime + from libmozdata import utils as lmdutils from auto_nag import utils from auto_nag.bzcleaner import BzCleaner +from auto_nag.component_triagers import ComponentName from auto_nag.escalation import Escalation from auto_nag.nag_me import Nag from auto_nag.round_robin import RoundRobin +ESCALATION_CONFIG = { + "first": { + "default": { + "[0;+∞[": { + "supervisor": "self", + "days": ["Mon", "Tue", "Wed", "Thu", "Fri"], + }, + }, + }, + "second": { + "default": { + "[0;+∞[": { + "supervisor": "n+1", + "days": ["Mon", "Thu"], + }, + }, + }, +} + + +class NotTriaged(BzCleaner, Nag): + """Bugs that are not triaged""" -class NoSeverity(BzCleaner, Nag): - def __init__(self, typ, inactivity_days: int = 3): + def __init__( + self, + typ, + inactivity_days: int = 3, + oldest_bug_days: int = 360, + first_step_weeks: int = 2, + second_step_weeks: int = 4, + ): """Constructor Args: @@ -20,36 +51,39 @@ def __init__(self, typ, inactivity_days: int = 3): emails will be sent only if `typ` is second. inactivity_days: number of days that a bug should be inactive before being considered. + oldest_bug_days: the max number of days since the creation of a bug + to be considered. + first_step_weeks: number of weeks to consider the bug for the the + first step. + second_step_weeks number of weeks to consider the bug for the the + second step. """ - super(NoSeverity, self).__init__() - assert typ in {"first", "second"} + super().__init__() + self.date: datetime self.typ = typ - self.lookup_first = utils.get_config(self.name(), "first-step", 2) - self.lookup_second = utils.get_config(self.name(), "second-step", 4) + self.oldest_bug_days = oldest_bug_days + self.lookup_first = first_step_weeks + self.lookup_second = second_step_weeks self.escalation = Escalation( self.people, - data=utils.get_config(self.name(), "escalation-{}".format(typ)), + data=ESCALATION_CONFIG[typ], skiplist=utils.get_config("workflow", "supervisor_skiplist", []), ) self.round_robin = RoundRobin.get_instance() - self.components_skiplist = utils.get_config("workflow", "components_skiplist") + self.components_skiplist = { + ComponentName.from_str(pc) + for pc in utils.get_config("workflow", "components_skiplist") + } self.activity_date = lmdutils.get_date("today", inactivity_days) def description(self): - return "Bugs without a severity or statuses set" + return "Bugs that are not triaged" def nag_template(self): return self.template() def nag_preamble(self): - return """

-

-

""" + return True def get_extra_for_template(self): return { @@ -73,8 +107,7 @@ def columns(self): def handle_bug(self, bug, data): if ( - # check if the product::component is in the list - utils.check_product_component(self.components_skiplist, bug) + ComponentName.from_bug(bug) in self.components_skiplist or utils.get_last_no_bot_comment_date(bug) > self.activity_date ): return None @@ -115,21 +148,21 @@ def get_bz_params(self, date): ] params = { "include_fields": fields, - "keywords": "intermittent-failure", + "keywords": ["intermittent-failure", "triaged"], "keywords_type": "nowords", "email2": "wptsync@mozilla.bugs", "emailreporter2": "1", "emailtype2": "notequals", "resolution": "---", + "f1": "creation_ts", + "o1": "greaterthan", + "v1": f"-{self.oldest_bug_days}d", "f21": "bug_type", "o21": "equals", "v21": "defect", "f22": "flagtypes.name", - "o22": "notsubstring", + "o22": "notequals", "v22": "needinfo?", - "f23": "bug_severity", - "o23": "anyexact", - "v23": "--, n/a", } self.date = lmdutils.get_date_ymd(date) first = f"-{self.lookup_first * 7}d" @@ -144,9 +177,6 @@ def get_bz_params(self, date): # ((second < creation < first) && pc never changed) params.update( { - "f2": "flagtypes.name", - "o2": "notequals", - "v2": "needinfo?", "j3": "OR", "f3": "OP", "j4": "AND", @@ -240,5 +270,5 @@ def get_bz_params(self, date): if __name__ == "__main__": - NoSeverity("first").run() - NoSeverity("second").run() + NotTriaged("first").run() + NotTriaged("second").run() diff --git a/auto_nag/scripts/workflow/triaged_no_severity.py b/auto_nag/scripts/workflow/triaged_no_severity.py new file mode 100644 index 000000000..33bc3c3fb --- /dev/null +++ b/auto_nag/scripts/workflow/triaged_no_severity.py @@ -0,0 +1,170 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +from typing import Dict + +from libmozdata import utils as lmdutils + +from auto_nag import utils +from auto_nag.bzcleaner import BzCleaner +from auto_nag.components import ComponentName +from auto_nag.escalation import Escalation +from auto_nag.nag_me import Nag +from auto_nag.round_robin import RoundRobin + +ESCALATION_CONFIG = { + "normal": { + "[0;+∞[": { + "supervisor": "self", + "days": ["Mon", "Tue", "Wed", "Thu", "Fri"], + }, + }, + "high": { + "[0;+∞[": { + "supervisor": "n+1", + "days": ["Mon", "Thu"], + }, + }, +} + + +class TriagedNoSeverity(BzCleaner, Nag): + def __init__( + self, + inactivity_days: int = 3, + oldest_bug_days: int = 360, + normal_escalation_days: int = 14, + high_escalation_days: int = 28, + ): + """Constructor + + Args: + inactivity_days: number of days that a bug should be inactive before + being considered. + oldest_bug_days: the max number of days since the creation of a bug + to be considered. + normal_escalation_days: number of days since a bug is triaged to be + consider for normal escalation. + high_escalation_days: number of days since a bug is triaged to be + consider for high escalation. + """ + super(TriagedNoSeverity, self).__init__() + self.oldest_bug_days = oldest_bug_days + self.escalation = Escalation( + self.people, + data=ESCALATION_CONFIG, + skiplist=utils.get_config("workflow", "supervisor_skiplist", []), + ) + self.round_robin = RoundRobin.get_instance() + self.components_skiplist = { + ComponentName.from_str(pc) + for pc in utils.get_config("workflow", "components_skiplist") + } + + self.activity_date = lmdutils.get_date("today", inactivity_days) + self.normal_escalation_date = lmdutils.get_date("today", normal_escalation_days) + self.high_escalation_date = lmdutils.get_date("today", high_escalation_days) + + # FIXME: This is a workaround to pass the priority to `set_people_to_nag` + # without altering the bug object. + self.bug_priority: Dict[str, str] = {} + + def description(self): + return "Triaged bugs without a severity set" + + def nag_template(self): + return self.template() + + def nag_preamble(self): + return True + + def has_product_component(self): + return True + + def ignore_meta(self): + return True + + def columns(self): + return ["component", "id", "summary", "triaged_since"] + + def handle_bug(self, bug, data): + if ( + ComponentName.from_bug(bug) in self.components_skiplist + or utils.get_last_no_bot_comment_date(bug) > self.activity_date + ): + return None + + triaged_date = utils.get_last_triaged_date(bug) + if triaged_date < self.high_escalation_date: + priority = "high" + elif triaged_date < self.normal_escalation_date: + priority = "normal" + else: + return None + + bugid = str(bug["id"]) + data[bugid] = { + "triaged_since": utils.get_human_lag(triaged_date), + } + + self.bug_priority[bugid] = priority + return bug + + def get_mail_to_auto_ni(self, bug): + mail, nick = self.round_robin.get(bug, self.date) + if mail and nick: + return {"mail": mail, "nickname": nick} + + return None + + def set_people_to_nag(self, bug, buginfo): + priority = self.bug_priority[buginfo["id"]] + if priority == "normal": + return bug + + owners = self.round_robin.get(bug, self.date, only_one=False, has_nick=False) + real_owner = bug["triage_owner"] + self.add_triage_owner(owners, real_owner=real_owner) + if not self.add(owners, buginfo, priority=priority): + self.add_no_manager(buginfo["id"]) + return bug + + def get_bz_params(self, date): + fields = [ + "triage_owner", + "comments.creator", + "comments.creation_time", + "history", + ] + params = { + "include_fields": fields, + "keywords": "intermittent-failure", + "keywords_type": "nowords", + "email2": "wptsync@mozilla.bugs", + "emailreporter2": "1", + "emailtype2": "notequals", + "resolution": "---", + "f1": "creation_ts", + "o1": "greaterthan", + "v1": f"-{self.oldest_bug_days}d", + "f21": "bug_type", + "o21": "equals", + "v21": "defect", + "f22": "flagtypes.name", + "o22": "notequals", + "v22": "needinfo?", + "f23": "bug_severity", + "o23": "anyexact", + "v23": "--, n/a", + "f24": "keywords", + "o24": "anyexact", + "v24": "triaged", + } + self.date = lmdutils.get_date_ymd(date) + + return params + + +if __name__ == "__main__": + TriagedNoSeverity().run() diff --git a/auto_nag/utils.py b/auto_nag/utils.py index 8c94a41e8..466fa95f6 100644 --- a/auto_nag/utils.py +++ b/auto_nag/utils.py @@ -560,6 +560,7 @@ def bz_ignore_case(s): def check_product_component(data, bug): + """TODO: drop in favour of using ComponentName""" prod = bug["product"] comp = bug["component"] pc = prod + "::" + comp @@ -645,6 +646,27 @@ def get_last_no_bot_comment_date(bug: dict) -> str: return bug["comments"][0]["creation_time"] +def get_last_triaged_date(bug: dict) -> str: + """Get the date when the bug was last triaged. + + Args: + bug: the bug dictionary; it must has the history list. + + Returns: + The date when the triaged keyword was added. If the bug history does not + show the triaged keyword, an exception will be raised. + """ + for entry in reversed(bug["history"]): + for field in entry["changes"]: + if field["field_name"] == "whiteboard": + if "triaged" in field["added"] and "triaged" not in field["removed"]: + return entry["when"] + + break + + raise Exception("The bug is not triaged") + + def get_sort_by_bug_importance_key(bug): """ We need bugs with high severity (S1 or S2) or high priority (P1 or P2) to be diff --git a/templates/no_severity.html b/templates/not_triaged.html similarity index 50% rename from templates/no_severity.html rename to templates/not_triaged.html index d6b235653..d2a4cb5c7 100644 --- a/templates/no_severity.html +++ b/templates/not_triaged.html @@ -1,6 +1,16 @@ -{{ nag_preamble }} +{% if nag_preamble %}

- The following {{ plural('bug has', data, pword='bugs have') }} no Severity field set for the last {{ extra['nweeks'] }} {{ plural('week', extra['nweeks']) }}: +

+

+{% endif %} + +

+ The following {{ plural('bug has', data, pword='bugs have') }} not triaged for the last {{ extra['nweeks'] }} {{ plural('week', extra['nweeks']) }}:

diff --git a/templates/no_severity_needinfo.txt b/templates/not_triaged_needinfo.txt similarity index 60% rename from templates/no_severity_needinfo.txt rename to templates/not_triaged_needinfo.txt index 0d6684c14..199190dc1 100644 --- a/templates/no_severity_needinfo.txt +++ b/templates/not_triaged_needinfo.txt @@ -1,4 +1,4 @@ -The severity field is not set for this bug. +This bug is not triaged. :{{ nickname }}, could you have a look please? {{ documentation }} diff --git a/templates/triaged_no_severity.html b/templates/triaged_no_severity.html new file mode 100644 index 000000000..c103dea7f --- /dev/null +++ b/templates/triaged_no_severity.html @@ -0,0 +1,36 @@ +{% if nag_preamble %} +

+

+

+{% endif %} +

+ The following triaged {{ plural('bug has', data, pword='bugs have') }} no Severity field set: +

+
+ + + + + + + {% for i, (comp, bugid, summary, triaged_since) in enumerate(data) -%} + + + + + + + {% endfor -%} + +
ComponentBugSummaryTriaged Since
+ {{ comp | e }} + + {{ bugid }} + + {{ summary | e }} + + {{ triaged_since }} +
+{% if query_url_nag %}

See the query on Bugzilla.

{% endif -%} diff --git a/templates/triaged_no_severity_needinfo.txt b/templates/triaged_no_severity_needinfo.txt new file mode 100644 index 000000000..d7e4c593f --- /dev/null +++ b/templates/triaged_no_severity_needinfo.txt @@ -0,0 +1,4 @@ +The bug was triaged, but the severity field still is not set. +:{{ nickname }}, could you have a look please? + +{{ documentation }}