Skip to content

[HDX-5080] Chart editor UI for metric formulas - #2909

Open
wrn14897 wants to merge 1 commit into
warren/HDX-5079-render-formulasfrom
warren/HDX-5080-render-metric-formula-UI
Open

[HDX-5080] Chart editor UI for metric formulas#2909
wrn14897 wants to merge 1 commit into
warren/HDX-5079-render-formulasfrom
warren/HDX-5080-render-metric-formula-UI

Conversation

@wrn14897

Copy link
Copy Markdown
Member

Summary

Exposes metric formulas (HDX-5078's formulas config, rendered by HDX-5079) in the chart editor for metric sources, so a derived series like A / (A + B + C) * 100 can be built, validated, saved, and reloaded from the UI.

Stacked on #2908 — only the last commit is new here.

Editor

  • Formula rows (ChartFormulaEditor) on metric-source builder charts (time series / table / number): "Add Formula" appends a row with a monospace letter-ref expression input, an alias, a per-formula number format (reuses the per-series format drawer), and "Remove Formula".
  • Inline validation with the structured validator from HDX-5078 (validateFormula): malformed expressions, unknown series refs, constant-only expressions, etc. surface live under the input; validateChartForm blocks save/run with the same messages so an invalid expression can never reach ClickHouse.
  • Letter badges (A, B, C, ...) on metric series rows so formula refs are discoverable.
  • "Show input series" toggle drives showOperandSeries (formula + raw operand series vs formula column(s) only). Adding a formula on a Number tile defaults operands to hidden, since Number tiles render the first value column.
  • Mutual exclusion with ratio: the "As Ratio" switch is hidden while a formula exists, and "Add Formula" is hidden while ratio mode is on (formulas supersede ratio in the renderer).
  • The Number-tile series cap (1, or 2 for ratio) is lifted when formulas exist, so operand-only series like A / (A + B + C) can be built.
  • normalizeChartConfig strips formulas/showOperandSeries on save for non-metric sources and for display types the composed metric query does not render (pie/bar/heatmap/search/patterns), mirroring the existing metricName/having stripping. The form state keeps them, so switching back restores the rows.

Rendering consumers (positional value-column contract)

The composed metric query projects operand columns (unless hidden) then formula columns, ahead of group-by passthrough columns. Updated the consumers that map columns positionally:

  • useChartNumberFormats: operand columns → select[i].numberFormat, formula columns → formulas[j].numberFormat, both falling back to the chart-wide format; chart-wide axis format prefers formula formats when operands are hidden.
  • New getBuilderValueColumnCount helper (formula/ratio-aware) used by DBTableChart for group-by column inference; per-column color mapping skips hidden-operand formula configs.
  • DBTimeChart drill-down skips the value-range filter when operands are hidden (formula columns don't map onto select expressions).
  • Legend/tooltip naming needs no changes — formula columns arrive as named result columns (alias || expression).

Persistence needs no API changes: tiles validate against SavedChartConfigSchema, which already carries formulas/showOperandSeries, and builderToRawSql already rejects formula configs with a clear message on the Builder → SQL switch.

Testing

  • make ci-lint, make ci-unit pass.
  • New unit tests:
    • DBEditTimeChartForm.test.tsx: Add/Remove Formula, inline validation (malformed / unknown ref / clears when fixed), save round-trip, save blocked on invalid expression, ratio mutual exclusion, showOperandSeries toggle, non-metric sources show no formula controls.
    • ChartEditor/utils.test.ts: validateChartForm formula rules (including the Number-tile cap lift) and normalization stripping/round-trip.
    • source.test.ts: useChartNumberFormats formula-column mapping (operands shown/hidden, ratio precedence, chart-format fallbacks) and getBuilderValueColumnCount.
  • New dashboard E2E (make dev-e2e FILE=dashboard GREP="Metric formulas", passing): creates a metric table tile with two gauge series + A / (A + B) * 100, asserts the inline error for an invalid ref, hides operands, saves, reloads the page, verifies the formula column renders with a finite value, and reopens the editor to verify the round-trip.

How to test on Vercel preview

  1. Open a dashboard → Add tile → select a metrics source.
  2. Add two series (note the A/B badges), click Add Formula, enter A / (A + B) * 100.
  3. Try A / C to see the inline error; toggle Show input series; save, reload, and confirm the tile renders the formula series.

References

Expose metric formulas (HDX-5078 schema, HDX-5079 rendering) in the chart
editor for metric sources:

- Formula rows in the builder (Add Formula / Remove Formula) with a
  letter-ref expression input, alias, and per-formula number format
- Inline validation via the structured validator (malformed expressions,
  unknown series refs) plus save-time validation in validateChartForm
- Letter badges (A, B, C) on metric series rows so refs are discoverable
- Show input series toggle (showOperandSeries) for formula-only output;
  Number tiles default to hidden operands since they render the first
  value column
- Formulas and the As Ratio toggle are mutually exclusive in the editor
- normalizeChartConfig strips formulas for non-metric sources and
  display types the composed metric query does not render
- useChartNumberFormats / DBTableChart / DBTimeChart updated for the
  formula-aware value-column projection (operands first unless hidden,
  then formula columns)
@changeset-bot

changeset-bot Bot commented Aug 14, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2067484

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

This PR includes changesets to release 3 packages
Name Type
@hyperdx/app 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 Aug 14, 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 Aug 14, 2026 5:31am
hyperdx-storybook Ready Ready Preview Aug 14, 2026 5:31am

Request Review

@greptile-apps

greptile-apps Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR exposes metric formulas in the metric chart editor and updates positional chart consumers for formula result columns.

  • Adds formula expression, alias, number-format, validation, and operand-visibility controls.
  • Adds formula-aware table column inference, number formatting, and time-chart drill-down handling.
  • Adds unit and dashboard E2E coverage for formula editing, persistence, and rendering.

Confidence Score: 4/5

The positional formula rebinding defect should be fixed before merging because ordinary series editing can silently change the meaning of saved formulas.

Formula letters resolve by current series position, but existing reorder and insertion controls mutate those positions without rewriting or invalidating formula expressions, allowing charts to save and display derived values from the wrong operands.

Files Needing Attention: packages/app/src/components/DBEditTimeChartForm/ChartSeriesEditor.tsx, packages/app/src/components/DBEditTimeChartForm/ChartEditorControls.tsx

Important Files Changed

Filename Overview
packages/app/src/components/DBEditTimeChartForm/ChartEditorControls.tsx Adds formula field-array controls, visibility toggling, and ratio exclusion; series mutations are not coordinated with positional formulas.
packages/app/src/components/DBEditTimeChartForm/ChartSeriesEditor.tsx Adds positional reference badges while leaving reorder, duplicate, and removal actions capable of silently rebinding formulas.
packages/app/src/components/ChartEditor/utils.ts Adds formula normalization and save-time validation, but count-based validation cannot detect semantically rebound in-range references.
packages/app/src/source.ts Adds formula-aware result-column counting and number-format mapping.
packages/app/src/components/DBTableChart.tsx Uses formula-aware value-column counts for group-by inference and avoids color mapping onto hidden operands.
packages/app/src/components/DBTimeChart.tsx Avoids attributing formula-only values to operand expressions during drill-down.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  S[Metric series A, B, ...] --> E[Chart formula editor]
  E --> V[Formula validation]
  V --> C[Saved chart config]
  C --> Q[Composed metric query]
  Q --> O[Operand columns]
  Q --> F[Formula columns]
  O --> R[Table / Time / Number renderer]
  F --> R
Loading

Comments Outside Diff (1)

  1. packages/app/src/components/DBEditTimeChartForm/ChartSeriesEditor.tsx, line 288-327 (link)

    P1 Series edits rebind formula operands

    When a chart already has a positional formula, moving or duplicating a series changes which operand each letter identifies without updating the expression, causing the saved chart to compute a different value while still passing validation. Removing a referenced series instead leaves the formula invalid and blocks saving until it is repaired manually.

    Knowledge Base Used: App Components and Charts

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

Fix all with Greploop

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

Reviews (1): Last reviewed commit: "feat: chart editor UI for metric formula..." | Re-trigger Greptile

@github-actions

Copy link
Copy Markdown
Contributor

Deep Review

✅ No critical issues found.

The formula-editor wiring holds up on the highest-risk paths. Verified directly against the diff and the composed-query renderer: the showOperandSeries tri-state is consistent (app consumers treat === false as hidden; the renderer projects operands on !== false), validateChartForm gates every editor save/run path that can reach the composed metric query, handleRemoveFormula's formulaFields.length <= 1 correctly identifies removal of the last formula, and getBuilderValueColumnCount matches the renderer's operand→formula→group-by projection order. The substantive risk is duplication that can silently drift.

🟡 P2 — recommended

  • packages/app/src/components/DBEditTimeChartForm/ChartEditorControls.tsx:118displayTypeSupportsFormulas re-lists Line || StackedBar || Table || Number inline, duplicating the exported isFormulaDisplayType that normalizeChartConfig and validateChartForm use to gate stripping; if the two hand-synced lists drift, the editor offers "Add Formula" for a display type whose formulas the normalizer then discards on save.
    • Fix: Import and reuse isFormulaDisplayType from @/components/ChartEditor/utils instead of the inline predicate.

🟡 P2 — recommended

  • packages/app/src/source.ts:606 — the "metric formula config with hidden operand series" concept that drives positional column mapping is encoded three times (getFormulaConfig, DBTableChart color guard, DBTimeChart operandsHidden) with divergent guards — only getFormulaConfig checks metricTables != null — so the shared positional-mapping invariant can diverge if formula detection changes in one consumer but not the others.
    • Fix: Export one predicate (e.g. areOperandSeriesHidden(config)) and consume it in all three call sites.
🔵 P3 nitpicks (1)
  • packages/common-utils/src/core/renderChartConfig.tsgetBuilderValueColumnCount and useChartNumberFormats positionally re-encode the renderer's column projection order across the package boundary with no compile-time link; a change to the renderer's column ordering silently breaks number-format and color mapping.
    • Fix: Add a comment cross-reference or a shared column-order constant so the coupling is discoverable, and cover the operands-shown/hidden orderings with a test that exercises the renderer output against the consumers.

Reviewers (3): maintainability, learnings-researcher, orchestrator verification.

Testing gaps:

  • No test asserts the two display-type lists (isFormulaDisplayType vs displayTypeSupportsFormulas) stay in sync.
  • The divergent operands-hidden predicate is not covered by a shared test exercising DBTableChart, DBTimeChart, and source.ts against the same config.

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