Fixed a few stupid mistakes
This commit is contained in:
@@ -11,10 +11,12 @@ from fastapi import Depends, FastAPI, Header, HTTPException, Request, status
|
||||
from fastapi.exception_handlers import http_exception_handler
|
||||
from fastapi.responses import HTMLResponse
|
||||
from starlette.exceptions import HTTPException as StarletteHTTPException
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from gitea_codex_bot.config import Settings, get_settings
|
||||
from gitea_codex_bot.db import get_session
|
||||
from gitea_codex_bot.models import JobStatus, ReviewJob
|
||||
from gitea_codex_bot.services.commands import parse_command
|
||||
from gitea_codex_bot.services.gitea import GiteaClient
|
||||
from gitea_codex_bot.services.jobs import cooldown_remaining_seconds, enqueue_job, persist_webhook_event
|
||||
@@ -147,6 +149,13 @@ def _load_repo_review_config_for_pr(gitea: GiteaClient, repo: str, pr_number: in
|
||||
return parse_repo_review_config_text(cfg_text, configured=True), head_sha
|
||||
|
||||
|
||||
def _resolve_pr_head_sha(gitea: GiteaClient, repo: str, pr_number: int, fallback: str) -> str:
|
||||
try:
|
||||
return gitea.get_pull_request(repo, pr_number).head_sha
|
||||
except Exception:
|
||||
return fallback
|
||||
|
||||
|
||||
def _render_landing_page() -> str:
|
||||
return """<!doctype html>
|
||||
<html lang="en">
|
||||
@@ -164,6 +173,8 @@ def _render_landing_page() -> str:
|
||||
<p class="mt-4 text-base leading-7 text-slate-300">This endpoint powers automated pull request review workflows for Gitea. It validates signed webhook events, queues review jobs, and posts structured feedback back to pull requests.</p>
|
||||
<div class="mt-8 flex flex-wrap gap-3 text-sm">
|
||||
<button id="health-button" type="button" class="rounded-lg border border-slate-700 bg-slate-800/80 px-3 py-2 text-slate-200 transition hover:border-slate-500 hover:bg-slate-700">Health: <code>/healthz</code></button>
|
||||
<button id="failure-button" type="button" class="rounded-lg border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-amber-200 transition hover:border-amber-400 hover:bg-amber-500/20">Latest failure: <code>/healthz/latest-failure</code></button>
|
||||
<button id="job-button" type="button" class="rounded-lg border border-cyan-500/40 bg-cyan-500/10 px-3 py-2 text-cyan-200 transition hover:border-cyan-400 hover:bg-cyan-500/20">Latest job: <code>/healthz/latest-job</code></button>
|
||||
<span class="rounded-lg border border-slate-700 bg-slate-800/80 px-3 py-2 text-slate-200">Webhook: <code>POST /webhook/gitea</code></span>
|
||||
</div>
|
||||
</section>
|
||||
@@ -179,6 +190,8 @@ def _render_landing_page() -> str:
|
||||
</div>
|
||||
<script>
|
||||
const healthButton = document.getElementById("health-button");
|
||||
const failureButton = document.getElementById("failure-button");
|
||||
const jobButton = document.getElementById("job-button");
|
||||
const healthModal = document.getElementById("health-modal");
|
||||
const closeModal = document.getElementById("close-modal");
|
||||
const healthResult = document.getElementById("health-result");
|
||||
@@ -196,6 +209,57 @@ def _render_landing_page() -> str:
|
||||
}
|
||||
}
|
||||
|
||||
async function loadLatestFailure() {
|
||||
healthResult.textContent = "Loading...";
|
||||
try {
|
||||
const response = await fetch("/healthz/latest-failure", { headers: { Accept: "application/json" } });
|
||||
const payload = await response.json();
|
||||
if (!payload.has_failed_job) {
|
||||
healthResult.textContent = "No failed jobs found.";
|
||||
return;
|
||||
}
|
||||
const failedAt = payload.failed_at ? payload.failed_at : "unknown";
|
||||
const errorText = payload.error ? payload.error : "unknown";
|
||||
healthResult.textContent =
|
||||
"Latest failed job #" + payload.job_id +
|
||||
" | " + payload.repo + "#" + payload.pr_number +
|
||||
" | command=" + payload.command +
|
||||
" | commit=" + payload.head_sha.slice(0, 7) +
|
||||
" | failed_at=" + failedAt +
|
||||
" | error=" + errorText;
|
||||
} catch (_error) {
|
||||
healthResult.textContent = "Could not load latest failure output.";
|
||||
}
|
||||
}
|
||||
|
||||
async function loadLatestJob() {
|
||||
healthResult.textContent = "Loading...";
|
||||
try {
|
||||
const response = await fetch("/healthz/latest-job", { headers: { Accept: "application/json" } });
|
||||
const payload = await response.json();
|
||||
if (!payload.has_job) {
|
||||
healthResult.textContent = "No jobs found yet.";
|
||||
return;
|
||||
}
|
||||
const startedAt = payload.started_at ? payload.started_at : "not started";
|
||||
const finishedAt = payload.finished_at ? payload.finished_at : "not finished";
|
||||
const errorText = payload.error ? payload.error : "none";
|
||||
const summary = payload.result_summary ? payload.result_summary : "none";
|
||||
healthResult.textContent =
|
||||
"Latest job #" + payload.job_id +
|
||||
" | " + payload.repo + "#" + payload.pr_number +
|
||||
" | command=" + payload.command +
|
||||
" | status=" + payload.job_status +
|
||||
" | commit=" + payload.head_sha.slice(0, 7) +
|
||||
" | started_at=" + startedAt +
|
||||
" | finished_at=" + finishedAt +
|
||||
" | error=" + errorText +
|
||||
" | summary=" + summary;
|
||||
} catch (_error) {
|
||||
healthResult.textContent = "Could not load latest job output.";
|
||||
}
|
||||
}
|
||||
|
||||
function showModal() {
|
||||
healthModal.classList.remove("hidden");
|
||||
healthModal.classList.add("flex");
|
||||
@@ -210,6 +274,14 @@ def _render_landing_page() -> str:
|
||||
showModal();
|
||||
await loadHealth();
|
||||
});
|
||||
failureButton.addEventListener("click", async function () {
|
||||
showModal();
|
||||
await loadLatestFailure();
|
||||
});
|
||||
jobButton.addEventListener("click", async function () {
|
||||
showModal();
|
||||
await loadLatestJob();
|
||||
});
|
||||
|
||||
closeModal.addEventListener("click", hideModal);
|
||||
healthModal.addEventListener("click", function (event) {
|
||||
@@ -264,6 +336,54 @@ def healthz(settings: Settings = Depends(get_settings)) -> dict[str, str]:
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@app.get("/healthz/latest-failure")
|
||||
def healthz_latest_failure(session: Session = Depends(get_session)) -> dict[str, Any]:
|
||||
failed_job = session.execute(
|
||||
select(ReviewJob).where(ReviewJob.status == JobStatus.failed).order_by(ReviewJob.created_at.desc(), ReviewJob.id.desc()).limit(1)
|
||||
).scalar_one_or_none()
|
||||
if not failed_job:
|
||||
return {"status": "ok", "has_failed_job": False}
|
||||
return {
|
||||
"status": "ok",
|
||||
"has_failed_job": True,
|
||||
"job_id": failed_job.id,
|
||||
"repo": failed_job.repo,
|
||||
"pr_number": failed_job.pr_number,
|
||||
"command": failed_job.command,
|
||||
"head_sha": failed_job.head_sha,
|
||||
"error": failed_job.last_error or "",
|
||||
"failed_at": failed_job.finished_at.isoformat() if failed_job.finished_at else None,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/healthz/latest-job")
|
||||
def healthz_latest_job(session: Session = Depends(get_session)) -> dict[str, Any]:
|
||||
latest_job = session.execute(select(ReviewJob).order_by(ReviewJob.created_at.desc(), ReviewJob.id.desc()).limit(1)).scalar_one_or_none()
|
||||
if not latest_job:
|
||||
return {"status": "ok", "has_job": False}
|
||||
|
||||
result_summary = ""
|
||||
if isinstance(latest_job.result_json, dict):
|
||||
summary = latest_job.result_json.get("summary")
|
||||
if isinstance(summary, str):
|
||||
result_summary = summary
|
||||
return {
|
||||
"status": "ok",
|
||||
"has_job": True,
|
||||
"job_id": latest_job.id,
|
||||
"repo": latest_job.repo,
|
||||
"pr_number": latest_job.pr_number,
|
||||
"command": latest_job.command,
|
||||
"head_sha": latest_job.head_sha,
|
||||
"job_status": latest_job.status.value if hasattr(latest_job.status, "value") else str(latest_job.status),
|
||||
"error": latest_job.last_error or "",
|
||||
"result_summary": result_summary,
|
||||
"created_at": latest_job.created_at.isoformat() if latest_job.created_at else None,
|
||||
"started_at": latest_job.started_at.isoformat() if latest_job.started_at else None,
|
||||
"finished_at": latest_job.finished_at.isoformat() if latest_job.finished_at else None,
|
||||
}
|
||||
|
||||
|
||||
@app.post("/webhook/gitea")
|
||||
async def gitea_webhook(
|
||||
request: Request,
|
||||
@@ -334,6 +454,7 @@ async def gitea_webhook(
|
||||
|
||||
gitea = GiteaClient(settings)
|
||||
if parsed_command.name in {"review", "rerun"}:
|
||||
head_sha = _resolve_pr_head_sha(gitea, repo, pr_number, head_sha)
|
||||
repo_cfg: RepoReviewConfig | None = None
|
||||
try:
|
||||
repo_cfg, resolved_head_sha = _load_repo_review_config_for_pr(gitea, repo, pr_number)
|
||||
@@ -341,10 +462,7 @@ async def gitea_webhook(
|
||||
except Exception:
|
||||
repo_cfg = None
|
||||
if head_sha == "unknown":
|
||||
try:
|
||||
head_sha = gitea.get_pull_request(repo, pr_number).head_sha
|
||||
except Exception:
|
||||
pass
|
||||
head_sha = _resolve_pr_head_sha(gitea, repo, pr_number, head_sha)
|
||||
if repo_cfg and not repo_cfg.enabled:
|
||||
gitea.post_issue_comment(repo, pr_number, format_disabled_ack())
|
||||
return {"accepted": True, "reason": "review disabled by repo config"}
|
||||
|
||||
Reference in New Issue
Block a user