Skip to content

Commit 10b7949

Browse files
committed
feat(webapp): runs-list column registry, URL codec, and smart-column parsing
Isomorphic column catalog plus the URL state codec (cols/sc) and the client-side payload/metadata/output parsing and JSON subpath extraction that the customizable runs list is built on. Pure, unit-tested; no behavior change on its own.
1 parent 6e77102 commit 10b7949

5 files changed

Lines changed: 807 additions & 0 deletions

File tree

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import { describe, expect, it } from "vitest";
2+
import {
3+
availableStandardColumns,
4+
decodeSmartColumn,
5+
deriveRunSelect,
6+
encodeColumnLayout,
7+
encodeSmartColumn,
8+
resolveColumnLayout,
9+
type RunColumnRuntime,
10+
type SmartColumnDef,
11+
} from "./runColumns";
12+
13+
const cloud: RunColumnRuntime = { isManagedCloud: true, isDevelopment: false };
14+
const dev: RunColumnRuntime = { isManagedCloud: false, isDevelopment: true };
15+
16+
describe("deriveRunSelect", () => {
17+
it("always includes the presenter's scalar contract", () => {
18+
const select = deriveRunSelect([], []);
19+
for (const field of [
20+
"id",
21+
"friendlyId",
22+
"spanId",
23+
"status",
24+
"runtimeEnvironmentId",
25+
"rootTaskRunId",
26+
"createdAt",
27+
"updatedAt",
28+
"startedAt",
29+
"lockedAt",
30+
"completedAt",
31+
"queueTimestamp",
32+
"delayUntil",
33+
"scheduleId",
34+
"metadata",
35+
"metadataType",
36+
"taskIdentifier",
37+
"machinePreset",
38+
"queue",
39+
"runTags",
40+
]) {
41+
expect(select[field as keyof typeof select]).toBe(true);
42+
}
43+
});
44+
45+
it("does not hydrate the large blobs unless a smart column references them", () => {
46+
const select = deriveRunSelect(["task", "status", "tags"], []);
47+
expect(select.payload).toBeUndefined();
48+
expect(select.payloadType).toBeUndefined();
49+
expect(select.output).toBeUndefined();
50+
expect(select.outputType).toBeUndefined();
51+
});
52+
53+
it("adds payload/output fields only for referenced smart sources", () => {
54+
const payloadOnly = deriveRunSelect([], ["payload"]);
55+
expect(payloadOnly.payload).toBe(true);
56+
expect(payloadOnly.payloadType).toBe(true);
57+
expect(payloadOnly.output).toBeUndefined();
58+
59+
const both = deriveRunSelect([], ["payload", "output"]);
60+
expect(both.output).toBe(true);
61+
expect(both.outputType).toBe(true);
62+
});
63+
64+
it("references metadata from the always-selected set without a smart source", () => {
65+
const select = deriveRunSelect([], ["metadata"]);
66+
expect(select.metadata).toBe(true);
67+
expect(select.metadataType).toBe(true);
68+
});
69+
});
70+
71+
describe("availableStandardColumns gating", () => {
72+
it("includes compute and region on managed cloud", () => {
73+
const ids = availableStandardColumns(cloud).map((c) => c.id);
74+
expect(ids).toContain("compute");
75+
expect(ids).toContain("region");
76+
});
77+
78+
it("drops compute and region on development / self-host", () => {
79+
const ids = availableStandardColumns(dev).map((c) => c.id);
80+
expect(ids).not.toContain("compute");
81+
expect(ids).not.toContain("region");
82+
});
83+
});
84+
85+
describe("resolveColumnLayout", () => {
86+
it("returns the default layout when cols is absent", () => {
87+
const layout = resolveColumnLayout({ cols: [], sc: [] }, cloud);
88+
expect(layout.isCustomized).toBe(false);
89+
expect(layout.hiddenStandard).toHaveLength(0);
90+
expect(layout.visible[0]).toMatchObject({ kind: "standard", def: { id: "id" } });
91+
expect(layout.visible).toHaveLength(availableStandardColumns(cloud).length);
92+
});
93+
94+
it("keeps locked columns in the requested order (they are reorderable)", () => {
95+
const layout = resolveColumnLayout({ cols: ["task", "status", "id"], sc: [] }, cloud);
96+
const ids = layout.visible.map((c) => (c.kind === "standard" ? c.def.id : "smart"));
97+
expect(ids).toEqual(["task", "status", "id"]);
98+
});
99+
100+
it("moves omitted standard columns into hiddenStandard", () => {
101+
const layout = resolveColumnLayout({ cols: ["id", "task", "status"], sc: [] }, cloud);
102+
const hidden = layout.hiddenStandard.map((c) => c.id);
103+
expect(hidden).toContain("tags");
104+
expect(hidden).toContain("ttl");
105+
expect(hidden).not.toContain("id");
106+
});
107+
108+
it("never hides locked columns and reinserts them if the URL omits them", () => {
109+
const layout = resolveColumnLayout({ cols: ["id", "ver"], sc: [] }, cloud);
110+
const ids = layout.visible.filter((c) => c.kind === "standard").map((c) => c.def.id);
111+
expect(ids).toContain("task");
112+
expect(ids).toContain("status");
113+
const hidden = layout.hiddenStandard.map((c) => c.id);
114+
expect(hidden).not.toContain("task");
115+
expect(hidden).not.toContain("status");
116+
});
117+
118+
it("resolves smart-column refs positionally", () => {
119+
const sc = [
120+
encodeSmartColumn({ source: "metadata", path: "$.failed", label: "Failed", displayAs: "number" }),
121+
];
122+
const layout = resolveColumnLayout({ cols: ["id", "sc1"], sc }, cloud);
123+
const smart = layout.visible.find((c) => c.kind === "smart");
124+
expect(smart).toMatchObject({ kind: "smart", def: { label: "Failed", source: "metadata" } });
125+
});
126+
127+
it("drops gated columns referenced on a runtime that lacks them", () => {
128+
const layout = resolveColumnLayout({ cols: ["id", "region", "compute", "task"], sc: [] }, dev);
129+
const ids = layout.visible.map((c) => (c.kind === "standard" ? c.def.id : "smart"));
130+
expect(ids).toEqual(["id", "task", "status"]);
131+
});
132+
});
133+
134+
describe("encodeColumnLayout round-trip", () => {
135+
it("encodes the default layout to empty params", () => {
136+
const layout = resolveColumnLayout({ cols: [], sc: [] }, cloud);
137+
expect(encodeColumnLayout(layout.visible, cloud)).toEqual({ cols: [], sc: [] });
138+
});
139+
140+
it("round-trips a reordered, hidden, smart-augmented layout", () => {
141+
const scDef: SmartColumnDef = {
142+
source: "payload",
143+
path: "$.order.total",
144+
label: "Order total",
145+
displayAs: "number",
146+
};
147+
const encoded = encodeColumnLayout(
148+
[
149+
{ kind: "standard", def: availableStandardColumns(cloud).find((c) => c.id === "id")! },
150+
{ kind: "standard", def: availableStandardColumns(cloud).find((c) => c.id === "status")! },
151+
{ kind: "smart", index: 0, def: scDef },
152+
],
153+
cloud
154+
);
155+
expect(encoded.cols).toEqual(["id", "status", "sc1"]);
156+
expect(encoded.sc).toHaveLength(1);
157+
158+
const layout = resolveColumnLayout(encoded, cloud);
159+
const ids = layout.visible.map((c) => (c.kind === "standard" ? c.def.id : c.def.label));
160+
expect(ids).toEqual(["id", "task", "status", "Order total"]);
161+
});
162+
});
163+
164+
describe("smart column codec", () => {
165+
it("round-trips including delimiter-dangerous characters", () => {
166+
const def: SmartColumnDef = {
167+
source: "metadata",
168+
path: "$['a:b'].c",
169+
label: "Weird: 50%",
170+
displayAs: "badge",
171+
};
172+
const decoded = decodeSmartColumn(encodeSmartColumn(def));
173+
expect(decoded).toEqual(def);
174+
});
175+
176+
it("rejects an unknown source or display", () => {
177+
expect(decodeSmartColumn("bogus:$.a:A:number")).toBeUndefined();
178+
expect(decodeSmartColumn("metadata:$.a:A:bogus")).toBeUndefined();
179+
expect(decodeSmartColumn("metadata::A:number")).toBeUndefined();
180+
});
181+
});

0 commit comments

Comments
 (0)