+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { throwGlobalError } from '../helpers/error';
-import { post } from '../helpers/request';
-
-export function addUserToGroup(data: { name: string; login?: string }) {
- return post('/api/user_groups/add_user', data).catch(throwGlobalError);
-}
-
-export function removeUserFromGroup(data: { name: string; login?: string }) {
- return post('/api/user_groups/remove_user', data).catch(throwGlobalError);
-}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { cloneDeep } from 'lodash';
-import {
- mockGroup,
- mockIdentityProvider,
- mockPaging,
- mockUserGroupMember,
-} from '../../helpers/testMocks';
+import { mockGroup, mockIdentityProvider } from '../../helpers/testMocks';
import { Group, IdentityProvider, Paging, Provider } from '../../types/types';
import { createGroup, deleteGroup, getUsersGroups, updateGroup } from '../user_groups';
export default class GroupsServiceMock {
provider: Provider | undefined;
- paging: Paging;
groups: Group[];
readOnlyGroups = [
mockGroup({ name: 'managed-group', managed: true, id: '1' }),
mockGroup({ name: 'local-group', managed: false, id: '2' }),
];
- defaultUsers = [
- mockUserGroupMember({ name: 'alice', login: 'alice.dev' }),
- mockUserGroupMember({ name: 'bob', login: 'bob.dev' }),
- mockUserGroupMember({ selected: false }),
- ];
-
constructor() {
this.groups = cloneDeep(this.readOnlyGroups);
- this.paging = mockPaging({
- pageIndex: 1,
- pageSize: 2,
- total: 200,
- });
jest.mocked(getUsersGroups).mockImplementation((p) => this.handleSearchUsersGroups(p));
jest.mocked(createGroup).mockImplementation((g) => this.handleCreateGroup(g));
this.groups = cloneDeep(this.readOnlyGroups);
}
- setPaging(paging: Partial<Paging>) {
- this.paging = { ...this.paging, ...paging };
- }
-
handleCreateGroup = (group: { name: string; description?: string }): Promise<Group> => {
const newGroup = mockGroup(group);
this.groups.push(newGroup);
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({ page, groups });
- }
- if (params.managed === undefined) {
- return this.reply({
- page,
- groups: this.groups.filter((g) => (params?.q ? g.name.includes(params.q) : true)),
- });
- }
- const groups = this.groups.filter((group) => group.managed === params.managed);
+ const pageIndex = params.pageIndex ?? 1;
+ const pageSize = params.pageSize ?? 10;
+ const groups = this.groups
+ .filter((g) => !params.q || g.name.includes(params.q))
+ .filter((g) => params.managed === undefined || g.managed === params.managed);
return this.reply({
- page,
- groups: groups.filter((g) => (params?.q ? g.name.includes(params.q) : true)),
+ page: {
+ pageIndex,
+ pageSize,
+ total: groups.length,
+ },
+ groups: groups.slice((pageIndex - 1) * pageSize, pageIndex * pageSize),
});
};
NoticeType,
RestUserDetailed,
} from '../../types/users';
-import { addUserToGroup, removeUserFromGroup } from '../legacy-group-membership';
import {
- UserGroup,
changePassword,
deleteUser,
dismissNotice,
getCurrentUser,
getIdentityProviders,
- getUserGroups,
getUsers,
postUser,
updateUser,
import GroupMembershipsServiceMock from './GroupMembersipsServiceMock';
jest.mock('../users');
-jest.mock('../legacy-group-membership');
const DEFAULT_USERS = [
mockRestUser({
}),
];
-const DEFAULT_GROUPS: UserGroup[] = [
- {
- id: 1001,
- name: 'test1',
- description: 'test1',
- selected: true,
- default: true,
- },
- {
- id: 1002,
- name: 'test2',
- description: 'test2',
- selected: true,
- default: false,
- },
- {
- id: 1003,
- name: 'test3',
- description: 'test3',
- selected: true,
- default: false,
- },
- {
- id: 1004,
- name: 'test4',
- description: 'test4',
- selected: false,
- default: false,
- },
-];
-
const DEFAULT_PASSWORD = 'test';
export default class UsersServiceMock {
isManaged = true;
users = cloneDeep(DEFAULT_USERS);
currentUser = mockLoggedInUser();
- groups = cloneDeep(DEFAULT_GROUPS);
password = DEFAULT_PASSWORD;
groupMembershipsServiceMock?: GroupMembershipsServiceMock = undefined;
constructor(groupMembershipsServiceMock?: GroupMembershipsServiceMock) {
jest.mocked(getUsers).mockImplementation(this.handleGetUsers);
jest.mocked(postUser).mockImplementation(this.handlePostUser);
jest.mocked(updateUser).mockImplementation(this.handleUpdateUser);
- jest.mocked(getUserGroups).mockImplementation(this.handleGetUserGroups);
- jest.mocked(addUserToGroup).mockImplementation(this.handleAddUserToGroup);
- jest.mocked(removeUserFromGroup).mockImplementation(this.handleRemoveUserFromGroup);
jest.mocked(changePassword).mockImplementation(this.handleChangePassword);
jest.mocked(deleteUser).mockImplementation(this.handleDeactivateUser);
jest.mocked(dismissNotice).mockImplementation(this.handleDismissNotification);
});
};
- handleGetUserGroups: typeof getUserGroups = (data) => {
- if (data.login !== 'alice.merveille') {
- return this.reply({
- paging: { pageIndex: 1, pageSize: 10, total: 0 },
- groups: [],
- });
- }
- const filteredGroups = this.groups
- .filter((g) => g.name.includes(data.q ?? ''))
- .filter((g) => {
- switch (data.selected) {
- case 'all':
- return true;
- case 'deselected':
- return !g.selected;
- default:
- return g.selected;
- }
- });
-
- return this.reply({
- paging: { pageIndex: 1, pageSize: 10, total: filteredGroups.length },
- groups: filteredGroups,
- });
- };
-
- handleAddUserToGroup: typeof addUserToGroup = ({ name }) => {
- this.groups = this.groups.map((g) => (g.name === name ? { ...g, selected: true } : g));
- return this.reply({});
- };
-
- handleRemoveUserFromGroup: typeof removeUserFromGroup = ({ name }) => {
- let isDefault = false;
- this.groups = this.groups.map((g) => {
- if (g.name === name) {
- if (g.default) {
- isDefault = true;
- return g;
- }
- return { ...g, selected: false };
- }
- return g;
- });
- return isDefault
- ? Promise.reject({
- errors: [{ msg: 'Cannot remove Default group' }],
- })
- : this.reply({});
- };
-
handleChangePassword: typeof changePassword = (data) => {
if (data.previousPassword !== this.password) {
return Promise.reject(ChangePasswordResults.OldPasswordIncorrect);
reset = () => {
this.isManaged = true;
this.users = cloneDeep(DEFAULT_USERS);
- this.groups = cloneDeep(DEFAULT_GROUPS);
this.password = DEFAULT_PASSWORD;
this.currentUser = mockLoggedInUser();
};
export function getUsersGroups(params: {
q?: string;
- managed: boolean | undefined;
+ managed?: boolean;
pageIndex?: number;
pageSize?: number;
}): Promise<{ groups: Group[]; page: Paging }> {
});
}
-export interface UserGroup {
- default: boolean;
- description: string;
- id: number;
- name: string;
- selected: boolean;
-}
-
-export function getUserGroups(data: {
- login: string;
- p?: number;
- ps?: number;
- q?: string;
- selected?: string;
-}): Promise<{ paging: Paging; groups: UserGroup[] }> {
- return getJSON('/api/users/groups', data);
-}
-
export function getIdentityProviders(): Promise<{ identityProviders: IdentityProvider[] }> {
return getJSON('/api/users/identity_providers').catch(throwGlobalError);
}
import GroupsServiceMock from '../../../api/mocks/GroupsServiceMock';
import SystemServiceMock from '../../../api/mocks/SystemServiceMock';
import UsersServiceMock from '../../../api/mocks/UsersServiceMock';
-import { mockGroupMembership, mockRestUser } from '../../../helpers/testMocks';
+import { mockGroup, mockGroupMembership, mockRestUser } from '../../../helpers/testMocks';
import { renderApp } from '../../../helpers/testReactTestingUtils';
import { byRole, byText } from '../../../helpers/testSelector';
import { Feature } from '../../../types/features';
it('should be able load more group', async () => {
const user = userEvent.setup();
+ handler.groups = new Array(15)
+ .fill(null)
+ .map((_, index) => mockGroup({ id: index.toString(), name: `group${index}` }));
renderGroupsApp();
- expect(await ui.localGroupRow.find()).toBeInTheDocument();
- expect(await screen.findAllByRole('row')).toHaveLength(3);
+ expect(await ui.showMore.find()).toBeInTheDocument();
+ expect(await screen.findAllByRole('row')).toHaveLength(11);
await user.click(await ui.showMore.find());
- expect(await screen.findAllByRole('row')).toHaveLength(5);
+ expect(await screen.findAllByRole('row')).toHaveLength(16);
});
});
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { screen, waitFor, within } from '@testing-library/react';
+import { screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import * as React from 'react';
import selectEvent from 'react-select-event';
import ComponentsServiceMock from '../../../api/mocks/ComponentsServiceMock';
import GithubProvisioningServiceMock from '../../../api/mocks/GithubProvisioningServiceMock';
+import GroupMembershipsServiceMock from '../../../api/mocks/GroupMembersipsServiceMock';
+import GroupsServiceMock from '../../../api/mocks/GroupsServiceMock';
import SettingsServiceMock from '../../../api/mocks/SettingsServiceMock';
import SystemServiceMock from '../../../api/mocks/SystemServiceMock';
import UserTokensMock from '../../../api/mocks/UserTokensMock';
import UsersServiceMock from '../../../api/mocks/UsersServiceMock';
-import { mockCurrentUser, mockLoggedInUser, mockRestUser } from '../../../helpers/testMocks';
+import {
+ mockCurrentUser,
+ mockGroup,
+ mockGroupMembership,
+ mockLoggedInUser,
+ mockRestUser,
+} from '../../../helpers/testMocks';
import { renderApp } from '../../../helpers/testReactTestingUtils';
import { byLabelText, byRole, byText } from '../../../helpers/testSelector';
import { Feature } from '../../../types/features';
const componentsHandler = new ComponentsServiceMock();
const settingsHandler = new SettingsServiceMock();
const githubHandler = new GithubProvisioningServiceMock();
+const membershipHandler = new GroupMembershipsServiceMock();
+const groupsHandler = new GroupsServiceMock();
const ui = {
createUserButton: byRole('button', { name: 'users.create_user' }),
selectedFilter: byRole('radio', { name: 'selected' }),
unselectedFilter: byRole('radio', { name: 'unselected' }),
- getGroups: () => within(ui.dialogGroups.get()).getAllByRole('checkbox'),
+ groups: byRole('dialog', { name: 'users.update_groups' }).byRole('checkbox'),
dialogTokens: byRole('dialog', { name: /users.user_X_tokens/ }),
dialogPasswords: byRole('dialog', { name: 'my_profile.password.title' }),
dialogUpdateUser: byRole('dialog', { name: 'users.update_user' }),
settingsHandler.reset();
systemHandler.reset();
githubHandler.reset();
+ membershipHandler.reset();
+ groupsHandler.reset();
});
describe('different filters combinations', () => {
it('should be able to edit the groups of a user', async () => {
const user = userEvent.setup();
+ groupsHandler.groups = new Array(105).fill(null).map((_, index) =>
+ mockGroup({
+ id: index.toString(),
+ name: `group${index}`,
+ // eslint-disable-next-line jest/no-conditional-in-test
+ description: index === 0 ? 'description99' : undefined,
+ }),
+ );
+ membershipHandler.memberships = [
+ mockGroupMembership({ userId: '2', groupId: '1' }),
+ mockGroupMembership({ userId: '2', groupId: '2' }),
+ mockGroupMembership({ userId: '2', groupId: '3' }),
+ ];
renderUsersApp();
expect(await ui.aliceRow.byText('3').find()).toBeInTheDocument();
await user.click(await ui.aliceUpdateGroupButton.find());
expect(await ui.dialogGroups.find()).toBeInTheDocument();
- expect(ui.getGroups()).toHaveLength(3);
+ expect(await ui.groups.findAll()).toHaveLength(3);
await user.click(await ui.allFilter.find());
- expect(ui.getGroups()).toHaveLength(4);
+ expect(ui.groups.getAll()).toHaveLength(105);
await user.click(ui.unselectedFilter.get());
+ expect(ui.groups.getAll()).toHaveLength(102);
expect(ui.reloadButton.query()).not.toBeInTheDocument();
- await user.click(ui.getGroups()[0]);
+ await user.click(ui.groups.getAt(0));
expect(await ui.reloadButton.find()).toBeInTheDocument();
await user.click(ui.selectedFilter.get());
- expect(ui.getGroups()).toHaveLength(4);
+ expect(ui.groups.getAll()).toHaveLength(4);
await user.click(ui.doneButton.get());
expect(ui.dialogGroups.query()).not.toBeInTheDocument();
await user.click(ui.selectedFilter.get());
- await user.click(ui.getGroups()[1]);
+ await user.click(ui.groups.getAt(1));
expect(await ui.reloadButton.find()).toBeInTheDocument();
await user.click(ui.reloadButton.get());
- expect(ui.getGroups()).toHaveLength(3);
+ expect(ui.groups.getAll()).toHaveLength(3);
- await user.type(ui.dialogGroups.byRole('searchbox').get(), '4');
+ await user.type(ui.dialogGroups.byRole('searchbox').get(), '99');
- expect(ui.getGroups()).toHaveLength(1);
+ expect(ui.groups.getAll()).toHaveLength(2);
await user.click(ui.doneButton.get());
expect(ui.dialogGroups.query()).not.toBeInTheDocument();
*/
import { LightPrimary, Modal, Note } from 'design-system';
-import { find, without } from 'lodash';
+import { find } from 'lodash';
import * as React from 'react';
-import { UserGroup, getUserGroups } from '../../../api/users';
import SelectList, {
SelectListFilter,
SelectListSearchParams,
} from '../../../components/controls/SelectList';
import { translate } from '../../../helpers/l10n';
-import { useAddUserToGroupMutation, useRemoveUserToGroupMutation } from '../../../queries/users';
+import {
+ useAddGroupMembershipMutation,
+ useRemoveGroupMembershipMutation,
+ useUserGroupsQuery,
+} from '../../../queries/group-memberships';
import { RestUserDetailed } from '../../../types/users';
interface Props {
export default function GroupsForm(props: Props) {
const { user } = props;
- const [needToReload, setNeedToReload] = React.useState<boolean>(false);
- const [lastSearchParams, setLastSearchParams] = React.useState<
- SelectListSearchParams | undefined
- >(undefined);
- const [groups, setGroups] = React.useState<UserGroup[]>([]);
- const [groupsTotalCount, setGroupsTotalCount] = React.useState<number | undefined>(undefined);
- const [selectedGroups, setSelectedGroups] = React.useState<string[]>([]);
- const { mutateAsync: addUserToGroup } = useAddUserToGroupMutation();
- const { mutateAsync: removeUserFromGroup } = useRemoveUserToGroupMutation();
+ const [query, setQuery] = React.useState<string>('');
+ const [filter, setFilter] = React.useState<SelectListFilter>(SelectListFilter.Selected);
+ const [changedGroups, setChangedGroups] = React.useState<Map<string, boolean>>(new Map());
+ const {
+ data: groups,
+ isLoading,
+ refetch,
+ } = useUserGroupsQuery({
+ q: query,
+ filter,
+ userId: user.id,
+ });
+ const { mutateAsync: addUserToGroup } = useAddGroupMembershipMutation();
+ const { mutateAsync: removeUserFromGroup } = useRemoveGroupMembershipMutation();
- const fetchUsers = (searchParams: SelectListSearchParams) =>
- getUserGroups({
- login: user.login,
- p: searchParams.page,
- ps: searchParams.pageSize,
- q: searchParams.query !== '' ? searchParams.query : undefined,
- selected: searchParams.filter,
- }).then((data) => {
- const more = searchParams.page != null && searchParams.page > 1;
- const allGroups = more ? [...groups, ...data.groups] : data.groups;
- const newSeletedGroups = data.groups.filter((gp) => gp.selected).map((gp) => gp.name);
- const allSelectedGroups = more ? [...selectedGroups, ...newSeletedGroups] : newSeletedGroups;
+ const onSearch = (searchParams: SelectListSearchParams) => {
+ if (query === searchParams.query && filter === searchParams.filter) {
+ refetch();
+ } else {
+ setQuery(searchParams.query);
+ setFilter(searchParams.filter);
+ }
- setLastSearchParams(searchParams);
- setNeedToReload(false);
- setGroups(allGroups);
- setGroupsTotalCount(data.paging.total);
- setSelectedGroups(allSelectedGroups);
- });
+ setChangedGroups(new Map());
+ };
- const handleSelect = (name: string) =>
+ const handleSelect = (groupId: string) =>
addUserToGroup({
- name,
- login: user.login,
+ userId: user.id,
+ groupId,
}).then(() => {
- setNeedToReload(true);
- setSelectedGroups([...selectedGroups, name]);
+ const newChangedGroups = new Map(changedGroups);
+ newChangedGroups.set(groupId, true);
+ setChangedGroups(newChangedGroups);
});
- const handleUnselect = (name: string) =>
+ const handleUnselect = (groupId: string) =>
removeUserFromGroup({
- name,
- login: user.login,
+ groupId,
+ userId: user.id,
}).then(() => {
- setNeedToReload(true);
- setSelectedGroups(without(selectedGroups, name));
+ const newChangedGroups = new Map(changedGroups);
+ newChangedGroups.set(groupId, false);
+ setChangedGroups(newChangedGroups);
});
- const renderElement = (name: string): React.ReactNode => {
- const group = find(groups, { name });
+ const renderElement = (groupId: string): React.ReactNode => {
+ const group = find(groups, { id: groupId });
return (
<div>
{group === undefined ? (
- <LightPrimary>{name}</LightPrimary>
+ <LightPrimary>{groupId}</LightPrimary>
) : (
<>
<LightPrimary>{group.name}</LightPrimary>
body={
<div className="sw-pt-1">
<SelectList
- elements={groups.map((group) => group.name)}
- elementsTotalCount={groupsTotalCount}
- needToReload={
- needToReload && lastSearchParams && lastSearchParams.filter !== SelectListFilter.All
- }
- onSearch={fetchUsers}
+ elements={groups?.map((group) => group.id.toString()) ?? []}
+ elementsTotalCount={groups?.length}
+ needToReload={changedGroups.size > 0 && filter !== SelectListFilter.All}
+ onSearch={onSearch}
onSelect={handleSelect}
onUnselect={handleUnselect}
renderElement={renderElement}
- selectedElements={selectedGroups}
- withPaging
+ selectedElements={
+ groups
+ ?.filter((g) => (changedGroups.has(g.id) ? changedGroups.get(g.id) : g.selected))
+ .map((g) => g.id) ?? []
+ }
+ loading={isLoading}
/>
</div>
}
import * as React from 'react';
import DateFromNow from '../../../components/intl/DateFromNow';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { useUserGroupsCountQuery, useUserTokensQuery } from '../../../queries/users';
+import { useUserGroupsCountQuery } from '../../../queries/group-memberships';
+import { useUserTokensQuery } from '../../../queries/users';
import { IdentityProvider, Provider } from '../../../types/types';
import { RestUserDetailed } from '../../../types/users';
import GroupsForm from './GroupsForm';
manageProvider: Provider | undefined;
}
-export default function UserListItem(props: UserListItemProps) {
+export default function UserListItem(props: Readonly<UserListItemProps>) {
const { identityProvider, user, manageProvider } = props;
const {
+ id,
name,
login,
avatar,
const [openTokenForm, setOpenTokenForm] = React.useState(false);
const [openGroupForm, setOpenGroupForm] = React.useState(false);
const { data: tokens, isLoading: tokensAreLoading } = useUserTokensQuery(login);
- const { data: groupsCount, isLoading: groupsAreLoading } = useUserGroupsCountQuery(login);
+ const { data: groupsCount, isLoading: groupsAreLoading } = useUserGroupsCountQuery(id);
return (
<TableRow>
import { translateWithParameters } from '../helpers/l10n';
import { getNextPageParam, getPreviousPageParam } from '../helpers/react-query';
import { RestUserDetailed } from '../types/users';
+import { useGroupsQueries } from './groups';
const DOMAIN = 'group-memberships';
const GROUP_SUB_DOMAIN = 'users-of-group';
+const USER_SUB_DOMAIN = 'groups-of-user';
export function useGroupMembersQuery(params: {
filter?: SelectListFilter;
});
}
+export function useUserGroupsQuery(params: {
+ filter?: SelectListFilter;
+ q?: string;
+ userId: string;
+}) {
+ const { q, filter, userId } = params;
+ const {
+ data: groupsPages,
+ isLoading: loadingGroups,
+ fetchNextPage: fetchNextPageGroups,
+ hasNextPage: hasNextPageGroups,
+ } = useGroupsQueries({});
+ const {
+ data: membershipsPages,
+ isLoading: loadingMemberships,
+ fetchNextPage: fetchNextPageMemberships,
+ hasNextPage: hasNextPageMemberships,
+ } = useInfiniteQuery({
+ queryKey: [DOMAIN, USER_SUB_DOMAIN, 'memberships', userId],
+ queryFn: ({ pageParam = 1 }) =>
+ getGroupMemberships({ userId, pageSize: 100, pageIndex: pageParam }),
+ getNextPageParam,
+ getPreviousPageParam,
+ });
+ if (hasNextPageGroups) {
+ fetchNextPageGroups();
+ }
+ if (hasNextPageMemberships) {
+ fetchNextPageMemberships();
+ }
+ return useQuery({
+ queryKey: [DOMAIN, USER_SUB_DOMAIN, params],
+ queryFn: () => {
+ const memberships =
+ membershipsPages?.pages.flatMap((page) => page.groupMemberships).flat() ?? [];
+ const groups = (groupsPages?.pages.flatMap((page) => page.groups).flat() ?? [])
+ .filter(
+ (group) =>
+ q === undefined ||
+ group.name.toLowerCase().includes(q.toLowerCase()) ||
+ group.description?.toLowerCase().includes(q.toLowerCase()),
+ )
+ .map((group) => ({
+ ...group,
+ selected: memberships.some((membership) => membership.groupId === group.id),
+ }));
+ switch (filter) {
+ case SelectListFilter.All:
+ return groups;
+ case SelectListFilter.Unselected:
+ return groups.filter((group) => !group.selected);
+ default:
+ return groups.filter((group) => group.selected);
+ }
+ },
+ enabled: !loadingGroups && !hasNextPageGroups && !loadingMemberships && !hasNextPageMemberships,
+ });
+}
+
export function useGroupMembersCountQuery(groupId: string) {
return useQuery({
queryKey: [DOMAIN, GROUP_SUB_DOMAIN, 'count', groupId],
});
}
+export function useUserGroupsCountQuery(userId: string) {
+ return useQuery({
+ queryKey: [DOMAIN, USER_SUB_DOMAIN, 'count', userId],
+ queryFn: () => getGroupMemberships({ userId, pageSize: 0 }).then((r) => r.page.total),
+ });
+}
+
export function useAddGroupMembershipMutation() {
const queryClient = useQueryClient();
[DOMAIN, GROUP_SUB_DOMAIN, 'count', data.groupId],
(oldData) => (oldData !== undefined ? oldData + 1 : undefined),
);
+ queryClient.setQueryData<number>(
+ [DOMAIN, USER_SUB_DOMAIN, 'count', data.userId],
+ (oldData) => (oldData !== undefined ? oldData + 1 : undefined),
+ );
+ queryClient.invalidateQueries([DOMAIN, USER_SUB_DOMAIN, 'memberships', data.userId]);
},
});
}
[DOMAIN, GROUP_SUB_DOMAIN, 'count', data.groupId],
(oldData) => (oldData !== undefined ? oldData - 1 : undefined),
);
+ queryClient.setQueryData<number>(
+ [DOMAIN, USER_SUB_DOMAIN, 'count', data.userId],
+ (oldData) => (oldData !== undefined ? oldData - 1 : undefined),
+ );
+ queryClient.invalidateQueries([DOMAIN, USER_SUB_DOMAIN, 'memberships', data.userId]);
},
});
}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
-import { addUserToGroup, removeUserFromGroup } from '../api/legacy-group-membership';
import { generateToken, getTokens, revokeToken } from '../api/user-tokens';
-import {
- deleteUser,
- dismissNotice,
- getUserGroups,
- getUsers,
- postUser,
- updateUser,
-} from '../api/users';
+import { deleteUser, dismissNotice, getUsers, postUser, updateUser } from '../api/users';
import { useCurrentUser } from '../app/components/current-user/CurrentUserContext';
import { getNextPageParam, getPreviousPageParam } from '../helpers/react-query';
import { UserToken } from '../types/token';
});
}
-export function useUserGroupsCountQuery(login: string) {
- return useQuery({
- queryKey: ['user', login, 'groups', 'total'],
- queryFn: () => getUserGroups({ login, ps: 1 }).then((r) => r.paging.total),
- });
-}
-
export function usePostUserMutation() {
const queryClient = useQueryClient();
});
}
-export function useAddUserToGroupMutation() {
- const queryClient = useQueryClient();
- return useMutation({
- mutationFn: (data: Parameters<typeof addUserToGroup>[0]) => addUserToGroup(data),
- onSuccess(_, data) {
- queryClient.setQueryData<number>(['user', data.login, 'groups', 'total'], (oldData) =>
- oldData !== undefined ? oldData + 1 : undefined,
- );
- },
- });
-}
-
-export function useRemoveUserToGroupMutation() {
- const queryClient = useQueryClient();
- return useMutation({
- mutationFn: (data: Parameters<typeof removeUserFromGroup>[0]) => removeUserFromGroup(data),
- onSuccess(_, data) {
- queryClient.setQueryData<number>(['user', data.login, 'groups', 'total'], (oldData) =>
- oldData !== undefined ? oldData - 1 : undefined,
- );
- },
- });
-}
-
export function useDismissNoticeMutation() {
const { updateDismissedNotices } = useCurrentUser();