refactor(status): carry series dim/node structurally instead of the dim|node string#1484
Conversation
…im|node string
Series now carries structured dim/node fields populated by runPipeline on
every path (groupBy per-node/cluster/'node'/Other, field per-node/cluster).
The composite key remains only as a React/recharts id via the exported
makeSeriesKey and is never parsed back, so dimension values containing '|'
(URL paths can) no longer corrupt per-node splitting. connection.tsx's
secondary ' · ' split is replaced by a composite→{path,method} map, with
scope-less thresholds kept global regardless of map hits (Gemini review).
The forked extractNode semantics are unified on the structured fields:
SmallMultiples legends per-node series only; LineChartWithNodeLegend keeps
a documented node-or-whole-key fallback for cluster/derived series. The
copy-pasted per-node filter/relabel/color scaffolding and 5x duplicated
shortenNodeLabel collapse into useNodeFilteredSeries + lib/nodeLabels.
Fixes #1450
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UZEaYdKFXuQw2Hf6XpPdEV
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kriszyp
left a comment
There was a problem hiding this comment.
Clean structured-field refactor — nicely done. I verified the restructured data flow preserves per-series/per-node grouping end to end: every pipeline emit path sets dim/node, all six consumers are migrated off the dim|node string protocol (zero |-parsers left in the analytics tree), and the #1477 per-instance threshold scoping is intact — actually strengthened by the scope-order and numeric-cast fixes in connection.tsx.
One minor, non-blocking suggestion: the group === '' branch in LineChartWithNodeLegend.legendGroup looks unreachable (legendGroup returns s.node or s.key, neither ever '') — harmless, could be dropped whenever you're back in that file.
Approving. Thanks for the thorough tests and the documented extractNode divergence.
— Claude (Sonnet 5), reviewed via review-queue
Closes #1450 — Use structured series keys instead of the dim|node string protocol.
The corruption class this closes
The pipeline encoded per-node series identity as a
`${dim}|${node}`string, and six consumers re-deriveddim/nodefrom it — four with hand-rolled first-|splits, two via forkedextractNodecopies. Any dimension value containing|(URL paths can) truncated the dim and mangled the node — e.g. dim/api|v2/xon noden1.example.comproduced a chip labeled/apiand a series labeledv2/x|n1.connection.tsxlayered a second string protocol on top, splitting its composite chip value on' · 'to recover{path, method}for threshold scoping — same corruption class.Now
Seriescarries structureddimandnodefields, populated byrunPipelineon every path (groupBy per-node, groupBy cluster, groupBy-'node', Other bucket, field mode per-node and cluster). The composite key still exists — as a React key / recharts series name, built by the exportedmakeSeriesKey(dim, node)— but it is documented as write-only and no consumer parses it back. Hand-rolledindexOf('|')/sliceparsing is gone fromconnection.tsx,memory.tsx,main-thread-utilization.tsx,DimensionSelectorRenderer,SmallMultiples, andLineChartWithNodeLegend;connection.tsx's' · 'split is replaced by a composite-value →{path, method}map built during preprocessing.extractNode unification
The two forked parsers now share one vocabulary — the structured fields — with the divergence made explicit instead of accidental:
SmallMultipleslegends per-node series only: it readsSeries.nodeand cluster-aggregate series (nonode) stay out of the legend, exactly as before.LineChartWithNodeLegendkeeps its deliberate fallback via a documentedlegendGroup(s) = s.node ?? s.key: generic'line'dispatch also feeds it cluster series keyed by dimension value (database-size withperNode: false) and derived series keyed by node id, both of which want one legend chip per key. Previously this fallback was an undocumented divergence in a copy-pastedextractNode.groupBy-
'node'specs (utilization, tls-reused, storage-volume) now getnodeset by the pipeline itself, so the fallback is only exercised by the two key-per-legend-chip cases above.Shared per-node scaffolding
The ~15-line filter/relabel/color block (and the 5× copy-pasted
shortenNodeLabel) collapse into:hooks/useNodeFilteredSeries(data, nodes)— relabels per-node series with the short node name + stable node color, filters by the NodeLegend active-set, and returnsisActive/handleLegendClickfor the legend. Adopted byconnection,memory,main-thread-utilization, andDimensionSelectorRenderer.lib/nodeLabels.tsshortenNodeLabel— single definition, also adopted byPerPathRateRenderer(which had its own copy).The surrounding chip/legend JSX was left in place — each renderer's layout differs enough (quantile radios, combobox switch, threshold filtering) that a shared layout component would have needed as many props as it saved lines.
Tests
pipeline-per-node.test.ts: new assertions that structureddim/nodeare populated on the groupBy per-node, field-mode per-node, cluster, and groupBy-'node'paths, plus the regression the issue was filed for — a dim of/api|v2/xround-trips uncorrupted.dimension-selector-pipe-dim.test.tsx: renderer-layer regression — with records whose path contains|, the chip row shows the full value as one chip, and selecting it yields one cleanly-labeled line per node (asserted via the chart's aria-label, which the old first-|split rendered asv2/x|n1).tsc -b,oxlint,dprintclean.Open concerns
makeSeriesKeyoutput is still ambiguous as a string (dima|b+ nodeccollides with dima+ nodeb|c). It is only a React/recharts id now, so a collision would need two series in the same panel with that exact overlap — accepted as out of scope; making the key collision-proof (e.g. length-prefixing) would churn the keys pinned in tests for no user-visible gain.request-rate,error-rate, etc.) still emitSerieswithoutdim/node; consumers use the?? s.keyfallback for them. Migrating derived producers is follow-up material.connection.tsx's composite chip value (path · method) can theoretically collide if a path contains' · '— but that collision already merges the records in the pipeline's groupBy dimension itself, so it predates this PR and isn't made worse by the newdimPartsmap (Gemini review nit, acknowledged).Cross-model review
review --uncommitted): no actionable correctness issues.thresholdMatcheschecked!selectedDimsbefore!t.scope, so scope-less (global) thresholds would vanish when the selected dim had nodimPartsentry (e.g. a futureOtherbucket); fixed by testing!t.scopefirst. Its remaining notes verified the legend-semantics preservation in both primitives and confirmed no lingering|parsers anywhere in the analytics tree.Generated by kAIle (Claude Fable 5).