@ryanfw/prompt-orchestration-pipeline 1.2.7 → 1.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/package.json +1 -1
  2. package/src/config/__tests__/models.test.ts +31 -1
  3. package/src/config/models.ts +81 -35
  4. package/src/config/paths.ts +13 -8
  5. package/src/core/__tests__/config.test.ts +121 -0
  6. package/src/core/__tests__/job-concurrency.test.ts +554 -0
  7. package/src/core/__tests__/orchestrator.test.ts +353 -0
  8. package/src/core/__tests__/pipeline-runner.test.ts +430 -2
  9. package/src/core/__tests__/task-runner.test.ts +1 -2
  10. package/src/core/config.ts +48 -1
  11. package/src/core/job-concurrency.ts +462 -0
  12. package/src/core/orchestrator.ts +370 -57
  13. package/src/core/pipeline-runner.ts +79 -15
  14. package/src/core/status-writer.ts +4 -0
  15. package/src/core/task-runner.ts +1 -1
  16. package/src/providers/__tests__/base.test.ts +1 -1
  17. package/src/ui/client/__tests__/api.test.ts +101 -1
  18. package/src/ui/client/__tests__/job-adapter.test.ts +12 -0
  19. package/src/ui/client/__tests__/useConcurrencyStatus.test.ts +126 -0
  20. package/src/ui/client/adapters/job-adapter.ts +1 -0
  21. package/src/ui/client/api.ts +77 -7
  22. package/src/ui/client/hooks/useConcurrencyStatus.ts +102 -0
  23. package/src/ui/client/types.ts +34 -1
  24. package/src/ui/components/DAGGrid.tsx +11 -1
  25. package/src/ui/components/JobDetail.tsx +2 -1
  26. package/src/ui/components/__tests__/DAGGrid.test.tsx +92 -0
  27. package/src/ui/components/__tests__/JobDetail.test.tsx +62 -0
  28. package/src/ui/components/types.ts +2 -0
  29. package/src/ui/dist/assets/{index-SKy2shWc.js → index-BnAqY4_n.js} +336 -52
  30. package/src/ui/dist/assets/index-BnAqY4_n.js.map +1 -0
  31. package/src/ui/dist/assets/style-BKG0bHu-.css +2 -0
  32. package/src/ui/dist/index.html +2 -2
  33. package/src/ui/embedded-assets.js +6 -6
  34. package/src/ui/pages/PromptPipelineDashboard.tsx +186 -4
  35. package/src/ui/pages/__tests__/PromptPipelineDashboard.test.tsx +272 -1
  36. package/src/ui/server/__tests__/concurrency-endpoint.test.ts +190 -0
  37. package/src/ui/server/__tests__/index.test.ts +92 -3
  38. package/src/ui/server/__tests__/job-control-endpoints.test.ts +660 -3
  39. package/src/ui/server/endpoints/concurrency-endpoint.ts +72 -0
  40. package/src/ui/server/endpoints/job-control-endpoints.ts +248 -37
  41. package/src/ui/server/index.ts +21 -2
  42. package/src/ui/server/router.ts +2 -0
  43. package/src/ui/state/__tests__/watcher.test.ts +31 -0
  44. package/src/ui/state/transformers/__tests__/status-transformer.test.ts +15 -0
  45. package/src/ui/state/transformers/status-transformer.ts +1 -0
  46. package/src/ui/state/types.ts +3 -0
  47. package/src/ui/state/watcher.ts +9 -1
  48. package/src/utils/__tests__/dag.test.ts +35 -0
  49. package/src/utils/dag.ts +1 -0
  50. package/src/ui/dist/assets/index-SKy2shWc.js.map +0 -1
  51. package/src/ui/dist/assets/style-DA1Ma4YS.css +0 -2
@@ -117,3 +117,95 @@ test("DAGGrid opens task details from keyboard interaction and renders connector
117
117
  expect(view.getByText("Build · done")).toBeTruthy();
118
118
  expect(view.container.querySelectorAll('path[marker-end="url(#dag-grid-arrow)"]').length).toBe(1);
119
119
  });
120
+
121
+ test("DAGGrid does not render the restart badge when restartCount is absent or zero", () => {
122
+ installMatchMedia();
123
+
124
+ const view = render(
125
+ <DAGGrid
126
+ items={[makeItem({ id: "build", title: "Build", status: "pending" })]}
127
+ jobId="job-1"
128
+ taskById={{ build: makeTaskState({ name: "build", state: "pending" }) }}
129
+ pipelineTasks={["build"]}
130
+ filesByTypeForItem={() => emptyFiles}
131
+ geometryAdapter={geometryAdapter}
132
+ />,
133
+ );
134
+
135
+ expect(view.container.querySelector('[data-role="restart-badge"]')).toBeNull();
136
+
137
+ const viewZero = render(
138
+ <DAGGrid
139
+ items={[makeItem({ id: "build", title: "Build", status: "pending", restartCount: 0 })]}
140
+ jobId="job-1"
141
+ taskById={{ build: makeTaskState({ name: "build", state: "pending" }) }}
142
+ pipelineTasks={["build"]}
143
+ filesByTypeForItem={() => emptyFiles}
144
+ geometryAdapter={geometryAdapter}
145
+ />,
146
+ );
147
+
148
+ expect(viewZero.container.querySelector('[data-role="restart-badge"]')).toBeNull();
149
+ });
150
+
151
+ test("DAGGrid renders the restart badge with singular label when restartCount is 1", () => {
152
+ installMatchMedia();
153
+
154
+ const view = render(
155
+ <DAGGrid
156
+ items={[makeItem({ id: "build", title: "Build", status: "pending", restartCount: 1 })]}
157
+ jobId="job-1"
158
+ taskById={{ build: makeTaskState({ name: "build", state: "pending" }) }}
159
+ pipelineTasks={["build"]}
160
+ filesByTypeForItem={() => emptyFiles}
161
+ geometryAdapter={geometryAdapter}
162
+ />,
163
+ );
164
+
165
+ const badge = view.container.querySelector('[data-role="restart-badge"]');
166
+ expect(badge).not.toBeNull();
167
+ expect(badge!.getAttribute("aria-label")).toBe("Restarted 1 time");
168
+ expect(badge!.getAttribute("title")).toBe("Restarted 1 time");
169
+ expect(badge!.textContent).toBe("↻ 1");
170
+ });
171
+
172
+ test("DAGGrid renders the restart badge with plural label when restartCount is greater than 1", () => {
173
+ installMatchMedia();
174
+
175
+ const view = render(
176
+ <DAGGrid
177
+ items={[makeItem({ id: "build", title: "Build", status: "pending", restartCount: 5 })]}
178
+ jobId="job-1"
179
+ taskById={{ build: makeTaskState({ name: "build", state: "pending" }) }}
180
+ pipelineTasks={["build"]}
181
+ filesByTypeForItem={() => emptyFiles}
182
+ geometryAdapter={geometryAdapter}
183
+ />,
184
+ );
185
+
186
+ const badge = view.container.querySelector('[data-role="restart-badge"]');
187
+ expect(badge).not.toBeNull();
188
+ expect(badge!.getAttribute("aria-label")).toBe("Restarted 5 times");
189
+ expect(badge!.textContent).toBe("↻ 5");
190
+ });
191
+
192
+ test("DAGGrid renders the restart badge as the first child of the card header", () => {
193
+ installMatchMedia();
194
+
195
+ const view = render(
196
+ <DAGGrid
197
+ items={[makeItem({ id: "build", title: "Build", status: "pending", restartCount: 2 })]}
198
+ jobId="job-1"
199
+ taskById={{ build: makeTaskState({ name: "build", state: "pending" }) }}
200
+ pipelineTasks={["build"]}
201
+ filesByTypeForItem={() => emptyFiles}
202
+ geometryAdapter={geometryAdapter}
203
+ />,
204
+ );
205
+
206
+ const header = view.container.querySelector('[data-role="card-header"]');
207
+ expect(header).not.toBeNull();
208
+ const firstChild = header!.firstElementChild;
209
+ expect(firstChild).not.toBeNull();
210
+ expect(firstChild!.getAttribute("data-role")).toBe("restart-badge");
211
+ });
@@ -0,0 +1,62 @@
1
+ import "./test-dom";
2
+
3
+ import { render } from "@testing-library/react";
4
+ import { afterEach, expect, mock, test } from "bun:test";
5
+
6
+ import type { DagItem, JobDetail as JobDetailType, PipelineType } from "../types";
7
+
8
+ const capturedItems: DagItem[][] = [];
9
+
10
+ mock.module("../DAGGrid", () => ({
11
+ default: ({ items }: { items: DagItem[] }) => {
12
+ capturedItems.push(items);
13
+ return null;
14
+ },
15
+ }));
16
+
17
+ const JobDetail = (await import("../JobDetail")).default;
18
+
19
+ afterEach(() => {
20
+ document.body.innerHTML = "";
21
+ capturedItems.length = 0;
22
+ });
23
+
24
+ function makeJob(restartCount: number): JobDetailType {
25
+ return {
26
+ id: "job-1",
27
+ name: "Job One",
28
+ status: "running",
29
+ tasks: {
30
+ build: {
31
+ name: "build",
32
+ state: "running",
33
+ startedAt: 1_000,
34
+ files: { artifacts: [], logs: [], tmp: [] },
35
+ restartCount,
36
+ },
37
+ },
38
+ pipeline: { tasks: ["build"] },
39
+ current: "build",
40
+ };
41
+ }
42
+
43
+ const pipeline: PipelineType = {
44
+ name: "Test",
45
+ slug: "test",
46
+ description: "",
47
+ tasks: [{ name: "build" }],
48
+ };
49
+
50
+ test("JobDetail re-emits dag item when only restartCount changes", () => {
51
+ const view = render(<JobDetail job={makeJob(1)} pipeline={pipeline} />);
52
+ expect(capturedItems.length).toBeGreaterThan(0);
53
+ const firstItems = capturedItems[capturedItems.length - 1] as DagItem[];
54
+ const firstBuild = firstItems.find((item) => item.id === "build");
55
+ expect(firstBuild?.restartCount).toBe(1);
56
+
57
+ view.rerender(<JobDetail job={makeJob(2)} pipeline={pipeline} />);
58
+ const lastItems = capturedItems[capturedItems.length - 1] as DagItem[];
59
+ const secondBuild = lastItems.find((item) => item.id === "build");
60
+ expect(secondBuild?.restartCount).toBe(2);
61
+ expect(secondBuild).not.toBe(firstBuild);
62
+ });
@@ -43,6 +43,7 @@ export interface TaskStateObject {
43
43
  artifacts?: string[];
44
44
  tokenUsage?: Record<string, unknown>;
45
45
  attempts?: number;
46
+ restartCount?: number;
46
47
  executionTimeMs?: number;
47
48
  }
48
49
 
@@ -105,6 +106,7 @@ export interface DagItem {
105
106
  body: string | null;
106
107
  startedAt: string | number;
107
108
  endedAt: string | number | null;
109
+ restartCount?: number;
108
110
  }
109
111
 
110
112
  export interface PipelineTask {