@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.
- package/package.json +1 -1
- package/src/config/__tests__/models.test.ts +31 -1
- package/src/config/models.ts +81 -35
- package/src/config/paths.ts +13 -8
- package/src/core/__tests__/config.test.ts +121 -0
- package/src/core/__tests__/job-concurrency.test.ts +554 -0
- package/src/core/__tests__/orchestrator.test.ts +353 -0
- package/src/core/__tests__/pipeline-runner.test.ts +430 -2
- package/src/core/__tests__/task-runner.test.ts +1 -2
- package/src/core/config.ts +48 -1
- package/src/core/job-concurrency.ts +462 -0
- package/src/core/orchestrator.ts +370 -57
- package/src/core/pipeline-runner.ts +79 -15
- package/src/core/status-writer.ts +4 -0
- package/src/core/task-runner.ts +1 -1
- package/src/providers/__tests__/base.test.ts +1 -1
- package/src/ui/client/__tests__/api.test.ts +101 -1
- package/src/ui/client/__tests__/job-adapter.test.ts +12 -0
- package/src/ui/client/__tests__/useConcurrencyStatus.test.ts +126 -0
- package/src/ui/client/adapters/job-adapter.ts +1 -0
- package/src/ui/client/api.ts +77 -7
- package/src/ui/client/hooks/useConcurrencyStatus.ts +102 -0
- package/src/ui/client/types.ts +34 -1
- package/src/ui/components/DAGGrid.tsx +11 -1
- package/src/ui/components/JobDetail.tsx +2 -1
- package/src/ui/components/__tests__/DAGGrid.test.tsx +92 -0
- package/src/ui/components/__tests__/JobDetail.test.tsx +62 -0
- package/src/ui/components/types.ts +2 -0
- package/src/ui/dist/assets/{index-SKy2shWc.js → index-BnAqY4_n.js} +336 -52
- package/src/ui/dist/assets/index-BnAqY4_n.js.map +1 -0
- package/src/ui/dist/assets/style-BKG0bHu-.css +2 -0
- package/src/ui/dist/index.html +2 -2
- package/src/ui/embedded-assets.js +6 -6
- package/src/ui/pages/PromptPipelineDashboard.tsx +186 -4
- package/src/ui/pages/__tests__/PromptPipelineDashboard.test.tsx +272 -1
- package/src/ui/server/__tests__/concurrency-endpoint.test.ts +190 -0
- package/src/ui/server/__tests__/index.test.ts +92 -3
- package/src/ui/server/__tests__/job-control-endpoints.test.ts +660 -3
- package/src/ui/server/endpoints/concurrency-endpoint.ts +72 -0
- package/src/ui/server/endpoints/job-control-endpoints.ts +248 -37
- package/src/ui/server/index.ts +21 -2
- package/src/ui/server/router.ts +2 -0
- package/src/ui/state/__tests__/watcher.test.ts +31 -0
- package/src/ui/state/transformers/__tests__/status-transformer.test.ts +15 -0
- package/src/ui/state/transformers/status-transformer.ts +1 -0
- package/src/ui/state/types.ts +3 -0
- package/src/ui/state/watcher.ts +9 -1
- package/src/utils/__tests__/dag.test.ts +35 -0
- package/src/utils/dag.ts +1 -0
- package/src/ui/dist/assets/index-SKy2shWc.js.map +0 -1
- 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 {
|