diff options
author | guillaume-peoch-sonarsource <guillaume.peoch@sonarsource.com> | 2023-11-21 16:05:27 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-11-23 20:02:58 +0000 |
commit | 94a8b446ce8a36f3410a4c6c92da737f93bce5f8 (patch) | |
tree | b161c30a4e7f2cfa12b4ffa7f621a3e46f807471 /server/sonar-web/src/main/js/apps/groups | |
parent | 5925e543d8a2771bf0dda3c0ecd60308eefbae8b (diff) | |
download | sonarqube-94a8b446ce8a36f3410a4c6c92da737f93bce5f8.tar.gz sonarqube-94a8b446ce8a36f3410a4c6c92da737f93bce5f8.zip |
SONAR-21086 Refactor using ReactQuery
Diffstat (limited to 'server/sonar-web/src/main/js/apps/groups')
7 files changed, 83 insertions, 132 deletions
diff --git a/server/sonar-web/src/main/js/apps/groups/GroupsApp.tsx b/server/sonar-web/src/main/js/apps/groups/GroupsApp.tsx index 5a18957633c..aa4f890f768 100644 --- a/server/sonar-web/src/main/js/apps/groups/GroupsApp.tsx +++ b/server/sonar-web/src/main/js/apps/groups/GroupsApp.tsx @@ -18,9 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { useCallback, useEffect, useState } from 'react'; +import { useState } from 'react'; import { Helmet } from 'react-helmet-async'; -import { searchUsersGroups } from '../../api/user_groups'; import GitHubSynchronisationWarning from '../../app/components/GitHubSynchronisationWarning'; import ListFooter from '../../components/controls/ListFooter'; import { ManagedFilter } from '../../components/controls/ManagedFilter'; @@ -28,67 +27,37 @@ import SearchBox from '../../components/controls/SearchBox'; import Suggestions from '../../components/embed-docs-modal/Suggestions'; import { Provider, useManageProvider } from '../../components/hooks/useManageProvider'; import { translate } from '../../helpers/l10n'; -import { Group, Paging } from '../../types/types'; +import { useGroupsQueries } from '../../queries/groups'; import Header from './components/Header'; import List from './components/List'; import './groups.css'; export default function GroupsApp() { - const [loading, setLoading] = useState<boolean>(true); - const [paging, setPaging] = useState<Paging>(); + const [numberOfPages, setNumberOfPages] = useState<number>(1); const [search, setSearch] = useState<string>(''); - const [groups, setGroups] = useState<Group[]>([]); const [managed, setManaged] = useState<boolean | undefined>(); const manageProvider = useManageProvider(); - const fetchGroups = useCallback(async () => { - setLoading(true); - try { - const { groups, paging } = await searchUsersGroups({ - q: search, - managed, - }); - setGroups(groups); - setPaging(paging); - } finally { - setLoading(false); - } - }, [search, managed]); - - const fetchMoreGroups = useCallback(async () => { - if (!paging) { - return; - } - setLoading(true); - try { - const { groups: nextGroups, paging: nextPage } = await searchUsersGroups({ - q: search, - managed, - p: paging.pageIndex + 1, - }); - setPaging(nextPage); - setGroups([...groups, ...nextGroups]); - } finally { - setLoading(false); - } - }, [groups, search, managed, paging]); - - useEffect(() => { - fetchGroups(); - }, [search, managed]); + const { groups, total, isLoading } = useGroupsQueries( + { + q: search, + managed, + }, + numberOfPages, + ); return ( <> <Suggestions suggestions="user_groups" /> <Helmet defer={false} title={translate('user_groups.page')} /> <main className="page page-limited" id="groups-page"> - <Header reload={fetchGroups} manageProvider={manageProvider} /> + <Header manageProvider={manageProvider} /> {manageProvider === Provider.Github && <GitHubSynchronisationWarning short />} <div className="display-flex-justify-start big-spacer-bottom big-spacer-top"> <ManagedFilter manageProvider={manageProvider} - loading={loading} + loading={isLoading} managed={managed} setManaged={setManaged} /> @@ -101,19 +70,17 @@ export default function GroupsApp() { /> </div> - <List groups={groups} reload={fetchGroups} manageProvider={manageProvider} /> + <List groups={groups} manageProvider={manageProvider} /> - {paging !== undefined && ( - <div id="groups-list-footer"> - <ListFooter - count={groups.length} - loading={loading} - loadMore={fetchMoreGroups} - ready={!loading} - total={paging.total} - /> - </div> - )} + <div id="groups-list-footer"> + <ListFooter + count={groups.length} + loading={isLoading} + loadMore={() => setNumberOfPages((n) => n + 1)} + ready={!isLoading} + total={total} + /> + </div> </main> </> ); diff --git a/server/sonar-web/src/main/js/apps/groups/__tests__/GroupsApp-it.tsx b/server/sonar-web/src/main/js/apps/groups/__tests__/GroupsApp-it.tsx index d6e591c004b..34263fe216e 100644 --- a/server/sonar-web/src/main/js/apps/groups/__tests__/GroupsApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/groups/__tests__/GroupsApp-it.tsx @@ -64,8 +64,8 @@ const ui = { membersDialog: byRole('dialog', { name: 'users.update' }), getMembers: () => within(ui.membersDialog.get()).getAllByRole('checkbox'), - managedGroupRow: byRole('row', { name: 'managed-group 1' }), - githubManagedGroupRow: byRole('row', { name: 'managed-group github 1' }), + managedGroupRow: byRole('row', { name: 'managed-group 3' }), + githubManagedGroupRow: byRole('row', { name: 'managed-group github 3' }), managedGroupEditMembersButton: byRole('button', { name: 'groups.users.edit.managed-group' }), managedGroupViewMembersButton: byRole('button', { name: 'groups.users.view.managed-group' }), @@ -75,13 +75,13 @@ const ui = { managedEditButton: byRole('button', { name: 'groups.edit.managed-group' }), - localGroupRow: byRole('row', { name: 'local-group 1' }), + localGroupRow: byRole('row', { name: 'local-group 3' }), localGroupEditMembersButton: byRole('button', { name: 'groups.users.edit.local-group' }), - localGroupRow2: byRole('row', { name: 'local-group 2 1 group 2 is loco!' }), - editedLocalGroupRow: byRole('row', { name: 'local-group 3 1 group 3 rocks!' }), + localGroupRow2: byRole('row', { name: 'local-group 2 3 group 2 is loco!' }), + editedLocalGroupRow: byRole('row', { name: 'local-group 3 3 group 3 rocks!' }), localEditButton: byRole('button', { name: 'groups.edit.local-group' }), localGroupRowWithLocalBadge: byRole('row', { - name: 'local-group local 1', + name: 'local-group local 3', }), githubProvisioningPending: byText(/synchronization_pending/), @@ -299,7 +299,7 @@ describe('in manage mode', () => { await act(async () => expect(await ui.localAndManagedFilter.find()).toBeInTheDocument()); - expect(ui.localGroupRowWithLocalBadge.get()).toBeInTheDocument(); + expect(await ui.localGroupRowWithLocalBadge.find()).toBeInTheDocument(); expect(ui.managedGroupRow.get()).toBeInTheDocument(); }); @@ -311,8 +311,8 @@ describe('in manage mode', () => { await user.click(await ui.managedFilter.find()); }); + expect(await ui.managedGroupRow.find()).toBeInTheDocument(); expect(ui.localGroupRow.query()).not.toBeInTheDocument(); - expect(ui.managedGroupRow.get()).toBeInTheDocument(); }); it('should render list of local groups', async () => { @@ -323,7 +323,7 @@ describe('in manage mode', () => { await user.click(await ui.localFilter.find()); }); - expect(ui.localGroupRowWithLocalBadge.get()).toBeInTheDocument(); + expect(await ui.localGroupRowWithLocalBadge.find()).toBeInTheDocument(); expect(ui.managedGroupRow.query()).not.toBeInTheDocument(); }); @@ -395,7 +395,7 @@ describe('in manage mode', () => { }); expect( - within(ui.githubManagedGroupRow.get()).getByRole('img', { name: 'github' }), + within(await ui.githubManagedGroupRow.find()).getByRole('img', { name: 'github' }), ).toBeInTheDocument(); }); }); diff --git a/server/sonar-web/src/main/js/apps/groups/components/DeleteGroupForm.tsx b/server/sonar-web/src/main/js/apps/groups/components/DeleteGroupForm.tsx index 3345f248d98..6b491854dbe 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/DeleteGroupForm.tsx +++ b/server/sonar-web/src/main/js/apps/groups/components/DeleteGroupForm.tsx @@ -18,30 +18,33 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { useCallback } from 'react'; -import { deleteGroup } from '../../../api/user_groups'; import SimpleModal from '../../../components/controls/SimpleModal'; import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons'; import Spinner from '../../../components/ui/Spinner'; import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { useDeleteGroupMutation } from '../../../queries/groups'; import { Group } from '../../../types/types'; interface Props { group: Group; onClose: () => void; - reload: () => void; } export default function DeleteGroupForm(props: Props) { - const header = translate('groups.delete_group'); - const { group, reload, onClose } = props; + const { group } = props; + + const { mutate: deleteGroup } = useDeleteGroupMutation(); - const onSubmit = useCallback(async () => { - await deleteGroup({ name: group.name }); - reload(); - onClose(); - }, [group, reload, onClose]); + const onSubmit = () => { + deleteGroup( + { name: group.name }, + { + onSuccess: props.onClose, + }, + ); + }; + const header = translate('groups.delete_group'); return ( <SimpleModal header={header} onClose={props.onClose} onSubmit={onSubmit}> {({ onCloseClick, onFormSubmit, submitting }) => ( diff --git a/server/sonar-web/src/main/js/apps/groups/components/GroupForm.tsx b/server/sonar-web/src/main/js/apps/groups/components/GroupForm.tsx index b13b23bb7f2..84eff7ad012 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/GroupForm.tsx +++ b/server/sonar-web/src/main/js/apps/groups/components/GroupForm.tsx @@ -18,8 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { useCallback, useEffect, useState } from 'react'; -import { createGroup, updateGroup } from '../../../api/user_groups'; +import { useState } from 'react'; import SimpleModal from '../../../components/controls/SimpleModal'; import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons'; import MandatoryFieldMarker from '../../../components/ui/MandatoryFieldMarker'; @@ -27,6 +26,7 @@ import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsEx 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'; type Props = @@ -34,52 +34,46 @@ type Props = create: true; group?: undefined; onClose: () => void; - reload: () => void; } | { create: false; group: Group; onClose: () => void; - reload: () => void; }; export default function GroupForm(props: Props) { - const { group, create, reload, onClose } = props; + const { group, create } = props; - const [name, setName] = useState<string>(''); - const [description, setDescription] = useState<string>(''); + const [name, setName] = useState<string>(create ? '' : group.name); + const [description, setDescription] = useState<string>(create ? '' : group.description ?? ''); - const handleSubmit = useCallback(async () => { - try { - if (create) { - await createGroup({ name, description }); - } else { - const data = { - currentName: group.name, - description, - // pass `name` only if it has changed, otherwise the WS fails - ...omitNil({ name: name !== group.name ? name : undefined }), - }; - await updateGroup(data); - } - } finally { - reload(); - onClose(); - } - }, [name, description, group, create, reload, onClose]); + const { mutate: createGroup } = useCreateGroupMutation(); + const { mutate: updateGroup } = useUpdateGroupMutation(); + + const handleCreateGroup = () => { + createGroup({ name, description }, { onSuccess: props.onClose }); + }; - useEffect(() => { - if (!create) { - setDescription(group.description ?? ''); - setName(group.name); + const handleUpdateGroup = () => { + if (!group) { + return; } - }, []); + updateGroup( + { + currentName: group.name, + description, + // pass `name` only if it has changed, otherwise the WS fails + ...omitNil({ name: name !== group.name ? name : undefined }), + }, + { onSuccess: props.onClose }, + ); + }; return ( <SimpleModal header={create ? translate('groups.create_group') : translate('groups.update_group')} onClose={props.onClose} - onSubmit={handleSubmit} + onSubmit={create ? handleCreateGroup : handleUpdateGroup} size="small" > {({ onCloseClick, onFormSubmit, submitting }) => ( diff --git a/server/sonar-web/src/main/js/apps/groups/components/Header.tsx b/server/sonar-web/src/main/js/apps/groups/components/Header.tsx index 5f7f7d83c36..f285ff37c38 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/Header.tsx +++ b/server/sonar-web/src/main/js/apps/groups/components/Header.tsx @@ -26,7 +26,6 @@ import { translate } from '../../../helpers/l10n'; import GroupForm from './GroupForm'; interface HeaderProps { - reload: () => void; manageProvider?: string; } @@ -68,9 +67,7 @@ export default function Header(props: HeaderProps) { </Alert> )} </div> - {createModal && ( - <GroupForm onClose={() => setCreateModal(false)} create reload={props.reload} /> - )} + {createModal && <GroupForm onClose={() => setCreateModal(false)} create />} </> ); } diff --git a/server/sonar-web/src/main/js/apps/groups/components/List.tsx b/server/sonar-web/src/main/js/apps/groups/components/List.tsx index b15de183e9c..b699b7eb70a 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/List.tsx +++ b/server/sonar-web/src/main/js/apps/groups/components/List.tsx @@ -25,7 +25,6 @@ import ListItem from './ListItem'; interface Props { groups: Group[]; - reload: () => void; manageProvider: string | undefined; } @@ -49,12 +48,7 @@ export default function List(props: Props) { </thead> <tbody> {sortBy(groups, (group) => group.name.toLowerCase()).map((group) => ( - <ListItem - group={group} - key={group.name} - reload={props.reload} - manageProvider={manageProvider} - /> + <ListItem group={group} key={group.name} manageProvider={manageProvider} /> ))} </tbody> </table> diff --git a/server/sonar-web/src/main/js/apps/groups/components/ListItem.tsx b/server/sonar-web/src/main/js/apps/groups/components/ListItem.tsx index 4c1ccea5f31..561c46536ce 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/ListItem.tsx +++ b/server/sonar-web/src/main/js/apps/groups/components/ListItem.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Spinner } from 'design-system'; import * as React from 'react'; import { useState } from 'react'; import ActionsDropdown, { @@ -26,6 +27,7 @@ import ActionsDropdown, { import { Provider } from '../../../components/hooks/useManageProvider'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { getBaseUrl } from '../../../helpers/system'; +import { useMembersCountQuery } from '../../../queries/groups'; import { Group } from '../../../types/types'; import DeleteGroupForm from './DeleteGroupForm'; import GroupForm from './GroupForm'; @@ -33,17 +35,18 @@ import Members from './Members'; export interface ListItemProps { group: Group; - reload: () => void; manageProvider: string | undefined; } export default function ListItem(props: ListItemProps) { const { manageProvider, group } = props; - const { name, managed, membersCount, description } = group; + const { name, managed, description } = group; const [groupToDelete, setGroupToDelete] = useState<Group | undefined>(); const [groupToEdit, setGroupToEdit] = useState<Group | undefined>(); + const { data: membersCount, isLoading, refetch } = useMembersCountQuery(group.name); + const isManaged = () => { return manageProvider !== undefined; }; @@ -73,8 +76,10 @@ export default function ListItem(props: ListItemProps) { </td> <td className="group-members display-flex-justify-end" headers="list-group-member"> - <span>{membersCount}</span> - <Members group={group} onEdit={props.reload} isManaged={isManaged()} /> + <Spinner loading={isLoading}> + <span>{membersCount}</span> + </Spinner> + <Members group={group} onEdit={refetch} isManaged={isManaged()} /> </td> <td className="width-40" headers="list-group-description"> @@ -107,19 +112,10 @@ export default function ListItem(props: ListItemProps) { </ActionsDropdown> )} {groupToDelete && ( - <DeleteGroupForm - group={groupToDelete} - reload={props.reload} - onClose={() => setGroupToDelete(undefined)} - /> + <DeleteGroupForm group={groupToDelete} onClose={() => setGroupToDelete(undefined)} /> )} {groupToEdit && ( - <GroupForm - create={false} - group={groupToEdit} - reload={props.reload} - onClose={() => setGroupToEdit(undefined)} - /> + <GroupForm create={false} group={groupToEdit} onClose={() => setGroupToEdit(undefined)} /> )} </td> </tr> |