Add caching to the useEntityPermission hook (#9064)

* Add caching to the useEntityPermission hook

Signed-off-by: Joon Park <joonp@spotify.com>

* Remove useRef

Signed-off-by: Joon Park <joonp@spotify.com>

* Use useSWR hook

Signed-off-by: Joon Park <joonp@spotify.com>
This commit is contained in:
Joon Park
2022-02-02 04:49:04 -06:00
committed by GitHub
parent 74298c1ae3
commit 6acc8f7db7
5 changed files with 62 additions and 49 deletions
@@ -19,9 +19,8 @@ import { render } from '@testing-library/react';
import { usePermission } from './usePermission';
import { AuthorizeResult } from '@backstage/plugin-permission-common';
import { TestApiProvider } from '@backstage/test-utils';
import { permissionApiRef } from '../apis';
const mockAuthorize = jest.fn();
import { PermissionApi, permissionApiRef } from '../apis';
import { SWRConfig } from 'swr';
const permission = {
name: 'access.something',
@@ -39,49 +38,48 @@ const TestComponent: FC = () => {
);
};
describe('usePermission', () => {
it('Returns loading when permissionApi has not yet responded.', () => {
mockAuthorize.mockReturnValueOnce(new Promise(() => {}));
const { getByText } = render(
<TestApiProvider
apis={[[permissionApiRef, { authorize: mockAuthorize }]]}
>
function renderComponent(mockApi: PermissionApi) {
return render(
<SWRConfig value={{ provider: () => new Map() }}>
<TestApiProvider apis={[[permissionApiRef, mockApi]]}>
<TestComponent />
</TestApiProvider>,
);
</TestApiProvider>
,
</SWRConfig>,
);
}
expect(mockAuthorize).toHaveBeenCalledWith({ permission });
describe('usePermission', () => {
const mockPermissionApi = { authorize: jest.fn() };
it('Returns loading when permissionApi has not yet responded.', () => {
mockPermissionApi.authorize.mockReturnValueOnce(new Promise(() => {}));
const { getByText } = renderComponent(mockPermissionApi);
expect(mockPermissionApi.authorize).toHaveBeenCalledWith({ permission });
expect(getByText('loading')).toBeTruthy();
});
it('Returns allowed when permissionApi allows authorization.', async () => {
mockAuthorize.mockResolvedValueOnce({ result: AuthorizeResult.ALLOW });
mockPermissionApi.authorize.mockResolvedValueOnce({
result: AuthorizeResult.ALLOW,
});
const { findByText } = render(
<TestApiProvider
apis={[[permissionApiRef, { authorize: mockAuthorize }]]}
>
<TestComponent />
</TestApiProvider>,
);
const { findByText } = renderComponent(mockPermissionApi);
expect(mockAuthorize).toHaveBeenCalledWith({ permission });
expect(mockPermissionApi.authorize).toHaveBeenCalledWith({ permission });
expect(await findByText('content')).toBeTruthy();
});
it('Returns not allowed when permissionApi denies authorization.', async () => {
mockAuthorize.mockResolvedValueOnce({ result: AuthorizeResult.DENY });
mockPermissionApi.authorize.mockResolvedValueOnce({
result: AuthorizeResult.DENY,
});
const { findByText } = render(
<TestApiProvider
apis={[[permissionApiRef, { authorize: mockAuthorize }]]}
>
<TestComponent />
</TestApiProvider>,
);
const { findByText } = renderComponent(mockPermissionApi);
expect(mockAuthorize).toHaveBeenCalledWith({ permission });
expect(mockPermissionApi.authorize).toHaveBeenCalledWith({ permission });
await expect(findByText('content')).rejects.toThrowError();
});
});
@@ -14,13 +14,13 @@
* limitations under the License.
*/
import useAsync from 'react-use/lib/useAsync';
import { useApi } from '@backstage/core-plugin-api';
import { permissionApiRef } from '../apis';
import {
AuthorizeResult,
Permission,
} from '@backstage/plugin-permission-common';
import useSWR from 'swr';
/** @public */
export type AsyncPermissionResult = {
@@ -30,9 +30,16 @@ export type AsyncPermissionResult = {
};
/**
* React hook utlity for authorization. Given a {@link @backstage/plugin-permission-common#Permission} and an optional
* resourceRef, it will return whether or not access is allowed (for the given resource, if resourceRef is provided). See
* {@link @backstage/plugin-permission-common/PermissionClient#authorize} for more details.
* React hook utility for authorization. Given a
* {@link @backstage/plugin-permission-common#Permission} and an optional
* resourceRef, it will return whether or not access is allowed (for the given
* resource, if resourceRef is provided). See
* {@link @backstage/plugin-permission-common/PermissionClient#authorize} for
* more details.
*
* Note: This hook uses stale-while-revalidate to help avoid flicker in UI
* elements that would be conditionally rendered based on the `allowed` result
* of this hook.
* @public
*/
export const usePermission = (
@@ -40,21 +47,16 @@ export const usePermission = (
resourceRef?: string,
): AsyncPermissionResult => {
const permissionApi = useApi(permissionApiRef);
const { loading, error, value } = useAsync(async () => {
const { result } = await permissionApi.authorize({
permission,
resourceRef,
});
const { data, error } = useSWR({ permission, resourceRef }, async args => {
const { result } = await permissionApi.authorize(args);
return result;
}, [permissionApi, permission, resourceRef]);
});
if (loading) {
return { loading: true, allowed: false };
}
if (error) {
return { error, loading: false, allowed: false };
}
return { loading: false, allowed: value === AuthorizeResult.ALLOW };
if (data === undefined) {
return { loading: true, allowed: false };
}
return { loading: false, allowed: data === AuthorizeResult.ALLOW };
};