Merge pull request #5707 from backstage/jhaals/orphan-warning
catalog: Display warning when entity is orphan
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog': patch
|
||||
---
|
||||
|
||||
Display warning when Entity has orphan annotation.
|
||||
@@ -40,6 +40,8 @@ import {
|
||||
isComponentType,
|
||||
isKind,
|
||||
EntityHasResourcesCard,
|
||||
EntityOrphanWarning,
|
||||
isOrphan,
|
||||
} from '@backstage/plugin-catalog';
|
||||
import {
|
||||
EntityCircleCIContent,
|
||||
@@ -214,7 +216,15 @@ const errorsContent = (
|
||||
|
||||
const overviewContent = (
|
||||
<Grid container spacing={3} alignItems="stretch">
|
||||
<Grid item md={6}>
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={isOrphan}>
|
||||
<Grid item xs={12}>
|
||||
<EntityOrphanWarning />
|
||||
</Grid>
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
|
||||
<Grid item md={8} xs={12}>
|
||||
<EntityAboutCard variant="gridItem" />
|
||||
</Grid>
|
||||
|
||||
@@ -226,7 +236,7 @@ const overviewContent = (
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
|
||||
<Grid item md={4} sm={6}>
|
||||
<Grid item md={4} xs={12}>
|
||||
<EntityLinksCard />
|
||||
</Grid>
|
||||
|
||||
@@ -260,7 +270,7 @@ const overviewContent = (
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
|
||||
<Grid item md={6}>
|
||||
<Grid item md={8} xs={12}>
|
||||
<EntityHasSubcomponentsCard variant="gridItem" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* 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 userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { DeleteEntityDialog } from './DeleteEntityDialog';
|
||||
import { ORIGIN_LOCATION_ANNOTATION } from '@backstage/catalog-model';
|
||||
import {
|
||||
AlertApi,
|
||||
alertApiRef,
|
||||
ApiProvider,
|
||||
ApiRegistry,
|
||||
} from '@backstage/core';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { catalogApiRef } from '@backstage/plugin-catalog-react';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
|
||||
describe('DeleteEntityDialog', () => {
|
||||
const alertApi: jest.Mocked<AlertApi> = {
|
||||
post: jest.fn(),
|
||||
alert$: jest.fn(),
|
||||
};
|
||||
|
||||
const catalogClient: jest.Mocked<CatalogApi> = {
|
||||
removeEntityByUid: jest.fn(),
|
||||
} as any;
|
||||
const apis = ApiRegistry.with(catalogApiRef, catalogClient).with(
|
||||
alertApiRef,
|
||||
alertApi,
|
||||
);
|
||||
|
||||
const entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
uid: '123',
|
||||
name: 'n',
|
||||
namespace: 'ns',
|
||||
annotations: {
|
||||
[ORIGIN_LOCATION_ANNOTATION]: 'url:http://example.com',
|
||||
},
|
||||
},
|
||||
spec: {},
|
||||
};
|
||||
|
||||
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
|
||||
<ApiProvider apis={apis}>{children}</ApiProvider>
|
||||
);
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('can cancel', async () => {
|
||||
const onClose = jest.fn();
|
||||
|
||||
await renderInTestApp(
|
||||
<Wrapper>
|
||||
<DeleteEntityDialog
|
||||
open
|
||||
onClose={onClose}
|
||||
onConfirm={() => {}}
|
||||
entity={entity}
|
||||
/>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
userEvent.click(screen.getByText('Cancel'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onClose).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('can delete', async () => {
|
||||
const onConfirm = jest.fn();
|
||||
|
||||
await renderInTestApp(
|
||||
<Wrapper>
|
||||
<DeleteEntityDialog
|
||||
open
|
||||
onClose={() => {}}
|
||||
onConfirm={onConfirm}
|
||||
entity={entity}
|
||||
/>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
userEvent.click(screen.getByText('Delete'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(catalogClient.removeEntityByUid).toBeCalledWith('123');
|
||||
expect(onConfirm).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles error', async () => {
|
||||
const onConfirm = jest.fn();
|
||||
|
||||
await renderInTestApp(
|
||||
<Wrapper>
|
||||
<DeleteEntityDialog
|
||||
open
|
||||
onClose={() => {}}
|
||||
onConfirm={onConfirm}
|
||||
entity={entity}
|
||||
/>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
catalogClient.removeEntityByUid.mockRejectedValue(new Error('no no no'));
|
||||
userEvent.click(screen.getByText('Delete'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(catalogClient.removeEntityByUid).toBeCalledWith('123');
|
||||
expect(alertApi.post).toBeCalledWith({ message: 'no no no' });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* 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 { Entity } from '@backstage/catalog-model';
|
||||
import { alertApiRef, useApi } from '@backstage/core';
|
||||
import { catalogApiRef } from '@backstage/plugin-catalog-react';
|
||||
import { Button, Dialog, DialogActions, DialogTitle } from '@material-ui/core';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
type Props = {
|
||||
open: boolean;
|
||||
onClose: () => any;
|
||||
onConfirm: () => any;
|
||||
entity: Entity;
|
||||
};
|
||||
|
||||
export const DeleteEntityDialog = ({
|
||||
open,
|
||||
onClose,
|
||||
onConfirm,
|
||||
entity,
|
||||
}: Props) => {
|
||||
const [busy, setBusy] = useState(false);
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
const alertApi = useApi(alertApiRef);
|
||||
|
||||
const onDelete = async () => {
|
||||
setBusy(true);
|
||||
try {
|
||||
const uid = entity.metadata.uid;
|
||||
await catalogApi.removeEntityByUid(uid!);
|
||||
onConfirm();
|
||||
} catch (err) {
|
||||
alertApi.post({ message: err.message });
|
||||
} 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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 { ApiProvider, ApiRegistry } from '@backstage/core';
|
||||
|
||||
import {
|
||||
CatalogApi,
|
||||
catalogApiRef,
|
||||
catalogRouteRef,
|
||||
EntityProvider,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
import { EntityOrphanWarning } from './EntityOrphanWarning';
|
||||
|
||||
describe('<EntityOrphanWarning />', () => {
|
||||
const catalogClient: jest.Mocked<CatalogApi> = {
|
||||
removeEntityByUid: jest.fn(),
|
||||
} as any;
|
||||
const apis = ApiRegistry.with(catalogApiRef, catalogClient);
|
||||
|
||||
it('renders EntityOrphanWarning if the entity is orphan', async () => {
|
||||
const entity = {
|
||||
apiVersion: 'v1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'software',
|
||||
description: 'This is the description',
|
||||
annotations: { 'backstage.io/orphan': 'true' },
|
||||
},
|
||||
|
||||
spec: {
|
||||
owner: 'guest',
|
||||
type: 'service',
|
||||
lifecycle: 'production',
|
||||
},
|
||||
};
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entity}>
|
||||
<EntityOrphanWarning />
|
||||
</EntityProvider>
|
||||
</ApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/create': catalogRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(
|
||||
getByText(
|
||||
'This entity is not referenced by any location and is therefore not receiving updates. Click here to delete.',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* 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 { Entity } from '@backstage/catalog-model';
|
||||
import { useRouteRef } from '@backstage/core';
|
||||
import { catalogRouteRef, useEntity } from '@backstage/plugin-catalog-react';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { DeleteEntityDialog } from './DeleteEntityDialog';
|
||||
|
||||
export const isOrphan = (entity: Entity) =>
|
||||
entity?.metadata?.annotations?.['backstage.io/orphan'] === 'true';
|
||||
|
||||
/**
|
||||
* Displays a warning alert if the entity is marked as orphan with the ability to delete said entity.
|
||||
*/
|
||||
export const EntityOrphanWarning = () => {
|
||||
const navigate = useNavigate();
|
||||
const catalogLink = useRouteRef(catalogRouteRef);
|
||||
const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false);
|
||||
const { entity } = useEntity();
|
||||
|
||||
const cleanUpAfterRemoval = async () => {
|
||||
setConfirmationDialogOpen(false);
|
||||
navigate(catalogLink());
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Alert severity="warning" onClick={() => setConfirmationDialogOpen(true)}>
|
||||
This entity is not referenced by any location and is therefore not
|
||||
receiving updates. Click here to delete.
|
||||
</Alert>
|
||||
<DeleteEntityDialog
|
||||
open={confirmationDialogOpen}
|
||||
entity={entity!}
|
||||
onConfirm={cleanUpAfterRemoval}
|
||||
onClose={() => setConfirmationDialogOpen(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { EntityOrphanWarning, isOrphan } from './EntityOrphanWarning';
|
||||
@@ -35,3 +35,4 @@ export {
|
||||
EntityLinksCard,
|
||||
EntitySystemDiagramCard,
|
||||
} from './plugin';
|
||||
export * from './components/EntityOrphanWarning';
|
||||
|
||||
Reference in New Issue
Block a user