mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-08 03:10:26 +00:00
Extract shared run data, add list view for workflow runs tab
Shared data module at data/runs.ts powers both the kanban board (/runs) and the vertical list view (/workflows/:name/runs). List view shows status dot, repo, title, diff stats, CI badge, comments, and elapsed time in a compact row format. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
093eecdb6e
commit
0600b2ab59
4 changed files with 270 additions and 174 deletions
180
apps/arc-web/app/data/runs.ts
Normal file
180
apps/arc-web/app/data/runs.ts
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
export type CiStatus = "passing" | "failing" | "pending";
|
||||
|
||||
export interface RunItem {
|
||||
repo: string;
|
||||
title: string;
|
||||
number?: number;
|
||||
additions?: number;
|
||||
deletions?: number;
|
||||
ci?: CiStatus;
|
||||
elapsed?: string;
|
||||
elapsedWarning?: boolean;
|
||||
resources?: string;
|
||||
actionDisabled?: boolean;
|
||||
comments?: number;
|
||||
}
|
||||
|
||||
export type ColumnStatus = "working" | "pending" | "review" | "merge";
|
||||
|
||||
export interface RunWithStatus extends RunItem {
|
||||
status: ColumnStatus;
|
||||
statusLabel: string;
|
||||
}
|
||||
|
||||
export const columns: {
|
||||
id: ColumnStatus;
|
||||
name: string;
|
||||
accent: string;
|
||||
iconColor: string;
|
||||
iconType: "branch" | "pr";
|
||||
actions: string[];
|
||||
items: RunItem[];
|
||||
}[] = [
|
||||
{
|
||||
id: "working",
|
||||
name: "Working",
|
||||
accent: "bg-teal-500",
|
||||
iconColor: "text-teal-500",
|
||||
iconType: "branch",
|
||||
actions: ["Watch", "Steer"],
|
||||
items: [
|
||||
{
|
||||
repo: "api-server",
|
||||
title: "Add rate limiting to auth endpoints",
|
||||
resources: "4 CPU / 8 GB",
|
||||
elapsed: "7m",
|
||||
},
|
||||
{
|
||||
repo: "web-dashboard",
|
||||
title: "Migrate to React Router v7",
|
||||
resources: "8 CPU / 16 GB",
|
||||
elapsed: "2h 15m",
|
||||
},
|
||||
{
|
||||
repo: "cli-tools",
|
||||
title: "Fix config parsing for nested values",
|
||||
resources: "2 CPU / 4 GB",
|
||||
elapsed: "45m",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "pending",
|
||||
name: "Pending",
|
||||
accent: "bg-amber",
|
||||
iconColor: "text-amber",
|
||||
iconType: "branch",
|
||||
actions: ["Answer Question"],
|
||||
items: [
|
||||
{
|
||||
repo: "api-server",
|
||||
title: "Update OpenAPI spec for v3",
|
||||
additions: 567,
|
||||
deletions: 234,
|
||||
elapsed: "1h 12m",
|
||||
},
|
||||
{
|
||||
repo: "shared-types",
|
||||
title: "Add pipeline event types",
|
||||
additions: 145,
|
||||
deletions: 23,
|
||||
elapsed: "28m",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "review",
|
||||
name: "Verify",
|
||||
accent: "bg-mint",
|
||||
iconColor: "text-mint",
|
||||
iconType: "pr",
|
||||
actions: ["Resolve"],
|
||||
items: [
|
||||
{
|
||||
repo: "web-dashboard",
|
||||
title: "Add dark mode toggle",
|
||||
number: 889,
|
||||
additions: 234,
|
||||
deletions: 67,
|
||||
ci: "failing",
|
||||
elapsed: "35m",
|
||||
comments: 4,
|
||||
},
|
||||
{
|
||||
repo: "infrastructure",
|
||||
title: "Terraform module for Redis cluster",
|
||||
number: 156,
|
||||
additions: 412,
|
||||
deletions: 0,
|
||||
ci: "pending",
|
||||
elapsed: "12m",
|
||||
actionDisabled: true,
|
||||
comments: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "merge",
|
||||
name: "Merge",
|
||||
accent: "bg-teal-300",
|
||||
iconColor: "text-teal-300",
|
||||
iconType: "pr",
|
||||
actions: ["Merge"],
|
||||
items: [
|
||||
{
|
||||
repo: "api-server",
|
||||
title: "Implement webhook retry logic",
|
||||
number: 1249,
|
||||
additions: 189,
|
||||
deletions: 45,
|
||||
ci: "passing",
|
||||
elapsed: "3d",
|
||||
elapsedWarning: true,
|
||||
comments: 7,
|
||||
},
|
||||
{
|
||||
repo: "cli-tools",
|
||||
title: "Add --verbose flag to run command",
|
||||
number: 430,
|
||||
additions: 56,
|
||||
deletions: 12,
|
||||
ci: "passing",
|
||||
elapsed: "1h 5m",
|
||||
comments: 2,
|
||||
},
|
||||
{
|
||||
repo: "shared-types",
|
||||
title: "Export utility type helpers",
|
||||
number: 76,
|
||||
additions: 34,
|
||||
deletions: 8,
|
||||
ci: "passing",
|
||||
elapsed: "48m",
|
||||
comments: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export function allRunsFlat(): RunWithStatus[] {
|
||||
return columns.flatMap((col) =>
|
||||
col.items.map((item) => ({
|
||||
...item,
|
||||
status: col.id,
|
||||
statusLabel: col.name,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
export const statusColors: Record<ColumnStatus, { dot: string; text: string }> = {
|
||||
working: { dot: "bg-teal-500", text: "text-teal-500" },
|
||||
pending: { dot: "bg-amber", text: "text-amber" },
|
||||
review: { dot: "bg-mint", text: "text-mint" },
|
||||
merge: { dot: "bg-teal-300", text: "text-teal-300" },
|
||||
};
|
||||
|
||||
export const ciConfig: Record<CiStatus, { label: string; dot: string; text: string }> = {
|
||||
passing: { label: "Passing", dot: "bg-mint", text: "text-mint" },
|
||||
failing: { label: "Changes needed", dot: "bg-coral", text: "text-coral" },
|
||||
pending: { label: "Pending", dot: "bg-amber", text: "text-amber" },
|
||||
};
|
||||
|
|
@ -45,8 +45,9 @@ export default function AppShell() {
|
|||
const currentNav = navigation.find((item) => pathname.startsWith(item.href));
|
||||
const title = currentNav?.name ?? "";
|
||||
const lastMatch = matches[matches.length - 1];
|
||||
const headerExtra = (lastMatch?.handle as { headerExtra?: React.ReactNode })
|
||||
?.headerExtra;
|
||||
const handle = lastMatch?.handle as { headerExtra?: React.ReactNode; wide?: boolean } | undefined;
|
||||
const headerExtra = handle?.headerExtra;
|
||||
const maxWidth = handle?.wide ? "max-w-7xl" : "max-w-5xl";
|
||||
|
||||
return (
|
||||
<div className="min-h-full">
|
||||
|
|
@ -187,13 +188,15 @@ export default function AppShell() {
|
|||
</Disclosure>
|
||||
|
||||
<header className="relative bg-navy-800 after:pointer-events-none after:absolute after:inset-x-0 after:inset-y-0 after:bottom-0 after:border-y after:border-white/10">
|
||||
<div className="flex items-center px-4 py-4 sm:px-6 lg:px-8">
|
||||
<h1 className="text-lg/6 font-semibold text-white">{title}</h1>
|
||||
{headerExtra && <div className="ml-auto">{headerExtra}</div>}
|
||||
<div className={`mx-auto ${maxWidth} px-4 py-4 sm:px-6 lg:px-8`}>
|
||||
<div className="flex items-center">
|
||||
<h1 className="text-lg/6 font-semibold text-white">{title}</h1>
|
||||
{headerExtra && <div className="ml-auto">{headerExtra}</div>}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<div className="px-4 py-6 sm:px-6 lg:px-8">
|
||||
<div className={`mx-auto ${maxWidth} px-4 py-6 sm:px-6 lg:px-8`}>
|
||||
<Outlet />
|
||||
</div>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { columns, ciConfig } from "../data/runs";
|
||||
import type { CiStatus, RunItem } from "../data/runs";
|
||||
import type { Route } from "./+types/pipelines";
|
||||
|
||||
export function meta({}: Route.MetaArgs) {
|
||||
|
|
@ -31,18 +33,10 @@ function GitPullRequestIcon({ className }: { className?: string }) {
|
|||
);
|
||||
}
|
||||
|
||||
type CiStatus = "passing" | "failing" | "pending";
|
||||
|
||||
const ciConfig: Record<CiStatus, { label: string; dot: string; text: string }> =
|
||||
{
|
||||
passing: { label: "Passing", dot: "bg-mint", text: "text-mint" },
|
||||
failing: {
|
||||
label: "Changes needed",
|
||||
dot: "bg-coral",
|
||||
text: "text-coral",
|
||||
},
|
||||
pending: { label: "Pending", dot: "bg-amber", text: "text-amber" },
|
||||
};
|
||||
const iconMap = {
|
||||
branch: GitBranchIcon,
|
||||
pr: GitPullRequestIcon,
|
||||
};
|
||||
|
||||
function CiBadge({ status }: { status: CiStatus }) {
|
||||
const config = ciConfig[status];
|
||||
|
|
@ -54,157 +48,6 @@ function CiBadge({ status }: { status: CiStatus }) {
|
|||
);
|
||||
}
|
||||
|
||||
interface PullRequest {
|
||||
repo: string;
|
||||
title: string;
|
||||
number?: number;
|
||||
additions?: number;
|
||||
deletions?: number;
|
||||
ci?: CiStatus;
|
||||
elapsed?: string;
|
||||
elapsedWarning?: boolean;
|
||||
resources?: string;
|
||||
actionDisabled?: boolean;
|
||||
comments?: number;
|
||||
}
|
||||
|
||||
interface Column {
|
||||
id: string;
|
||||
name: string;
|
||||
accent: string;
|
||||
iconColor: string;
|
||||
icon: React.ComponentType<{ className?: string }>;
|
||||
actions?: string[];
|
||||
items: PullRequest[];
|
||||
}
|
||||
|
||||
const columns: Column[] = [
|
||||
{
|
||||
id: "working",
|
||||
name: "Working",
|
||||
accent: "bg-teal-500",
|
||||
iconColor: "text-teal-500",
|
||||
icon: GitBranchIcon,
|
||||
actions: ["Watch", "Steer"],
|
||||
items: [
|
||||
{
|
||||
repo: "api-server",
|
||||
title: "Add rate limiting to auth endpoints",
|
||||
resources: "4 CPU / 8 GB",
|
||||
elapsed: "7m",
|
||||
},
|
||||
{
|
||||
repo: "web-dashboard",
|
||||
title: "Migrate to React Router v7",
|
||||
resources: "8 CPU / 16 GB",
|
||||
elapsed: "2h 15m",
|
||||
},
|
||||
{
|
||||
repo: "cli-tools",
|
||||
title: "Fix config parsing for nested values",
|
||||
resources: "2 CPU / 4 GB",
|
||||
elapsed: "45m",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "pending",
|
||||
name: "Pending",
|
||||
accent: "bg-amber",
|
||||
iconColor: "text-amber",
|
||||
icon: GitBranchIcon,
|
||||
actions: ["Answer Question"],
|
||||
items: [
|
||||
{
|
||||
repo: "api-server",
|
||||
title: "Update OpenAPI spec for v3",
|
||||
additions: 567,
|
||||
deletions: 234,
|
||||
elapsed: "1h 12m",
|
||||
},
|
||||
{
|
||||
repo: "shared-types",
|
||||
title: "Add pipeline event types",
|
||||
additions: 145,
|
||||
deletions: 23,
|
||||
elapsed: "28m",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "review",
|
||||
name: "Verify",
|
||||
accent: "bg-mint",
|
||||
iconColor: "text-mint",
|
||||
icon: GitPullRequestIcon,
|
||||
actions: ["Resolve"],
|
||||
items: [
|
||||
{
|
||||
repo: "web-dashboard",
|
||||
title: "Add dark mode toggle",
|
||||
number: 889,
|
||||
additions: 234,
|
||||
deletions: 67,
|
||||
ci: "failing",
|
||||
elapsed: "35m",
|
||||
comments: 4,
|
||||
},
|
||||
{
|
||||
repo: "infrastructure",
|
||||
title: "Terraform module for Redis cluster",
|
||||
number: 156,
|
||||
additions: 412,
|
||||
deletions: 0,
|
||||
ci: "pending",
|
||||
elapsed: "12m",
|
||||
actionDisabled: true,
|
||||
comments: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "merge",
|
||||
name: "Merge",
|
||||
accent: "bg-teal-300",
|
||||
iconColor: "text-teal-300",
|
||||
icon: GitPullRequestIcon,
|
||||
actions: ["Merge"],
|
||||
items: [
|
||||
{
|
||||
repo: "api-server",
|
||||
title: "Implement webhook retry logic",
|
||||
number: 1249,
|
||||
additions: 189,
|
||||
deletions: 45,
|
||||
ci: "passing",
|
||||
elapsed: "3d",
|
||||
elapsedWarning: true,
|
||||
comments: 7,
|
||||
},
|
||||
{
|
||||
repo: "cli-tools",
|
||||
title: "Add --verbose flag to run command",
|
||||
number: 430,
|
||||
additions: 56,
|
||||
deletions: 12,
|
||||
ci: "passing",
|
||||
elapsed: "1h 5m",
|
||||
comments: 2,
|
||||
},
|
||||
{
|
||||
repo: "shared-types",
|
||||
title: "Export utility type helpers",
|
||||
number: 76,
|
||||
additions: 34,
|
||||
deletions: 8,
|
||||
ci: "passing",
|
||||
elapsed: "48m",
|
||||
comments: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const totalCards = columns.reduce((sum, col) => sum + col.items.length, 0);
|
||||
const totalPrs = columns.reduce(
|
||||
(sum, col) => sum + col.items.filter((item) => item.number != null).length,
|
||||
|
|
@ -212,6 +55,7 @@ const totalPrs = columns.reduce(
|
|||
);
|
||||
|
||||
export const handle = {
|
||||
wide: true,
|
||||
headerExtra: (
|
||||
<div className="flex items-center gap-4 font-mono text-xs text-ice-300">
|
||||
<span>
|
||||
|
|
@ -230,7 +74,7 @@ function PrCard({
|
|||
iconColor,
|
||||
actions,
|
||||
}: {
|
||||
pr: PullRequest;
|
||||
pr: RunItem;
|
||||
icon: React.ComponentType<{ className?: string }>;
|
||||
iconColor: string;
|
||||
actions?: string[];
|
||||
|
|
@ -332,7 +176,8 @@ function PrCard({
|
|||
);
|
||||
}
|
||||
|
||||
function BoardColumn({ column }: { column: Column }) {
|
||||
function BoardColumn({ column }: { column: (typeof columns)[number] }) {
|
||||
const Icon = iconMap[column.iconType];
|
||||
return (
|
||||
<div className="flex min-w-[280px] flex-1 flex-col">
|
||||
<div className="mb-4 flex items-center gap-3">
|
||||
|
|
@ -350,7 +195,7 @@ function BoardColumn({ column }: { column: Column }) {
|
|||
<PrCard
|
||||
key={`${pr.repo}-${pr.number ?? pr.title}`}
|
||||
pr={pr}
|
||||
icon={column.icon}
|
||||
icon={Icon}
|
||||
iconColor={column.iconColor}
|
||||
actions={column.actions}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,71 @@
|
|||
export default function WorkflowRuns() {
|
||||
return <p className="text-sm text-navy-600">Recent runs will appear here.</p>;
|
||||
import { allRunsFlat, ciConfig, statusColors } from "../data/runs";
|
||||
import type { CiStatus, RunWithStatus } from "../data/runs";
|
||||
|
||||
const runs = allRunsFlat();
|
||||
|
||||
function CiBadge({ status }: { status: CiStatus }) {
|
||||
const config = ciConfig[status];
|
||||
return (
|
||||
<span className={`inline-flex items-center gap-1.5 font-mono text-xs ${config.text}`}>
|
||||
<span className={`size-1.5 rounded-full ${config.dot}`} />
|
||||
{config.label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function RunRow({ run }: { run: RunWithStatus }) {
|
||||
const colors = statusColors[run.status];
|
||||
return (
|
||||
<div className="flex items-center gap-4 rounded-lg border border-white/[0.06] bg-navy-800/80 px-4 py-3 transition-all duration-200 hover:border-white/[0.12] hover:bg-navy-800">
|
||||
<span className="flex items-center gap-2 min-w-[80px]">
|
||||
<span className={`size-2 shrink-0 rounded-full ${colors.dot}`} />
|
||||
<span className={`text-xs font-medium ${colors.text}`}>{run.statusLabel}</span>
|
||||
</span>
|
||||
|
||||
<span className="font-mono text-xs font-medium text-teal-500 min-w-[110px]">
|
||||
{run.repo}
|
||||
{run.number != null && (
|
||||
<span className="text-navy-600"> #{run.number}</span>
|
||||
)}
|
||||
</span>
|
||||
|
||||
<span className="flex-1 truncate text-sm text-ice-100">{run.title}</span>
|
||||
|
||||
{run.additions != null && run.deletions != null && (
|
||||
<span className="hidden sm:flex items-center gap-2 font-mono text-xs">
|
||||
<span className="text-mint">+{run.additions.toLocaleString()}</span>
|
||||
<span className="text-coral">-{run.deletions.toLocaleString()}</span>
|
||||
</span>
|
||||
)}
|
||||
|
||||
{run.ci != null && (
|
||||
<span className="hidden md:block min-w-[120px]">
|
||||
<CiBadge status={run.ci} />
|
||||
</span>
|
||||
)}
|
||||
|
||||
{run.comments != null && run.comments > 0 && (
|
||||
<span className="hidden sm:inline-flex items-center gap-1 font-mono text-xs text-navy-600">
|
||||
<svg viewBox="0 0 16 16" fill="currentColor" className="size-3" aria-hidden="true">
|
||||
<path d="M1 2.75C1 1.784 1.784 1 2.75 1h10.5c.966 0 1.75.784 1.75 1.75v7.5A1.75 1.75 0 0 1 13.25 12H9.06l-2.573 2.573A1.458 1.458 0 0 1 4 13.543V12H2.75A1.75 1.75 0 0 1 1 10.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h4.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z" />
|
||||
</svg>
|
||||
{run.comments}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<span className={`font-mono text-xs min-w-[60px] text-right ${run.elapsedWarning ? "text-amber" : "text-navy-600"}`}>
|
||||
{run.elapsed}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function WorkflowRuns() {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{runs.map((run) => (
|
||||
<RunRow key={`${run.repo}-${run.number ?? run.title}`} run={run} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue