diff --git a/.server-changes/fix-metrics-dashboard-chart-bugs.md b/.server-changes/fix-metrics-dashboard-chart-bugs.md new file mode 100644 index 0000000000..efbd9238b9 --- /dev/null +++ b/.server-changes/fix-metrics-dashboard-chart-bugs.md @@ -0,0 +1,6 @@ +--- +area: webapp +type: fix +--- + +Fix metrics dashboard chart series colors going out of sync and widgets not reloading stale data when scrolled back into view diff --git a/apps/webapp/app/components/code/QueryResultsChart.tsx b/apps/webapp/app/components/code/QueryResultsChart.tsx index 2da90c9e00..190304a1e9 100644 --- a/apps/webapp/app/components/code/QueryResultsChart.tsx +++ b/apps/webapp/app/components/code/QueryResultsChart.tsx @@ -889,13 +889,15 @@ export const QueryResultsChart = memo(function QueryResultsChart({ const cfg: ChartConfig = {}; sortedSeries.forEach((s, i) => { const statusColor = groupByIsRunStatus ? getRunStatusHexColor(s) : undefined; + const originalIndex = config.yAxisColumns.indexOf(s); + const colorIndex = originalIndex >= 0 ? originalIndex : i; cfg[s] = { label: s, - color: statusColor ?? config.seriesColors?.[s] ?? getSeriesColor(i), + color: statusColor ?? config.seriesColors?.[s] ?? getSeriesColor(colorIndex), }; }); return cfg; - }, [sortedSeries, groupByIsRunStatus, config.seriesColors]); + }, [sortedSeries, groupByIsRunStatus, config.seriesColors, config.yAxisColumns]); // Custom tooltip label formatter for better date display const tooltipLabelFormatter = useMemo(() => { diff --git a/apps/webapp/app/routes/resources.metric.tsx b/apps/webapp/app/routes/resources.metric.tsx index a037fb7882..3c19d3947f 100644 --- a/apps/webapp/app/routes/resources.metric.tsx +++ b/apps/webapp/app/routes/resources.metric.tsx @@ -173,6 +173,7 @@ export function MetricWidget({ const [response, setResponse] = useState(null); const [isLoading, setIsLoading] = useState(false); const abortControllerRef = useRef(null); + const isDirtyRef = useRef(false); // Track the latest props so the submit callback always uses fresh values // without needing to be recreated (which would cause useInterval to re-register listeners). @@ -180,8 +181,11 @@ export function MetricWidget({ propsRef.current = props; const submit = useCallback(() => { - // Skip fetching if the widget is not visible on screen - if (!isVisibleRef.current) return; + if (!isVisibleRef.current) { + isDirtyRef.current = true; + return; + } + isDirtyRef.current = false; // Abort any in-flight request for this widget abortControllerRef.current?.abort(); @@ -225,7 +229,7 @@ export function MetricWidget({ // When a widget scrolls into view and has no data yet, trigger a load. const { ref: visibilityRef, isVisibleRef } = useElementVisibility({ onVisibilityChange: (visible) => { - if (visible && !response) { + if (visible && (!response || isDirtyRef.current)) { submit(); } },