This is an automated email from the ASF dual-hosted git repository. vavila pushed a commit to branch fix/slack-search in repository https://gitbox.apache.org/repos/asf/superset.git
commit 1afdcf07acdded0419756b3f80aaddb144298386 Author: Vitor Avila <vitor.av...@preset.io> AuthorDate: Mon Aug 11 11:44:53 2025 -0300 fix: Slack channels and Color Palettes search --- .../src/components/Select/Select.test.tsx | 113 +++++++++++++++++++++ .../src/components/Select/Select.tsx | 24 ++++- 2 files changed, 134 insertions(+), 3 deletions(-) diff --git a/superset-frontend/packages/superset-ui-core/src/components/Select/Select.test.tsx b/superset-frontend/packages/superset-ui-core/src/components/Select/Select.test.tsx index b6c2af4a81..93608a5031 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/Select/Select.test.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/Select/Select.test.tsx @@ -1047,6 +1047,119 @@ test('typing and deleting the last character for a new option displays correctly jest.useRealTimers(); }); +describe('grouped options search', () => { + const GROUPED_OPTIONS = [ + { + label: 'Male', + options: OPTIONS.filter(option => option.gender === 'Male'), + }, + { + label: 'Female', + options: OPTIONS.filter(option => option.gender === 'Female'), + }, + ]; + + it('searches within grouped options and shows matching groups', async () => { + render(<Select {...defaultProps} options={GROUPED_OPTIONS} />); + await open(); + + await type('John'); + + expect(await findSelectOption('John')).toBeInTheDocument(); + expect(await findSelectOption('Johnny')).toBeInTheDocument(); + expect(screen.queryByText('Female')).not.toBeInTheDocument(); + expect(screen.queryByText('Olivia')).not.toBeInTheDocument(); + expect(screen.getByText('Male')).toBeInTheDocument(); + expect(screen.queryByText('Female')).not.toBeInTheDocument(); + }); + + it('shows multiple groups when search matches both', async () => { + render(<Select {...defaultProps} options={GROUPED_OPTIONS} />); + await open(); + + await type('er'); + + expect(screen.getByText('Male')).toBeInTheDocument(); + expect(screen.getByText('Female')).toBeInTheDocument(); + expect(await findSelectOption('Oliver')).toBeInTheDocument(); + expect(await findSelectOption('Cher')).toBeInTheDocument(); + expect(await findSelectOption('Her')).toBeInTheDocument(); + }); + + it('handles case-insensitive search in grouped options', async () => { + render(<Select {...defaultProps} options={GROUPED_OPTIONS} />); + await open(); + + await type('EMMA'); + + expect(await findSelectOption('Emma')).toBeInTheDocument(); + expect(screen.getByText('Female')).toBeInTheDocument(); + expect(screen.queryByText('Male')).not.toBeInTheDocument(); + }); + + it('shows no options when search matches nothing in any group', async () => { + render(<Select {...defaultProps} options={GROUPED_OPTIONS} />); + await open(); + + await type('xyz123'); + + expect(screen.queryByText('Male')).not.toBeInTheDocument(); + expect(screen.queryByText('Female')).not.toBeInTheDocument(); + expect( + screen.getByText(NO_DATA, { selector: '.ant-empty-description' }), + ).toBeInTheDocument(); + }); + + it('works in multiple selection mode with grouped options', async () => { + render( + <Select {...defaultProps} options={GROUPED_OPTIONS} mode="multiple" />, + ); + await open(); + + await type('John'); + + await userEvent.click(await findSelectOption('John')); + + // Clear search and search for female name + await clearTypedText(); + await type('Emma'); + await userEvent.click(await findSelectOption('Emma')); + + // Both should be selected + const values = await findAllSelectValues(); + expect(values).toHaveLength(2); + expect(values[0]).toHaveTextContent('John'); + expect(values[1]).toHaveTextContent('Emma'); + }); + + it('preserves group structure when not searching', async () => { + render(<Select {...defaultProps} options={GROUPED_OPTIONS} />); + await open(); + + expect(screen.getByText('Male')).toBeInTheDocument(); + expect(screen.getByText('Female')).toBeInTheDocument(); + expect(await findSelectOption('John')).toBeInTheDocument(); + expect(await findSelectOption('Emma')).toBeInTheDocument(); + }); + + it('handles empty groups gracefully', async () => { + const optionsWithEmptyGroup = [ + ...GROUPED_OPTIONS, + { + label: 'Empty Group', + options: [], + }, + ]; + + render(<Select {...defaultProps} options={optionsWithEmptyGroup} />); + await open(); + + await type('John'); + expect(await findSelectOption('John')).toBeInTheDocument(); + expect(screen.queryByText('Empty Group')).not.toBeInTheDocument(); + }); +}); + /* TODO: Add tests that require scroll interaction. Needs further investigation. - Fetches more data when scrolling and more data is available diff --git a/superset-frontend/packages/superset-ui-core/src/components/Select/Select.tsx b/superset-frontend/packages/superset-ui-core/src/components/Select/Select.tsx index 48cb2cfe17..adfa1f7bcb 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/Select/Select.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/Select/Select.tsx @@ -373,9 +373,27 @@ const Select = forwardRef( setSelectOptions(updatedOptions); } - const filteredOptions = updatedOptions.filter( - (option: AntdLabeledValue) => handleFilterOption(search, option), - ); + const filteredOptions = updatedOptions + .map((option: any) => { + /* + If it's a group, filter its nested options and only return it + if it has matching options + */ + if ('options' in option && Array.isArray(option.options)) { + const filteredGroupOptions = option.options.filter( + (subOption: AntdLabeledValue) => + handleFilterOption(search, subOption), + ); + return filteredGroupOptions.length > 0 + ? { ...option, options: filteredGroupOptions } + : null; + } + + return handleFilterOption(search, option as AntdLabeledValue) + ? option + : null; + }) + .filter((option): option is AntdLabeledValue => option !== null); setVisibleOptions(filteredOptions); setInputValue(searchValue);