fix: chart Y-axis auto-scaling, error rate unit, memory reference line, pointer events
- Throughput chart: divide totalCount by bucket duration (seconds) so Y-axis shows true msg/s instead of raw bucket counts; fixes flat-line appearance when TPS is low but totalCount is large - Error Rate chart: convert failedCount/totalCount to percentage; change yLabel from "err/h" to "%" to match KPI stat card unit - Memory chart: add threshold line at jvm.memory.heap.max so chart Y-axis extends to max heap and shows the reference line (spec 5.3) - Agent state: suppress containerStatus badge when value is "UNKNOWN"; only render it with "Container: <state>" label when a non-UNKNOWN secondary state is present (spec 5.4) - DashboardTab chartGrid: add pointer-events:none with pointer-events:auto on children so the chart grid overlay does not intercept clicks on the Application Health table rows below (spec 5.5) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -66,16 +66,20 @@ export default function AgentInstance() {
|
|||||||
60,
|
60,
|
||||||
);
|
);
|
||||||
|
|
||||||
const chartData = useMemo(
|
const chartData = useMemo(() => {
|
||||||
() =>
|
const buckets: any[] = timeseries?.buckets || [];
|
||||||
(timeseries?.buckets || []).map((b: any) => ({
|
// Compute bucket duration in seconds from consecutive timestamps (for msg/s conversion)
|
||||||
time: new Date(b.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
|
const bucketSecs =
|
||||||
throughput: b.totalCount,
|
buckets.length >= 2
|
||||||
latency: b.avgDurationMs,
|
? (new Date(buckets[1].timestamp).getTime() - new Date(buckets[0].timestamp).getTime()) / 1000
|
||||||
errors: b.failedCount,
|
: 60;
|
||||||
})),
|
return buckets.map((b: any) => ({
|
||||||
[timeseries],
|
time: new Date(b.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
|
||||||
);
|
throughput: bucketSecs > 0 ? b.totalCount / bucketSecs : b.totalCount,
|
||||||
|
latency: b.avgDurationMs,
|
||||||
|
errorPct: b.totalCount > 0 ? (b.failedCount / b.totalCount) * 100 : 0,
|
||||||
|
}));
|
||||||
|
}, [timeseries]);
|
||||||
|
|
||||||
const feedEvents = useMemo<FeedEvent[]>(() => {
|
const feedEvents = useMemo<FeedEvent[]>(() => {
|
||||||
const mapped = (events || [])
|
const mapped = (events || [])
|
||||||
@@ -118,7 +122,7 @@ export default function AgentInstance() {
|
|||||||
const throughputSeries = useMemo(
|
const throughputSeries = useMemo(
|
||||||
() =>
|
() =>
|
||||||
chartData.length
|
chartData.length
|
||||||
? [{ label: 'Throughput', data: chartData.map((d: any, i: number) => ({ x: i, y: d.throughput })) }]
|
? [{ label: 'msg/s', data: chartData.map((d: any, i: number) => ({ x: i, y: d.throughput })) }]
|
||||||
: null,
|
: null,
|
||||||
[chartData],
|
[chartData],
|
||||||
);
|
);
|
||||||
@@ -126,7 +130,7 @@ export default function AgentInstance() {
|
|||||||
const errorSeries = useMemo(
|
const errorSeries = useMemo(
|
||||||
() =>
|
() =>
|
||||||
chartData.length
|
chartData.length
|
||||||
? [{ label: 'Errors', data: chartData.map((d: any, i: number) => ({ x: i, y: d.errors })) }]
|
? [{ label: 'Error %', data: chartData.map((d: any, i: number) => ({ x: i, y: d.errorPct })) }]
|
||||||
: null,
|
: null,
|
||||||
[chartData],
|
[chartData],
|
||||||
);
|
);
|
||||||
@@ -229,6 +233,9 @@ export default function AgentInstance() {
|
|||||||
<span className={styles.scopeCurrent}>{agent.displayName}</span>
|
<span className={styles.scopeCurrent}>{agent.displayName}</span>
|
||||||
<StatusDot variant={statusVariant} />
|
<StatusDot variant={statusVariant} />
|
||||||
<Badge label={agent.status} color={statusColor} />
|
<Badge label={agent.status} color={statusColor} />
|
||||||
|
{agent.containerStatus && agent.containerStatus !== 'UNKNOWN' && (
|
||||||
|
<Badge label={`Container: ${agent.containerStatus}`} variant="outlined" color="auto" />
|
||||||
|
)}
|
||||||
{agent.version && <Badge label={agent.version} variant="outlined" color="auto" />}
|
{agent.version && <Badge label={agent.version} variant="outlined" color="auto" />}
|
||||||
<Badge
|
<Badge
|
||||||
label={`${agent.activeRoutes ?? (agent.routeIds?.length ?? 0)}/${agent.totalRoutes ?? (agent.routeIds?.length ?? 0)} routes`}
|
label={`${agent.activeRoutes ?? (agent.routeIds?.length ?? 0)}/${agent.totalRoutes ?? (agent.routeIds?.length ?? 0)} routes`}
|
||||||
@@ -324,7 +331,12 @@ export default function AgentInstance() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{heapSeries ? (
|
{heapSeries ? (
|
||||||
<AreaChart series={heapSeries} height={160} yLabel="MB" />
|
<AreaChart
|
||||||
|
series={heapSeries}
|
||||||
|
height={160}
|
||||||
|
yLabel="MB"
|
||||||
|
threshold={heapMax != null ? { value: heapMax / (1024 * 1024), label: 'Max Heap' } : undefined}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<EmptyState title="No data" description="No heap metrics available" />
|
<EmptyState title="No data" description="No heap metrics available" />
|
||||||
)}
|
)}
|
||||||
@@ -352,7 +364,7 @@ export default function AgentInstance() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{errorSeries ? (
|
{errorSeries ? (
|
||||||
<LineChart series={errorSeries} height={160} yLabel="err/h" />
|
<LineChart series={errorSeries} height={160} yLabel="%" />
|
||||||
) : (
|
) : (
|
||||||
<EmptyState title="No data" description="No error data in range" />
|
<EmptyState title="No data" description="No error data in range" />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -14,6 +14,11 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chartGrid > * {
|
||||||
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chartRow {
|
.chartRow {
|
||||||
|
|||||||
Reference in New Issue
Block a user