feat: add mobile review readiness inspector (#121)
CI / lint (pull_request) Successful in 14s
CI / build-frontend (pull_request) Successful in 6s

This commit is contained in:
timmy committed 2026-08-06 17:09:40 +00:00
1 parent 329591ebd6
commit 83788c6b82
7 files changed
+391 -9

No files matched your search

+64
View File
@@ -55,6 +55,70 @@ async def pull_requests() -> list[dict]:
return list(merged.values())
async def is_requested_review(repository: str, number: int) -> bool:
pulls = await pull_requests()
return any(
isinstance(pull, dict)
and pull.get("number") == number
and "review_requested" in (pull.get("work_reasons") or [])
and isinstance(pull.get("repository"), dict)
and pull["repository"].get("full_name") == repository
for pull in pulls
)
async def pull_review_detail(repository: str, number: int) -> dict:
base = f"repos/{repository}/pulls/{number}"
pull = await fetch(base)
if not isinstance(pull, dict):
raise ValueError("Gitea pull request response was not an object")
files = await fetch(f"{base}/files")
head_value = pull.get("head")
head: dict = head_value if isinstance(head_value, dict) else {}
sha_value = head.get("sha")
sha = sha_value if isinstance(sha_value, str) else ""
status = await fetch(f"repos/{repository}/commits/{sha}/status")
reviews = await fetch(f"{base}/reviews")
user_value = pull.get("user")
user: dict = user_value if isinstance(user_value, dict) else {}
normalized_files = [
{
"filename": file["filename"],
"status": file.get("status") or "changed",
"additions": file.get("additions") or 0,
"deletions": file.get("deletions") or 0,
}
for file in (files if isinstance(files, list) else [])[:100]
if isinstance(file, dict) and isinstance(file.get("filename"), str)
]
normalized_reviews = [
{
"user": {
"login": (
review["user"].get("login", "")
if isinstance(review.get("user"), dict)
else ""
)
},
"state": review.get("state") or "COMMENT",
"body": review.get("body") or "",
}
for review in (reviews if isinstance(reviews, list) else [])[:50]
if isinstance(review, dict)
]
return {
"repository": repository,
"number": number,
"title": pull.get("title", ""),
"body": pull.get("body") or "",
"url": pull.get("html_url", ""),
"author": user.get("login", ""),
"ci_state": status.get("state", "unknown") if isinstance(status, dict) else "unknown",
"files": normalized_files,
"reviews": normalized_reviews,
}
async def activity_events() -> list[dict]:
user = await current_user()
events = await fetch(f"users/{user['login']}/activities/feeds?limit=20")
+37 -4
View File
@@ -2,13 +2,21 @@ import asyncio
import math
from pathlib import Path
from fastapi import FastAPI
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
from src.gitea_proxy import activity_events, current_user, repos, issues, pull_requests
from src.models import User, Repo, Issue, PullRequest
from src.gitea_proxy import (
activity_events,
current_user,
is_requested_review,
issues,
pull_requests,
pull_review_detail,
repos,
)
from src.models import Issue, PullRequest, Repo, User
from src.suggestion_engine import compute
from src.views import router as frontend_router
@@ -41,7 +49,10 @@ app.include_router(frontend_router)
@app.middleware("http")
async def prevent_live_api_caching(request, call_next):
response = await call_next(request)
if request.url.path in {"/api/v1/context", "/api/v1/events"}:
if request.url.path in {"/api/v1/context", "/api/v1/events"} or (
request.url.path.startswith("/api/v1/repos/")
and request.url.path.endswith("/review")
):
response.headers["Cache-Control"] = "no-store"
return response
@@ -247,3 +258,25 @@ async def event_stream():
)
},
)
@app.get("/api/v1/repos/{owner}/{repo}/pulls/{number}/review")
async def review_detail(owner: str, repo: str, number: int):
try:
repository = f"{owner}/{repo}"
if not await asyncio.wait_for(
is_requested_review(repository, number),
timeout=CONTEXT_TIMEOUT_SECONDS,
):
raise HTTPException(status_code=404, detail="Review request not found")
return await asyncio.wait_for(
pull_review_detail(repository, number),
timeout=CONTEXT_TIMEOUT_SECONDS,
)
except HTTPException:
raise
except Exception:
return JSONResponse(
{"error": "Pull request review details are temporarily unavailable"},
status_code=503,
)