diff --git a/src/components/common/DeleteResource.tsx b/src/components/common/DeleteResource.tsx index 0afc46d..597a43a 100644 --- a/src/components/common/DeleteResource.tsx +++ b/src/components/common/DeleteResource.tsx @@ -1,9 +1,11 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Button, Card, + Checkbox, CircularProgress, Dialog, + FormControlLabel, Grid, IconButton, makeStyles, @@ -11,17 +13,25 @@ import { Typography, } from '@material-ui/core'; import { getIn, useFormikContext } from 'formik'; -import { httpClient, post } from '@src/helper/api-client'; +import { httpClient, httpDelete, post } from '@src/helper/api-client'; import { toast } from 'react-toastify'; -import { Done } from '@material-ui/icons'; +import { CheckBox, Done } from '@material-ui/icons'; import DialogContent from '@material-ui/core/DialogContent'; -import { API_TO_POST_MANIFEST } from '@src/constants/Endpoints'; +import { + API_CREATE_REQUEST, + API_DELETE_K8S_RESOURCE, + API_GET_K8S_RESOURCE_STATUS, + API_TO_POST_MANIFEST, + DELETE_K8S_RESOURCE_API, +} from '@src/constants/Endpoints'; import { forEach } from 'lodash'; +import { getFieldNameFromPath } from '@src/models/Manifest'; +import { validate } from 'webpack'; interface DeleteResourceProps { name: string; fieldPath: string; - onDelete?: Function; + onDelete: Function; disabled?: boolean; children?: React.ReactNode; index?: number; @@ -29,17 +39,20 @@ interface DeleteResourceProps { } interface ConfirmationPopupProps { - handleDeleteResource: Function; + onDelete: Function; + k8sResourceName: string; + loading: boolean; + setLoading: Function; + isResourceDeployed: boolean; + deleteObjectEndpoint: string; + manifestId: number; handleClose: Function; openPopup: boolean; - loading: boolean; - setDeleted: Function; - confirmDelete: boolean; + setOpenPopup: Function; } interface NotifyDeleteProps { fieldPath: string; - handleClose: Function; } const useStyles = makeStyles({ @@ -78,42 +91,96 @@ const useStyles = makeStyles({ }, }); -const kubeObjectType = { - LoadBalancers: 'ingress', - 'Service Monitor': 'serviceMonitor', - Deployment: 'deployment', - deployment: { - commonApiGateways: 'commonApiGateways', - loadBalancers: 'ingress', - hpa: { - cronJobs: 'cronHpa', - }, - }, - 'Flink Job': 'flinkSessionJob', - 'API Gateway': 'commonApiGateways', -}; - const ConfirmationPopup = (props: ConfirmationPopupProps) => { const classes = useStyles(); - const { handleClose, handleDeleteResource } = props; + const { handleClose, onDelete } = props; + const [notifyDelete, setNotifyDelete] = useState(false); + const [deleteMessage, setDeleteMessage] = useState(); + let [deleteFromManifest, setDeleteFromManifest] = useState(false); + const { submitForm }: { submitForm: any } = useFormikContext(); + + const toggleDeleteFromManifest = () => { + setDeleteFromManifest(!deleteFromManifest); + }; + + const deleteK8sResource = () => { + props.setLoading(true); + httpDelete({}, props.deleteObjectEndpoint) + .then(res => { + props.setLoading(false); + setDeleteMessage('Successfully deleted Kubernetes resource'); + setNotifyDelete(true); + }) + .catch(error => { + props.setLoading(false); + toast.error(error.message); + }); + }; + + const handleDelete = () => { + if (deleteFromManifest) { + onDelete(); + submitForm(); + } else { + deleteK8sResource(); + } + }; + + const NotifyDelete = () => { + const classes = useStyles(); + return ( + + + + + {deleteMessage} + + + + + + + + + ); + }; const ConfirmDelete = () => { - return ( + return notifyDelete ? ( + + ) : ( - {`Proceed with deletion of kubernetes resource?`}{' '} + {`Proceed with deletion of kubernetes resource ?`}{' '} +
+
+ } + label="Delete from manifest also" + onClick={toggleDeleteFromManifest} + checked={deleteFromManifest} + /> - - - - ); +const KubeObjectTypeMap = { + loadBalancers: { + name: 'ingress', + uniqueIdentifier: 'id', + }, + commonApiGateways: { + name: 'commonApiGateways', + uniqueIdentifier: 'pathName', + }, }; -const noop = (): void => undefined; +const DeleteImplementedMap = { + 'deployment.loadBalancers': true, + deployment: true, + 'deployment.commonApiGateways': true, + 'deployment.hpa.croHpa': true, + 'deployment.serviceMonitor': true, +}; -const DeleteResource = ({ onDelete = noop, ...props }: DeleteResourceProps) => { +const DeleteResource = (props: DeleteResourceProps) => { const [openPopup, setOpenPopup] = useState(false); - const deleteButtonTooltip = props.tooltip ? props.tooltip : 'Delete'; - const name = props.name; - const { values }: { values: any } = useFormikContext(); + const deleteButtonTooltip = props.tooltip ? props.tooltip : 'Delete from Manifest or Kubernetes'; + const { values, submitForm }: { values: any; submitForm: any } = useFormikContext(); const manifestId = values?.id; - const [deleted, setDeleted] = useState(false); - const [confirmDelete, setConfirmDelete] = useState(false); + + const [isResourceDeployed, setIsResourceDeployed] = useState(false); const [loading, setLoading] = useState(false); - const kubeObjectKind = getIn(kubeObjectType, name); - const kubeObject = getIn(values, props.name); - const deleteItemAndSaveManifest = (fieldPath: string) => { - console.error('Path not found', fieldPath); - const keys = fieldPath.split('.'); - let current = Object.assign({}, values); - if (keys.length == 1) { - delete current[keys[0]]; - console.log(current); - } else { - for (let i = 1; i < keys.length; i++) { - current = current[keys[i - 1]]; - } - delete current[keys[keys.length - 1]]; - } - post(current, API_TO_POST_MANIFEST).then(res => { - if (!res.ok) { - throw new Error('Error submitting manifest.'); - } else { - location.reload(); - } - }); - }; + function getPathAndIndex(fieldPath) { + const match = fieldPath.match(/\.(\d+)$/); + const index = match ? parseInt(match[1], 10) : undefined; + const pathWithoutIndex = match ? fieldPath.slice(0, fieldPath.lastIndexOf('.')) : fieldPath; + return { + index: index, + pathWithoutIndex: pathWithoutIndex, + }; + } - const deleteWithToastOnError = (fieldPath: string): void => { - try { - deleteItemAndSaveManifest(fieldPath); - onDelete(); - setDeleted(false); - setOpenPopup(false); - } catch (err) { - toast.error(err.message); - } - }; + const { index, pathWithoutIndex }: { index: any; pathWithoutIndex: string } = getPathAndIndex( + props.fieldPath, + ); + const fieldName = getFieldNameFromPath(pathWithoutIndex); + let k8sResourceName = fieldName; + if (KubeObjectTypeMap.hasOwnProperty(fieldName)) { + k8sResourceName = KubeObjectTypeMap[fieldName]['name']; + } + let getObjectEndpoint = API_GET_K8S_RESOURCE_STATUS( + manifestId, + k8sResourceName, + undefined, + undefined, + ); + let deleteObjectEndpoint = API_DELETE_K8S_RESOURCE( + manifestId, + k8sResourceName, + undefined, + undefined, + ); - const handleDelete = () => { - setOpenPopup(true); - if (kubeObjectKind !== undefined && manifestId !== undefined) { + if (index !== undefined) { + k8sResourceName = KubeObjectTypeMap[fieldName]['name']; + let uniqueIdentifierName = + KubeObjectTypeMap[fieldName]['uniqueIdentifierName'] !== undefined + ? KubeObjectTypeMap[fieldName]['uniqueIdentifierName'] + : fieldName; + let uniqueIdentiferValue = + getIn(values, props.fieldPath) !== undefined + ? getIn(values, props.fieldPath)[uniqueIdentifierName] + : undefined; + getObjectEndpoint = API_GET_K8S_RESOURCE_STATUS( + manifestId, + k8sResourceName, + uniqueIdentifierName, + uniqueIdentiferValue, + ); + deleteObjectEndpoint = API_GET_K8S_RESOURCE_STATUS( + manifestId, + k8sResourceName, + uniqueIdentifierName, + uniqueIdentiferValue, + ); + } + + const checkIfDeployed = () => { + if (manifestId !== undefined) { + setOpenPopup(true); setLoading(true); - let getObjectEndpoint = `/api/kube/manifest/${values.id}/kubeObjectType/${kubeObjectKind}`; - if (props.name === 'deployment.loadBalancers' && props.index !== undefined) { - const resourceId = getIn(kubeObject[props.index], 'id'); - if (resourceId === undefined) { - deleteWithToastOnError(props.fieldPath); - setOpenPopup(false); - return; - } - getObjectEndpoint = `/api/kube/manifest/${values.id}/kubeObjectType/${kubeObjectKind}?resourceId=${resourceId}`; - } - httpClient(getObjectEndpoint) .then(res => { - res - .json() - .then(respJson => { - if (res.ok) { + if (res.ok) { + res + .json() + .then(respJson => { if (respJson === true) { - setConfirmDelete(true); + setLoading(false); + setIsResourceDeployed(true); } else { - setOpenPopup(false); - deleteWithToastOnError(props.fieldPath); + props.onDelete(); } - } else { - toast.error(`${respJson.message}`); + }) + .catch(err => { + toast.error(`${err.message}`); setOpenPopup(false); - } - }) - .catch(resJson => { - toast.error( - 'Could not check if resource is deployed on kubernetes, please reach out to Cloud-Platform team', - resJson.statusText, - ); - setOpenPopup(false); - }); + }); + } else { + setLoading(false); + toast.error( + 'Could not check if resource is deployed on kubernetes, please reach out to Cloud-Platform team', + resJson.statusText, + ); + } }) - .finally(() => { - setLoading(false); + .catch(resJson => { + toast.error( + 'Could not check if resource is deployed on kubernetes, please reach out to Cloud-Platform team', + resJson.statusText, + ); + setOpenPopup(false); }); } else { - // item is only in the UI and not in the DB - deleteWithToastOnError(props.fieldPath); - setOpenPopup(false); + setIsResourceDeployed(false); } }; - const deleteResource = () => { - let deleteEndpoint = getDeleteEndpoint(values, kubeObjectKind, props, kubeObject); - setLoading(true); - httpClient(deleteEndpoint, { method: 'DELETE' }) - .then(res => { - if (res.ok) { - setDeleted(true); - } else { - setLoading(false); - setOpenPopup(false); - toast.error(`Unable to delete resource : ${res.statusText} ${res.status}`); - setOpenPopup(false); - } - }) - .catch(error => { - setLoading(false); - toast.error(`API call failed with error ${error}`); - }); - }; - - const getDeleteEndpoint = ( - values: any, - kubeObjectKind: any, - props: { - name: string; - disabled?: boolean; - children?: React.ReactNode; - index?: number; - tooltip?: string; - }, - kubeObject: any, - ) => { - let deleteEndpoint = `/api/kube/manifest/${values.id}/kubeObjectType/${kubeObjectKind}`; - if (props.name === 'deployment.loadBalancers' && props.index !== undefined) { - deleteEndpoint = `/api/kube/manifest/${ - values.id - }/kubeObjectType/${kubeObjectKind}?resourceId=${getIn(kubeObject[props.index], 'id')}`; - } else if ( - props.name === 'deployment.commonApiGateways.0.gatewayAttributes' && - props.index !== undefined - ) { - deleteEndpoint = `/api/kube/manifest/${ - values.id - }/kubeObjectType/${kubeObjectKind}?apiGatewayPathName=${getIn( - kubeObject[props.index], - 'pathName', - )}`; - } - return deleteEndpoint; - }; - - const handleClosePopup = () => { - deleteWithToastOnError(props.fieldPath); - }; - return ( <> { setOpenPopup(false); }} /> - - handleClosePopup()} fieldPath={props.fieldPath} /> - - + {props.children} diff --git a/src/components/common/FormikCardList.tsx b/src/components/common/FormikCardList.tsx index bfe8cca..8126ae6 100644 --- a/src/components/common/FormikCardList.tsx +++ b/src/components/common/FormikCardList.tsx @@ -4,6 +4,7 @@ import * as React from 'react'; import AddIcon from '@material-ui/icons/Add'; import DeleteIcon from '@material-ui/icons/Delete'; import DeleteResource from './DeleteResource'; +import { path } from '@src/models/Manifest'; const useStyles = makeStyles({ root: { @@ -57,6 +58,7 @@ export const FormikCardList = (props: FormikCardListProps) => { { const { removeAction, ...buttonProps } = props; + return ( @@ -6,3 +8,43 @@ export const API_TO_EXPORT_MANIFEST = (manifestId: string, version): string => : `/api/manifest/${manifestId}/export`; export const API_TO_MANAGE_ARGO_ROLLOUT = (manifestId: number, operation: string): string => `/api/kube/manifest/${manifestId}/rollout/${operation}`; +export const API_TO_DELETE_INGRESS = ( + manifestId: number, + kubeObjectKind: string, + resourceId: number, +): string => + resourceId + ? `/api/kube/manifest/${manifestId}/kubeObjectKind/${kubeObjectKind}?resourceId=${resourceId}` + : `/api/kube/manifest/${manifestId}/kubeObjectKind/${kubeObjectKind}?resourceId=${resourceId}`; +export const API_TO_DELETE_COMMON_API_GATEWAY = ( + manifestId: number, + kubeObjectKind: string, + apiGatewayPathName: string, +): string => + apiGatewayPathName + ? `/api/kube/manifest/${manifestId}/kubeObjectKind/${kubeObjectKind}/apiGatewayPathName?apiGatewayPathName=${apiGatewayPathName}` + : `/api/kube/manifest/${manifestId}/kubeObjectKind/${kubeObjectKind}/apiGatewayPathName?apiGatewayPathName=${apiGatewayPathName}`; + +export const API_GET_K8S_RESOURCE_STATUS = ( + manifestId: number, + k8sResourceName: string, + uniqueIdentifierName: string | undefined, + uniqueIdentiferValue: any, +) => { + if (uniqueIdentifierName !== undefined && uniqueIdentiferValue !== undefined) { + return `/api/kube/manifest/${manifestId}/kubeObjectType/${k8sResourceName}?${uniqueIdentifierName}=${uniqueIdentiferValue}`; + } + return `/api/kube/manifest/${manifestId}/kubeObjectType/${k8sResourceName}`; +}; + +export const API_DELETE_K8S_RESOURCE = ( + manifestId: number, + k8sResourceName: string, + uniqueIdentifierName: string | undefined, + uniqueIdentifierValue: any, +) => { + if (uniqueIdentifierName !== undefined && uniqueIdentifierValue !== undefined) { + return `/api/kube/manifest/${manifestId}/kubeObjectType/${k8sResourceName}?${uniqueIdentifierName}=${uniqueIdentifierValue}`; + } + return `/api/kube/manifest/${manifestId}/kubeObjectType/${k8sResourceName}`; +}; diff --git a/src/coreform/deployment/LoadBalancerBasicTab.tsx b/src/coreform/deployment/LoadBalancerBasicTab.tsx index dc97e40..d47fb6d 100644 --- a/src/coreform/deployment/LoadBalancerBasicTab.tsx +++ b/src/coreform/deployment/LoadBalancerBasicTab.tsx @@ -382,7 +382,6 @@ const LoadBalancerBasicTab: FC = () => { name="deployment.loadBalancers" newItem={_m.newLoadBalancer} newItemLabel="Add LoadBalancer" - deleteItemFn={deleteItemAndSaveManifest} > {(i: number) => ( <> diff --git a/src/coreform/flink/FlinkForm.tsx b/src/coreform/flink/FlinkForm.tsx index 8ae8839..c8cd173 100644 --- a/src/coreform/flink/FlinkForm.tsx +++ b/src/coreform/flink/FlinkForm.tsx @@ -382,7 +382,11 @@ const FlinkJob = (): React.JSX.Element => { name="flink.flinkJob.entryClass" >
- + {}} + name={'Flink Job'} + tooltip="Deletes flink job from Kubernetes" + >
diff --git a/src/helper/api-client.ts b/src/helper/api-client.ts index cc07c8c..7707453 100644 --- a/src/helper/api-client.ts +++ b/src/helper/api-client.ts @@ -47,3 +47,10 @@ export const post = async (values: any, apiPath: string): Promise => { body: JSON.stringify(values), }); }; + +export const httpDelete = async (values: any, apiPath: string): Promise => { + return httpClient(apiPath, { + method: 'DELETE', + body: JSON.stringify(values), + }); +}; diff --git a/src/models/Manifest.ts b/src/models/Manifest.ts index cb8f0ed..b94d31f 100644 --- a/src/models/Manifest.ts +++ b/src/models/Manifest.ts @@ -49,6 +49,15 @@ export const path = { scylladb: 'deployment.scyllaDb', }; +export const getFieldNameFromPath = (fieldPath: string): string => { + for (const [key, value] of Object.entries(path)) { + if (value === fieldPath) { + return key; + } + } + return fieldPath; +}; + // Constants const commonNamespaces = ['perf', 'monitoring', 'infrastructure', 'airflow', 'data-platform']; export const namespaces = {