|
| 1 | +import { useMemo, useState } from 'react' |
| 2 | +import { useParams } from 'react-router-dom' |
| 3 | + |
| 4 | +import { |
| 5 | + APIResponseHandler, |
| 6 | + ConfirmationModal, |
| 7 | + ConfirmationModalProps, |
| 8 | + ConfirmationModalVariantType, |
| 9 | + DeploymentAppTypes, |
| 10 | + ERROR_STATUS_CODE, |
| 11 | + FiltersTypeEnum, |
| 12 | + ForceDeleteConfirmationModal, |
| 13 | + PaginationEnum, |
| 14 | + ServerErrors, |
| 15 | + showError, |
| 16 | + Table, |
| 17 | + ToastManager, |
| 18 | + ToastVariantType, |
| 19 | + useAsync, |
| 20 | +} from '@devtron-labs/devtron-fe-common-lib' |
| 21 | + |
| 22 | +import ClusterNotReachableDialog from '@Components/common/ClusterNotReachableDialog/ClusterNotReachableDialog' |
| 23 | +import { DELETE_ACTION } from '@Config/constants' |
| 24 | +import { ApplicationDeletionInfo } from '@Pages/Shared/ApplicationDeletionInfo/ApplicationDeletionInfo' |
| 25 | + |
| 26 | +import { DEPLOYMENTS_TABLE_COLUMNS, DeploymentsTableViewWrapper } from './ChartDetailsTableComponents' |
| 27 | +import { deleteChartDeployment, fetchChartDeployments } from './services' |
| 28 | +import { |
| 29 | + ChartDeploymentsDTO, |
| 30 | + ChartDetailsDeploymentsProps, |
| 31 | + ChartDetailsRouteParams, |
| 32 | + DeploymentsTableAdditionalProps, |
| 33 | + DeploymentsTableProps, |
| 34 | +} from './types' |
| 35 | + |
| 36 | +export const ChartDetailsDeployments = ({ chartIcon }: ChartDetailsDeploymentsProps) => { |
| 37 | + // STATES |
| 38 | + const [isOpenDeleteConfirmationModal, setIsOpenDeleteConfirmationModal] = useState(false) |
| 39 | + const [deleteAppDetails, setDeleteAppDetails] = useState<ChartDeploymentsDTO | null>(null) |
| 40 | + const [isDeleting, setIsDeleting] = useState(false) |
| 41 | + const [forceDeleteDialogDetails, setForceDeleteDialogDetails] = useState<Pick< |
| 42 | + ConfirmationModalProps, |
| 43 | + 'title' | 'subtitle' |
| 44 | + > | null>(null) |
| 45 | + const [nonCascadeDeleteDialogClusterName, setNonCascadeDeleteDialogClusterName] = useState<string | null>(null) |
| 46 | + |
| 47 | + // HOOKS |
| 48 | + const { chartId } = useParams<ChartDetailsRouteParams>() |
| 49 | + |
| 50 | + // ASYNC CALLS |
| 51 | + const [isFetchingChartDeployments, chartDeployments, chartDeploymentsErr, reloadChartDeployments] = useAsync( |
| 52 | + () => fetchChartDeployments(chartId), |
| 53 | + [chartId], |
| 54 | + ) |
| 55 | + |
| 56 | + // CONFIGS |
| 57 | + const rows = useMemo<DeploymentsTableProps['rows']>( |
| 58 | + () => |
| 59 | + (chartDeployments || []).map<DeploymentsTableProps['rows'][0]>((data) => ({ |
| 60 | + id: data.installedAppId.toString(), |
| 61 | + data, |
| 62 | + })), |
| 63 | + [chartDeployments], |
| 64 | + ) |
| 65 | + |
| 66 | + // HANDLERS |
| 67 | + const filter: DeploymentsTableProps['filter'] = (rowData, filterData) => |
| 68 | + rowData.data.appName.includes(filterData.searchKey.toLowerCase()) |
| 69 | + |
| 70 | + const handleCloseDeleteConfirmationModal = () => setIsOpenDeleteConfirmationModal(false) |
| 71 | + |
| 72 | + const handleCloseForceDeleteModal = () => { |
| 73 | + setIsOpenDeleteConfirmationModal(false) |
| 74 | + setForceDeleteDialogDetails(null) |
| 75 | + } |
| 76 | + |
| 77 | + const handleCloseNonCascadeDeleteModal = () => { |
| 78 | + setNonCascadeDeleteDialogClusterName(null) |
| 79 | + } |
| 80 | + |
| 81 | + const handleRowDelete = (rowData: ChartDeploymentsDTO) => { |
| 82 | + setIsOpenDeleteConfirmationModal(true) |
| 83 | + setDeleteAppDetails(rowData) |
| 84 | + } |
| 85 | + |
| 86 | + const handleDelete = async (deleteAction: DELETE_ACTION) => { |
| 87 | + setIsDeleting(true) |
| 88 | + |
| 89 | + try { |
| 90 | + const result = await deleteChartDeployment({ |
| 91 | + installedAppId: deleteAppDetails.installedAppId, |
| 92 | + isGitops: deleteAppDetails.deploymentAppType === DeploymentAppTypes.ARGO, |
| 93 | + deleteAction, |
| 94 | + }) |
| 95 | + if (result.deleteResponse?.deleteInitiated) { |
| 96 | + ToastManager.showToast({ |
| 97 | + variant: ToastVariantType.success, |
| 98 | + description: 'Successfully deleted', |
| 99 | + }) |
| 100 | + setIsOpenDeleteConfirmationModal(false) |
| 101 | + setNonCascadeDeleteDialogClusterName(null) |
| 102 | + setForceDeleteDialogDetails(null) |
| 103 | + reloadChartDeployments() |
| 104 | + } else if ( |
| 105 | + deleteAction !== DELETE_ACTION.NONCASCADE_DELETE && |
| 106 | + !result.deleteResponse?.clusterReachable && |
| 107 | + deleteAppDetails.deploymentAppType === DeploymentAppTypes.ARGO |
| 108 | + ) { |
| 109 | + setNonCascadeDeleteDialogClusterName(result.deleteResponse?.clusterName) |
| 110 | + setIsOpenDeleteConfirmationModal(false) |
| 111 | + } |
| 112 | + } catch (err) { |
| 113 | + if (deleteAction !== DELETE_ACTION.FORCE_DELETE && err.code !== ERROR_STATUS_CODE.PERMISSION_DENIED) { |
| 114 | + setIsOpenDeleteConfirmationModal(false) |
| 115 | + setNonCascadeDeleteDialogClusterName(null) |
| 116 | + if (err instanceof ServerErrors && Array.isArray(err.errors)) { |
| 117 | + err.errors.forEach(({ userMessage, internalMessage }) => { |
| 118 | + setForceDeleteDialogDetails({ |
| 119 | + title: userMessage, |
| 120 | + subtitle: internalMessage, |
| 121 | + }) |
| 122 | + }) |
| 123 | + } |
| 124 | + } else { |
| 125 | + showError(err) |
| 126 | + } |
| 127 | + } finally { |
| 128 | + setIsDeleting(false) |
| 129 | + } |
| 130 | + } |
| 131 | + |
| 132 | + const handleCascadeDelete = async () => { |
| 133 | + await handleDelete(DELETE_ACTION.DELETE) |
| 134 | + } |
| 135 | + |
| 136 | + const handleForceDelete = async () => { |
| 137 | + await handleDelete(DELETE_ACTION.FORCE_DELETE) |
| 138 | + } |
| 139 | + |
| 140 | + const handleNonCascadeDelete = async () => { |
| 141 | + await handleDelete(DELETE_ACTION.NONCASCADE_DELETE) |
| 142 | + } |
| 143 | + |
| 144 | + return ( |
| 145 | + <> |
| 146 | + <div className="mh-500 flexbox-col bg__primary border__primary br-4 w-100 dc__overflow-auto"> |
| 147 | + <APIResponseHandler |
| 148 | + isLoading={false} |
| 149 | + progressingProps={{ size: 24 }} |
| 150 | + error={chartDeploymentsErr} |
| 151 | + errorScreenManagerProps={{ |
| 152 | + code: chartDeploymentsErr?.code, |
| 153 | + reload: reloadChartDeployments, |
| 154 | + }} |
| 155 | + > |
| 156 | + <Table<ChartDeploymentsDTO, FiltersTypeEnum.STATE, DeploymentsTableAdditionalProps> |
| 157 | + id="table__chart-details-deployments" |
| 158 | + loading={isFetchingChartDeployments} |
| 159 | + columns={DEPLOYMENTS_TABLE_COLUMNS} |
| 160 | + rows={rows} |
| 161 | + stylesConfig={{ showSeparatorBetweenRows: false }} |
| 162 | + emptyStateConfig={{ |
| 163 | + noRowsConfig: { |
| 164 | + title: 'This chart is ready for launch', |
| 165 | + subTitle: |
| 166 | + 'This chart hasn’t been deployed yet. Click Deploy Chart to get started with your first deployment.', |
| 167 | + imgName: 'img-man-on-rocket', |
| 168 | + }, |
| 169 | + noRowsForFilterConfig: { |
| 170 | + title: 'No results', |
| 171 | + subTitle: 'We couldn’t find any matching results', |
| 172 | + }, |
| 173 | + }} |
| 174 | + paginationVariant={PaginationEnum.NOT_PAGINATED} |
| 175 | + filtersVariant={FiltersTypeEnum.STATE} |
| 176 | + filter={filter} |
| 177 | + ViewWrapper={DeploymentsTableViewWrapper} |
| 178 | + additionalProps={{ |
| 179 | + chartIcon, |
| 180 | + onDelete: handleRowDelete, |
| 181 | + }} |
| 182 | + additionalFilterProps={{ |
| 183 | + initialSortKey: 'appName', |
| 184 | + }} |
| 185 | + /> |
| 186 | + </APIResponseHandler> |
| 187 | + </div> |
| 188 | + {isOpenDeleteConfirmationModal && ( |
| 189 | + <ConfirmationModal |
| 190 | + variant={ConfirmationModalVariantType.delete} |
| 191 | + title={`Delete app ‘${deleteAppDetails.appName}’`} |
| 192 | + subtitle={<ApplicationDeletionInfo />} |
| 193 | + buttonConfig={{ |
| 194 | + secondaryButtonConfig: { |
| 195 | + onClick: handleCloseDeleteConfirmationModal, |
| 196 | + text: 'Cancel', |
| 197 | + }, |
| 198 | + primaryButtonConfig: { |
| 199 | + isLoading: isDeleting, |
| 200 | + onClick: handleCascadeDelete, |
| 201 | + text: 'Delete', |
| 202 | + }, |
| 203 | + }} |
| 204 | + handleClose={handleCloseDeleteConfirmationModal} |
| 205 | + /> |
| 206 | + )} |
| 207 | + {!!forceDeleteDialogDetails && ( |
| 208 | + <ForceDeleteConfirmationModal |
| 209 | + {...forceDeleteDialogDetails} |
| 210 | + onDelete={handleForceDelete} |
| 211 | + closeConfirmationModal={handleCloseForceDeleteModal} |
| 212 | + /> |
| 213 | + )} |
| 214 | + {!!nonCascadeDeleteDialogClusterName && ( |
| 215 | + <ClusterNotReachableDialog |
| 216 | + clusterName={nonCascadeDeleteDialogClusterName} |
| 217 | + onClickCancel={handleCloseNonCascadeDeleteModal} |
| 218 | + onClickDelete={handleNonCascadeDelete} |
| 219 | + /> |
| 220 | + )} |
| 221 | + </> |
| 222 | + ) |
| 223 | +} |
0 commit comments