sync/notify/bugupdate.py (136 lines of code) (raw):
from collections import defaultdict
from datetime import datetime
import bugsy
from ..base import ProcessName, ProcessData
from ..env import Environment
from ..lock import mut, MutGuard, ProcLock
from ..meta import Metadata
from typing import Tuple
env = Environment()
bugzilla_url = "https://bugzilla.mozilla.org"
def from_iso_str(datetime_str):
return datetime.strptime(datetime_str,
'%Y-%m-%dT%H:%M:%S.%f')
class ProcData(ProcessData):
obj_type = "proc"
class TriageBugs:
process_name = ProcessName("proc", "bugzilla", str(0), 0)
def __init__(self, repo):
self._lock = None
self.data = ProcData(repo, self.process_name)
# TODO: not sure the locking here is correct
self.wpt_metadata = Metadata(self.process_name)
self._last_update = None
def as_mut(self, lock):
return MutGuard(lock, self, [self.data,
self.wpt_metadata])
@property
def lock_key(self) -> Tuple[str, str]:
return (self.process_name.subtype,
self.process_name.obj_id)
@property
def last_update(self):
if self._last_update is None and "last_update" in self.data:
self._last_update = from_iso_str(self.data["last-update"])
return self._last_update
@last_update.setter # type: ignore
@mut()
def last_update(self, value):
self.data["last-update"] = value.isoformat()
self._last_update = None
def meta_links(self):
rv = defaultdict(list)
for link in self.wpt_metadata.iter_bug_links(test_id=None,
product="firefox",
prefixes=(bugzilla_url,)):
bug = int(env.bz.id_from_url(link.url, bugzilla_url))
rv[bug].append(link)
return rv
def updated_bugs(self, bug_ids):
"""Get a list of all bugs which are associated with wpt results and have had their
resolution changed since the last update time
:param bug_ids: List of candidate bugs to check
"""
rv = []
params = {}
update_date = None
if self.last_update:
update_date = self.last_update.strftime("%Y-%m-%d")
params["chfieldfrom"] = update_date
if bug_ids:
# TODO: this could make the query over-long; we should probably split
# into multiple queries
params["bug_id"] = ",".join(str(item) for item in bug_ids)
search_resp = env.bz.bugzilla.session.get("%s/rest/bug" % bugzilla_url,
params=params)
search_resp.raise_for_status()
search_data = search_resp.json()
if self.last_update:
history_params = {"new_since": update_date}
else:
history_params = {}
for bug in search_data.get("bugs", []):
if (not self.last_update or
from_iso_str(bug["last_change_time"]) > self.last_update):
history_resp = env.bz.bugzilla.session.get(
"{}/rest/bug/{}/history".format(bugzilla_url, bug["id"]),
params=history_params)
history_resp.raise_for_status()
history_data = history_resp.json()
bugs = history_data.get("bugs")
if not bugs:
continue
assert len(bugs) == 1
for entry in bugs[0].get("history", []):
if not self.last_update or from_iso_str(entry["when"]) > self.last_update:
if any(change["field_name"] == "resolution" for change in entry["changes"]):
rv.append(bugsy.Bug(env.bz.bugzilla, **bug))
continue
return rv
def update_triage_bugs(git_gecko, comment=True):
triage_bugs = TriageBugs(git_gecko)
run_time = datetime.now()
meta_links = triage_bugs.meta_links()
updates = {}
for bug in triage_bugs.updated_bugs(list(meta_links.keys())):
if bug.resolution == "INVALID":
updates[bug.id] = None
elif bug.resolution == "DUPLICATE":
duped_to = bug._bug["dupe_of"]
while duped_to:
final_bug = duped_to
duped_to = env.bz.get_dupe(final_bug)
updates[bug.id] = final_bug
# TODO: handle some more cases here. Notably where the bug is marked as
# FIXED, but the tests don't actually pass
removed_by_bug = {}
with ProcLock.for_process(TriageBugs.process_name) as lock:
with triage_bugs.as_mut(lock):
for old_bug, new_bug in updates.items():
links = meta_links[old_bug]
if new_bug is None:
removed_by_bug[old_bug] = links
for item in links:
item.delete()
else:
new_url = env.bz.bugzilla_url(new_bug)
for link in links:
link.url = new_url
triage_bugs.last_update = run_time
# Now that the above change is commited, add some comments to bugzilla for the
# case where we removed URLs
comments = {}
for bug, old_links in removed_by_bug.items():
comments[bug] = comment_removed(bug, old_links, submit_comment=comment)
return updates, comments
def comment_removed(bug_id, links, submit_comment=True):
by_test = defaultdict(list)
for link in links:
by_test[link.test_id].append(link)
triage_lines = []
for test_id, links in sorted(by_test.items()):
triage_lines.append(test_id)
for link in links:
parts = []
if link.subtest:
parts.append("subtest: %s" % link.subtest)
if link.status:
parts.append("status: %s" % link.status)
if not parts:
parts.append("Parent test, any status")
triage_lines.append(" %s" % " ".join(parts))
with env.bz.bug_ctx(bug_id) as bug:
comment = """Resolving bug as invalid removed the following wpt triage data:
%s""" % "\n".join(triage_lines)
if submit_comment:
bug.add_comment(comment)
return comment