diff --git a/apps/fabro-web/app/components/run-summary-panel.tsx b/apps/fabro-web/app/components/run-summary-panel.tsx index 21e2bc3e1..5d9c78ce4 100644 --- a/apps/fabro-web/app/components/run-summary-panel.tsx +++ b/apps/fabro-web/app/components/run-summary-panel.tsx @@ -1,14 +1,5 @@ import type { ReactNode } from "react"; -import { - BoltIcon, - ChatBubbleLeftEllipsisIcon, - Cog6ToothIcon, - CpuChipIcon, - QuestionMarkCircleIcon, - ServerIcon, -} from "@heroicons/react/20/solid"; import type { - Principal, Run, SandboxResources, SandboxState, @@ -20,6 +11,7 @@ import { formatCpuCores, formatUsdMicros, } from "../lib/format"; +import { principalDisplay } from "../lib/principal-display"; import { useRun, useRunArtifacts, useRunSandboxDetails } from "../lib/queries"; import { SANDBOX_STATE_DISPLAY } from "../lib/sandbox-state"; import { Tooltip } from "./ui"; @@ -53,62 +45,6 @@ function Cell({ label, children }: { label: string; children: ReactNode }) { ); } -interface CreatedByDisplay { - glyph: ReactNode; - label: string; -} - -function principalGlyph(icon: ReactNode) { - return ( - - {icon} - - ); -} - -function createdByDisplay(actor: Principal): CreatedByDisplay { - switch (actor.kind) { - case "user": { - let glyph: ReactNode; - if (actor.avatar_url) { - glyph = ( - - ); - } else { - const initial = actor.login.charAt(0).toUpperCase() || "?"; - glyph = ( - - {initial} - - ); - } - return { glyph, label: actor.login }; - } - case "agent": - return { glyph: principalGlyph(), label: "agent" }; - case "system": - return { glyph: principalGlyph(), label: "system" }; - case "slack": - return { - glyph: principalGlyph(), - label: "slack", - }; - case "webhook": - return { glyph: principalGlyph(), label: "webhook" }; - case "worker": - return { glyph: principalGlyph(), label: "worker" }; - case "anonymous": - return { - glyph: principalGlyph(), - label: "anonymous", - }; - } -} - export interface RunSummaryPanelViewProps { run: Run | null; runLoading: boolean; @@ -156,7 +92,7 @@ export function RunSummaryPanelView({ artifactsCount, artifactsLoading, }: RunSummaryPanelViewProps) { - const created = run?.created_by ? createdByDisplay(run.created_by) : null; + const created = run?.created_by ? principalDisplay(run.created_by) : null; const diff = run?.diff ?? null; const cost = formatUsdMicros(run?.billing?.total_usd_micros); diff --git a/apps/fabro-web/app/components/runs-list/run-table-row.tsx b/apps/fabro-web/app/components/runs-list/run-table-row.tsx index d1b328178..ca521fb92 100644 --- a/apps/fabro-web/app/components/runs-list/run-table-row.tsx +++ b/apps/fabro-web/app/components/runs-list/run-table-row.tsx @@ -3,9 +3,11 @@ import { Link } from "react-router"; import { ciConfig, columnStatusDisplay, deriveCiStatus } from "../../data/runs"; import type { RunWithStatus } from "../../data/runs"; import { formatRelativeTime } from "../../lib/format"; +import { principalDisplay } from "../../lib/principal-display"; import { InlineMarkdown } from "../inline-markdown"; import { PullRequestChip } from "../pull-request-chip"; import { SizeChip } from "../size-chip"; +import { Tooltip } from "../ui"; import { RowActionsMenu } from "./row-actions-menu"; import { SelectionCheckbox } from "./selection-checkbox"; import type { ToggleableColumn } from "./toggleable-column"; @@ -50,6 +52,18 @@ export function RunTableRow({ {run.statusLabel} + {show("created_by") && ( + + {run.createdBy && (() => { + const display = principalDisplay(run.createdBy); + return ( + + {display.glyph} + + ); + })()} + + )} {show("repo") && ( {run.repo} diff --git a/apps/fabro-web/app/components/runs-list/runs-list-view.tsx b/apps/fabro-web/app/components/runs-list/runs-list-view.tsx index b15e216e4..8c6fe1428 100644 --- a/apps/fabro-web/app/components/runs-list/runs-list-view.tsx +++ b/apps/fabro-web/app/components/runs-list/runs-list-view.tsx @@ -152,6 +152,11 @@ export function RunsListView({ /> + {show("created_by") && ( + + By + + )} {show("repo") && ( )} diff --git a/apps/fabro-web/app/components/runs-list/toggleable-column.ts b/apps/fabro-web/app/components/runs-list/toggleable-column.ts index 80d0adf12..4d4b4fa77 100644 --- a/apps/fabro-web/app/components/runs-list/toggleable-column.ts +++ b/apps/fabro-web/app/components/runs-list/toggleable-column.ts @@ -1,4 +1,5 @@ export const TOGGLEABLE_COLUMNS = [ + "created_by", "repo", "workflow", "created", @@ -14,14 +15,15 @@ const TOGGLEABLE_COLUMN_SET = new Set(TOGGLEABLE_COLUMNS); export type ToggleableColumn = (typeof TOGGLEABLE_COLUMNS)[number]; export const toggleableColumnLabels: Record = { - repo: "Repo", - workflow: "Workflow", - created: "Created", - updated: "Updated", - elapsed: "Elapsed", - size: "Size", - changes: "Changes", - pr: "PR", + repo: "Repo", + workflow: "Workflow", + created_by: "Created by", + created: "Created", + updated: "Updated", + elapsed: "Elapsed", + size: "Size", + changes: "Changes", + pr: "PR", }; export function parseHiddenColumns(raw: string | null): Set { diff --git a/apps/fabro-web/app/data/runs.ts b/apps/fabro-web/app/data/runs.ts index 1ddcb14a1..f820b24c1 100644 --- a/apps/fabro-web/app/data/runs.ts +++ b/apps/fabro-web/app/data/runs.ts @@ -1,6 +1,7 @@ import { formatDurationMs } from "../lib/format"; import { BoardColumn, + type Principal, type Run, type RunSize, type RunStatus as ApiRunStatus, @@ -39,6 +40,7 @@ export interface RunItem { sandboxWorkingDirectory?: string; sourceDirectory?: string; createdAt?: string; + createdBy?: Principal | null; lastEventAt?: string; size?: RunSize; } @@ -108,6 +110,7 @@ export function mapRunListItem(item: Run): RunItem { sandboxWorkingDirectory: runtime?.working_directory ?? undefined, sourceDirectory: item.source_directory ?? undefined, createdAt: item.timestamps.created_at, + createdBy: item.created_by, lastEventAt: item.timestamps.last_event_at ?? undefined, additions: item.diff?.additions, deletions: item.diff?.deletions, diff --git a/apps/fabro-web/app/lib/principal-display.tsx b/apps/fabro-web/app/lib/principal-display.tsx new file mode 100644 index 000000000..fa9d4ec16 --- /dev/null +++ b/apps/fabro-web/app/lib/principal-display.tsx @@ -0,0 +1,66 @@ +import type { ReactNode } from "react"; +import { + BoltIcon, + ChatBubbleLeftEllipsisIcon, + Cog6ToothIcon, + CpuChipIcon, + QuestionMarkCircleIcon, + ServerIcon, +} from "@heroicons/react/20/solid"; +import type { Principal } from "@qltysh/fabro-api-client"; + +export interface PrincipalDisplay { + glyph: ReactNode; + label: string; +} + +function principalIconGlyph(icon: ReactNode) { + return ( + + {icon} + + ); +} + +export function principalDisplay(actor: Principal): PrincipalDisplay { + switch (actor.kind) { + case "user": { + let glyph: ReactNode; + if (actor.avatar_url) { + glyph = ( + + ); + } else { + const initial = actor.login.charAt(0).toUpperCase() || "?"; + glyph = ( + + {initial} + + ); + } + return { glyph, label: actor.login }; + } + case "agent": + return { glyph: principalIconGlyph(), label: "agent" }; + case "system": + return { glyph: principalIconGlyph(), label: "system" }; + case "slack": + return { + glyph: principalIconGlyph(), + label: "slack", + }; + case "webhook": + return { glyph: principalIconGlyph(), label: "webhook" }; + case "worker": + return { glyph: principalIconGlyph(), label: "worker" }; + case "anonymous": + return { + glyph: principalIconGlyph(), + label: "anonymous", + }; + } +}