Skip to content

Commit e52b363

Browse files
atheurerclaude
andcommitted
Save supplemental metrics and hidden fields in Share URL
- Encode supplemental metric configs (source, type, display, chartType, breakouts, filter, sampleIndex) in the URL hash - Encode hidden fields in the URL hash - On URL restore, re-fetch each saved metric with its configuration - Fix timing: save restoredMetrics in React state instead of reading from restoredState ref (which is cleared before CompareView mounts) - CompareView wrapped with forwardRef, exposes getSupplementalMetrics via useImperativeHandle for the Share button - Fix formatBarLabel and formatValue to handle non-numeric values Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d00ba2b commit e52b363

2 files changed

Lines changed: 72 additions & 6 deletions

File tree

queries/cdmq/web-ui/src/App.jsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import DebugConsole from './components/DebugConsole';
77
import './index.css';
88

99
// Encode workflow state into a URL hash string
10-
function encodeState(filters, selectedRunIds, view, groupByList) {
10+
function encodeState(filters, selectedRunIds, view, groupByList, hiddenFields, supplementalMetrics) {
1111
var state = {};
1212
if (filters) {
1313
if (filters.benchmark) state.benchmark = filters.benchmark;
@@ -23,6 +23,17 @@ function encodeState(filters, selectedRunIds, view, groupByList) {
2323
if (selectedRunIds && selectedRunIds.length > 0) state.selectedRuns = selectedRunIds;
2424
if (view && view !== 'search') state.view = view;
2525
if (groupByList && groupByList.length > 0) state.groupBy = groupByList;
26+
if (hiddenFields && hiddenFields.length > 0) state.hidden = hiddenFields;
27+
if (supplementalMetrics && supplementalMetrics.length > 0) {
28+
state.metrics = supplementalMetrics.map(function (m) {
29+
var entry = { source: m.source, type: m.type, display: m.display };
30+
if (m.chartType && m.chartType !== 'bar') entry.chartType = m.chartType;
31+
if (m.breakouts && m.breakouts.length > 0) entry.breakouts = m.breakouts;
32+
if (m.filter) entry.filter = m.filter;
33+
if (m.sampleIndex != null) entry.sampleIndex = m.sampleIndex;
34+
return entry;
35+
});
36+
}
2637
return '#' + encodeURIComponent(JSON.stringify(state));
2738
}
2839

@@ -44,6 +55,7 @@ export default function App() {
4455
}, [theme]);
4556

4657
const searchRef = useRef(null);
58+
const compareRef = useRef(null);
4759
const [iterations, setIterations] = useState([]);
4860
const [selected, setSelected] = useState(new Map());
4961
const [loading, setLoading] = useState(false);
@@ -54,6 +66,7 @@ export default function App() {
5466
const [shareMsg, setShareMsg] = useState('');
5567
const lastFilters = useRef(null);
5668
const restoredState = useRef(null);
69+
const [restoredMetrics, setRestoredMetrics] = useState(null);
5770

5871
// On mount, check for state in URL hash
5972
// Don't switch view yet — wait until search completes and selections are applied
@@ -62,6 +75,8 @@ export default function App() {
6275
if (state) {
6376
restoredState.current = state;
6477
if (state.groupBy) setGroupByList(Array.isArray(state.groupBy) ? state.groupBy : [state.groupBy]);
78+
if (state.hidden) setHiddenFields(Array.isArray(state.hidden) ? state.hidden : []);
79+
if (state.metrics) setRestoredMetrics(state.metrics);
6580
}
6681
}, []);
6782

@@ -148,7 +163,8 @@ export default function App() {
148163
var runIdSet = new Set();
149164
selected.forEach(function (it) { runIdSet.add(it.runId); });
150165
var selectedRunIds = Array.from(runIdSet);
151-
var hash = encodeState(filters, selectedRunIds, view, groupByList);
166+
var suppMetrics = compareRef.current ? compareRef.current.getSupplementalMetrics() : null;
167+
var hash = encodeState(filters, selectedRunIds, view, groupByList, hiddenFields, suppMetrics);
152168
var url = window.location.origin + window.location.pathname + hash;
153169
// Update the URL bar so the user can see and copy it directly
154170
window.history.replaceState(null, '', hash);
@@ -230,7 +246,7 @@ export default function App() {
230246
)}
231247

232248
{view === 'compare' && (
233-
<CompareView selected={selected} groupByList={groupByList} setGroupByList={setGroupByList} hiddenFields={hiddenFields} setHiddenFields={setHiddenFields} />
249+
<CompareView ref={compareRef} selected={selected} groupByList={groupByList} setGroupByList={setGroupByList} hiddenFields={hiddenFields} setHiddenFields={setHiddenFields} restoredMetrics={restoredMetrics} />
234250
)}
235251

236252
{view === 'deepdive' && (

queries/cdmq/web-ui/src/components/CompareView.jsx

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect, useMemo, useCallback } from 'react';
1+
import { useState, useEffect, useMemo, useCallback, useRef, useImperativeHandle, forwardRef } from 'react';
22
import { ComposedChart, Bar, Line, XAxis, YAxis, CartesianGrid, Tooltip, ErrorBar, ResponsiveContainer, Legend, Cell, ReferenceLine, LabelList } from 'recharts';
33
import * as api from '../api/cdm';
44
import { timeWork } from '../debugLog';
@@ -28,6 +28,8 @@ function formatYTick(value) {
2828
// Compact value for bar labels — max 4 significant digits
2929
function formatBarLabel(v) {
3030
if (v == null) return '';
31+
v = Number(v);
32+
if (isNaN(v)) return '';
3133
var abs = Math.abs(v);
3234
if (abs === 0) return '0';
3335
if (abs >= 1000000) return (v / 1000000).toPrecision(3) + 'M';
@@ -39,6 +41,8 @@ function formatBarLabel(v) {
3941

4042
function formatValue(v) {
4143
if (v == null) return '';
44+
v = Number(v);
45+
if (isNaN(v)) return '';
4246
if (Math.abs(v) >= 1000) return v.toFixed(0);
4347
if (Math.abs(v) >= 1) return v.toFixed(2);
4448
return v.toPrecision(3);
@@ -488,7 +492,7 @@ function buildDimOptions(iterations) {
488492
return opts;
489493
}
490494

491-
export default function CompareView({ selected, groupByList, setGroupByList, hiddenFields, setHiddenFields }) {
495+
const CompareView = forwardRef(function CompareView({ selected, groupByList, setGroupByList, hiddenFields, setHiddenFields, restoredMetrics }, ref) {
492496
var [metricValues, setMetricValues] = useState({});
493497
var [loading, setLoading] = useState(false);
494498
var [supplementalMetrics, setSupplementalMetrics] = useState([]); // [{ source, type, values: {iterId: {mean,...}} }]
@@ -505,6 +509,12 @@ export default function CompareView({ selected, groupByList, setGroupByList, hid
505509
return Array.from(selected.values());
506510
}, [selected]);
507511

512+
useImperativeHandle(ref, function () {
513+
return {
514+
getSupplementalMetrics: function () { return supplementalMetrics; },
515+
};
516+
}, [supplementalMetrics]);
517+
508518
// Helper to get run IDs and date range from iterations
509519
function getRunContext() {
510520
var runIdSet = new Set();
@@ -574,6 +584,44 @@ export default function CompareView({ selected, groupByList, setGroupByList, hid
574584
}
575585
}, [iterations.length > 0 && dimOptions.length > 1]);
576586

587+
// Restore supplemental metrics from URL state
588+
var restoredMetricsApplied = useRef(false);
589+
useEffect(function () {
590+
if (restoredMetricsApplied.current) return;
591+
if (!restoredMetrics || restoredMetrics.length === 0) return;
592+
if (iterations.length === 0) return;
593+
restoredMetricsApplied.current = true;
594+
var ctx = getRunContext();
595+
restoredMetrics.forEach(function (rm) {
596+
var bestIdx = computeBestSampleIndex();
597+
var sIdx = rm.sampleIndex != null ? rm.sampleIndex : bestIdx;
598+
timeWork('Restore ' + rm.source + '::' + rm.type, function () {
599+
return api.getSupplementalMetric({
600+
iterations: ctx.iterations, start: ctx.start, end: ctx.end,
601+
source: rm.source, type: rm.type,
602+
breakout: rm.breakouts || [],
603+
filter: rm.filter || null,
604+
sampleIndex: sIdx,
605+
});
606+
}).then(function (res) {
607+
setSupplementalMetrics(function (prev) {
608+
return prev.concat([{
609+
source: rm.source,
610+
type: rm.type,
611+
values: res.values || {},
612+
display: rm.display || 'panel',
613+
chartType: rm.chartType || 'bar',
614+
filter: rm.filter || '',
615+
sampleIndex: sIdx,
616+
breakouts: rm.breakouts || [],
617+
remainingBreakouts: res.remainingBreakouts || [],
618+
loading: false,
619+
}]);
620+
});
621+
});
622+
});
623+
}, [iterations.length > 0]);
624+
577625
var handleShowAddMetric = useCallback(function () {
578626
setShowAddMetric(true);
579627
setAddMetricSource('');
@@ -1936,4 +1984,6 @@ export default function CompareView({ selected, groupByList, setGroupByList, hid
19361984

19371985
</div>
19381986
);
1939-
}
1987+
});
1988+
1989+
export default CompareView;

0 commit comments

Comments
 (0)