feat(app): full-featured histogram and filters across multiple sources - #2877
feat(app): full-featured histogram and filters across multiple sources#2877teeohhem wants to merge 2 commits into
Conversation
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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
| 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 SummaryThis PR unifies the search histogram, total count, and filter sidebar across single- and multi-source searches.
Confidence Score: 3/5The 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
|
| 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
Reviews (1): Last reviewed commit: "feat(app): one filters sidebar for any n..." | Re-trigger Greptile
| for (const [value, count] of slot.data) { | ||
| merged.set(value, (merged.get(value) ?? 0) + count); |
There was a problem hiding this comment.
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
| hasSharedPins, | ||
| } = usePinnedFilters(sourceId ?? null); | ||
| } = usePinnedFiltersForSources(sourceIds); | ||
| const { data: pinnedFiltersApiData } = usePinnedFiltersApi(sourceId ?? null); |
There was a problem hiding this comment.
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.
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 fullDBTimeChart— 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 acount()series per source. Routing that inside the component leaves one call site per surface and puts the trade-off in one place.Filters sidebar.
DBSearchPageFiltersnow 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 — pinServiceNamewhile searching logs and traces and it stays pinned in both. Filter-key escaping resolves against the union of the selected schemas. The cut-downMultiSourceSearchFiltersis 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}andSpanAttributes {92}side by side, logs-onlySeverityTextalongside traces-onlyStatusCode, summed counts, and pins present.make ci-lintandmake ci-unitpass (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: