mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
fix(frontend): hide unavailable drawer history metrics
This commit is contained in:
@@ -703,7 +703,9 @@ change may globally weaken the Task 03 lifecycle-state idempotency invariant.
|
||||
accumulating, but it must never be expanded into synthetic timestamps or a
|
||||
flat line. Fewer than two stored points remains `Collecting history` and
|
||||
only metrics-store samples may contribute chart geometry, bounds, or hover
|
||||
inspection.
|
||||
inspection. Current-value legends must preserve the same availability
|
||||
semantics as workload rows: the poller's negative unknown-disk sentinel is
|
||||
unavailable and renders `-`, never a fabricated negative percentage.
|
||||
History cards must let the plot area stretch to the card height instead of
|
||||
pinning the SVG wrapper to a fixed short height, so guest and node drawer
|
||||
history cards do not leave unused card space when headers have different
|
||||
|
||||
@@ -1,30 +1,35 @@
|
||||
{
|
||||
"version": 1,
|
||||
"base_sha": "caf7f80867871f0a601d9a572cbab309f8d4b41f",
|
||||
"verified_at": "2026-08-25T18:42:00Z",
|
||||
"base_sha": "3678d583bf43840d0086e4ba70c81aec77dadd01",
|
||||
"verified_at": "2026-08-25T19:06:05Z",
|
||||
"result": "passed",
|
||||
"changed_paths": [
|
||||
"frontend-modern/src/components/AI/AICostDashboard.tsx",
|
||||
"frontend-modern/src/utils/aiCostPresentation.ts"
|
||||
"frontend-modern/src/components/Workloads/guestDrawerModel.ts"
|
||||
],
|
||||
"content_sha256": {
|
||||
"frontend-modern/src/components/AI/AICostDashboard.tsx": "eb9291049d9c415bfc24868cf45da5f9d862bb24f98100bc0fb10e2188554100",
|
||||
"frontend-modern/src/utils/aiCostPresentation.ts": "816376697dc3eefa1b3d3a198ab29f80ae9148ddbffaf3dc1ab18fb22bc46041"
|
||||
"frontend-modern/src/components/Workloads/guestDrawerModel.ts": "5aaccadfaa746f391431c5138e7a1c051b643e264e57fb8e2f58f121cffb0de9"
|
||||
},
|
||||
"routes": ["/settings/pulse-intelligence/provider"],
|
||||
"routes": [
|
||||
"/proxmox/overview",
|
||||
"/docker/overview",
|
||||
"/kubernetes/overview",
|
||||
"/truenas/apps"
|
||||
],
|
||||
"viewports": [
|
||||
{ "width": 1440, "height": 900 },
|
||||
{ "width": 1280, "height": 800 },
|
||||
{ "width": 390, "height": 844 }
|
||||
],
|
||||
"states": [
|
||||
"loaded provider usage range with no provider/model rows",
|
||||
"phone projection retaining Provider, Model, and Est. USD",
|
||||
"desktop projection restoring Input, Output, and Total"
|
||||
"stopped Proxmox guest History drawer using the negative unknown-disk sentinel",
|
||||
"running Docker container History drawer with populated utilization data",
|
||||
"degraded Kubernetes pod History drawer with populated utilization and network data",
|
||||
"TrueNAS app Overview drawer with long container and identity values"
|
||||
],
|
||||
"interactions": [
|
||||
"navigated to Provider & Models and scrolled the settings surface to Provider Usage & Spend",
|
||||
"verified the empty-state row is fully readable at 390px after correcting fixed-table text clipping",
|
||||
"measured zero table-shell and page overflow at 390px and 1440px",
|
||||
"confirmed all six desktop headers return at 1440px"
|
||||
"opened the stopped dev-portal Proxmox guest and switched from Overview to History at 390px",
|
||||
"verified the unavailable disk fallback renders as a dash instead of a negative percentage at 390px and 1280px",
|
||||
"opened Docker and Kubernetes History tabs to confirm valid current metrics and chart geometry remain intact",
|
||||
"opened a TrueNAS app drawer and inspected long-value truncation, stacking, scrolling, and tab placement",
|
||||
"restored the browser viewport after responsive verification"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -502,6 +502,33 @@ describe('GuestDrawer', () => {
|
||||
expect(utilizationChart.querySelector('path')).toBeNull();
|
||||
});
|
||||
|
||||
it('renders unavailable current disk usage as a dash', async () => {
|
||||
chartsApiMocks.getMetricsHistory.mockResolvedValue({
|
||||
resourceType: 'vm',
|
||||
resourceId: 'inst1:node1:100',
|
||||
range: '24h',
|
||||
start: 0,
|
||||
end: 0,
|
||||
metrics: {},
|
||||
source: 'store',
|
||||
});
|
||||
render(() => (
|
||||
<GuestDrawer
|
||||
guest={makeGuest({
|
||||
disk: { total: 100, used: 0, free: 100, usage: -1 },
|
||||
})}
|
||||
onClose={vi.fn()}
|
||||
/>
|
||||
));
|
||||
|
||||
await fireEvent.click(screen.getByText('History'));
|
||||
await waitFor(() => expect(chartsApiMocks.getMetricsHistory).toHaveBeenCalled());
|
||||
|
||||
const utilizationChart = screen.getAllByTestId('guest-history-group-chart')[0];
|
||||
expect(utilizationChart).toHaveTextContent('Disk-');
|
||||
expect(utilizationChart).not.toHaveTextContent('Disk-100.0%');
|
||||
});
|
||||
|
||||
it('updates grouped metric header values on History chart hover', async () => {
|
||||
render(() => <GuestDrawer guest={makeGuest()} onClose={vi.fn()} />);
|
||||
|
||||
|
||||
@@ -142,6 +142,15 @@ describe('guestDrawerModel (branch coverage)', () => {
|
||||
expect(result.memory).toBeUndefined();
|
||||
});
|
||||
|
||||
it('does not render the negative unknown-disk sentinel as a current history value', () => {
|
||||
const result = getGuestDrawerCurrentMetrics(
|
||||
makeGuest({
|
||||
disk: { total: 100, used: 0, free: 100, usage: -1 },
|
||||
}),
|
||||
);
|
||||
expect(result.disk).toBeUndefined();
|
||||
});
|
||||
|
||||
it('drops a non-finite network value via the finite() guard', () => {
|
||||
const result = getGuestDrawerCurrentMetrics(
|
||||
makeGuest({ networkIn: Number.POSITIVE_INFINITY }),
|
||||
|
||||
@@ -73,13 +73,19 @@ export const getGuestDrawerCurrentMetrics = (guest: Guest): Record<string, numbe
|
||||
const cpuPercent = available('cpu') ? getWorkloadCPUPercent(guest.cpu) : undefined;
|
||||
const memUsage =
|
||||
available('memory') && !guest.memory?.usageUnavailable ? guest.memory?.usage : undefined;
|
||||
const diskUsage = available('disk') ? guest.disk?.usage : undefined;
|
||||
const reportedDiskUsage = available('disk') ? guest.disk?.usage : undefined;
|
||||
const diskUsage =
|
||||
typeof reportedDiskUsage === 'number' &&
|
||||
Number.isFinite(reportedDiskUsage) &&
|
||||
reportedDiskUsage >= 0
|
||||
? reportedDiskUsage
|
||||
: undefined;
|
||||
const finite = (value: number | undefined): number | undefined =>
|
||||
typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
||||
return {
|
||||
cpu: finite(cpuPercent),
|
||||
memory: finite(memUsage),
|
||||
disk: finite(diskUsage),
|
||||
disk: diskUsage,
|
||||
netin: available('networkIO') ? finite(guest.networkIn) : undefined,
|
||||
netout: available('networkIO') ? finite(guest.networkOut) : undefined,
|
||||
diskread: available('diskIO') ? finite(guest.diskRead) : undefined,
|
||||
|
||||
Reference in New Issue
Block a user