feat: Exemplars for metric & PromQL charts#2536
Conversation
🦋 Changeset detectedLatest commit: 4d3ef3a The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
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 |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🔴 Tier 4 — CriticalTouches auth, data models, config, tasks, OTel pipeline, ClickHouse, or CI/CD. Why this tier:
Review process: Deep review from a domain expert. Synchronous walkthrough may be required. Stats
|
Greptile SummaryThis PR adds exemplar markers for metric and PromQL time charts. The main changes are:
Confidence Score: 5/5This looks safe to merge.
|
| Filename | Overview |
|---|---|
| packages/common-utils/src/core/renderChartConfig.ts | Adds exemplar eligibility checks and scoped metric exemplar SQL generation. |
| packages/api/src/config.ts | Adds spanmetrics and Prometheus remote-write configuration flags. |
| packages/api/src/opamp/controllers/opampController.ts | Adds optional spanmetrics connector and remote-write exporter generation. |
| packages/app/src/hooks/useExemplars.tsx | Adds gated exemplar fetching for metric and PromQL chart sources. |
| packages/app/src/components/ChartEditor/PromqlChartEditor.tsx | Adds the PromQL exemplar toggle and trace source selection. |
Reviews (11): Last reviewed commit: "fix(exemplars): address review P2s on el..." | Re-trigger Greptile
E2E Test Results✅ All tests passed • 242 passed • 1 skipped • 996s
Tests ran across 4 shards in parallel. |
…ated collector config, so the collector container no longer needs SPAN_METRICS_PROM_RW_ENDPOINT in its own environment.
Deep ReviewThirteen reviewer agents ran against the exemplars feature surface. Note on method: 🔴 P0/P1 — must fix
🟡 P2 — recommended
🔵 P3 nitpicks (13)
Reviewers (13): correctness, adversarial, security, performance, reliability, api-contract, testing, maintainability, project-standards, kieran-typescript, julik-frontend-races, agent-native, learnings-researcher. Testing gaps:
|
# Conflicts: # knip.json # packages/app/src/HDXMultiSeriesTimeChart.tsx # packages/app/src/components/ChartEditor/utils.ts # packages/app/src/components/DBEditTimeChartForm/ChartEditorControls.tsx # packages/app/src/components/DBTimeChart.tsx # packages/app/src/components/TeamSettings/TeamQueryConfigSection.tsx # packages/common-utils/src/types.ts
Move the inline ExemplarDot marker and ExemplarHoverCard popover out of the oversized HDXMultiSeriesTimeChart and DBTimeChart into a focused components/Exemplars/ directory, and add a Storybook story for each so the marker and every hover-card state (full/partial metadata, loading, not-found, no-trace-source) can be developed and reviewed in isolation across light/dark and both brand themes. Behaviour-preserving: the extracted component bodies are unchanged; ExemplarDot also gains a real props type in place of `any`.
…crushing the axis Addresses review feedback (Greptile P1 + Deep Review P2s) and hardens the exemplar overlay while it's still being tested. Correctness / scoping: - Metric exemplar SQL now ANDs the metric-name predicate separately instead of appending it to the user filter group, so a chart with filtersLogicalOperator 'OR' can no longer let the exemplar scan match other metrics. - Prometheus normaliser drops the overlay when the query returns >1 series (exemplars are single-series only), and the PromQL result is capped client-side (no native limit param) to bound render/thinning work. - enableExemplars is cleared when a chart leaves single-series so a stale flag can't persist on a config that no longer supports it. Feature gate: - Whole feature (editor toggle, PromQL toggle, data fetch, team setting) gated behind NEXT_PUBLIC_ENABLE_EXEMPLARS; on in dev, off by default elsewhere. Rendering: - Exemplar outliers no longer stretch the y-axis: the domain follows the visible series and each marker is clamped to the series max (its hover card still shows the true value), so one slow trace can't flatten the p99 line. - The series hover tooltip is suppressed while hovering an exemplar so the two cards don't overlap; the chart resets this against the rendered points so a refetch/re-thinning that unmounts the hovered marker can't leave it stuck. - Higher-contrast marker outline for light mode. Maintainability / tests: - Exemplar bucketing extracted to a pure, unit-tested helper; Prometheus exemplar response type de-duplicated. Added SQL OR-filter and multi-series regression tests.
The exemplars work pushed packages/app over the --max-warnings 740 floor. Fix five real warnings (drop an unnecessary offsetParent cast, avoid an empty-function stub, rename a useId/ref binding, narrow traceSourceId with an 'in' check instead of a cast) and disable no-unnecessary-use-prefix in DBSearchPageQueryKey.test.tsx, where jest.mock factories must mirror the real hook names.
Resolves the five P2 findings from the PR review, plus two follow-ons the re-review surfaced in the fixes themselves. - Exemplar eligibility (single, non-ratio, non-grouped histogram series) now lives in one predicate, isExemplarEligible, which both the SQL renderer and the chart editor delegate to. The missing Group By guard came from the rule being reimplemented per call site: a grouped chart pooled exemplars from every group into one unattributable set, so the renderer now returns null for it. - Exemplar markers are clamped into both ends of the y-axis domain the chart actually renders, not just the top. A fitYAxisToData floor can sit above an individual duration when the series is a high quantile, and recharts' ReferenceDot discards out-of-domain markers — which silently emptied the overlay. Extracted as pure helpers so the bounds are testable. - The stale-flag cleanup effect no longer keys on NEXT_PUBLIC_ENABLE_EXEMPLARS, so opening a saved exemplar chart with the deployment flag off suppresses the overlay instead of stripping the persisted config on the next save. It also waits for the async source to resolve (shape unknown != shape wrong, which stripped config on every cold load) and leaves exemplarTraceSourceId intact, so re-enabling after a Group By typo is lossless. - Tests: the useExemplars hook (gating, ClickHouse row mapping, the PromQL result cap, error surfacing), isExemplarEligible's matrix including the enum-casing hazard, the y-clamp bounds, the Group By and non-histogram null branches, and the query_exemplars 400/404 guards.
Why
Engineers staring at a latency spike on a chart have no way to jump to a trace that caused it.
This change adds exemplars, clickable markers overlaid on time charts, each linking to a representative trace.
Works for metric and PromQL sources.
Also added some test-telemetry infrastructure to build and validate the feature against more complex data sets.
CleanShot.2026-06-29.at.13.50.52.mp4
Exemplar overlay (app)
Two data backends
Fully-OTLP coherent metrics (collector)
Telemetry generator (telemetry-generator/)
Team setting
Scoping
Out of scope (separate tickets)
Testing
Changesets
Notes / caveats
Storybook: exemplar components
Extracted the two exemplar UI pieces — the
ExemplarDotchart marker and theExemplarHoverCardtrace popover — out ofHDXMultiSeriesTimeChartandDBTimeChartinto a focusedcomponents/Exemplars/directory, with a Storybook story for each. The card story covers every state (full/partial metadata, loading, trace-not-found, no-trace-source-configured) and the dot story renders the marker in isolation, so both can be reviewed across light/dark and both brand themes without a live ClickHouse query. Behaviour-preserving;ExemplarDotalso gains a real props type in place ofany.Merged
mainto bring the branch current — this includes the recharts 2→3 upgrade, for which the exemplarReferenceDotmarker was adjusted to the v3 API.Review hardening & feature gate (latest)
Follow-up addressing review feedback (Greptile P1 + Deep Review P2s) and gating the feature for a safe rollout:
NEXT_PUBLIC_ENABLE_EXEMPLARS: off by default, enabled in local dev. It can ship dark while we finish testing against real data.filtersLogicalOperator: 'OR'can no longer let the exemplar scan match other metrics.enableExemplarsis cleared when a chart leaves single-series so a stale flag can't linger.Note: the exemplar marker value was verified correct — it matches the span's real duration to the nanosecond; the earlier "off by an order of magnitude" appearance was a single slow outlier stretching the y-axis, addressed above.