fix(robomp): clear needs-info on resume
This commit is contained in:
@@ -79,6 +79,7 @@ class GitHubBackend(Protocol):
|
||||
) -> None: ...
|
||||
|
||||
async def add_issue_labels(self, repo: str, number: int, labels: list[str]) -> tuple[str, ...]: ...
|
||||
async def remove_issue_label(self, repo: str, number: int, label: str) -> None: ...
|
||||
|
||||
async def submit_pr_review(
|
||||
self,
|
||||
|
||||
@@ -7,6 +7,7 @@ import time
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
from urllib.parse import quote
|
||||
|
||||
import httpx
|
||||
|
||||
@@ -446,6 +447,16 @@ class GitHubClient:
|
||||
)
|
||||
return tuple(str(lbl["name"]) if isinstance(lbl, dict) else str(lbl) for lbl in (data or []))
|
||||
|
||||
async def remove_issue_label(self, repo: str, number: int, label: str) -> None:
|
||||
"""Remove one label from an issue (or PR)."""
|
||||
if not label:
|
||||
return
|
||||
encoded = quote(label, safe="")
|
||||
await self.request(
|
||||
"DELETE",
|
||||
f"/repos/{repo}/issues/{number}/labels/{encoded}",
|
||||
)
|
||||
|
||||
async def submit_pr_review(
|
||||
self,
|
||||
*,
|
||||
|
||||
@@ -509,6 +509,19 @@ def create_proxy_app(settings: Settings) -> FastAPI:
|
||||
return _gh_error_response(exc)
|
||||
return JSONResponse({"labels": list(applied)})
|
||||
|
||||
@app.post("/gh/v1/remove_issue_label")
|
||||
async def remove_issue_label(request: Request) -> JSONResponse:
|
||||
data = await _json_body(request)
|
||||
repo = _require_str(data.get("repo"), "repo")
|
||||
number = _require_int(data.get("number"), "number")
|
||||
label = _require_str(data.get("label"), "label")
|
||||
github: GitHubClient = request.app.state.github
|
||||
try:
|
||||
await github.remove_issue_label(repo, number, label)
|
||||
except GitHubError as exc:
|
||||
return _gh_error_response(exc)
|
||||
return JSONResponse({"ok": True})
|
||||
|
||||
@app.post("/gh/v1/submit_pr_review")
|
||||
async def submit_pr_review(request: Request) -> JSONResponse:
|
||||
data = await _json_body(request)
|
||||
|
||||
@@ -282,6 +282,15 @@ class GitHubProxyClient:
|
||||
)
|
||||
return tuple(str(lbl) for lbl in (data.get("labels") if isinstance(data, dict) else None) or [])
|
||||
|
||||
async def remove_issue_label(self, repo: str, number: int, label: str) -> None:
|
||||
if not label:
|
||||
return
|
||||
await self._request(
|
||||
"POST",
|
||||
"/gh/v1/remove_issue_label",
|
||||
json_body={"repo": repo, "number": number, "label": label},
|
||||
)
|
||||
|
||||
async def submit_pr_review(
|
||||
self,
|
||||
*,
|
||||
|
||||
@@ -22,6 +22,7 @@ from robomp.sandbox import GitTransport, SandboxManager
|
||||
from robomp.worker import DirectiveInfo, TaskInputs, ThreadMessage, run_task
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
_NEEDS_INFO_LABEL = "needs-info"
|
||||
|
||||
|
||||
def _comment_from_payload(payload: Mapping[str, Any]) -> CommentInfo:
|
||||
@@ -498,6 +499,13 @@ async def handle_comment(
|
||||
author_email=settings.git_author_email,
|
||||
slot_uid=slot_uid,
|
||||
)
|
||||
if existing.state == "needs_info":
|
||||
try:
|
||||
await github.remove_issue_label(repo.full_name, issue.number, _NEEDS_INFO_LABEL)
|
||||
except GitHubError as exc:
|
||||
if exc.status != 404:
|
||||
log.warning("needs-info label cleanup failed", extra={"key": key, "err": str(exc)})
|
||||
db.set_issue_state(key, "reproducing")
|
||||
inputs = TaskInputs(
|
||||
settings=settings,
|
||||
db=db,
|
||||
|
||||
@@ -338,6 +338,8 @@ def round_trip_app(proxy_settings: Settings):
|
||||
return httpx.Response(201, json={})
|
||||
if path == "/repos/octo/widget/issues/1/labels":
|
||||
return httpx.Response(200, json=[{"name": "triage"}])
|
||||
if path == "/repos/octo/widget/issues/1/labels/needs-info" and req.method == "DELETE":
|
||||
return httpx.Response(200, json={})
|
||||
if path == "/repos/octo/widget/issues/1/assignees":
|
||||
return httpx.Response(201, json={})
|
||||
return httpx.Response(404, json={"message": f"unrouted {req.method} {path}"})
|
||||
@@ -405,6 +407,7 @@ async def test_round_trip_all_endpoints(round_trip_app) -> None:
|
||||
assert await client.request_reviewers(repo="octo/widget", pr_number=4, reviewers=["alice"]) is None
|
||||
|
||||
labels = await client.add_issue_labels("octo/widget", 1, ["triage"])
|
||||
assert await client.remove_issue_label("octo/widget", 1, "needs-info") is None
|
||||
assert labels == ("triage",)
|
||||
|
||||
assert await client.add_assignees("octo/widget", 1, ["alice"]) is None
|
||||
|
||||
@@ -505,6 +505,27 @@ async def test_add_issue_labels(proxy_settings: Settings) -> None:
|
||||
assert json.loads(captured["req"].content) == {"labels": ["triage", "bug"]}
|
||||
|
||||
|
||||
async def test_remove_issue_label(proxy_settings: Settings) -> None:
|
||||
captured: dict[str, httpx.Request] = {}
|
||||
|
||||
def gh(req: httpx.Request) -> httpx.Response:
|
||||
captured["req"] = req
|
||||
return httpx.Response(200, json={})
|
||||
|
||||
app = _build_app(proxy_settings, gh)
|
||||
body = b'{"repo":"octo/widget","number":1,"label":"needs-info"}'
|
||||
async with await _async_client(app) as client:
|
||||
resp = await client.post(
|
||||
"/gh/v1/remove_issue_label",
|
||||
content=body,
|
||||
headers={**_signed("POST", "/gh/v1/remove_issue_label", body), "Content-Type": "application/json"},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json() == {"ok": True}
|
||||
assert captured["req"].method == "DELETE"
|
||||
assert captured["req"].url.path == "/repos/octo/widget/issues/1/labels/needs-info"
|
||||
|
||||
|
||||
async def test_add_assignees(proxy_settings: Settings) -> None:
|
||||
captured: dict[str, httpx.Request] = {}
|
||||
|
||||
|
||||
@@ -2248,12 +2248,17 @@ async def test_handle_comment_resumes_needs_info_reply(
|
||||
monkeypatch.setattr(tasks, "_resolve_repo_and_issue", _resolve)
|
||||
|
||||
post_comment_calls: list = []
|
||||
removed_labels: list[tuple[str, int, str]] = []
|
||||
|
||||
async def _capture_post(self, *args, **kwargs):
|
||||
post_comment_calls.append((args, kwargs))
|
||||
return None
|
||||
|
||||
async def _capture_remove_label(self, repo: str, number: int, label: str) -> None:
|
||||
removed_labels.append((repo, number, label))
|
||||
|
||||
monkeypatch.setattr(GitHubClient, "post_comment", _capture_post)
|
||||
monkeypatch.setattr(GitHubClient, "remove_issue_label", _capture_remove_label)
|
||||
|
||||
payload = {
|
||||
"action": "created",
|
||||
@@ -2280,9 +2285,10 @@ async def test_handle_comment_resumes_needs_info_reply(
|
||||
assert call["task_kind"] == "handle_comment"
|
||||
assert call["comment"].body == "I am on Bun 1.3.14 and here is the trace"
|
||||
assert sandbox.ensure_calls[0]["existing_branch"] == "farm/old/branch"
|
||||
assert removed_labels == [("octo/widget", 88, "needs-info")]
|
||||
assert post_comment_calls == [], "needs-info replies must not get the finalized-issue notice"
|
||||
row = db.get_issue("octo/widget#88")
|
||||
assert row is not None and row.state == "needs_info"
|
||||
assert row is not None and row.state == "reproducing"
|
||||
close_database()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user