@zaip-ai/cli 1.3.1 → 1.3.4
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 +11 -4
- 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 +403 -383
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-ovxurkIk.js → CostCalculationGuidePage-BZWjXQB-.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-BgVgrfhQ.js → DashboardHelpDialog-D8qqeIPE.js} +58 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-vSvvOHQp.js → PricingSettingsDialog-Cm12_A0v.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-zH758vfs.js → ProjectSettingsPage-DxO9F8B4.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-DLn-Mz2M.js → TraceabilityWorkspace-_x0twOcS.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-Cpp8xFij.js +68 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-B_lDDHn1.js → icons-vendor-CR4Fbf3g.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-ncC1rxap.js +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-B3TJrD4O.js → react-vendor-DRJQbKnp.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-7f1j5wx3.js → traceability-CMrDYqjR.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/index.html +5 -5
- package/templates/.agents/dashboard/apps/frontend/src/App.tsx +48 -3
- package/templates/.agents/dashboard/apps/frontend/src/AppRequestGuards.tsx +44 -0
- package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +92 -1
- 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/export/ExcelExportAction.test.ts +59 -0
- package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +55 -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-operations-work-list.test.ts +70 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-operations-work-list.ts +74 -7
- 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-period-transition.spec.ts +232 -0
- package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +26 -5
- 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 +44 -0
- package/templates/template-manifest.json +536 -932
- 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-CZQlsLV9.js +0 -2
|
@@ -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
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
2
4
|
import { describe, expect, it } from "vitest";
|
|
3
5
|
import {
|
|
4
6
|
allPhaseMetricTotals,
|
|
@@ -24,6 +26,7 @@ import {
|
|
|
24
26
|
matchesPeriodEntity,
|
|
25
27
|
resolveCompactCostPageRange,
|
|
26
28
|
resolveCostGroupPageRange,
|
|
29
|
+
resolveDeveloperAttributionRange,
|
|
27
30
|
selectDimensionalCostRows,
|
|
28
31
|
visibleExecutionRows,
|
|
29
32
|
} from "./CostMonitorPage.js";
|
|
@@ -33,13 +36,110 @@ import {
|
|
|
33
36
|
reconcileCostGroupTotalsWithCards,
|
|
34
37
|
} from "./use-cost-group-page.js";
|
|
35
38
|
|
|
39
|
+
declare global {
|
|
40
|
+
interface Window {
|
|
41
|
+
__costMonitorProbe: { localeChanges: number };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
36
45
|
describe("cost monitor navigation", () => {
|
|
46
|
+
it("keeps an open filter popup through the embedded DOM cycle", async () => {
|
|
47
|
+
const browserRequire = createRequire(
|
|
48
|
+
fileURLToPath(new URL("../../../../tests/e2e/package.json", import.meta.url)),
|
|
49
|
+
);
|
|
50
|
+
const esbuild = browserRequire("esbuild");
|
|
51
|
+
const { chromium, expect: expectPlaywright } = browserRequire("@playwright/test");
|
|
52
|
+
const harness = await esbuild.build({
|
|
53
|
+
bundle: true,
|
|
54
|
+
define: { "import.meta.url": '"http://localhost/"' },
|
|
55
|
+
format: "iife",
|
|
56
|
+
loader: { ".css": "text", ".png": "dataurl" },
|
|
57
|
+
platform: "browser",
|
|
58
|
+
stdin: {
|
|
59
|
+
contents: `
|
|
60
|
+
import { createRoot } from "react-dom/client";
|
|
61
|
+
import React, { useState } from "react";
|
|
62
|
+
import { CostMonitorPage } from "../../../modules/cost-monitor/src/react/CostMonitorPage.js";
|
|
63
|
+
const coverage = { events: 0, tokens: 0, usd: 0, brl: 0 };
|
|
64
|
+
const data = {
|
|
65
|
+
currency: "BRL",
|
|
66
|
+
pricing: { catalog_updated_at: null, fx_rate: null, conversion_applied: false },
|
|
67
|
+
filters: { from: null, to: null },
|
|
68
|
+
overview: { task_count: 0, run_count: 0, event_count: 0, total_tokens: 0, estimated_usd: 0, estimated_brl: 0, recorded_usd: 0, recorded_brl: 0, average_run_cost_usd: null, average_run_cost_brl: null, average_run_duration_ms: null, known_tokens_per_cycle: null, top_10_token_share: null, coverage },
|
|
69
|
+
filters_available: { providers: ["OpenAI"], agents: [], models: [], statuses: [], priorities: [], risks: [], phases: [], origins: [], work_types: [] },
|
|
70
|
+
configured_agents: [], usage: {}, origins: {}, prds: [], specs: [], runs: [],
|
|
71
|
+
groups: { conversations: [], tasks: [], providers: [], agents: [], models: [], daily: [], monthly: [], annual: [] },
|
|
72
|
+
};
|
|
73
|
+
const probe = { localeChanges: 0 };
|
|
74
|
+
Object.assign(window, { __costMonitorProbe: probe });
|
|
75
|
+
function Harness() {
|
|
76
|
+
const [embedded, setEmbedded] = useState(false);
|
|
77
|
+
return <>
|
|
78
|
+
<button onClick={() => setEmbedded((current) => !current)} type="button">Alternar incorporado</button>
|
|
79
|
+
<CostMonitorPage data={data as any} embedded={embedded} lastUpdatedAt={null} locale="pt-BR" loading={false} loadPeriod={async () => data as any} onLocaleChange={() => { probe.localeChanges += 1; }} onOpenTrace={() => {}} onRefresh={() => {}} />
|
|
80
|
+
</>;
|
|
81
|
+
}
|
|
82
|
+
createRoot(document.getElementById("root")).render(<Harness />);
|
|
83
|
+
`,
|
|
84
|
+
loader: "tsx",
|
|
85
|
+
resolveDir: fileURLToPath(new URL("../../../../apps/frontend/src", import.meta.url)),
|
|
86
|
+
sourcefile: "CostMonitorPage.browser-harness.tsx",
|
|
87
|
+
},
|
|
88
|
+
write: false,
|
|
89
|
+
});
|
|
90
|
+
const browser = await chromium.launch({ headless: true });
|
|
91
|
+
try {
|
|
92
|
+
const page = await browser.newPage();
|
|
93
|
+
const pageErrors: string[] = [];
|
|
94
|
+
page.on("pageerror", (error: Error) => pageErrors.push(error.message));
|
|
95
|
+
await page.setContent('<div id="root"></div>');
|
|
96
|
+
await page.addScriptTag({ content: harness.outputFiles[0].text });
|
|
97
|
+
await page.waitForTimeout(100);
|
|
98
|
+
expect(pageErrors).toEqual([]);
|
|
99
|
+
|
|
100
|
+
const navigation = page.getByLabel("Navegação do Cost Monitor");
|
|
101
|
+
await navigation.getByRole("button", { name: "Filtros" }).click();
|
|
102
|
+
await expectPlaywright(page.locator(".cost-nav-filter-popup")).toBeVisible();
|
|
103
|
+
await page.getByRole("button", { name: "Alternar incorporado" }).click();
|
|
104
|
+
await expectPlaywright(page.getByLabel("Navegação do Cost Monitor")).toHaveCount(0);
|
|
105
|
+
await page.getByRole("button", { name: "Alternar incorporado" }).click();
|
|
106
|
+
await expectPlaywright(page.locator(".cost-nav-filter-popup")).toBeVisible();
|
|
107
|
+
await navigation.getByRole("button", { name: /PT-BR/ }).click();
|
|
108
|
+
await page.getByRole("button", { name: /English/ }).click();
|
|
109
|
+
await expectPlaywright
|
|
110
|
+
.poll(() => page.evaluate(() => window.__costMonitorProbe.localeChanges))
|
|
111
|
+
.toBe(1);
|
|
112
|
+
} finally {
|
|
113
|
+
await browser.close();
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
37
117
|
it("hides Excel export from the overview while retaining it on tabular views", () => {
|
|
38
118
|
expect(isCostViewExportEligible("overview")).toBe(false);
|
|
39
119
|
expect(isCostViewExportEligible("daily")).toBe(true);
|
|
40
120
|
expect(isCostViewExportEligible("agentCalls")).toBe(true);
|
|
41
121
|
});
|
|
42
122
|
|
|
123
|
+
it("keeps embedded developers all-time despite residual local filters", () => {
|
|
124
|
+
const monthRange = { from: "2026-08-01", to: "2026-08-31" };
|
|
125
|
+
expect(
|
|
126
|
+
resolveDeveloperAttributionRange({
|
|
127
|
+
embedded: true,
|
|
128
|
+
runPageRange: monthRange,
|
|
129
|
+
}),
|
|
130
|
+
).toBeUndefined();
|
|
131
|
+
expect(
|
|
132
|
+
resolveDeveloperAttributionRange({
|
|
133
|
+
embedded: true,
|
|
134
|
+
globalDateRange: { start: "2026-07-01", end: "2026-08-31" },
|
|
135
|
+
runPageRange: { from: "2026-07-01", to: "2026-08-31" },
|
|
136
|
+
}),
|
|
137
|
+
).toEqual({ from: "2026-07-01", to: "2026-08-31" });
|
|
138
|
+
expect(resolveDeveloperAttributionRange({ embedded: false, runPageRange: monthRange })).toEqual(
|
|
139
|
+
monthRange,
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
|
|
43
143
|
it("keeps the embedded default group pager on the all-time snapshot without explicit filters", () => {
|
|
44
144
|
const monthRange = costPeriodDateRange(defaultCostPeriod, new Date("2026-08-17T12:00:00.000Z"));
|
|
45
145
|
expect(
|
|
@@ -365,16 +465,36 @@ describe("cost monitor navigation", () => {
|
|
|
365
465
|
expect(content).toContain('{view === "developers" ? developerPanel : null}');
|
|
366
466
|
});
|
|
367
467
|
|
|
468
|
+
it("prepares the complete developer API population with the same all-time range as the table", () => {
|
|
469
|
+
const page = readFileSync(new URL("./CostMonitorPage.tsx", import.meta.url), "utf8");
|
|
470
|
+
const exportSection = page.slice(
|
|
471
|
+
page.indexOf("const developerExportSource"),
|
|
472
|
+
page.indexOf("const costGroupExportSource"),
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
expect(exportSection).toContain('view === "developers"');
|
|
476
|
+
expect(exportSection).toContain("loadCostDeveloperAttribution");
|
|
477
|
+
expect(exportSection).toContain(
|
|
478
|
+
"...(developerAttributionRange === undefined ? {} : developerAttributionRange)",
|
|
479
|
+
);
|
|
480
|
+
expect(exportSection).toContain("totalRows: developerRows.length");
|
|
481
|
+
expect(page).toContain("const developerAttributionRange = resolveDeveloperAttributionRange");
|
|
482
|
+
expect(page).toContain("...(developerAttributionRange === undefined");
|
|
483
|
+
expect(page).toMatch(
|
|
484
|
+
/agentInvocationExportSource \?\?\s*developerExportSource \?\?\s*costGroupExportSource/,
|
|
485
|
+
);
|
|
486
|
+
});
|
|
487
|
+
|
|
368
488
|
it("prepares Agent Invocations from every server page for export", () => {
|
|
369
489
|
const page = readFileSync(new URL("./CostMonitorPage.tsx", import.meta.url), "utf8");
|
|
370
490
|
const exportSection = page.slice(
|
|
371
491
|
page.indexOf("const agentInvocationExportSource"),
|
|
372
|
-
page.indexOf("const
|
|
492
|
+
page.indexOf("const developerExportSource"),
|
|
373
493
|
);
|
|
374
494
|
expect(page).toContain('view === "agentCalls" && serverPagingEligible');
|
|
375
495
|
expect(page).toContain("agentInvocationExportSource");
|
|
376
496
|
expect(page).toMatch(
|
|
377
|
-
/pageSource=\{\s*compactExportSource
|
|
497
|
+
/pageSource=\{\s*compactExportSource \?\?\s*agentInvocationExportSource \?\?\s*developerExportSource \?\?\s*costGroupExportSource\s*\}/,
|
|
378
498
|
);
|
|
379
499
|
expect(page).toContain("if (page.runs.length === 0) return");
|
|
380
500
|
expect(exportSection.match(/limit: 100/g)).toHaveLength(2);
|