Skip to content

feat: Wire heatmap chart into dashboard editor and tile rendering#2107

Open
alex-fedotyev wants to merge 24 commits intomainfrom
cursor/heatmap-chart-editor-dashboard-5801
Open

feat: Wire heatmap chart into dashboard editor and tile rendering#2107
alex-fedotyev wants to merge 24 commits intomainfrom
cursor/heatmap-chart-editor-dashboard-5801

Conversation

@alex-fedotyev
Copy link
Copy Markdown
Contributor

@alex-fedotyev alex-fedotyev commented Apr 11, 2026

What

Wires the existing DBHeatmapChart component and DisplayType.Heatmap enum into the chart editor, dashboard tile rendering, and unifies display settings across all heatmap contexts (search Event Deltas, chart editor, dashboards).

Why

Users should be able to create and view heatmap charts from the chart editor and dashboards, alongside existing chart types. Heatmaps visualize distributions over time (e.g., latency distributions) and were previously only available in the search Event Deltas view.

Changes

Shared Display Settings Drawer (HeatmapSettingsDrawer.tsx):

  • Extracted from the inline drawer in DBSearchHeatmapChart.tsx into a reusable component
  • Contains Scale (Log/Linear), Value expression, Count expression
  • Used by search Event Deltas (gear icon), chart editor, and dashboards
  • defaultValues memoized at call sites to prevent unnecessary form resets

Chart Editor (EditTimeChartForm, ChartEditorControls, HeatmapSeriesEditor):

  • Added Heatmap tab with IconGrid3x3 icon
  • Heatmap editor shows Where clause + Display Settings button (opens shared drawer)
  • Single series constraint enforced; validation requires value expression
  • Auto-populates getDurationMsExpression(source) and count() when a Trace source is selected (on tab switch or source change)
  • Sets numberFormat: { output: 'duration', factor: 0.001 } for proper Y-axis labels
  • Generated SQL section hidden for heatmap (queries are built internally by HeatmapContainer)

Dashboard Tiles (DBDashboardPage):

  • HeatmapTile component renders DBHeatmapChart via toHeatmapChartConfig() helper
  • autoRun passed to EditTimeChartForm in dashboard edit modal so saved charts preview immediately
  • numberFormat and scaleType passed through from saved config

Chart Preview (ChartPreviewPanel):

  • HeatmapPreview component renders heatmap in the chart editor preview area

Heatmap UX improvements (DBHeatmapChart.tsx):

  • Dynamic Y-axis sizing via ctx.measureText() — measures actual formatted tick labels
  • Compact tick formatter (formatDurationMsCompact): m instead of min, values under 2min stay as seconds, fewer decimals
  • X-axis: gap: 10 + space: 60 prevents label overlap in narrow containers
  • padding: [8, 8, 0, 4] prevents label clipping at edges
  • Drag-to-select and "Click & Drag" prompt disabled when onFilter is not provided (editor/dashboard); hover tooltip still shows for inspection
  • Full interactivity preserved in search Event Deltas where onFilter is wired

Config helper (toHeatmapChartConfig):

  • Shared function converts builder config to HeatmapChartConfig format
  • Eliminates duplicated config construction and unsafe casts
  • Exported alongside HeatmapChartConfig type from DBHeatmapChart.tsx
Open in Web Open in Cursor 

- Add Heatmap tab to chart editor form with IconGrid3x3 icon
- Create HeatmapSeriesEditor with value expression (Y-axis) and
  count expression (intensity) inputs
- Add heatmap display type to displayTypeToActiveTab mapping
- Add heatmap preview rendering in ChartPreviewPanel
- Add heatmap tile rendering in DBDashboardPage
- Add heatmap validation: single series, required value expression
- Constrain heatmap to single series (no Add Series / ratio)

Co-authored-by: Alex Fedotyev <alex-fedotyev@users.noreply.github.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 11, 2026

⚠️ No Changeset found

Latest commit: a587ca2

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 11, 2026

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

Project Deployment Actions Updated (UTC)
hyperdx-oss Ready Ready Preview, Comment Apr 15, 2026 2:03am

Request Review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 11, 2026

E2E Test Results

All tests passed • 131 passed • 3 skipped • 1057s

Status Count
✅ Passed 131
❌ Failed 0
⚠️ Flaky 4
⏭️ Skipped 3

Tests ran across 4 shards in parallel.

View full report →

…ormat, reduce chart whitespace

- When switching to Heatmap with a trace source, auto-fill value
  expression with getDurationMsExpression and set numberFormat to
  duration with factor 0.001 for proper Y-axis labels (4ms, 100ms, 1.2s)
- Pass numberFormat from config through to DBHeatmapChart in both
  ChartPreviewPanel and DBDashboardPage for correct tick formatting
- Reduce Stack gap in ChartContainer from default to 4px to minimize
  whitespace above the chart area

Co-authored-by: Alex Fedotyev <alex-fedotyev@users.noreply.github.com>
Co-authored-by: Alex Fedotyev <alex-fedotyev@users.noreply.github.com>
…ctivity in editor/dashboard

- Add effect to populate duration defaults when source changes while
  already in heatmap mode (not just when switching to heatmap tab)
- Disable drag-to-select and hide 'Click & Drag' prompt in Heatmap
  when onFilter is not provided (chart editor and dashboard contexts)
- Keep hover tooltip with Y/Count values for read-only inspection

Co-authored-by: Alex Fedotyev <alex-fedotyev@users.noreply.github.com>
…ngs button for heatmap

- Fill Value field with getDurationMsExpression and Count field with
  count() when switching to Heatmap with a trace source (visible text,
  not just behind the scenes)
- Add Display Settings button below heatmap editor, same pattern as
  other chart types, opening ChartDisplaySettingsDrawer for number
  format configuration

Co-authored-by: Alex Fedotyev <alex-fedotyev@users.noreply.github.com>
…hboards

- Extract HeatmapSettingsDrawer into shared component used by both
  search Event Deltas and chart editor/dashboard contexts
- Remove Value/Count fields from the main heatmap editor form — move
  them into the Heatmap Settings drawer (Scale, Value, Count)
- Replace 'Display Settings' button with 'Heatmap Settings' button
  that opens the same drawer as search Event Deltas gear icon
- Pass scaleType (log/linear) from form state through to DBHeatmapChart
  in preview panel and dashboard tiles
- Pre-populate Value with getDurationMsExpression and Count with count()
  from data source config, editable via Heatmap Settings

Co-authored-by: Alex Fedotyev <alex-fedotyev@users.noreply.github.com>
…artContainer gap

- Add padding: [8,0,0,0] to uPlot options to reduce whitespace above
  the topmost Y-axis tick in heatmap charts
- Set lockScroll=false on HeatmapSettingsDrawer to prevent layout shift
  when opening settings inside a dashboard edit modal
- Restore ChartContainer gap to 'xs' (was incorrectly set to 4px
  affecting all chart types)

Co-authored-by: Alex Fedotyev <alex-fedotyev@users.noreply.github.com>
…ChartContainer changes

- Wrap HeatmapSettingsDrawer in Portal so it doesn't participate in
  the flex layout of the editor form, preventing content shift when
  opening/closing settings in dashboard edit modal
- Revert ChartContainer gap/margin changes to preserve existing
  spacing for all chart types

Co-authored-by: Alex Fedotyev <alex-fedotyev@users.noreply.github.com>
cursoragent and others added 2 commits April 13, 2026 16:46
Pass autoRun to EditTimeChartForm in the dashboard edit modal so
saved charts (including heatmaps) render their preview immediately
when opened for editing, matching the Chart Explorer behavior.

Co-authored-by: Alex Fedotyev <alex-fedotyev@users.noreply.github.com>
Co-authored-by: Alex Fedotyev <alex-fedotyev@users.noreply.github.com>
… layout shift

Move the HeatmapSettingsDrawer out of HeatmapSeriesEditor and render
it at the EditTimeChartForm top level (same pattern as
ChartDisplaySettingsDrawer). This prevents any DOM insertion from
affecting the flex layout between the settings button and run button
when the drawer opens/closes.

Co-authored-by: Alex Fedotyev <alex-fedotyev@users.noreply.github.com>
@alex-fedotyev alex-fedotyev marked this pull request as ready for review April 13, 2026 17:30
@github-actions github-actions bot added the review/tier-3 Standard — full human review required label Apr 13, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 13, 2026

🟡 Tier 3 — Standard

Introduces new logic, modifies core functionality, or touches areas with non-trivial risk.

Why this tier:

  • Standard feature/fix — introduces new logic or modifies core functionality

Review process: Full human review — logic, architecture, edge cases.
SLA: First-pass feedback within 1 business day.

Stats
  • Files changed: 10
  • Lines changed: 670
  • Branch: cursor/heatmap-chart-editor-dashboard-5801
  • Author: alex-fedotyev

To override this classification, remove the review/tier-3 label and apply a different review/tier-* label. Manual overrides are preserved on subsequent pushes.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 13, 2026

PR Review

  • ⚠️ Multiple as any casts for out-of-schema form fields (series.0.countExpression, series.0.heatmapScaleType) → These extra fields bypass TypeScript and Zod validation. Violates the project's "use TypeScript strictly; Zod schemas for validation" principle (EditTimeChartForm.tsx:318,356,392). They should be added to ChartEditorFormState / the select type, or handled outside the form.

  • ⚠️ handleHeatmapScaleTypeChange only calls setValue, not onSubmit → The chart preview won't reflect a scale-type change until the user clicks "Apply". Confusing UX since the SegmentedControl toggle feels immediate. Either call onSubmit(true) after setValue, or remove the live callback and apply only on form submit (EditTimeChartForm.tsx:378–383).

  • ⚠️ Duplicate heatmap series initialization in two useEffects (EditTimeChartForm.tsx:302–323 and 335–359) → Same heatmapSeries construction repeated verbatim. Extract to a local helper to avoid divergence.

  • ⚠️ useEffect dep suppression in HeatmapSettingsDrawer (HeatmapSettingsDrawer.tsx:760–763) → // eslint-disable-next-line react-hooks/exhaustive-deps -- form object is stable suppresses a lint rule. While useForm's return is stable, suppressing deps rules silently breaks when the form instance changes. Prefer form.reset as a stable ref or include it in deps.

  • ℹ️ buildChartConfigForExplanations returns raw builder config for heatmap (DBEditTimeChartForm/utils.ts:522–524) → Since SQL preview is hidden for heatmap (ChartEditorControls.tsx:224), this is safe now, but the fallthrough to returning the raw config is fragile if that guard is ever removed.

✅ Core feature logic (extraction of HeatmapSettingsDrawer, toHeatmapChartConfig helper, onFilter-gated drag-select, dynamic Y-axis sizing) looks correct and well-structured.

…le form, revert ChartContainer

- Extract toHeatmapChartConfig() helper into DBHeatmapChart.tsx,
  eliminating duplicated config construction and unsafe casts in
  DBDashboardPage and ChartPreviewPanel
- Export HeatmapChartConfig and HeatmapSelectExtras types for typed
  access to countExpression and heatmapScaleType on select items
- Add useEffect to sync HeatmapSettingsDrawer form when defaultValues
  change (prevents stale closure when source changes while drawer open)
- Fully revert ChartContainer to original (no gap change)

Co-authored-by: Alex Fedotyev <alex-fedotyev@users.noreply.github.com>
Co-authored-by: Alex Fedotyev <alex-fedotyev@users.noreply.github.com>
- Remove export from HeatmapSelectExtras (fixes knip unused export)
- Memoize heatmapSettingsDefaults in EditTimeChartForm and
  DBSearchHeatmapChart to prevent form.reset on every parent render

Co-authored-by: Alex Fedotyev <alex-fedotyev@users.noreply.github.com>
- Increase Y-axis size from 60px to 70px so labels like '1000ms'
  and '.67min' are not truncated
- Add 8px right padding so X-axis labels at the right edge are
  not clipped by the container overflow

Co-authored-by: Alex Fedotyev <alex-fedotyev@users.noreply.github.com>
- Replace fixed Y-axis size with dynamic measurement that uses
  canvas measureText on the actual formatted tick labels + padding
- Add formatDurationMsCompact for axis ticks: shorter units ('m'
  instead of 'min'), values under 2min stay as seconds (no '.67min'),
  fewer decimal places throughout
- Full formatDurationMs still used in tooltips via the same formatter

Co-authored-by: Alex Fedotyev <alex-fedotyev@users.noreply.github.com>
Co-authored-by: Alex Fedotyev <alex-fedotyev@users.noreply.github.com>
- Add gap=8 to X-axis to prevent tick labels from overlapping
- Replace IIFE patterns with HeatmapTile and HeatmapPreview
  components in DBDashboardPage and ChartPreviewPanel

Co-authored-by: Alex Fedotyev <alex-fedotyev@users.noreply.github.com>
…all containers

Set space=60 on the X-axis so uPlot allocates at least 60px per tick
interval. This prevents labels from being packed together in narrow
contexts like the search heatmap (260px height). The fix is in the
shared opt constant used by all heatmap renderings.

Co-authored-by: Alex Fedotyev <alex-fedotyev@users.noreply.github.com>
Co-authored-by: Alex Fedotyev <alex-fedotyev@users.noreply.github.com>
…lipping

Co-authored-by: Alex Fedotyev <alex-fedotyev@users.noreply.github.com>
Co-authored-by: Alex Fedotyev <alex-fedotyev@users.noreply.github.com>
- Remove 'heatmap' from TABS_WITH_GENERATED_SQL since the heatmap
  builds its own queries internally (min/max + bucket), so the
  Generated SQL section would show misleading SQL
- Remove stable form object from useEffect dependency array in
  HeatmapSettingsDrawer

Co-authored-by: Alex Fedotyev <alex-fedotyev@users.noreply.github.com>
@alex-fedotyev alex-fedotyev requested review from a team and pulpdrew and removed request for a team April 14, 2026 03:39
Copy link
Copy Markdown
Contributor

@pulpdrew pulpdrew left a comment

Choose a reason for hiding this comment

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

Cool stuff, but I have some suggestions.

Also, do we have a followup ticket to update the external API implementation+docs+tests to cover this new visualization type?

Comment on lines +420 to +424
const traceSource =
tableSource?.kind === SourceKind.Trace &&
tableSource.durationExpression
? tableSource
: undefined;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we need some sort of messaging or improved error messages here, or some way to make this work by default for non-trace sources.

I was very confused why this just errors out by default for non-Trace sources:

Image

Comment on lines +784 to +793
<HeatmapSettingsDrawer
opened={heatmapSettingsOpened}
onClose={closeHeatmapSettings}
connection={tableConnection}
parentRef={parentRef}
defaultValues={heatmapSettingsDefaults}
scaleType={heatmapScaleType}
onScaleTypeChange={handleHeatmapScaleTypeChange}
onSubmit={handleUpdateHeatmapSettings}
/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I understand that we're probably doing it this way because the Event Deltas heatmap uses a drawer for the settings. And that makes sense because it is restricted to Trace sources and a particular use case, which have good defaults.

In the context of the chart editor, the point is to be able to edit the chart, and I think it makes less sense to hide the heatmap settings in a drawer. Particularly because for metric and log sources, the defaults error out and it's not at all clear where I would set a 'value expression' if I'm not already aware that the settings are hidden in display settings.

I'd also suggest that the value and count expressions are not display settings but are just as central to this chart as the series are a part of the line chart or table chart - they should be front and center IMO

Image

}

// Validate number and pie charts only have one series
// Validate number, pie, and heatmap charts only have one series
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please update the unit tests to cover the changes in this file

chartConfigForExplanations?: ChartConfigWithOptTimestamp;
onSubmit: (suppressErrorNotification?: boolean) => void;
openDisplaySettings: () => void;
openHeatmapSettings?: () => void;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does this need to be optional? Looks like it is always provided

Suggested change
openHeatmapSettings?: () => void;
openHeatmapSettings: () => void;

setValue={setValue}
tableSource={tableSource}
onSubmit={onSubmit}
onOpenDisplaySettings={openHeatmapSettings ?? openDisplaySettings}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would we ever want to open the display settings here?

Suggested change
onOpenDisplaySettings={openHeatmapSettings ?? openDisplaySettings}
onOpenHeatmapSettings={openHeatmapSettings}

import { SQLPreview } from './ChartSQLPreview';

/** Compact duration labels for axis ticks — fewer decimals, shorter units. */
function formatDurationMsCompact(ms: number): string {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thoughts on making this a shared util alongside formatDurationMs in packages/app/src/utils.ts?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It would also be great if we could add some unit tests here.

];
setValue('select', heatmapSeries);
setValue('series', heatmapSeries);
setValue('series.0.countExpression' as any, 'count()');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should add countExpression to the SelectItem schema/type so that we don't need a cast here

setValue('series.0.countExpression' as any, 'count()');
if (traceSource) {
setValue('numberFormat', {
output: 'duration' as any,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is not needed. Casting as any will hide any future type errors, and should be avoided when possible.

Suggested change
output: 'duration' as any,
output: 'duration',

setValue('select', heatmapSeries);
setValue('series', heatmapSeries);
setValue('series.0.countExpression' as any, 'count()');
setValue('numberFormat', { output: 'duration' as any, factor: 0.001 });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This cast is not needed

Suggested change
setValue('numberFormat', { output: 'duration' as any, factor: 0.001 });
setValue('numberFormat', { output: 'duration', factor: 0.001 });

defaultValues: HeatmapSettingsValues;
scaleType: HeatmapScaleType;
onScaleTypeChange: (v: HeatmapScaleType) => void;
onSubmit: (v: HeatmapSettingsValues) => void;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would it make sense to combine scaleType into HeatmapSettingsValues instead of treating them separately here and having to handle both handleHeatmapScaleTypeChange and handleUpdateHeatmapSettings in the parent component?

Heatmap charts require trace data (duration expression) to work properly.
Non-trace sources would error out with no guidance. Instead of adding
complex error messaging, filter the source dropdown to only show trace
sources when heatmap display type is selected.
@alex-fedotyev
Copy link
Copy Markdown
Contributor Author

Pushed a587ca2 — restricts the data source picker to trace sources only when heatmap display type is selected.

This addresses:

  • Non-trace source error UX: the option to pick a non-trace source simply won't appear, so no confusing error state
  • Simplified heatmap defaults: since the source is always a trace, duration formatting is applied unconditionally

The SourceSelectControlled component already supported allowedSourceKinds filtering (used on Sessions, Search pages) — just wired it up for heatmap.

Still working through the remaining review items (removing as any casts, unit tests, schema updates, etc.).

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

Labels

review/tier-3 Standard — full human review required

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants