Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions .changeset/multi-source-filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
'@hyperdx/app': minor
---

The filters sidebar now works when searching multiple sources. Facet fields
and values merge across the selected sources, and checking a value filters
every source that has the field. A source whose table lacks a filtered column
is excluded from the results with a visible reason on its status chip instead
of silently returning unfiltered rows. Filter pills and add-to-filter from the
row side panel work in multi-source mode too.
The filters sidebar works across every selected source. Facet fields and
values merge across sources, value counts are summed, "load more" fans out,
and pins (personal and team-shared) read as a union and apply to the whole
selection. Checking a value filters every source that has the field; a source
whose table lacks a filtered column is excluded from the results with a
visible reason on its status chip instead of silently returning unfiltered
rows. Filter pills and add-to-filter from the row side panel work across
sources too.
100 changes: 52 additions & 48 deletions packages/app/src/DBSearchPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,25 +93,24 @@ import { AlertStatusIcon } from '@/components/AlertStatusIcon';
import { ContactSupportText } from '@/components/ContactSupportText';
import { DBSearchPageFilters } from '@/components/DBSearchPageFilters';
import { cleanClickHouseExpression } from '@/components/DBSearchPageFilters/utils';
import { DBTimeChart, type SeriesGroupFilter } from '@/components/DBTimeChart';
import { type SeriesGroupFilter } from '@/components/DBTimeChart';
import EmptyState from '@/components/EmptyState';
import { ErrorBoundary } from '@/components/Error/ErrorBoundary';
import { FavoriteButton } from '@/components/FavoriteButton';
import ResourceTerraformPopover from '@/components/Iac/ResourceTerraformPopover';
import { InputControlled } from '@/components/InputControlled';
import MultiSourceColumnPicker from '@/components/MultiSourceColumnPicker';
import MultiSourceSearchFilters from '@/components/MultiSourceSearchFilters';
import {
MultiSourceTimeChart,
MultiSourceTotalCountChart,
} from '@/components/MultiSourceTimeChart';
import OnboardingModal from '@/components/OnboardingModal';
import {
SearchHistogram,
type SearchHistogramSpec,
SearchTotalCount,
} from '@/components/SearchHistogram';
import SearchWhereInput, {
getStoredLanguage,
} from '@/components/SearchInput/SearchWhereInput';
import SearchPageActionBar from '@/components/SearchPageActionBar';
import SearchResultsTable from '@/components/SearchResultsTable';
import SearchTotalCountChart from '@/components/SearchTotalCountChart';
import { SourceMultiSelectControlled } from '@/components/SourceMultiSelect';
import { TableSourceForm } from '@/components/Sources/SourceForm';
import { SourceSelectControlled } from '@/components/SourceSelect';
Expand Down Expand Up @@ -349,21 +348,21 @@ function ExpandFiltersButton({ onExpand }: { onExpand: () => void }) {
function SearchResultsCountGroup({
isFilterSidebarCollapsed,
onExpandFilters,
histogramTimeChartConfig,
histogramSpecs,
enableParallelQueries,
}: {
isFilterSidebarCollapsed: boolean;
onExpandFilters: () => void;
histogramTimeChartConfig: BuilderChartConfigWithDateRange;
histogramSpecs: SearchHistogramSpec[];
enableParallelQueries?: boolean;
}) {
return (
<Group gap={4} align="center">
{isFilterSidebarCollapsed && (
<ExpandFiltersButton onExpand={onExpandFilters} />
)}
<SearchTotalCountChart
config={histogramTimeChartConfig}
<SearchTotalCount
specs={histogramSpecs}
queryKeyPrefix={QUERY_KEY_PREFIX}
enableParallelQueries={enableParallelQueries}
/>
Expand Down Expand Up @@ -2195,6 +2194,20 @@ export function DBSearchPage() {
searchedConfig.select,
]);

// The chart/count query plan for however many sources are selected: one
// source keeps its severity-grouped histogram, several get one count()
// series each (stacked by source).
const histogramSpecs = useMemo(() => {
if (isMultiSource) return multiHistogramSpecs;
if (searchedSource == null || histogramTimeChartConfig == null) return [];
return [{ source: searchedSource, config: histogramTimeChartConfig }];
}, [
isMultiSource,
multiHistogramSpecs,
searchedSource,
histogramTimeChartConfig,
]);

const onFormSubmit = useCallback<FormEventHandler<HTMLFormElement>>(
e => {
e.preventDefault();
Expand Down Expand Up @@ -2276,6 +2289,22 @@ export function DBSearchPage() {
};
}, [chartConfig, searchedTimeRange, aliasWith]);

// The sidebar reads facets, values, and pins across everything selected;
// with one source that is exactly the single-source sidebar.
const filterSidebarSources = useMemo(() => {
if (isMultiSource) {
// Facet queries want each source's search shape (its own FROM,
// connection, and WHERE), not the aggregated histogram config.
return searchStreamSpecs.map(({ source, config }) => ({
source,
config: { ...config, orderBy: undefined },
}));
}
return searchedSource != null
? [{ source: searchedSource, config: filtersChartConfig }]
: [];
}, [isMultiSource, searchStreamSpecs, searchedSource, filtersChartConfig]);

const openNewSourceModal = useCallback(() => {
setNewSourceModalOpened(true);
}, []);
Expand Down Expand Up @@ -2834,30 +2863,15 @@ export function DBSearchPage() {
height: '100%',
}}
>
{!isFilterSidebarCollapsed &&
isMultiSource &&
!isMultiSourceSqlBlocked && (
<ErrorBoundary message="Unable to render search filters">
<MultiSourceSearchFilters
specs={multiHistogramSpecs}
dateRange={searchedTimeRange}
isLive={isLive ?? true}
knownColumns={knownColumns}
searchFilters={searchFilters}
onCollapse={() => setIsFilterSidebarCollapsed(true)}
/>
</ErrorBoundary>
)}
{!isFilterSidebarCollapsed && !isMultiSource && (
{!isFilterSidebarCollapsed && (
<ErrorBoundary message="Unable to render search filters">
<DBSearchPageFilters
denoiseResults={denoiseResults}
setDenoiseResults={setDenoiseResults}
isLive={isLive}
analysisMode={analysisMode}
setAnalysisMode={setAnalysisMode}
chartConfig={filtersChartConfig}
sourceId={inputSourceObj?.id}
sources={filterSidebarSources}
showDelta={
!!(searchedSource?.kind === SourceKind.Trace
? searchedSource.durationExpression
Expand All @@ -2884,7 +2898,7 @@ export function DBSearchPage() {
onExpandFilters={() =>
setIsFilterSidebarCollapsed(false)
}
histogramTimeChartConfig={histogramTimeChartConfig}
histogramSpecs={histogramSpecs}
/>
<SearchNumRows
config={{
Expand All @@ -2904,14 +2918,9 @@ export function DBSearchPage() {
className={searchPageStyles.timeChartContainer}
mih="0"
>
<DBTimeChart
sourceId={searchedConfig.source ?? undefined}
showLegend={false}
config={histogramTimeChartConfig}
<SearchHistogram
specs={histogramSpecs}
enabled={isReady}
showDisplaySwitcher={false}
showMVOptimizationIndicator={false}
showDateRangeIndicator={false}
queryKeyPrefix={QUERY_KEY_PREFIX}
onTimeRangeSelect={handleTimeRangeSelect}
onFocusSeries={handleFocusSeries}
Expand Down Expand Up @@ -2989,8 +2998,8 @@ export function DBSearchPage() {
}
/>
)}
<MultiSourceTotalCountChart
specs={multiHistogramSpecs}
<SearchTotalCount
specs={histogramSpecs}
enabled={isReady}
queryKeyPrefix={QUERY_KEY_PREFIX}
enableParallelQueries
Expand All @@ -3007,8 +3016,8 @@ export function DBSearchPage() {
className={searchPageStyles.timeChartContainer}
mih="0"
>
<MultiSourceTimeChart
specs={multiHistogramSpecs}
<SearchHistogram
specs={histogramSpecs}
enabled={isReady}
queryKeyPrefix={QUERY_KEY_PREFIX}
enableParallelQueries
Expand Down Expand Up @@ -3057,7 +3066,7 @@ export function DBSearchPage() {
onExpandFilters={() =>
setIsFilterSidebarCollapsed(false)
}
histogramTimeChartConfig={histogramTimeChartConfig}
histogramSpecs={histogramSpecs}
enableParallelQueries
/>
<Group gap="sm" align="center">
Expand Down Expand Up @@ -3086,14 +3095,9 @@ export function DBSearchPage() {
className={searchPageStyles.timeChartContainer}
mih="0"
>
<DBTimeChart
sourceId={searchedConfig.source ?? undefined}
showLegend={false}
config={histogramTimeChartConfig}
<SearchHistogram
specs={histogramSpecs}
enabled={isReady}
showDisplaySwitcher={false}
showMVOptimizationIndicator={false}
showDateRangeIndicator={false}
queryKeyPrefix={QUERY_KEY_PREFIX}
onTimeRangeSelect={handleTimeRangeSelect}
onFocusSeries={handleFocusSeries}
Expand Down
6 changes: 3 additions & 3 deletions packages/app/src/__tests__/DBSearchPage.directTrace.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,9 @@ jest.mock('../components/DBSqlRowTableWithSidebar', () => () => <div />);
// Multi-source components pull in DBRowSidePanel (and its deep import graph),
// which this test isolates away just like DBSqlRowTableWithSidebar above.
jest.mock('../components/SearchResultsTable', () => () => <div />);
jest.mock('../components/MultiSourceTimeChart', () => ({
MultiSourceTimeChart: () => <div />,
MultiSourceTotalCountChart: () => <div />,
jest.mock('../components/SearchHistogram', () => ({
SearchHistogram: () => <div />,
SearchTotalCount: () => <div />,
}));
jest.mock('../components/PatternTable', () => () => <div />);
jest.mock('../components/Search/DBSearchHeatmapChart', () => ({
Expand Down
Loading