Skip to content

Commit 0efa026

Browse files
harsh-vadorclaude
andcommitted
feat(ui): render Explore quick filters through FilterSelect
Replace QuickFilterDropdown with the FilterSelect primitive now in @openmetadata/ui-core-components, and move the shared quick-filter types below the component layer so utils no longer import from components. - ExploreQuickFilters renders FilterSelect (button trigger, immediate or staged by immediateApply), debouncing search at 500ms and cancelling the pending call on close - service-type, entity-type and domain options carry their icons - ExploreV1's toolbar gives the filters a zero-basis flex column and the sort controls a content-sized one, so the row does not wrap - QuickFilterDropdown and its interface are deleted; the types live in src/interface/quickFilter.interface.ts and src/enums/Assets.enum.ts Regenerated the playwright impact map for the moved files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 5e6b35c commit 0efa026

14 files changed

Lines changed: 381 additions & 885 deletions

File tree

.github/playwright/impact-map.generated.json

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6379,30 +6379,6 @@
63796379
"playwright/e2e/Pages/ExploreResilience.spec.ts"
63806380
]
63816381
},
6382-
{
6383-
"sources": [
6384-
"openmetadata-ui/src/main/resources/ui/src/components/Explore/QuickFilterDropdown.tsx"
6385-
],
6386-
"specs": [
6387-
"playwright/e2e/Features/ColumnBulkOperations.spec.ts",
6388-
"playwright/e2e/Features/DataQuality/CertificationFilter.spec.ts",
6389-
"playwright/e2e/Features/DataQuality/DataObservabilityGovernanceTab.spec.ts",
6390-
"playwright/e2e/Features/DataQuality/DataQualityDashboard.spec.ts",
6391-
"playwright/e2e/Features/DataQuality/TestDefinitionFilters.spec.ts",
6392-
"playwright/e2e/Features/DomainFilterQueryFilter.spec.ts",
6393-
"playwright/e2e/Features/ExploreFilterComposition.spec.ts",
6394-
"playwright/e2e/Features/ExploreQuickFilters.spec.ts",
6395-
"playwright/e2e/Features/ExploreSortOrderFilter.spec.ts",
6396-
"playwright/e2e/Features/ExploreUrlState.spec.ts",
6397-
"playwright/e2e/Features/ImpactAnalysis.spec.ts",
6398-
"playwright/e2e/Flow/ExploreDiscovery.spec.ts",
6399-
"playwright/e2e/Pages/AuditLogs.spec.ts",
6400-
"playwright/e2e/Pages/DataProductCertificationFilter.spec.ts",
6401-
"playwright/e2e/Pages/ExploreBrowse.spec.ts",
6402-
"playwright/e2e/Pages/LearningResources.spec.ts",
6403-
"playwright/e2e/Pages/Lineage/LineageFilters.spec.ts"
6404-
]
6405-
},
64066382
{
64076383
"sources": [
64086384
"openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreSearchCard/ExploreSearchCard.tsx"

openmetadata-ui/src/main/resources/ui/src/components/Explore/ExplorePage.interface.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ import { TestCase } from '../../generated/tests/testCase';
4949
import { TestSuite } from '../../generated/tests/testSuite';
5050
import { Aggregations, SearchResponse } from '../../interface/search.interface';
5151
import { QueryFilterInterface } from '../../pages/ExplorePage/ExplorePage.interface';
52-
import { SearchDropdownOption } from '../SearchDropdown/SearchDropdown.interface';
5352
import { SearchedDataProps } from '../SearchedData/SearchedData.interface';
5453

5554
export type UrlParams = {
@@ -130,20 +129,9 @@ export interface ExploreProps {
130129
}) => void;
131130
}
132131

133-
export interface ExploreQuickFilterField {
134-
key: string;
135-
label: string;
136-
labelKeyOptions?: Record<string, string | number | boolean>;
137-
options?: SearchDropdownOption[];
138-
value?: SearchDropdownOption[];
139-
hideCounts?: boolean;
140-
hideSearchBar?: boolean;
141-
searchIndex?: SearchIndex;
142-
searchKey?: string;
143-
dropdownClassName?: string;
144-
singleSelect?: boolean;
145-
sourceFields?: string;
146-
}
132+
import type { ExploreQuickFilterField } from '../../interface/quickFilter.interface';
133+
134+
export type { ExploreQuickFilterField };
147135

148136
// Type for all the explore tab entities
149137
export type EntityUnion =

openmetadata-ui/src/main/resources/ui/src/components/Explore/ExploreQuickFilters.test.tsx

Lines changed: 85 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
* limitations under the License.
1212
*/
1313

14+
import type { FilterSelectProps } from '@openmetadata/ui-core-components';
1415
import {
1516
act,
1617
fireEvent,
@@ -22,7 +23,6 @@ import userEvent from '@testing-library/user-event';
2223
import { EntityFields } from '../../enums/AdvancedSearch.enum';
2324
import { SearchIndex } from '../../enums/search.enum';
2425
import { getAggregationOptions } from '../../utils/ExploreUtils';
25-
import { SearchDropdownProps } from '../SearchDropdown/SearchDropdown.interface';
2626
import { ExploreQuickFilterField } from './ExplorePage.interface';
2727
import ExploreQuickFilters from './ExploreQuickFilters';
2828
import {
@@ -35,6 +35,18 @@ const mockQueryFilter = {};
3535
const mockUseAdvanceSearch = jest.fn();
3636
const mockUseSearchStore = jest.fn();
3737

38+
const mockDebounceCancel = jest.fn();
39+
40+
jest.mock('lodash', () => ({
41+
...jest.requireActual('lodash'),
42+
// The component debounces search; tests drive it synchronously and only
43+
// assert that closing a dropdown cancels a pending keystroke.
44+
debounce: (fn: (...args: unknown[]) => unknown) =>
45+
Object.assign((...args: unknown[]) => fn(...args), {
46+
cancel: mockDebounceCancel,
47+
}),
48+
}));
49+
3850
jest.mock('../../hooks/useCustomLocation/useCustomLocation', () => ({
3951
__esModule: true,
4052
default: () => mockUseCustomLocation(),
@@ -57,75 +69,67 @@ jest.mock('../../hooks/useSearchStore', () => ({
5769
const mockOnFieldValueSelect = jest.fn();
5870
const mockGetAggregationOptions = jest.fn();
5971

60-
jest.mock('../SearchDropdown/SearchDropdown', () => ({
61-
__esModule: true,
62-
default: ({
72+
jest.mock('@openmetadata/ui-core-components', () => ({
73+
FilterSelect: ({
74+
'data-testid': testId,
6375
options,
64-
searchKey,
65-
isSuggestionsLoading,
76+
selectedValues,
77+
selectionMode,
78+
nullOption,
79+
hideCounts,
80+
isLoading,
6681
onChange,
6782
onSearch,
68-
onGetInitialOptions,
69-
selectedKeys,
70-
hasNullOption,
71-
hideCounts,
72-
independent,
73-
showSelectedCounts,
74-
singleSelect,
75-
getPopupContainer,
76-
index: dropdownIndex,
77-
}: SearchDropdownProps) => (
78-
<div data-testid={`search-dropdown-${searchKey}`} title="search-dropdown">
79-
<span data-testid={`label-${searchKey}`}>{searchKey}</span>
80-
<span data-testid={`single-select-${searchKey}`}>
81-
{singleSelect ? 'true' : 'false'}
82-
</span>
83-
<span data-testid={`index-${searchKey}`}>{dropdownIndex}</span>
84-
<span data-testid={`has-null-option-${searchKey}`}>
85-
{hasNullOption ? 'true' : 'false'}
86-
</span>
87-
<span data-testid={`hide-counts-${searchKey}`}>
88-
{hideCounts ? 'true' : 'false'}
89-
</span>
90-
<span data-testid={`independent-${searchKey}`}>
91-
{independent ? 'true' : 'false'}
92-
</span>
93-
<span data-testid={`show-selected-counts-${searchKey}`}>
94-
{showSelectedCounts ? 'true' : 'false'}
95-
</span>
96-
<span data-testid={`popup-container-${searchKey}`}>
97-
{getPopupContainer ? 'true' : 'false'}
98-
</span>
99-
<span data-testid={`selected-count-${searchKey}`}>
100-
{selectedKeys?.length ?? 0}
101-
</span>
102-
<span data-testid={`suggestions-loading-${searchKey}`}>
103-
{isSuggestionsLoading ? 'true' : 'false'}
104-
</span>
105-
{options.map((option, index) => (
106-
<div data-testid={`option-${searchKey}-${index}`} key={option.key}>
107-
{option.label} - {option.count}
108-
</div>
109-
))}
110-
<button
111-
data-testid={`onGetInitialOptions-${searchKey}`}
112-
onClick={() => onGetInitialOptions?.(searchKey)}>
113-
Get Initial Options
114-
</button>
115-
<button
116-
data-testid={`onSearch-${searchKey}`}
117-
onClick={() => onSearch('test', searchKey)}>
118-
Search
119-
</button>
120-
<button
121-
data-testid={`onChange-${searchKey}`}
122-
onClick={() =>
123-
onChange([{ key: 'test-key', label: 'test-label' }], searchKey)
124-
}>
125-
Change
126-
</button>
127-
</div>
128-
),
83+
onOpenChange,
84+
}: FilterSelectProps) => {
85+
const searchKey = (testId ?? '').replace('search-dropdown-', '');
86+
87+
return (
88+
<div data-testid={testId} title="search-dropdown">
89+
<span data-testid={`label-${searchKey}`}>{searchKey}</span>
90+
<span data-testid={`single-select-${searchKey}`}>
91+
{selectionMode === 'single' ? 'true' : 'false'}
92+
</span>
93+
<span data-testid={`has-null-option-${searchKey}`}>
94+
{nullOption ? 'true' : 'false'}
95+
</span>
96+
<span data-testid={`hide-counts-${searchKey}`}>
97+
{hideCounts ? 'true' : 'false'}
98+
</span>
99+
<span data-testid={`selected-count-${searchKey}`}>
100+
{selectedValues?.length ?? 0}
101+
</span>
102+
<span data-testid={`suggestions-loading-${searchKey}`}>
103+
{isLoading ? 'true' : 'false'}
104+
</span>
105+
{options.map((option, index) => (
106+
<div data-testid={`option-${searchKey}-${index}`} key={option.value}>
107+
{option.label} - {option.count}
108+
</div>
109+
))}
110+
<button
111+
data-testid={`onGetInitialOptions-${searchKey}`}
112+
onClick={() => onOpenChange?.(true)}>
113+
Get Initial Options
114+
</button>
115+
<button
116+
data-testid={`onSearch-${searchKey}`}
117+
onClick={() => onSearch?.('test')}>
118+
Search
119+
</button>
120+
<button
121+
data-testid={`onClose-${searchKey}`}
122+
onClick={() => onOpenChange?.(false)}>
123+
Close
124+
</button>
125+
<button
126+
data-testid={`onChange-${searchKey}`}
127+
onClick={() => onChange(['test-key'])}>
128+
Change
129+
</button>
130+
</div>
131+
);
132+
},
129133
}));
130134

131135
jest.mock('../../utils/ExploreUtils', () => ({
@@ -210,36 +214,6 @@ describe('ExploreQuickFilters component', () => {
210214
});
211215

212216
describe('Props handling', () => {
213-
it('should pass independent prop to SearchDropdown', () => {
214-
render(<ExploreQuickFilters {...mockProps} independent />);
215-
216-
mockFields.forEach((field) => {
217-
expect(
218-
screen.getByTestId(`independent-${field.key}`)
219-
).toHaveTextContent('true');
220-
});
221-
});
222-
223-
it('should pass showSelectedCounts prop to SearchDropdown', () => {
224-
render(<ExploreQuickFilters {...mockProps} showSelectedCounts />);
225-
226-
mockFields.forEach((field) => {
227-
expect(
228-
screen.getByTestId(`show-selected-counts-${field.key}`)
229-
).toHaveTextContent('true');
230-
});
231-
});
232-
233-
it('should pass popup container override to SearchDropdown', () => {
234-
render(<ExploreQuickFilters {...mockProps} />);
235-
236-
mockFields.forEach((field) => {
237-
expect(
238-
screen.getByTestId(`popup-container-${field.key}`)
239-
).toHaveTextContent('true');
240-
});
241-
});
242-
243217
it('should pass hasNullOption for fields in fieldsWithNullValues', () => {
244218
const fieldsWithNullValues = ['owner.displayName' as EntityFields];
245219
render(
@@ -678,6 +652,18 @@ describe('ExploreQuickFilters component', () => {
678652
});
679653
});
680654

655+
describe('Debounce cancellation', () => {
656+
it('should cancel a pending debounced search when the dropdown closes', async () => {
657+
render(<ExploreQuickFilters {...mockProps} />);
658+
659+
await act(async () => {
660+
screen.getByTestId('onClose-database.name').click();
661+
});
662+
663+
expect(mockDebounceCancel).toHaveBeenCalled();
664+
});
665+
});
666+
681667
describe('onChange handling', () => {
682668
it('should call onFieldValueSelect when filter value changes', async () => {
683669
render(
@@ -696,7 +682,7 @@ describe('ExploreQuickFilters component', () => {
696682
expect(mockOnFieldValueSelect).toHaveBeenCalledWith({
697683
label: 'Database',
698684
key: 'database.name',
699-
value: [{ key: 'test-key', label: 'test-label' }],
685+
value: [{ key: 'test-key', label: 'test-key' }],
700686
});
701687
});
702688

@@ -711,7 +697,7 @@ describe('ExploreQuickFilters component', () => {
711697

712698
const updatedFields = mockFields.map((f) =>
713699
f.key === 'database.name'
714-
? { ...f, value: [{ key: 'test-key', label: 'test-label' }] }
700+
? { ...f, value: [{ key: 'test-key', label: 'test-key' }] }
715701
: f
716702
);
717703

@@ -1150,21 +1136,6 @@ describe('ExploreQuickFilters component', () => {
11501136
});
11511137
});
11521138

1153-
describe('Multi-index display', () => {
1154-
it('should pass first index as display index to SearchDropdown', () => {
1155-
const multiIndexProps = {
1156-
...mockProps,
1157-
index: [SearchIndex.TABLE, SearchIndex.TOPIC] as unknown as SearchIndex,
1158-
};
1159-
1160-
render(<ExploreQuickFilters {...multiIndexProps} />);
1161-
1162-
expect(screen.getByTestId('index-database.name')).toHaveTextContent(
1163-
SearchIndex.TABLE
1164-
);
1165-
});
1166-
});
1167-
11681139
describe('additionalActions', () => {
11691140
it('should render additionalActions content', () => {
11701141
render(

0 commit comments

Comments
 (0)