feat: load pull review feedback on demand (Closes #1342)
CI / lint (pull_request) Successful in 3m25s
CI / build-release (pull_request) Successful in 7s
CI / browser-journey (pull_request) Successful in 5m34s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-24 09:16:30 +00:00
1 parent 50817bed9a
commit 611e79fc1e
7 files changed
+364 -28

No files matched your search

+34 -14
View File
@@ -2938,20 +2938,6 @@ async def pull_completion_review(repository: str, number: int) -> dict:
diff, diff_truncated = diff_result
previews = _diff_previews(diff, diff_truncated)
reviewers = _normalize_reviewer_statuses(pull, reviews, sha)
feedback_ids = _latest_feedback_review_ids(reviews)
if feedback_ids:
feedback_payloads = await asyncio.gather(*(
fetch(f"{base}/reviews/{review_id}/comments")
for review_id in feedback_ids.values()
))
feedback = {
login: _normalize_review_comments(payload)
for login, payload in zip(feedback_ids, feedback_payloads)
}
for reviewer in reviewers:
comments = feedback.get(reviewer["login"].casefold(), [])
if comments:
reviewer["comments"] = comments
return {
"repository": repository,
"number": number,
@@ -2985,6 +2971,40 @@ async def pull_completion_review(repository: str, number: int) -> dict:
}
async def pull_review_feedback(
repository: str, number: int, review_id: int, expected_head_sha: str
) -> dict:
"""Load optional inline feedback only for a review on the confirmed pull head."""
base = f"repos/{repository}/pulls/{number}"
pull, reviews = await asyncio.gather(
fetch(base),
fetch(f"{base}/reviews?limit=100"),
)
head = pull.get("head") if isinstance(pull, dict) else None
head_sha = head.get("sha") if isinstance(head, dict) else None
if head_sha != expected_head_sha:
raise StaleReviewError("Pull request head changed")
review_items = reviews if isinstance(reviews, list) else []
latest_ids = set(_latest_feedback_review_ids(review_items).values())
review = next(
(
item for item in review_items
if isinstance(item, dict) and item.get("id") == review_id
),
None,
)
if review is None or review_id not in latest_ids:
raise StaleReviewError("Review feedback is no longer current")
comments = await fetch(f"{base}/reviews/{review_id}/comments")
reviewed_head = review.get("commit_id")
return {
"review_id": review_id,
"head_sha": head_sha,
"reviewed_head_sha": reviewed_head if isinstance(reviewed_head, str) else "",
"comments": _normalize_review_comments(comments),
}
async def release_receipt_status(repository: str, commit_sha: str) -> dict:
"""Return CI and release evidence for one exact merge commit."""
status, releases = await asyncio.gather(
+44
View File
@@ -6640,6 +6640,50 @@ async def assigned_pull_review_data(
)
@app.get("/api/v1/repos/{owner}/{repo}/pulls/{number}/reviews/{review_id}/feedback")
async def assigned_pull_review_feedback(
owner: str,
repo: str,
number: int = PathParam(gt=0),
review_id: int = PathParam(gt=0),
expected_head_sha: str = Query(min_length=7, max_length=64, pattern=r"^[A-Fa-f0-9]+$"),
):
repository = f"{owner}/{repo}"
async def load_feedback():
if not await gitea_proxy.is_assigned_pull(repository, number):
raise HTTPException(status_code=404, detail="Assigned pull request not found")
return await gitea_proxy.pull_review_feedback(
repository, number, review_id, expected_head_sha
)
try:
result = await asyncio.wait_for(
load_feedback(), timeout=REVIEW_DETAIL_TIMEOUT_SECONDS
)
except HTTPException:
raise
except gitea_proxy.StaleReviewError:
return JSONResponse(
{"error": "The pull request or review changed. Reload review data before retrying."},
status_code=409,
headers={"Cache-Control": "no-store"},
)
except TimeoutError:
return JSONResponse(
{"error": "Loading review feedback timed out. Please retry."},
status_code=503,
headers={"Cache-Control": "no-store", "Retry-After": "1"},
)
except Exception:
return JSONResponse(
{"error": "Review feedback is temporarily unavailable. Please retry."},
status_code=503,
headers={"Cache-Control": "no-store", "Retry-After": "1"},
)
return JSONResponse(result, headers={"Cache-Control": "no-store"})
@app.get("/api/v1/repos/{owner}/{repo}/pulls/{number}/checks")
async def assigned_pull_checks(
owner: str, repo: str, number: int = PathParam(gt=0)