aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web
diff options
context:
space:
mode:
authorSarath Nair <91882341+sarath-nair-sonarsource@users.noreply.github.com>2024-10-03 14:17:13 +0200
committersonartech <sonartech@sonarsource.com>2024-10-03 20:02:51 +0000
commite4d86bcecaa3fc20d37971f11be44b0150195692 (patch)
tree78789e87cbe728bed7adde61aca58ac30a36e38c /server/sonar-web
parent4b6cae980fe81ca8e6368785873159a868832426 (diff)
downloadsonarqube-e4d86bcecaa3fc20d37971f11be44b0150195692.tar.gz
sonarqube-e4d86bcecaa3fc20d37971f11be44b0150195692.zip
SONAR-22486 Show group membership in user list when auto provision is enabled (#11933)
Diffstat (limited to 'server/sonar-web')
-rw-r--r--server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx15
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/UserListItem.tsx37
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/ViewGroupsModal.tsx79
3 files changed, 116 insertions, 15 deletions
diff --git a/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx
index d6d7940c7df..b0a6876f2c1 100644
--- a/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx
+++ b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx
@@ -64,10 +64,12 @@ const ui = {
localFilter: byRole('radio', { name: 'local' }),
showMore: byRole('button', { name: 'show_more' }),
aliceUpdateGroupButton: byRole('button', { name: 'users.update_users_groups.alice.merveille' }),
+ aliceViewGroupButton: byRole('button', { name: 'users.view_users_groups.alice.merveille' }),
aliceUpdateButton: byRole('button', { name: 'users.manage_user.alice.merveille' }),
denisUpdateButton: byRole('button', { name: 'users.manage_user.denis.villeneuve' }),
alicedDeactivateButton: byText('users.deactivate'),
bobUpdateGroupButton: byRole('button', { name: 'users.update_users_groups.bob.marley' }),
+ bobViewGroupButton: byRole('button', { name: 'users.view_users_groups.bob.marley' }),
bobUpdateButton: byRole('button', { name: 'users.manage_user.bob.marley' }),
scmAddButton: byRole('button', { name: 'add_verb' }),
createUserDialogButton: byRole('button', { name: 'create' }),
@@ -117,6 +119,8 @@ const ui = {
jackRow: byRole('row', { name: /Jack/ }),
dialogGroups: byRole('dialog', { name: 'users.update_groups' }),
+ dialogViewGroups: byRole('dialog', { name: 'users.view_groups' }),
+ buttonCloseDialogViewGroups: byRole('button', { name: 'modal.close' }),
allFilter: byRole('radio', { name: 'all' }),
selectedFilter: byRole('radio', { name: 'selected' }),
unselectedFilter: byRole('radio', { name: 'unselected' }),
@@ -509,13 +513,18 @@ describe('in manage mode', () => {
expect(ui.createUserButton.get()).toBeDisabled();
});
- it("should not be able to add/remove a user's group", async () => {
+ it("should be able to view only a user's group", async () => {
+ const user = userEvent.setup({ skipHover: true });
renderUsersApp();
expect(await ui.aliceRowWithLocalBadge.find()).toBeInTheDocument();
- expect(ui.aliceUpdateGroupButton.query()).not.toBeInTheDocument();
+ await user.click(ui.aliceViewGroupButton.get());
+ expect(ui.dialogViewGroups.get()).toBeInTheDocument();
+ expect(ui.dialogViewGroups.byRole('checkbox').query()).not.toBeInTheDocument();
+ await user.click(ui.buttonCloseDialogViewGroups.get());
expect(ui.bobRow.get()).toBeInTheDocument();
- expect(ui.bobUpdateGroupButton.query()).not.toBeInTheDocument();
+ await user.click(ui.bobViewGroupButton.get());
+ expect(ui.dialogViewGroups.byRole('checkbox').query()).not.toBeInTheDocument();
});
it('should not be able to update scm account', async () => {
diff --git a/server/sonar-web/src/main/js/apps/users/components/UserListItem.tsx b/server/sonar-web/src/main/js/apps/users/components/UserListItem.tsx
index 51b8b12c5bb..6fbb0e6ccd6 100644
--- a/server/sonar-web/src/main/js/apps/users/components/UserListItem.tsx
+++ b/server/sonar-web/src/main/js/apps/users/components/UserListItem.tsx
@@ -37,6 +37,7 @@ import TokensFormModal from './TokensFormModal';
import UserActions from './UserActions';
import UserListItemIdentity from './UserListItemIdentity';
import UserScmAccounts from './UserScmAccounts';
+import ViewGroupsModal from './ViewGroupsModal';
export interface UserListItemProps {
identityProvider?: IdentityProvider;
@@ -85,17 +86,24 @@ export default function UserListItem(props: Readonly<UserListItemProps>) {
<ContentCell>
<Spinner isLoading={groupsAreLoading}>
{groupsCount}
- {manageProvider === undefined && (
- <ButtonIcon
- Icon={IconMoreVertical}
- tooltipContent={translate('users.update_groups')}
- className="it__user-groups sw-ml-2"
- ariaLabel={translateWithParameters('users.update_users_groups', user.login)}
- onClick={() => setOpenGroupForm(true)}
- size={ButtonSize.Medium}
- variety={ButtonVariety.DefaultGhost}
- />
- )}
+ <ButtonIcon
+ Icon={IconMoreVertical}
+ tooltipContent={
+ manageProvider === undefined
+ ? translate('users.update_groups')
+ : translate('users.view_groups')
+ }
+ className="it__user-groups sw-ml-2"
+ ariaLabel={translateWithParameters(
+ manageProvider === undefined
+ ? 'users.update_users_groups'
+ : 'users.view_users_groups',
+ user.login,
+ )}
+ onClick={() => setOpenGroupForm(true)}
+ size={ButtonSize.Medium}
+ variety={ButtonVariety.DefaultGhost}
+ />
</Spinner>
</ContentCell>
<ContentCell>
@@ -119,7 +127,12 @@ export default function UserListItem(props: Readonly<UserListItemProps>) {
</ActionCell>
{openTokenForm && <TokensFormModal onClose={() => setOpenTokenForm(false)} user={user} />}
- {openGroupForm && <GroupsForm onClose={() => setOpenGroupForm(false)} user={user} />}
+ {openGroupForm && manageProvider === undefined && (
+ <GroupsForm onClose={() => setOpenGroupForm(false)} user={user} />
+ )}
+ {openGroupForm && manageProvider !== undefined && (
+ <ViewGroupsModal onClose={() => setOpenGroupForm(false)} user={user} />
+ )}
</TableRow>
);
}
diff --git a/server/sonar-web/src/main/js/apps/users/components/ViewGroupsModal.tsx b/server/sonar-web/src/main/js/apps/users/components/ViewGroupsModal.tsx
new file mode 100644
index 00000000000..adf8b46e3a3
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/users/components/ViewGroupsModal.tsx
@@ -0,0 +1,79 @@
+/*
+ * 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 { Button, Modal, ModalSize, Spinner, Text } from '@sonarsource/echoes-react';
+import { Badge, InputSearch } from 'design-system/lib';
+import * as React from 'react';
+import { translate } from '../../../helpers/l10n';
+import { useUserGroupsQuery } from '../../../queries/group-memberships';
+import { RestUserDetailed } from '../../../types/users';
+
+interface Props {
+ onClose: () => void;
+ user: RestUserDetailed;
+}
+
+export default function ViewGroupsModal(props: Readonly<Props>) {
+ const { onClose, user } = props;
+ const [query, setQuery] = React.useState<string>('');
+ const { data: groups, isLoading } = useUserGroupsQuery({
+ q: query,
+ userId: user.id,
+ });
+
+ const modalBody = (
+ <>
+ <InputSearch
+ className="sw-w-full"
+ loading={isLoading}
+ onChange={setQuery}
+ placeholder={translate('search_verb')}
+ value={query}
+ />
+ <div className="sw-mt-6">
+ <Spinner isLoading={isLoading} />
+ <ul className="sw-flex sw-flex-col sw-gap-4">
+ {(groups || []).map(({ id, description, managed, name }) => (
+ <li key={id} className="sw-flex sw-items-center">
+ <span className="sw-flex sw-gap-2 sw-justify-between sw-items-center sw-w-full">
+ <span>
+ {name}
+ <br />
+ <Text isSubdued> {description} </Text>
+ </span>
+ {!managed && <Badge>{translate('local')}</Badge>}
+ </span>
+ </li>
+ ))}
+ </ul>
+ </div>
+ </>
+ );
+
+ return (
+ <Modal
+ content={modalBody}
+ isOpen
+ onOpenChange={onClose}
+ secondaryButton={<Button onClick={onClose}>{translate('close')}</Button>}
+ size={ModalSize.Default}
+ title={translate('users.view_groups')}
+ />
+ );
+}