diff --git a/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/miscAPI.ts b/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/miscAPI.ts index bdc1ecc58e2..90570966ae2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/miscAPI.ts +++ b/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/miscAPI.ts @@ -12,8 +12,10 @@ */ import { AxiosResponse } from 'axios'; +import { isUndefined } from 'lodash'; import { Edge } from '../components/EntityLineage/EntityLineage.interface'; import { SearchIndex } from '../enums/search.enum'; +import { getURLWithQueryFields } from '../utils/APIUtils'; import { getCurrentUserId } from '../utils/CommonUtils'; import { getSearchAPIQuery } from '../utils/SearchUtils'; import APIClient from './index'; @@ -116,7 +118,18 @@ export const getInitialEntity: Function = (): Promise => { export const deleteEntity: Function = ( entityType: string, - entityId: string + entityId: string, + isRecursive: boolean ): Promise => { - return APIClient.delete(`/${entityType}/${entityId}?hardDelete=true`); + const searchParams = new URLSearchParams({ hardDelete: `true` }); + if (!isUndefined(isRecursive)) { + searchParams.set('recursive', `${isRecursive}`); + } + const path = getURLWithQueryFields( + `/${entityType}/${entityId}`, + '', + `${searchParams.toString()}` + ); + + return APIClient.delete(path); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ManageTab/ManageTab.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ManageTab/ManageTab.component.tsx index c4a4b044bba..27c2d7b724e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ManageTab/ManageTab.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ManageTab/ManageTab.component.tsx @@ -26,6 +26,7 @@ import { getCategory } from '../../axiosAPIs/tagAPI'; import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants'; import { TITLE_FOR_NON_ADMIN_ACTION } from '../../constants/constants'; import { ENTITY_DELETE_STATE } from '../../constants/entity.constants'; +import { EntityType } from '../../enums/entity.enum'; import { Operation } from '../../generated/entity/policies/accessControl/rule'; import { useAuth } from '../../hooks/authHooks'; import jsonData from '../../jsons/en'; @@ -53,6 +54,8 @@ const ManageTab: FunctionComponent = ({ entityName, entityType, entityId, + isRecursiveDelete, + deletEntityMessage, }: ManageProps) => { const history = useHistory(); const { userPermissions, isAdminUser } = useAuth(); @@ -166,9 +169,28 @@ const ManageTab: FunctionComponent = ({ setEntityDeleteState(ENTITY_DELETE_STATE); }; + const prepareEntityType = () => { + const services = [ + EntityType.DASHBOARD_SERVICE, + EntityType.DATABASE_SERVICE, + EntityType.MESSAGING_SERVICE, + EntityType.PIPELINE_SERVICE, + ]; + + if (services.includes((entityType || '') as EntityType)) { + return `services/${entityType}s`; + } else { + return `${entityType}s`; + } + }; + + const prepareDeleteMessage = () => { + return `Once you delete this ${entityType}, it will be removed permanently`; + }; + const handleOnEntityDeleteConfirm = () => { setEntityDeleteState((prev) => ({ ...prev, loading: 'waiting' })); - deleteEntity(`${entityType}s`, entityId) + deleteEntity(prepareEntityType(), entityId, isRecursiveDelete) .then((res: AxiosResponse) => { if (res.status === 200) { setTimeout(() => { @@ -201,6 +223,7 @@ const ManageTab: FunctionComponent = ({ if (allowDelete && entityDeleteState.state) { return ( = ({

Delete {entityType} {entityName}

-

- {`Once you delete this ${entityType}, it would be removed permanently`} -

+

{prepareDeleteMessage()}

{ const inputBox = await findByTestId(container, 'confirmation-text-input'); fireEvent.change(inputBox, { - target: { value: `${mockProp.entityType}/${mockProp.entityName}` }, + target: { value: 'DELETE' }, }); expect(confirmButton).not.toBeDisabled(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityDeleteModal/EntityDeleteModal.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityDeleteModal/EntityDeleteModal.tsx index 9546b45e6aa..b95d0854084 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityDeleteModal/EntityDeleteModal.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityDeleteModal/EntityDeleteModal.tsx @@ -28,6 +28,7 @@ interface Prop extends HTMLAttributes { entityName: string; entityType: string; loadingState: string; + bodyText?: string; } const EntityDeleteModal: FC = ({ @@ -37,6 +38,7 @@ const EntityDeleteModal: FC = ({ entityType, onCancel, onConfirm, + bodyText, }: Prop) => { const [name, setName] = useState(''); @@ -45,7 +47,7 @@ const EntityDeleteModal: FC = ({ }; const isNameMatching = useCallback(() => { - return name === `${entityType}/${entityName}`; + return name === 'DELETE'; }, [name]); return ( @@ -60,13 +62,12 @@ const EntityDeleteModal: FC = ({

-

{`Once you delete this ${entityType}, it would be removed permanently`}

- Type{' '} - - {entityType}/{entityName} - {' '} - to confirm + {bodyText || + `Once you delete this ${entityType}, it will be removed permanently`} +

+

+ Type DELETE to confirm

= ({ indexType, matches, tableType, - service, - database, - databaseSchema, deleted = false, }: Props) => { const location = useLocation(); @@ -116,45 +112,6 @@ const TableDataCard: FunctionComponent = ({ } }; - const getPopOverContent = () => { - const entityDetails = [ - { - key: 'Service Type', - value: serviceType, - }, - ]; - if (service) { - entityDetails.push({ - key: 'Service', - value: service, - }); - } - - if (database) { - entityDetails.push({ - key: 'Database', - value: database, - }); - } - if (databaseSchema) { - entityDetails.push({ - key: 'Schema', - value: databaseSchema, - }); - } - - return ( -
- {entityDetails.map((detail) => ( -

- {detail.key} : - {detail.value} -

- ))} -
- ); - }; - return (
= ({ className="tw-inline tw-h-5 tw-w-5" src={serviceTypeLogo(serviceType || '')} /> - -
- -
-
+
+ +
{deleted && ( <>
{ ); }; + const getDeleteEntityMessage = () => { + if (!tableInstanceCount) { + return; + } + + return `Deleting this databaseSchema will also delete ${pluralize( + tableInstanceCount, + 'table', + 's' + )}.`; + }; + useEffect(() => { if (TabSpecificField.ACTIVITY_FEED === tab) { fetchActivityFeed(); @@ -675,7 +688,9 @@ const DatabaseSchemaPage: FunctionComponent = () => { { useState(pagingObject); const [databaseSchemaInstanceCount, setSchemaInstanceCount] = useState(0); + const [tableInstanceCount, setTableInstanceCount] = useState(0); const [activeTab, setActiveTab] = useState( getCurrentDatabaseDetailsTab(tab) @@ -521,6 +528,25 @@ const DatabaseDetails: FunctionComponent = () => { }); }; + const fetchTablesCount = () => { + // limit=0 will fetch empty data list with total count + getAllTables('', 0) + .then((res: AxiosResponse) => { + if (res.data) { + setTableInstanceCount(res.data.paging.total); + } else { + throw jsonData['api-error-messages']['unexpected-server-response']; + } + }) + .catch((err: AxiosError) => { + showErrorToast( + err, + jsonData['api-error-messages']['unexpected-server-response'] + ); + setTableInstanceCount(0); + }); + }; + const getLoader = () => { return isentityThreadLoading ? : null; }; @@ -535,8 +561,21 @@ const DatabaseDetails: FunctionComponent = () => { } }; + const getDeleteEntityMessage = () => { + if (!databaseSchemaInstanceCount && !tableInstanceCount) { + return; + } + + return `Deleting this database will also delete ${pluralize( + databaseSchemaInstanceCount, + 'schema', + 's' + )} and ${pluralize(tableInstanceCount, 'table', 's')}.`; + }; + useEffect(() => { getEntityFeedCount(); + fetchTablesCount(); }, []); useEffect(() => { @@ -758,7 +797,9 @@ const DatabaseDetails: FunctionComponent = () => { {