refactor-unregister-entity

Signed-off-by: sjeyaraman <sjeyaraman@expedia.com>
This commit is contained in:
sjeyaraman
2022-06-24 14:45:13 -07:00
parent 06905f4ed0
commit 258057a4b9
8 changed files with 173 additions and 77 deletions
@@ -1,9 +1,7 @@
---
'example-app': patch
'@backstage/plugin-catalog': patch
---
Adding ability to customize the "unregister entity" menu item in the entity context menu on the entity page with options 'visible','hidden','disabled'.
With this three new options, one can hide the "unregister entity" menu item from the list, disable or keep it enabled.
Adding ability to customize the "unregister entity" menu item in the entity context menu on the entity page with options 'visible','hidden','disabled'.With this three new options, one can hide the "unregister entity" menu item from the list, disable or keep it enabled.
The boolean input for "unregister entity" will be deprecated later in favour of the above three options.
@@ -43,6 +43,7 @@ import {
} from '@backstage/plugin-azure-devops';
import { EntityBadgesDialog } from '@backstage/plugin-badges';
import {
EntityContextMenuOptions,
EntityAboutCard,
EntityDependsOnComponentsCard,
EntityDependsOnResourcesCard,
@@ -165,13 +166,7 @@ const EntityLayoutWrapper = (props: { children?: ReactNode }) => {
];
}, []);
type VisibleType = 'visible' | 'hidden' | 'disabled';
type contextMenuOptions = {
disableUnregister: boolean | VisibleType;
};
const options: contextMenuOptions = {
const options: EntityContextMenuOptions = {
disableUnregister: 'visible',
};
@@ -60,30 +60,6 @@ describe('ComponentContextMenu', () => {
expect(mockCallback).toBeCalled();
});
it('check Unregister entity button is disabled', async () => {
const mockCallback = jest.fn();
const { getByText } = await render(
<EntityContextMenu
UNSTABLE_contextMenuOptions={{ disableUnregister: 'disabled' }}
onUnregisterEntity={mockCallback}
onInspectEntity={() => {}}
/>,
);
const button = await screen.findByTestId('menu-button');
expect(button).toBeInTheDocument();
fireEvent.click(button);
const unregister = await screen.getByText('Unregister entity');
expect(unregister).toBeInTheDocument();
const unregisterSpanItem = getByText(/Unregister entity/);
const unregisterMenuListItem =
unregisterSpanItem?.parentElement?.parentElement;
expect(unregisterMenuListItem).toHaveAttribute('aria-disabled');
});
it('should call onInspectEntity on button click', async () => {
const mockCallback = jest.fn();
@@ -24,7 +24,6 @@ import {
Popover,
} from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import CancelIcon from '@material-ui/icons/Cancel';
import BugReportIcon from '@material-ui/icons/BugReport';
import MoreVert from '@material-ui/icons/MoreVert';
import React, { useState } from 'react';
@@ -32,6 +31,8 @@ import { IconComponent } from '@backstage/core-plugin-api';
import { useEntityPermission } from '@backstage/plugin-catalog-react';
import { catalogEntityDeletePermission } from '@backstage/plugin-catalog-common';
import { BackstageTheme } from '@backstage/theme';
import { UnregisterEntity, UnregisterEntityOptions } from './UnregisterEntity';
/** @public */
export type EntityContextMenuClassKey = 'button';
@@ -55,16 +56,9 @@ interface ExtraContextMenuItem {
onClick: () => void;
}
type VisibleType = 'visible' | 'hidden' | 'disabled';
// unstable context menu option, eg: disable the unregister entity menu
interface contextMenuOptions {
disableUnregister: boolean | VisibleType;
}
interface EntityContextMenuProps {
UNSTABLE_extraContextMenuItems?: ExtraContextMenuItem[];
UNSTABLE_contextMenuOptions?: contextMenuOptions;
UNSTABLE_contextMenuOptions?: UnregisterEntityOptions;
onUnregisterEntity: () => void;
onInspectEntity: () => void;
}
@@ -81,6 +75,7 @@ export function EntityContextMenu(props: EntityContextMenuProps) {
const unregisterPermission = useEntityPermission(
catalogEntityDeletePermission,
);
const isAllowed = unregisterPermission.allowed;
const onOpen = (event: React.SyntheticEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
@@ -108,35 +103,6 @@ export function EntityContextMenu(props: EntityContextMenuProps) {
<Divider key="the divider is here!" />,
];
const isBoolean =
typeof UNSTABLE_contextMenuOptions?.disableUnregister === 'boolean';
const disableUnregister =
(!unregisterPermission.allowed ||
(isBoolean
? UNSTABLE_contextMenuOptions?.disableUnregister
: UNSTABLE_contextMenuOptions?.disableUnregister === 'disabled')) ??
false;
let unregisterButton = <></>;
if (UNSTABLE_contextMenuOptions?.disableUnregister !== 'hidden') {
unregisterButton = (
<MenuItem
onClick={() => {
onClose();
onUnregisterEntity();
}}
disabled={disableUnregister}
>
<ListItemIcon>
<CancelIcon fontSize="small" />
</ListItemIcon>
<ListItemText primary="Unregister entity" />
</MenuItem>
);
}
return (
<>
<IconButton
@@ -162,7 +128,12 @@ export function EntityContextMenu(props: EntityContextMenuProps) {
>
<MenuList>
{extraItems}
{unregisterButton}
<UnregisterEntity
unregisterEntityOptions={UNSTABLE_contextMenuOptions}
isUnregisterAllowed={isAllowed}
onUnregisterEntity={onUnregisterEntity}
onClose={onClose}
/>
<MenuItem
onClick={() => {
onClose();
@@ -0,0 +1,80 @@
/*
* Copyright 2020 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 { EntityProvider } from '@backstage/plugin-catalog-react';
import { permissionApiRef } from '@backstage/plugin-permission-react';
import {
MockPermissionApi,
renderInTestApp,
TestApiProvider,
} from '@backstage/test-utils';
import { fireEvent, screen } from '@testing-library/react';
import * as React from 'react';
import { UnregisterEntity } from './UnregisterEntity';
const mockPermissionApi = new MockPermissionApi();
function render(children: React.ReactNode) {
return renderInTestApp(
<TestApiProvider apis={[[permissionApiRef, mockPermissionApi]]}>
<EntityProvider
entity={{ apiVersion: 'a', kind: 'b', metadata: { name: 'c' } }}
children={children}
/>
</TestApiProvider>,
);
}
describe('ComponentContextMenu', () => {
it('should call onUnregisterEntity on button click', async () => {
const mockCallback = jest.fn();
await render(
<UnregisterEntity
unregisterEntityOptions={{ disableUnregister: 'visible' }}
isUnregisterAllowed
onUnregisterEntity={mockCallback}
onClose={() => {}}
/>,
);
const unregister = await screen.findByText('Unregister entity');
expect(unregister).toBeInTheDocument();
fireEvent.click(unregister);
expect(mockCallback).toBeCalled();
});
it('check Unregister entity button is disabled', async () => {
const mockCallback = jest.fn();
const { getByText } = await render(
<UnregisterEntity
unregisterEntityOptions={{ disableUnregister: 'disable' }}
isUnregisterAllowed
onUnregisterEntity={mockCallback}
onClose={() => {}}
/>,
);
const unregister = await screen.getByText('Unregister entity');
expect(unregister).toBeInTheDocument();
const unregisterSpanItem = getByText(/Unregister entity/);
const unregisterMenuListItem =
unregisterSpanItem?.parentElement?.parentElement;
expect(unregisterMenuListItem).toHaveAttribute('aria-disabled');
});
});
@@ -0,0 +1,72 @@
/*
* Copyright 2020 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 React from 'react';
import { ListItemIcon, ListItemText, MenuItem } from '@material-ui/core';
import CancelIcon from '@material-ui/icons/Cancel';
type VisibleType = 'visible' | 'hidden' | 'disable';
export type UnregisterEntityOptions = {
disableUnregister: boolean | VisibleType;
};
interface UnregisterEntityProps {
unregisterEntityOptions?: UnregisterEntityOptions;
isUnregisterAllowed: boolean;
onUnregisterEntity: () => void;
onClose: () => void;
}
export function UnregisterEntity(props: UnregisterEntityProps) {
const {
unregisterEntityOptions,
isUnregisterAllowed,
onUnregisterEntity,
onClose,
} = props;
const isBoolean =
typeof unregisterEntityOptions?.disableUnregister === 'boolean';
const isDisabled =
(!isUnregisterAllowed ||
(isBoolean
? !!unregisterEntityOptions?.disableUnregister
: unregisterEntityOptions?.disableUnregister === 'disable')) ??
false;
let unregisterButton = <></>;
if (unregisterEntityOptions?.disableUnregister !== 'hidden') {
unregisterButton = (
<MenuItem
onClick={() => {
onClose();
onUnregisterEntity();
}}
disabled={isDisabled}
>
<ListItemIcon>
<CancelIcon fontSize="small" />
</ListItemIcon>
<ListItemText primary="Unregister entity" />
</MenuItem>
);
}
return <>{unregisterButton}</>;
}
@@ -142,17 +142,17 @@ interface ExtraContextMenuItem {
onClick: () => void;
}
type VisibleType = 'visible' | 'hidden' | 'disabled';
type VisibleType = 'visible' | 'hidden' | 'disable';
// unstable context menu option, eg: disable the unregister entity menu
interface contextMenuOptions {
export interface EntityContextMenuOptions {
disableUnregister: boolean | VisibleType;
}
/** @public */
export interface EntityLayoutProps {
UNSTABLE_extraContextMenuItems?: ExtraContextMenuItem[];
UNSTABLE_contextMenuOptions?: contextMenuOptions;
UNSTABLE_contextMenuOptions?: EntityContextMenuOptions;
children?: React.ReactNode;
NotFoundComponent?: React.ReactNode;
}
@@ -15,4 +15,8 @@
*/
export { EntityLayout } from './EntityLayout';
export type { EntityLayoutProps, EntityLayoutRouteProps } from './EntityLayout';
export type {
EntityLayoutProps,
EntityLayoutRouteProps,
EntityContextMenuOptions,
} from './EntityLayout';