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} - +
{ e.preventDefault(); e.stopPropagation(); }} + > + +
+
+
+ {checks.map((check) => ( +
+ + {check.name} + + {check.duration ?? (check.status === "skipped" ? "skipped" : check.status === "queued" ? "queued" : "")} + +
+ ))} +
+
+
+
); } @@ -115,7 +233,7 @@ function PrCard({

{pr.title}

- {(pr.additions != null || pr.resources != null || pr.ci != null || pr.elapsed != null) && ( + {(pr.additions != null || pr.resources != null || pr.elapsed != null) && (
{pr.resources != null && ( {pr.resources} @@ -144,7 +262,13 @@ function PrCard({
)} - {(actions != null && actions.length > 0 || pr.ci != null) && ( + {pr.checks != null && } + + {pr.question != null && ( +

{pr.question}

+ )} + + {actions != null && actions.length > 0 && (
{actions?.map((label) => (
)} @@ -299,7 +422,7 @@ function RunRow({ run }: { run: RunWithStatus }) { <> #{run.number} - {run.ci != null && } + {run.checks != null && } )} diff --git a/apps/arc-web/app/routes/workflow-runs.tsx b/apps/arc-web/app/routes/workflow-runs.tsx index 94873edd1..482bd9de7 100644 --- a/apps/arc-web/app/routes/workflow-runs.tsx +++ b/apps/arc-web/app/routes/workflow-runs.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import { ChevronDownIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline"; import { Link } from "react-router"; -import { allRunsFlat, ciConfig, columns, statusColors } from "../data/runs"; +import { allRunsFlat, ciConfig, columns, deriveCiStatus, statusColors } from "../data/runs"; import type { ColumnStatus, RunWithStatus } from "../data/runs"; const runs = allRunsFlat(); @@ -49,7 +49,7 @@ function RunRow({ run }: { run: RunWithStatus }) { <> #{run.number} - {run.ci != null && } + {run.checks != null && } )}