diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md
index 8550b6a5c6..129f7b36af 100644
--- a/plugins/catalog-react/api-report.md
+++ b/plugins/catalog-react/api-report.md
@@ -6,7 +6,6 @@
///
import { ApiRef } from '@backstage/core-plugin-api';
-import { AsyncPermissionResult } from '@backstage/plugin-permission-react';
import { AsyncState } from 'react-use/lib/useAsync';
import { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client';
import { CatalogApi } from '@backstage/catalog-client';
@@ -882,9 +881,11 @@ export function useEntityOwnership(): {
};
// @public
-export function useEntityPermission(
- permission: Permission,
-): AsyncPermissionResult;
+export function useEntityPermission(permission: Permission): {
+ loading: boolean;
+ allowed: boolean;
+ error?: Error;
+};
// Warning: (ae-forgotten-export) The symbol "EntityTypeReturn" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "useEntityTypeFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
diff --git a/plugins/catalog-react/src/hooks/useEntityPermission.test.tsx b/plugins/catalog-react/src/hooks/useEntityPermission.test.tsx
index db7202de20..553d999b61 100644
--- a/plugins/catalog-react/src/hooks/useEntityPermission.test.tsx
+++ b/plugins/catalog-react/src/hooks/useEntityPermission.test.tsx
@@ -14,51 +14,84 @@
* limitations under the License.
*/
-import React, { PropsWithChildren } from 'react';
import { catalogEntityDeletePermission } from '@backstage/plugin-catalog-common';
import { renderHook } from '@testing-library/react-hooks';
import { useEntityPermission } from './useEntityPermission';
-import { MockPermissionApi, TestApiProvider } from '@backstage/test-utils';
-import { permissionApiRef } from '@backstage/plugin-permission-react';
-import { Entity } from '@backstage/catalog-model';
-import { EntityProvider } from './useEntity';
+import { useEntity } from './useEntity';
+import { usePermission } from '@backstage/plugin-permission-react';
-const mockPermissionApi = new MockPermissionApi();
-
-function createWrapper(entity?: Entity) {
- return ({ children }: PropsWithChildren<{}>) => (
-
-
-
- );
-}
+jest.mock('./useEntity', () => ({
+ ...jest.requireActual('./useEntity'),
+ useEntity: jest.fn(),
+}));
+jest.mock('@backstage/plugin-permission-react', () => ({
+ ...jest.requireActual('@backstage/plugin-permission-react'),
+ usePermission: jest.fn(),
+}));
+const useEntityMock = useEntity as jest.Mock;
+const usePermissionMock = usePermission as jest.Mock;
describe('useEntityPermission', () => {
- it('returns authorization result', async () => {
- const { result, waitForValueToChange } = renderHook(
- () => useEntityPermission(catalogEntityDeletePermission),
- {
- wrapper: createWrapper({
- apiVersion: 'a',
- kind: 'b',
- metadata: { name: 'c' },
- }),
- },
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it('returns loading when entity is loading', () => {
+ useEntityMock.mockReturnValue({ loading: true, entity: undefined });
+ usePermissionMock.mockReturnValue({ loading: false, allowed: false });
+ const { result } = renderHook(() =>
+ useEntityPermission(catalogEntityDeletePermission),
);
- await waitForValueToChange(() => result.current);
+ expect(result.current.loading).toBe(true);
+ });
+
+ it('returns loading when permission is loading', () => {
+ useEntityMock.mockReturnValue({
+ loading: false,
+ entity: {
+ apiVersion: 'a',
+ kind: 'b',
+ metadata: { name: 'c' },
+ },
+ });
+ usePermissionMock.mockReturnValue({ loading: true, allowed: false });
+ const { result } = renderHook(() =>
+ useEntityPermission(catalogEntityDeletePermission),
+ );
+
+ expect(result.current.loading).toBe(true);
+ });
+
+ it('does not authorize when there is an entity error', () => {
+ useEntityMock.mockReturnValue({
+ loading: false,
+ entity: undefined,
+ error: new Error(),
+ });
+ usePermissionMock.mockReturnValue({ loading: false, allowed: false });
+ const { result } = renderHook(() =>
+ useEntityPermission(catalogEntityDeletePermission),
+ );
+
+ expect(result.current.error).toBeInstanceOf(Error);
+ expect(result.current.allowed).toBe(false);
+ });
+
+ it('returns authorization result', () => {
+ useEntityMock.mockReturnValue({
+ loading: false,
+ entity: {
+ apiVersion: 'a',
+ kind: 'b',
+ metadata: { name: 'c' },
+ },
+ });
+ usePermissionMock.mockReturnValue({ loading: false, allowed: true });
+ const { result } = renderHook(() =>
+ useEntityPermission(catalogEntityDeletePermission),
+ );
expect(result.current.allowed).toBe(true);
});
-
- it('throws error if no entity is found', async () => {
- const { waitForNextUpdate } = renderHook(
- () => useEntityPermission(catalogEntityDeletePermission),
- {
- wrapper: createWrapper(),
- },
- );
-
- await expect(() => waitForNextUpdate()).rejects.toThrowError();
- });
});
diff --git a/plugins/catalog-react/src/hooks/useEntityPermission.ts b/plugins/catalog-react/src/hooks/useEntityPermission.ts
index 1783526e34..c1f62cfc39 100644
--- a/plugins/catalog-react/src/hooks/useEntityPermission.ts
+++ b/plugins/catalog-react/src/hooks/useEntityPermission.ts
@@ -24,12 +24,32 @@ import { useEntity } from './useEntity';
* {@link @backstage/plugin-permission-react#usePermission} hook which uses the
* current entity in context to make an authorization request for the given
* permission.
+ *
+ * Note: this hook blocks the permission request until the entity has loaded in
+ * context. If you have the entityRef and need concurrent requests, use the
+ * `usePermission` hook directly.
* @public
*/
-export function useEntityPermission(permission: Permission) {
- const { entity } = useEntity();
- if (!entity) {
- throw new Error('No entity in current context.');
+export function useEntityPermission(permission: Permission): {
+ loading: boolean;
+ allowed: boolean;
+ error?: Error;
+} {
+ const { entity, loading: loadingEntity, error: entityError } = useEntity();
+ const {
+ allowed,
+ loading: loadingPermission,
+ error: permissionError,
+ } = usePermission(
+ permission,
+ entity ? stringifyEntityRef(entity) : undefined,
+ );
+
+ if (loadingEntity || loadingPermission) {
+ return { loading: true, allowed: false };
}
- return usePermission(permission, stringifyEntityRef(entity));
+ if (entityError) {
+ return { loading: false, allowed: false, error: entityError };
+ }
+ return { loading: false, allowed, error: permissionError };
}