diff --git a/.changeset/angry-boats-lie.md b/.changeset/angry-boats-lie.md new file mode 100644 index 0000000000..a5e9d5fc74 --- /dev/null +++ b/.changeset/angry-boats-lie.md @@ -0,0 +1,5 @@ +--- +"@hyperdx/app": patch +--- + +fix(search-filters): prevent nested filter dropdowns from disappearing on reopen diff --git a/packages/app/src/components/DBSearchPageFilters/NestedFilterGroup.tsx b/packages/app/src/components/DBSearchPageFilters/NestedFilterGroup.tsx index 8a3106b5cc..945b9d60e5 100644 --- a/packages/app/src/components/DBSearchPageFilters/NestedFilterGroup.tsx +++ b/packages/app/src/components/DBSearchPageFilters/NestedFilterGroup.tsx @@ -149,122 +149,115 @@ export const NestedFilterGroup = ({ pb={0} pl="xs" > - {isExpanded && ( -
- {childFilters.length === 0 ? ( - - - No properties found - - - ) : ( +
+ {childFilters.length === 0 ? ( + + + No properties found + + + ) : ( +
-
- {rowVirtualizer.getVirtualItems().map(virtualRow => { - const child = childFilters[virtualRow.index]; - const childSelectedValues = selectedValues[ - child.key - ] || { - included: new Set(), - excluded: new Set(), - }; - const childHasSelections = - childSelectedValues.included.size + - childSelectedValues.excluded.size > - 0; + {rowVirtualizer.getVirtualItems().map(virtualRow => { + const child = childFilters[virtualRow.index]; + const childSelectedValues = selectedValues[child.key] || { + included: new Set(), + excluded: new Set(), + }; + const childHasSelections = + childSelectedValues.included.size + + childSelectedValues.excluded.size > + 0; - return ( -
+ ({ + value: value, + label: value.toString(), + }))} + optionsLoading={false} + selectedValues={childSelectedValues} + onChange={value => onChange(child.key, value)} + onClearClick={() => onClearClick(child.key)} + onOnlyClick={value => onOnlyClick(child.key, value)} + onExcludeClick={value => + onExcludeClick(child.key, value) + } + valuePins={{ + onPinClick: value => onPinClick(child.key, value), + isPinned: value => isPinned(child.key, value), + onSharedPinClick: onSharedPinClick + ? value => onSharedPinClick(child.key, value) + : undefined, + isSharedPinned: isSharedPinned + ? value => isSharedPinned(child.key, value) + : undefined, }} - > - ({ - value: value, - label: value.toString(), - }))} - optionsLoading={false} - selectedValues={childSelectedValues} - onChange={value => onChange(child.key, value)} - onClearClick={() => onClearClick(child.key)} - onOnlyClick={value => - onOnlyClick(child.key, value) - } - onExcludeClick={value => - onExcludeClick(child.key, value) - } - valuePins={{ - onPinClick: value => - onPinClick(child.key, value), - isPinned: value => isPinned(child.key, value), - onSharedPinClick: onSharedPinClick - ? value => onSharedPinClick(child.key, value) - : undefined, - isSharedPinned: isSharedPinned - ? value => isSharedPinned(child.key, value) - : undefined, - }} - fieldPins={{ - onFieldPinClick: () => - onFieldPinClick?.(child.key), - isFieldPinned: isFieldPinned?.(child.key), - onToggleSharedFieldPin: () => - onToggleSharedFieldPin?.(child.key), - isSharedFieldPinned: isSharedFieldPinned?.( - child.key, - ), - }} - onColumnToggle={ - onColumnToggle - ? () => onColumnToggle(child.key) - : undefined - } - isColumnDisplayed={displayedColumns?.includes( + fieldPins={{ + onFieldPinClick: () => + onFieldPinClick?.(child.key), + isFieldPinned: isFieldPinned?.(child.key), + onToggleSharedFieldPin: () => + onToggleSharedFieldPin?.(child.key), + isSharedFieldPinned: isSharedFieldPinned?.( child.key, - )} - onLoadMore={() => onLoadMore(child.key)} - loadMoreLoading={ - loadMoreLoading[child.key] || false - } - hasLoadedMore={hasLoadedMore[child.key] || false} - showFilterCounts={showFilterCounts} - isDefaultExpanded={childHasSelections} - chartConfig={chartConfig} - isLive={isLive} - /> -
- ); - })} -
+ ), + }} + 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} + showFilterCounts={showFilterCounts} + isDefaultExpanded={childHasSelections} + chartConfig={chartConfig} + isLive={isLive} + /> +
+ ); + })}
- )} -
- )} +
+ )} + diff --git a/packages/app/src/components/__tests__/DBSearchPageFilters.test.tsx b/packages/app/src/components/__tests__/DBSearchPageFilters.test.tsx index f1c54849b9..a275dbcec0 100644 --- a/packages/app/src/components/__tests__/DBSearchPageFilters.test.tsx +++ b/packages/app/src/components/__tests__/DBSearchPageFilters.test.tsx @@ -691,4 +691,27 @@ describe('NestedFilterGroup', () => { expect(screen.queryAllByTestId(/nested-filter-group-attr/)).toHaveLength(0); }); + + it('should allow reopening after close (panel stays accessible)', async () => { + renderWithMantine( + , + ); + + // Initially closed + const panel = screen.getByTestId('nested-filter-group-panel'); + expect(panel).toHaveAttribute('aria-hidden', 'true'); + + // Open the group + const control = screen.getByTestId('nested-filter-group-control'); + await userEvent.click(control); + expect(panel).toHaveAttribute('aria-hidden', 'false'); + + // Close the group + await userEvent.click(control); + expect(panel).toHaveAttribute('aria-hidden', 'true'); + + // Reopen the group — should not be stuck closed + await userEvent.click(control); + expect(panel).toHaveAttribute('aria-hidden', 'false'); + }); });