Merge pull request #20786 from NeuralEvolution/bug/18436-entity-unregister-redirect
Bug/18436 entity unregister redirect
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-catalog': patch
|
||||
---
|
||||
|
||||
- Fixes bug where after unregistering an entity you are redirected to `/`.
|
||||
- Adds an optional external route `unregisterRedirect` to override this behaviour to another route.
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { EntityLayout } from '@backstage/plugin-catalog';
|
||||
import { EntityLayout, catalogPlugin } from '@backstage/plugin-catalog';
|
||||
import {
|
||||
EntityProvider,
|
||||
starredEntitiesApiRef,
|
||||
@@ -51,6 +51,7 @@ describe('EntityPage Test', () => {
|
||||
listWorkflowRuns: jest.fn().mockResolvedValue([]),
|
||||
};
|
||||
const mockPermissionApi = new MockPermissionApi();
|
||||
const rootRouteRef = catalogPlugin.routes.catalogIndex;
|
||||
|
||||
describe('cicdContent', () => {
|
||||
it('Should render GitHub Actions View', async () => {
|
||||
@@ -70,6 +71,11 @@ describe('EntityPage Test', () => {
|
||||
</EntityLayout>
|
||||
</EntityProvider>
|
||||
</TestApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(rendered.getByText('ExampleComponent')).toBeInTheDocument();
|
||||
|
||||
@@ -122,6 +122,7 @@ export const catalogPlugin: BackstagePlugin<
|
||||
},
|
||||
true
|
||||
>;
|
||||
unregisterRedirect: ExternalRouteRef<undefined, true>;
|
||||
}
|
||||
>;
|
||||
|
||||
|
||||
@@ -15,7 +15,11 @@
|
||||
*/
|
||||
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import {
|
||||
ANNOTATION_ORIGIN_LOCATION,
|
||||
Entity,
|
||||
RELATION_OWNED_BY,
|
||||
} from '@backstage/catalog-model';
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
import { AlertApi, alertApiRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
@@ -30,27 +34,30 @@ import { permissionApiRef } from '@backstage/plugin-permission-react';
|
||||
import {
|
||||
MockPermissionApi,
|
||||
renderInTestApp,
|
||||
TestApiProvider,
|
||||
TestApiRegistry,
|
||||
} from '@backstage/test-utils';
|
||||
import { act, fireEvent, screen } from '@testing-library/react';
|
||||
import { act, fireEvent, screen, waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { EntityLayout } from './EntityLayout';
|
||||
|
||||
const mockEntity = {
|
||||
kind: 'MyKind',
|
||||
metadata: {
|
||||
name: 'my-entity',
|
||||
},
|
||||
} as Entity;
|
||||
|
||||
const mockApis = TestApiRegistry.from(
|
||||
[catalogApiRef, {} as CatalogApi],
|
||||
[alertApiRef, {} as AlertApi],
|
||||
[starredEntitiesApiRef, new MockStarredEntitiesApi()],
|
||||
[permissionApiRef, new MockPermissionApi()],
|
||||
);
|
||||
import { rootRouteRef, unregisterRedirectRouteRef } from '../../routes';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
|
||||
describe('EntityLayout', () => {
|
||||
const mockEntity = {
|
||||
kind: 'MyKind',
|
||||
metadata: {
|
||||
name: 'my-entity',
|
||||
},
|
||||
} as Entity;
|
||||
|
||||
const mockApis = TestApiRegistry.from(
|
||||
[catalogApiRef, {} as CatalogApi],
|
||||
[alertApiRef, {} as AlertApi],
|
||||
[starredEntitiesApiRef, new MockStarredEntitiesApi()],
|
||||
[permissionApiRef, new MockPermissionApi()],
|
||||
);
|
||||
|
||||
it('renders simplest case', async () => {
|
||||
await renderInTestApp(
|
||||
<ApiProvider apis={mockApis}>
|
||||
@@ -65,6 +72,7 @@ describe('EntityLayout', () => {
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
'/catalog': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -97,6 +105,7 @@ describe('EntityLayout', () => {
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
'/catalog': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -120,6 +129,7 @@ describe('EntityLayout', () => {
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
'/catalog': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -146,6 +156,7 @@ describe('EntityLayout', () => {
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
'/catalog': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -178,6 +189,7 @@ describe('EntityLayout', () => {
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
'/catalog': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -225,6 +237,7 @@ describe('EntityLayout', () => {
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
'/catalog': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -253,6 +266,7 @@ describe('EntityLayout', () => {
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
'/catalog': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -265,3 +279,168 @@ describe('EntityLayout', () => {
|
||||
expect(linkParent?.tagName).toBe('P');
|
||||
});
|
||||
});
|
||||
|
||||
describe('EntityLayout - CleanUpAfterRemoval', () => {
|
||||
const entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'n',
|
||||
namespace: 'ns',
|
||||
annotations: {
|
||||
[ANNOTATION_ORIGIN_LOCATION]: 'url:http://example.com',
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
owner: 'tools',
|
||||
type: 'service',
|
||||
},
|
||||
relations: [
|
||||
{
|
||||
type: RELATION_OWNED_BY,
|
||||
targetRef: 'group:default/tools',
|
||||
},
|
||||
],
|
||||
};
|
||||
const getLocationByRef: jest.MockedFunction<CatalogApi['getLocationByRef']> =
|
||||
jest.fn();
|
||||
const getEntities: jest.MockedFunction<CatalogApi['getEntities']> = jest.fn();
|
||||
const removeEntityByUid: jest.MockedFunction<
|
||||
CatalogApi['removeEntityByUid']
|
||||
> = jest.fn();
|
||||
const getEntityFacets: jest.MockedFunction<CatalogApi['getEntityFacets']> =
|
||||
jest.fn();
|
||||
getLocationByRef.mockResolvedValue(undefined);
|
||||
getEntities.mockResolvedValue({ items: [{ ...entity }] });
|
||||
getEntityFacets.mockResolvedValue({
|
||||
facets: {
|
||||
'relations.ownedBy': [{ count: 1, value: 'group:default/tools' }],
|
||||
},
|
||||
});
|
||||
|
||||
const alertApi: AlertApi = {
|
||||
post() {
|
||||
return undefined;
|
||||
},
|
||||
alert$() {
|
||||
throw new Error('not implemented');
|
||||
},
|
||||
};
|
||||
|
||||
it('redirects to externalRouteRef when unregisterRedirectRouteRef is bound', async () => {
|
||||
await renderInTestApp(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[
|
||||
catalogApiRef,
|
||||
{
|
||||
getLocationByRef,
|
||||
getEntities,
|
||||
removeEntityByUid,
|
||||
getEntityFacets,
|
||||
},
|
||||
],
|
||||
[alertApiRef, alertApi],
|
||||
[starredEntitiesApiRef, new MockStarredEntitiesApi()],
|
||||
[permissionApiRef, new MockPermissionApi()],
|
||||
]}
|
||||
>
|
||||
<EntityProvider entity={entity}>
|
||||
<EntityLayout>
|
||||
<EntityLayout.Route path="/" title="tabbed-test-title">
|
||||
<div>tabbed-test-content</div>
|
||||
</EntityLayout.Route>
|
||||
</EntityLayout>
|
||||
</EntityProvider>
|
||||
<Routes>
|
||||
<Route path="/catalog" element={<p>catalog-page</p>} />
|
||||
<Route path="/testRoute" element={<p>external-page</p>} />
|
||||
</Routes>
|
||||
</TestApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
'/catalog': rootRouteRef,
|
||||
'/testRoute': unregisterRedirectRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const menuButton = screen.queryAllByTestId('menu-button')[0];
|
||||
fireEvent.click(menuButton);
|
||||
const listItemUnregister = screen.queryAllByRole('menuitem', {
|
||||
name: /Unregister entity/i,
|
||||
})[0];
|
||||
fireEvent.click(listItemUnregister);
|
||||
await waitFor(() => {
|
||||
const deleteEntityButton = screen.getByRole('button', {
|
||||
name: /Delete Entity/i,
|
||||
});
|
||||
act(() => {
|
||||
fireEvent.click(deleteEntityButton);
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('external-page')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('redirects to rootRouteRef when unregisterRedirectRouteRef is not bound', async () => {
|
||||
await renderInTestApp(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[
|
||||
catalogApiRef,
|
||||
{
|
||||
getLocationByRef,
|
||||
getEntities,
|
||||
removeEntityByUid,
|
||||
getEntityFacets,
|
||||
},
|
||||
],
|
||||
[alertApiRef, alertApi],
|
||||
[starredEntitiesApiRef, new MockStarredEntitiesApi()],
|
||||
[permissionApiRef, new MockPermissionApi()],
|
||||
]}
|
||||
>
|
||||
<EntityProvider entity={entity}>
|
||||
<EntityLayout>
|
||||
<EntityLayout.Route path="/" title="tabbed-test-title">
|
||||
<div>tabbed-test-content</div>
|
||||
</EntityLayout.Route>
|
||||
</EntityLayout>
|
||||
</EntityProvider>
|
||||
<Routes>
|
||||
<Route path="/catalog" element={<p>catalog-page</p>} />
|
||||
<Route path="/testRoute" element={<p>external-page</p>} />
|
||||
</Routes>
|
||||
</TestApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
'/catalog': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const menuButton = screen.queryAllByTestId('menu-button')[0];
|
||||
fireEvent.click(menuButton);
|
||||
const listItemUnregister = screen.queryAllByRole('menuitem', {
|
||||
name: /Unregister entity/i,
|
||||
})[0];
|
||||
fireEvent.click(listItemUnregister);
|
||||
await waitFor(() => {
|
||||
const deleteEntityButton = screen.getByRole('button', {
|
||||
name: /Delete Entity/i,
|
||||
});
|
||||
act(() => {
|
||||
fireEvent.click(deleteEntityButton);
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('catalog-page')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
attachComponentData,
|
||||
IconComponent,
|
||||
useElementFilter,
|
||||
useRouteRef,
|
||||
useRouteRefParams,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import {
|
||||
@@ -50,6 +51,7 @@ import { Alert } from '@material-ui/lab';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu';
|
||||
import { rootRouteRef, unregisterRedirectRouteRef } from '../../routes';
|
||||
|
||||
/** @public */
|
||||
export type EntityLayoutRouteProps = {
|
||||
@@ -229,10 +231,15 @@ export const EntityLayout = (props: EntityLayoutProps) => {
|
||||
const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false);
|
||||
const [inspectionDialogOpen, setInspectionDialogOpen] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const catalogRoute = useRouteRef(rootRouteRef);
|
||||
const unregisterRedirectRoute = useRouteRef(unregisterRedirectRouteRef);
|
||||
|
||||
const cleanUpAfterRemoval = async () => {
|
||||
setConfirmationDialogOpen(false);
|
||||
setInspectionDialogOpen(false);
|
||||
navigate('/');
|
||||
navigate(
|
||||
unregisterRedirectRoute ? unregisterRedirectRoute() : catalogRoute(),
|
||||
);
|
||||
};
|
||||
|
||||
// Make sure to close the dialog if the user clicks links in it that navigate
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
import {
|
||||
createComponentRouteRef,
|
||||
createFromTemplateRouteRef,
|
||||
unregisterRedirectRouteRef,
|
||||
viewTechDocRouteRef,
|
||||
} from './routes';
|
||||
import {
|
||||
@@ -89,6 +90,7 @@ export const catalogPlugin = createPlugin({
|
||||
createComponent: createComponentRouteRef,
|
||||
viewTechDoc: viewTechDocRouteRef,
|
||||
createFromTemplate: createFromTemplateRouteRef,
|
||||
unregisterRedirect: unregisterRedirectRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -36,6 +36,11 @@ export const createFromTemplateRouteRef = createExternalRouteRef({
|
||||
params: ['namespace', 'templateName'],
|
||||
});
|
||||
|
||||
export const unregisterRedirectRouteRef = createExternalRouteRef({
|
||||
id: 'catalog:unregister-redirect',
|
||||
optional: true,
|
||||
});
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
id: 'catalog',
|
||||
});
|
||||
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
mockedCatalogApiSupportingGroups,
|
||||
} from '../../../../__testUtils__/catalogMocks';
|
||||
import { permissionApiRef } from '@backstage/plugin-permission-react';
|
||||
import { EntityLayout } from '@backstage/plugin-catalog';
|
||||
import { EntityLayout, catalogPlugin } from '@backstage/plugin-catalog';
|
||||
import { screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { Observable } from '@backstage/types';
|
||||
@@ -63,6 +63,8 @@ const mockedStarredEntitiesApi: Partial<StarredEntitiesApi> = {
|
||||
},
|
||||
};
|
||||
|
||||
const rootRouteRef = catalogPlugin.routes.catalogIndex;
|
||||
|
||||
describe('MemberTab Test', () => {
|
||||
const groupEntity: GroupEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
@@ -122,6 +124,7 @@ describe('MemberTab Test', () => {
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
'/catalog': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -151,6 +154,7 @@ describe('MemberTab Test', () => {
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
'/catalog': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -179,6 +183,7 @@ describe('MemberTab Test', () => {
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
'/catalog': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -206,6 +211,7 @@ describe('MemberTab Test', () => {
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
'/catalog': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -231,6 +237,7 @@ describe('MemberTab Test', () => {
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
'/catalog': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -265,6 +272,7 @@ describe('MemberTab Test', () => {
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
'/catalog': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -299,6 +307,7 @@ describe('MemberTab Test', () => {
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
'/catalog': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user