feat: triage mobile issue capture with labels (#163)
CI / lint (pull_request) Successful in 13s
CI / build-frontend (pull_request) Successful in 5s

This commit is contained in:
timmy committed 2026-08-07 03:55:51 +00:00
1 parent e5f001f2b0
commit b6390d3298
6 files changed
+297 -19

No files matched your search

+44 -3
View File
@@ -372,11 +372,46 @@ async def comment_on_issue(repository: str, number: int, body: str) -> dict:
return _normalize_issue_comment(comment)
async def create_issue(repository: str, title: str, body: str, assignee: str) -> dict:
async def repo_labels(repository: str) -> list[dict]:
response = await _get_client().get(
f"/api/v1/repos/{repository}/labels",
headers=_auth(),
params={"limit": 50, "page": 1},
)
response.raise_for_status()
payload = response.json()
if not isinstance(payload, list):
raise ValueError("Gitea labels response was not a list")
return [
{
"id": item["id"],
"name": item["name"],
"color": item.get("color", "") if isinstance(item.get("color"), str) else "",
"description": item.get("description", "")
if isinstance(item.get("description"), str)
else "",
}
for item in payload
if isinstance(item, dict)
and isinstance(item.get("id"), int)
and isinstance(item.get("name"), str)
]
async def create_issue(
repository: str,
title: str,
body: str,
assignee: str,
label_ids: list[int] | None = None,
) -> dict:
payload: dict = {"title": title, "body": body, "assignee": assignee}
if label_ids:
payload["labels"] = label_ids
response = await _get_client().post(
f"/api/v1/repos/{repository}/issues",
headers=_auth(),
json={"title": title, "body": body, "assignee": assignee},
json=payload,
)
response.raise_for_status()
issue = response.json()
@@ -391,6 +426,8 @@ async def create_issue(repository: str, title: str, body: str, assignee: str) ->
]
if assignee not in confirmed_assignees:
raise ValueError("Gitea did not confirm issue self-assignment")
labels_value = issue.get("labels")
labels = labels_value if isinstance(labels_value, list) else []
return {
"id": issue.get("id"),
"number": issue["number"],
@@ -401,7 +438,11 @@ async def create_issue(repository: str, title: str, body: str, assignee: str) ->
if isinstance(issue.get("state"), str)
else "",
"repository": repository,
"labels": [],
"labels": [
item["name"]
for item in labels
if isinstance(item, dict) and isinstance(item.get("name"), str)
],
"assignees": confirmed_assignees,
"updated_at": issue.get("updated_at", "")
if isinstance(issue.get("updated_at"), str)
+41 -1
View File
@@ -109,6 +109,7 @@ class IssueComment(BaseModel):
class IssueCreation(BaseModel):
title: str = Field(min_length=1, max_length=255)
body: str = Field(default="", max_length=10_000)
label_ids: list[int] = Field(default_factory=list, max_length=20)
@field_validator("title")
@classmethod
@@ -202,6 +203,9 @@ async def prevent_live_api_caching(request, call_next):
) or (
request.url.path.startswith("/api/v1/repos/")
and request.url.path.endswith("/issues")
) or (
request.url.path.startswith("/api/v1/repos/")
and request.url.path.endswith("/labels")
):
response.headers["Cache-Control"] = "no-store"
return response
@@ -734,6 +738,33 @@ async def assigned_issue_detail(owner: str, repo: str, number: int = PathParam(g
)
@app.get("/api/v1/repos/{owner}/{repo}/labels")
async def repository_labels(owner: str, repo: str):
repository = f"{owner}/{repo}"
async def load_labels():
available = await gitea_proxy.repos()
accessible = {
item.get("full_name")
for item in (available if isinstance(available, list) else [])
if isinstance(item, dict)
}
if repository not in accessible:
raise HTTPException(status_code=404, detail="Repository not found")
return await gitea_proxy.repo_labels(repository)
try:
return await asyncio.wait_for(load_labels(), timeout=ISSUE_ACTION_TIMEOUT_SECONDS)
except HTTPException:
raise
except Exception:
return JSONResponse(
{"error": "Labels could not be loaded. Issue creation is still available."},
status_code=503,
headers={"Retry-After": "1"},
)
@app.post("/api/v1/repos/{owner}/{repo}/issues", status_code=201)
async def create_assigned_issue(creation: IssueCreation, owner: str, repo: str):
repository = f"{owner}/{repo}"
@@ -750,8 +781,17 @@ async def create_assigned_issue(creation: IssueCreation, owner: str, repo: str):
}
if not login or repository not in accessible:
raise HTTPException(status_code=404, detail="Repository not found")
if creation.label_ids:
available_labels = await gitea_proxy.repo_labels(repository)
valid_label_ids = {
item.get("id")
for item in available_labels
if isinstance(item, dict) and isinstance(item.get("id"), int)
}
if any(label_id not in valid_label_ids for label_id in creation.label_ids):
raise HTTPException(status_code=422, detail="Unknown repository label")
return await gitea_proxy.create_issue(
repository, creation.title, creation.body, login
repository, creation.title, creation.body, login, creation.label_ids
)
try: