|
| 1 | +import html |
| 2 | +import logging |
| 3 | +import urllib |
| 4 | +from typing import Any, Mapping, MutableMapping, Optional |
| 5 | + |
| 6 | +import requests |
| 7 | + |
| 8 | +from helpers import github, issue |
| 9 | + |
| 10 | +Bug = Mapping[str, Any] |
| 11 | + |
| 12 | +logging.basicConfig() |
| 13 | +logger = logging.getLogger() |
| 14 | +logger.setLevel(logging.DEBUG) |
| 15 | + |
| 16 | + |
| 17 | +def get_kb_bugs() -> set[int]: |
| 18 | + resp = requests.get("https://bugzilla.mozilla.org/rest/bug?product=Web+Compatibility&Component=Knowledge+Base&include_fields=id") |
| 19 | + resp.raise_for_status() |
| 20 | + return {item["id"] for item in resp.json()["bugs"]} |
| 21 | + |
| 22 | + |
| 23 | +def get_bug_list() -> list[Bug]: |
| 24 | + resp = requests.get("https://bugzilla.mozilla.org/rest/bug?f1=see_also&f2=component&n2=1&o1=anywordssubstr&o2=anywordssubstr&classification=Components&product=Core&resolution=---&v1=webcompat&v2=Privacy&limit=0&include_fields=id,component,see_also,priority,severity,summary,blocks") |
| 25 | + resp.raise_for_status() |
| 26 | + return resp.json()["bugs"] |
| 27 | + |
| 28 | + |
| 29 | +def webcompat_link(see_also: str) -> Optional[str]: |
| 30 | + try: |
| 31 | + url = urllib.parse.urlparse(see_also) |
| 32 | + except Exception: |
| 33 | + return None |
| 34 | + if url.hostname == "webcompat.com": |
| 35 | + bug_id = url.path.rsplit("/", 1)[1] |
| 36 | + return f"https://github.com/webcompat/web-bugs/issues/{bug_id}" |
| 37 | + if url.hostname == "github.com" and url.path.startswith("/webcompat/web-bugs/issues/"): |
| 38 | + return f"{url.scheme}://{url.hostname}{url.path}" |
| 39 | + return None |
| 40 | + |
| 41 | + |
| 42 | +def bug_link(dest_id: int) -> str: |
| 43 | + return f"<a href=https://bugzilla.mozilla.org/show_bug.cgi?id={dest_id}>{dest_id}</a>" |
| 44 | + |
| 45 | + |
| 46 | +def html_list(list_items: list[str]) -> str: |
| 47 | + if list_items: |
| 48 | + return "<ul>" + "".join(f"<li>{item}" for item in list_items) + "</ul>" |
| 49 | + return "" |
| 50 | + |
| 51 | + |
| 52 | +def format_as_html(bugs: list[Bug]) -> str: |
| 53 | + rv = ["<!doctype html>", |
| 54 | + "<title>Core Bugs with WebCompat Links</title>", |
| 55 | + "<h1>Core Bugs with WebCompat Links</h1>", |
| 56 | + f"<p>Found {len(bugs)} bugs"] |
| 57 | + for offset in range(4): |
| 58 | + rv.extend([f"<h2 id=group{offset + 1}>Group {offset + 1}</h2>", |
| 59 | + "<table>", |
| 60 | + "<tr><th>id<th>Summary<th>Component<th>Severity<th>Webcompat Links<th>Issue Priority Score<th>Max Issue Severity<th>KB Entries"]) |
| 61 | + for bug in bugs[offset::4]: |
| 62 | + kb_links = html_list([bug_link(item) for item in bug["kb_entries"]]) |
| 63 | + webcompat_links = [] |
| 64 | + for (link, issue) in zip(bug["webcompat_links"], bug["issues"]): |
| 65 | + priority, severity = get_severity_priority(issue) |
| 66 | + webcompat_links.append(f"<a href={link}>{link}</a> [P{priority} S{severity}]") |
| 67 | + webcompat_details = f"<details>{html_list(webcompat_links)}</details>" if webcompat_links else "" |
| 68 | + rv.append(f"<tr><td>{bug_link(bug['id'])}<td>{html.escape(bug['summary'])}<td>{bug['component']}<td>{bug['severity']}<td>{len(bug['webcompat_links'])}{webcompat_details}<td>{bug['score'][0]}<td>{-bug['score'][1]}<td>{kb_links}") |
| 69 | + rv.append("</table>") |
| 70 | + return "\n".join(rv) |
| 71 | + |
| 72 | + |
| 73 | +def bug_score(bug): |
| 74 | + bug_severity = int(bug["severity"][1]) if bug["severity"][0] == "S" else 5 |
| 75 | + issue_score = [] |
| 76 | + severity_score = 4 |
| 77 | + priority_score = 0 |
| 78 | + for issue in bug["issues"]: |
| 79 | + issue_priority, issue_severity = get_severity_priority(issue) |
| 80 | + priority_score += 10 ** (4 - issue_priority) |
| 81 | + severity_score = min(severity_score, issue_severity) |
| 82 | + return (priority_score, -severity_score, len(bug["webcompat_links"]), -bug_severity) |
| 83 | + |
| 84 | + |
| 85 | +severity_map = {"critical": 2, "important": 3, "minor": 4} |
| 86 | + |
| 87 | + |
| 88 | +def get_severity_priority(gh_issue): |
| 89 | + priority = 4 |
| 90 | + severity = 4 |
| 91 | + for label in gh_issue.labels: |
| 92 | + if label.startswith("diagnosis-priority-"): |
| 93 | + priority = int(label[-1]) |
| 94 | + elif label.startswith("severity"): |
| 95 | + severity = severity_map[label.rsplit("-", 1)[1]] |
| 96 | + return (priority, severity) |
| 97 | + |
| 98 | + |
| 99 | +def main(): |
| 100 | + bug_cache = {} |
| 101 | + kb_bugs = get_kb_bugs() |
| 102 | + bugs = get_bug_list() |
| 103 | + bugs = [bug for bug in bugs if "Layout" in bug["component"]] |
| 104 | + gh_client = github.GitHub() |
| 105 | + for bug in bugs: |
| 106 | + bug["webcompat_links"] = [] |
| 107 | + bug["issues"] = [] |
| 108 | + for item in bug.get("see_also", []): |
| 109 | + link = webcompat_link(item) |
| 110 | + if link is not None: |
| 111 | + bug["webcompat_links"].append(link) |
| 112 | + issue_id = link.rsplit("/", 1)[1] |
| 113 | + bug["issues"].append(issue.WebcompatIssue.from_dict(gh_client.fetch_issue_by_number("webcompat", "web-bugs", issue_id, timeline=False))) |
| 114 | + bug["kb_entries"] = [item for item in bug["blocks"] if item in kb_bugs] |
| 115 | + bug["score"] = bug_score(bug) |
| 116 | + bugs.sort(key=lambda bug: bug["score"], reverse=True) |
| 117 | + print(format_as_html(bugs)) |
| 118 | + |
| 119 | + |
| 120 | +if __name__ == "__main__": |
| 121 | + main() |
0 commit comments