Skip to content

feat(app): full-featured histogram and filters across multiple sources - #2877

Closed
teeohhem wants to merge 2 commits into
tom/search-n-ary-defaultfrom
tom/search-n-ary-histogram
Closed

feat(app): full-featured histogram and filters across multiple sources#2877
teeohhem wants to merge 2 commits into
tom/search-n-ary-defaultfrom
tom/search-n-ary-histogram

Conversation

@teeohhem

Copy link
Copy Markdown
Contributor

Summary

Stacked on #2869 — review that first; this PR's diff is the delta on top of it.

#2869 made the results table N-ary but left the histogram and the filters sidebar picking between single- and multi-source variants at the call site, and the multi-source sidebar was a cut-down copy with no pins, value counts, or "load more". This closes both gaps, so multi-source search is fully featured rather than a reduced mode.

Histogram and count. The page renders SearchHistogram / SearchTotalCount, which take the same per-source spec list the results table does. One source keeps the full DBTimeChart — severity/status grouping, series drill-down and focus, the pinned tooltip — because a single source has a severity vocabulary to group by. Several sources can't share one, so they stack a count() series per source. Routing that inside the component leaves one call site per surface and puts the trade-off in one place.

Filters sidebar. DBSearchPageFilters now takes a list of sources. Facet fields and values merge, value counts are summed so a percentage describes the whole search, "load more" fans out to every source and unions the results, and pins (personal and team-shared) read as a union and write to every selected source — pin ServiceName while searching logs and traces and it stays pinned in both. Filter-key escaping resolves against the union of the selected schemas. The cut-down MultiSourceSearchFilters is deleted.

With one source, every path is the one it was before: analysis-mode tabs, denoise, shared filters, and the distribution error handling that drops percentages rather than showing counts that silently omit a source.

Verification

Verified live against the demo stack in both modes. Single source: sidebar keeps its tabs, denoise toggle, per-value counts, pins, "Show more" and "More filters"; histogram draws its severity-grouped bars; sorting, side panel, and denoise still work. Two sources (logs + traces): 33 merged facet groups including nested LogAttributes {34} and SpanAttributes {92} side by side, logs-only SeverityText alongside traces-only StatusCode, summed counts, and pins present. make ci-lint and make ci-unit pass (one unrelated dashboard-form test flakes only in the parallel run and passes in isolation).

How to test on Vercel preview

Preview routes: /search

Steps:

  1. Open /search and wait for the Filters panel to list field groups.
  2. Confirm the panel shows "Analysis Mode" tabs, a "Denoise Results" checkbox, and per-value counts in parentheses next to field names.
  3. Click the "+" button next to the source selector (data-testid "add-search-source").
  4. Click the sources multi-select (data-testid "source-multi-selector"), select "Demo Traces", then press Escape.
  5. Wait for the Filters panel to reload (this can take up to 60 seconds).
  6. Verify the panel now lists fields from both sources together, for example "SeverityText" (logs) and "StatusCode" (traces), each still showing value counts and a pin icon.

Compound Engineering
Claude Code

The search page picked between DBTimeChart and a multi-source chart (and
between two total-count components) at the call site. It now renders
SearchHistogram/SearchTotalCount, which take the same per-source spec
list the results table does.

One source keeps the full DBTimeChart — severity/status grouping, series
drill-down and focus, the pinned tooltip — because a single source has a
severity vocabulary to group by. Several sources can't share one, so they
stack a count() series per source instead. Routing that choice inside the
component leaves the page with a single call site per surface and makes
the trade-off explicit in one place.
DBSearchPageFilters now takes a list of sources instead of one, so the
sidebar users already know works across a whole selection: facet fields
and values merge, value counts are summed, "load more" fans out to every
source, and pins read as a union and write to all selected sources (pin
ServiceName while searching logs and traces and it stays pinned in both).
Filter-key escaping resolves against the union of the selected schemas.

This replaces the cut-down multi-source sidebar, which had no pins,
counts, or load-more. With one source every path is the one it was
before: the analysis-mode tabs, denoise toggle, and shared-filters
section are untouched.

The value-distribution hook gained a merged variant so a percentage
describes the whole search rather than one table, and it still surfaces
a failure so a group can drop percentages instead of showing counts that
silently omit a source.
@vercel

vercel Bot commented Aug 12, 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 12, 2026 2:27pm
hyperdx-storybook Ready Ready Preview Aug 12, 2026 2:27pm

Request Review

@changeset-bot

changeset-bot Bot commented Aug 12, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 81eeb69

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 changesets to release 3 packages
Name Type
@hyperdx/app Minor
@hyperdx/api Minor
@hyperdx/otel-collector Minor

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

@greptile-apps

greptile-apps Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR unifies the search histogram, total count, and filter sidebar across single- and multi-source searches.

  • Routes histogram and count rendering through source-count-aware components.
  • Merges facet fields and values across selected sources and fans out load-more operations.
  • Adds merged distribution and personal/team pin behavior for multi-source filters.
  • Extracts the fixed source-slot hook into a shared module.

Confidence Score: 3/5

The PR should not merge until multi-source percentages and secondary-source shared-filter visibility are corrected.

The merged distribution path mathematically combines percentages as counts, while the Shared Filters section omits team pins that exist only on non-primary selected sources.

Files Needing Attention: packages/app/src/hooks/useMetadata.tsx; packages/app/src/components/DBSearchPageFilters.tsx

Important Files Changed

Filename Overview
packages/app/src/DBSearchPage.tsx Replaces separate single- and multi-source histogram/filter call sites with unified source-spec interfaces.
packages/app/src/components/DBSearchPageFilters.tsx Adds multi-source facets, distributions, and pins, but the Shared Filters section still reads only the primary source's API data.
packages/app/src/components/DBSearchPageFilters/hooks.ts Fans out facet pipelines and merges fields and values while preserving partial results on source errors.
packages/app/src/hooks/useMetadata.tsx Adds distribution merging but incorrectly sums independently normalized per-source percentages.
packages/app/src/searchFilters.tsx Implements union pin reads and coordinated pin mutations across selected sources.
packages/app/src/components/SearchHistogram.tsx Routes one-source rendering through DBTimeChart and multiple sources through the stacked source chart.
packages/app/src/hooks/useSourceSlots.ts Extracts the fixed-cardinality source-slot hook without changing its behavior.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  S[Selected sources] --> C[Per-source search configs]
  C --> H[SearchHistogram / SearchTotalCount]
  C --> F[Per-source facet queries]
  C --> D[Per-source distribution queries]
  C --> P[Per-source pin state]
  F --> FM[Merged fields and values]
  D --> DM[Merged distributions]
  P --> PM[Union reads and fan-out writes]
  FM --> UI[Unified filters sidebar]
  DM --> UI
  PM --> UI
Loading

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

Reviews (1): Last reviewed commit: "feat(app): one filters sidebar for any n..." | Re-trigger Greptile

Comment on lines +547 to +548
for (const [value, count] of slot.data) {
merged.set(value, (merged.get(value) ?? 0) + 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.

P1 Merged percentages are summed incorrectly

When a field has distributions from multiple sources, useMergedValuesDistribution adds percentages that were independently normalized against each source. The resulting value is not the field's percentage across the combined search, causing incorrect labels—including values collapsed to >99%—and incorrect frequency sorting.

Knowledge Base Used: App Components and Charts

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

hasSharedPins,
} = usePinnedFilters(sourceId ?? null);
} = usePinnedFiltersForSources(sourceIds);
const { data: pinnedFiltersApiData } = usePinnedFiltersApi(sourceId ?? null);

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.

P1 Shared pins use primary source only

When a team-shared pin exists only on a non-primary selected source, this primary-source-only API response omits it from sharedFilterKeys and sharedFacets. The Shared Filters section therefore displays an incomplete union and leaves the omitted shared field in the regular filter list.

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

@teeohhem

Copy link
Copy Markdown
Contributor Author

Superseded by the re-split stack: #2881#2882#2883#2884#2885#2886. Same end state (the final branch's production code is byte-identical to what this stack produced), but sliced into reviewable pieces with the throwaway intermediate components removed.

@teeohhem teeohhem closed this Aug 12, 2026
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