Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions auto_nag/scripts/reminder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# 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/.

import re

from dateutil.parser import ParserError
from libmozdata import utils as lmdutils
from libmozdata.bugzilla import BugzillaUser

from auto_nag import utils
from auto_nag.bzcleaner import BzCleaner

REMINDER_TYPES = ["test", "pref", "disclosure"]
DATE_MATCH = re.compile(
r"\[reminder\-(%s) ([0-9]{4}-[0-9]{1,2}-[0-9]{1,2})\]" % "|".join(REMINDER_TYPES)
)


class Reminder(BzCleaner):
def __init__(self):
super(Reminder, self).__init__()
self.extra_ni = {}
self.autofix_whiteboard = {}

def description(self):
return "Bugs with whiteboard reminders"

def get_bz_params(self, date):
self.today = lmdutils.get_date_ymd(date)

params = {
"include_fields": ["assigned_to", "whiteboard", "triage_owner", "history"],
"f1": "status_whiteboard",
"o1": "substring",
"v1": "[reminder-",
}

return params

def handle_bug(self, bug, data):
bugid = str(bug["id"])

new_whiteboard = whiteboard = bug["whiteboard"]
matches = DATE_MATCH.findall(whiteboard)

# We support having multiple reminders that expire on a single day.
# (I'm not sure why, but I guess we should.)
reminders = []

for m in matches:
tag, date = m

# If we can't parse the date, do a little hack to throw it back
# at the user.
try:
parsed_date = lmdutils.get_date_ymd(date)
except ParserError:
replace_string = f"[reminder-{tag} {date}]"
new_whiteboard = new_whiteboard.replace(replace_string, "")
reminders.append({"full_tag": replace_string, "invalid_date": True})
continue

if parsed_date < self.today:
replace_string = f"[reminder-{tag} {date}]"
new_whiteboard = new_whiteboard.replace(replace_string, "")
reminders.append({"full_tag": replace_string})

if new_whiteboard == whiteboard:
return

target_entries = []
for entry in bug["history"]:
for field in entry["changes"]:
if field["field_name"] == "whiteboard":
# Check to see if any of the replace strings appeared in this change.
for r in reminders:
if (
r["full_tag"] in field["added"]
and r["full_tag"] not in field["removed"]
):
entry["full_tag"] = r["full_tag"]
target_entries.append(entry)
break

user_emails_to_names = self._get_user_emails_to_names(target_entries)

for r in reminders:
for entry in target_entries:
if r["full_tag"] == entry["full_tag"]:
if user_emails_to_names[entry["who"]] == "Invalid User":
reminders.remove(r)
else:
r["who"] = user_emails_to_names[entry["who"]]
r["when"] = utils.get_human_lag(entry["when"])

if not reminders:
return

data[bugid] = {
"full_tags": ", ".join([r["full_tag"] for r in reminders]),
}

self.autofix_whiteboard[bugid] = {
"whiteboard": new_whiteboard,
}

self.extra_ni[bugid] = {"reminders": reminders}

return bug

def _get_user_emails_to_names(self, target_entries):
"""
Do a bunch of annoying stuff to translate bugzilla email addresses
to nicknames, and then from a list of nicknames to a nicely formatted
string.
"""

# emails -> nicks
def user_handler(user, data):
data[user["name"]] = "Invalid User"
for g in user["groups"]:
if g["name"] == "editbugs" or g["name"] == "canconfirm":
data[user["name"]] = (
user["real_name"] or user["nick"] or user["name"]
)

user_emails_to_names = {}
BugzillaUser(
user_names=[entry["who"] for entry in target_entries],
include_fields=["real_name", "nick", "name", "groups"],
user_handler=user_handler,
user_data=user_emails_to_names,
).wait()

return user_emails_to_names

def get_extra_for_needinfo_template(self):
return self.extra_ni

def get_autofix_change(self):
return self.autofix_whiteboard

def columns(self):
return ["id", "summary", "full_tags"]

def get_mail_to_auto_ni(self, bug):
for field in ["assigned_to", "triage_owner"]:
person = bug.get(field, "")
if person and not utils.is_no_assignee(person):
return {"mail": person, "nickname": bug[f"{field}_detail"]["nick"]}

return None


if __name__ == "__main__":
Reminder().run()
4 changes: 4 additions & 0 deletions runauto_nag_daily.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ python -m auto_nag.next_release --production
# Daily
python -m auto_nag.scripts.to_triage --production

# Process reminders
# Daily
python -m auto_nag.scripts.reminder --production

# Nag triage fallback to update calendar
# Daily
python -m auto_nag.round_robin_fallback --production
Expand Down
24 changes: 24 additions & 0 deletions templates/reminder.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<p>The following {{ plural('bug has', data, pword='bugs have') }} a reminder set on them that has expired:
<table {{ table_attrs }}>
<thead>
<tr>
<th>Bug</th><th>Summary</th><th>Tags</th>
</tr>
</thead>
<tbody>
{% for i, (bugid, summary, full_tags) in enumerate(data) -%}
<tr {% if i % 2 == 0 %}bgcolor="#E0E0E0"{% endif -%}>
<td>
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id={{ bugid }}">{{ bugid }}</a>
</td>
<td>
{{ summary }}
</td>
<td>
{{ full_tags }}
</td>
</tr>
{% endfor -%}
</tbody>
</table>
</p>
7 changes: 7 additions & 0 deletions templates/reminder_needinfo.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{% for reminder in extra[bugid]["reminders"] -%}

{{ reminder["when"] }} ago, {{ reminder["who"] }} placed a reminder on the bug using the whiteboard tag {% if reminder["invalid_date"] %} which could not be parsed as a valid `yyyy-m-d` date {% else -%} `{{ reminder["full_tag"] }}` {% endif %}.

{% endfor -%}

{{ nickname }}, please refer to the original comment to better understand the reason for the {{ plural("reminder", extra[bugid]["reminders"]) }}.