@zaip-ai/cli 1.3.2 → 1.3.5
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/LICENSE +6 -3
- package/README.md +8 -2
- package/dist/services/codex-pre-tool-hook-template.js +1 -1
- package/dist/services/runtime-template.test.js +9 -8
- package/dist/services/runtime-template.test.js.map +1 -1
- package/package.json +2 -2
- package/templates/.agents/dashboard/.zaip-managed.json +414 -394
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-ovxurkIk.js → CostCalculationGuidePage-D1mpyYwh.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-BXy6nti1.js → DashboardHelpDialog-CKO47D5s.js} +30 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-vSvvOHQp.js → PricingSettingsDialog-BuSlCbyZ.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-zH758vfs.js → ProjectSettingsPage-CUHFkAhZ.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-CTH2wDs5.js → TraceabilityWorkspace-BmASq2gW.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-Csj6LiAt.js +68 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-B_lDDHn1.js → icons-vendor-VyTTl_ph.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DIwDvCu5.js +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-hPPfVGQv.css +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-B3TJrD4O.js → react-vendor-CTC6vtln.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-7f1j5wx3.js → traceability-9Mtvbwrg.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/index.html +6 -6
- package/templates/.agents/dashboard/apps/frontend/src/App.tsx +74 -10
- package/templates/.agents/dashboard/apps/frontend/src/AppRequestGuards.tsx +44 -0
- package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +103 -2
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-capability-nav.css +11 -0
- package/templates/.agents/dashboard/apps/frontend/src/operations-period-contract.test.ts +14 -0
- package/templates/.agents/dashboard/apps/frontend/src/operations-period.test.ts +3 -6
- package/templates/.agents/dashboard/apps/frontend/src/operations-period.ts +3 -2
- package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +2 -2
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostDeveloperAttributionPanel.test.ts +60 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostDeveloperAttributionPanel.tsx +26 -9
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorNavigation.tsx +189 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +122 -2
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +70 -155
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.helpers.tsx +2 -1
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.test.ts +12 -4
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +67 -24
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +19 -4
- package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.test.ts +59 -0
- package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +55 -2
- package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.test.ts +25 -0
- package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.ts +29 -0
- package/templates/.agents/dashboard/modules/operations/src/react/operations-coverage-toast.test.ts +3 -2
- package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +12 -2
- package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +274 -246
- package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +255 -227
- package/templates/.agents/dashboard/packages/local-runtime/server.mjs +263 -235
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-dimension-facts.test.ts +282 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-dimension-facts.ts +71 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-launch-progress.ts +18 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-stale-seed.ts +50 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +9 -6
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +45 -73
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-seed.test.ts +79 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-work-item-index.test.ts +56 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-page.mjs +26 -8
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-page.test.mjs +45 -4
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-lifecycle.mjs +58 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-lifecycle.test.mjs +81 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +171 -71
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +152 -11
- package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +4 -9
- package/templates/.agents/dashboard/tests/e2e/playwright.config.ts +9 -1
- package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-loading.spec.ts +1 -1
- package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-pagination.spec.ts +4 -0
- package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-period-transition.spec.ts +238 -0
- package/templates/.agents/dashboard/tests/e2e/specs/dashboard-performance-matrix.spec.ts +4 -0
- package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +60 -17
- package/templates/.agents/requirements/autonomous-assurance.md +1 -1
- package/templates/.agents/requirements/comparative-audit.md +1 -1
- package/templates/.agents/requirements/context-engine.md +1 -1
- package/templates/.agents/requirements/cost-observability.md +1 -1
- package/templates/.agents/requirements/dashboard-launch.md +1 -1
- package/templates/.agents/requirements/data-export.md +1 -1
- package/templates/.agents/requirements/design-system.md +1 -1
- package/templates/.agents/requirements/execution-engine.md +1 -1
- package/templates/.agents/requirements/kanban-dashboard.md +1 -1
- package/templates/.agents/requirements/model-orchestration.md +1 -1
- package/templates/.agents/requirements/public-distribution.md +1 -1
- package/templates/.agents/requirements/software-architecture.md +1 -1
- package/templates/.agents/requirements/strategic-improvements.md +1 -1
- package/templates/.agents/requirements/work-lifecycle.md +1 -1
- package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
- package/templates/.agents/runtime/bin/zaip.mjs +7 -7
- package/templates/.agents/runtime/checksums.sha256 +3 -3
- package/templates/.agents/runtime/provenance.json +3 -3
- package/templates/.agents/runtime/sbom.cdx.json +1595 -3491
- package/templates/.agents/skills/zaip-skill-lifecycle-closure/SKILL.md +16 -0
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-local-telemetry.mjs +19 -20
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-local-telemetry.test.mjs +143 -0
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.mjs +14 -0
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.mjs +3 -1
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.test.mjs +21 -0
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/source-size-telemetry/model-identity-reconciliation.mjs +3 -0
- package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +29 -0
- package/templates/template-manifest.json +539 -935
- package/templates/.agents/dashboard/.tmp-run-list.json +0 -254
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-DKwP1stV.js +0 -68
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-B31GMG5O.css +0 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-CuMcoUni.js +0 -2
|
@@ -8,6 +8,50 @@ export function needsAtomicCostSnapshot(dashboardView: unknown, costsLoaded: boo
|
|
|
8
8
|
return false;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* State updates from a period picker are asynchronous. Keep the authoritative
|
|
13
|
+
* loaded flag in the decision so a navigation event cannot use the previous
|
|
14
|
+
* render's matching currency and skip the replacement Cost snapshot.
|
|
15
|
+
*/
|
|
16
|
+
export function shouldLoadCostSummary({
|
|
17
|
+
costsLoaded,
|
|
18
|
+
currency,
|
|
19
|
+
snapshotCurrency,
|
|
20
|
+
}: {
|
|
21
|
+
costsLoaded: boolean;
|
|
22
|
+
currency: string;
|
|
23
|
+
snapshotCurrency: string | undefined;
|
|
24
|
+
}): boolean {
|
|
25
|
+
return !costsLoaded || snapshotCurrency !== currency;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A late summary from another period or currency must never restore stale costs. */
|
|
29
|
+
export function isCurrentCostSummaryRequest({
|
|
30
|
+
aborted,
|
|
31
|
+
currentCurrency,
|
|
32
|
+
currentPeriod,
|
|
33
|
+
currentRequestVersion,
|
|
34
|
+
requestVersion,
|
|
35
|
+
requestedCurrency,
|
|
36
|
+
requestedPeriod,
|
|
37
|
+
}: {
|
|
38
|
+
aborted: boolean;
|
|
39
|
+
currentCurrency: string;
|
|
40
|
+
currentPeriod: { from: string | null; to: string | null };
|
|
41
|
+
currentRequestVersion: number;
|
|
42
|
+
requestVersion: number;
|
|
43
|
+
requestedCurrency: string;
|
|
44
|
+
requestedPeriod: { from: string | null; to: string | null };
|
|
45
|
+
}): boolean {
|
|
46
|
+
return (
|
|
47
|
+
!aborted &&
|
|
48
|
+
requestVersion === currentRequestVersion &&
|
|
49
|
+
requestedCurrency === currentCurrency &&
|
|
50
|
+
requestedPeriod.from === currentPeriod.from &&
|
|
51
|
+
requestedPeriod.to === currentPeriod.to
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
11
55
|
export function isCurrentDashboardRequest(requestVersion: number, currentVersion: number): boolean {
|
|
12
56
|
return requestVersion === currentVersion;
|
|
13
57
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { describe, expect, it } from "vitest";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
isCurrentCostSummaryRequest,
|
|
5
|
+
isCurrentDashboardRequest,
|
|
6
|
+
needsAtomicCostSnapshot,
|
|
7
|
+
shouldLoadCostSummary,
|
|
8
|
+
} from "./App.js";
|
|
4
9
|
|
|
5
10
|
describe("Cost period refresh", () => {
|
|
6
11
|
it("does not retain a prior Cost Monitor snapshot while Todo o Período is loading", () => {
|
|
@@ -16,6 +21,102 @@ describe("Cost period refresh", () => {
|
|
|
16
21
|
expect(exportedSnapshot).toBeNull();
|
|
17
22
|
});
|
|
18
23
|
|
|
24
|
+
it("starts a fresh Cost request when a period change invalidates a same-currency snapshot", () => {
|
|
25
|
+
// This reproduces clicking Monitor de Custos before React commits the
|
|
26
|
+
// `setCosts(null)` render: the event closure can still see BRL, but the
|
|
27
|
+
// ref was invalidated synchronously by the period transition.
|
|
28
|
+
expect(
|
|
29
|
+
shouldLoadCostSummary({
|
|
30
|
+
costsLoaded: false,
|
|
31
|
+
currency: "BRL",
|
|
32
|
+
snapshotCurrency: "BRL",
|
|
33
|
+
}),
|
|
34
|
+
).toBe(true);
|
|
35
|
+
expect(
|
|
36
|
+
shouldLoadCostSummary({
|
|
37
|
+
costsLoaded: true,
|
|
38
|
+
currency: "BRL",
|
|
39
|
+
snapshotCurrency: "BRL",
|
|
40
|
+
}),
|
|
41
|
+
).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("invalidates and aborts an old Cost summary on a global period transition", () => {
|
|
45
|
+
const app = readFileSync(new URL("./App.tsx", import.meta.url), "utf8");
|
|
46
|
+
const periodChange = app.slice(
|
|
47
|
+
app.indexOf("onDateChange={(preset, range) => {"),
|
|
48
|
+
app.indexOf("settings={"),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect(periodChange).toContain("costSummaryAbortRef.current?.abort();");
|
|
52
|
+
expect(periodChange).toContain("++costSummaryRequestVersion.current;");
|
|
53
|
+
expect(app).toContain("isCurrentCostSummaryRequest({");
|
|
54
|
+
expect(app).toContain('if (failedArea === "costs") void loadCostSummary();');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("reloads the invalidated BRL Cost snapshot for Cost Monitor and All Time presentation", () => {
|
|
58
|
+
const app = readFileSync(new URL("./App.tsx", import.meta.url), "utf8");
|
|
59
|
+
const activeCostReload = app.slice(
|
|
60
|
+
app.indexOf("// A global period change invalidates the Cost snapshot"),
|
|
61
|
+
app.indexOf("const loadMoreTraceTasks"),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
expect(activeCostReload).toContain('dashboardView !== "cost-monitor" && operationsPeriod.preset !== "all"');
|
|
65
|
+
expect(activeCostReload).toContain("costSummaryAbortRef.current !== null");
|
|
66
|
+
expect(activeCostReload).toContain("costsLoaded: costsLoadedRef.current");
|
|
67
|
+
expect(activeCostReload).toContain("void loadCostSummary();");
|
|
68
|
+
expect(activeCostReload).toContain("operationsPeriod.preset");
|
|
69
|
+
expect(activeCostReload).not.toContain("failedArea");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("does not make the all-time data request depend on its displayed factual range", () => {
|
|
73
|
+
const app = readFileSync(new URL("./App.tsx", import.meta.url), "utf8");
|
|
74
|
+
|
|
75
|
+
expect(app).toContain('operationsPeriod.preset === "all"');
|
|
76
|
+
expect(app).toContain('? "all"');
|
|
77
|
+
expect(app).toContain("const operationsRequestPeriod = useMemo(");
|
|
78
|
+
expect(app).toContain("[api, currency, operationsRequestPeriod]");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("rejects a late all-time summary after a newer period or currency takes ownership", () => {
|
|
82
|
+
const allTime = { from: null, to: null };
|
|
83
|
+
const currentMonth = { from: "2026-08-01", to: "2026-08-31" };
|
|
84
|
+
|
|
85
|
+
expect(
|
|
86
|
+
isCurrentCostSummaryRequest({
|
|
87
|
+
aborted: false,
|
|
88
|
+
currentCurrency: "BRL",
|
|
89
|
+
currentPeriod: currentMonth,
|
|
90
|
+
currentRequestVersion: 3,
|
|
91
|
+
requestVersion: 2,
|
|
92
|
+
requestedCurrency: "BRL",
|
|
93
|
+
requestedPeriod: allTime,
|
|
94
|
+
}),
|
|
95
|
+
).toBe(false);
|
|
96
|
+
expect(
|
|
97
|
+
isCurrentCostSummaryRequest({
|
|
98
|
+
aborted: false,
|
|
99
|
+
currentCurrency: "USD",
|
|
100
|
+
currentPeriod: allTime,
|
|
101
|
+
currentRequestVersion: 3,
|
|
102
|
+
requestVersion: 3,
|
|
103
|
+
requestedCurrency: "BRL",
|
|
104
|
+
requestedPeriod: allTime,
|
|
105
|
+
}),
|
|
106
|
+
).toBe(false);
|
|
107
|
+
expect(
|
|
108
|
+
isCurrentCostSummaryRequest({
|
|
109
|
+
aborted: false,
|
|
110
|
+
currentCurrency: "BRL",
|
|
111
|
+
currentPeriod: allTime,
|
|
112
|
+
currentRequestVersion: 3,
|
|
113
|
+
requestVersion: 3,
|
|
114
|
+
requestedCurrency: "BRL",
|
|
115
|
+
requestedPeriod: allTime,
|
|
116
|
+
}),
|
|
117
|
+
).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
|
|
19
120
|
it("mounts Cost from a Daily seed without a full summary request", () => {
|
|
20
121
|
const app = readFileSync(new URL("./App.tsx", import.meta.url), "utf8");
|
|
21
122
|
const refresh = app.slice(
|
|
@@ -35,7 +136,7 @@ describe("Cost period refresh", () => {
|
|
|
35
136
|
);
|
|
36
137
|
expect(refresh).toContain("setDisplayedCosts(null);");
|
|
37
138
|
expect(refresh).toContain("const activeDashboardView = dashboardViewRef.current");
|
|
38
|
-
expect(refresh).toContain("[api, currency,
|
|
139
|
+
expect(refresh).toContain("[api, currency, operationsRequestPeriod]");
|
|
39
140
|
expect(refresh).not.toContain("[api, currency, dashboardView, operationsPeriod]");
|
|
40
141
|
});
|
|
41
142
|
|
|
@@ -10,6 +10,17 @@
|
|
|
10
10
|
background: rgba(8, 14, 25, 0.72);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
.dashboard-capability-shell {
|
|
14
|
+
flex: 0 0 auto;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* The landing and Traceability surfaces have no temporal/search result to filter.
|
|
18
|
+
* Keep the controls available for the two capability views that consume them. */
|
|
19
|
+
.dashboard-capability-shell--initial + .dashboard-global-filters,
|
|
20
|
+
.dashboard-capability-shell--traceability + .dashboard-global-filters {
|
|
21
|
+
display: none;
|
|
22
|
+
}
|
|
23
|
+
|
|
13
24
|
.dashboard-capability-nav button {
|
|
14
25
|
display: flex;
|
|
15
26
|
min-height: 52px;
|
|
@@ -39,6 +39,20 @@ describe("Operations period request contract", () => {
|
|
|
39
39
|
expect(requestPeriodForOperations({ preset: "all", range })).toEqual({ from: null, to: null });
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
+
it("keeps an All Time request unbounded when its displayed factual range changes", () => {
|
|
43
|
+
const initialDisplay = { start: "2026-09-01", end: "2026-09-01" };
|
|
44
|
+
const factualDisplay = { start: "2026-06-02", end: "2026-09-01" };
|
|
45
|
+
|
|
46
|
+
expect(requestPeriodForOperations({ preset: "all", range: initialDisplay })).toEqual({
|
|
47
|
+
from: null,
|
|
48
|
+
to: null,
|
|
49
|
+
});
|
|
50
|
+
expect(requestPeriodForOperations({ preset: "all", range: factualDisplay })).toEqual({
|
|
51
|
+
from: null,
|
|
52
|
+
to: null,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
42
56
|
it("keeps the latest Today/Yesterday selection when rapid requests resolve out of order", () => {
|
|
43
57
|
const today = { start: "2026-08-08", end: "2026-08-08" };
|
|
44
58
|
const yesterday = { start: "2026-08-07", end: "2026-08-07" };
|
|
@@ -2,12 +2,9 @@ import { describe, expect, it } from "vitest";
|
|
|
2
2
|
import { defaultOperationsPeriod, requestPeriodForOperations } from "./operations-period.js";
|
|
3
3
|
|
|
4
4
|
describe("dashboard default Operations period", () => {
|
|
5
|
-
it("opens in
|
|
6
|
-
expect(defaultOperationsPeriod.preset).toBe("
|
|
7
|
-
expect(requestPeriodForOperations(defaultOperationsPeriod)).toEqual({
|
|
8
|
-
from: defaultOperationsPeriod.range.start,
|
|
9
|
-
to: defaultOperationsPeriod.range.end,
|
|
10
|
-
});
|
|
5
|
+
it("opens in All Time without date bounds", () => {
|
|
6
|
+
expect(defaultOperationsPeriod.preset).toBe("all");
|
|
7
|
+
expect(requestPeriodForOperations(defaultOperationsPeriod)).toEqual({ from: null, to: null });
|
|
11
8
|
});
|
|
12
9
|
|
|
13
10
|
it("keeps an explicit Sao Paulo This Month selection bounded", () => {
|
|
@@ -9,9 +9,10 @@ export interface OperationsPeriodState {
|
|
|
9
9
|
range: OperationDateRange;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
/** Operations opens
|
|
12
|
+
/** Operations opens across the full available period for the dashboard overview. */
|
|
13
13
|
export const defaultOperationsPeriod: OperationsPeriodState = {
|
|
14
|
-
preset: "
|
|
14
|
+
preset: "all",
|
|
15
|
+
// All Time deliberately ignores these display bounds in requests.
|
|
15
16
|
range: dateRangeForPreset("this-month"),
|
|
16
17
|
};
|
|
17
18
|
|
|
@@ -74,11 +74,11 @@ describe("atomic Cost snapshot loading", () => {
|
|
|
74
74
|
expect(initialLoad).not.toContain("api.snapshot(currency, period)");
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
-
it("retries a failed Cost snapshot through
|
|
77
|
+
it("retries a failed Cost snapshot through its summary request", () => {
|
|
78
78
|
const app = readFileSync(new URL("./App.tsx", import.meta.url), "utf8");
|
|
79
79
|
const retry = app.slice(app.indexOf("const retryLastLoad"), app.indexOf("const setupRoute"));
|
|
80
80
|
|
|
81
|
-
expect(retry).toContain('if (failedArea === "costs") void
|
|
81
|
+
expect(retry).toContain('if (failedArea === "costs") void loadCostSummary();');
|
|
82
82
|
expect(retry).not.toContain("loadCosts");
|
|
83
83
|
});
|
|
84
84
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { loadCostDeveloperAttribution } from "./CostDeveloperAttributionPanel.js";
|
|
3
|
+
|
|
4
|
+
describe("loadCostDeveloperAttribution", () => {
|
|
5
|
+
it("requests the active period and returns every row supplied by the API", async () => {
|
|
6
|
+
const rows = Array.from({ length: 11 }, (_, index) => ({
|
|
7
|
+
account_id: String(index + 1),
|
|
8
|
+
login_snapshot: `dev-${index + 1}`,
|
|
9
|
+
prd_count: 0,
|
|
10
|
+
spec_count: 0,
|
|
11
|
+
task_count: 1,
|
|
12
|
+
run_count: 1,
|
|
13
|
+
test_count: null,
|
|
14
|
+
evidence_count: null,
|
|
15
|
+
total_duration_ms: null,
|
|
16
|
+
total_tokens: null,
|
|
17
|
+
total_cost_brl: null,
|
|
18
|
+
duration_covered_runs: 0,
|
|
19
|
+
token_covered_runs: 0,
|
|
20
|
+
brl_covered_runs: 0,
|
|
21
|
+
}));
|
|
22
|
+
const fetcher = vi
|
|
23
|
+
.fn<typeof fetch>()
|
|
24
|
+
.mockResolvedValue(new Response(JSON.stringify({ rows }), { status: 200 }));
|
|
25
|
+
|
|
26
|
+
await expect(
|
|
27
|
+
loadCostDeveloperAttribution({
|
|
28
|
+
from: "2026-08-01",
|
|
29
|
+
to: "2026-08-31",
|
|
30
|
+
fetcher,
|
|
31
|
+
}),
|
|
32
|
+
).resolves.toEqual(rows);
|
|
33
|
+
expect(fetcher).toHaveBeenCalledWith("/api/v2/developers?from=2026-08-01&to=2026-08-31", {});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("keeps the embedded all-time population unbounded and returns all 25 developers", async () => {
|
|
37
|
+
const rows = Array.from({ length: 25 }, (_, index) => ({
|
|
38
|
+
account_id: String(index + 1),
|
|
39
|
+
login_snapshot: `dev-${index + 1}`,
|
|
40
|
+
prd_count: 0,
|
|
41
|
+
spec_count: 0,
|
|
42
|
+
task_count: 1,
|
|
43
|
+
run_count: 1,
|
|
44
|
+
test_count: null,
|
|
45
|
+
evidence_count: null,
|
|
46
|
+
total_duration_ms: null,
|
|
47
|
+
total_tokens: null,
|
|
48
|
+
total_cost_brl: null,
|
|
49
|
+
duration_covered_runs: 0,
|
|
50
|
+
token_covered_runs: 0,
|
|
51
|
+
brl_covered_runs: 0,
|
|
52
|
+
}));
|
|
53
|
+
const fetcher = vi
|
|
54
|
+
.fn<typeof fetch>()
|
|
55
|
+
.mockResolvedValue(new Response(JSON.stringify({ rows }), { status: 200 }));
|
|
56
|
+
|
|
57
|
+
await expect(loadCostDeveloperAttribution({ fetcher })).resolves.toHaveLength(25);
|
|
58
|
+
expect(fetcher).toHaveBeenCalledWith("/api/v2/developers", {});
|
|
59
|
+
});
|
|
60
|
+
});
|
package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostDeveloperAttributionPanel.tsx
CHANGED
|
@@ -2,6 +2,28 @@ import { useEffect, useState } from "react";
|
|
|
2
2
|
import { type CostDeveloperAttributionRow, CostDeveloperAttributionTable } from "./CostTables.js";
|
|
3
3
|
import type { CostLocale } from "./cost-monitor-copy.js";
|
|
4
4
|
|
|
5
|
+
export async function loadCostDeveloperAttribution({
|
|
6
|
+
from,
|
|
7
|
+
to,
|
|
8
|
+
signal,
|
|
9
|
+
fetcher = fetch,
|
|
10
|
+
}: {
|
|
11
|
+
from?: string;
|
|
12
|
+
to?: string;
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
fetcher?: typeof fetch;
|
|
15
|
+
}): Promise<readonly CostDeveloperAttributionRow[]> {
|
|
16
|
+
const params = new URLSearchParams();
|
|
17
|
+
if (from) params.set("from", from);
|
|
18
|
+
if (to) params.set("to", to);
|
|
19
|
+
const response = await fetcher(`/api/v2/developers${params.size === 0 ? "" : `?${params}`}`, {
|
|
20
|
+
...(signal === undefined ? {} : { signal }),
|
|
21
|
+
});
|
|
22
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
23
|
+
const payload = (await response.json()) as { rows?: CostDeveloperAttributionRow[] };
|
|
24
|
+
return payload.rows ?? [];
|
|
25
|
+
}
|
|
26
|
+
|
|
5
27
|
/** Cost-specific rendering of the factual developer aggregate endpoint. */
|
|
6
28
|
export function CostDeveloperAttributionPanel({
|
|
7
29
|
from,
|
|
@@ -19,18 +41,13 @@ export function CostDeveloperAttributionPanel({
|
|
|
19
41
|
}>({ loading: true, error: null, rows: [] });
|
|
20
42
|
useEffect(() => {
|
|
21
43
|
const controller = new AbortController();
|
|
22
|
-
const params = new URLSearchParams();
|
|
23
|
-
if (from) params.set("from", from);
|
|
24
|
-
if (to) params.set("to", to);
|
|
25
44
|
setState((previous) => ({ ...previous, loading: true, error: null }));
|
|
26
|
-
|
|
45
|
+
loadCostDeveloperAttribution({
|
|
46
|
+
...(from === undefined ? {} : { from }),
|
|
47
|
+
...(to === undefined ? {} : { to }),
|
|
27
48
|
signal: controller.signal,
|
|
28
49
|
})
|
|
29
|
-
.then(
|
|
30
|
-
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
31
|
-
return response.json() as Promise<{ rows?: CostDeveloperAttributionRow[] }>;
|
|
32
|
-
})
|
|
33
|
-
.then((payload) => setState({ loading: false, error: null, rows: payload.rows ?? [] }))
|
|
50
|
+
.then((rows) => setState({ loading: false, error: null, rows }))
|
|
34
51
|
.catch((error: unknown) => {
|
|
35
52
|
if ((error as { name?: string }).name !== "AbortError")
|
|
36
53
|
setState({
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import type { CostMonitorProjection } from "../contracts.js";
|
|
2
|
+
import { type CostFilterState, CostFilters, type CostPeriod } from "./CostFilters.js";
|
|
3
|
+
import { ChevronDownIcon, ListFilterIcon, LocaleFlag, SettingsIcon } from "./CostMonitorIcons.js";
|
|
4
|
+
import type { CostCurrency, CostLocale, costCopy } from "./cost-monitor-copy.js";
|
|
5
|
+
|
|
6
|
+
const zaipLogoUrl = new URL("../../../../../config/src/logo_novo_azul.png", import.meta.url).href;
|
|
7
|
+
|
|
8
|
+
type CostNavigationCopy = (typeof costCopy)[CostLocale];
|
|
9
|
+
|
|
10
|
+
export type CostNavigationPopup = "filters" | "locale" | "settings" | null;
|
|
11
|
+
|
|
12
|
+
export function toggleCostNavigationPopup(
|
|
13
|
+
popup: CostNavigationPopup,
|
|
14
|
+
requestedPopup: Exclude<CostNavigationPopup, null>,
|
|
15
|
+
): CostNavigationPopup {
|
|
16
|
+
return popup === requestedPopup ? null : requestedPopup;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function CostMonitorNavigation({
|
|
20
|
+
currency,
|
|
21
|
+
filters,
|
|
22
|
+
loading,
|
|
23
|
+
locale,
|
|
24
|
+
onCurrencyChange,
|
|
25
|
+
onFiltersChange,
|
|
26
|
+
onLocaleChange,
|
|
27
|
+
onPopupChange,
|
|
28
|
+
onPeriodChange,
|
|
29
|
+
onRefresh,
|
|
30
|
+
period,
|
|
31
|
+
popup,
|
|
32
|
+
text,
|
|
33
|
+
updatedLabel,
|
|
34
|
+
filtersAvailable,
|
|
35
|
+
}: {
|
|
36
|
+
currency: CostCurrency;
|
|
37
|
+
filters: CostFilterState;
|
|
38
|
+
filtersAvailable: CostMonitorProjection["filters_available"];
|
|
39
|
+
loading: boolean;
|
|
40
|
+
locale: CostLocale;
|
|
41
|
+
onCurrencyChange: (currency: CostCurrency) => void;
|
|
42
|
+
onFiltersChange: (filters: CostFilterState) => void;
|
|
43
|
+
onLocaleChange: (locale: CostLocale) => void;
|
|
44
|
+
onPopupChange: (popup: CostNavigationPopup) => void;
|
|
45
|
+
onPeriodChange: (period: CostPeriod) => void;
|
|
46
|
+
onRefresh: () => void;
|
|
47
|
+
period: CostPeriod;
|
|
48
|
+
popup: CostNavigationPopup;
|
|
49
|
+
text: CostNavigationCopy;
|
|
50
|
+
updatedLabel: string;
|
|
51
|
+
}) {
|
|
52
|
+
const filtersOpen = popup === "filters";
|
|
53
|
+
const localeOpen = popup === "locale";
|
|
54
|
+
const settingsOpen = popup === "settings";
|
|
55
|
+
const changeLocale = (next: CostLocale) => {
|
|
56
|
+
onLocaleChange(next);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<>
|
|
61
|
+
<nav aria-label="Navegação do Cost Monitor" className="cost-monitor-nav">
|
|
62
|
+
<div className="cost-monitor-brand">
|
|
63
|
+
<strong className="cost-monitor-title">{text.title}</strong>
|
|
64
|
+
<small className="cost-last-updated">{updatedLabel}</small>
|
|
65
|
+
</div>
|
|
66
|
+
<img alt="ZAIP" className="cost-monitor-logo" src={zaipLogoUrl} />
|
|
67
|
+
<div className="cost-nav-actions">
|
|
68
|
+
<button
|
|
69
|
+
aria-label={text.refresh}
|
|
70
|
+
className="cost-nav-icon cost-refresh-action"
|
|
71
|
+
disabled={loading}
|
|
72
|
+
onClick={onRefresh}
|
|
73
|
+
type="button"
|
|
74
|
+
>
|
|
75
|
+
↻
|
|
76
|
+
</button>
|
|
77
|
+
<div className="cost-nav-popup-anchor cost-filter-action">
|
|
78
|
+
<button
|
|
79
|
+
className={filtersOpen ? "cost-nav-button is-active" : "cost-nav-button"}
|
|
80
|
+
onClick={() => onPopupChange(toggleCostNavigationPopup(popup, "filters"))}
|
|
81
|
+
type="button"
|
|
82
|
+
>
|
|
83
|
+
<ListFilterIcon /> {text.filters}
|
|
84
|
+
</button>
|
|
85
|
+
{filtersOpen ? (
|
|
86
|
+
<div className="cost-nav-popup cost-nav-filter-popup">
|
|
87
|
+
<CostFilters
|
|
88
|
+
available={filtersAvailable}
|
|
89
|
+
hideQuery={false}
|
|
90
|
+
locale={locale}
|
|
91
|
+
onChange={onFiltersChange}
|
|
92
|
+
value={filters}
|
|
93
|
+
/>
|
|
94
|
+
</div>
|
|
95
|
+
) : null}
|
|
96
|
+
</div>
|
|
97
|
+
<div className="cost-nav-popup-anchor cost-locale-action">
|
|
98
|
+
<button
|
|
99
|
+
className={localeOpen ? "cost-nav-button is-active" : "cost-nav-button"}
|
|
100
|
+
onClick={() => onPopupChange(toggleCostNavigationPopup(popup, "locale"))}
|
|
101
|
+
type="button"
|
|
102
|
+
>
|
|
103
|
+
<LocaleFlag locale={locale} />
|
|
104
|
+
{locale === "pt-BR" ? "PT-BR" : "EN-US"}
|
|
105
|
+
<ChevronDownIcon />
|
|
106
|
+
</button>
|
|
107
|
+
{localeOpen ? (
|
|
108
|
+
<div className="cost-nav-popup cost-nav-locale-popup">
|
|
109
|
+
<button onClick={() => changeLocale("pt-BR")} type="button">
|
|
110
|
+
<LocaleFlag locale="pt-BR" /> Português do Brasil {locale === "pt-BR" ? "✓" : ""}
|
|
111
|
+
</button>
|
|
112
|
+
<button onClick={() => changeLocale("en-US")} type="button">
|
|
113
|
+
<LocaleFlag locale="en-US" /> English {locale === "en-US" ? "✓" : ""}
|
|
114
|
+
</button>
|
|
115
|
+
</div>
|
|
116
|
+
) : null}
|
|
117
|
+
</div>
|
|
118
|
+
<div className="cost-nav-popup-anchor cost-settings-action">
|
|
119
|
+
<button
|
|
120
|
+
aria-label={text.settings}
|
|
121
|
+
className={settingsOpen ? "cost-nav-icon is-active" : "cost-nav-icon"}
|
|
122
|
+
onClick={() => onPopupChange(toggleCostNavigationPopup(popup, "settings"))}
|
|
123
|
+
type="button"
|
|
124
|
+
>
|
|
125
|
+
<SettingsIcon />
|
|
126
|
+
</button>
|
|
127
|
+
{settingsOpen ? (
|
|
128
|
+
<div className="cost-nav-popup cost-nav-settings-popup">
|
|
129
|
+
<div className="cost-mobile-settings">
|
|
130
|
+
<small className="cost-mobile-updated">{updatedLabel}</small>
|
|
131
|
+
<button disabled={loading} onClick={onRefresh} type="button">
|
|
132
|
+
↻{" "}
|
|
133
|
+
{loading ? (locale === "pt-BR" ? "Atualizando…" : "Refreshing…") : text.refresh}
|
|
134
|
+
</button>
|
|
135
|
+
<label>
|
|
136
|
+
<span>{locale === "pt-BR" ? "Idioma" : "Language"}</span>
|
|
137
|
+
<select
|
|
138
|
+
onChange={(event) => changeLocale(event.target.value as CostLocale)}
|
|
139
|
+
value={locale}
|
|
140
|
+
>
|
|
141
|
+
<option value="pt-BR">Português do Brasil</option>
|
|
142
|
+
<option value="en-US">English</option>
|
|
143
|
+
</select>
|
|
144
|
+
</label>
|
|
145
|
+
<div className="cost-mobile-filters">
|
|
146
|
+
<strong>{text.filters}</strong>
|
|
147
|
+
<CostFilters
|
|
148
|
+
available={filtersAvailable}
|
|
149
|
+
hideQuery={false}
|
|
150
|
+
locale={locale}
|
|
151
|
+
onChange={onFiltersChange}
|
|
152
|
+
value={filters}
|
|
153
|
+
/>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
<label>
|
|
157
|
+
<span>{text.currency}</span>
|
|
158
|
+
<select
|
|
159
|
+
onChange={(event) => onCurrencyChange(event.target.value as CostCurrency)}
|
|
160
|
+
value={currency}
|
|
161
|
+
>
|
|
162
|
+
<option value="BRL">BRL · R$</option>
|
|
163
|
+
<option value="USD">USD · $</option>
|
|
164
|
+
</select>
|
|
165
|
+
</label>
|
|
166
|
+
</div>
|
|
167
|
+
) : null}
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
</nav>
|
|
171
|
+
<header className="cost-hero">
|
|
172
|
+
<p className="cost-hero-description">{text.subtitle}</p>
|
|
173
|
+
<label className="cost-period">
|
|
174
|
+
<span>{text.period}</span>
|
|
175
|
+
<select
|
|
176
|
+
onChange={(event) => onPeriodChange(event.target.value as CostPeriod)}
|
|
177
|
+
value={period}
|
|
178
|
+
>
|
|
179
|
+
<option value="all">{text.allTime}</option>
|
|
180
|
+
<option value="7">{text.last7}</option>
|
|
181
|
+
<option value="30">{text.last30}</option>
|
|
182
|
+
<option value="90">{text.last90}</option>
|
|
183
|
+
<option value="month">{text.thisMonth}</option>
|
|
184
|
+
</select>
|
|
185
|
+
</label>
|
|
186
|
+
</header>
|
|
187
|
+
</>
|
|
188
|
+
);
|
|
189
|
+
}
|