diff --git a/apps/arc-web/app/data/runs.ts b/apps/arc-web/app/data/runs.ts index 16a3b6d97..ed703ab2a 100644 --- a/apps/arc-web/app/data/runs.ts +++ b/apps/arc-web/app/data/runs.ts @@ -1,5 +1,13 @@ export type CiStatus = "passing" | "failing" | "pending"; +export type CheckStatus = "success" | "failure" | "skipped" | "pending" | "queued"; + +export interface CheckRun { + name: string; + status: CheckStatus; + duration?: string; +} + export interface RunItem { id: string; repo: string; @@ -8,12 +16,13 @@ export interface RunItem { number?: number; additions?: number; deletions?: number; - ci?: CiStatus; + checks?: CheckRun[]; elapsed?: string; elapsedWarning?: boolean; resources?: string; actionDisabled?: boolean; comments?: number; + question?: string; } export type ColumnStatus = "working" | "pending" | "review" | "merge"; @@ -23,6 +32,12 @@ export interface RunWithStatus extends RunItem { statusLabel: string; } +export function deriveCiStatus(checks: CheckRun[]): CiStatus { + if (checks.some((c) => c.status === "failure")) return "failing"; + if (checks.some((c) => c.status === "pending" || c.status === "queued")) return "pending"; + return "passing"; +} + export const columns: { id: ColumnStatus; name: string; @@ -82,6 +97,7 @@ export const columns: { additions: 567, deletions: 234, elapsed: "1h 12m", + question: "Accept or push for another round?", }, { id: "run-5", @@ -91,6 +107,7 @@ export const columns: { additions: 145, deletions: 23, elapsed: "28m", + question: "Proceed from investigation to fix?", }, ], }, @@ -110,7 +127,15 @@ export const columns: { number: 889, additions: 234, deletions: 67, - ci: "failing", + checks: [ + { name: "lint", status: "success", duration: "23s" }, + { name: "typecheck", status: "success", duration: "1m 12s" }, + { name: "unit-tests", status: "success", duration: "2m 34s" }, + { name: "integration-tests", status: "failure", duration: "4m 56s" }, + { name: "e2e / chrome", status: "failure", duration: "3m 2s" }, + { name: "build", status: "success", duration: "1m 45s" }, + { name: "coverage", status: "skipped" }, + ], elapsed: "35m", comments: 4, }, @@ -122,7 +147,13 @@ export const columns: { number: 156, additions: 412, deletions: 0, - ci: "pending", + checks: [ + { name: "lint", status: "success", duration: "18s" }, + { name: "typecheck", status: "success", duration: "56s" }, + { name: "unit-tests", status: "pending" }, + { name: "integration-tests", status: "queued" }, + { name: "build", status: "pending" }, + ], elapsed: "12m", actionDisabled: true, comments: 1, @@ -145,7 +176,20 @@ export const columns: { number: 1249, additions: 189, deletions: 45, - ci: "passing", + checks: [ + { name: "lint", status: "success", duration: "21s" }, + { name: "typecheck", status: "success", duration: "1m 8s" }, + { name: "unit-tests", status: "success", duration: "3m 12s" }, + { name: "integration-tests", status: "success", duration: "5m 34s" }, + { name: "e2e / chrome", status: "success", duration: "4m 22s" }, + { name: "e2e / firefox", status: "success", duration: "4m 45s" }, + { name: "build", status: "success", duration: "2m 1s" }, + { name: "deploy-preview", status: "success", duration: "1m 33s" }, + { name: "security-scan", status: "skipped" }, + { name: "performance", status: "success", duration: "2m 18s" }, + { name: "bundle-size", status: "success", duration: "34s" }, + { name: "accessibility", status: "success", duration: "1m 12s" }, + ], elapsed: "3d", elapsedWarning: true, comments: 7, @@ -158,7 +202,14 @@ export const columns: { number: 430, additions: 56, deletions: 12, - ci: "passing", + checks: [ + { name: "lint", status: "success", duration: "15s" }, + { name: "typecheck", status: "success", duration: "48s" }, + { name: "unit-tests", status: "success", duration: "1m 56s" }, + { name: "build", status: "success", duration: "1m 22s" }, + { name: "coverage", status: "success", duration: "2m 4s" }, + { name: "bundle-size", status: "skipped" }, + ], elapsed: "1h 5m", comments: 2, }, @@ -170,7 +221,12 @@ export const columns: { number: 76, additions: 34, deletions: 8, - ci: "passing", + checks: [ + { name: "lint", status: "success", duration: "12s" }, + { name: "typecheck", status: "success", duration: "34s" }, + { name: "unit-tests", status: "success", duration: "1m 15s" }, + { name: "build", status: "success", duration: "58s" }, + ], elapsed: "48m", comments: 0, }, diff --git a/apps/arc-web/app/routes/pipelines.tsx b/apps/arc-web/app/routes/pipelines.tsx index e9bbd35f3..17fcb7083 100644 --- a/apps/arc-web/app/routes/pipelines.tsx +++ b/apps/arc-web/app/routes/pipelines.tsx @@ -18,8 +18,8 @@ import { arrayMove, } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; -import { columns as staticColumns, ciConfig, statusColors } from "../data/runs"; -import type { CiStatus, RunItem, RunWithStatus } from "../data/runs"; +import { columns as staticColumns, ciConfig, statusColors, deriveCiStatus } from "../data/runs"; +import type { CiStatus, CheckRun, CheckStatus, RunItem, RunWithStatus } from "../data/runs"; import type { Route } from "./+types/pipelines"; export function meta({}: Route.MetaArgs) { @@ -58,13 +58,131 @@ const iconMap = { pr: GitPullRequestIcon, }; -function CiBadge({ status }: { status: CiStatus }) { - const config = ciConfig[status]; +function CheckStatusIcon({ status }: { status: CheckStatus }) { + switch (status) { + case "success": + return ( + + ); + case "failure": + return ( + + ); + case "pending": + return ( + + + + ); + case "queued": + return ( + + + + ); + case "skipped": + return ( + + ); + } +} + +function SummaryStatusIcon({ status }: { status: CiStatus }) { + switch (status) { + case "passing": + return ( + + ); + case "failing": + return ( + + ); + case "pending": + return ( + + ); + } +} + +function summarizeChecks(checks: CheckRun[]) { + const counts = { + success: checks.filter((c) => c.status === "success").length, + failure: checks.filter((c) => c.status === "failure").length, + skipped: checks.filter((c) => c.status === "skipped").length, + pending: checks.filter((c) => c.status === "pending" || c.status === "queued").length, + }; + + let summary: string; + const parts: string[] = []; + + if (counts.failure > 0) { + summary = `${counts.failure} failing check${counts.failure !== 1 ? "s" : ""}`; + if (counts.success > 0) parts.push(`${counts.success} success`); + if (counts.skipped > 0) parts.push(`${counts.skipped} skipped`); + if (counts.pending > 0) parts.push(`${counts.pending} pending`); + } else if (counts.pending > 0) { + summary = `${counts.pending} check${counts.pending !== 1 ? "s" : ""} pending`; + if (counts.success > 0) parts.push(`${counts.success} success`); + if (counts.skipped > 0) parts.push(`${counts.skipped} skipped`); + } else { + summary = "All checks passing"; + if (counts.skipped > 0) { + parts.push(`${counts.skipped} skipped`); + parts.push(`${counts.success} success`); + } + } + + return { summary, detail: parts.join(", ") }; +} + +function ChecksStatus({ checks }: { checks: CheckRun[] }) { + const [expanded, setExpanded] = useState(false); + const overallStatus = deriveCiStatus(checks); + const config = ciConfig[overallStatus]; + const { summary, detail } = summarizeChecks(checks); + return ( - - - {config.label} - +
{pr.title}
- {(pr.additions != null || pr.resources != null || pr.ci != null || pr.elapsed != null) && ( + {(pr.additions != null || pr.resources != null || pr.elapsed != null) && ({pr.question}
+ )} + + {actions != null && actions.length > 0 && (