diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index f5152ffe2e..54eac880ec 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -101,7 +101,7 @@ import * as plugins from './plugins'; import { techDocsPage } from './components/techdocs/TechDocsPage'; import { ApacheAirflowPage } from '@backstage/plugin-apache-airflow'; -import { PermissionedRoute } from '@backstage/plugin-permission-react'; +import { RequirePermission } from '@backstage/plugin-permission-react'; import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common'; const app = createApp({ @@ -157,10 +157,13 @@ const routes = ( > {entityPage} - } + element={ + + + + } /> ; -// @public +// @public @deprecated export const PermissionedRoute: ( - props: ComponentProps & { + _props: ComponentProps & { errorComponent?: ReactElement | null; } & ( | { @@ -62,7 +63,27 @@ export const PermissionedRoute: ( resourceRef: string | undefined; } ), -) => JSX.Element; +) => never; + +// @public +export function RequirePermission( + props: RequirePermissionProps, +): JSX.Element | null; + +// @public +export type RequirePermissionProps = ( + | { + permission: Exclude; + resourceRef?: never; + } + | { + permission: ResourcePermission; + resourceRef: string | undefined; + } +) & { + errorPage?: ReactNode; + children: ReactNode; +}; // @public export function usePermission( diff --git a/plugins/permission-react/src/components/PermissionedRoute.tsx b/plugins/permission-react/src/components/PermissionedRoute.tsx index dd39c5bb4f..4a0046c61a 100644 --- a/plugins/permission-react/src/components/PermissionedRoute.tsx +++ b/plugins/permission-react/src/components/PermissionedRoute.tsx @@ -14,12 +14,9 @@ * limitations under the License. */ -import React, { ComponentProps, ReactElement } from 'react'; +import { ComponentProps, ReactElement } from 'react'; import { Route } from 'react-router'; -import { useApp } from '@backstage/core-plugin-api'; -import { usePermission } from '../hooks'; import { - isResourcePermission, Permission, ResourcePermission, } from '@backstage/plugin-permission-common'; @@ -29,9 +26,10 @@ import { * NotFoundErrorPage (see {@link @backstage/core-app-api#AppComponents}). * * @public + * @deprecated This component no longer works with the most recent version of `@backstage/core-app-api` and react-router v6, use {@link RequirePermission} instead. */ export const PermissionedRoute = ( - props: ComponentProps & { + _props: ComponentProps & { errorComponent?: ReactElement | null; } & ( | { @@ -44,24 +42,7 @@ export const PermissionedRoute = ( } ), ) => { - const { permission, resourceRef, errorComponent, ...otherProps } = props; - - const permissionResult = usePermission( - isResourcePermission(permission) - ? { permission, resourceRef } - : { permission }, + throw new Error( + 'PermissionedRoute is no longer supported, switch to using instead: ...}/>', ); - const app = useApp(); - const { NotFoundErrorPage } = app.getComponents(); - - let shownElement: ReactElement | null | undefined = - errorComponent === undefined ? : errorComponent; - - if (permissionResult.loading) { - shownElement = null; - } else if (permissionResult.allowed) { - shownElement = props.element; - } - - return ; }; diff --git a/plugins/permission-react/src/components/PermissionedRoute.test.tsx b/plugins/permission-react/src/components/RequirePermission.test.tsx similarity index 85% rename from plugins/permission-react/src/components/PermissionedRoute.test.tsx rename to plugins/permission-react/src/components/RequirePermission.test.tsx index 9159227925..262a602ead 100644 --- a/plugins/permission-react/src/components/PermissionedRoute.test.tsx +++ b/plugins/permission-react/src/components/RequirePermission.test.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; -import { PermissionedRoute } from '.'; +import { RequirePermission } from './RequirePermission'; import { usePermission } from '../hooks'; import { renderInTestApp } from '@backstage/test-utils'; import { createPermission } from '@backstage/plugin-permission-common'; @@ -32,14 +32,14 @@ const permission = createPermission({ attributes: { action: 'read' }, }); -describe('PermissionedRoute', () => { +describe('RequirePermission', () => { it('Does not render when loading', async () => { mockUsePermission.mockReturnValue({ loading: true, allowed: false }); const { queryByText } = await renderInTestApp( - content} + children={
content
} />, ); @@ -50,9 +50,9 @@ describe('PermissionedRoute', () => { mockUsePermission.mockReturnValue({ loading: false, allowed: true }); const { getByText } = await renderInTestApp( - content} + children={
content
} />, ); @@ -64,9 +64,9 @@ describe('PermissionedRoute', () => { await expect( renderInTestApp( - content} + children={
content
} />, ), ).rejects.toThrowError('Reached NotFound Page'); @@ -76,10 +76,10 @@ describe('PermissionedRoute', () => { mockUsePermission.mockReturnValue({ loading: false, allowed: false }); const { getByText } = await renderInTestApp( - content} - errorComponent={

Custom Error

} + children={
content
} + errorPage={

Custom Error

} />, ); diff --git a/plugins/permission-react/src/components/RequirePermission.tsx b/plugins/permission-react/src/components/RequirePermission.tsx new file mode 100644 index 0000000000..801eb0fe24 --- /dev/null +++ b/plugins/permission-react/src/components/RequirePermission.tsx @@ -0,0 +1,82 @@ +/* + * Copyright 2021 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, { ReactNode } from 'react'; +import { useApp } from '@backstage/core-plugin-api'; +import { usePermission } from '../hooks'; +import { + isResourcePermission, + Permission, + ResourcePermission, +} from '@backstage/plugin-permission-common'; + +/** + * Properties for {@link RequirePermission} + * + * @public + */ +export type RequirePermissionProps = ( + | { + permission: Exclude; + resourceRef?: never; + } + | { + permission: ResourcePermission; + resourceRef: string | undefined; + } +) & { + /** + * The error page to be displayed if the user is not allowed access. + * + * Defaults to the `NotFoundErrorPage` app component. + */ + errorPage?: ReactNode; + children: ReactNode; +}; + +/** + * A boundary that only renders its child elements if the user has the specified permission. + * + * While loading, nothing will be rendered. If the user does not have + * permission, the `errorPage` element will be rendered, falling back + * to the `NotFoundErrorPage` app component if no `errorPage` is provider. + * + * @public + */ +export function RequirePermission( + props: RequirePermissionProps, +): JSX.Element | null { + const { permission, resourceRef } = props; + const permissionResult = usePermission( + isResourcePermission(permission) + ? { permission, resourceRef } + : { permission }, + ); + const app = useApp(); + + if (permissionResult.loading) { + return null; + } else if (permissionResult.allowed) { + return <>{props.children}; + } + + if (props.errorPage) { + return <>{props.errorPage}; + } + // If no explicit error element is provided, the not found page is used as fallback. + const { NotFoundErrorPage } = app.getComponents(); + return ; +} diff --git a/plugins/permission-react/src/components/index.ts b/plugins/permission-react/src/components/index.ts index 05f13bca81..e443ad0b5e 100644 --- a/plugins/permission-react/src/components/index.ts +++ b/plugins/permission-react/src/components/index.ts @@ -15,3 +15,5 @@ */ export { PermissionedRoute } from './PermissionedRoute'; +export { RequirePermission } from './RequirePermission'; +export type { RequirePermissionProps } from './RequirePermission';