@nanobpm/nano-workforce 0.142.0 → 0.144.0
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/CHANGELOG.md +12 -0
- package/app/deliveryGraphReadModel.test.ts +392 -0
- package/app/deliveryGraphReadModel.ts +166 -0
- package/app/deliveryRunner.test.ts +50 -8
- package/app/deliveryRunner.ts +43 -1
- package/app/stepAxis.test.ts +142 -0
- package/app/stepAxis.ts +217 -0
- package/db/migrations/087_delivery_graph_read_model.sql +75 -0
- package/package.json +1 -1
- package/pages/delivery-graph-detail.page.json +18 -2
- package/pages/delivery-graphs.page.json +25 -8
- package/pages/overview.page.json +18 -2
- package/scripts/pages-contract.test.ts +15 -11
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
"data": {
|
|
119
119
|
"kind": "datasource",
|
|
120
120
|
"source": "app",
|
|
121
|
-
"table": "
|
|
121
|
+
"table": "delivery_graph_read_model",
|
|
122
122
|
"orderBy": { "field": "updated_at", "dir": "desc" },
|
|
123
123
|
"filter": [{ "field": "status", "in": ["awaiting-approval", "running"] }]
|
|
124
124
|
},
|
|
@@ -128,15 +128,32 @@
|
|
|
128
128
|
{ "label": "All", "filter": [] }
|
|
129
129
|
],
|
|
130
130
|
"columns": [
|
|
131
|
-
{ "field": "title", "template": "{{title}}", "header": "Graph", "subtitleField": "run_key", "truncate": true, "width": "
|
|
132
|
-
{ "field": "status", "header": "Status", "truncate": true, "width": "
|
|
133
|
-
{ "field": "process_key", "header": "Instance", "width": "
|
|
134
|
-
{
|
|
131
|
+
{ "field": "title", "template": "{{title}}", "header": "Graph", "subtitleField": "run_key", "truncate": true, "width": "18%", "link": { "kind": "page", "page": "delivery-graph-detail", "keyField": "run_key" } },
|
|
132
|
+
{ "field": "status", "header": "Status", "truncate": true, "width": "10%", "link": { "kind": "processExplorer", "keyField": "process_key" } },
|
|
133
|
+
{ "field": "process_key", "header": "Instance", "width": "8%", "truncate": true, "link": { "kind": "processExplorer", "keyField": "process_key" } },
|
|
134
|
+
{
|
|
135
|
+
"field": "stage",
|
|
136
|
+
"header": "Pipeline",
|
|
137
|
+
"kind": "pipeline",
|
|
138
|
+
"width": "18%",
|
|
139
|
+
"stages": [
|
|
140
|
+
{ "key": "Requested", "label": "Requested" },
|
|
141
|
+
{ "key": "Implementing", "label": "Implementing" },
|
|
142
|
+
{ "key": "PR open", "label": "PR open" },
|
|
143
|
+
{ "key": "Converging", "label": "Converging" },
|
|
144
|
+
{ "key": "Merging", "label": "Merging" },
|
|
145
|
+
{ "key": "Done", "label": "Done" }
|
|
146
|
+
],
|
|
147
|
+
"activeField": "stage",
|
|
148
|
+
"stateField": "stage_state",
|
|
149
|
+
"locus": { "field": "process_key", "link": { "kind": "processExplorer", "keyField": "process_key" } }
|
|
150
|
+
},
|
|
151
|
+
{ "field": "park_label", "header": "Parked", "truncate": true, "width": "12%" },
|
|
135
152
|
{ "field": "node_count", "header": "Nodes", "width": "5%" },
|
|
136
153
|
{ "field": "human_node_count", "header": "Human", "width": "5%" },
|
|
137
|
-
{ "field": "side_effect_count", "header": "Effects", "width": "
|
|
138
|
-
{ "field": "created_at", "header": "Dispatched", "width": "
|
|
139
|
-
{ "field": "updated_at", "header": "Updated", "width": "
|
|
154
|
+
{ "field": "side_effect_count", "header": "Effects", "width": "6%" },
|
|
155
|
+
{ "field": "created_at", "header": "Dispatched", "width": "7%", "truncate": true, "format": "datetime" },
|
|
156
|
+
{ "field": "updated_at", "header": "Updated", "width": "6%", "truncate": true, "format": "datetime" }
|
|
140
157
|
],
|
|
141
158
|
"detail": {
|
|
142
159
|
"fields": [
|
package/pages/overview.page.json
CHANGED
|
@@ -207,14 +207,30 @@
|
|
|
207
207
|
"data": {
|
|
208
208
|
"kind": "datasource",
|
|
209
209
|
"source": "app",
|
|
210
|
-
"table": "
|
|
210
|
+
"table": "delivery_graph_read_model",
|
|
211
211
|
"orderBy": { "field": "updated_at", "dir": "desc" },
|
|
212
212
|
"filter": [{ "field": "status", "in": ["awaiting-approval", "running"] }]
|
|
213
213
|
},
|
|
214
214
|
"columns": [
|
|
215
215
|
{ "field": "title", "template": "{{title}}", "header": "Item", "subtitleField": "run_key", "truncate": true, "width": "30%", "link": { "kind": "page", "page": "delivery-graph-detail", "keyField": "run_key" } },
|
|
216
216
|
{ "field": "status", "header": "Status", "link": { "kind": "processExplorer", "keyField": "process_key" } },
|
|
217
|
-
{
|
|
217
|
+
{
|
|
218
|
+
"field": "stage",
|
|
219
|
+
"header": "Pipeline",
|
|
220
|
+
"kind": "pipeline",
|
|
221
|
+
"stages": [
|
|
222
|
+
{ "key": "Requested", "label": "Requested" },
|
|
223
|
+
{ "key": "Implementing", "label": "Implementing" },
|
|
224
|
+
{ "key": "PR open", "label": "PR open" },
|
|
225
|
+
{ "key": "Converging", "label": "Converging" },
|
|
226
|
+
{ "key": "Merging", "label": "Merging" },
|
|
227
|
+
{ "key": "Done", "label": "Done" }
|
|
228
|
+
],
|
|
229
|
+
"activeField": "stage",
|
|
230
|
+
"stateField": "stage_state",
|
|
231
|
+
"locus": { "field": "process_key", "link": { "kind": "processExplorer", "keyField": "process_key" } }
|
|
232
|
+
},
|
|
233
|
+
{ "field": "park_label", "header": "Parked", "truncate": true, "width": "28%" },
|
|
218
234
|
{ "field": "updated_at", "header": "Updated", "width": "9rem", "format": "datetime" }
|
|
219
235
|
],
|
|
220
236
|
"detail": {
|
|
@@ -333,8 +333,10 @@ test("issue #205: overview is the landing page and first nav item", async () =>
|
|
|
333
333
|
plan_read_model: { field: "list_bucket", in: ["active"] },
|
|
334
334
|
feature_runs: { field: "status", in: ["running", "escalated", "awaiting_operator"] },
|
|
335
335
|
// The 4th dispatch surface (issue #386) — active delivery graphs. Both in-flight statuses
|
|
336
|
-
// (`awaiting-approval` parked at the gate, `running` dispatched) show here.
|
|
337
|
-
|
|
336
|
+
// (`awaiting-approval` parked at the gate, `running` dispatched) show here. Binds the derived
|
|
337
|
+
// `delivery_graph_read_model` VIEW (S7 / #541 — the single source of truth for the pipeline
|
|
338
|
+
// projection it also renders), which re-exports every run column plus the effective `status`.
|
|
339
|
+
delivery_graph_read_model: { field: "status", in: ["awaiting-approval", "running"] },
|
|
338
340
|
};
|
|
339
341
|
const grids = (overview.nodes ?? []).filter((n: Json) => n.type === "dataGrid");
|
|
340
342
|
for (const [table, { field, in: values }] of Object.entries(expected)) {
|
|
@@ -361,19 +363,21 @@ test("issue #386: the human-facing Delivery Graphs surface is wired (nav tab, pa
|
|
|
361
363
|
assert(tab, "pages/_nav.json must carry a `Delivery Graphs` nav tab → the delivery-graphs page");
|
|
362
364
|
|
|
363
365
|
// 2) The page carries the compose → preview → dispatch App View (issue #441 — the rendered preview
|
|
364
|
-
// that consumes the compile output), plus an in-flight grid over the
|
|
365
|
-
//
|
|
366
|
-
//
|
|
367
|
-
//
|
|
366
|
+
// that consumes the compile output), plus an in-flight grid over the delivery-graph run data (the
|
|
367
|
+
// derived `delivery_graph_read_model` VIEW, S7 / #541 — which re-exports every `delivery_graph_runs`
|
|
368
|
+
// column plus the pipeline projection) that links to the per-graph detail page. The rich preview
|
|
369
|
+
// (mermaid diagram + humanNodes[] + sideEffects[] + inline errors) can't render in a bare
|
|
370
|
+
// `actionForm` (its response is discarded), so the surface is an `appView` embed over the SAME
|
|
371
|
+
// compile/dispatch doors.
|
|
368
372
|
const page = JSON.parse(readFileSync(`${ROOT}pages/delivery-graphs.page.json`, "utf8"));
|
|
369
373
|
const compose = (page.nodes ?? []).find(
|
|
370
374
|
(n: Json) => n.type === "appView" && typeof n.props?.embed === "string" && n.props.embed.includes("delivery-graphs/embed.html"),
|
|
371
375
|
);
|
|
372
376
|
assert(compose, "delivery-graphs page must have an appView embedding ./delivery-graphs/embed.html (the compose → preview → dispatch view, #441)");
|
|
373
377
|
const grid = (page.nodes ?? []).find(
|
|
374
|
-
(n: Json) => n.type === "dataGrid" && n.props?.data?.table === "
|
|
378
|
+
(n: Json) => n.type === "dataGrid" && n.props?.data?.table === "delivery_graph_read_model",
|
|
375
379
|
);
|
|
376
|
-
assert(grid, "delivery-graphs page must have an in-flight grid over
|
|
380
|
+
assert(grid, "delivery-graphs page must have an in-flight grid over the derived delivery_graph_read_model VIEW");
|
|
377
381
|
const linkCol = (grid.props?.columns ?? []).find((c: Json) => c.link?.page === "delivery-graph-detail");
|
|
378
382
|
assert(
|
|
379
383
|
linkCol && linkCol.link?.keyField === "run_key",
|
|
@@ -383,9 +387,9 @@ test("issue #386: the human-facing Delivery Graphs surface is wired (nav tab, pa
|
|
|
383
387
|
// 3) The per-graph detail page reads the run aggregate scoped to the route param (run_key).
|
|
384
388
|
const detail = JSON.parse(readFileSync(`${ROOT}pages/delivery-graph-detail.page.json`, "utf8"));
|
|
385
389
|
const runGrid = (detail.nodes ?? []).find(
|
|
386
|
-
(n: Json) => n.type === "dataGrid" && n.props?.data?.table === "
|
|
390
|
+
(n: Json) => n.type === "dataGrid" && n.props?.data?.table === "delivery_graph_read_model",
|
|
387
391
|
);
|
|
388
|
-
assert(runGrid, "delivery-graph-detail must bind a grid to
|
|
392
|
+
assert(runGrid, "delivery-graph-detail must bind a grid to the derived delivery_graph_read_model VIEW");
|
|
389
393
|
assert(
|
|
390
394
|
(runGrid.props?.data?.filter ?? []).some((fl: Json) => fl.field === "run_key" && fl.eqParam === true),
|
|
391
395
|
"delivery-graph-detail must scope its run grid to the route param (run_key eqParam)",
|
|
@@ -399,7 +403,7 @@ test("issue #386: the human-facing Delivery Graphs surface is wired (nav tab, pa
|
|
|
399
403
|
"overview subtitle must no longer say 'three dispatch surfaces' (a delivery graph is a 4th)",
|
|
400
404
|
);
|
|
401
405
|
const ovGrid = (overview.nodes ?? []).find(
|
|
402
|
-
(n: Json) => n.type === "dataGrid" && n.props?.data?.table === "
|
|
406
|
+
(n: Json) => n.type === "dataGrid" && n.props?.data?.table === "delivery_graph_read_model",
|
|
403
407
|
);
|
|
404
408
|
const ovLink = (ovGrid?.props?.columns ?? []).find((c: Json) => c.link?.page === "delivery-graph-detail");
|
|
405
409
|
assert(
|