Skip to content

Commit 6001e2e

Browse files
fix(metrics): Polish AI Edge metrics charts (#1320)
## Summary AI Edge proxy metrics charts had uneven axes, misaligned hover highlights, overly thin WAF bars, and toolbar controls at inconsistent heights. This PR aligns chart styling with org usage patterns, improves readability, and syncs cross-chart hover across requests, latency, and WAF views. Key behaviors: - Y-axis ticks use linear scale math so labels match positions; stacked bar Y max sums series correctly - Cross-chart hover sync via shared `syncId`, step bucketing, fixed Y-axis width, and index-based sync - WAF events render as stacked bars with proportional bar width; latency uses a line chart - Metrics default to the last 30 minutes; toolbar controls share a consistent height <img width="1624" height="1061" alt="Screenshot 2026-06-19 at 16 58 30" src="https://github.com/user-attachments/assets/4fd5ea04-028f-4e39-9258-cd6b5d007c88" />
2 parents 2566b3c + 2cb1292 commit 6001e2e

16 files changed

Lines changed: 455 additions & 64 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/** Recharts syncId — links tooltip/cursor across AI Edge metric charts. */
2+
export const AI_EDGE_METRICS_SYNC_ID = 'ai-edge-proxy-metrics';

app/features/edge/proxy/metrics/edge-requests.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { DateTime } from '@/components/date-time';
2+
import { AI_EDGE_METRICS_SYNC_ID } from '@/features/edge/proxy/metrics/constants';
23
import {
34
MetricChart,
45
MetricChartTooltipContent,
@@ -76,9 +77,9 @@ export const HttpProxyEdgeRequests = ({
7677
chartType="area"
7778
showLegend={false}
7879
colorOverrides={RESPONSE_CODE_COLORS}
79-
height={140}
80-
yAxisFormatter={(value) => String(Math.round(value))}
81-
yAxisOptions={{ width: 55 }}
80+
padToTimeRange
81+
syncId={AI_EDGE_METRICS_SYNC_ID}
82+
height={200}
8283
onSeriesChange={setSeries}
8384
className="text-foreground shadow-none"
8485
/>
@@ -101,13 +102,14 @@ export const HttpProxyEdgeRequests = ({
101102
groupBy: ['le'],
102103
})
103104
}
104-
chartType="area"
105+
chartType="line"
105106
showLegend={false}
106107
colorOverrides={{ Series: 'var(--primary)' }}
107108
valueFormat="milliseconds-auto"
108-
height={140}
109+
padToTimeRange
110+
syncId={AI_EDGE_METRICS_SYNC_ID}
111+
height={200}
109112
yAxisFormatter={(value) => formatValue(value, 'milliseconds-auto')}
110-
yAxisOptions={{ width: 55 }}
111113
tooltipContent={({ active, payload, label, ...props }) => {
112114
if (!active || !payload?.length) return null;
113115
const filteredPayload = payload.filter((p) => (p.value as number) > 0);

app/features/edge/proxy/metrics/waf-events.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { DateTime } from '@/components/date-time';
2+
import { AI_EDGE_METRICS_SYNC_ID } from '@/features/edge/proxy/metrics/constants';
23
import {
34
MetricChart,
45
MetricChartTooltipContent,
@@ -143,13 +144,13 @@ export const HttpProxyWafEvents = ({
143144
`[${step}:1m]))`
144145
);
145146
}}
146-
chartType="area"
147+
chartType="bar"
147148
showLegend={false}
148149
colorOverrides={OUTCOME_COLORS}
149150
padToTimeRange
150-
height={140}
151-
yAxisFormatter={(value) => String(Math.round(value))}
152-
yAxisOptions={{ width: 55 }}
151+
stackBars
152+
syncId={AI_EDGE_METRICS_SYNC_ID}
153+
height={200}
153154
onSeriesChange={setSeries}
154155
tooltipContent={({ active, payload, label, ...props }) => {
155156
if (!active || !payload?.length) return null;

app/modules/metrics/components/base-metric.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
CardTitle,
99
} from '@datum-cloud/datum-ui/card';
1010
import { Icon } from '@datum-cloud/datum-ui/icons';
11-
import { AlertCircle, Minus } from 'lucide-react';
11+
import { AlertCircle } from 'lucide-react';
1212
import React from 'react';
1313

1414
// Deterministic heights so the skeleton doesn't shift on re-render
@@ -44,13 +44,10 @@ export function BaseMetric({
4444
emptyState,
4545
height,
4646
}: BaseMetricProps): React.ReactElement {
47-
const containerStyle = height ? { minHeight: height } : {};
47+
const containerStyle = height ? { height } : {};
4848

4949
const DefaultEmptyState = (
50-
<div className="text-center">
51-
<Icon icon={Minus} className="mx-auto mb-2 h-8 w-8" />
52-
<p>No data available</p>
53-
</div>
50+
<p className="text-muted-foreground text-sm">No data recorded in this period.</p>
5451
);
5552

5653
const renderContent = () => {

app/modules/metrics/components/controls/refresh-control.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { REFRESH_OPTIONS } from '@/modules/metrics/constants';
1+
import { METRICS_CONTROL_HEIGHT_CLASS, REFRESH_OPTIONS } from '@/modules/metrics/constants';
22
import { useMetrics } from '@/modules/metrics/context';
33
import { parseDurationToMs } from '@/modules/metrics/utils/date-parsers';
44
import { createMetricsParser } from '@/modules/metrics/utils/url-parsers';
@@ -12,6 +12,7 @@ import {
1212
SelectValue,
1313
} from '@datum-cloud/datum-ui/select';
1414
import { Tooltip } from '@datum-cloud/datum-ui/tooltip';
15+
import { cn } from '@datum-cloud/datum-ui/utils';
1516
import { useQueryClient } from '@tanstack/react-query';
1617
import { RefreshCw } from 'lucide-react';
1718
import { useQueryState } from 'nuqs';
@@ -100,14 +101,18 @@ export const RefreshControl = ({
100101
};
101102

102103
return (
103-
<div className="border-input bg-background flex h-[36px] items-center overflow-hidden rounded-md border shadow-none">
104+
<div
105+
className={cn(
106+
'border-input bg-background flex items-center overflow-hidden rounded-md border shadow-none',
107+
METRICS_CONTROL_HEIGHT_CLASS
108+
)}>
104109
{/* Manual Refresh Button */}
105110
<Tooltip message={getTooltipText()}>
106111
<Button
107112
type="quaternary"
108113
theme="borderless"
109114
size="small"
110-
className="size-9 rounded-r-none border-r"
115+
className="h-full w-9 shrink-0 rounded-r-none border-r px-0"
111116
onClick={handleManualRefresh}
112117
disabled={isManualRefreshing || isAutoRefreshing}>
113118
<Icon

app/modules/metrics/components/controls/step-control.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { STEP_OPTIONS } from '@/modules/metrics/constants';
1+
import { METRICS_CONTROL_HEIGHT_CLASS, STEP_OPTIONS } from '@/modules/metrics/constants';
22
import { useMetrics } from '@/modules/metrics/context';
33
import { createMetricsParser } from '@/modules/metrics/utils/url-parsers';
44
import { Button } from '@datum-cloud/datum-ui/button';
@@ -9,6 +9,7 @@ import {
99
SelectTrigger,
1010
SelectValue,
1111
} from '@datum-cloud/datum-ui/select';
12+
import { cn } from '@datum-cloud/datum-ui/utils';
1213
import { useQueryState } from 'nuqs';
1314
import { useEffect } from 'react';
1415

@@ -46,7 +47,11 @@ export const StepControl = ({ filterKey = 'step', defaultValue = '15m' }: StepCo
4647
}, [updateUrlStateEntry, filterKey, step]);
4748

4849
return (
49-
<div className="border-input bg-background flex h-[36px] items-center overflow-hidden rounded-md border shadow-none">
50+
<div
51+
className={cn(
52+
'border-input bg-background flex items-center overflow-hidden rounded-md border shadow-none',
53+
METRICS_CONTROL_HEIGHT_CLASS
54+
)}>
5055
<Button
5156
type="quaternary"
5257
theme="borderless"

app/modules/metrics/components/controls/time-range-control.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { PRESET_RANGES } from '@/modules/metrics/constants';
1+
import {
2+
DEFAULT_TIME_RANGE,
3+
METRICS_CONTROL_HEIGHT_CLASS,
4+
PRESET_RANGES,
5+
} from '@/modules/metrics/constants';
26
import { useMetrics } from '@/modules/metrics/context/metrics.context';
37
import {
48
getPresetDateRange,
@@ -9,14 +13,22 @@ import { createMetricsParser } from '@/modules/metrics/utils/url-parsers';
913
import { useApp } from '@/providers/app.provider';
1014
import type { PresetConfig, TimeRangeValue } from '@datum-cloud/datum-ui/date-picker';
1115
import { TimeRangePicker, getBrowserTimezone } from '@datum-cloud/datum-ui/date-picker';
16+
import { cn } from '@datum-cloud/datum-ui/utils';
1217
import { useQueryState } from 'nuqs';
1318
import { useCallback, useEffect, useMemo } from 'react';
1419

1520
/** Metrics presets as PresetConfig for the datum-ui TimeRangePicker */
1621
const METRICS_PRESETS: PresetConfig[] = PRESET_RANGES.map((p, i) => ({
1722
key: p.value,
1823
label: p.label,
19-
shortcut: p.value === 'now-7d' ? 'w' : p.value === 'now-24h' ? 'd' : String(i + 1),
24+
shortcut:
25+
p.value === 'now-7d'
26+
? 'w'
27+
: p.value === 'now-1h'
28+
? 'h'
29+
: p.value === 'now-24h'
30+
? 'd'
31+
: String(i + 1),
2032
getRange: (timezone: string) => getPresetDateRange(p.value, timezone),
2133
}));
2234

@@ -65,7 +77,7 @@ export interface TimeRangeControlProps {
6577
filterKey?: string;
6678
/**
6779
* Default time range value when no URL state exists.
68-
* Defaults to 'now-24h'.
80+
* Defaults to {@link DEFAULT_TIME_RANGE} (`now-30m`).
6981
*/
7082
defaultValue?: string;
7183
}
@@ -76,7 +88,7 @@ export interface TimeRangeControlProps {
7688
*/
7789
export const TimeRangeControl = ({
7890
filterKey = 'timeRange',
79-
defaultValue = 'now-24h',
91+
defaultValue = DEFAULT_TIME_RANGE,
8092
}: TimeRangeControlProps) => {
8193
const { registerUrlState, updateUrlStateEntry } = useMetrics();
8294
const { userPreferences } = useApp();
@@ -125,7 +137,7 @@ export const TimeRangeControl = ({
125137
disableFuture
126138
placeholder="Select time range"
127139
align="start"
128-
className="w-full sm:w-auto"
140+
className={cn('w-full sm:w-auto', METRICS_CONTROL_HEIGHT_CLASS, 'py-0 text-sm')}
129141
/>
130142
);
131143
};

0 commit comments

Comments
 (0)