You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(viz): add the display processor — value mappings and noValue #69
#30 landed units.ts (number → string) and colors.ts (value → colour). Neither handles the step before them: a field config can declare that 1 means "Up", that null renders as "No data", or that anything above 90 is "Critical" — and that substitution has to happen before the formatter ever sees the number.
Grafana calls this the display processor, and it is a single function because the order is not negotiable.
The contract
displayProcessor(config: FieldConfig,ctx: DisplayContext): (value: unknown)=>DisplayValueinterfaceDisplayValue{text: string;// what to rendersuffix?: string;// kept separate, as units.ts doescolor: string;// resolved, never a bare tokennumeric: number;// for sparklines and bar widths — NaN when unmappable}
The order, which is the whole point
Value mappings first. A mapping that turns 1 into "Up" must win over the unit formatter, or the cell reads 1.0 B.
noValue when the value is null/undefined/NaNand no mapping claimed it. A mapping can legitimately map null to text, so this is not simply a null check at the top.
Units and decimals on whatever numeric value survives.
Colour last, from fieldColorFn — except that a mapping may carry its own colour, which overrides.
The model (src/lib/dashboard/model.ts) already validates MAPPING_TYPES: value, range, regex, special. Mappings are ordered and first match wins — not last, not most specific.
Traps
regex mappings are deferred to feat(panel): add the table panel #38 in the model and must stay deferred here. A user-supplied regex run against every cell of a 200k-row frame is a ReDoS vector against the browser tab. Whatever lands needs a linear-time matcher or a hard timeout, and that is its own design conversation.
special covers null, nan, null+nan, empty, true, false — and true/false must match the boolean, not the strings "true"/"false".
Range mappings are inclusive at both ends and from/to may each be null, meaning unbounded.
The processor is called once per cell. A 500×50 table is 25,000 calls per redraw. Build the mapping lookup once in the closure — a Map for exact values, an array only for ranges — not per call. colors.ts has the same shape: thresholdScale is built once, thresholdColorAt is the hot call.
A mapping's result.color is a stored token, so it goes through resolveColor, not into the DOM directly.
Scope
src/lib/dashboard/display.ts + tests. Pure and isomorphic, same rules as units.ts, colors.ts and frame.ts — no DOM, no imports beyond the sibling modules.
Blocks the stat (#37), table (#40) and gauge panels, which all render a mapped value rather than a raw one.
Closing: the dashboards feature has been dropped from Launchpad. The implemented work was removed in #87 and this work will not be picked up. Recoverable from git tag pre-dashboards-removal-backup if the feature is ever revived.
#30landedunits.ts(number → string) andcolors.ts(value → colour). Neither handles the step before them: a field config can declare that1means"Up", thatnullrenders as"No data", or that anything above 90 is"Critical"— and that substitution has to happen before the formatter ever sees the number.Grafana calls this the display processor, and it is a single function because the order is not negotiable.
The contract
The order, which is the whole point
1into"Up"must win over the unit formatter, or the cell reads1.0 B.noValuewhen the value is null/undefined/NaN and no mapping claimed it. A mapping can legitimately mapnullto text, so this is not simply a null check at the top.fieldColorFn— except that a mapping may carry its own colour, which overrides.The model (
src/lib/dashboard/model.ts) already validatesMAPPING_TYPES:value,range,regex,special. Mappings are ordered and first match wins — not last, not most specific.Traps
regexmappings are deferred to feat(panel): add the table panel #38 in the model and must stay deferred here. A user-supplied regex run against every cell of a 200k-row frame is a ReDoS vector against the browser tab. Whatever lands needs a linear-time matcher or a hard timeout, and that is its own design conversation.specialcoversnull,nan,null+nan,empty,true,false— andtrue/falsemust match the boolean, not the strings"true"/"false".from/tomay each be null, meaning unbounded.Mapfor exact values, an array only for ranges — not per call.colors.tshas the same shape:thresholdScaleis built once,thresholdColorAtis the hot call.result.coloris a stored token, so it goes throughresolveColor, not into the DOM directly.Scope
src/lib/dashboard/display.ts+ tests. Pure and isomorphic, same rules asunits.ts,colors.tsandframe.ts— no DOM, no imports beyond the sibling modules.Blocks the stat (#37), table (#40) and gauge panels, which all render a mapped value rather than a raw one.