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",
+ };
+ }
+}