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:
+
+
+
+
+ Component | Bug | Summary | Triaged Since |
+
+
+
+ {% for i, (comp, bugid, summary, triaged_since) in enumerate(data) -%}
+
+
+ {{ comp | e }}
+ |
+
+ {{ bugid }}
+ |
+
+ {{ summary | e }}
+ |
+
+ {{ triaged_since }}
+ |
+
+ {% endfor -%}
+
+
+{% 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 }}