mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-25 09:28:23 +00:00
supported async delete functionality for service entities (#20275)
* supported async delete functionality for service entities * remove the multiple delete showing logic in ui and handle the recursive false case in case entity is not empty * added unit test for the provider * change playwright as per async delete * change the toast notification to success for the delete process being started * fix playwright test * fix playwright failure * fix the service ingestion playwright failure
This commit is contained in:
parent
20086f71ee
commit
b61c6d6269
@ -97,13 +97,13 @@ test.describe('API service', () => {
|
||||
await page.fill('[data-testid="confirmation-text-input"]', 'DELETE');
|
||||
|
||||
const deleteResponse = page.waitForResponse(
|
||||
'/api/v1/services/apiServices/*?hardDelete=true&recursive=true'
|
||||
'/api/v1/services/apiServices/async/*?hardDelete=true&recursive=true'
|
||||
);
|
||||
|
||||
await page.click('[data-testid="confirm-button"]');
|
||||
|
||||
await deleteResponse;
|
||||
|
||||
await toastNotification(page, /deleted successfully!/);
|
||||
await toastNotification(page, /Delete operation initiated for/);
|
||||
});
|
||||
});
|
||||
|
@ -248,13 +248,13 @@ test.describe('Entity Version pages', () => {
|
||||
|
||||
await page.fill('[data-testid="confirmation-text-input"]', 'DELETE');
|
||||
const deleteResponse = page.waitForResponse(
|
||||
`/api/v1/${entity.endpoint}/*?hardDelete=false&recursive=true`
|
||||
`/api/v1/${entity.endpoint}/async/*?hardDelete=false&recursive=true`
|
||||
);
|
||||
await page.click('[data-testid="confirm-button"]');
|
||||
|
||||
await deleteResponse;
|
||||
|
||||
await toastNotification(page, /deleted successfully!/);
|
||||
await toastNotification(page, /Delete operation initiated for/);
|
||||
|
||||
await page.reload();
|
||||
|
||||
|
@ -208,13 +208,13 @@ test.describe('Service Version pages', () => {
|
||||
|
||||
await page.fill('[data-testid="confirmation-text-input"]', 'DELETE');
|
||||
const deleteResponse = page.waitForResponse(
|
||||
`/api/v1/${entity.endpoint}/*?hardDelete=false&recursive=true`
|
||||
`/api/v1/${entity.endpoint}/async/*?hardDelete=false&recursive=true`
|
||||
);
|
||||
await page.click('[data-testid="confirm-button"]');
|
||||
|
||||
await deleteResponse;
|
||||
|
||||
await toastNotification(page, /deleted successfully!/);
|
||||
await toastNotification(page, /Delete operation initiated for/);
|
||||
|
||||
await page.reload();
|
||||
|
||||
|
@ -1221,13 +1221,13 @@ export const softDeleteEntity = async (
|
||||
|
||||
await page.fill('[data-testid="confirmation-text-input"]', 'DELETE');
|
||||
const deleteResponse = page.waitForResponse(
|
||||
`/api/v1/${endPoint}/*?hardDelete=false&recursive=true`
|
||||
`/api/v1/${endPoint}/async/*?hardDelete=false&recursive=true`
|
||||
);
|
||||
await page.click('[data-testid="confirm-button"]');
|
||||
|
||||
await deleteResponse;
|
||||
|
||||
await toastNotification(page, /deleted successfully!/);
|
||||
await toastNotification(page, /Delete operation initiated for/);
|
||||
|
||||
await page.reload();
|
||||
|
||||
@ -1287,12 +1287,12 @@ export const hardDeleteEntity = async (
|
||||
await page.check('[data-testid="hard-delete"]');
|
||||
await page.fill('[data-testid="confirmation-text-input"]', 'DELETE');
|
||||
const deleteResponse = page.waitForResponse(
|
||||
`/api/v1/${endPoint}/*?hardDelete=true&recursive=true`
|
||||
`/api/v1/${endPoint}/async/*?hardDelete=true&recursive=true`
|
||||
);
|
||||
await page.click('[data-testid="confirm-button"]');
|
||||
await deleteResponse;
|
||||
|
||||
await toastNotification(page, /deleted successfully!/);
|
||||
await toastNotification(page, /Delete operation initiated for/);
|
||||
};
|
||||
|
||||
export const checkDataAssetWidget = async (page: Page, serviceType: string) => {
|
||||
|
@ -111,7 +111,9 @@ export const deleteService = async (
|
||||
response
|
||||
.url()
|
||||
.includes(
|
||||
`/api/v1/services/${getServiceCategoryFromService(typeOfService)}`
|
||||
`/api/v1/services/${getServiceCategoryFromService(
|
||||
typeOfService
|
||||
)}s/async`
|
||||
)
|
||||
);
|
||||
|
||||
@ -120,7 +122,10 @@ export const deleteService = async (
|
||||
await deleteResponse;
|
||||
|
||||
// Closing the toast notification
|
||||
await toastNotification(page, `"${serviceName}" deleted successfully!`);
|
||||
await toastNotification(
|
||||
page,
|
||||
`Delete operation initiated for ${serviceName}`
|
||||
);
|
||||
|
||||
await page.waitForSelector(`[data-testid="service-name-${serviceName}"]`, {
|
||||
state: 'hidden',
|
||||
|
@ -23,6 +23,7 @@ import { EntityExportModalProvider } from './components/Entity/EntityExportModal
|
||||
import ApplicationsProvider from './components/Settings/Applications/ApplicationsProvider/ApplicationsProvider';
|
||||
import WebAnalyticsProvider from './components/WebAnalytics/WebAnalyticsProvider';
|
||||
import AntDConfigProvider from './context/AntDConfigProvider/AntDConfigProvider';
|
||||
import AsyncDeleteProvider from './context/AsyncDeleteProvider/AsyncDeleteProvider';
|
||||
import PermissionProvider from './context/PermissionProvider/PermissionProvider';
|
||||
import TourProvider from './context/TourProvider/TourProvider';
|
||||
import WebSocketProvider from './context/WebSocketProvider/WebSocketProvider';
|
||||
@ -83,9 +84,11 @@ const App: FC = () => {
|
||||
<PermissionProvider>
|
||||
<WebSocketProvider>
|
||||
<ApplicationsProvider>
|
||||
<EntityExportModalProvider>
|
||||
<AppRouter />
|
||||
</EntityExportModalProvider>
|
||||
<AsyncDeleteProvider>
|
||||
<EntityExportModalProvider>
|
||||
<AppRouter />
|
||||
</EntityExportModalProvider>
|
||||
</AsyncDeleteProvider>
|
||||
</ApplicationsProvider>
|
||||
</WebSocketProvider>
|
||||
</PermissionProvider>
|
||||
|
@ -536,6 +536,7 @@ export const DataAssetsHeader = ({
|
||||
</Tooltip>
|
||||
|
||||
<ManageButton
|
||||
isAsyncDelete
|
||||
afterDeleteAction={afterDeleteAction}
|
||||
allowSoftDelete={!dataAsset.deleted && allowSoftDelete}
|
||||
canDelete={permissions.Delete}
|
||||
|
@ -55,6 +55,8 @@ import {
|
||||
} from '../../constants/constants';
|
||||
import { GlobalSettingsMenuCategory } from '../../constants/GlobalSettings.constants';
|
||||
import { HELP_ITEMS_ENUM } from '../../constants/Navbar.constants';
|
||||
import { useAsyncDeleteProvider } from '../../context/AsyncDeleteProvider/AsyncDeleteProvider';
|
||||
import { AsyncDeleteWebsocketResponse } from '../../context/AsyncDeleteProvider/AsyncDeleteProvider.interface';
|
||||
import { useWebSocketConnector } from '../../context/WebSocketProvider/WebSocketProvider';
|
||||
import { EntityTabs, EntityType } from '../../enums/entity.enum';
|
||||
import { EntityReference } from '../../generated/entity/type';
|
||||
@ -119,6 +121,7 @@ const NavBar = ({
|
||||
handleClear,
|
||||
}: NavBarProps) => {
|
||||
const { onUpdateCSVExportJob } = useEntityExportModalProvider();
|
||||
const { handleDeleteEntityWebsocketResponse } = useAsyncDeleteProvider();
|
||||
const { searchCriteria, updateSearchCriteria } = useApplicationStore();
|
||||
const searchContainerRef = useRef<HTMLDivElement>(null);
|
||||
const Logo = useMemo(() => brandClassBase.getMonogram().src, []);
|
||||
@ -405,14 +408,23 @@ const NavBar = ({
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on(SOCKET_EVENTS.DELETE_ENTITY_CHANNEL, (deleteResponse) => {
|
||||
if (deleteResponse) {
|
||||
const deleteResponseData = JSON.parse(
|
||||
deleteResponse
|
||||
) as AsyncDeleteWebsocketResponse;
|
||||
handleDeleteEntityWebsocketResponse(deleteResponseData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
socket && socket.off(SOCKET_EVENTS.TASK_CHANNEL);
|
||||
socket && socket.off(SOCKET_EVENTS.MENTION_CHANNEL);
|
||||
socket && socket.off(SOCKET_EVENTS.CSV_EXPORT_CHANNEL);
|
||||
socket && socket.off(SOCKET_EVENTS.CSV_EXPORT_CHANNEL);
|
||||
socket && socket.off(SOCKET_EVENTS.BACKGROUND_JOB_CHANNEL);
|
||||
socket && socket.off(SOCKET_EVENTS.DELETE_ENTITY_CHANNEL);
|
||||
};
|
||||
}, [socket, onUpdateCSVExportJob]);
|
||||
|
||||
|
@ -106,6 +106,7 @@ const PageLayoutV1: FC<PageLayoutProp> = ({
|
||||
<AlertBar message={alert.message} type={alert.type} />
|
||||
</Col>
|
||||
)}
|
||||
|
||||
<Col className={`${alert && 'p-t-sm'}`} span={24}>
|
||||
{children}
|
||||
</Col>
|
||||
|
@ -32,6 +32,7 @@ export interface DeleteWidgetModalProps {
|
||||
entityType: EntityType;
|
||||
isAdminUser?: boolean;
|
||||
entityId?: string;
|
||||
isAsyncDelete?: boolean;
|
||||
prepareType?: boolean;
|
||||
isRecursiveDelete?: boolean;
|
||||
successMessage?: string;
|
||||
|
@ -31,6 +31,7 @@ import React, {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAsyncDeleteProvider } from '../../../context/AsyncDeleteProvider/AsyncDeleteProvider';
|
||||
import { EntityType } from '../../../enums/entity.enum';
|
||||
import { useApplicationStore } from '../../../hooks/useApplicationStore';
|
||||
import { deleteEntity } from '../../../rest/miscAPI';
|
||||
@ -57,6 +58,7 @@ const DeleteWidgetModal = ({
|
||||
onCancel,
|
||||
entityId,
|
||||
prepareType = true,
|
||||
isAsyncDelete = false,
|
||||
isRecursiveDelete,
|
||||
afterDeleteAction,
|
||||
successMessage,
|
||||
@ -67,6 +69,7 @@ const DeleteWidgetModal = ({
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const { currentUser, onLogoutHandler } = useApplicationStore();
|
||||
const { handleOnAsyncEntityDeleteConfirm } = useAsyncDeleteProvider();
|
||||
const [deleteConfirmationText, setDeleteConfirmationText] =
|
||||
useState<string>('');
|
||||
const [deletionType, setDeletionType] = useState<DeleteType>(
|
||||
@ -192,6 +195,36 @@ const DeleteWidgetModal = ({
|
||||
]
|
||||
);
|
||||
|
||||
const onFormFinish = useCallback(
|
||||
async (values: DeleteWidgetFormFields) => {
|
||||
if (isAsyncDelete) {
|
||||
setIsLoading(true);
|
||||
await handleOnAsyncEntityDeleteConfirm({
|
||||
entityName,
|
||||
entityId: entityId ?? '',
|
||||
entityType,
|
||||
deleteType: values.deleteType,
|
||||
prepareType,
|
||||
isRecursiveDelete: isRecursiveDelete ?? false,
|
||||
});
|
||||
setIsLoading(false);
|
||||
handleOnEntityDeleteCancel();
|
||||
} else {
|
||||
onDelete ? onDelete(values) : handleOnEntityDeleteConfirm(values);
|
||||
}
|
||||
},
|
||||
[
|
||||
entityId,
|
||||
onDelete,
|
||||
entityName,
|
||||
entityType,
|
||||
prepareType,
|
||||
isRecursiveDelete,
|
||||
handleOnEntityDeleteConfirm,
|
||||
handleOnEntityDeleteCancel,
|
||||
]
|
||||
);
|
||||
|
||||
const onChange = useCallback((e: RadioChangeEvent) => {
|
||||
const value = e.target.value;
|
||||
setDeletionType(value);
|
||||
@ -278,7 +311,7 @@ const DeleteWidgetModal = ({
|
||||
open={visible}
|
||||
title={`${t('label.delete')} ${entityType} "${entityName}"`}
|
||||
onCancel={handleOnEntityDeleteCancel}>
|
||||
<Form form={form} onFinish={onDelete ?? handleOnEntityDeleteConfirm}>
|
||||
<Form form={form} onFinish={onFormFinish}>
|
||||
<Form.Item<DeleteWidgetFormFields> className="m-0" name="deleteType">
|
||||
<Radio.Group onChange={onChange}>
|
||||
{(deleteOptions ?? DELETE_OPTION).map(
|
||||
|
@ -29,6 +29,7 @@ export interface ManageButtonProps {
|
||||
softDeleteMessagePostFix?: string;
|
||||
hardDeleteMessagePostFix?: string;
|
||||
canDelete?: boolean;
|
||||
isAsyncDelete?: boolean;
|
||||
extraDropdownContent?: ItemType[];
|
||||
onAnnouncementClick?: () => void;
|
||||
onRestoreEntity?: () => Promise<void>;
|
||||
|
@ -47,6 +47,7 @@ const ManageButton: FC<ManageButtonProps> = ({
|
||||
entityType,
|
||||
canDelete,
|
||||
entityId,
|
||||
isAsyncDelete = false,
|
||||
isRecursiveDelete,
|
||||
extraDropdownContent,
|
||||
onAnnouncementClick,
|
||||
@ -278,6 +279,7 @@ const ManageButton: FC<ManageButtonProps> = ({
|
||||
entityName={displayName ?? entityName}
|
||||
entityType={entityType}
|
||||
hardDeleteMessagePostFix={hardDeleteMessagePostFix}
|
||||
isAsyncDelete={isAsyncDelete}
|
||||
isRecursiveDelete={isRecursiveDelete}
|
||||
prepareType={prepareType}
|
||||
softDeleteMessagePostFix={softDeleteMessagePostFix}
|
||||
|
@ -299,6 +299,7 @@ export const SOCKET_EVENTS = {
|
||||
BULK_ASSETS_CHANNEL: 'bulkAssetsChannel',
|
||||
CSV_IMPORT_CHANNEL: 'csvImportChannel',
|
||||
BACKGROUND_JOB_CHANNEL: 'backgroundJobStatus',
|
||||
DELETE_ENTITY_CHANNEL: 'deleteEntityChannel',
|
||||
};
|
||||
|
||||
export const IN_PAGE_SEARCH_ROUTES: Record<string, Array<string>> = {
|
||||
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
import { ReactNode } from 'react';
|
||||
import { DeleteType } from '../../components/common/DeleteWidget/DeleteWidget.interface';
|
||||
|
||||
export interface AsyncDeleteProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export interface DeleteWidgetAsyncFormFields {
|
||||
entityName: string;
|
||||
entityId: string;
|
||||
entityType: string;
|
||||
deleteType: DeleteType;
|
||||
prepareType: boolean;
|
||||
isRecursiveDelete: boolean;
|
||||
}
|
||||
|
||||
export interface AsyncDeleteContextType {
|
||||
asyncDeleteJob?: Partial<AsyncDeleteJob>;
|
||||
handleOnAsyncEntityDeleteConfirm: ({
|
||||
deleteType,
|
||||
}: DeleteWidgetAsyncFormFields) => Promise<void>;
|
||||
handleDeleteEntityWebsocketResponse: (
|
||||
response: AsyncDeleteWebsocketResponse
|
||||
) => void;
|
||||
}
|
||||
|
||||
export type AsyncDeleteResponse = {
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type AsyncDeleteWebsocketResponse = {
|
||||
jobId: string;
|
||||
status: 'COMPLETED' | 'FAILED';
|
||||
entityName: string;
|
||||
error: string | null;
|
||||
};
|
||||
|
||||
export type AsyncDeleteJob = {
|
||||
hardDelete: boolean;
|
||||
recursive: boolean;
|
||||
} & Partial<AsyncDeleteWebsocketResponse> &
|
||||
AsyncDeleteResponse;
|
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
import { act } from '@testing-library/react';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import React from 'react';
|
||||
import { DeleteType } from '../../components/common/DeleteWidget/DeleteWidget.interface';
|
||||
import { deleteAsyncEntity } from '../../rest/miscAPI';
|
||||
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
|
||||
import AsyncDeleteProvider, {
|
||||
useAsyncDeleteProvider,
|
||||
} from './AsyncDeleteProvider';
|
||||
import { AsyncDeleteWebsocketResponse } from './AsyncDeleteProvider.interface';
|
||||
|
||||
jest.mock('../../utils/ToastUtils', () => ({
|
||||
showErrorToast: jest.fn(),
|
||||
showSuccessToast: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../rest/miscAPI', () => ({
|
||||
deleteAsyncEntity: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||
}));
|
||||
|
||||
describe('AsyncDeleteProvider', () => {
|
||||
const mockResponse = {
|
||||
entityName: 'DELETE',
|
||||
hardDelete: false,
|
||||
jobId: 'efc87367-01bd-4f9d-8d78-fea93bcb412f',
|
||||
message: 'Delete operation initiated for DELETE',
|
||||
recursive: true,
|
||||
};
|
||||
|
||||
const mockDeleteParams = {
|
||||
entityName: 'TestEntity',
|
||||
entityId: 'test-id',
|
||||
entityType: 'test-type',
|
||||
deleteType: DeleteType.SOFT_DELETE,
|
||||
prepareType: false,
|
||||
isRecursiveDelete: false,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<AsyncDeleteProvider>{children}</AsyncDeleteProvider>
|
||||
);
|
||||
|
||||
it('should initialize with empty asyncDeleteJob', () => {
|
||||
const { result } = renderHook(() => useAsyncDeleteProvider(), { wrapper });
|
||||
|
||||
expect(result.current.asyncDeleteJob).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle successful entity deletion', async () => {
|
||||
(deleteAsyncEntity as jest.Mock).mockResolvedValueOnce(mockResponse);
|
||||
|
||||
const { result } = renderHook(() => useAsyncDeleteProvider(), { wrapper });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.handleOnAsyncEntityDeleteConfirm(mockDeleteParams);
|
||||
});
|
||||
|
||||
expect(deleteAsyncEntity).toHaveBeenCalledWith(
|
||||
'test-type',
|
||||
'test-id',
|
||||
false,
|
||||
false
|
||||
);
|
||||
expect(showSuccessToast).toHaveBeenCalledWith(mockResponse.message);
|
||||
expect(result.current.asyncDeleteJob).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
it('should handle failed entity deletion', async () => {
|
||||
const mockError = new Error('Delete failed');
|
||||
(deleteAsyncEntity as jest.Mock).mockRejectedValueOnce(mockError);
|
||||
|
||||
const { result } = renderHook(() => useAsyncDeleteProvider(), { wrapper });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.handleOnAsyncEntityDeleteConfirm(mockDeleteParams);
|
||||
});
|
||||
|
||||
expect(showErrorToast).toHaveBeenCalledWith(
|
||||
mockError,
|
||||
'server.delete-entity-error'
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle websocket response', async () => {
|
||||
const mockWebsocketResponse: AsyncDeleteWebsocketResponse = {
|
||||
status: 'COMPLETED',
|
||||
jobId: '123',
|
||||
entityName: 'TestEntity',
|
||||
error: null,
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useAsyncDeleteProvider(), { wrapper });
|
||||
|
||||
act(() => {
|
||||
result.current.handleDeleteEntityWebsocketResponse(mockWebsocketResponse);
|
||||
});
|
||||
|
||||
expect(result.current.asyncDeleteJob).toEqual(
|
||||
expect.objectContaining(mockWebsocketResponse)
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle failed status from ref', async () => {
|
||||
const mockFailedResponse: AsyncDeleteWebsocketResponse = {
|
||||
status: 'FAILED',
|
||||
error: 'Delete operation failed',
|
||||
jobId: '123',
|
||||
entityName: 'TestEntity',
|
||||
};
|
||||
|
||||
(deleteAsyncEntity as jest.Mock).mockResolvedValueOnce(mockFailedResponse);
|
||||
|
||||
const { result } = renderHook(() => useAsyncDeleteProvider(), { wrapper });
|
||||
|
||||
await act(async () => {
|
||||
result.current.handleDeleteEntityWebsocketResponse(mockFailedResponse);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.handleOnAsyncEntityDeleteConfirm(mockDeleteParams);
|
||||
});
|
||||
|
||||
expect(showErrorToast).toHaveBeenCalledWith('Delete operation failed');
|
||||
});
|
||||
|
||||
it('should handle prepared entity type', async () => {
|
||||
(deleteAsyncEntity as jest.Mock).mockResolvedValueOnce(mockResponse);
|
||||
|
||||
const { result } = renderHook(() => useAsyncDeleteProvider(), { wrapper });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.handleOnAsyncEntityDeleteConfirm({
|
||||
...mockDeleteParams,
|
||||
prepareType: true,
|
||||
});
|
||||
});
|
||||
|
||||
expect(deleteAsyncEntity).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
'test-id',
|
||||
false,
|
||||
false
|
||||
);
|
||||
});
|
||||
});
|
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
import { AxiosError } from 'axios';
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DeleteType } from '../../components/common/DeleteWidget/DeleteWidget.interface';
|
||||
import { deleteAsyncEntity } from '../../rest/miscAPI';
|
||||
import deleteWidgetClassBase from '../../utils/DeleteWidget/DeleteWidgetClassBase';
|
||||
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
|
||||
import {
|
||||
AsyncDeleteContextType,
|
||||
AsyncDeleteJob,
|
||||
AsyncDeleteProviderProps,
|
||||
AsyncDeleteWebsocketResponse,
|
||||
DeleteWidgetAsyncFormFields,
|
||||
} from './AsyncDeleteProvider.interface';
|
||||
|
||||
export const AsyncDeleteContext = createContext({} as AsyncDeleteContextType);
|
||||
|
||||
const AsyncDeleteProvider = ({ children }: AsyncDeleteProviderProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [asyncDeleteJob, setAsyncDeleteJob] =
|
||||
useState<Partial<AsyncDeleteJob>>();
|
||||
const asyncDeleteJobRef = useRef<Partial<AsyncDeleteJob>>();
|
||||
|
||||
const handleOnAsyncEntityDeleteConfirm = async ({
|
||||
entityName,
|
||||
entityId,
|
||||
entityType,
|
||||
deleteType,
|
||||
prepareType,
|
||||
isRecursiveDelete,
|
||||
}: DeleteWidgetAsyncFormFields) => {
|
||||
try {
|
||||
const response = await deleteAsyncEntity(
|
||||
prepareType
|
||||
? deleteWidgetClassBase.prepareEntityType(entityType)
|
||||
: entityType,
|
||||
entityId ?? '',
|
||||
Boolean(isRecursiveDelete),
|
||||
deleteType === DeleteType.HARD_DELETE
|
||||
);
|
||||
|
||||
// In case of recursive delete if false and entity has data.
|
||||
// sometime socket throw the error before the response
|
||||
if (asyncDeleteJobRef.current?.status === 'FAILED') {
|
||||
showErrorToast(
|
||||
asyncDeleteJobRef.current.error ??
|
||||
t('server.delete-entity-error', {
|
||||
entity: entityName,
|
||||
})
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setAsyncDeleteJob(response);
|
||||
asyncDeleteJobRef.current = response;
|
||||
showSuccessToast(response.message);
|
||||
} catch (error) {
|
||||
showErrorToast(
|
||||
error as AxiosError,
|
||||
t('server.delete-entity-error', {
|
||||
entity: entityName,
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteEntityWebsocketResponse = (
|
||||
response: AsyncDeleteWebsocketResponse
|
||||
) => {
|
||||
const updatedAsyncDeleteJob: Partial<AsyncDeleteJob> = {
|
||||
...response,
|
||||
...asyncDeleteJob,
|
||||
};
|
||||
setAsyncDeleteJob(updatedAsyncDeleteJob);
|
||||
asyncDeleteJobRef.current = updatedAsyncDeleteJob;
|
||||
};
|
||||
|
||||
const activityFeedContextValues = useMemo(() => {
|
||||
return {
|
||||
asyncDeleteJob,
|
||||
handleOnAsyncEntityDeleteConfirm,
|
||||
handleDeleteEntityWebsocketResponse,
|
||||
};
|
||||
}, [handleOnAsyncEntityDeleteConfirm, handleDeleteEntityWebsocketResponse]);
|
||||
|
||||
return (
|
||||
<AsyncDeleteContext.Provider value={activityFeedContextValues}>
|
||||
{children}
|
||||
</AsyncDeleteContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useAsyncDeleteProvider = () => useContext(AsyncDeleteContext);
|
||||
|
||||
export default AsyncDeleteProvider;
|
@ -15,6 +15,7 @@ import { AxiosResponse } from 'axios';
|
||||
import { Edge } from '../components/Entity/EntityLineage/EntityLineage.interface';
|
||||
import { ExploreSearchIndex } from '../components/Explore/ExplorePage.interface';
|
||||
import { PAGE_SIZE } from '../constants/constants';
|
||||
import { AsyncDeleteJob } from '../context/AsyncDeleteProvider/AsyncDeleteProvider.interface';
|
||||
import { SearchIndex } from '../enums/search.enum';
|
||||
import { AuthenticationConfiguration } from '../generated/configuration/authenticationConfiguration';
|
||||
import { AuthorizerConfiguration } from '../generated/configuration/authorizerConfiguration';
|
||||
@ -130,6 +131,27 @@ export const deleteEntity = async (
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteAsyncEntity = async (
|
||||
entityType: string,
|
||||
entityId: string,
|
||||
isRecursive: boolean,
|
||||
isHardDelete = true
|
||||
) => {
|
||||
const params = {
|
||||
hardDelete: isHardDelete,
|
||||
recursive: isRecursive,
|
||||
};
|
||||
|
||||
const response = await APIClient.delete<AsyncDeleteJob>(
|
||||
`/${entityType}/async/${entityId}`,
|
||||
{
|
||||
params,
|
||||
}
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the aggregate field options based on the provided parameters.
|
||||
*
|
||||
|
Loading…
x
Reference in New Issue
Block a user