diff --git a/.changeset/cold-apples-film.md b/.changeset/thick-cats-kiss.md similarity index 62% rename from .changeset/cold-apples-film.md rename to .changeset/thick-cats-kiss.md index d8c7ec283d..8817115ec3 100644 --- a/.changeset/cold-apples-film.md +++ b/.changeset/thick-cats-kiss.md @@ -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. diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 24ca1b900b..c2ae001060 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -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', }; diff --git a/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.test.tsx b/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.test.tsx index 9906cd9c12..5d995828e6 100644 --- a/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.test.tsx +++ b/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.test.tsx @@ -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( - {}} - />, - ); - - 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(); diff --git a/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx b/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx index 0a5e2e1bfe..94df8dac29 100644 --- a/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx +++ b/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx @@ -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) => { setAnchorEl(event.currentTarget); @@ -108,35 +103,6 @@ export function EntityContextMenu(props: EntityContextMenuProps) { , ]; - 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 = ( - { - onClose(); - onUnregisterEntity(); - }} - disabled={disableUnregister} - > - - - - - - ); - } - return ( <> {extraItems} - {unregisterButton} + { onClose(); diff --git a/plugins/catalog/src/components/EntityContextMenu/UnregisterEntity.test.tsx b/plugins/catalog/src/components/EntityContextMenu/UnregisterEntity.test.tsx new file mode 100644 index 0000000000..70b75c7bae --- /dev/null +++ b/plugins/catalog/src/components/EntityContextMenu/UnregisterEntity.test.tsx @@ -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( + + + , + ); +} + +describe('ComponentContextMenu', () => { + it('should call onUnregisterEntity on button click', async () => { + const mockCallback = jest.fn(); + await render( + {}} + />, + ); + + 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( + {}} + />, + ); + + 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'); + }); +}); diff --git a/plugins/catalog/src/components/EntityContextMenu/UnregisterEntity.tsx b/plugins/catalog/src/components/EntityContextMenu/UnregisterEntity.tsx new file mode 100644 index 0000000000..05044bf935 --- /dev/null +++ b/plugins/catalog/src/components/EntityContextMenu/UnregisterEntity.tsx @@ -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 = ( + { + onClose(); + onUnregisterEntity(); + }} + disabled={isDisabled} + > + + + + + + ); + } + + return <>{unregisterButton}; +} diff --git a/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx b/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx index 4cbb72db5c..5d82b5ccab 100644 --- a/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx +++ b/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx @@ -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; } diff --git a/plugins/catalog/src/components/EntityLayout/index.ts b/plugins/catalog/src/components/EntityLayout/index.ts index 251d213637..73a58dfddc 100644 --- a/plugins/catalog/src/components/EntityLayout/index.ts +++ b/plugins/catalog/src/components/EntityLayout/index.ts @@ -15,4 +15,8 @@ */ export { EntityLayout } from './EntityLayout'; -export type { EntityLayoutProps, EntityLayoutRouteProps } from './EntityLayout'; +export type { + EntityLayoutProps, + EntityLayoutRouteProps, + EntityContextMenuOptions, +} from './EntityLayout';