Skip to content

Commit 6fca937

Browse files
atheurerclaude
andcommitted
Update DESIGN.md with all recent compare view features
Major documentation updates: - Multi-group-by with auto-grouping and chip reordering - Hierarchical group-by headers (pivot-table-like) - Series-by removed (replaced by full auto-grouping) - Click-to-pin with reference lines and bar dimming - Per-sample metric selection - Metric filter support (gt/ge/lt/le) - Breakout chart type selector (bars/stacked/lines) - Breakout sidebar table with rowSpan and text dedup - Bar value labels with conditional display - Supplemental panels below primary chart - Hide fields feature - Iteration-targeted supplemental metric queries - Updated URL state format (groupBy as array, no seriesBy) - Updated known limitations and planned features Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2424a72 commit 6fca937

1 file changed

Lines changed: 106 additions & 38 deletions

File tree

queries/cdmq/web-ui/DESIGN.md

Lines changed: 106 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,9 @@ The text input at the top filters iterations by param name/value:
243243
2. Click "Compare (N)" button in the nav bar
244244
3. Primary metric values auto-fetched
245245
4. Bar chart displayed with one bar per iteration
246-
5. Use Group-by and Series-by dropdowns to organize bars
246+
5. Group-by auto-populated (sorted by distinct value count, fewest first)
247247
6. Add supplemental metrics via "+ Add Metric"
248+
7. Click a bar to pin it — shows values in sidebars, dims other bars
248249

249250
### Chart Architecture
250251

@@ -253,19 +254,18 @@ The chart uses Recharts' `ComposedChart` which supports mixing Bar and Line comp
253254
**Chart data entry structure:**
254255
```javascript
255256
{
256-
name: "wsize=64, nthreads=1", // X-axis label (only varying, non-grouped params/tags)
257-
value: 1234.56, // Primary metric mean
258-
errorY: 23.4, // Stddev for error bars
257+
name: "wsize=64", // X-axis label (only remaining varying params/tags)
258+
value: 1234.56, // Primary metric mean
259+
errorY: 23.4, // Stddev for error bars
259260
iterationId: "uuid",
260-
groupValue: "nthreads=1", // Group dimension value
261-
seriesValue: "gro=on", // Series dimension value
262-
color: "#5b8def", // Bar fill color
263-
isGap: false, // Gap entries for visual separation
264-
'supp_0': 0.42, // Supplemental metric 0 value
261+
groupValue: "nthreads=1, protocol=tcp", // Compound group key
262+
color: "#5b8def", // Bar fill color
263+
isGap: false, // Gap entries for visual separation
264+
'supp_0': 0.42, // Supplemental metric 0 value
265265
'supp_0_stddevPct': 1.5,
266266
'supp_0_error': 0.006,
267267
'supp_0_samples': 3,
268-
'supp_0_<rx>': 5.2, // Breakout label values (when breakouts active)
268+
'supp_0_<rx>': 5.2, // Breakout label values (when breakouts active)
269269
'supp_0_<tx>': 3.8,
270270
}
271271
```
@@ -275,24 +275,44 @@ The chart uses Recharts' `ComposedChart` which supports mixing Bar and Line comp
275275
The chart applies a hierarchical approach to reduce label redundancy:
276276

277277
1. **Chart subtitle**: Globally common params/tags/benchmark (same across all selected iterations)
278-
2. **Group headers** (tab-like bars above chart): Group-by dimension value + per-group common items
279-
3. **Bar labels** (X-axis): Only params/tags that vary within each group, excluding group-by dimension and per-group common items
280-
4. **Legend**: Series-by dimension value-to-color mapping
278+
2. **Hierarchical group headers**: Each group-by dimension gets its own row above the chart. Each row shows the dimension name on the left and value labels spanning the bars they cover, creating a pivot-table-like header.
279+
3. **Bar labels** (X-axis): Only the remaining varying dimension(s) not used in group-by
280+
4. **Bar value labels**: Metric values shown inside bars when they fit (checked against bar width and height). Hidden automatically when bars are too narrow.
281+
5. **Value consolidation**: Params/tags with the same value are grouped in labels (e.g., `bs,rw,size=4k` instead of repeating `=4k` three times)
281282

282-
**Example:** If you group by `param:nthreads` and series by `tag:gro`:
283-
- Subtitle: `benchmark=uperf, protocol=tcp` (common to all)
284-
- Group headers: `nthreads=1` (with `wsize=64` if wsize is the same within that group), `nthreads=8`
285-
- Bar labels: Only remaining varying dimensions (e.g., `wsize=256`)
286-
- Legend: `gro=on` (blue), `gro=off` (green)
283+
### Auto-Grouping
284+
285+
When entering the compare view, group-by dimensions are auto-populated:
286+
1. `buildDimOptions()` scans iterations for varying dimensions only
287+
2. Dimensions sorted by distinct value count (fewest first — best grouping levels)
288+
3. All but the last dimension become group-by levels
289+
4. The last dimension stays as the bar label
290+
5. User can reorder chips with left/right arrow buttons
291+
6. "Auto" button recomputes; "Clear" removes all
292+
293+
### Hide Fields
294+
295+
Users can hide specific params/tags from the compare view:
296+
- Hidden fields excluded from group-by/series-by dropdowns, common/varying computation, and bar labels
297+
- Shown as red strikethrough chips
298+
- Hiding auto-removes from group-by if currently used
299+
300+
### Click-to-Pin and Selection Indicators
301+
302+
- **Click a bar** in the primary chart to pin its data in all sidebars
303+
- **Red dashed ReferenceLine** appears at the selected bar's position in all chart panels
304+
- **Non-selected bars dim to 20% opacity** across all panels
305+
- Click the same bar again to unpin
287306

288307
### Grouping Implementation
289308

290309
1. `buildDimOptions()` scans iterations for varying dimensions only (>1 distinct value) — common dimensions are excluded from dropdowns
291-
2. Iterations sorted by `naturalCompare` (numeric-aware: 64 before 256 before 1024)
292-
3. Per-group common items computed: params/tags that vary globally but are constant within a group
293-
4. `buildIterLabel()` excludes group-by dimension and per-group common items from bar labels
294-
5. Gap entries (`{ isGap: true }`) inserted between groups for visual separation
295-
6. Group headers rendered as proportionally-sized tabs above the chart using flex layout
310+
2. Multi-group-by: `groupByList` is an array of dimension strings, compound key computed by `getCompoundGroupValue()`
311+
3. Iterations sorted by compound group key using `naturalCompare` (numeric-aware: 64 before 256 before 1024)
312+
4. Per-group common items computed: params/tags that vary globally but are constant within a group
313+
5. `buildIterLabel()` excludes all group-by dimensions and per-group common items from bar labels; consolidates params with same value
314+
6. Gap entries (`{ isGap: true }`) inserted between groups for visual separation
315+
7. Hierarchical headers rendered inside `compare-chart-area` with matching chart margins
296316

297317
### Y-Axis Management
298318

@@ -302,6 +322,7 @@ The chart applies a hierarchical approach to reduce label redundancy:
302322
- **Alignment**: Hidden right Y-axis (width=80 or width=1) added to all charts so bars align across panels
303323
- **HTML labels**: Y-axis metric names rendered as HTML `<div>` elements outside the SVG to avoid Recharts clip-path issues
304324
- **Tick formatting**: `formatYTick()` adapts precision based on magnitude (e.g., "1.2k", "0.423", "80")
325+
- **Sidebar**: Each chart panel has a sidebar (300px) showing pinned iteration values
305326

306327
---
307328

@@ -319,21 +340,34 @@ The chart applies a hierarchical approach to reduce label redundancy:
319340

320341
Each added metric is displayed as a row with:
321342
- Colored left border (consistent color across chart and panel)
322-
- Metric name (source::type)
323-
- Display mode label ("overlay" or "panel")
343+
- Metric name (source::type) in monospace font
344+
- Display mode badge ("overlay" or "panel")
324345
- **+ Breakout** dropdown (populated with `remainingBreakouts` from server)
346+
- **Chart type** selector (Bars, Stacked, Lines) — visible when breakouts are active
347+
- **Sample** selector — choose which sample to display (auto-selects the sample closest to the primary metric mean)
348+
- **Filter** input — accepts `gt:N`, `ge:N`, `lt:N`, `le:N` syntax for server-side label filtering
325349
- Remove button (x)
326-
- Active breakout chips (removable)
350+
- Active breakout chips with editable filter values
351+
352+
### Per-Sample Selection
353+
354+
Supplemental metrics query a single sample instead of averaging across all:
355+
- Client computes the best sample index from `metricValues.sampleValues` (closest to mean)
356+
- Server uses the provided `sampleIndex` directly
357+
- Sample dropdown shows each sample's primary metric value for reference
358+
- Filters work correctly since they operate on single-sample data
327359

328360
### Breakout Workflow
329361

330362
1. Click "+ Breakout" dropdown, select a dimension (e.g., "direction")
331363
2. Client re-queries the metric with `breakout: ["direction"]`
332364
3. Server returns multi-label values: `{ "<rx>": { mean, ... }, "<tx>": { mean, ... } }`
333365
4. Server also returns updated `remainingBreakouts` for further drilling
334-
5. Chart renders one bar/line per label
366+
5. Chart renders one bar/line per label; chart type selectable (Bars/Stacked/Lines)
335367
6. User can add another breakout level (e.g., "hostname") — labels become `"<rx>-<host1>"`, etc.
336-
7. Removing a breakout re-queries with the reduced breakout array
368+
7. Breakout chips have editable filter inputs accepting exact values, `val1+val2`, `r/regex/`, or `R/regex/`
369+
8. "Apply" button re-queries with the filter applied
370+
9. Removing a breakout re-queries with the reduced breakout array
337371

338372
### Data Format
339373

@@ -351,12 +385,40 @@ The empty string label `""` with no breakouts is correct — it means there's no
351385

352386
### Chart Rendering with Breakouts
353387

354-
**Panel mode:** Each label becomes its own `<Bar>` component with a distinct color.
388+
**Panel mode:** Each label becomes its own `<Bar>` component with a distinct color. Chart type selectable:
389+
- **Bars**: Side-by-side bars per breakout label
390+
- **Stacked**: Bars stacked using Recharts `stackId`
391+
- **Lines**: Line chart with dots per breakout label
355392

356393
**Overlay mode:** Each label becomes its own `<Line>` component on the right Y-axis.
357394

395+
**Supplemental panels render below the primary chart** so the primary chart with its X-axis labels appears first.
396+
358397
Label detection is done dynamically by scanning chart data entry keys for the pattern `supp_{index}_{label}` (excluding keys ending in `_stddevPct`, `_error`, `_samples`).
359398

399+
### Breakout Sidebar (Value Legend)
400+
401+
When a bar is clicked (pinned), each chart panel shows a sidebar with metric values:
402+
403+
- **No breakouts**: Simple label + value display
404+
- **With breakouts**: Table layout with rowSpan for repeated segment values (like the iteration table's run grouping)
405+
- Column headers from breakout dimension names
406+
- Labels parsed into `<segment>` patterns and grouped hierarchically
407+
- Common text suffixes stripped and shown in header (e.g., `.local.net` removed from hostnames)
408+
- Common text prefixes stripped if no suffix found (e.g., `host-` removed)
409+
- Numeric-only columns skip deduplication
410+
- Delimiter-boundary detection (`.`, `-`, `_`, `/`) prevents splitting words
411+
412+
### Bar Value Labels
413+
414+
Metric values are displayed inside bars when they fit:
415+
- `formatBarLabel()` produces compact values (3-4 significant digits, k/M suffixes)
416+
- Custom `<LabelList>` content function checks `props.width` and `props.height` against text width estimate
417+
- **Side-by-side bars**: shown if bar width > text width + 4px AND height > 16px
418+
- **Stacked bars**: shown if segment height > 14px
419+
- **Lines**: labels always skipped
420+
- Font: 12px bold monospace, white with 90% opacity
421+
360422
---
361423

362424
## Autocomplete Dropdowns
@@ -423,21 +485,21 @@ http://host:3000/#%7B%22benchmark%22%3A%22uperf%22%2C%22start%22%3A%222026.01%22
423485
params: [{ arg: "wsize", val: "64,256" }],
424486
selectedRuns: ["uuid1", "uuid2"], // Run IDs (not iteration IDs — much shorter)
425487
view: "compare",
426-
groupBy: "param:nthreads",
427-
seriesBy: "tag:gro"
488+
groupBy: ["param:nthreads", "param:protocol"] // Array of group-by dimensions
428489
}
429490
```
430491

431492
### Restoration Flow
432493

433494
1. On mount: `decodeState(window.location.hash)` parsed, stored in `restoredState.current`
434-
2. `groupBy`/`seriesBy` set immediately
495+
2. `groupByList` set immediately (handles both array and legacy single-string format)
435496
3. `view` NOT set yet (deferred until search completes)
436497
4. After SearchPanel mounts: `setFiltersAndSearch(filters)` called via ref
437498
5. SearchPanel updates filters, triggers search via `pendingSearch` ref + useEffect
438499
6. `handleSearchResults` receives results, auto-selects iterations from matching run IDs
439500
7. View switched to saved view (e.g., "compare")
440501
8. `restoredState.current` cleared to prevent re-application on next search
502+
9. If no groupByList was saved, auto-group runs on CompareView mount
441503

442504
**Key design decision:** View switch is deferred until after search + selection to avoid showing an empty Compare view while data is loading.
443505

@@ -493,8 +555,14 @@ The `/iterations/details` endpoint accepts `{ runIds, start, end }` and returns
493555
| POST | `/api/v1/iterations/metric-types` | Types for a given source |
494556
| POST | `/api/v1/iterations/supplemental-metric` | Fetch metric values with optional breakouts |
495557

496-
The supplemental-metric endpoint:
497-
1. Discovers iterations and their passing samples
558+
The supplemental-metric endpoint accepts `{ iterations, runIds, start, end, source, type, breakout, filter, sampleIndex }`:
559+
- `iterations`: array of `{iterationId, runId}` for targeted queries (avoids discovering all iterations from runIds)
560+
- `sampleIndex`: which sample to query (client computes best sample from primary metric values)
561+
- `breakout`: array of breakout dimensions with optional filters
562+
- `filter`: value filter (gt:N, ge:N, lt:N, le:N)
563+
564+
Processing:
565+
1. Uses provided iteration IDs directly (or discovers from runIds as fallback)
498566
2. Gets primary period IDs and ranges
499567
3. Builds metric data sets with the specified source, type, and breakouts
500568
4. Calls `cdm.getMetricDataSets()` to fetch from OpenSearch
@@ -590,14 +658,14 @@ Primary metric values are NOT loaded with iteration details (would add ~7 second
590658
### Current Limitations
591659

592660
- **Phase 3 (Deep Dive):** Time-series line charts are not yet implemented
593-
- **Breakout filters:** Breakout dimensions can be selected but not filtered to specific values (e.g., `package=0`). The data structures support this for future implementation.
594-
- **Panel alignment with overlays:** Slight misalignment can occur when one chart has an overlay right Y-axis and another doesn't, despite hidden Y-axis placeholders
595661
- **Large result sets:** Searching across many months with hundreds of runs can be slow due to sequential OpenSearch queries
596662
- **Bundle size:** Recharts adds ~400KB to the bundle. Code splitting could help.
663+
- **Breakout label parsing:** CDM may omit breakout dimensions with single values from labels, making label-to-dimension mapping imperfect. The sidebar uses segment-based grouping to work around this.
664+
- **Supplemental metric in URL state:** Currently supplemental metrics, breakouts, and hidden fields are not encoded in the Share URL
597665

598666
### Planned Features
599667

600-
- **Breakout value filters:** Allow `breakout=["package=0"]` to filter to specific breakout values
601668
- **Deep Dive view:** Time-series line charts with zoom/pan and interactive breakout exploration
602669
- **Save/load workflows:** Server-side or localStorage persistence of named workflows
603-
- **Supplemental metric in URL state:** Currently supplemental metrics and breakouts are not encoded in the Share URL
670+
- **Supplemental metrics in URL state:** Encode added metrics, breakouts, and display modes in the Share URL
671+
- **Drag-to-reorder:** Group-by chips currently use arrow buttons; drag-and-drop would be more intuitive

0 commit comments

Comments
 (0)