diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ExploreQuickFilters.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ExploreQuickFilters.spec.ts index ce56342bea9e..e73a7cb9b7db 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ExploreQuickFilters.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ExploreQuickFilters.spec.ts @@ -329,6 +329,93 @@ test.describe('Tier filter - aggregation-based options', () => { }); }); +test.describe('Filter persistence after bug fixes', () => { + test('explore tree sidebar selection is not cleared when a top dropdown filter is applied', async ({ + page, + }) => { + test.slow(); + + await test.step('Click on Databases in the explore tree to select it', async () => { + const treeSearchRes = page.waitForResponse( + (resp) => + resp.url().includes('/api/v1/search/query') && + resp.url().includes('index=dataAsset') + ); + await page.getByTestId('explore-tree-title-Databases').click(); + await treeSearchRes; + await waitForAllLoadersToDisappear(page); + }); + + await test.step('Verify the Databases node is marked as selected', async () => { + await expect(page.locator('.ant-tree-node-selected')).toBeVisible(); + }); + + await test.step('Apply Tag filter from top dropdown', async () => { + await page.getByTestId('search-dropdown-Tag').click(); + await searchAndClickOnOption( + page, + { key: 'tags.tagFQN', label: 'Tag', value: 'PersonalData.Personal' }, + true + ); + const queryRes = page.waitForResponse( + '/api/v1/search/query?*index=dataAsset*' + ); + await page.getByTestId('update-btn').click(); + await queryRes; + await waitForAllLoadersToDisappear(page); + }); + + await test.step('Verify Databases node selection is still preserved after filter change', async () => { + await expect(page.locator('.ant-tree-node-selected')).toBeVisible(); + }); + }); + + test('sort order is preserved in URL when explore tree node is clicked after applying a top dropdown filter', async ({ + page, + }) => { + test.slow(); + + await test.step('Toggle sort order to ascending', async () => { + const sortRes = page.waitForResponse( + '/api/v1/search/query?*sort_order=asc*' + ); + await page.getByTestId('sort-order-button').click(); + await sortRes; + await waitForAllLoadersToDisappear(page); + }); + + await test.step('Apply Tag filter from top dropdown', async () => { + await page.getByTestId('search-dropdown-Tag').click(); + await searchAndClickOnOption( + page, + { key: 'tags.tagFQN', label: 'Tag', value: 'PersonalData.Personal' }, + true + ); + const queryRes = page.waitForResponse( + '/api/v1/search/query?*index=dataAsset*' + ); + await page.getByTestId('update-btn').click(); + await queryRes; + await waitForAllLoadersToDisappear(page); + }); + + await test.step('Click on Databases in the explore tree', async () => { + const treeSearchRes = page.waitForResponse( + (resp) => + resp.url().includes('/api/v1/search/query') && + resp.url().includes('index=dataAsset') + ); + await page.getByTestId('explore-tree-title-Databases').click(); + await treeSearchRes; + await waitForAllLoadersToDisappear(page); + }); + + await test.step('Verify sort order is preserved in the URL after tree node click', async () => { + await expect(page).toHaveURL(/sortOrder=asc/); + }); + }); +}); + test.describe('Metric search result highlight', () => { const metric = new MetricClass(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreV1.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreV1.component.tsx index 022cbcbea131..9b7d64f08b27 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreV1.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreV1.component.tsx @@ -341,21 +341,24 @@ const ExploreV1: React.FC = ({ onResetAllFilters(); }; - const handleQuickFiltersChange = (data: ExploreQuickFilterField[]) => { - const must = getExploreQueryFilterMust(data); - - onChangeAdvancedSearchQuickFilters( - isEmpty(must) - ? undefined - : { - query: { - bool: { - must, + const handleQuickFiltersChange = useCallback( + (data: ExploreQuickFilterField[]) => { + const must = getExploreQueryFilterMust(data); + + onChangeAdvancedSearchQuickFilters( + isEmpty(must) + ? undefined + : { + query: { + bool: { + must, + }, }, - }, - } - ); - }; + } + ); + }, + [onChangeAdvancedSearchQuickFilters] + ); const handleQuickFiltersValueSelect = (field: ExploreQuickFilterField) => { setSelectedQuickFilters((pre) => { @@ -405,7 +408,14 @@ const ExploreV1: React.FC = ({ } return ; - }, [searchQueryParam, tabItems]); + }, [ + searchQueryParam, + tabItems, + handleQuickFiltersChange, + activeTabKey, + loading, + onChangeSearchIndex, + ]); useEffect(() => { const escapeKeyHandler = (e: KeyboardEvent) => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreV1.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreV1.test.tsx index 0cec3ff660f9..529fc32965e8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreV1.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreV1.test.tsx @@ -28,6 +28,7 @@ import { MOCK_EXPLORE_TAB_ITEMS, } from '../Explore/Explore.mock'; import { ExploreSearchIndex } from '../Explore/ExplorePage.interface'; +import ExploreTree from '../Explore/ExploreTree/ExploreTree'; import ExploreV1 from './ExploreV1.component'; jest.mock('@openmetadata/ui-core-components', () => { @@ -522,6 +523,28 @@ describe('ExploreV1', () => { await waitFor(() => expect(exportButton).toBeEnabled()); }); + it('passes updated onFieldValueSelect to ExploreTree when onChangeAdvancedSearchQuickFilters prop changes', () => { + const callbackV1 = jest.fn(); + const callbackV2 = jest.fn(); + const ExploreTreeMock = ExploreTree as jest.Mock; + + const { rerender } = render( + , + { wrapper: Wrapper } + ); + + rerender( + + ); + + const lastCallProps = + ExploreTreeMock.mock.calls[ExploreTreeMock.mock.calls.length - 1][0]; + lastCallProps.onFieldValueSelect([]); + + expect(callbackV2).toHaveBeenCalledTimes(1); + expect(callbackV1).not.toHaveBeenCalled(); + }); + it('disables export and shows alert when all matching assets exceed export limit', async () => { (searchQuery as jest.Mock).mockResolvedValueOnce({ hits: { total: { value: 200001 }, hits: [] }, diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/ExplorePage/ExplorePageV1.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/ExplorePage/ExplorePageV1.component.tsx index 412232e88498..5003c570c533 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/ExplorePage/ExplorePageV1.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/ExplorePage/ExplorePageV1.component.tsx @@ -373,7 +373,6 @@ const ExplorePageV1: FC = () => { const handleAdvanceSearchQuickFiltersChange = useCallback( (filter?: QueryFilterInterface) => { - handlePageChange(1); setAdvancedSearchQuickFilters(filter); handleQuickFilterChange(filter); }, diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/ExplorePage/ExplorePageV1.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/ExplorePage/ExplorePageV1.test.tsx index f5f3ff54848f..6d92600e6e7b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/ExplorePage/ExplorePageV1.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/ExplorePage/ExplorePageV1.test.tsx @@ -10,8 +10,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { render, screen } from '@testing-library/react'; +import { act, render, screen } from '@testing-library/react'; import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import ExploreV1 from '../../components/ExploreV1/ExploreV1.component'; import ExplorePageV1 from './ExplorePageV1.component'; jest.mock( @@ -67,4 +69,44 @@ describe('ExplorePageV1', () => { expect(await screen.findByText('ExploreV1')).toBeInTheDocument(); }); + + it('calls navigate exactly once with quickFilter when filter changes', async () => { + const mockNavigate = jest.fn(); + (useNavigate as jest.Mock).mockReturnValue(mockNavigate); + + let capturedCallback: + | ((filter?: Record) => void) + | undefined; + (ExploreV1 as jest.Mock).mockImplementationOnce( + ({ + onChangeAdvancedSearchQuickFilters, + }: { + onChangeAdvancedSearchQuickFilters?: ( + filter?: Record + ) => void; + }) => { + capturedCallback = onChangeAdvancedSearchQuickFilters; + + return

ExploreV1

; + } + ); + + render(); + await screen.findByText('ExploreV1'); + + const testFilter = { + query: { + bool: { + must: [{ bool: { should: [{ term: { entityType: 'table' } }] } }], + }, + }, + }; + + act(() => { + capturedCallback!(testFilter); + }); + + expect(mockNavigate).toHaveBeenCalledTimes(1); + expect(mockNavigate.mock.calls[0][0].search).toContain('quickFilter'); + }); });