feat: Add Column toggle button to filter panel in DBSearchPage (#1947)

## Summary

Adds a column toggle button (+ / - icon) next to the "Show Distribution" button in each filter group header on the search page. Clicking the button adds or removes the filter's field from the `SELECT` statement, and the table reflects the change immediately.

### Changes

- **`FilterGroup`** (`DBSearchPageFilters.tsx`): Added `onColumnToggle` and `isColumnDisplayed` props. Renders an `ActionIcon` with `IconPlus` (add) or `IconMinus` (remove) between the distribution toggle and the pin field button.
- **`NestedFilterGroup`**: Passes the new column toggle props through to child `FilterGroup` components.
- **`DBSearchPage.tsx`**: Passes `toggleColumn` and `displayedColumns` to `DBSearchPageFilters`, reusing the existing `toggleColumn` callback that manages the `SELECT` form field.

### Screenshots or video

| Before | After |
| :----- | :---- |
| Only distribution and pin buttons in filter header | New +/- column button appears between distribution and pin buttons |

### How to test locally or on Vercel

1. Navigate to the Search page
2. Open the filter panel on the left side
3. Find any filter group and hover over the header area — a `+` icon should appear next to the distribution chart icon
4. Click the `+` icon — the field should be added to the `SELECT` input and appear as a column in the results table
5. Click the `-` icon (now shown since the column is displayed) — the field should be removed from `SELECT` and the column disappears

### References

- Linear Issue: HDX-3770



Linear Issue: [HDX-3770](https://linear.app/clickhouse/issue/HDX-3770/telstra-add-column-from-filter-panel-in-dbsearchpage)

<div><a href="https://cursor.com/agents/bc-11d702b5-a58e-485c-982f-61d990e45091"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-11d702b5-a58e-485c-982f-61d990e45091"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>



Co-authored-by: Cursor Agent <199161495+cursoragent@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Mike Shi 2026-03-23 08:21:57 -07:00 committed by GitHub
parent b642ce43d3
commit c9d1dda358
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 59 additions and 1 deletions

View file

@ -0,0 +1,5 @@
---
"@hyperdx/app": patch
---
feat: Add column toggle button to filter panel in DBSearchPage

View file

@ -1769,6 +1769,8 @@ function DBSearchPage() {
? searchedSource.durationExpression
: undefined)
}
onColumnToggle={toggleColumn}
displayedColumns={displayedColumns}
{...searchFilters}
/>
</ErrorBoundary>

View file

@ -36,8 +36,10 @@ import {
IconChevronRight,
IconChevronUp,
IconFilterOff,
IconMinus,
IconPin,
IconPinFilled,
IconPlus,
IconRefresh,
IconSearch,
IconShadow,
@ -341,6 +343,8 @@ export type FilterGroupProps = {
isPinned: (value: string | boolean) => boolean;
onFieldPinClick?: VoidFunction;
isFieldPinned?: boolean;
onColumnToggle?: VoidFunction;
isColumnDisplayed?: boolean;
onLoadMore: (key: string) => void;
loadMoreLoading: boolean;
hasLoadedMore: boolean;
@ -349,7 +353,7 @@ export type FilterGroupProps = {
chartConfig: BuilderChartConfigWithDateRange;
isLive?: boolean;
onRangeChange?: (range: { min: number; max: number }) => void;
distributionKey?: string; // Optional key to use for distribution queries, defaults to name
distributionKey?: string;
};
export const FilterGroup = ({
@ -365,6 +369,8 @@ export const FilterGroup = ({
onPinClick,
onFieldPinClick,
isFieldPinned,
onColumnToggle,
isColumnDisplayed,
onLoadMore,
loadMoreLoading,
hasLoadedMore,
@ -641,6 +647,29 @@ export const FilterGroup = ({
)}
</ActionIcon>
</Tooltip>
{onColumnToggle && (
<Tooltip
label={isColumnDisplayed ? 'Remove Column' : 'Add Column'}
position="top"
withArrow
fz="xxs"
color="gray"
>
<ActionIcon
size="xs"
variant="subtle"
color="gray"
onClick={onColumnToggle}
data-testid={`toggle-column-button-${name}`}
>
{isColumnDisplayed ? (
<IconMinus size={14} />
) : (
<IconPlus size={14} />
)}
</ActionIcon>
</Tooltip>
)}
{onFieldPinClick && (
<Tooltip
label={isFieldPinned ? 'Unpin Field' : 'Pin Field'}
@ -844,6 +873,8 @@ const DBSearchPageFiltersComponent = ({
denoiseResults,
setDenoiseResults,
setFilterRange,
onColumnToggle,
displayedColumns,
}: {
analysisMode: 'results' | 'delta' | 'pattern';
setAnalysisMode: (mode: 'results' | 'delta' | 'pattern') => void;
@ -854,6 +885,8 @@ const DBSearchPageFiltersComponent = ({
denoiseResults: boolean;
setDenoiseResults: (denoiseResults: boolean) => void;
setFilterRange: (key: string, range: { min: number; max: number }) => void;
onColumnToggle?: (column: string) => void;
displayedColumns?: string[];
} & FilterStateHook) => {
const setFilterValue = useCallback(
(
@ -1334,6 +1367,8 @@ const DBSearchPageFiltersComponent = ({
isPinned={(key, value) => isFilterPinned(key, value)}
onFieldPinClick={key => toggleFieldPin(key)}
isFieldPinned={key => isFieldPinned(key)}
onColumnToggle={onColumnToggle}
displayedColumns={displayedColumns}
onLoadMore={loadMoreFilterValuesForKey}
loadMoreLoading={group.children.reduce(
(acc, child) => {
@ -1394,6 +1429,12 @@ const DBSearchPageFiltersComponent = ({
isPinned={value => isFilterPinned(facet.key, value)}
onFieldPinClick={() => toggleFieldPin(facet.key)}
isFieldPinned={isFieldPinned(facet.key)}
onColumnToggle={
onColumnToggle
? () => onColumnToggle(facet.key)
: undefined
}
isColumnDisplayed={displayedColumns?.includes(facet.key)}
onLoadMore={loadMoreFilterValuesForKey}
loadMoreLoading={loadMoreLoadingKeys.has(facet.key)}
hasLoadedMore={Boolean(extraFacets[facet.key])}

View file

@ -30,6 +30,8 @@ export type NestedFilterGroupProps = {
isPinned: (key: string, value: string | boolean) => boolean;
onFieldPinClick?: (key: string) => void;
isFieldPinned?: (key: string) => boolean;
onColumnToggle?: (column: string) => void;
displayedColumns?: string[];
onLoadMore: (key: string) => void;
loadMoreLoading: Record<string, boolean>;
hasLoadedMore: Record<string, boolean>;
@ -51,6 +53,8 @@ export const NestedFilterGroup = ({
isPinned,
onFieldPinClick,
isFieldPinned,
onColumnToggle,
displayedColumns,
onLoadMore,
loadMoreLoading,
hasLoadedMore,
@ -153,6 +157,12 @@ export const NestedFilterGroup = ({
isPinned={value => isPinned(child.key, value)}
onFieldPinClick={() => onFieldPinClick?.(child.key)}
isFieldPinned={isFieldPinned?.(child.key)}
onColumnToggle={
onColumnToggle
? () => onColumnToggle(child.key)
: undefined
}
isColumnDisplayed={displayedColumns?.includes(child.key)}
onLoadMore={() => onLoadMore(child.key)}
loadMoreLoading={loadMoreLoading[child.key] || false}
hasLoadedMore={hasLoadedMore[child.key] || false}