Skip to content

feat(dashboards): background trend sparkline + reference line on number tiles#2489

Draft
alex-fedotyev wants to merge 2 commits into
mainfrom
alex/HDX-1360-number-tile-background-chart
Draft

feat(dashboards): background trend sparkline + reference line on number tiles#2489
alex-fedotyev wants to merge 2 commits into
mainfrom
alex/HDX-1360-number-tile-background-chart

Conversation

@alex-fedotyev

Copy link
Copy Markdown
Contributor

Number tiles can now show a faint trend sparkline behind the value, with an optional reference line to mark a threshold such as a zero error budget, an SLA, or a target. This is the first in a short series building toward SLO / error-budget tiles; it covers builder number tiles. Raw SQL support and click-through drill-down follow in separate PRs.

Builds on #1360.

Summary

  • Add an optional backgroundChart to the number-tile config: a line or area sparkline drawn behind the value, derived from a time-bucketed version of the tile's own query.
  • Add an optional referenceLine (a value plus optional label and palette color) rendered across the sparkline, for example a zero-budget line or an SLA.
  • The sparkline color inherits the tile color by default and can be overridden to any palette token; the reference line defaults to a warning hue.

Changes

  • common-utils: BackgroundChartSchema ({ type, color?, referenceLine? }) on the shared chart settings, mirroring the existing color / colorRules placement so it flows through the saved-config types unchanged.
  • app: NumberTileBackgroundChart renders the sparkline (recharts area / line) and the reference line (recharts ReferenceLine with a hidden YAxis and ifOverflow="extendDomain", so the line stays visible when its value sits outside the data range) behind DBNumberChart. New BackgroundChartInput control in the display-settings drawer, wired through EditTimeChartForm.

Why

Number tiles are the natural KPI surface, and a trend behind the value plus a threshold line is the common "stat with sparkline" pattern used to build SLO dashboards. Builder tiles can auto-derive the sparkline because the query is structured; raw SQL needs an explicit query, which is a follow-up.

Test plan

  • lint + typecheck (eslint, tsc --noEmit) on the touched packages
  • unit tests: common-utils schema round-trip (line / area / color / reference line, positive per input + negative per rule) and app render-wiring + the sparkline points helper
  • manual: builder number tile, enable Background chart (area / line), add a reference line; confirm the sparkline and line render in light and dark

What's not in this PR (follow-ups)

  • Raw SQL background-query mode (author the sparkline query directly).
  • Click-through drill-down on number tiles.
  • External API (v2 REST) + MCP parity for backgroundChart.
  • Customer docs for the SLO / error-budget recipe.

Screenshots

Draft: visual verification is in progress. The sparkline rendering is confirmed on a local stack; full before/after captures (light + dark, builder tile with a reference line) will be attached before I mark this ready for review.

[ui-check: light-only]
[ui-states: allow]
[viewport: allow]
[no-story: allow]

Notes on the markers above: the sparkline color resolves through theme tokens (getColorFromCSSToken) so it reflows in dark; the sparkline is decorative behind the value and only mounts in the success branch, so the tile's empty / loading / error states are unchanged; it uses ResponsiveContainer, so it is size-independent; and the two new components render only with live time-series data, so they are demonstrated in product rather than in Storybook.

alex-fedotyev and others added 2 commits June 18, 2026 01:00
Number tiles can render a faint line or area sparkline behind the value,
derived from a time-bucketed version of the same query, so the trend over
the selected range is visible at a glance. Useful for SLO / error-budget
tiles where the burn over time matters as much as the current number.

- common-utils: BackgroundChartSchema (line / area + optional palette color
  override) on SharedChartSettingsSchema, mirroring color / colorRules.
- app: NumberTileBackgroundChart draws the sparkline behind DBNumberChart,
  reusing convertToTimeChartConfig + formatResponseForTimeChart for the
  bucketed series. Builder number tiles only; raw SQL has no time dimension
  to bucket.
- Display Settings: a Background chart control (type + optional color),
  gated on builder number tiles.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds an optional horizontal reference line to the background sparkline
(value plus optional label and palette color), so a number tile can mark a
0 error budget, an SLA, or a target. Rendered via a recharts ReferenceLine
with a hidden YAxis and ifOverflow="extendDomain" so it stays visible when
the value sits outside the data range.

- common-utils: backgroundChart.referenceLine { value, label?, color? }.
- app: NumberTileBackgroundChart draws the line on the area / line sparkline;
  BackgroundChartInput gains value / label / color controls.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jun 18, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fcb80bd

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@hyperdx/app Minor
@hyperdx/common-utils Minor
@hyperdx/api Minor
@hyperdx/otel-collector Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jun 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hyperdx-oss Ready Ready Preview, Comment Jun 18, 2026 9:03pm
hyperdx-storybook Ready Ready Preview, Comment Jun 18, 2026 9:03pm

Request Review

@greptile-apps

greptile-apps Bot commented Jun 18, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds an optional background trend sparkline (line or area) and reference line to number tiles, derived from a time-bucketed version of the tile's own query. The feature is scoped to builder number tiles and is gated in the UI on configType !== 'sql'.

  • BackgroundChartSchema is added to common-utils with Zod-validated type, optional palette-token color, and an optional referenceLine (value: z.number().finite(), optional label/color); it is stored in SharedChartSettingsSchema alongside color and colorRules.
  • NumberTileBackgroundChart issues a second TanStack Query (keyed on a DisplayType.Line version of the tile config) and renders a recharts AreaChart or LineChart absolutely positioned behind the value; a hidden YAxis + ifOverflow=\"extendDomain\" keep the optional reference line visible even when its value is outside the data range.
  • BackgroundChartInput wires the new config into the Display Settings drawer via react-hook-form; the reference-line sub-controls appear progressively once a numeric value is entered.

Confidence Score: 4/5

Safe to merge; all changes are additive and behind an explicit opt-in config field.

The implementation is well-structured and the error boundary ensures sparkline failures can never affect the tile's primary value display. The main issue is that backgroundChart propagates into the sparkline's TanStack Query key via convertToTimeChartConfig's ...config spread, so any display-only setting change (sparkline type, color, reference-line label or color) busts the data cache and triggers an unnecessary refetch. The sparklinePointsFromGraphResults finite check is also asymmetric — y is guarded with Number.isFinite but x is not. Both are non-blocking quality issues on a new opt-in feature.

packages/app/src/components/NumberTileBackgroundChart.tsx — the query key construction and the finite-check asymmetry are both in this file.

Important Files Changed

Filename Overview
packages/app/src/components/NumberTileBackgroundChart.tsx New sparkline renderer; well-structured but backgroundChart leaks into the query key via convertToTimeChartConfig's spread, causing unnecessary refetches on display-only settings changes. Finite-check on timestamp x is also weaker than on y.
packages/common-utils/src/types.ts Adds BackgroundChartSchema and BackgroundChart type with proper Zod validation (z.number().finite() for reference line value, palette-token enum for color). Schema additions are clean and well-documented.
packages/app/src/components/BackgroundChartInput.tsx New form control for editing background sparkline settings; correctly handles the None→Line/Area transition, reference-line clearing via destructuring, and Mantine's NumberInput returning `number
packages/app/src/components/DBNumberChart.tsx Wraps existing value display in a relative container and conditionally mounts the sparkline behind it; layout change is minimal and correct.
packages/app/src/components/ChartDisplaySettingsDrawer.tsx Correctly gates the BackgroundChartInput on builder number tiles and wires it through react-hook-form; applyDefaultSettings pass-through is straightforward.
packages/app/src/components/tests/NumberTileBackgroundChart.test.tsx Unit tests cover the exported sparklinePointsFromGraphResults helper for the happy path and all three skip conditions (missing key, NaN, undefined).

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant DrawerForm as ChartDisplaySettingsDrawer
    participant BGInput as BackgroundChartInput
    participant DBNumber as DBNumberChart
    participant BGChart as NumberTileBackgroundChart
    participant Query as useQueriedChartConfig

    User->>DrawerForm: Opens Display Settings
    DrawerForm->>BGInput: Renders BackgroundChartInput
    User->>BGInput: Selects "Area" type
    BGInput->>DrawerForm: "onChange({ type: 'area' })"
    DrawerForm->>DBNumber: "config.backgroundChart = { type: 'area' }"

    DBNumber->>BGChart: Mount with config + backgroundChart
    BGChart->>BGChart: isBuilderChartConfig check
    BGChart->>BGChart: "Build timeConfig (displayType=Line)"
    BGChart->>Query: useQueriedChartConfig(queriedConfig)
    Query-->>BGChart: time-series data
    BGChart->>BGChart: formatResponseForTimeChart → sparklinePoints
    BGChart-->>DBNumber: "Renders recharts AreaChart (absolute, z=0)"
    DBNumber-->>User: "Number value (z=1) over sparkline"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant DrawerForm as ChartDisplaySettingsDrawer
    participant BGInput as BackgroundChartInput
    participant DBNumber as DBNumberChart
    participant BGChart as NumberTileBackgroundChart
    participant Query as useQueriedChartConfig

    User->>DrawerForm: Opens Display Settings
    DrawerForm->>BGInput: Renders BackgroundChartInput
    User->>BGInput: Selects "Area" type
    BGInput->>DrawerForm: "onChange({ type: 'area' })"
    DrawerForm->>DBNumber: "config.backgroundChart = { type: 'area' }"

    DBNumber->>BGChart: Mount with config + backgroundChart
    BGChart->>BGChart: isBuilderChartConfig check
    BGChart->>BGChart: "Build timeConfig (displayType=Line)"
    BGChart->>Query: useQueriedChartConfig(queriedConfig)
    Query-->>BGChart: time-series data
    BGChart->>BGChart: formatResponseForTimeChart → sparklinePoints
    BGChart-->>DBNumber: "Renders recharts AreaChart (absolute, z=0)"
    DBNumber-->>User: "Number value (z=1) over sparkline"
Loading

Fix All in Claude Code Fix All in Conductor Fix All in Cursor Fix All in Codex

Reviews (1): Last reviewed commit: "feat(dashboards): add a reference line t..." | Re-trigger Greptile

Comment on lines +91 to +98
const queriedConfig = useMemo(
() => convertToTimeChartConfig(timeConfig),
[timeConfig],
);

const { data } = useQueriedChartConfig(queriedConfig, {
placeholderData: prev => prev,
queryKey: ['number-tile-background', queriedConfig],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 backgroundChart bleeds into the sparkline query key, causing spurious refetches

timeConfig is built by spreading ...config, so it carries backgroundChart. convertToTimeChartConfig then spreads ...config again (line 142 of ChartUtils.tsx), so queriedConfig — and therefore the TanStack Query key — retains backgroundChart. Any purely visual change (switching type from line to area, editing the reference-line label or color) will produce a new queriedConfig object, bust the cache, and fire a fresh network request even though the underlying time-series data is identical.

The fix is to strip backgroundChart (and other display-only fields) before computing queriedConfig. The simplest approach is to destructure it out when constructing timeConfig, since the rendering code already receives backgroundChart as a separate prop and never reads it from config inside this component.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Conductor Fix in Cursor Fix in Codex

for (const row of graphResults) {
const x = row[timestampKey];
const y = row[valueKey];
if (typeof x === 'number' && typeof y === 'number' && Number.isFinite(y)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The finite check on x is weaker than the one on y. typeof x === 'number' passes for NaN and ±Infinity, both of which are valid typeof 'number' values. Applying Number.isFinite(x) keeps the guard symmetrical and prevents a degenerate timestamp (e.g. a malformed bucket edge) from being pushed into the points array and handed to recharts.

Suggested change
if (typeof x === 'number' && typeof y === 'number' && Number.isFinite(y)) {
if (typeof x === 'number' && Number.isFinite(x) && typeof y === 'number' && Number.isFinite(y)) {

Fix in Claude Code Fix in Conductor Fix in Cursor Fix in Codex

@github-actions

Copy link
Copy Markdown
Contributor

E2E Test Results

All tests passed • 202 passed • 3 skipped • 1358s

Status Count
✅ Passed 202
❌ Failed 0
⚠️ Flaky 2
⏭️ Skipped 3

Tests ran across 4 shards in parallel.

View full report →

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant