@zaip-ai/cli 1.3.4 → 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/package.json +1 -1
- package/templates/.agents/dashboard/.zaip-managed.json +44 -44
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-BZWjXQB-.js → CostCalculationGuidePage-D1mpyYwh.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-D8qqeIPE.js → DashboardHelpDialog-CKO47D5s.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-Cm12_A0v.js → PricingSettingsDialog-BuSlCbyZ.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-DxO9F8B4.js → ProjectSettingsPage-CUHFkAhZ.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-_x0twOcS.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-CR4Fbf3g.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-DRJQbKnp.js → react-vendor-CTC6vtln.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-CMrDYqjR.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 +32 -13
- package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +13 -3
- 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/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/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/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 +6 -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 +40 -18
- 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/template-manifest.json +61 -61
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-Cpp8xFij.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-ncC1rxap.js +0 -2
package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.helpers.tsx
CHANGED
|
@@ -537,7 +537,8 @@ export function formatExecutionAverageHint(
|
|
|
537
537
|
totalDays: number,
|
|
538
538
|
locale: OperationsLocale,
|
|
539
539
|
): string {
|
|
540
|
-
if (
|
|
540
|
+
if (totalTasks <= 0) return "0";
|
|
541
|
+
if (totalDurationMs <= 0 || totalDays <= 0) {
|
|
541
542
|
return locale === "pt-BR"
|
|
542
543
|
? "Médias de execução indisponíveis"
|
|
543
544
|
: "Execution averages unavailable";
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
findLatestCreatedAt,
|
|
13
13
|
findLatestTaskCreatedAt,
|
|
14
14
|
formatExecutionAverageHint,
|
|
15
|
+
formatSummaryMetricValue,
|
|
15
16
|
formatTokenThroughputHint,
|
|
16
17
|
macroClassificationStats,
|
|
17
18
|
macroStatusDistribution,
|
|
@@ -278,11 +279,12 @@ describe("execution average summary", () => {
|
|
|
278
279
|
);
|
|
279
280
|
});
|
|
280
281
|
|
|
281
|
-
it("localizes English and
|
|
282
|
+
it("localizes English and shows zero for an empty population", () => {
|
|
282
283
|
expect(formatExecutionAverageHint(3_600_000, 3, 1, "en-US")).toBe(
|
|
283
284
|
"20 min / task (1 h / day) (1 day)",
|
|
284
285
|
);
|
|
285
|
-
expect(formatExecutionAverageHint(0, 0, 0, "pt-BR")).toBe("
|
|
286
|
+
expect(formatExecutionAverageHint(0, 0, 0, "pt-BR")).toBe("0");
|
|
287
|
+
expect(formatExecutionAverageHint(0, 0, 0, "en-US")).toBe("0");
|
|
286
288
|
});
|
|
287
289
|
});
|
|
288
290
|
|
|
@@ -351,14 +353,20 @@ describe("overviewMetricAverages", () => {
|
|
|
351
353
|
});
|
|
352
354
|
});
|
|
353
355
|
|
|
354
|
-
it("
|
|
356
|
+
it("keeps unknown totals null internally and renders zero only for an empty population", () => {
|
|
355
357
|
const averages = overviewMetricAverages({ durationMs: null, tokens: null, costBrl: null }, 3);
|
|
356
358
|
expect(averages).toEqual({ durationMs: null, tokens: null, costBrl: null });
|
|
357
359
|
const source = readOverviewSource();
|
|
358
360
|
expect(source).toContain(
|
|
359
361
|
"durationMs: totalDurationMetric, tokens: totalTokensMetric, costBrl: totalCostMetric",
|
|
360
362
|
);
|
|
361
|
-
expect(
|
|
363
|
+
expect(formatSummaryMetricValue(null, 0, (value) => String(value), "pt-BR")).toBe("0");
|
|
364
|
+
expect(formatSummaryMetricValue(null, 0, (value) => String(value), "en-US")).toBe("0");
|
|
365
|
+
expect(formatSummaryMetricValue(null, 1, (value) => String(value), "pt-BR")).toBe(
|
|
366
|
+
"Indisponível",
|
|
367
|
+
);
|
|
368
|
+
expect(formatSummaryMetricValue(42, 0, (value) => String(value), "pt-BR")).toBe("42");
|
|
369
|
+
expect(source).toContain("formatSummaryMetricValue(\n averageDuration,");
|
|
362
370
|
expect(source).not.toContain("executionMetrics.durationMs ?? 0");
|
|
363
371
|
});
|
|
364
372
|
});
|
|
@@ -98,6 +98,20 @@ function formatEntityDuration(value: number | null, locale: OperationsLocale): s
|
|
|
98
98
|
if (value !== null) return formatDurationMs(value, locale);
|
|
99
99
|
return locale === "pt-BR" ? "Indisponível" : "Unavailable";
|
|
100
100
|
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Keeps missing source facts as null while making a genuinely empty selected
|
|
104
|
+
* population readable as the factual numeric zero in the overview.
|
|
105
|
+
*/
|
|
106
|
+
export function formatSummaryMetricValue(
|
|
107
|
+
value: number | null,
|
|
108
|
+
populationCount: number | null,
|
|
109
|
+
formatKnownValue: (knownValue: number) => string,
|
|
110
|
+
locale: OperationsLocale,
|
|
111
|
+
): string {
|
|
112
|
+
if (value !== null) return formatKnownValue(value);
|
|
113
|
+
return populationCount === 0 ? "0" : formatEntityDuration(null, locale);
|
|
114
|
+
}
|
|
101
115
|
export function OperationsOverview({
|
|
102
116
|
currency,
|
|
103
117
|
data,
|
|
@@ -399,18 +413,32 @@ export function OperationsOverview({
|
|
|
399
413
|
: findLatestCreatedAt(tasks)
|
|
400
414
|
: (selectedMacroAggregate?.latest_created_at ?? findLatestCreatedAt(selectedMacros));
|
|
401
415
|
const metricStats = [
|
|
402
|
-
[
|
|
416
|
+
[
|
|
417
|
+
text.averageTime,
|
|
418
|
+
formatSummaryMetricValue(
|
|
419
|
+
averageDuration,
|
|
420
|
+
selectedCount,
|
|
421
|
+
(value) => formatDurationMs(value, locale),
|
|
422
|
+
locale,
|
|
423
|
+
),
|
|
424
|
+
],
|
|
403
425
|
[
|
|
404
426
|
text.averageTokens,
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
427
|
+
formatSummaryMetricValue(
|
|
428
|
+
averageTokens,
|
|
429
|
+
selectedCount,
|
|
430
|
+
(value) => formatCompactNumber(value, locale),
|
|
431
|
+
locale,
|
|
432
|
+
),
|
|
408
433
|
],
|
|
409
434
|
[
|
|
410
435
|
text.averageCost,
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
436
|
+
formatSummaryMetricValue(
|
|
437
|
+
averageCost,
|
|
438
|
+
selectedCount,
|
|
439
|
+
(value) => formatCurrency(value, currency, locale),
|
|
440
|
+
locale,
|
|
441
|
+
),
|
|
414
442
|
],
|
|
415
443
|
] as const;
|
|
416
444
|
const aggregateTopTasks: EntityMetric[] = (overviewMetrics?.top_tasks ?? []).map((metric) => ({
|
|
@@ -580,14 +608,23 @@ export function OperationsOverview({
|
|
|
580
608
|
? `${text.totalExecutionTime} de ${copy.shortPlural}`
|
|
581
609
|
: `${copy.shortPlural} ${text.totalExecutionTime.toLowerCase()}`
|
|
582
610
|
}
|
|
583
|
-
value={
|
|
611
|
+
value={formatSummaryMetricValue(
|
|
612
|
+
totalDurationMetric,
|
|
613
|
+
metricPopulationCount,
|
|
614
|
+
(value) => formatDurationMs(value, locale),
|
|
615
|
+
locale,
|
|
616
|
+
)}
|
|
584
617
|
/>
|
|
585
618
|
<Kpi
|
|
586
619
|
hint={
|
|
587
620
|
tokensPerTask === null || tokensPerHour === null
|
|
588
|
-
?
|
|
589
|
-
?
|
|
590
|
-
|
|
621
|
+
? metricPopulationCount === 0
|
|
622
|
+
? locale === "pt-BR"
|
|
623
|
+
? `0 / ${metricUnit} (0 / hora)`
|
|
624
|
+
: `0 / ${metricUnit} (0 / hour)`
|
|
625
|
+
: locale === "pt-BR"
|
|
626
|
+
? "Tokens por tarefa e por hora indisponíveis"
|
|
627
|
+
: "Tokens per task and hour unavailable"
|
|
591
628
|
: locale === "pt-BR"
|
|
592
629
|
? `${formatCompactNumber(tokensPerTask, locale)} / ${metricUnit} (${formatCompactNumber(tokensPerHour, locale, 0)} / hora)`
|
|
593
630
|
: `${formatCompactNumber(tokensPerTask, locale)} / ${metricUnit} (${formatCompactNumber(tokensPerHour, locale, 0)} / hour)`
|
|
@@ -601,18 +638,23 @@ export function OperationsOverview({
|
|
|
601
638
|
? `Tokens gastos por ${copy.shortPlural}`
|
|
602
639
|
: `${copy.shortPlural} tokens spent`
|
|
603
640
|
}
|
|
604
|
-
value={
|
|
605
|
-
totalTokensMetric
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
641
|
+
value={formatSummaryMetricValue(
|
|
642
|
+
totalTokensMetric,
|
|
643
|
+
metricPopulationCount,
|
|
644
|
+
(value) => formatCompactNumber(value, locale),
|
|
645
|
+
locale,
|
|
646
|
+
)}
|
|
609
647
|
/>
|
|
610
648
|
<Kpi
|
|
611
649
|
hint={
|
|
612
650
|
hourlyCost === null || costPerTask === null
|
|
613
|
-
?
|
|
614
|
-
?
|
|
615
|
-
|
|
651
|
+
? metricPopulationCount === 0
|
|
652
|
+
? locale === "pt-BR"
|
|
653
|
+
? `${formatRoundedCurrency(0, currency, locale)} / ${metricUnit} (${formatRoundedCurrency(0, currency, locale)} / hora)`
|
|
654
|
+
: `${formatRoundedCurrency(0, currency, locale)} / ${metricUnit} (${formatRoundedCurrency(0, currency, locale)} / hour)`
|
|
655
|
+
: locale === "pt-BR"
|
|
656
|
+
? "Custo por tarefa e por hora indispon\u00edvel"
|
|
657
|
+
: "Cost per task and hour unavailable"
|
|
616
658
|
: locale === "pt-BR"
|
|
617
659
|
? `${formatRoundedCurrency(costPerTask, currency, locale)} / ${metricUnit} (${formatRoundedCurrency(hourlyCost, currency, locale)} / hora)`
|
|
618
660
|
: `${formatRoundedCurrency(costPerTask, currency, locale)} / ${metricUnit} (${formatRoundedCurrency(hourlyCost, currency, locale)} / hour)`
|
|
@@ -626,11 +668,12 @@ export function OperationsOverview({
|
|
|
626
668
|
? `Custo de ${copy.shortPlural}`
|
|
627
669
|
: `${copy.shortPlural} cost`
|
|
628
670
|
}
|
|
629
|
-
value={
|
|
630
|
-
totalCostMetric
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
671
|
+
value={formatSummaryMetricValue(
|
|
672
|
+
totalCostMetric,
|
|
673
|
+
metricPopulationCount,
|
|
674
|
+
(value) => formatRoundedCurrency(value, currency, locale),
|
|
675
|
+
locale,
|
|
676
|
+
)}
|
|
634
677
|
/>
|
|
635
678
|
</div>
|
|
636
679
|
|
|
@@ -25,6 +25,7 @@ export {
|
|
|
25
25
|
|
|
26
26
|
import {
|
|
27
27
|
dateRangeForPreset,
|
|
28
|
+
factualCostDateRange,
|
|
28
29
|
initialOperationsDateRange,
|
|
29
30
|
isDateInOperationPreset,
|
|
30
31
|
isRunInOperationPreset,
|
|
@@ -130,10 +131,13 @@ export function OperationsPage({
|
|
|
130
131
|
const [filterLoading, setFilterLoading] = useState(false);
|
|
131
132
|
const filterLoadingTimer = useRef<number | null>(null);
|
|
132
133
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
133
|
-
const defaultDateRange = useMemo(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
const defaultDateRange = useMemo(() => {
|
|
135
|
+
const operationsRange = initialOperationsDateRange(
|
|
136
|
+
data.tasks,
|
|
137
|
+
Object.keys(data.summary.periods?.daily ?? {}),
|
|
138
|
+
);
|
|
139
|
+
return factualCostDateRange(factualCostPeriods?.day, operationsRange);
|
|
140
|
+
}, [data.summary.periods, data.tasks, factualCostPeriods?.day]);
|
|
137
141
|
const initialDateRange = useMemo(() => dateRangeForPreset("this-month"), []);
|
|
138
142
|
const [localDateRange, setLocalDateRange] = useState(() => initialDateRange);
|
|
139
143
|
const [localDatePreset, setLocalDatePreset] = useState<OperationDatePreset>("all");
|
|
@@ -156,6 +160,17 @@ export function OperationsPage({
|
|
|
156
160
|
},
|
|
157
161
|
[isPeriodControlled, onDateChange, onPeriodChange],
|
|
158
162
|
);
|
|
163
|
+
// A controlled parent initially owns the range selected before the factual
|
|
164
|
+
// Cost summary arrives. Once it does, synchronize only All Time's displayed
|
|
165
|
+
// coverage range; requestPeriodForOperations still omits both bounds.
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
if (
|
|
168
|
+
datePreset !== "all" ||
|
|
169
|
+
(dateRange.start === defaultDateRange.start && dateRange.end === defaultDateRange.end)
|
|
170
|
+
)
|
|
171
|
+
return;
|
|
172
|
+
updatePeriod("all", defaultDateRange);
|
|
173
|
+
}, [datePreset, dateRange.end, dateRange.start, defaultDateRange, updatePeriod]);
|
|
159
174
|
useEffect(() => {
|
|
160
175
|
if (!isPeriodControlled && datePreset === "all") setLocalDateRange(defaultDateRange);
|
|
161
176
|
}, [datePreset, defaultDateRange, isPeriodControlled]);
|
package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.test.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import {
|
|
3
3
|
dateRangeForPreset,
|
|
4
|
+
factualCostDateRange,
|
|
4
5
|
initialOperationsDateRange,
|
|
5
6
|
initialTaskDateRange,
|
|
6
7
|
isDateInOperationPreset,
|
|
@@ -34,6 +35,30 @@ describe("operation date ranges", () => {
|
|
|
34
35
|
});
|
|
35
36
|
});
|
|
36
37
|
|
|
38
|
+
it("uses the first factual Cost Monitor day for All Time while ending today", () => {
|
|
39
|
+
expect(
|
|
40
|
+
factualCostDateRange(
|
|
41
|
+
[
|
|
42
|
+
{ key: "2026-06-01", estimated_brl: null, estimated_usd: null },
|
|
43
|
+
{ key: "2026-06-02", estimated_brl: 0, estimated_usd: 0 },
|
|
44
|
+
{ key: "2026-06-03", estimated_brl: 1.25, estimated_usd: 0.24 },
|
|
45
|
+
],
|
|
46
|
+
{ start: "2026-07-10", end: "2026-07-14" },
|
|
47
|
+
today,
|
|
48
|
+
),
|
|
49
|
+
).toEqual({ start: "2026-06-02", end: "2026-07-14" });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("keeps the Operations range when no factual Cost Monitor day is available", () => {
|
|
53
|
+
expect(
|
|
54
|
+
factualCostDateRange(
|
|
55
|
+
[{ key: "2026-06-01", estimated_brl: null, estimated_usd: null }],
|
|
56
|
+
{ start: "2026-07-10", end: "2026-07-14" },
|
|
57
|
+
today,
|
|
58
|
+
),
|
|
59
|
+
).toEqual({ start: "2026-07-10", end: "2026-07-14" });
|
|
60
|
+
});
|
|
61
|
+
|
|
37
62
|
it.each([
|
|
38
63
|
["today", "2026-07-14", "2026-07-14"],
|
|
39
64
|
["yesterday", "2026-07-13", "2026-07-13"],
|
|
@@ -17,6 +17,35 @@ export interface OperationDateRange {
|
|
|
17
17
|
end: string;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/** A daily Cost Monitor group with a factual monetary measurement. */
|
|
21
|
+
export interface FactualCostDay {
|
|
22
|
+
key: string;
|
|
23
|
+
estimated_brl: number | null;
|
|
24
|
+
estimated_usd: number | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* All Time is unbounded for requests, but its disabled date controls should
|
|
29
|
+
* describe the actual Cost Monitor coverage rather than an Operations record.
|
|
30
|
+
*/
|
|
31
|
+
export function factualCostDateRange(
|
|
32
|
+
days: readonly FactualCostDay[] | undefined,
|
|
33
|
+
fallback: OperationDateRange,
|
|
34
|
+
today = new Date(),
|
|
35
|
+
): OperationDateRange {
|
|
36
|
+
const firstMeasuredDay = days
|
|
37
|
+
?.map((day) => day.key)
|
|
38
|
+
.filter(
|
|
39
|
+
(key, index, keys) =>
|
|
40
|
+
/^\d{4}-\d{2}-\d{2}$/.test(key) &&
|
|
41
|
+
(days[index]!.estimated_brl !== null || days[index]!.estimated_usd !== null),
|
|
42
|
+
)
|
|
43
|
+
.sort()[0];
|
|
44
|
+
return firstMeasuredDay === undefined
|
|
45
|
+
? fallback
|
|
46
|
+
: { start: firstMeasuredDay, end: toDateInputValue(today) };
|
|
47
|
+
}
|
|
48
|
+
|
|
20
49
|
export function initialTaskDateRange(
|
|
21
50
|
tasks: readonly { created_at: string }[],
|
|
22
51
|
today = new Date(),
|
package/templates/.agents/dashboard/modules/operations/src/react/operations-coverage-toast.test.ts
CHANGED
|
@@ -36,7 +36,7 @@ describe("Operations persisted aggregate display", () => {
|
|
|
36
36
|
expect(totals).toMatchObject({ duration_ms: null, tokens: 0, cost_brl: 0 });
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
it("preserves unavailable aggregate totals
|
|
39
|
+
it("preserves unavailable aggregate totals and renders zero only for an empty population", () => {
|
|
40
40
|
const overview = readFileSync(new URL("OperationsOverview.tsx", import.meta.url), "utf8");
|
|
41
41
|
|
|
42
42
|
expect(overview).toContain("overviewMetrics?.duration_ms ??");
|
|
@@ -48,7 +48,8 @@ describe("Operations persisted aggregate display", () => {
|
|
|
48
48
|
expect(overview).toContain(
|
|
49
49
|
"durationMs: totalDurationMetric, tokens: totalTokensMetric, costBrl: totalCostMetric",
|
|
50
50
|
);
|
|
51
|
-
expect(overview).toContain("
|
|
51
|
+
expect(overview).toContain("formatSummaryMetricValue(\n averageDuration,");
|
|
52
|
+
expect(overview).toContain('populationCount === 0 ? "0"');
|
|
52
53
|
expect(overview).not.toContain("allPhaseMetrics.tokens ?? 0");
|
|
53
54
|
expect(overview).not.toContain("executionMetrics.durationMs ?? 0");
|
|
54
55
|
});
|
|
@@ -50,6 +50,15 @@ describe("overview period filter", () => {
|
|
|
50
50
|
expect(shouldRenderOperationsContent(true)).toBe(false);
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
+
it("synchronizes the controlled All Time display after factual cost coverage arrives", () => {
|
|
54
|
+
const source = readFileSync(new URL("OperationsPage.tsx", import.meta.url), "utf8");
|
|
55
|
+
|
|
56
|
+
expect(source).toContain('datePreset !== "all"');
|
|
57
|
+
expect(source).toContain("dateRange.start === defaultDateRange.start");
|
|
58
|
+
expect(source).toContain('updatePeriod("all", defaultDateRange);');
|
|
59
|
+
expect(source).toContain("requestPeriodForOperations still omits both bounds");
|
|
60
|
+
});
|
|
61
|
+
|
|
53
62
|
it("derives all big-number KPIs from the period-filtered global aggregate", () => {
|
|
54
63
|
const source = readFileSync(new URL("OperationsOverview.tsx", import.meta.url), "utf8");
|
|
55
64
|
const helpers = readFileSync(
|
|
@@ -81,8 +90,9 @@ describe("overview period filter", () => {
|
|
|
81
90
|
|
|
82
91
|
expect(source).toContain('currency: "BRL" | "USD"');
|
|
83
92
|
expect(source).toContain("const selectedCurrencyCost =");
|
|
84
|
-
expect(source).toContain("formatCurrency(
|
|
85
|
-
expect(source).toContain("
|
|
93
|
+
expect(source).toContain("formatCurrency(value, currency, locale)");
|
|
94
|
+
expect(source).toContain("formatSummaryMetricValue(\n totalCostMetric,");
|
|
95
|
+
expect(source).toContain("formatRoundedCurrency(value, currency, locale)");
|
|
86
96
|
expect(source).toContain("const topEntitiesCost =");
|
|
87
97
|
expect(topPanel).toContain("formatCurrency(topCost, currency, locale)");
|
|
88
98
|
expect(periods).toContain("resolveRunCost?: ((runId: string) => number | null)");
|
|
@@ -126,7 +126,7 @@ test("loads every Cost Monitor tab before returning to Operation", async ({ page
|
|
|
126
126
|
|
|
127
127
|
async function expectGlobalMonthPreset(page: import("@playwright/test").Page) {
|
|
128
128
|
await expect(
|
|
129
|
-
page.locator(".dashboard-global-controls").
|
|
129
|
+
page.locator(".dashboard-global-controls").getByLabel("Período"),
|
|
130
130
|
).toHaveValue("this-month");
|
|
131
131
|
}
|
|
132
132
|
|
|
@@ -156,6 +156,10 @@ async function openAllTimeCostMonitor(page: Page) {
|
|
|
156
156
|
);
|
|
157
157
|
await page.goto("/?token=e2e-token");
|
|
158
158
|
await operations;
|
|
159
|
+
await page
|
|
160
|
+
.getByRole("navigation", { name: "Áreas do dashboard" })
|
|
161
|
+
.getByRole("button", { name: /Operação/ })
|
|
162
|
+
.click();
|
|
159
163
|
const period = page
|
|
160
164
|
.locator(".dashboard-global-controls")
|
|
161
165
|
.getByRole("combobox", { name: "Período" });
|
|
@@ -25,6 +25,12 @@ test("loads Cost Monitor within five seconds when the user enters after choosing
|
|
|
25
25
|
}) => {
|
|
26
26
|
await openDashboard(page);
|
|
27
27
|
|
|
28
|
+
await page
|
|
29
|
+
.getByRole("navigation", { name: "Áreas do dashboard" })
|
|
30
|
+
.getByRole("button", { name: /Operação/ })
|
|
31
|
+
.click();
|
|
32
|
+
await expect(periodControl(page)).toBeVisible();
|
|
33
|
+
|
|
28
34
|
await periodControl(page).selectOption("all");
|
|
29
35
|
await expect(page.locator(".period-transition-overlay")).toHaveCount(0, { timeout: 60_000 });
|
|
30
36
|
|
|
@@ -174,6 +174,10 @@ async function openDashboard(page: Page) {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
async function selectPeriod(page: Page, period: "this-month" | "all") {
|
|
177
|
+
await page
|
|
178
|
+
.getByRole("navigation", { name: "Áreas do dashboard" })
|
|
179
|
+
.getByRole("button", { name: /Operação/ })
|
|
180
|
+
.click();
|
|
177
181
|
const control = page
|
|
178
182
|
.locator(".dashboard-global-controls")
|
|
179
183
|
.getByRole("combobox", { name: "Período" });
|
|
@@ -163,10 +163,12 @@ test("exports Kanban, Lista and Cost Monitor through a save picker while the das
|
|
|
163
163
|
});
|
|
164
164
|
|
|
165
165
|
test("renders the modern read-only dashboard and meets accessibility gates", async ({ page }) => {
|
|
166
|
-
await openDashboard(page);
|
|
166
|
+
const initialResponse = await openDashboard(page);
|
|
167
167
|
const globalPeriod = page.getByRole("combobox", { name: /Per/ }).first();
|
|
168
|
-
await expect(globalPeriod).toHaveValue("
|
|
169
|
-
|
|
168
|
+
await expect(globalPeriod).toHaveValue("all");
|
|
169
|
+
const initialUrl = new URL(initialResponse.url());
|
|
170
|
+
expect(initialUrl.searchParams.has("from")).toBe(false);
|
|
171
|
+
expect(initialUrl.searchParams.has("to")).toBe(false);
|
|
170
172
|
await expect(page.getByRole("navigation", { name: "Dashboard Zaip AIOS menu" })).toBeVisible();
|
|
171
173
|
const capabilityNav = page.getByRole("navigation", { name: "Áreas do dashboard" });
|
|
172
174
|
const globalControls = page.locator(".dashboard-global-controls");
|
|
@@ -182,6 +184,13 @@ test("renders the modern read-only dashboard and meets accessibility gates", asy
|
|
|
182
184
|
buttons.map((button) => button.getBoundingClientRect().top),
|
|
183
185
|
);
|
|
184
186
|
expect(new Set(buttonBoxes.map((top) => Math.round(top))).size).toBe(1);
|
|
187
|
+
await expect(globalControls.getByRole("textbox", { name: "Busca" })).toBeHidden();
|
|
188
|
+
await expect(globalControls.getByLabel("Data inicial")).toBeHidden();
|
|
189
|
+
await expect(globalControls.getByLabel("Data final")).toBeHidden();
|
|
190
|
+
await expect(globalControls.getByRole("combobox", { name: "Período" })).toBeHidden();
|
|
191
|
+
await expect(page.getByLabel("Kanban de trabalho")).toHaveCount(0);
|
|
192
|
+
await expect(page.locator(".operations-task-card")).toHaveCount(0);
|
|
193
|
+
await selectOperations(page);
|
|
185
194
|
const searchInput = globalControls.getByRole("textbox", { name: "Busca" });
|
|
186
195
|
await expect(searchInput).toBeVisible();
|
|
187
196
|
await expect(globalControls.getByRole("button", { name: "Limpar busca" })).toHaveCount(0);
|
|
@@ -207,9 +216,6 @@ test("renders the modern read-only dashboard and meets accessibility gates", asy
|
|
|
207
216
|
elements.map((element) => Math.round(element.getBoundingClientRect().bottom)),
|
|
208
217
|
);
|
|
209
218
|
expect(new Set(controlRowBottoms).size).toBe(1);
|
|
210
|
-
await expect(page.getByLabel("Kanban de trabalho")).toHaveCount(0);
|
|
211
|
-
await expect(page.locator(".operations-task-card")).toHaveCount(0);
|
|
212
|
-
await selectOperations(page);
|
|
213
219
|
await expect(page.getByLabel("Kanban de trabalho")).toBeVisible();
|
|
214
220
|
await expect(page.locator(".operations-risk.is-standard").first()).toBeVisible();
|
|
215
221
|
const visibleCards = page.locator(".operations-task-card:visible");
|
|
@@ -266,6 +272,13 @@ test("renders the modern read-only dashboard and meets accessibility gates", asy
|
|
|
266
272
|
|
|
267
273
|
const results = await new AxeBuilder({ page }).analyze();
|
|
268
274
|
expect(results.violations).toEqual([]);
|
|
275
|
+
|
|
276
|
+
await capabilityNav.getByRole("button", { name: /Monitor de Custos/ }).click();
|
|
277
|
+
await expect(page.locator(".cost-tabs")).toBeVisible();
|
|
278
|
+
await expect(searchInput).toBeVisible();
|
|
279
|
+
await expect(startDate).toBeVisible();
|
|
280
|
+
await expect(endDate).toBeVisible();
|
|
281
|
+
await expect(period).toBeVisible();
|
|
269
282
|
});
|
|
270
283
|
|
|
271
284
|
test("loads a quick Operations summary without full snapshot hydration", async ({ page }) => {
|
|
@@ -456,9 +469,11 @@ test("groups macro work in All and reveals descendant Tasks only in Tasks", asyn
|
|
|
456
469
|
test("shows the observed all-time macro duration consistently for SPEC and PRD", async ({
|
|
457
470
|
page,
|
|
458
471
|
}) => {
|
|
459
|
-
await openDashboard(page);
|
|
460
|
-
await expect(page.getByRole("combobox", { name: /Per/ }).first()).toHaveValue("
|
|
461
|
-
const
|
|
472
|
+
const response = await openDashboard(page);
|
|
473
|
+
await expect(page.getByRole("combobox", { name: /Per/ }).first()).toHaveValue("all");
|
|
474
|
+
const responseUrl = new URL(response.url());
|
|
475
|
+
expect(responseUrl.searchParams.has("from")).toBe(false);
|
|
476
|
+
expect(responseUrl.searchParams.has("to")).toBe(false);
|
|
462
477
|
const projection = (await response.json()) as {
|
|
463
478
|
summary: {
|
|
464
479
|
overview_metrics?: {
|
|
@@ -526,20 +541,26 @@ test("loads monthly overview quickly and keeps PRD and SPEC chart totals reconci
|
|
|
526
541
|
page,
|
|
527
542
|
}) => {
|
|
528
543
|
const openedAt = Date.now();
|
|
529
|
-
await openDashboard(page);
|
|
544
|
+
const initialQuickResponse = await openDashboard(page);
|
|
530
545
|
expect(Date.now() - openedAt).toBeLessThan(3_000);
|
|
546
|
+
await selectOperations(page);
|
|
531
547
|
|
|
532
548
|
const period = page.getByRole("combobox", { name: /Per/ }).first();
|
|
533
549
|
for (const preset of ["all", "this-month"] as const) {
|
|
534
|
-
const response = page.waitForResponse(
|
|
535
|
-
(candidate) =>
|
|
536
|
-
candidate.status() === 200 &&
|
|
537
|
-
candidate.url().includes("/api/v2/operations") &&
|
|
538
|
-
candidate.url().includes("scope=quick"),
|
|
539
|
-
);
|
|
540
550
|
const selectedAt = Date.now();
|
|
541
|
-
|
|
542
|
-
|
|
551
|
+
const quickResponse =
|
|
552
|
+
preset === "all"
|
|
553
|
+
? initialQuickResponse
|
|
554
|
+
: await (async () => {
|
|
555
|
+
const response = page.waitForResponse(
|
|
556
|
+
(candidate) =>
|
|
557
|
+
candidate.status() === 200 &&
|
|
558
|
+
candidate.url().includes("/api/v2/operations") &&
|
|
559
|
+
candidate.url().includes("scope=quick"),
|
|
560
|
+
);
|
|
561
|
+
await period.selectOption(preset);
|
|
562
|
+
return response;
|
|
563
|
+
})();
|
|
543
564
|
const quickUrl = new URL(quickResponse.url());
|
|
544
565
|
const dateControls = page.locator(".dashboard-global-controls");
|
|
545
566
|
const expectedFrom =
|
|
@@ -671,6 +692,7 @@ async function selectPopulatedAllTime(page: import("@playwright/test").Page) {
|
|
|
671
692
|
const url = new URL(candidate.url());
|
|
672
693
|
return !url.searchParams.has("from") && !url.searchParams.has("to");
|
|
673
694
|
});
|
|
695
|
+
await selectOperations(page);
|
|
674
696
|
await page.getByRole("combobox", { name: /Per/ }).first().selectOption("all");
|
|
675
697
|
return response;
|
|
676
698
|
}
|