mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-02 11:39:12 +00:00
fix(ui): soft deleted assets visible in search suggestions (#22887)
* fix soft deleted assets in search suggestions * add e2e test
This commit is contained in:
parent
6c948e68bc
commit
bf87f1267c
@ -19,13 +19,16 @@ import { getJsonTreeObject } from '../../utils/exploreDiscovery';
|
||||
test.use({ storageState: 'playwright/.auth/admin.json' });
|
||||
|
||||
const table = new TableClass();
|
||||
const table1 = new TableClass();
|
||||
|
||||
test.describe('Explore Assets Discovery', () => {
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
const { apiContext, afterAction } = await createNewPage(browser);
|
||||
|
||||
await table.create(apiContext);
|
||||
await table1.create(apiContext);
|
||||
await table.delete(apiContext, false);
|
||||
// await table1.delete(apiContext, false);
|
||||
|
||||
await afterAction();
|
||||
});
|
||||
@ -34,6 +37,7 @@ test.describe('Explore Assets Discovery', () => {
|
||||
const { apiContext, afterAction } = await createNewPage(browser);
|
||||
|
||||
await table.delete(apiContext);
|
||||
await table1.delete(apiContext);
|
||||
|
||||
await afterAction();
|
||||
});
|
||||
@ -183,4 +187,46 @@ test.describe('Explore Assets Discovery', () => {
|
||||
)
|
||||
).not.toBeAttached();
|
||||
});
|
||||
|
||||
test('Should not display soft deleted assets in search suggestions', async ({
|
||||
page,
|
||||
}) => {
|
||||
await table1.visitEntityPage(page);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.getByTestId('manage-button').click();
|
||||
await page.getByTestId('delete-button').click();
|
||||
|
||||
await expect(
|
||||
page
|
||||
.locator('.ant-modal-title')
|
||||
.getByText(
|
||||
`Delete table "${
|
||||
table1.entityResponseData.displayName ??
|
||||
table1.entityResponseData.name
|
||||
}"`
|
||||
)
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByTestId('confirmation-text-input').click();
|
||||
await page.getByTestId('confirmation-text-input').fill('DELETE');
|
||||
|
||||
await expect(page.getByTestId('confirm-button')).toBeEnabled();
|
||||
|
||||
await page.getByTestId('confirm-button').click();
|
||||
|
||||
await page.reload();
|
||||
|
||||
await expect(page.getByTestId('deleted-badge')).toBeVisible();
|
||||
|
||||
await redirectToHomePage(page);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.getByTestId('searchBox').click();
|
||||
await page.getByTestId('searchBox').fill(table1.entityResponseData.name);
|
||||
|
||||
expect(
|
||||
page.locator('.ant-popover-inner-content').textContent()
|
||||
).not.toContain(table1.entityResponseData.name);
|
||||
});
|
||||
});
|
||||
|
||||
@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright 2025 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/*
|
||||
* Copyright 2022 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTourProvider } from '../../context/TourProvider/TourProvider';
|
||||
import { SearchIndex } from '../../enums/search.enum';
|
||||
import { searchQuery } from '../../rest/searchAPI';
|
||||
import Suggestions from './Suggestions';
|
||||
|
||||
// Mock dependencies
|
||||
jest.mock('../../rest/searchAPI');
|
||||
jest.mock('react-i18next', () => ({
|
||||
useTranslation: jest.fn(),
|
||||
}));
|
||||
jest.mock('../../context/TourProvider/TourProvider');
|
||||
jest.mock('../../utils/SearchUtils', () => ({
|
||||
filterOptionsByIndex: jest.fn((options, index) => {
|
||||
return options.filter((option: any) => option._index === index);
|
||||
}),
|
||||
getGroupLabel: jest.fn((index) => `Group ${index}`),
|
||||
getSuggestionElement: jest.fn((suggestion) => (
|
||||
<div data-testid={`suggestion-${suggestion._id}`} key={suggestion._id}>
|
||||
{suggestion._source.name}
|
||||
</div>
|
||||
)),
|
||||
}));
|
||||
jest.mock('../../utils/SearchClassBase', () => ({
|
||||
getEntitiesSuggestions: jest.fn(() => []),
|
||||
}));
|
||||
jest.mock('../../utils/CommonUtils', () => ({
|
||||
Transi18next: ({ i18nKey, values }: { i18nKey: string; values: any }) => (
|
||||
<span data-testid="transi18next">
|
||||
{i18nKey} {values?.keyword || ''}
|
||||
</span>
|
||||
),
|
||||
}));
|
||||
|
||||
// Mock location.search for the component
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: {
|
||||
search: '',
|
||||
},
|
||||
writable: true,
|
||||
});
|
||||
|
||||
const mockSearchQuery = searchQuery as jest.MockedFunction<typeof searchQuery>;
|
||||
const mockUseTranslation = useTranslation as jest.MockedFunction<
|
||||
typeof useTranslation
|
||||
>;
|
||||
const mockUseTourProvider = useTourProvider as jest.MockedFunction<
|
||||
typeof useTourProvider
|
||||
>;
|
||||
|
||||
const defaultProps = {
|
||||
searchText: 'test',
|
||||
setIsOpen: jest.fn(),
|
||||
isOpen: true,
|
||||
searchCriteria: SearchIndex.TABLE,
|
||||
isNLPActive: false,
|
||||
onSearchTextUpdate: jest.fn(),
|
||||
};
|
||||
|
||||
describe('Suggestions Component', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockUseTranslation.mockReturnValue({
|
||||
t: jest.fn((key: string) => key),
|
||||
i18n: { language: 'en' },
|
||||
} as any);
|
||||
mockUseTourProvider.mockReturnValue({
|
||||
isTourOpen: false,
|
||||
updateTourPage: jest.fn(),
|
||||
updateTourSearch: jest.fn(),
|
||||
} as any);
|
||||
});
|
||||
|
||||
describe('AI Query Suggestions', () => {
|
||||
it('should render all AI query suggestions', () => {
|
||||
render(<Suggestions {...defaultProps} isNLPActive searchText="" />);
|
||||
|
||||
const aiQueries = [
|
||||
'Tables owned by marketing',
|
||||
'Tables with Tier1 classification',
|
||||
'Find dashboards tagged with PII.Sensitive',
|
||||
'Topics with schema fields containing address',
|
||||
'Tables tagged with tier1 or tier2',
|
||||
];
|
||||
|
||||
aiQueries.forEach((query) => {
|
||||
expect(screen.getByText(query)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should call onSearchTextUpdate when AI query button is clicked', () => {
|
||||
const mockOnSearchTextUpdate = jest.fn();
|
||||
|
||||
render(
|
||||
<Suggestions
|
||||
{...defaultProps}
|
||||
isNLPActive
|
||||
searchText=""
|
||||
onSearchTextUpdate={mockOnSearchTextUpdate}
|
||||
/>
|
||||
);
|
||||
|
||||
const firstQueryButton = screen.getByText('Tables owned by marketing');
|
||||
fireEvent.click(firstQueryButton);
|
||||
|
||||
expect(mockOnSearchTextUpdate).toHaveBeenCalledWith(
|
||||
'Tables owned by marketing'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Props Handling', () => {
|
||||
it('should handle empty searchText', () => {
|
||||
render(<Suggestions {...defaultProps} searchText="" />);
|
||||
|
||||
expect(mockSearchQuery).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle isNLPActive prop correctly', () => {
|
||||
render(<Suggestions {...defaultProps} isNLPActive />);
|
||||
|
||||
expect(mockSearchQuery).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Component Behavior', () => {
|
||||
it('should show no results message when searchText is provided but no results', () => {
|
||||
render(<Suggestions {...defaultProps} />);
|
||||
|
||||
// The component should show the no results message
|
||||
expect(screen.getByTestId('transi18next')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not call searchQuery when tour is open', () => {
|
||||
mockUseTourProvider.mockReturnValue({
|
||||
isTourOpen: true,
|
||||
updateTourPage: jest.fn(),
|
||||
updateTourSearch: jest.fn(),
|
||||
} as any);
|
||||
|
||||
render(<Suggestions {...defaultProps} />);
|
||||
|
||||
expect(mockSearchQuery).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -292,6 +292,7 @@ const Suggestions = ({
|
||||
searchIndex: searchCriteria ?? SearchIndex.DATA_ASSET,
|
||||
queryFilter: quickFilter,
|
||||
pageSize: PAGE_SIZE_BASE,
|
||||
includeDeleted: false,
|
||||
});
|
||||
|
||||
setOptions(res.hits.hits as unknown as Option[]);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user