-
Notifications
You must be signed in to change notification settings - Fork 87
Send newly filed frontend bugs from Mozilla staff for automatic triage #2963
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
msujaws
wants to merge
1
commit into
mozilla:master
Choose a base branch
from
msujaws:frontend-triage-trigger
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| # 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 os | ||
|
|
||
| import requests | ||
|
|
||
| from bugbot import utils | ||
|
|
||
| DEFAULT_API_URL = "https://hackbot-api.moz.tools" | ||
|
|
||
| TIMEOUT = 60 | ||
|
|
||
|
|
||
| def api_url() -> str: | ||
| """The hackbot API base URL. | ||
|
|
||
| hackbot runs the LLM agents (see mozilla/bugbug's services/hackbot-api). We | ||
| only ever start a run from here: the agent applies its own findings to | ||
| Bugzilla and reports the result to Slack itself, so nothing comes back to us. | ||
|
|
||
| Defaults to the production deployment so the cron host needs no extra | ||
| environment, the way ``BUGBUG_HTTP_SERVER`` does. Override | ||
| ``HACKBOT_API_URL`` to point at another deployment — that is the name | ||
| hackbot's other clients already use (the pulse listener's setting and the | ||
| console's deploy env), so there is one name to remember across all three. | ||
|
|
||
| Read per call rather than at import so the override is settable by anything | ||
| that configures the environment after this module loads. | ||
| """ | ||
| return os.environ.get("HACKBOT_API_URL", DEFAULT_API_URL) | ||
|
|
||
|
|
||
| def trigger_agent_run(agent: str, inputs: dict) -> str: | ||
| """Start a hackbot agent run. | ||
|
|
||
| Args: | ||
| agent: The agent to run, e.g. `frontend-triage`. | ||
| inputs: The agent's per-run inputs, e.g. `{"bug_id": 1234567}`. | ||
|
|
||
| Returns: | ||
| The id of the created run. | ||
| """ | ||
| base_url = api_url() | ||
| if not base_url: | ||
| raise ValueError("HACKBOT_API_URL is not set") | ||
|
|
||
| response = requests.post( | ||
| f"{base_url.rstrip('/')}/agents/{agent}/runs", | ||
| headers={"X-API-Key": utils.get_login_info()["hackbot_api_key"]}, | ||
| json=inputs, | ||
| timeout=TIMEOUT, | ||
| ) | ||
| response.raise_for_status() | ||
|
|
||
| return response.json()["run_id"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| # 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 bugbot import logger | ||
| from bugbot.bzcleaner import BzCleaner | ||
| from bugbot.hackbot_utils import api_url, trigger_agent_run | ||
| from bugbot.people import People | ||
|
|
||
| AGENT = "frontend-triage" | ||
|
|
||
|
|
||
| class FrontendTriage(BzCleaner): | ||
| """Ask hackbot's frontend-triage agent to triage newly filed frontend bugs. | ||
|
|
||
| Scoped to bugs filed by Mozilla staff (which includes QA) in a small set of | ||
| components, because the agent's analysis is posted to the bug unattended when | ||
| it is confident. This rule only starts runs: the agent investigates the | ||
| source, comments on the bug, and reports to Slack itself, so nothing is | ||
| written to Bugzilla from here. | ||
| """ | ||
|
|
||
| def __init__(self, people: People | None = None) -> None: | ||
| super().__init__() | ||
| # Injectable because configs/people.json is gitignored and absent in CI. | ||
| self.people = people or People.get_instance() | ||
| self.max_triggers = self.get_config("max_triggers", 10) | ||
| self.left_for_next_run = 0 | ||
|
|
||
| def description(self): | ||
| return "[Using AI] Bugs sent for automatic frontend triage" | ||
|
|
||
| def columns(self): | ||
| return ["id", "summary", "creator", "run_id"] | ||
|
|
||
| def has_default_products(self): | ||
| # The query names its own product; the 19-product default list would put | ||
| # the whole tree in scope. | ||
| return False | ||
|
|
||
| def get_bz_params(self, date): | ||
| start_date, _ = self.get_dates(date) | ||
| components = self.get_config("components", {}) | ||
|
|
||
| return { | ||
| "include_fields": ["id", "summary", "creator", "component"], | ||
| "product": list(components.keys()), | ||
| "component": [c for cs in components.values() for c in cs], | ||
| # Defects only: the agent triages broken behaviour, not feature work. | ||
| "bug_type": "defect", | ||
| "resolution": "---", | ||
| "f1": "creation_ts", | ||
| "o1": "greaterthan", | ||
| "v1": start_date, | ||
| } | ||
|
|
||
| def handle_bug(self, bug, data): | ||
| # The IAM roster covers QA too, now that they file from @mozilla.com | ||
| # addresses, so staff membership is the whole filter. It beats a check on | ||
| # the address itself, which would miss the employees who file from a | ||
| # personal Bugzilla account. | ||
| if not self.people.is_mozilla(bug["creator"]): | ||
| return None | ||
|
|
||
| # `bughandler` rebuilds each row from id + summary alone, so the reporter | ||
| # has to be stashed here to reach the report. | ||
| data[str(bug["id"])] = {"creator": bug["creator"]} | ||
|
|
||
| return bug | ||
|
|
||
| def get_bugs(self, date="today", bug_ids=[], chunk_size=None): | ||
| return self.trigger_runs( | ||
| super().get_bugs(date=date, bug_ids=bug_ids, chunk_size=chunk_size) | ||
| ) | ||
|
|
||
| def trigger_runs(self, bugs: dict) -> dict: | ||
| """Start a triage run per bug, and return the ones we actually started. | ||
|
|
||
| Bugs we did not trigger are dropped from the result so they are neither | ||
| reported as triaged nor added to the cache — a bug skipped over the cap, | ||
| or one whose trigger failed, gets another chance on the next run. | ||
| """ | ||
| if self.dryrun: | ||
| logger.info( | ||
| "Dry run: would trigger %s for bugs %s", | ||
| AGENT, | ||
| ", ".join(bugs) or "(none)", | ||
| ) | ||
| for bug in bugs.values(): | ||
| bug["run_id"] = "(dry run)" | ||
| return bugs | ||
|
|
||
| if not api_url(): | ||
| # A misconfiguration, not a transient failure: fail once here rather | ||
| # than letting every bug look like its own flaky trigger below. The | ||
| # rule aborts and the cron error digest picks it up. | ||
| raise ValueError(f"HACKBOT_API_URL is not set; cannot start {AGENT} runs") | ||
|
|
||
| triggered: dict = {} | ||
| for bugid, bug in bugs.items(): | ||
| if len(triggered) >= self.max_triggers: | ||
| # Each run is real LLM spend, so a flood of filings must not turn | ||
| # into a flood of agent runs. | ||
| logger.warning( | ||
| "Reached the cap of %d %s runs; the rest wait for the next run", | ||
| self.max_triggers, | ||
| AGENT, | ||
| ) | ||
| break | ||
|
|
||
| try: | ||
| bug["run_id"] = trigger_agent_run(AGENT, {"bug_id": int(bugid)}) | ||
| except Exception: | ||
| logger.exception("Failed to trigger %s for bug %s", AGENT, bugid) | ||
| continue | ||
|
|
||
| triggered[bugid] = bug | ||
|
|
||
| # Covers both causes — over the cap, and a trigger that failed — since | ||
| # either way the bug stays out of the cache and comes back next run. | ||
| self.left_for_next_run = len(bugs) - len(triggered) | ||
|
|
||
| return triggered | ||
|
|
||
| def get_extra_for_template(self): | ||
| return {"left_for_next_run": self.left_for_next_run} | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| FrontendTriage().run() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| <p> | ||
| The following {{ plural('bug', data, pword='bugs') }} {{ plural('was', data, pword='were') }} sent to the frontend-triage agent. The agent posts its analysis to the bug itself when it is confident; otherwise the result waits for review in the hackbot UI. | ||
| </p> | ||
| <table {{ table_attrs }}> | ||
| <thead> | ||
| <tr> | ||
| <th>Bug</th> | ||
| <th>Summary</th> | ||
| <th>Reporter</th> | ||
| <th>Run</th> | ||
| </tr> | ||
| </thead> | ||
| <tbody> | ||
| {% for i, (bugid, summary, creator, run_id) 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 | e }}</td> | ||
| <td>{{ creator | e }}</td> | ||
| <td>{{ run_id }}</td> | ||
| </tr> | ||
| {% endfor -%} | ||
| </tbody> | ||
| </table> | ||
| {% if extra.left_for_next_run %} | ||
| <p> | ||
| <b>{{ extra.left_for_next_run }}</b> more {{ plural('bug', extra.left_for_next_run, pword='bugs') }} matched but {{ plural('was', extra.left_for_next_run, pword='were') }} not sent this time — either over the cap on how many agent runs one invocation may start, or the run failed to start. {{ plural('It', extra.left_for_next_run, pword='They') }} will be picked up on the next run. | ||
| </p> | ||
| {% endif %} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of @mozilla only, we could extend to everyone with "editbugs", which we also expect to file well.
You can simply change the query to check group membership (see for example the spambug case).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We intentionally want to just have this run on employee-filed bugs first, and then open it to editbugs next as part of our rollout plan once we are happy with the results.