Merge pull request #29253 from deepthi-28/devtoolsdelete

Delete confirmation pop up is added for Catalog Unprocessed Entities plugin
This commit is contained in:
Ben Lambert
2025-03-25 10:31:57 +01:00
committed by GitHub
3 changed files with 138 additions and 41 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-unprocessed-entities': patch
---
Added confirmation popup for delete entities in Catalog Unprocessed Entities plugin
@@ -0,0 +1,66 @@
/*
* Copyright 2021 The Backstage Authors
*
* 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 Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogTitle from '@material-ui/core/DialogTitle';
import React, { useState } from 'react';
import { assertError } from '@backstage/errors';
interface DeleteEntityConfirmationProps {
open: boolean;
onClose: () => any;
onConfirm: () => any;
}
export function DeleteEntityConfirmationDialog(
props: DeleteEntityConfirmationProps,
) {
const { open, onClose, onConfirm } = props;
const [busy, setBusy] = useState(false);
const onDelete = async () => {
setBusy(true);
try {
onConfirm();
} catch (err) {
assertError(err);
} finally {
setBusy(false);
}
};
return (
<Dialog open={open} onClose={onClose}>
<DialogTitle id="responsive-dialog-title">
Are you sure you want to delete this entity?
</DialogTitle>
<DialogActions>
<Button
variant="contained"
color="secondary"
disabled={busy}
onClick={onDelete}
>
Delete
</Button>
<Button onClick={onClose} color="primary">
Cancel
</Button>
</DialogActions>
</Dialog>
);
}
@@ -34,6 +34,7 @@ import { EntityDialog } from './EntityDialog';
import { catalogUnprocessedEntitiesApiRef } from '../api';
import useAsync from 'react-use/esm/useAsync';
import DeleteIcon from '@material-ui/icons/Delete';
import { DeleteEntityConfirmationDialog } from './DeleteEntityConfirmationDialog';
const useStyles = makeStyles((theme: Theme) => ({
errorBox: {
@@ -117,6 +118,13 @@ export const FailedEntities = () => {
const [, setSelectedSearchTerm] = useState<string>('');
const unprocessedEntityApi = useApi(catalogUnprocessedEntitiesApiRef);
const alertApi = useApi(alertApiRef);
const [selectedEntityId, setSelectedEntityId] = useState<string | undefined>(
undefined,
);
const [selectedEntityRef, setSelectedEntityRef] = useState<
string | undefined
>(undefined);
const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false);
if (loading) {
return <Progress />;
@@ -125,25 +133,34 @@ export const FailedEntities = () => {
return <ErrorPanel error={error} />;
}
const handleDelete = async ({
const handleDelete = ({
entityId,
entityRef,
}: {
entityId: string;
entityRef: string;
}) => {
setSelectedEntityId(entityId);
setSelectedEntityRef(entityRef);
setConfirmationDialogOpen(true);
};
const cleanUpAfterRemoval = async () => {
try {
await unprocessedEntityApi.delete(entityId);
alertApi.post({
message: `Entity ${entityRef} has been deleted`,
severity: 'success',
});
if (selectedEntityId) {
await unprocessedEntityApi.delete(selectedEntityId);
alertApi.post({
message: `Entity ${selectedEntityRef} has been deleted`,
severity: 'success',
});
}
} catch (e) {
alertApi.post({
message: `Ran into an issue when deleting ${entityRef}. Please try again later.`,
message: `Ran into an issue when deleting ${selectedEntityRef}. Please try again later.`,
severity: 'error',
});
}
setConfirmationDialogOpen(false);
};
const columns: TableColumn[] = [
@@ -213,8 +230,8 @@ export const FailedEntities = () => {
return (
<IconButton
aria-label="delete"
onClick={async () =>
await handleDelete({
onClick={() =>
handleDelete({
entityId: entity_id,
entityRef: entity_ref,
})
@@ -228,37 +245,46 @@ export const FailedEntities = () => {
];
return (
<Table
options={{ pageSize: 20, search: true }}
columns={columns}
data={data?.entities ?? []}
emptyContent={
<Typography className={classes.successMessage}>
No failed entities found
</Typography>
}
onSearchChange={(searchTerm: string) => setSelectedSearchTerm(searchTerm)}
detailPanel={({ rowData }) => {
const errors = (rowData as UnprocessedEntity).errors;
return (
<>
{errors?.map((e, idx) => {
return (
<Box key={idx} className={classes.errorBox}>
<Typography className={classes.errorTitle}>
{e.name}
</Typography>
<MarkdownContent content={e.message} />
<RenderErrorContext
error={e}
rowData={rowData as UnprocessedEntity}
/>
</Box>
);
})}
</>
);
}}
/>
<>
<Table
options={{ pageSize: 20, search: true }}
columns={columns}
data={data?.entities ?? []}
emptyContent={
<Typography className={classes.successMessage}>
No failed entities found
</Typography>
}
onSearchChange={(searchTerm: string) =>
setSelectedSearchTerm(searchTerm)
}
detailPanel={({ rowData }) => {
const errors = (rowData as UnprocessedEntity).errors;
return (
<>
{errors?.map((e, idx) => {
return (
<Box key={idx} className={classes.errorBox}>
<Typography className={classes.errorTitle}>
{e.name}
</Typography>
<MarkdownContent content={e.message} />
<RenderErrorContext
error={e}
rowData={rowData as UnprocessedEntity}
/>
</Box>
);
})}
</>
);
}}
/>
<DeleteEntityConfirmationDialog
open={confirmationDialogOpen}
onClose={() => setConfirmationDialogOpen(false)}
onConfirm={cleanUpAfterRemoval}
/>
</>
);
};