Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,21 +341,24 @@ const ExploreV1: React.FC<ExploreProps> = ({
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) => {
Expand Down Expand Up @@ -405,7 +408,14 @@ const ExploreV1: React.FC<ExploreProps> = ({
}

return <ExploreTree onFieldValueSelect={handleQuickFiltersChange} />;
}, [searchQueryParam, tabItems]);
}, [
searchQueryParam,
tabItems,
handleQuickFiltersChange,
activeTabKey,
loading,
onChangeSearchIndex,
]);

useEffect(() => {
const escapeKeyHandler = (e: KeyboardEvent) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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(
<ExploreV1 {...props} onChangeAdvancedSearchQuickFilters={callbackV1} />,
{ wrapper: Wrapper }
);

rerender(
<ExploreV1 {...props} onChangeAdvancedSearchQuickFilters={callbackV2} />
);

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: [] },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,6 @@ const ExplorePageV1: FC<unknown> = () => {

const handleAdvanceSearchQuickFiltersChange = useCallback(
(filter?: QueryFilterInterface) => {
handlePageChange(1);
Comment thread
chirag-madlani marked this conversation as resolved.
setAdvancedSearchQuickFilters(filter);
handleQuickFilterChange(filter);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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<string, unknown>) => void)
| undefined;
(ExploreV1 as jest.Mock).mockImplementationOnce(
({
onChangeAdvancedSearchQuickFilters,
}: {
onChangeAdvancedSearchQuickFilters?: (
filter?: Record<string, unknown>
) => void;
}) => {
capturedCallback = onChangeAdvancedSearchQuickFilters;

return <p>ExploreV1</p>;
}
);

render(<ExplorePageV1 {...mockProps} />);
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');
});
});
Loading