@nanobpm/nano-workforce 0.117.0 → 0.118.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.
@@ -29,6 +29,10 @@
29
29
  "label": "Feature",
30
30
  "page": "feature"
31
31
  },
32
+ {
33
+ "label": "Delivery Graphs",
34
+ "page": "delivery-graphs"
35
+ },
32
36
  {
33
37
  "label": "Tasks",
34
38
  "page": "tasks",
@@ -66,7 +70,7 @@
66
70
  "type": "text",
67
71
  "id": "subtitle",
68
72
  "props": {
69
- "text": "Everything currently in flight across the three dispatch surfaces \u2014 PR convergences, Epics, and Feature runs. Each section lists only its active items, remembers whether you collapsed it, and shows a live count in its header. Follow any row to its detail page.",
73
+ "text": "Everything currently in flight across the four dispatch surfaces \u2014 PR convergences, Epics, Feature runs, and Delivery Graphs. Each section lists only its active items, remembers whether you collapsed it, and shows a live count in its header. Follow any row to its detail page.",
70
74
  "variant": "sub"
71
75
  }
72
76
  },
@@ -208,7 +212,7 @@
208
212
  "filter": [{ "field": "status", "in": ["awaiting-approval", "running"] }]
209
213
  },
210
214
  "columns": [
211
- { "field": "title", "template": "{{title}}", "header": "Item", "subtitleField": "run_key", "truncate": true, "width": "30%" },
215
+ { "field": "title", "template": "{{title}}", "header": "Item", "subtitleField": "run_key", "truncate": true, "width": "30%", "link": { "kind": "page", "page": "delivery-graph-detail", "keyField": "run_key" } },
212
216
  { "field": "status", "header": "Status", "link": { "kind": "processExplorer", "keyField": "process_key" } },
213
217
  { "field": "phase", "header": "Phase", "truncate": true, "width": "28%" },
214
218
  { "field": "updated_at", "header": "Updated", "width": "9rem", "format": "datetime" }
@@ -29,6 +29,10 @@
29
29
  "label": "Feature",
30
30
  "page": "feature"
31
31
  },
32
+ {
33
+ "label": "Delivery Graphs",
34
+ "page": "delivery-graphs"
35
+ },
32
36
  {
33
37
  "label": "Tasks",
34
38
  "page": "tasks",
@@ -29,6 +29,10 @@
29
29
  "label": "Feature",
30
30
  "page": "feature"
31
31
  },
32
+ {
33
+ "label": "Delivery Graphs",
34
+ "page": "delivery-graphs"
35
+ },
32
36
  {
33
37
  "label": "Tasks",
34
38
  "page": "tasks",
@@ -282,6 +282,9 @@ test("issue #205: overview is the landing page and first nav item", async () =>
282
282
  },
283
283
  plans: { field: "list_bucket", in: ["active"] },
284
284
  feature_runs: { field: "status", in: ["running", "escalated", "awaiting_operator"] },
285
+ // The 4th dispatch surface (issue #386) — active delivery graphs. Both in-flight statuses
286
+ // (`awaiting-approval` parked at the gate, `running` dispatched) show here.
287
+ delivery_graph_runs: { field: "status", in: ["awaiting-approval", "running"] },
285
288
  };
286
289
  const grids = (overview.nodes ?? []).filter((n: Json) => n.type === "dataGrid");
287
290
  for (const [table, { field, in: values }] of Object.entries(expected)) {
@@ -298,3 +301,67 @@ test("issue #205: overview is the landing page and first nav item", async () =>
298
301
  }
299
302
  });
300
303
 
304
+
305
+ test("issue #386: the human-facing Delivery Graphs surface is wired (nav tab, page, detail, overview)", async () => {
306
+ // 1) A `Delivery Graphs` nav tab in the single-source nav, pointing at the delivery-graphs page.
307
+ const nav = JSON.parse(readFileSync(`${ROOT}pages/_nav.json`, "utf8"));
308
+ const tab = (nav.props?.items ?? []).find(
309
+ (i: Json) => i.page === "delivery-graphs" && i.label === "Delivery Graphs",
310
+ );
311
+ assert(tab, "pages/_nav.json must carry a `Delivery Graphs` nav tab → the delivery-graphs page");
312
+
313
+ // 2) The Submit page exists with a Preview (compile) action and a Dispatch (start) action, plus an
314
+ // in-flight grid over the delivery_graph_runs aggregate that links to the per-graph detail page.
315
+ const page = JSON.parse(readFileSync(`${ROOT}pages/delivery-graphs.page.json`, "utf8"));
316
+ const forms = (page.nodes ?? []).filter((n: Json) => n.type === "actionForm");
317
+ const preview = forms.find((f: Json) => f.props?.action?.path === "/app/api/actions/delivery-graph/preview");
318
+ const dispatch = forms.find((f: Json) => f.props?.action?.path === "/app/api/actions/delivery-graph/dispatch");
319
+ assert(preview, "delivery-graphs page must have a Preview form posting to /app/api/actions/delivery-graph/preview");
320
+ assert(dispatch, "delivery-graphs page must have a Dispatch form posting to /app/api/actions/delivery-graph/dispatch");
321
+ // Both forms take the pasted graph JSON; Dispatch also carries the explicit approve gate.
322
+ assert(
323
+ (preview.props?.fields ?? []).some((fl: Json) => fl.key === "graphJson"),
324
+ "the Preview form must have a graphJson paste field",
325
+ );
326
+ assert(
327
+ (dispatch.props?.fields ?? []).some((fl: Json) => fl.key === "graphJson") &&
328
+ (dispatch.props?.fields ?? []).some((fl: Json) => fl.key === "approve"),
329
+ "the Dispatch form must have a graphJson paste field and an approve gate",
330
+ );
331
+ const grid = (page.nodes ?? []).find(
332
+ (n: Json) => n.type === "dataGrid" && n.props?.data?.table === "delivery_graph_runs",
333
+ );
334
+ assert(grid, "delivery-graphs page must have an in-flight grid over delivery_graph_runs");
335
+ const linkCol = (grid.props?.columns ?? []).find((c: Json) => c.link?.page === "delivery-graph-detail");
336
+ assert(
337
+ linkCol && linkCol.link?.keyField === "run_key",
338
+ "the in-flight grid must link a column to delivery-graph-detail by run_key",
339
+ );
340
+
341
+ // 3) The per-graph detail page reads the run aggregate scoped to the route param (run_key).
342
+ const detail = JSON.parse(readFileSync(`${ROOT}pages/delivery-graph-detail.page.json`, "utf8"));
343
+ const runGrid = (detail.nodes ?? []).find(
344
+ (n: Json) => n.type === "dataGrid" && n.props?.data?.table === "delivery_graph_runs",
345
+ );
346
+ assert(runGrid, "delivery-graph-detail must bind a grid to delivery_graph_runs");
347
+ assert(
348
+ (runGrid.props?.data?.filter ?? []).some((fl: Json) => fl.field === "run_key" && fl.eqParam === true),
349
+ "delivery-graph-detail must scope its run grid to the route param (run_key eqParam)",
350
+ );
351
+
352
+ // 4) The Overview no longer claims only three surfaces, and its 4th section links to the detail page.
353
+ const overview = JSON.parse(readFileSync(`${ROOT}pages/overview.page.json`, "utf8"));
354
+ const subtitle = (overview.nodes ?? []).find((n: Json) => n.id === "subtitle");
355
+ assert(
356
+ typeof subtitle?.props?.text === "string" && !subtitle.props.text.includes("three dispatch surfaces"),
357
+ "overview subtitle must no longer say 'three dispatch surfaces' (a delivery graph is a 4th)",
358
+ );
359
+ const ovGrid = (overview.nodes ?? []).find(
360
+ (n: Json) => n.type === "dataGrid" && n.props?.data?.table === "delivery_graph_runs",
361
+ );
362
+ const ovLink = (ovGrid?.props?.columns ?? []).find((c: Json) => c.link?.page === "delivery-graph-detail");
363
+ assert(
364
+ ovLink && ovLink.link?.keyField === "run_key",
365
+ "overview delivery-graphs section must link its item to delivery-graph-detail by run_key",
366
+ );
367
+ });