jest.mocked(getIdentityProviders).mockImplementation(this.handleGetIdentityProviders);
jest.mocked(getUsersGroups).mockImplementation((p) => this.handleSearchUsersGroups(p));
jest.mocked(createGroup).mockImplementation((g) => this.handleCreateGroup(g));
- jest.mocked(deleteGroup).mockImplementation((g) => this.handleDeleteGroup(g));
- jest.mocked(updateGroup).mockImplementation((g) => this.handleUpdateGroup(g));
+ jest.mocked(deleteGroup).mockImplementation((id) => this.handleDeleteGroup(id));
+ jest.mocked(updateGroup).mockImplementation((id, data) => this.handleUpdateGroup(id, data));
jest.mocked(getUsersInGroup).mockImplementation(this.handlegetUsersInGroup);
jest.mocked(addUserToGroup).mockImplementation(this.handleAddUserToGroup);
jest.mocked(removeUserFromGroup).mockImplementation(this.handleRemoveUserFromGroup);
return this.reply(newGroup);
};
- handleDeleteGroup = (group: { name: string }): Promise<Record<string, never>> => {
- if (!this.groups.some((g) => g.name === group.name)) {
+ handleDeleteGroup: typeof deleteGroup = (id: string) => {
+ if (!this.groups.some((g) => g.id === id)) {
return Promise.reject();
}
- const groupToDelete = this.groups.find((g) => g.name === group.name);
+ const groupToDelete = this.groups.find((g) => g.id === id);
if (groupToDelete?.managed) {
return Promise.reject();
}
- this.groups = this.groups.filter((g) => g.name !== group.name);
- return this.reply({});
+ this.groups = this.groups.filter((g) => g.id !== id);
+ return this.reply(undefined);
};
- handleUpdateGroup = (group: {
- currentName: string;
- name?: string;
- description?: string;
- }): Promise<Record<string, never>> => {
- if (!this.groups.some((g) => group.currentName === g.name)) {
+ handleUpdateGroup: typeof updateGroup = (id, data): Promise<Record<string, never>> => {
+ const group = this.groups.find((g) => g.id === id);
+ if (group === undefined) {
return Promise.reject();
}
- this.groups.map((g) => {
- if (g.name === group.currentName) {
- if (group.name !== undefined) {
- g.name = group.name;
- }
- if (group.description !== undefined) {
- g.description = group.description;
- }
- }
- });
+ if (data.description !== undefined) {
+ group.description = data.description;
+ }
+
+ if (data.name !== undefined) {
+ group.name = data.name;
+ }
+
return this.reply({});
};
});
};
- handleSearchUsersGroups = (data: {
- f?: string;
- p?: number;
- ps?: number;
- q?: string;
- managed: boolean | undefined;
- }): Promise<{ groups: Group[]; paging: Paging }> => {
- const { paging } = this;
- if (data.p !== undefined && data.p !== paging.pageIndex) {
- this.setPaging({ pageIndex: paging.pageIndex++ });
+ handleSearchUsersGroups = (
+ params: Parameters<typeof getUsersGroups>[0],
+ ): Promise<{ groups: Group[]; page: Paging }> => {
+ const { paging: page } = this;
+ if (params.pageIndex !== undefined && params.pageIndex !== page.pageIndex) {
+ this.setPaging({ pageIndex: page.pageIndex++ });
const groups = [
mockGroup({ name: `local-group ${this.groups.length + 4}` }),
mockGroup({ name: `local-group ${this.groups.length + 5}` }),
];
- return this.reply({ paging, groups });
+ return this.reply({ page, groups });
}
if (this.isManaged) {
- if (data.managed === undefined) {
+ if (params.managed === undefined) {
return this.reply({
- paging,
- groups: this.groups.filter((g) => (data?.q ? g.name.includes(data.q) : true)),
+ page,
+ groups: this.groups.filter((g) => (params?.q ? g.name.includes(params.q) : true)),
});
}
- const groups = this.groups.filter((group) => group.managed === data.managed);
+ const groups = this.groups.filter((group) => group.managed === params.managed);
return this.reply({
- paging,
- groups: groups.filter((g) => (data?.q ? g.name.includes(data.q) : true)),
+ page,
+ groups: groups.filter((g) => (params?.q ? g.name.includes(params.q) : true)),
});
}
return this.reply({
- paging,
- groups: this.groups.filter((g) => (data?.q ? g.name.includes(data.q) : true)),
+ page,
+ groups: this.groups.filter((g) => (params?.q ? g.name.includes(params.q) : true)),
});
};
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import axios from 'axios';
import { throwGlobalError } from '../helpers/error';
-import { getJSON, post, postJSON } from '../helpers/request';
+import { axiosToCatch, getJSON, post } from '../helpers/request';
import { Group, Paging, UserGroupMember } from '../types/types';
-export function getUsersGroups(data: {
- f?: string;
- p?: number;
- ps?: number;
+const GROUPS_ENDPOINT = '/api/v2/authorizations/groups';
+
+export function getUsersGroups(params: {
q?: string;
managed: boolean | undefined;
-}): Promise<{ groups: Group[]; paging: Paging }> {
- return getJSON('/api/user_groups/search', data).catch(throwGlobalError);
+ pageIndex?: number;
+ pageSize?: number;
+}): Promise<{ groups: Group[]; page: Paging }> {
+ return axios.get(GROUPS_ENDPOINT, { params });
}
export function getUsersInGroup(data: {
}
export function createGroup(data: { description?: string; name: string }): Promise<Group> {
- return postJSON('/api/user_groups/create', data).then((r) => r.group, throwGlobalError);
+ return axios.post(GROUPS_ENDPOINT, data).then((r) => r.group);
}
-export function updateGroup(data: { description?: string; currentName: string; name?: string }) {
- return post('/api/user_groups/update', data).catch(throwGlobalError);
+export function updateGroup(
+ id: string,
+ data: {
+ name?: string;
+ description?: string;
+ },
+) {
+ return axiosToCatch.patch(`${GROUPS_ENDPOINT}/${id}`, data);
}
-export function deleteGroup(data: { name: string }) {
- return post('/api/user_groups/delete', data).catch(throwGlobalError);
+export function deleteGroup(id: string) {
+ return axios.delete(`${GROUPS_ENDPOINT}/${id}`);
}
import './groups.css';
export default function GroupsApp() {
- const [numberOfPages, setNumberOfPages] = useState<number>(1);
const [search, setSearch] = useState<string>('');
const [managed, setManaged] = useState<boolean | undefined>();
const manageProvider = useManageProvider();
- const { groups, total, isLoading } = useGroupsQueries(
- {
- q: search,
- managed,
- },
- numberOfPages,
- );
+ const { data, isLoading, fetchNextPage } = useGroupsQueries({
+ q: search,
+ managed,
+ });
+
+ const groups = data?.pages.flatMap((page) => page.groups) ?? [];
return (
<>
<ListFooter
count={groups.length}
loading={isLoading}
- loadMore={() => setNumberOfPages((n) => n + 1)}
+ loadMore={fetchNextPage}
ready={!isLoading}
- total={total}
+ total={data?.pages[0].page.total}
/>
</div>
</main>
const { mutate: deleteGroup } = useDeleteGroupMutation();
const onSubmit = () => {
- deleteGroup(
- { name: group.name },
- {
- onSuccess: props.onClose,
- },
- );
+ deleteGroup(group.id, {
+ onSuccess: props.onClose,
+ });
};
const header = translate('groups.delete_group');
import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsExplanation';
import Spinner from '../../../components/ui/Spinner';
import { translate } from '../../../helpers/l10n';
-import { omitNil } from '../../../helpers/request';
import { useCreateGroupMutation, useUpdateGroupMutation } from '../../../queries/groups';
import { Group } from '../../../types/types';
}
updateGroup(
{
- currentName: group.name,
- description,
- // pass `name` only if it has changed, otherwise the WS fails
- ...omitNil({ name: name !== group.name ? name : undefined }),
+ id: group.id,
+ data: {
+ name,
+ description,
+ },
},
{ onSuccess: props.onClose },
);
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { UseQueryResult } from '@tanstack/react-query';
+import { Paging } from '../types/types';
const notUndefined = <T>(x: T | undefined): x is T => x !== undefined;
data: notUndefined(res.data) ? mapper(res.data) : res.data,
} as UseQueryResult<R>;
};
+
+export const getNextPageParam = <T extends { page: Paging }>(params: T) =>
+ params.page.total <= params.page.pageIndex * params.page.pageSize
+ ? undefined
+ : params.page.pageIndex + 1;
+
+export const getPreviousPageParam = <T extends { page: Paging }>(params: T) =>
+ params.page.pageIndex === 1 ? undefined : params.page.pageIndex - 1;
export function mockGroup(overrides: Partial<Group> = {}): Group {
return {
- membersCount: 1,
+ id: Math.random().toString(),
name: 'Foo',
managed: false,
...overrides,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import {
- QueryFunctionContext,
- useMutation,
- useQueries,
- useQuery,
- useQueryClient,
-} from '@tanstack/react-query';
-import { range } from 'lodash';
+import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import {
createGroup,
deleteGroup,
getUsersInGroup,
updateGroup,
} from '../api/user_groups';
-import { Group } from '../types/types';
+import { getNextPageParam, getPreviousPageParam } from '../helpers/react-query';
const STALE_TIME = 4 * 60 * 1000;
export function useGroupsQueries(
getParams: Omit<Parameters<typeof getUsersGroups>[0], 'pageSize' | 'pageIndex'>,
- numberOfPages: number,
) {
- type QueryKey = [
- 'group',
- 'list',
- number,
- Omit<Parameters<typeof getUsersGroups>[0], 'pageSize' | 'pageIndex'>,
- ];
- const results = useQueries({
- queries: range(1, numberOfPages + 1).map((page: number) => ({
- queryKey: ['group', 'list', page, getParams],
- queryFn: ({ queryKey: [_u, _l, page, getParams] }: QueryFunctionContext<QueryKey>) =>
- getUsersGroups({ ...getParams, p: page }),
- staleTime: STALE_TIME,
- })),
+ return useInfiniteQuery({
+ queryKey: ['group', 'list', getParams],
+ queryFn: ({ pageParam = 1 }) => getUsersGroups({ ...getParams, pageIndex: pageParam }),
+ getNextPageParam,
+ getPreviousPageParam,
});
-
- return results.reduce<{ groups: Group[]; total: number | undefined; isLoading: boolean }>(
- (acc, { data, isLoading }) => ({
- groups: acc.groups.concat(data?.groups ?? []),
- total: data?.paging.total,
- isLoading: acc.isLoading || isLoading,
- }),
- { groups: [], total: 0, isLoading: false },
- );
}
export function useMembersCountQuery(name: string) {
const queryClient = useQueryClient();
return useMutation({
- mutationFn: (data: Parameters<typeof updateGroup>[0]) => updateGroup(data),
+ mutationFn: ({
+ id,
+ data,
+ }: {
+ id: Parameters<typeof updateGroup>[0];
+ data: Parameters<typeof updateGroup>[1];
+ }) => updateGroup(id, data),
onSuccess() {
queryClient.invalidateQueries({ queryKey: ['group', 'list'] });
},
updateUser,
} from '../api/users';
import { useCurrentUser } from '../app/components/current-user/CurrentUserContext';
+import { getNextPageParam, getPreviousPageParam } from '../helpers/react-query';
import { UserToken } from '../types/token';
import { NoticeType, RestUserBase } from '../types/users';
return useInfiniteQuery({
queryKey: ['user', 'list', getParams],
queryFn: ({ pageParam = 1 }) => getUsers<U>({ ...getParams, pageIndex: pageParam }),
- getNextPageParam: (lastPage) =>
- lastPage.page.total <= lastPage.page.pageIndex * lastPage.page.pageSize
- ? undefined
- : lastPage.page.pageIndex + 1,
- getPreviousPageParam: (firstPage) =>
- firstPage.page.pageIndex === 1 ? undefined : firstPage.page.pageIndex - 1,
+ getNextPageParam,
+ getPreviousPageParam,
});
}
}
export interface Group {
+ id: string;
default?: boolean;
- description?: string;
- membersCount: number;
name: string;
+ description?: string;
managed: boolean;
}