mirror of
https://github.com/strapi/strapi.git
synced 2025-11-16 18:19:34 +00:00
Merge pull request #16816 from strapi/enhancement/use-admin-users
Enhancement: Create useAdminUsers data fetching hook
This commit is contained in:
commit
aa461fbbc9
5
docs/docs/docs/01-core/admin/04-hooks/_category_.json
Normal file
5
docs/docs/docs/01-core/admin/04-hooks/_category_.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"label": "Hooks",
|
||||||
|
"collapsible": true,
|
||||||
|
"collapsed": true
|
||||||
|
}
|
||||||
67
docs/docs/docs/01-core/admin/04-hooks/use-admin-users.mdx
Normal file
67
docs/docs/docs/01-core/admin/04-hooks/use-admin-users.mdx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
---
|
||||||
|
title: useAdminUsers
|
||||||
|
description: API reference for the useAdminUsers hook
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
- hooks
|
||||||
|
- users
|
||||||
|
---
|
||||||
|
|
||||||
|
An abstraction around `react-query`'s `useQuery` hook. It can be used to fetch one ore more admin users.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The hooks can receive two optional parameters:
|
||||||
|
|
||||||
|
1. query params: an object containing the query params to be sent to the API. They are going to be
|
||||||
|
stringified by `qs`. All params are equal except `id`, which is used to fetch a single users, if
|
||||||
|
it is passed.
|
||||||
|
2. options: an object containing the options to be passed to `useQuery`.
|
||||||
|
|
||||||
|
It returns an object containing some of the react-query attributes.
|
||||||
|
|
||||||
|
## Typescript
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { UseQueryOptions } from 'react-query'
|
||||||
|
|
||||||
|
type User = object;
|
||||||
|
|
||||||
|
useAdminUsers(queryParams: object, reactQueryOptions: UseQueryOptions): {
|
||||||
|
users: User[];
|
||||||
|
pagination: {
|
||||||
|
page: number,
|
||||||
|
pageSize: number,
|
||||||
|
total: number,
|
||||||
|
} | null;
|
||||||
|
isLoading: boolean;
|
||||||
|
isError: boolean;
|
||||||
|
refetch: () => Promise<void>;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fetch all users
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { useAdminUsers } from 'path/to/hooks';
|
||||||
|
|
||||||
|
const MyComponent = ({ onMoveItem }) => {
|
||||||
|
const { users, isLoading, refetch } = useAdminUsers();
|
||||||
|
|
||||||
|
return /* ... */;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fetch one user
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { Box } from '@strapi/design-system';
|
||||||
|
|
||||||
|
import { useAdminUsers } from 'path/to/hooks';
|
||||||
|
|
||||||
|
const MyComponent = ({ onMoveItem }) => {
|
||||||
|
const { users: [user], isLoading, refetch } = useAdminUsers({ id: 1 });
|
||||||
|
|
||||||
|
return /* ... */;
|
||||||
|
};
|
||||||
|
```
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
export const useAdminUsers = jest.fn().mockReturnValue({
|
||||||
|
users: [],
|
||||||
|
isLoading: false,
|
||||||
|
isError: false,
|
||||||
|
});
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './useAdminUsers';
|
||||||
@ -0,0 +1,137 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { setupServer } from 'msw/node';
|
||||||
|
import { rest } from 'msw';
|
||||||
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
|
import { IntlProvider } from 'react-intl';
|
||||||
|
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||||
|
|
||||||
|
import { useAdminUsers } from '../useAdminUsers';
|
||||||
|
|
||||||
|
const server = setupServer(
|
||||||
|
rest.get('*/users', (req, res, ctx) =>
|
||||||
|
res(
|
||||||
|
ctx.json({
|
||||||
|
data: {
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
pagination: {
|
||||||
|
page: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
rest.get('*/users/1', (req, res, ctx) =>
|
||||||
|
res(
|
||||||
|
ctx.json({
|
||||||
|
data: {
|
||||||
|
id: 1,
|
||||||
|
params: {
|
||||||
|
some: req.url.searchParams.get('some'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const setup = (...args) =>
|
||||||
|
renderHook(() => useAdminUsers(...args), {
|
||||||
|
wrapper({ children }) {
|
||||||
|
const client = new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
retry: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<QueryClientProvider client={client}>
|
||||||
|
<IntlProvider locale="en" messages={{}}>
|
||||||
|
{children}
|
||||||
|
</IntlProvider>
|
||||||
|
</QueryClientProvider>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('useAdminUsers', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
server.listen();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
server.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fetches users', async () => {
|
||||||
|
const { result, waitFor } = setup();
|
||||||
|
|
||||||
|
expect(result.current.isLoading).toBe(true);
|
||||||
|
|
||||||
|
expect(result.current.users).toStrictEqual([]);
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||||
|
|
||||||
|
expect(result.current.users).toStrictEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: 1,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.current.pagination).toStrictEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
page: 1,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fetches a single user', async () => {
|
||||||
|
const { result, waitFor } = setup({ id: 1 });
|
||||||
|
|
||||||
|
expect(result.current.isLoading).toBe(true);
|
||||||
|
|
||||||
|
expect(result.current.users).toStrictEqual([]);
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||||
|
|
||||||
|
expect(result.current.users).toStrictEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: 1,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('forwards all query params except `id`', async () => {
|
||||||
|
const { result, waitFor } = setup({ id: 1, some: 'param' });
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||||
|
|
||||||
|
expect(result.current.users).toStrictEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
params: {
|
||||||
|
some: 'param',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('extends the default react-query options', async () => {
|
||||||
|
const { result } = setup(
|
||||||
|
{ id: null },
|
||||||
|
{
|
||||||
|
enabled: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.current.isLoading).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
import { useQuery } from 'react-query';
|
||||||
|
import { useFetchClient } from '@strapi/helper-plugin';
|
||||||
|
import { stringify } from 'qs';
|
||||||
|
|
||||||
|
export function useAdminUsers(params = {}, queryOptions = {}) {
|
||||||
|
const { id = '', ...queryParams } = params;
|
||||||
|
const queryString = stringify(queryParams, { encode: false });
|
||||||
|
|
||||||
|
const { get } = useFetchClient();
|
||||||
|
|
||||||
|
const { data, isError, isLoading, refetch } = useQuery(
|
||||||
|
['users', id, queryParams],
|
||||||
|
async () => {
|
||||||
|
const {
|
||||||
|
data: { data },
|
||||||
|
} = await get(`/admin/users/${id}${queryString ? `?${queryString}` : ''}`);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
queryOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
let users = [];
|
||||||
|
|
||||||
|
if (id && data) {
|
||||||
|
users = [data];
|
||||||
|
} else if (Array.isArray(data?.results)) {
|
||||||
|
users = data.results;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
users,
|
||||||
|
pagination: data?.pagination ?? null,
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
refetch,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -17,7 +17,6 @@ import {
|
|||||||
LoadingIndicatorPage,
|
LoadingIndicatorPage,
|
||||||
Link,
|
Link,
|
||||||
} from '@strapi/helper-plugin';
|
} from '@strapi/helper-plugin';
|
||||||
import { useQuery } from 'react-query';
|
|
||||||
import { Formik } from 'formik';
|
import { Formik } from 'formik';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
@ -32,11 +31,13 @@ import {
|
|||||||
} from '@strapi/design-system';
|
} from '@strapi/design-system';
|
||||||
import { ArrowLeft, Check } from '@strapi/icons';
|
import { ArrowLeft, Check } from '@strapi/icons';
|
||||||
import MagicLink from 'ee_else_ce/pages/SettingsPage/pages/Users/components/MagicLink';
|
import MagicLink from 'ee_else_ce/pages/SettingsPage/pages/Users/components/MagicLink';
|
||||||
|
|
||||||
import { formatAPIErrors, getFullName } from '../../../../../utils';
|
import { formatAPIErrors, getFullName } from '../../../../../utils';
|
||||||
import { fetchUser, putUser } from './utils/api';
|
import { putUser } from './utils/api';
|
||||||
import layout from './utils/layout';
|
import layout from './utils/layout';
|
||||||
import { editValidation } from '../utils/validations/users';
|
import { editValidation } from '../utils/validations/users';
|
||||||
import SelectRoles from '../components/SelectRoles';
|
import SelectRoles from '../components/SelectRoles';
|
||||||
|
import { useAdminUsers } from '../../../../../hooks/useAdminUsers';
|
||||||
|
|
||||||
const fieldsToPick = ['email', 'firstname', 'lastname', 'username', 'isActive', 'roles'];
|
const fieldsToPick = ['email', 'firstname', 'lastname', 'username', 'isActive', 'roles'];
|
||||||
|
|
||||||
@ -51,10 +52,14 @@ const EditPage = ({ canUpdate }) => {
|
|||||||
const { lockApp, unlockApp } = useOverlayBlocker();
|
const { lockApp, unlockApp } = useOverlayBlocker();
|
||||||
useFocusWhenNavigate();
|
useFocusWhenNavigate();
|
||||||
|
|
||||||
const { status, data } = useQuery(['user', id], () => fetchUser(id), {
|
const {
|
||||||
retry: false,
|
users: [user],
|
||||||
onError(err) {
|
isLoading,
|
||||||
const status = err.response.status;
|
} = useAdminUsers(
|
||||||
|
{ id },
|
||||||
|
{
|
||||||
|
onError(error) {
|
||||||
|
const { status } = error.response;
|
||||||
|
|
||||||
// Redirect the use to the homepage if is not allowed to read
|
// Redirect the use to the homepage if is not allowed to read
|
||||||
if (status === 403) {
|
if (status === 403) {
|
||||||
@ -67,10 +72,15 @@ const EditPage = ({ canUpdate }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
push('/');
|
push('/');
|
||||||
}
|
} else {
|
||||||
console.log(err.response.status);
|
toggleNotification({
|
||||||
},
|
type: 'warning',
|
||||||
|
message: { id: 'notification.error', defaultMessage: 'An error occured' },
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const handleSubmit = async (body, actions) => {
|
const handleSubmit = async (body, actions) => {
|
||||||
lockApp();
|
lockApp();
|
||||||
@ -113,19 +123,18 @@ const EditPage = ({ canUpdate }) => {
|
|||||||
unlockApp();
|
unlockApp();
|
||||||
};
|
};
|
||||||
|
|
||||||
const isLoading = status !== 'success';
|
|
||||||
const headerLabel = isLoading
|
const headerLabel = isLoading
|
||||||
? { id: 'app.containers.Users.EditPage.header.label-loading', defaultMessage: 'Edit user' }
|
? { id: 'app.containers.Users.EditPage.header.label-loading', defaultMessage: 'Edit user' }
|
||||||
: { id: 'app.containers.Users.EditPage.header.label', defaultMessage: 'Edit {name}' };
|
: { id: 'app.containers.Users.EditPage.header.label', defaultMessage: 'Edit {name}' };
|
||||||
|
|
||||||
const initialData = Object.keys(pick(data, fieldsToPick)).reduce((acc, current) => {
|
const initialData = Object.keys(pick(user, fieldsToPick)).reduce((acc, current) => {
|
||||||
if (current === 'roles') {
|
if (current === 'roles') {
|
||||||
acc[current] = (data?.roles || []).map(({ id }) => id);
|
acc[current] = (user?.roles || []).map(({ id }) => id);
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
acc[current] = data?.[current];
|
acc[current] = user?.[current];
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
@ -138,6 +147,7 @@ const EditPage = ({ canUpdate }) => {
|
|||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<Main aria-busy="true">
|
<Main aria-busy="true">
|
||||||
|
{/* TODO: translate */}
|
||||||
<SettingsPageTitle name="Users" />
|
<SettingsPageTitle name="Users" />
|
||||||
<HeaderLayout
|
<HeaderLayout
|
||||||
primaryAction={
|
primaryAction={
|
||||||
@ -200,9 +210,9 @@ const EditPage = ({ canUpdate }) => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<ContentLayout>
|
<ContentLayout>
|
||||||
{data?.registrationToken && (
|
{user?.registrationToken && (
|
||||||
<Box paddingBottom={6}>
|
<Box paddingBottom={6}>
|
||||||
<MagicLink registrationToken={data.registrationToken} />
|
<MagicLink registrationToken={user.registrationToken} />
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Flex direction="column" alignItems="stretch" gap={7}>
|
<Flex direction="column" alignItems="stretch" gap={7}>
|
||||||
|
|||||||
@ -1,12 +1,5 @@
|
|||||||
import { getFetchClient } from '@strapi/helper-plugin';
|
import { getFetchClient } from '@strapi/helper-plugin';
|
||||||
|
|
||||||
const fetchUser = async (id) => {
|
|
||||||
const { get } = getFetchClient();
|
|
||||||
const { data } = await get(`/admin/users/${id}`);
|
|
||||||
|
|
||||||
return data.data;
|
|
||||||
};
|
|
||||||
|
|
||||||
const putUser = async (id, body) => {
|
const putUser = async (id, body) => {
|
||||||
const { put } = getFetchClient();
|
const { put } = getFetchClient();
|
||||||
|
|
||||||
@ -15,4 +8,4 @@ const putUser = async (id, body) => {
|
|||||||
return data.data;
|
return data.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { fetchUser, putUser };
|
export { putUser };
|
||||||
|
|||||||
@ -15,7 +15,6 @@ import {
|
|||||||
Flex,
|
Flex,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@strapi/design-system';
|
} from '@strapi/design-system';
|
||||||
|
|
||||||
import { Formik } from 'formik';
|
import { Formik } from 'formik';
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
@ -24,20 +23,21 @@ import {
|
|||||||
useOverlayBlocker,
|
useOverlayBlocker,
|
||||||
useFetchClient,
|
useFetchClient,
|
||||||
} from '@strapi/helper-plugin';
|
} from '@strapi/helper-plugin';
|
||||||
import { useQueryClient, useMutation } from 'react-query';
|
import { useMutation } from 'react-query';
|
||||||
|
|
||||||
import formDataModel from 'ee_else_ce/pages/SettingsPage/pages/Users/ListPage/ModalForm/utils/formDataModel';
|
import formDataModel from 'ee_else_ce/pages/SettingsPage/pages/Users/ListPage/ModalForm/utils/formDataModel';
|
||||||
import roleSettingsForm from 'ee_else_ce/pages/SettingsPage/pages/Users/ListPage/ModalForm/utils/roleSettingsForm';
|
import roleSettingsForm from 'ee_else_ce/pages/SettingsPage/pages/Users/ListPage/ModalForm/utils/roleSettingsForm';
|
||||||
import MagicLink from 'ee_else_ce/pages/SettingsPage/pages/Users/components/MagicLink';
|
import MagicLink from 'ee_else_ce/pages/SettingsPage/pages/Users/components/MagicLink';
|
||||||
|
|
||||||
import SelectRoles from '../../components/SelectRoles';
|
import SelectRoles from '../../components/SelectRoles';
|
||||||
import layout from './utils/layout';
|
import layout from './utils/layout';
|
||||||
import schema from './utils/schema';
|
import schema from './utils/schema';
|
||||||
import stepper from './utils/stepper';
|
import stepper from './utils/stepper';
|
||||||
|
|
||||||
const ModalForm = ({ queryName, onToggle }) => {
|
const ModalForm = ({ onSuccess, onToggle }) => {
|
||||||
const [currentStep, setStep] = useState('create');
|
const [currentStep, setStep] = useState('create');
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [registrationToken, setRegistrationToken] = useState(null);
|
const [registrationToken, setRegistrationToken] = useState(null);
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const toggleNotification = useNotification();
|
const toggleNotification = useNotification();
|
||||||
const { lockApp, unlockApp } = useOverlayBlocker();
|
const { lockApp, unlockApp } = useOverlayBlocker();
|
||||||
@ -50,8 +50,7 @@ const ModalForm = ({ queryName, onToggle }) => {
|
|||||||
async onSuccess({ data }) {
|
async onSuccess({ data }) {
|
||||||
setRegistrationToken(data.data.registrationToken);
|
setRegistrationToken(data.data.registrationToken);
|
||||||
|
|
||||||
await queryClient.refetchQueries(queryName);
|
await onSuccess();
|
||||||
await queryClient.refetchQueries(['ee', 'license-limit-info']);
|
|
||||||
|
|
||||||
goNext();
|
goNext();
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
@ -216,7 +215,7 @@ const ModalForm = ({ queryName, onToggle }) => {
|
|||||||
|
|
||||||
ModalForm.propTypes = {
|
ModalForm.propTypes = {
|
||||||
onToggle: PropTypes.func.isRequired,
|
onToggle: PropTypes.func.isRequired,
|
||||||
queryName: PropTypes.array.isRequired,
|
onSuccess: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ModalForm;
|
export default ModalForm;
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import qs from 'qs';
|
||||||
import {
|
import {
|
||||||
DynamicTable,
|
DynamicTable,
|
||||||
SearchURLQuery,
|
SearchURLQuery,
|
||||||
@ -8,29 +9,28 @@ import {
|
|||||||
useFocusWhenNavigate,
|
useFocusWhenNavigate,
|
||||||
NoPermissions,
|
NoPermissions,
|
||||||
useAPIErrorHandler,
|
useAPIErrorHandler,
|
||||||
|
useFetchClient,
|
||||||
} from '@strapi/helper-plugin';
|
} from '@strapi/helper-plugin';
|
||||||
import {
|
import { ActionLayout, ContentLayout, HeaderLayout, Main } from '@strapi/design-system';
|
||||||
ActionLayout,
|
|
||||||
ContentLayout,
|
|
||||||
HeaderLayout,
|
|
||||||
Main,
|
|
||||||
useNotifyAT,
|
|
||||||
} from '@strapi/design-system';
|
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { useMutation, useQuery, useQueryClient } from 'react-query';
|
import { useMutation, useQueryClient } from 'react-query';
|
||||||
import CreateAction from 'ee_else_ce/pages/SettingsPage/pages/Users/ListPage/CreateAction';
|
import CreateAction from 'ee_else_ce/pages/SettingsPage/pages/Users/ListPage/CreateAction';
|
||||||
import useLicenseLimitNotification from 'ee_else_ce/hooks/useLicenseLimitNotification';
|
import useLicenseLimitNotification from 'ee_else_ce/hooks/useLicenseLimitNotification';
|
||||||
|
|
||||||
|
import { useAdminUsers } from '../../../../../hooks/useAdminUsers';
|
||||||
import adminPermissions from '../../../../../permissions';
|
import adminPermissions from '../../../../../permissions';
|
||||||
import TableRows from './DynamicTable/TableRows';
|
import TableRows from './DynamicTable/TableRows';
|
||||||
import Filters from '../../../components/Filters';
|
import Filters from '../../../components/Filters';
|
||||||
import ModalForm from './ModalForm';
|
import ModalForm from './ModalForm';
|
||||||
import PaginationFooter from './PaginationFooter';
|
import PaginationFooter from './PaginationFooter';
|
||||||
import { deleteData, fetchData } from './utils/api';
|
|
||||||
import displayedFilters from './utils/displayedFilters';
|
import displayedFilters from './utils/displayedFilters';
|
||||||
import tableHeaders from './utils/tableHeaders';
|
import tableHeaders from './utils/tableHeaders';
|
||||||
|
|
||||||
|
const EE_LICENSE_LIMIT_QUERY_KEY = ['ee', 'license-limit-info'];
|
||||||
|
|
||||||
const ListPage = () => {
|
const ListPage = () => {
|
||||||
|
const { post } = useFetchClient();
|
||||||
const { formatAPIError } = useAPIErrorHandler();
|
const { formatAPIError } = useAPIErrorHandler();
|
||||||
const [isModalOpened, setIsModalOpen] = useState(false);
|
const [isModalOpened, setIsModalOpen] = useState(false);
|
||||||
const {
|
const {
|
||||||
@ -42,8 +42,15 @@ const ListPage = () => {
|
|||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
useFocusWhenNavigate();
|
useFocusWhenNavigate();
|
||||||
useLicenseLimitNotification();
|
useLicenseLimitNotification();
|
||||||
const { notifyStatus } = useNotifyAT();
|
const {
|
||||||
const queryName = ['users', search];
|
users,
|
||||||
|
pagination,
|
||||||
|
isError,
|
||||||
|
isLoading,
|
||||||
|
refetchQueries: refetchAdminUsers,
|
||||||
|
} = useAdminUsers(qs.parse(search, { ignoreQueryPrefix: true }), {
|
||||||
|
enabled: canRead,
|
||||||
|
});
|
||||||
|
|
||||||
const headers = tableHeaders.map((header) => ({
|
const headers = tableHeaders.map((header) => ({
|
||||||
...header,
|
...header,
|
||||||
@ -58,43 +65,20 @@ const ListPage = () => {
|
|||||||
defaultMessage: 'Users',
|
defaultMessage: 'Users',
|
||||||
});
|
});
|
||||||
|
|
||||||
const notifyLoad = () => {
|
|
||||||
notifyStatus(
|
|
||||||
formatMessage(
|
|
||||||
{
|
|
||||||
id: 'app.utils.notify.data-loaded',
|
|
||||||
defaultMessage: 'The {target} has loaded',
|
|
||||||
},
|
|
||||||
{ target: title }
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const { status, data, isFetching } = useQuery(queryName, () => fetchData(search, notifyLoad), {
|
|
||||||
enabled: canRead,
|
|
||||||
retry: false,
|
|
||||||
onError(error) {
|
|
||||||
toggleNotification({
|
|
||||||
type: 'warning',
|
|
||||||
message: {
|
|
||||||
id: 'notification.error',
|
|
||||||
message: formatAPIError(error),
|
|
||||||
defaultMessage: 'An error occured',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleToggle = () => {
|
const handleToggle = () => {
|
||||||
setIsModalOpen((prev) => !prev);
|
setIsModalOpen((prev) => !prev);
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteAllMutation = useMutation((ids) => deleteData(ids), {
|
const deleteAllMutation = useMutation(
|
||||||
|
async (ids) => {
|
||||||
|
await post('/admin/users/batch-delete', { ids });
|
||||||
|
},
|
||||||
|
{
|
||||||
async onSuccess() {
|
async onSuccess() {
|
||||||
await queryClient.refetchQueries(queryName);
|
await refetchAdminUsers();
|
||||||
|
|
||||||
// Toggle enabled/ disabled state on the invite button
|
// Toggle enabled/ disabled state on the invite button
|
||||||
await queryClient.refetchQueries(['ee', 'license-limit-info']);
|
await queryClient.refetchQueries(EE_LICENSE_LIMIT_QUERY_KEY);
|
||||||
},
|
},
|
||||||
onError(error) {
|
onError(error) {
|
||||||
toggleNotification({
|
toggleNotification({
|
||||||
@ -106,11 +90,8 @@ const ListPage = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
}
|
||||||
|
);
|
||||||
// This can be improved but we need to show an something to the user
|
|
||||||
const isLoading =
|
|
||||||
(status !== 'success' && status !== 'error') || (status === 'success' && isFetching);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Main aria-busy={isLoading}>
|
<Main aria-busy={isLoading}>
|
||||||
@ -141,7 +122,8 @@ const ListPage = () => {
|
|||||||
|
|
||||||
<ContentLayout canRead={canRead}>
|
<ContentLayout canRead={canRead}>
|
||||||
{!canRead && <NoPermissions />}
|
{!canRead && <NoPermissions />}
|
||||||
{status === 'error' && <div>TODO: An error occurred</div>}
|
{/* TODO: Replace error message with something better */}
|
||||||
|
{isError && <div>TODO: An error occurred</div>}
|
||||||
{canRead && (
|
{canRead && (
|
||||||
<>
|
<>
|
||||||
<DynamicTable
|
<DynamicTable
|
||||||
@ -150,23 +132,32 @@ const ListPage = () => {
|
|||||||
onConfirmDeleteAll={deleteAllMutation.mutateAsync}
|
onConfirmDeleteAll={deleteAllMutation.mutateAsync}
|
||||||
onConfirmDelete={(id) => deleteAllMutation.mutateAsync([id])}
|
onConfirmDelete={(id) => deleteAllMutation.mutateAsync([id])}
|
||||||
headers={headers}
|
headers={headers}
|
||||||
rows={data?.results}
|
rows={users}
|
||||||
withBulkActions
|
withBulkActions
|
||||||
withMainAction={canDelete}
|
withMainAction={canDelete}
|
||||||
>
|
>
|
||||||
<TableRows
|
<TableRows
|
||||||
canDelete={canDelete}
|
canDelete={canDelete}
|
||||||
headers={headers}
|
headers={headers}
|
||||||
rows={data?.results || []}
|
rows={users}
|
||||||
withBulkActions
|
withBulkActions
|
||||||
withMainAction={canDelete}
|
withMainAction={canDelete}
|
||||||
/>
|
/>
|
||||||
</DynamicTable>
|
</DynamicTable>
|
||||||
<PaginationFooter pagination={data?.pagination} />
|
|
||||||
|
{pagination && <PaginationFooter pagination={pagination} />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</ContentLayout>
|
</ContentLayout>
|
||||||
{isModalOpened && <ModalForm onToggle={handleToggle} queryName={queryName} />}
|
{isModalOpened && (
|
||||||
|
<ModalForm
|
||||||
|
onSuccess={async () => {
|
||||||
|
await refetchAdminUsers();
|
||||||
|
await queryClient.refetchQueries(EE_LICENSE_LIMIT_QUERY_KEY);
|
||||||
|
}}
|
||||||
|
onToggle={handleToggle}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Main>
|
</Main>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -11,14 +11,10 @@ import Theme from '../../../../../../components/Theme';
|
|||||||
import ThemeToggleProvider from '../../../../../../components/ThemeToggleProvider';
|
import ThemeToggleProvider from '../../../../../../components/ThemeToggleProvider';
|
||||||
import ListPage from '../index';
|
import ListPage from '../index';
|
||||||
|
|
||||||
jest.mock('@strapi/helper-plugin', () => ({
|
jest.mock('../../../../../../hooks/useAdminUsers', () => ({
|
||||||
...jest.requireActual('@strapi/helper-plugin'),
|
__esModule: true,
|
||||||
getFetchClient: jest.fn(() => ({
|
useAdminUsers: jest.fn().mockReturnValue({
|
||||||
get: jest.fn().mockReturnValue({
|
users: [
|
||||||
data: {
|
|
||||||
data: {
|
|
||||||
pagination: { page: 1, pageSize: 10, pageCount: 2, total: 2 },
|
|
||||||
results: [
|
|
||||||
{
|
{
|
||||||
email: 'soup@strapi.io',
|
email: 'soup@strapi.io',
|
||||||
firstname: 'soup',
|
firstname: 'soup',
|
||||||
@ -50,11 +46,14 @@ jest.mock('@strapi/helper-plugin', () => ({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
pagination: { page: 1, pageSize: 10, pageCount: 2, total: 2 },
|
||||||
},
|
isLoading: false,
|
||||||
|
isError: false,
|
||||||
}),
|
}),
|
||||||
put: jest.fn(),
|
}));
|
||||||
})),
|
|
||||||
|
jest.mock('@strapi/helper-plugin', () => ({
|
||||||
|
...jest.requireActual('@strapi/helper-plugin'),
|
||||||
useNotification: jest.fn(),
|
useNotification: jest.fn(),
|
||||||
useFocusWhenNavigate: jest.fn(),
|
useFocusWhenNavigate: jest.fn(),
|
||||||
useRBAC: jest.fn(() => ({
|
useRBAC: jest.fn(() => ({
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
import { getFetchClient } from '@strapi/helper-plugin';
|
|
||||||
|
|
||||||
const fetchData = async (search, notify) => {
|
|
||||||
const { get } = getFetchClient();
|
|
||||||
const {
|
|
||||||
data: { data },
|
|
||||||
} = await get(`/admin/users${search}`);
|
|
||||||
|
|
||||||
notify();
|
|
||||||
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteData = async (ids) => {
|
|
||||||
const { post } = getFetchClient();
|
|
||||||
|
|
||||||
await post('/admin/users/batch-delete', { ids });
|
|
||||||
};
|
|
||||||
|
|
||||||
export { deleteData, fetchData };
|
|
||||||
@ -2,24 +2,13 @@ import { useQuery } from 'react-query';
|
|||||||
import { useNotification, useFetchClient } from '@strapi/helper-plugin';
|
import { useNotification, useFetchClient } from '@strapi/helper-plugin';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { useAdminUsers } from '../../../../../../../../admin/src/hooks/useAdminUsers';
|
||||||
|
|
||||||
const useAuditLogsData = ({ canReadAuditLogs, canReadUsers }) => {
|
const useAuditLogsData = ({ canReadAuditLogs, canReadUsers }) => {
|
||||||
const { get } = useFetchClient();
|
const { get } = useFetchClient();
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const toggleNotification = useNotification();
|
const toggleNotification = useNotification();
|
||||||
|
|
||||||
const fetchAuditLogsPage = async ({ queryKey }) => {
|
|
||||||
const search = queryKey[1];
|
|
||||||
const { data } = await get(`/admin/audit-logs${search}`);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchAllUsers = async () => {
|
|
||||||
const { data } = await get(`/admin/users`);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
const queryOptions = {
|
const queryOptions = {
|
||||||
keepPreviousData: true,
|
keepPreviousData: true,
|
||||||
retry: false,
|
retry: false,
|
||||||
@ -28,23 +17,42 @@ const useAuditLogsData = ({ canReadAuditLogs, canReadUsers }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: auditLogs,
|
users,
|
||||||
isLoading,
|
isError: isUsersError,
|
||||||
isError: isAuditLogsError,
|
isLoading: isLoadingUsers,
|
||||||
} = useQuery(['auditLogs', search], fetchAuditLogsPage, {
|
} = useAdminUsers(
|
||||||
...queryOptions,
|
{},
|
||||||
enabled: canReadAuditLogs,
|
{
|
||||||
});
|
|
||||||
|
|
||||||
const { data: users, isError: isUsersError } = useQuery(['auditLogsUsers'], fetchAllUsers, {
|
|
||||||
...queryOptions,
|
...queryOptions,
|
||||||
enabled: canReadUsers,
|
enabled: canReadUsers,
|
||||||
staleTime: 2 * (1000 * 60), // 2 minutes
|
staleTime: 2 * (1000 * 60), // 2 minutes
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const hasError = isAuditLogsError || isUsersError;
|
const {
|
||||||
|
data: auditLogs,
|
||||||
|
isLoading: isLoadingAuditLogs,
|
||||||
|
isError: isAuditLogsError,
|
||||||
|
} = useQuery(
|
||||||
|
['auditLogs', search],
|
||||||
|
async ({ queryKey }) => {
|
||||||
|
const search = queryKey[1];
|
||||||
|
const { data } = await get(`/admin/audit-logs${search}`);
|
||||||
|
|
||||||
return { auditLogs, users: users?.data, isLoading, hasError };
|
return data;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...queryOptions,
|
||||||
|
enabled: canReadAuditLogs,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
auditLogs,
|
||||||
|
users,
|
||||||
|
isLoading: isLoadingUsers || isLoadingAuditLogs,
|
||||||
|
hasError: isAuditLogsError || isUsersError,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useAuditLogsData;
|
export default useAuditLogsData;
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import getDisplayedFilters from '../utils/getDisplayedFilters';
|
import getDisplayedFilters from '../utils/getDisplayedFilters';
|
||||||
|
|
||||||
const mockUsers = {
|
const mockUsers = [
|
||||||
results: [
|
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
firstname: 'test',
|
firstname: 'test',
|
||||||
@ -16,8 +15,7 @@ const mockUsers = {
|
|||||||
username: null,
|
username: null,
|
||||||
email: 'test2@test.com',
|
email: 'test2@test.com',
|
||||||
},
|
},
|
||||||
],
|
];
|
||||||
};
|
|
||||||
|
|
||||||
describe('Audit Logs getDisplayedFilters', () => {
|
describe('Audit Logs getDisplayedFilters', () => {
|
||||||
it('should return all filters when canReadUsers is true', () => {
|
it('should return all filters when canReadUsers is true', () => {
|
||||||
|
|||||||
@ -74,7 +74,7 @@ const getDisplayedFilters = ({ formatMessage, users, canReadUsers }) => {
|
|||||||
return user.email;
|
return user.email;
|
||||||
};
|
};
|
||||||
|
|
||||||
const userOptions = users.results.map((user) => {
|
const userOptions = users.map((user) => {
|
||||||
return {
|
return {
|
||||||
label: getDisplayNameFromUser(user),
|
label: getDisplayNameFromUser(user),
|
||||||
// Combobox expects a string value
|
// Combobox expects a string value
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user