Chore: Refactor useLicenseLimits() hook

This commit is contained in:
Gustav Hansen 2023-06-21 15:26:08 +02:00
parent dda5eeebad
commit e3324379db
7 changed files with 137 additions and 98 deletions

View File

@ -19,18 +19,18 @@ const BILLING_SELF_HOSTED_URL = 'https://strapi.io/billing/request-seats';
const useLicenseLimitNotification = () => { const useLicenseLimitNotification = () => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
let { license } = useLicenseLimits(); let { license, isError, isLoading } = useLicenseLimits();
const toggleNotification = useNotification(); const toggleNotification = useNotification();
const { pathname } = useLocation(); const { pathname } = useLocation();
const { enforcementUserCount, permittedSeats, licenseLimitStatus, isHostedOnStrapiCloud } =
license;
useEffect(() => { useEffect(() => {
if (!license?.data) { if (isError || isLoading) {
return; return;
} }
const { enforcementUserCount, permittedSeats, licenseLimitStatus, isHostedOnStrapiCloud } =
license?.data ?? {};
const shouldDisplayNotification = const shouldDisplayNotification =
!isNil(permittedSeats) && !isNil(permittedSeats) &&
!window.sessionStorage.getItem(`${STORAGE_KEY_PREFIX}-${pathname}`) && !window.sessionStorage.getItem(`${STORAGE_KEY_PREFIX}-${pathname}`) &&
@ -84,7 +84,18 @@ const useLicenseLimitNotification = () => {
}, },
}); });
} }
}, [toggleNotification, license.data, pathname, formatMessage]); }, [
toggleNotification,
license,
pathname,
formatMessage,
isLoading,
permittedSeats,
licenseLimitStatus,
enforcementUserCount,
isHostedOnStrapiCloud,
isError,
]);
}; };
export default useLicenseLimitNotification; export default useLicenseLimitNotification;

View File

@ -54,9 +54,7 @@ describe('useLicenseLimitNotification', () => {
it('should return if no license info is available', () => { it('should return if no license info is available', () => {
useLicenseLimits.mockImplementationOnce(() => ({ useLicenseLimits.mockImplementationOnce(() => ({
license: { license: {},
data: {},
},
})); }));
renderHook(() => useLicenseLimitNotification()); renderHook(() => useLicenseLimitNotification());
@ -66,10 +64,8 @@ describe('useLicenseLimitNotification', () => {
it('should not display notification if permittedSeat info is missing', () => { it('should not display notification if permittedSeat info is missing', () => {
useLicenseLimits.mockImplementationOnce(() => ({ useLicenseLimits.mockImplementationOnce(() => ({
license: { license: {
data: { ...baseLicenseInfo,
...baseLicenseInfo, permittedSeats: undefined,
permittedSeats: undefined,
},
}, },
})); }));
@ -80,10 +76,8 @@ describe('useLicenseLimitNotification', () => {
it('should not display notification if status is not AT_LIMIT or OVER_LIMIT', () => { it('should not display notification if status is not AT_LIMIT or OVER_LIMIT', () => {
useLicenseLimits.mockImplementationOnce(() => ({ useLicenseLimits.mockImplementationOnce(() => ({
license: { license: {
data: { ...baseLicenseInfo,
...baseLicenseInfo, licenseLimitStatus: 'SOME_STRING',
licenseLimitStatus: 'SOME_STRING',
},
}, },
})); }));
@ -117,10 +111,8 @@ describe('useLicenseLimitNotification', () => {
it('should display a warning notification when license limit is at limit', () => { it('should display a warning notification when license limit is at limit', () => {
useLicenseLimits.mockImplementationOnce(() => ({ useLicenseLimits.mockImplementationOnce(() => ({
license: { license: {
data: { ...baseLicenseInfo,
...baseLicenseInfo, licenseLimitStatus: 'OVER_LIMIT',
licenseLimitStatus: 'OVER_LIMIT',
},
}, },
})); }));
@ -144,10 +136,8 @@ describe('useLicenseLimitNotification', () => {
it('should have cloud billing url if is hosted on strapi cloud', () => { it('should have cloud billing url if is hosted on strapi cloud', () => {
useLicenseLimits.mockImplementationOnce(() => ({ useLicenseLimits.mockImplementationOnce(() => ({
license: { license: {
data: { ...baseLicenseInfo,
...baseLicenseInfo, isHostedOnStrapiCloud: true,
isHostedOnStrapiCloud: true,
},
}, },
})); }));

View File

@ -1,34 +1,3 @@
import { useFetchClient, useRBAC } from '@strapi/helper-plugin'; import { useLicenseLimits } from './useLicenseLimits';
import { useQuery } from 'react-query';
import { useSelector } from 'react-redux';
import { selectAdminPermissions } from '../../../../admin/src/pages/App/selectors';
const useLicenseLimits = () => {
const permissions = useSelector(selectAdminPermissions);
const rbac = useRBAC(permissions.settings.users);
const {
isLoading: isRBACLoading,
allowedActions: { canRead, canCreate, canUpdate, canDelete },
} = rbac;
const isRBACAllowed = canRead && canCreate && canUpdate && canDelete;
const { get } = useFetchClient();
const fetchLicenseLimitInfo = async () => {
const {
data: { data },
} = await get('/admin/license-limit-information');
return data;
};
const license = useQuery(['ee', 'license-limit-info'], fetchLicenseLimitInfo, {
enabled: !isRBACLoading && isRBACAllowed,
});
return { license };
};
export default useLicenseLimits; export default useLicenseLimits;

View File

@ -1,19 +1,32 @@
import React from 'react'; import React from 'react';
import { fixtures } from '@strapi/admin-test-utils'; import { fixtures } from '@strapi/admin-test-utils';
import { useFetchClient } from '@strapi/helper-plugin'; import { useRBAC } from '@strapi/helper-plugin';
import { renderHook } from '@testing-library/react'; import { renderHook, waitFor } from '@testing-library/react';
import { useQuery } from 'react-query'; import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { QueryClient, QueryClientProvider } from 'react-query';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { createStore } from 'redux'; import { createStore } from 'redux';
import useLicenseLimits from '..'; import useLicenseLimits from '..';
const server = setupServer(
...[
rest.get('*/license-limit-information', (req, res, ctx) => {
return res(
ctx.json({
data: {
attribute: 1,
},
})
);
}),
]
);
jest.mock('@strapi/helper-plugin', () => ({ jest.mock('@strapi/helper-plugin', () => ({
// TODO: Replace with msw ...jest.requireActual('@strapi/helper-plugin'),
useFetchClient: jest.fn(() => ({
get: jest.fn(),
})),
useRBAC: jest.fn(() => ({ useRBAC: jest.fn(() => ({
isLoading: false, isLoading: false,
allowedActions: { allowedActions: {
@ -25,55 +38,71 @@ jest.mock('@strapi/helper-plugin', () => ({
})), })),
})); }));
// TODO: Replace with msw
jest.mock('react-query', () => ({
useQuery: jest.fn(),
}));
const setup = (...args) => const setup = (...args) =>
renderHook(() => useLicenseLimits(...args), { renderHook(() => useLicenseLimits(...args), {
wrapper({ children }) { wrapper({ children }) {
const client = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
});
return ( return (
<Provider <Provider
store={createStore((state) => state, { store={createStore((state) => state, {
admin_app: { permissions: fixtures.permissions.app }, admin_app: { permissions: fixtures.permissions.app },
})} })}
> >
{children} <QueryClientProvider client={client}>{children}</QueryClientProvider>
</Provider> </Provider>
); );
}, },
}); });
describe('useLicenseLimits', () => { describe('useLicenseLimits', () => {
beforeAll(() => server.listen());
afterEach(() => {
server.resetHandlers();
jest.clearAllMocks();
});
it('should fetch the license limit information', async () => { it('should fetch the license limit information', async () => {
const data = { data: { id: 1, name: 'Test License' } };
useQuery.mockReturnValue({
data: { id: 1, name: 'Test License' },
isLoading: false,
});
const { result } = setup(); const { result } = setup();
expect(useFetchClient).toHaveBeenCalled(); expect(result.current.license).toEqual({});
expect(useQuery).toHaveBeenCalledWith(['ee', 'license-limit-info'], expect.any(Function), {
enabled: true, await waitFor(() => expect(result.current.isLoading).toBeFalsy());
expect(result.current.license).toEqual({
attribute: 1,
}); });
expect(result.current.license.data).toEqual(data.data);
}); });
it('data should be undefined if there is an API error', async () => { it.each(['canRead', 'canCreate', 'canUpdate', 'canDelete'])(
// const data = { data: { id: 1, name: 'Test License' } }; 'should not fetch the license limit information, when the user does not have the %s permissions',
useQuery.mockReturnValue({ async (permission) => {
isError: true, const allowedActions = {
}); canRead: true,
canCreate: true,
canUpdate: true,
canDelete: true,
};
const { result } = setup(); allowedActions[permission] = false;
expect(useFetchClient).toHaveBeenCalled(); useRBAC.mockReturnValue({
expect(useQuery).toHaveBeenCalledWith(['ee', 'license-limit-info'], expect.any(Function), { isLoading: false,
enabled: true, allowedActions,
}); });
expect(result.current.license.data).toEqual(undefined);
}); const { result } = setup();
await waitFor(() => expect(result.current.isLoading).toBeFalsy());
expect(result.current.license).toEqual({});
}
);
}); });

View File

@ -0,0 +1,31 @@
import { useFetchClient, useRBAC } from '@strapi/helper-plugin';
import { useQuery } from 'react-query';
import { useSelector } from 'react-redux';
import { selectAdminPermissions } from '../../../../admin/src/pages/App/selectors';
export function useLicenseLimits() {
const permissions = useSelector(selectAdminPermissions);
const { get } = useFetchClient();
const {
isLoading: isRBACLoading,
allowedActions: { canRead, canCreate, canUpdate, canDelete },
} = useRBAC(permissions.settings.users);
const hasPermissions = canRead && canCreate && canUpdate && canDelete;
const { data, isError, isLoading } = useQuery(
['ee', 'license-limit-info'],
async () => {
const {
data: { data },
} = await get('/admin/license-limit-information');
return data;
},
{
enabled: !isRBACLoading && hasPermissions,
}
);
return { license: data ?? {}, isError, isLoading };
}

View File

@ -13,11 +13,13 @@ const BILLING_SELF_HOSTED_URL = 'https://strapi.io/billing/request-seats';
const AdminSeatInfo = () => { const AdminSeatInfo = () => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const { license } = useLicenseLimits(); const {
const { licenseLimitStatus, enforcementUserCount, permittedSeats, isHostedOnStrapiCloud } = license: { licenseLimitStatus, enforcementUserCount, permittedSeats, isHostedOnStrapiCloud },
license?.data ?? {}; isError,
isLoading,
} = useLicenseLimits();
if (!permittedSeats) { if (isError || isLoading) {
return null; return null;
} }

View File

@ -10,8 +10,15 @@ import { useLicenseLimits } from '../../../../../../hooks';
const CreateAction = ({ onClick }) => { const CreateAction = ({ onClick }) => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const { license } = useLicenseLimits(); const {
const { permittedSeats, shouldStopCreate } = license?.data ?? {}; license: { permittedSeats, shouldStopCreate },
isError,
isLoading,
} = useLicenseLimits();
if (isError || isLoading) {
return null;
}
return ( return (
<Flex gap={2}> <Flex gap={2}>