import { useMemo, useState, useEffect } from "react"; import { IconCheck, IconX, IconClock } from "@tabler/icons-react"; import type { CheckRun } from "@/lib/types/github"; import { CollapsibleSection, AddToContextButton, formatDuration, formatElapsed } from "./pr-shared"; function CheckStatusIcon({ check }: { check: CheckRun }) { const value = check.conclusion || check.status; if (value === "success") return ; if (value === "failure" || value === "timed_out") return ; if (value === "in_progress" || value === "queued") return ; return ; } function conclusionLabel(check: CheckRun): string | null { const c = check.conclusion; if (!c || c === "success" || c === "failure") return c || null; const labels: Record = { timed_out: "timed out", cancelled: "cancelled", action_required: "action required", skipped: "skipped", neutral: "neutral", stale: "stale", }; return labels[c] ?? c; } function checkDurationText(check: CheckRun): string | null { if (check.started_at && check.completed_at) { const ms = new Date(check.completed_at).getTime() - new Date(check.started_at).getTime(); if (ms < 1000) return null; return formatDuration(check.started_at, check.completed_at); } if (check.started_at && !check.completed_at) return `${formatElapsed(check.started_at)} running`; return null; } function isFailedCheck(check: CheckRun): boolean { return check.conclusion === "failure" || check.conclusion === "timed_out"; } function buildCheckMessage(check: CheckRun): string { const parts = [`CI check **${check.name}** failed (${check.conclusion}).`]; if (check.output) parts.push(check.output); if (check.html_url) parts.push(`Check URL: ${check.html_url}`); parts.push("Please investigate and fix this failing check."); return parts.join("\n\n"); } function buildAllFailedMessage(checks: CheckRun[]): string { const failed = checks.filter(isFailedCheck); const parts = [`### ${failed.length} CI Check${failed.length !== 1 ? "s" : ""} Failed`, ""]; for (const check of failed) { parts.push(`**${check.name}** — ${check.conclusion}`); if (check.output) parts.push(check.output); if (check.html_url) parts.push(`URL: ${check.html_url}`); parts.push(""); } parts.push("Please investigate and fix these failing checks."); return parts.join("\n"); } function formatSectionSummary(checks: CheckRun[]): string { const failed = checks.filter(isFailedCheck).length; const passed = checks.filter((c) => c.conclusion === "success").length; const pending = checks.length - failed - passed; const parts: string[] = []; if (failed > 0) parts.push(`${failed} failed`); if (passed > 0) parts.push(`${passed} passed`); if (pending > 0) parts.push(`${pending} pending`); return parts.join(", "); } export function ChecksSection({ checks, onAddAsContext, }: { checks: CheckRun[]; onAddAsContext: (message: string) => void; }) { const hasRunning = useMemo(() => checks.some((c) => c.started_at && !c.completed_at), [checks]); const [, setTick] = useState(0); useEffect(() => { if (!hasRunning) return; const id = setInterval(() => setTick((t) => t + 1), 1_000); return () => clearInterval(id); }, [hasRunning]); const summary = checks.length > 0 ? ` \u2014 ${formatSectionSummary(checks)}` : ""; const hasFailed = checks.some(isFailedCheck); return ( onAddAsContext(buildAllFailedMessage(checks)) : undefined} addAllLabel="Add all failed checks to chat context" > {checks.length === 0 &&

No checks

} {checks.map((check) => { const label = conclusionLabel(check); const duration = checkDurationText(check); const checkKey = `${check.name}-${check.html_url}-${check.source}-${check.started_at ?? ""}`; return (
{check.html_url ? ( {check.name} ) : ( {check.name} )} {label && {label}} {duration && ( {duration} )} {isFailedCheck(check) && (
onAddAsContext(buildCheckMessage(check))} />
)}
); })}
); }