From 4785d0561ee6525b37493d3b1d8c0b14eb6d7fc1 Mon Sep 17 00:00:00 2001 From: rui ma Date: Fri, 1 Dec 2023 10:59:18 +0800 Subject: [PATCH] feat: add permission to catalog create and refresh buttion Signed-off-by: rui ma --- .changeset/large-oranges-press.md | 6 ++ plugins/catalog/package.json | 3 +- .../components/AboutCard/AboutCard.test.tsx | 72 +++++++++++++++++++ .../src/components/AboutCard/AboutCard.tsx | 7 +- .../CatalogPage/DefaultCatalogPage.test.tsx | 3 + .../CatalogPage/DefaultCatalogPage.tsx | 15 ++-- plugins/org/package.json | 1 + .../Group/GroupProfile/GroupProfileCard.tsx | 7 +- yarn.lock | 2 + 9 files changed, 109 insertions(+), 7 deletions(-) create mode 100644 .changeset/large-oranges-press.md diff --git a/.changeset/large-oranges-press.md b/.changeset/large-oranges-press.md new file mode 100644 index 0000000000..b59520bf19 --- /dev/null +++ b/.changeset/large-oranges-press.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog': patch +'@backstage/plugin-org': patch +--- + +Add permission check to catalog create and refresh button diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 12d66efedb..13beb5b7a4 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -56,6 +56,7 @@ "@backstage/integration-react": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", + "@backstage/plugin-permission-react": "workspace:^", "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/plugin-search-common": "workspace:^", "@backstage/plugin-search-react": "workspace:^", @@ -83,7 +84,7 @@ "@backstage/cli": "workspace:^", "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", - "@backstage/plugin-permission-react": "workspace:^", + "@backstage/plugin-permission-common": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx index 1679e2ddf5..e27de38913 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx @@ -33,6 +33,12 @@ import { RELATION_OWNED_BY } from '@backstage/catalog-model'; import React from 'react'; import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { permissionApiRef } from '@backstage/plugin-permission-react'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; + +const mockAuthorize = jest.fn(); + +const mockPermissionApi = { authorize: mockAuthorize }; describe('', () => { const catalogApi: jest.Mocked = { @@ -87,6 +93,7 @@ describe('', () => { ), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > @@ -143,6 +150,7 @@ describe('', () => { ), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > @@ -198,6 +206,7 @@ describe('', () => { ), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > @@ -240,6 +249,7 @@ describe('', () => { ScmIntegrationsApi.fromConfig(new ConfigReader({})), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > @@ -276,6 +286,10 @@ describe('', () => { }, }; + mockAuthorize.mockImplementation(async () => ({ + result: AuthorizeResult.ALLOW, + })); + await renderInTestApp( ', () => { ScmIntegrationsApi.fromConfig(new ConfigReader({})), ], [catalogApiRef, catalogApi], + [permissionApiRef, mockPermissionApi], ]} > @@ -308,6 +323,55 @@ describe('', () => { ); }); + it('should not render refresh button if the permission is DENY', async () => { + const entity = { + apiVersion: 'v1', + kind: 'Component', + metadata: { + annotations: { + 'backstage.io/managed-by-location': + 'url:https://backstage.io/catalog-info.yaml', + }, + name: 'software-deny', + }, + spec: { + owner: 'guest', + type: 'service', + lifecycle: 'production', + }, + }; + + mockAuthorize.mockImplementation(async () => ({ + result: AuthorizeResult.DENY, + })); + + await renderInTestApp( + + + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, + }, + ); + + expect( + screen.queryByTitle('Schedule entity refresh'), + ).not.toBeInTheDocument(); + }); + it('should not render refresh button if the location is not an url or file', async () => { const entity = { apiVersion: 'v1', @@ -330,6 +394,7 @@ describe('', () => { ScmIntegrationsApi.fromConfig(new ConfigReader({})), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > @@ -384,6 +449,7 @@ describe('', () => { ), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > @@ -440,6 +506,7 @@ describe('', () => { ), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > @@ -493,6 +560,7 @@ describe('', () => { ), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > @@ -546,6 +614,7 @@ describe('', () => { ), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > @@ -592,6 +661,7 @@ describe('', () => { ), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > @@ -659,6 +729,7 @@ describe('', () => { ), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > @@ -707,6 +778,7 @@ describe('', () => { ), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index 34aa463c39..763f6fc094 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -60,6 +60,8 @@ import DocsIcon from '@material-ui/icons/Description'; import EditIcon from '@material-ui/icons/Edit'; import { isTemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { parseEntityRef } from '@backstage/catalog-model'; +import { useEntityPermission } from '@backstage/plugin-catalog-react/alpha'; +import { catalogEntityRefreshPermission } from '@backstage/plugin-catalog-common/alpha'; const TECHDOCS_ANNOTATION = 'backstage.io/techdocs-ref'; @@ -108,6 +110,9 @@ export function AboutCard(props: AboutCardProps) { const errorApi = useApi(errorApiRef); const viewTechdocLink = useRouteRef(viewTechDocRouteRef); const templateRoute = useRouteRef(createFromTemplateRouteRef); + const { allowed: canRefresh } = useEntityPermission( + catalogEntityRefreshPermission, + ); const entitySourceLocation = getEntitySourceLocation( entity, @@ -215,7 +220,7 @@ export function AboutCard(props: AboutCardProps) { title="About" action={ <> - {allowRefresh && ( + {allowRefresh && canRefresh && ( { const origReplaceState = window.history.replaceState; @@ -168,6 +170,7 @@ describe('DefaultCatalogPage', () => { [identityApiRef, identityApi], [storageApiRef, storageApi], [starredEntitiesApiRef, new MockStarredEntitiesApi()], + [permissionApiRef, new MockPermissionApi()], ]} > {children} diff --git a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx index 8dfc29f87f..779b347b48 100644 --- a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx @@ -45,6 +45,8 @@ import { catalogTranslationRef } from '../../translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { CatalogTableColumnsFunc } from '../CatalogTable/types'; +import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; +import { usePermission } from '@backstage/plugin-permission-react'; /** @internal */ export type BaseCatalogPageProps = { @@ -60,15 +62,20 @@ export function BaseCatalogPage(props: BaseCatalogPageProps) { useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage'; const createComponentLink = useRouteRef(createComponentRouteRef); const { t } = useTranslationRef(catalogTranslationRef); + const { allowed } = usePermission({ + permission: catalogEntityCreatePermission, + }); return ( - + {allowed && ( + + )} All your software catalog entities diff --git a/plugins/org/package.json b/plugins/org/package.json index 3e006fb958..6f46f44740 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -33,6 +33,7 @@ "@backstage/catalog-model": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", + "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx index 76527362ff..33c6ee137a 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx @@ -54,6 +54,8 @@ import EditIcon from '@material-ui/icons/Edit'; import EmailIcon from '@material-ui/icons/Email'; import GroupIcon from '@material-ui/icons/Group'; import { LinksGroup } from '../../Meta'; +import { useEntityPermission } from '@backstage/plugin-catalog-react/alpha'; +import { catalogEntityRefreshPermission } from '@backstage/plugin-catalog-common/alpha'; const CardTitle = (props: { title: string }) => ( @@ -70,6 +72,9 @@ export const GroupProfileCard = (props: { const catalogApi = useApi(catalogApiRef); const alertApi = useApi(alertApiRef); const { entity: group } = useEntity(); + const { allowed: canRefresh } = useEntityPermission( + catalogEntityRefreshPermission, + ); const refreshEntity = useCallback(async () => { await catalogApi.refreshEntity(stringifyEntityRef(group)); @@ -127,7 +132,7 @@ export const GroupProfileCard = (props: { variant={props.variant} action={ <> - {allowRefresh && ( + {allowRefresh && canRefresh && (