@nanobpm/nano-workforce 0.208.4 → 0.209.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/agentCompletion.test.ts +155 -0
- package/app/agentCompletion.ts +39 -0
- package/app/mergePolicyLock.test.ts +44 -0
- package/app/mergePolicyLock.ts +25 -0
- package/app/mergeStatus.test.ts +14 -12
- package/app/migration132.test.ts +72 -0
- package/app/pullRequestReadModel.test.ts +1 -1
- package/app/service.test.ts +168 -1
- package/app/service.ts +182 -26
- package/build-info.json +4 -4
- package/db/migrations/132_pull_requests_merge_policy.sql +22 -0
- package/db/migrations/133_pull_requests_read_model_merge_policy.sql +51 -0
- package/openapi.yaml +13 -6
- package/operations/listActivePrs.test.ts +207 -3
- package/operations/listActivePrs.ts +13 -9
- package/package.json +1 -1
- package/pages/_nav.json +12 -1
- package/pages/auto-resolutions.page.json +14 -1
- package/pages/board.page.json +14 -1
- package/pages/cockpit.page.json +14 -1
- package/pages/delivery-graph-detail.page.json +14 -1
- package/pages/delivery-graphs.page.json +14 -1
- package/pages/epic-detail.page.json +14 -1
- package/pages/epic.page.json +14 -1
- package/pages/feature.page.json +14 -1
- package/pages/home.page.json +14 -1
- package/pages/lineage.page.json +14 -1
- package/pages/mcp.page.json +14 -1
- package/pages/overview.page.json +14 -1
- package/pages/tasks.page.json +14 -1
- package/pages/velocity.page.json +14 -1
- package/scripts/sync-nav.test.ts +38 -0
package/openapi.yaml
CHANGED
|
@@ -177,23 +177,27 @@ components:
|
|
|
177
177
|
additionalProperties: false
|
|
178
178
|
description: >-
|
|
179
179
|
The effective merge policy for this PR's run (issue #916). `requested` is the run's merge
|
|
180
|
-
intent — the
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
180
|
+
intent — served from the durable `pull_requests` projection (issue #947) that
|
|
181
|
+
`submitPr`/`startMerge` stamp, falling back for a pre-projection (legacy) row to the
|
|
182
|
+
loop's `autoMerge` process variable (then the inverse of the legacy `convergeOnly`
|
|
183
|
+
variable, then the historical default `true` when the instance is unreadable).
|
|
184
|
+
`enabled` is the deployment's global `NANO_PR_AUTO_MERGE` switch.
|
|
184
185
|
`effective` is `requested && enabled && !humanApproval` — the run merges with no further
|
|
185
186
|
gate. `blockedBy` names WHY a requested merge will not proceed: `env` (the global switch
|
|
186
187
|
is off) or `human-approval` (parked at the merge-approval task); null when the merge is
|
|
187
|
-
effective or none was requested (converge-only).
|
|
188
|
+
effective or none was requested (converge-only). `stale` is the timing-guard marker
|
|
189
|
+
(issue #947): true when the policy could not be verified and the row was served from
|
|
190
|
+
the historical default rather than blocking the serve.
|
|
188
191
|
required:
|
|
189
192
|
- requested
|
|
190
193
|
- enabled
|
|
191
194
|
- effective
|
|
192
195
|
- blockedBy
|
|
196
|
+
- stale
|
|
193
197
|
properties:
|
|
194
198
|
requested:
|
|
195
199
|
type: boolean
|
|
196
|
-
description: The run's merge intent (the loop's `autoMerge` variable
|
|
200
|
+
description: The run's merge intent (the durable row projection, or the loop's `autoMerge` variable / the inverse of its legacy `convergeOnly` for a pre-projection row).
|
|
197
201
|
enabled:
|
|
198
202
|
type: boolean
|
|
199
203
|
description: The deployment's global NANO_PR_AUTO_MERGE switch.
|
|
@@ -205,6 +209,9 @@ components:
|
|
|
205
209
|
nullable: true
|
|
206
210
|
enum: ["env", "human-approval", null]
|
|
207
211
|
description: Why a requested merge will not proceed; null when effective or converge-only.
|
|
212
|
+
stale:
|
|
213
|
+
type: boolean
|
|
214
|
+
description: True when the policy could not be verified (a legacy row whose fallback engine read failed) and the row was served from the historical default instead of blocking (issue #947).
|
|
208
215
|
incident:
|
|
209
216
|
type: object
|
|
210
217
|
nullable: true
|
|
@@ -265,7 +265,7 @@ test("issue #936: N active rows cost a constant 3 engine variable reads, not 3N
|
|
|
265
265
|
);
|
|
266
266
|
// The batch still projects the per-row policy correctly.
|
|
267
267
|
for (const pr of res.body.prs) {
|
|
268
|
-
assertEquals(pr.merge, { requested: true, enabled: true, effective: true, blockedBy: null });
|
|
268
|
+
assertEquals(pr.merge, { requested: true, enabled: true, effective: true, blockedBy: null, stale: false });
|
|
269
269
|
}
|
|
270
270
|
});
|
|
271
271
|
|
|
@@ -420,7 +420,7 @@ test("PR #937 r4: a failed probe after a partial batch drops the partial entry (
|
|
|
420
420
|
} as any;
|
|
421
421
|
const res = (await handler(input(), { ...memApp([prRow936(1)]), engine: failing } as AppApi)) as any;
|
|
422
422
|
assertEquals(res.status, 200);
|
|
423
|
-
assertEquals(res.body.prs[0].merge, { requested: true, enabled: true, effective: true, blockedBy: null });
|
|
423
|
+
assertEquals(res.body.prs[0].merge, { requested: true, enabled: true, effective: true, blockedBy: null, stale: true });
|
|
424
424
|
});
|
|
425
425
|
|
|
426
426
|
test("PR #937 r4: a cut-off instance is recovered with name-filtered (untruncatable) root probes", async () => {
|
|
@@ -476,5 +476,209 @@ test("PR #937 r3: a failed BATCH read still degrades every row to the default (n
|
|
|
476
476
|
} as any;
|
|
477
477
|
const res = (await handler(input(), { ...memApp([prRow936(1)]), engine: failing } as AppApi)) as any;
|
|
478
478
|
assertEquals(res.status, 200);
|
|
479
|
-
assertEquals(res.body.prs[0].merge, { requested: true, enabled: true, effective: true, blockedBy: null });
|
|
479
|
+
assertEquals(res.body.prs[0].merge, { requested: true, enabled: true, effective: true, blockedBy: null, stale: true });
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
// ── Issue #947: /status serves the merge policy from the app's OWN projection — ZERO engine reads ─
|
|
483
|
+
// The #936/#937 batching left /status at 3 engine-wide `searchVariables` pages plus a per-(instance,
|
|
484
|
+
// name) probe for any name cut from its page. On the loaded merlin host (v0.208.1, 9 PRs) the
|
|
485
|
+
// variables search itself takes ~1.4 s and the engine-wide pages silently truncate, so every active
|
|
486
|
+
// row paid probes — ~30 s per serve and a 502 behind the console proxy. The fix for the CLASS: the
|
|
487
|
+
// merge policy is written onto the `pull_requests` row by the writers that already know it
|
|
488
|
+
// (`submitPr` stamps `merge_requested` at submit/reopen and `merge_human_approval` when it gates a
|
|
489
|
+
// live loop; `startMerge` stamps the merge-stage policy), so the hot read path serves it from the
|
|
490
|
+
// app's own DB and never touches the engine. A NULL `merge_requested` marks a row written before
|
|
491
|
+
// this projection existed; only those legacy rows fall back to the #937 batched engine read.
|
|
492
|
+
|
|
493
|
+
/** A PR row carrying the durable merge-policy projection (issue #947): the shape every writer
|
|
494
|
+
* stamps from this fix on, so /status never pays an engine round-trip for it. */
|
|
495
|
+
function prRow947(n: number, over: Record<string, unknown> = {}) {
|
|
496
|
+
return { ...prRow936(n), merge_requested: 1, merge_human_approval: 0, ...over };
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/** An engine double that counts calls and fails the test if it is read at all. */
|
|
500
|
+
function countingEngine(rows: { name: string; value: string; processInstanceKey: string; scopeKey: string }[] = []) {
|
|
501
|
+
const queries: { processInstanceKey?: string; scopeKey?: string; name?: string }[] = [];
|
|
502
|
+
return {
|
|
503
|
+
queries,
|
|
504
|
+
searchVariables: async (q: { processInstanceKey?: string; scopeKey?: string; name?: string }) => {
|
|
505
|
+
queries.push(q);
|
|
506
|
+
return rows
|
|
507
|
+
.filter(
|
|
508
|
+
(r) =>
|
|
509
|
+
(q.processInstanceKey == null || r.processInstanceKey === q.processInstanceKey) &&
|
|
510
|
+
(q.scopeKey == null || r.scopeKey === q.scopeKey) &&
|
|
511
|
+
(q.name == null || r.name === q.name),
|
|
512
|
+
)
|
|
513
|
+
.slice(0, 10);
|
|
514
|
+
},
|
|
515
|
+
} as any;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
test("issue #947: N policy-stamped rows cost ZERO engine reads — /status serves its own projection", async () => {
|
|
519
|
+
// Red against the pre-fix shape: `activePrs` consulted the engine for every row, so this fails
|
|
520
|
+
// with 3N+ calls. Green when the merge policy is served from the `pull_requests` projection.
|
|
521
|
+
const N = 9; // the merlin fleet size that took 29.6 s
|
|
522
|
+
const rows = Array.from({ length: N }, (_, i) => prRow947(i + 1));
|
|
523
|
+
const engine = countingEngine();
|
|
524
|
+
const started = Date.now();
|
|
525
|
+
const res = (await handler(input(), { ...memApp(rows), engine } as AppApi)) as any;
|
|
526
|
+
const elapsed = Date.now() - started;
|
|
527
|
+
assertEquals(res.status, 200);
|
|
528
|
+
assertEquals(res.body.count, N);
|
|
529
|
+
assertEquals(
|
|
530
|
+
engine.queries.length,
|
|
531
|
+
0,
|
|
532
|
+
"a row whose merge policy is projected on the pull_requests row never costs an engine round-trip",
|
|
533
|
+
);
|
|
534
|
+
assert(
|
|
535
|
+
elapsed < 1000,
|
|
536
|
+
`the projected read serves ${N} rows in well under the 2 s acceptance budget (took ${elapsed} ms)`,
|
|
537
|
+
);
|
|
538
|
+
for (const pr of res.body.prs) {
|
|
539
|
+
assertEquals(pr.merge, { requested: true, enabled: true, effective: true, blockedBy: null, stale: false });
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
test("issue #947: the projected policy drives requested/blockedBy per row (review-only + approval gate)", async () => {
|
|
544
|
+
const rows = [
|
|
545
|
+
prRow947(1, { merge_requested: 0, merge_human_approval: 0 }), // review-only
|
|
546
|
+
prRow947(2, { merge_requested: 1, merge_human_approval: 1 }), // parked at merge-approval
|
|
547
|
+
prRow947(3, { merge_requested: 1, merge_human_approval: null }), // gated, approval outstanding
|
|
548
|
+
prRow947(4, { merge_requested: 1, merge_human_approval: 0 }), // approved / never gated
|
|
549
|
+
];
|
|
550
|
+
const engine = countingEngine();
|
|
551
|
+
const res = (await handler(input(), { ...memApp(rows), engine } as AppApi)) as any;
|
|
552
|
+
assertEquals(engine.queries.length, 0);
|
|
553
|
+
const byKey = (k: string) => res.body.prs.find((p: any) => p.prKey === k).merge;
|
|
554
|
+
assertEquals(byKey("o/r#1"), { requested: false, enabled: true, effective: false, blockedBy: null, stale: false });
|
|
555
|
+
assertEquals(byKey("o/r#2"), { requested: true, enabled: true, effective: false, blockedBy: "human-approval", stale: false });
|
|
556
|
+
assertEquals(byKey("o/r#3"), { requested: true, enabled: true, effective: false, blockedBy: "human-approval", stale: false });
|
|
557
|
+
assertEquals(byKey("o/r#4"), { requested: true, enabled: true, effective: true, blockedBy: null, stale: false });
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
test("issue #947: ONLY legacy (unstamped) rows fall back to the batched engine read — stamped rows never do", async () => {
|
|
561
|
+
// Expand-and-contract back-compat: a row written before the projection existed carries
|
|
562
|
+
// `merge_requested: NULL`. It keeps the #937 batched engine read (3 constant calls for the whole
|
|
563
|
+
// legacy set, never per-row), while its stamped siblings are served from the projection.
|
|
564
|
+
const rows = [
|
|
565
|
+
prRow947(1, { merge_requested: 1, merge_human_approval: 0 }), // projected
|
|
566
|
+
prRow936(2), // legacy: no projection columns
|
|
567
|
+
];
|
|
568
|
+
const engine = countingEngine([
|
|
569
|
+
v("PI-2", "autoMerge", false),
|
|
570
|
+
v("PI-2", "convergeOnly", true),
|
|
571
|
+
v("PI-2", "humanApproval", true),
|
|
572
|
+
]);
|
|
573
|
+
const res = (await handler(input(), { ...memApp(rows), engine } as AppApi)) as any;
|
|
574
|
+
assertEquals(res.status, 200);
|
|
575
|
+
assertEquals(
|
|
576
|
+
engine.queries.length,
|
|
577
|
+
3,
|
|
578
|
+
"the legacy fallback stays the constant 3 name-filtered reads — never a per-row fan-out",
|
|
579
|
+
);
|
|
580
|
+
const byKey = (k: string) => res.body.prs.find((p: any) => p.prKey === k).merge;
|
|
581
|
+
assertEquals(byKey("o/r#1"), { requested: true, enabled: true, effective: true, blockedBy: null, stale: false });
|
|
582
|
+
assertEquals(byKey("o/r#2"), { requested: false, enabled: true, effective: false, blockedBy: null, stale: false });
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
test("issue #947: a legacy row whose engine read fails degrades to the default, flagged stale", async () => {
|
|
586
|
+
// The timing guard's stale marker: when the policy cannot be verified (engine unreachable for a
|
|
587
|
+
// legacy row), the row is served immediately from the historical default and flagged `stale`
|
|
588
|
+
// rather than blocking the serve.
|
|
589
|
+
const failing = {
|
|
590
|
+
searchVariables: async () => {
|
|
591
|
+
throw new Error("engine unreachable");
|
|
592
|
+
},
|
|
593
|
+
} as any;
|
|
594
|
+
const res = (await handler(input(), { ...memApp([prRow936(1)]), engine: failing } as AppApi)) as any;
|
|
595
|
+
assertEquals(res.status, 200);
|
|
596
|
+
assertEquals(res.body.prs[0].merge, { requested: true, enabled: true, effective: true, blockedBy: null, stale: true });
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
// ── PR #950 review: the legacy fallback is BOUNDED and SELF-HEALING ───────────────────────────────
|
|
600
|
+
test("PR #950 review: truncated pages bound legacy per-instance probes, regardless of legacy row count", async () => {
|
|
601
|
+
const { MAX_PROBED_LEGACY_INSTANCES } = await import("../app/service.ts");
|
|
602
|
+
const N = 20; // an upgraded install: every row unstamped
|
|
603
|
+
const rows = Array.from({ length: N }, (_, i) => prRow936(i + 1));
|
|
604
|
+
// Every engine-wide page is truncated to nothing, so every legacy instance would need probes.
|
|
605
|
+
const queries: any[] = [];
|
|
606
|
+
const engine = {
|
|
607
|
+
searchVariables: async (q: any) => {
|
|
608
|
+
queries.push(q);
|
|
609
|
+
if (q.processInstanceKey == null) return [];
|
|
610
|
+
return [v(q.processInstanceKey, q.name, q.name === "autoMerge")];
|
|
611
|
+
},
|
|
612
|
+
} as any;
|
|
613
|
+
const res = (await handler(input(), { ...memApp(rows), engine } as AppApi)) as any;
|
|
614
|
+
assertEquals(res.status, 200);
|
|
615
|
+
assertEquals(res.body.count, N);
|
|
616
|
+
assert(
|
|
617
|
+
queries.length <= 3 + 3 * MAX_PROBED_LEGACY_INSTANCES,
|
|
618
|
+
`engine calls stay bounded (got ${queries.length} for ${N} legacy rows)`,
|
|
619
|
+
);
|
|
620
|
+
const stale = res.body.prs.filter((p: any) => p.merge.stale).length;
|
|
621
|
+
assertEquals(stale, N - MAX_PROBED_LEGACY_INSTANCES, "unprobed rows degrade to the stale default for this serve");
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
test("PR #950 review: a legacy row whose policy the engine proved is hydrated onto the projection", async () => {
|
|
625
|
+
const rows: any[] = [prRow936(1), prRow936(2)];
|
|
626
|
+
const engine = countingEngine([
|
|
627
|
+
v("PI-1", "autoMerge", false),
|
|
628
|
+
v("PI-1", "convergeOnly", true),
|
|
629
|
+
v("PI-1", "humanApproval", false),
|
|
630
|
+
v("PI-2", "autoMerge", true), // humanApproval PROVEN absent by its root probe (pre-humanApproval loop)
|
|
631
|
+
v("PI-2", "convergeOnly", false),
|
|
632
|
+
]);
|
|
633
|
+
const app: any = memApp(rows);
|
|
634
|
+
const writes: unknown[][] = [];
|
|
635
|
+
app.data.open = () => ({
|
|
636
|
+
exec: async (sql: string, params: unknown[]) => {
|
|
637
|
+
assert(/WHERE "pr_key" = \? AND "process_key" = \? AND "merge_requested" IS NULL$/.test(sql), "guarded write-back");
|
|
638
|
+
writes.push(params);
|
|
639
|
+
return { changed: 1 };
|
|
640
|
+
},
|
|
641
|
+
});
|
|
642
|
+
const res = (await handler(input(), { ...app, engine } as AppApi)) as any;
|
|
643
|
+
assertEquals(res.status, 200);
|
|
644
|
+
assertEquals(
|
|
645
|
+
[...writes].sort((a, b) => String(a[2]).localeCompare(String(b[2]))),
|
|
646
|
+
[
|
|
647
|
+
[0, 0, "o/r#1", "PI-1"],
|
|
648
|
+
[1, 0, "o/r#2", "PI-2"],
|
|
649
|
+
],
|
|
650
|
+
"complete reads (incl. proven-absent humanApproval) are written back",
|
|
651
|
+
);
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
test("PR #950 review: hydration never overwrites a concurrently stamped approval decision", async () => {
|
|
655
|
+
const rows: any[] = [prRow936(1)];
|
|
656
|
+
const engine = countingEngine([v("PI-1", "autoMerge", true), v("PI-1", "humanApproval", true)]);
|
|
657
|
+
const app: any = memApp(rows);
|
|
658
|
+
const sqls: string[] = [];
|
|
659
|
+
app.data.open = () => ({
|
|
660
|
+
exec: async (sql: string) => {
|
|
661
|
+
sqls.push(sql);
|
|
662
|
+
return { changed: 1 };
|
|
663
|
+
},
|
|
664
|
+
});
|
|
665
|
+
await handler(input(), { ...app, engine } as AppApi);
|
|
666
|
+
assertEquals(sqls.length, 1);
|
|
667
|
+
assert(/"merge_human_approval" = COALESCE\("merge_human_approval", \?\)/.test(sqls[0]), "approval stamp preserved");
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
test("PR #950 review: the bounded probe window rotates so unhydratable leading rows cannot starve later rows", async () => {
|
|
671
|
+
const { MAX_PROBED_LEGACY_INSTANCES } = await import("../app/service.ts");
|
|
672
|
+
const N = MAX_PROBED_LEGACY_INSTANCES * 3;
|
|
673
|
+
const rows = Array.from({ length: N }, (_, i) => prRow936(i + 1));
|
|
674
|
+
const probed = new Set<string>();
|
|
675
|
+
const engine = {
|
|
676
|
+
searchVariables: async (q: any) => {
|
|
677
|
+
if (q.processInstanceKey == null) return [];
|
|
678
|
+
probed.add(q.processInstanceKey);
|
|
679
|
+
return []; // nothing provable -> never hydrated, stays in the queue
|
|
680
|
+
},
|
|
681
|
+
} as any;
|
|
682
|
+
for (let i = 0; i < 3; i++) await handler(input(), { ...memApp(rows), engine } as AppApi);
|
|
683
|
+
assertEquals(probed.size, N, "every legacy row is eventually probed");
|
|
480
684
|
});
|
|
@@ -20,11 +20,13 @@ export default defineOperation("listActivePrs", async ({ req }, app) => {
|
|
|
20
20
|
app.log.warn("listActivePrs rejected: missing/invalid shared secret");
|
|
21
21
|
return { status: 401, body: { error: "unauthorized" } };
|
|
22
22
|
}
|
|
23
|
-
// Per-phase timing (issue #936): /status regressed to ~13 s (and
|
|
24
|
-
// into proxy 502s) on an unprofiled per-row engine fan-out
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
23
|
+
// Per-phase timing (issue #936, budget raised for #947): /status regressed to ~13 s (and
|
|
24
|
+
// serialised concurrent callers into proxy 502s) on an unprofiled per-row engine fan-out, then
|
|
25
|
+
// to ~30 s on merlin when the batched fallback's page-cut probes still scaled with the fleet.
|
|
26
|
+
// The merge-policy engine read is now served from the app's OWN projection (zero engine reads
|
|
27
|
+
// for every projected row; the #937 batched read only for legacy rows), and these phase timings
|
|
28
|
+
// make any future regression attributable from the logs instead of rediscovered by hand. Debug
|
|
29
|
+
// level: /status is a hot polling endpoint, so this must not spam info.
|
|
28
30
|
const t0 = Date.now();
|
|
29
31
|
const [prs, deliveries] = await Promise.all([activePrs(app.data, app.engine), activeDeliveries(app.data)]);
|
|
30
32
|
const readMs = Date.now() - t0;
|
|
@@ -42,7 +44,9 @@ export default defineOperation("listActivePrs", async ({ req }, app) => {
|
|
|
42
44
|
return { status: 200, body: { count: prs.length, prs, deliveries, version: { version, gitSha } } };
|
|
43
45
|
});
|
|
44
46
|
|
|
45
|
-
/** The wall-clock budget over which a /status serve is logged as slow (issue #
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
|
|
47
|
+
/** The wall-clock budget over which a /status serve is logged as slow (issue #947: raised from the
|
|
48
|
+
* #936 1 s to 5 s — the console-proxy timeout is the hard ceiling this must never approach; the
|
|
49
|
+
* acceptance target is 2 s at fleet scale, so 5 s leaves headroom that is still an unmistakable
|
|
50
|
+
* regression signal an operator can grep for). The endpoint targets well under 1 s for a handful
|
|
51
|
+
* of rows now that the merge policy is served from the app's own projection. */
|
|
52
|
+
const STATUS_SLOW_MS = 5_000;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/nano-workforce",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.209.0",
|
|
4
4
|
"description": "Nano Workforce — an Agent Graph Orchestration application for Agentic SDLC: durable BPMN processes that coordinate a graph of AI agents across the software delivery lifecycle.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "main.ts",
|
package/pages/_nav.json
CHANGED
|
@@ -11,7 +11,18 @@
|
|
|
11
11
|
{ "label": "Convergence", "page": "home" },
|
|
12
12
|
{ "label": "Epics", "page": "epic" },
|
|
13
13
|
{ "label": "Feature", "page": "feature" },
|
|
14
|
-
{
|
|
14
|
+
{
|
|
15
|
+
"label": "Delivery Graphs",
|
|
16
|
+
"page": "delivery-graphs",
|
|
17
|
+
"badge": {
|
|
18
|
+
"source": "app",
|
|
19
|
+
"table": "delivery_graph_proposals",
|
|
20
|
+
"filter": [{ "field": "status", "eq": "staged" }],
|
|
21
|
+
"tone": "ok",
|
|
22
|
+
"refreshMs": 5000,
|
|
23
|
+
"hideWhenZero": true
|
|
24
|
+
}
|
|
25
|
+
},
|
|
15
26
|
{
|
|
16
27
|
"label": "Tasks",
|
|
17
28
|
"page": "tasks",
|
|
@@ -35,7 +35,20 @@
|
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"label": "Delivery Graphs",
|
|
38
|
-
"page": "delivery-graphs"
|
|
38
|
+
"page": "delivery-graphs",
|
|
39
|
+
"badge": {
|
|
40
|
+
"source": "app",
|
|
41
|
+
"table": "delivery_graph_proposals",
|
|
42
|
+
"filter": [
|
|
43
|
+
{
|
|
44
|
+
"field": "status",
|
|
45
|
+
"eq": "staged"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"tone": "ok",
|
|
49
|
+
"refreshMs": 5000,
|
|
50
|
+
"hideWhenZero": true
|
|
51
|
+
}
|
|
39
52
|
},
|
|
40
53
|
{
|
|
41
54
|
"label": "Tasks",
|
package/pages/board.page.json
CHANGED
|
@@ -35,7 +35,20 @@
|
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"label": "Delivery Graphs",
|
|
38
|
-
"page": "delivery-graphs"
|
|
38
|
+
"page": "delivery-graphs",
|
|
39
|
+
"badge": {
|
|
40
|
+
"source": "app",
|
|
41
|
+
"table": "delivery_graph_proposals",
|
|
42
|
+
"filter": [
|
|
43
|
+
{
|
|
44
|
+
"field": "status",
|
|
45
|
+
"eq": "staged"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"tone": "ok",
|
|
49
|
+
"refreshMs": 5000,
|
|
50
|
+
"hideWhenZero": true
|
|
51
|
+
}
|
|
39
52
|
},
|
|
40
53
|
{
|
|
41
54
|
"label": "Tasks",
|
package/pages/cockpit.page.json
CHANGED
|
@@ -35,7 +35,20 @@
|
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"label": "Delivery Graphs",
|
|
38
|
-
"page": "delivery-graphs"
|
|
38
|
+
"page": "delivery-graphs",
|
|
39
|
+
"badge": {
|
|
40
|
+
"source": "app",
|
|
41
|
+
"table": "delivery_graph_proposals",
|
|
42
|
+
"filter": [
|
|
43
|
+
{
|
|
44
|
+
"field": "status",
|
|
45
|
+
"eq": "staged"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"tone": "ok",
|
|
49
|
+
"refreshMs": 5000,
|
|
50
|
+
"hideWhenZero": true
|
|
51
|
+
}
|
|
39
52
|
},
|
|
40
53
|
{
|
|
41
54
|
"label": "Tasks",
|
|
@@ -35,7 +35,20 @@
|
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"label": "Delivery Graphs",
|
|
38
|
-
"page": "delivery-graphs"
|
|
38
|
+
"page": "delivery-graphs",
|
|
39
|
+
"badge": {
|
|
40
|
+
"source": "app",
|
|
41
|
+
"table": "delivery_graph_proposals",
|
|
42
|
+
"filter": [
|
|
43
|
+
{
|
|
44
|
+
"field": "status",
|
|
45
|
+
"eq": "staged"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"tone": "ok",
|
|
49
|
+
"refreshMs": 5000,
|
|
50
|
+
"hideWhenZero": true
|
|
51
|
+
}
|
|
39
52
|
},
|
|
40
53
|
{
|
|
41
54
|
"label": "Tasks",
|
|
@@ -35,7 +35,20 @@
|
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"label": "Delivery Graphs",
|
|
38
|
-
"page": "delivery-graphs"
|
|
38
|
+
"page": "delivery-graphs",
|
|
39
|
+
"badge": {
|
|
40
|
+
"source": "app",
|
|
41
|
+
"table": "delivery_graph_proposals",
|
|
42
|
+
"filter": [
|
|
43
|
+
{
|
|
44
|
+
"field": "status",
|
|
45
|
+
"eq": "staged"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"tone": "ok",
|
|
49
|
+
"refreshMs": 5000,
|
|
50
|
+
"hideWhenZero": true
|
|
51
|
+
}
|
|
39
52
|
},
|
|
40
53
|
{
|
|
41
54
|
"label": "Tasks",
|
|
@@ -35,7 +35,20 @@
|
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"label": "Delivery Graphs",
|
|
38
|
-
"page": "delivery-graphs"
|
|
38
|
+
"page": "delivery-graphs",
|
|
39
|
+
"badge": {
|
|
40
|
+
"source": "app",
|
|
41
|
+
"table": "delivery_graph_proposals",
|
|
42
|
+
"filter": [
|
|
43
|
+
{
|
|
44
|
+
"field": "status",
|
|
45
|
+
"eq": "staged"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"tone": "ok",
|
|
49
|
+
"refreshMs": 5000,
|
|
50
|
+
"hideWhenZero": true
|
|
51
|
+
}
|
|
39
52
|
},
|
|
40
53
|
{
|
|
41
54
|
"label": "Tasks",
|
package/pages/epic.page.json
CHANGED
|
@@ -35,7 +35,20 @@
|
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"label": "Delivery Graphs",
|
|
38
|
-
"page": "delivery-graphs"
|
|
38
|
+
"page": "delivery-graphs",
|
|
39
|
+
"badge": {
|
|
40
|
+
"source": "app",
|
|
41
|
+
"table": "delivery_graph_proposals",
|
|
42
|
+
"filter": [
|
|
43
|
+
{
|
|
44
|
+
"field": "status",
|
|
45
|
+
"eq": "staged"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"tone": "ok",
|
|
49
|
+
"refreshMs": 5000,
|
|
50
|
+
"hideWhenZero": true
|
|
51
|
+
}
|
|
39
52
|
},
|
|
40
53
|
{
|
|
41
54
|
"label": "Tasks",
|
package/pages/feature.page.json
CHANGED
|
@@ -35,7 +35,20 @@
|
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"label": "Delivery Graphs",
|
|
38
|
-
"page": "delivery-graphs"
|
|
38
|
+
"page": "delivery-graphs",
|
|
39
|
+
"badge": {
|
|
40
|
+
"source": "app",
|
|
41
|
+
"table": "delivery_graph_proposals",
|
|
42
|
+
"filter": [
|
|
43
|
+
{
|
|
44
|
+
"field": "status",
|
|
45
|
+
"eq": "staged"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"tone": "ok",
|
|
49
|
+
"refreshMs": 5000,
|
|
50
|
+
"hideWhenZero": true
|
|
51
|
+
}
|
|
39
52
|
},
|
|
40
53
|
{
|
|
41
54
|
"label": "Tasks",
|
package/pages/home.page.json
CHANGED
|
@@ -35,7 +35,20 @@
|
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"label": "Delivery Graphs",
|
|
38
|
-
"page": "delivery-graphs"
|
|
38
|
+
"page": "delivery-graphs",
|
|
39
|
+
"badge": {
|
|
40
|
+
"source": "app",
|
|
41
|
+
"table": "delivery_graph_proposals",
|
|
42
|
+
"filter": [
|
|
43
|
+
{
|
|
44
|
+
"field": "status",
|
|
45
|
+
"eq": "staged"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"tone": "ok",
|
|
49
|
+
"refreshMs": 5000,
|
|
50
|
+
"hideWhenZero": true
|
|
51
|
+
}
|
|
39
52
|
},
|
|
40
53
|
{
|
|
41
54
|
"label": "Tasks",
|
package/pages/lineage.page.json
CHANGED
|
@@ -35,7 +35,20 @@
|
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"label": "Delivery Graphs",
|
|
38
|
-
"page": "delivery-graphs"
|
|
38
|
+
"page": "delivery-graphs",
|
|
39
|
+
"badge": {
|
|
40
|
+
"source": "app",
|
|
41
|
+
"table": "delivery_graph_proposals",
|
|
42
|
+
"filter": [
|
|
43
|
+
{
|
|
44
|
+
"field": "status",
|
|
45
|
+
"eq": "staged"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"tone": "ok",
|
|
49
|
+
"refreshMs": 5000,
|
|
50
|
+
"hideWhenZero": true
|
|
51
|
+
}
|
|
39
52
|
},
|
|
40
53
|
{
|
|
41
54
|
"label": "Tasks",
|
package/pages/mcp.page.json
CHANGED
|
@@ -35,7 +35,20 @@
|
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"label": "Delivery Graphs",
|
|
38
|
-
"page": "delivery-graphs"
|
|
38
|
+
"page": "delivery-graphs",
|
|
39
|
+
"badge": {
|
|
40
|
+
"source": "app",
|
|
41
|
+
"table": "delivery_graph_proposals",
|
|
42
|
+
"filter": [
|
|
43
|
+
{
|
|
44
|
+
"field": "status",
|
|
45
|
+
"eq": "staged"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"tone": "ok",
|
|
49
|
+
"refreshMs": 5000,
|
|
50
|
+
"hideWhenZero": true
|
|
51
|
+
}
|
|
39
52
|
},
|
|
40
53
|
{
|
|
41
54
|
"label": "Tasks",
|
package/pages/overview.page.json
CHANGED
|
@@ -35,7 +35,20 @@
|
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"label": "Delivery Graphs",
|
|
38
|
-
"page": "delivery-graphs"
|
|
38
|
+
"page": "delivery-graphs",
|
|
39
|
+
"badge": {
|
|
40
|
+
"source": "app",
|
|
41
|
+
"table": "delivery_graph_proposals",
|
|
42
|
+
"filter": [
|
|
43
|
+
{
|
|
44
|
+
"field": "status",
|
|
45
|
+
"eq": "staged"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"tone": "ok",
|
|
49
|
+
"refreshMs": 5000,
|
|
50
|
+
"hideWhenZero": true
|
|
51
|
+
}
|
|
39
52
|
},
|
|
40
53
|
{
|
|
41
54
|
"label": "Tasks",
|
package/pages/tasks.page.json
CHANGED
|
@@ -35,7 +35,20 @@
|
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"label": "Delivery Graphs",
|
|
38
|
-
"page": "delivery-graphs"
|
|
38
|
+
"page": "delivery-graphs",
|
|
39
|
+
"badge": {
|
|
40
|
+
"source": "app",
|
|
41
|
+
"table": "delivery_graph_proposals",
|
|
42
|
+
"filter": [
|
|
43
|
+
{
|
|
44
|
+
"field": "status",
|
|
45
|
+
"eq": "staged"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"tone": "ok",
|
|
49
|
+
"refreshMs": 5000,
|
|
50
|
+
"hideWhenZero": true
|
|
51
|
+
}
|
|
39
52
|
},
|
|
40
53
|
{
|
|
41
54
|
"label": "Tasks",
|
package/pages/velocity.page.json
CHANGED
|
@@ -35,7 +35,20 @@
|
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"label": "Delivery Graphs",
|
|
38
|
-
"page": "delivery-graphs"
|
|
38
|
+
"page": "delivery-graphs",
|
|
39
|
+
"badge": {
|
|
40
|
+
"source": "app",
|
|
41
|
+
"table": "delivery_graph_proposals",
|
|
42
|
+
"filter": [
|
|
43
|
+
{
|
|
44
|
+
"field": "status",
|
|
45
|
+
"eq": "staged"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"tone": "ok",
|
|
49
|
+
"refreshMs": 5000,
|
|
50
|
+
"hideWhenZero": true
|
|
51
|
+
}
|
|
39
52
|
},
|
|
40
53
|
{
|
|
41
54
|
"label": "Tasks",
|