feat: choose initial owner during issue filing
CI / lint (pull_request) Successful in 1m48s
CI / build-release (pull_request) Successful in 6s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-14 18:26:33 +00:00
1 parent 322f025794
commit 6449ead83d
13 files changed
+383 -38

No files matched your search

+4 -2
View File
@@ -1452,8 +1452,10 @@ async def create_issue(
for item in assignees
if isinstance(item, dict) and isinstance(item.get("login"), str)
]
if assignee not in confirmed_assignees:
raise ValueError("Gitea did not confirm issue self-assignment")
if confirmed_assignees != [assignee]:
raise ValueError(
"Gitea did not confirm self-assignment or exact issue assignment"
)
labels_value = issue.get("labels")
labels = labels_value if isinstance(labels_value, list) else []
milestone_value = issue.get("milestone")
+47 -2
View File
@@ -714,6 +714,9 @@ def _validate_binary_attachment(filename: str, content_type: str, content: bytes
class IssueCreation(BaseModel):
title: str = Field(min_length=1, max_length=255)
body: str = Field(default="", max_length=10_000)
assignee: str | None = Field(
default=None, pattern=r"^[A-Za-z0-9_.-]+$", max_length=255
)
label_ids: list[int] = Field(default_factory=list, max_length=20)
milestone_id: PositiveInt | None = None
due_date: str | None = Field(
@@ -4525,6 +4528,36 @@ async def repository_labels(owner: str, repo: str):
)
@app.get("/api/v1/repos/{owner}/{repo}/issue-assignees")
async def repository_issue_assignees(owner: str, repo: str):
repository = f"{owner}/{repo}"
async def load_assignees():
if await gitea_proxy.repository_access(repository) is None:
raise HTTPException(status_code=404, detail="Repository not found")
candidates = [
item
for item in await gitea_proxy.issue_handoff_candidates(repository)
if isinstance(item, dict)
and isinstance(item.get("login"), str)
and re.fullmatch(r"[A-Za-z0-9_.-]+", item["login"])
][:25]
return JSONResponse(candidates, headers={"Cache-Control": "no-store"})
try:
return await asyncio.wait_for(
load_assignees(), timeout=ISSUE_ACTION_TIMEOUT_SECONDS
)
except HTTPException:
raise
except Exception:
return JSONResponse(
{"error": "Owners could not be loaded. You can still assign the issue to yourself."},
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,
@@ -4541,6 +4574,17 @@ async def create_assigned_issue(
login = user.get("login") if isinstance(user, dict) else None
if not login or accessible is None:
raise HTTPException(status_code=404, detail="Repository not found")
assignee = login
if creation.assignee and creation.assignee != login:
eligible = {
item["login"]
for item in await gitea_proxy.issue_handoff_candidates(repository)
}
if creation.assignee not in eligible:
raise HTTPException(
status_code=422, detail="Selected owner is no longer eligible"
)
assignee = creation.assignee
if creation.label_ids:
available_labels = await gitea_proxy.repo_labels(repository)
valid_label_ids = {
@@ -4566,13 +4610,13 @@ async def create_assigned_issue(
repository,
creation.title,
creation.body,
login,
assignee,
creation.label_ids,
creation.milestone_id,
creation.due_date,
)
return await gitea_proxy.create_issue(
repository, creation.title, creation.body, login, creation.label_ids
repository, creation.title, creation.body, assignee, creation.label_ids
)
try:
@@ -4584,6 +4628,7 @@ async def create_assigned_issue(
repository,
creation.title,
creation.body,
creation.assignee,
tuple(creation.label_ids),
creation.milestone_id,
creation.due_date,