Refactor useEntityPermission
Signed-off-by: Joon Park <joonp@spotify.com>
This commit is contained in:
@@ -6,7 +6,6 @@
|
||||
/// <reference types="react" />
|
||||
|
||||
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)
|
||||
|
||||
@@ -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<{}>) => (
|
||||
<TestApiProvider apis={[[permissionApiRef, mockPermissionApi]]}>
|
||||
<EntityProvider entity={entity} children={children} />
|
||||
</TestApiProvider>
|
||||
);
|
||||
}
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user