aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src
diff options
context:
space:
mode:
authorMathieu Suen <mathieu.suen@sonarsource.com>2023-03-29 10:01:45 +0200
committersonartech <sonartech@sonarsource.com>2023-03-30 20:03:08 +0000
commit4a890c4af46a720b7da263b7b9d46712242220b7 (patch)
treeef27ee225e4ab4dec3bdffc9c6d2bf4dbb3a0b86 /server/sonar-web/src
parent5bc7a4f08642272126c5bc29787fed8b9c24ead8 (diff)
downloadsonarqube-4a890c4af46a720b7da263b7b9d46712242220b7.tar.gz
sonarqube-4a890c4af46a720b7da263b7b9d46712242220b7.zip
SONAR-18657 Add members view for groups in a managed instance
Diffstat (limited to 'server/sonar-web/src')
-rw-r--r--server/sonar-web/src/main/js/api/mocks/GroupsServiceMock.ts22
-rw-r--r--server/sonar-web/src/main/js/api/user_groups.ts8
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/EditMembersModal.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/ListItem.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/Members.tsx (renamed from server/sonar-web/src/main/js/apps/groups/components/EditMembers.tsx)27
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/ViewMembersModal.tsx119
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembers-test.tsx39
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/GroupsApp-it.tsx18
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/List-test.tsx36
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/ListItem-test.tsx34
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembers-test.tsx.snap50
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembersModal-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/List-test.tsx.snap80
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/ListItem-test.tsx.snap109
-rw-r--r--server/sonar-web/src/main/js/apps/groups/groups.css16
-rw-r--r--server/sonar-web/src/main/js/components/controls/buttons.css4
-rw-r--r--server/sonar-web/src/main/js/types/types.ts7
17 files changed, 213 insertions, 374 deletions
diff --git a/server/sonar-web/src/main/js/api/mocks/GroupsServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/GroupsServiceMock.ts
index 3965397c9d8..fcf3191a99e 100644
--- a/server/sonar-web/src/main/js/api/mocks/GroupsServiceMock.ts
+++ b/server/sonar-web/src/main/js/api/mocks/GroupsServiceMock.ts
@@ -26,7 +26,13 @@ import {
mockPaging,
mockUser,
} from '../../helpers/testMocks';
-import { Group, IdentityProvider, Paging, SysInfoCluster, UserSelected } from '../../types/types';
+import {
+ Group,
+ IdentityProvider,
+ Paging,
+ SysInfoCluster,
+ UserGroupMember,
+} from '../../types/types';
import { getSystemInfo } from '../system';
import { getIdentityProviders } from '../users';
import {
@@ -117,19 +123,25 @@ export default class GroupsServiceMock {
return this.reply({});
};
- handlegetUsersInGroup = (): Promise<Paging & { users: UserSelected[] }> => {
+ handlegetUsersInGroup = (data: {
+ name?: string;
+ p?: number;
+ ps?: number;
+ q?: string;
+ selected?: string;
+ }): Promise<Paging & { users: UserGroupMember[] }> => {
return this.reply({
...this.paging,
users: [
{
...mockUser({ name: 'alice' }),
selected: true,
- } as UserSelected,
+ } as UserGroupMember,
{
...mockUser({ name: 'bob' }),
selected: false,
- } as UserSelected,
- ],
+ } as UserGroupMember,
+ ].filter((u) => u.name.includes(data.q ?? '')),
});
};
diff --git a/server/sonar-web/src/main/js/api/user_groups.ts b/server/sonar-web/src/main/js/api/user_groups.ts
index 9b2ebe32352..0e7fd144827 100644
--- a/server/sonar-web/src/main/js/api/user_groups.ts
+++ b/server/sonar-web/src/main/js/api/user_groups.ts
@@ -19,7 +19,7 @@
*/
import { throwGlobalError } from '../helpers/error';
import { getJSON, post, postJSON } from '../helpers/request';
-import { Group, Paging, UserSelected } from '../types/types';
+import { Group, Paging, UserGroupMember } from '../types/types';
export function searchUsersGroups(data: {
f?: string;
@@ -37,7 +37,11 @@ export function getUsersInGroup(data: {
ps?: number;
q?: string;
selected?: string;
-}): Promise<Paging & { users: UserSelected[] }> {
+}): Promise<
+ Paging & {
+ users: UserGroupMember[];
+ }
+> {
return getJSON('/api/user_groups/users', data).catch(throwGlobalError);
}
diff --git a/server/sonar-web/src/main/js/apps/groups/components/EditMembersModal.tsx b/server/sonar-web/src/main/js/apps/groups/components/EditMembersModal.tsx
index 322294ed3bd..016e04c619f 100644
--- a/server/sonar-web/src/main/js/apps/groups/components/EditMembersModal.tsx
+++ b/server/sonar-web/src/main/js/apps/groups/components/EditMembersModal.tsx
@@ -141,7 +141,11 @@ export default class EditMembersModal extends React.PureComponent<Props, State>
render() {
const modalHeader = translate('users.update');
return (
- <Modal contentLabel={modalHeader} onRequestClose={this.props.onClose}>
+ <Modal
+ className="group-menbers-modal"
+ contentLabel={modalHeader}
+ onRequestClose={this.props.onClose}
+ >
<header className="modal-head">
<h2>{modalHeader}</h2>
</header>
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 93acc5ca969..d6a19a6d225 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,7 +17,6 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import classNames from 'classnames';
import * as React from 'react';
import { useState } from 'react';
import ActionsDropdown, {
@@ -27,8 +26,8 @@ import ActionsDropdown, {
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { Group } from '../../../types/types';
import DeleteGroupForm from './DeleteGroupForm';
-import EditMembers from './EditMembers';
import GroupForm from './GroupForm';
+import Members from './Members';
export interface ListItemProps {
group: Group;
@@ -60,12 +59,8 @@ export default function ListItem(props: ListItemProps) {
</td>
<td className="group-members display-flex-justify-end" headers="list-group-member">
- <span
- className={classNames({ 'big-padded-right spacer-right': group.default && !isManaged() })}
- >
- {membersCount}
- </span>
- {!group.default && !isManaged() && <EditMembers group={group} onEdit={props.reload} />}
+ <span>{membersCount}</span>
+ <Members group={group} onEdit={props.reload} isManaged={isManaged()} />
</td>
<td className="width-40" headers="list-group-description">
diff --git a/server/sonar-web/src/main/js/apps/groups/components/EditMembers.tsx b/server/sonar-web/src/main/js/apps/groups/components/Members.tsx
index 05fe264ad5b..8e77e94bf8b 100644
--- a/server/sonar-web/src/main/js/apps/groups/components/EditMembers.tsx
+++ b/server/sonar-web/src/main/js/apps/groups/components/Members.tsx
@@ -23,8 +23,10 @@ import BulletListIcon from '../../../components/icons/BulletListIcon';
import { translateWithParameters } from '../../../helpers/l10n';
import { Group } from '../../../types/types';
import EditMembersModal from './EditMembersModal';
+import ViewMembersModal from './ViewMembersModal';
interface Props {
+ isManaged: boolean;
group: Group;
onEdit: () => void;
}
@@ -33,8 +35,7 @@ interface State {
modal: boolean;
}
-export default class EditMembers extends React.PureComponent<Props, State> {
- container?: HTMLElement | null;
+export default class Members extends React.PureComponent<Props, State> {
mounted = false;
state: State = { modal: false };
@@ -51,26 +52,36 @@ export default class EditMembers extends React.PureComponent<Props, State> {
};
handleModalClose = () => {
+ const { isManaged, group } = this.props;
if (this.mounted) {
this.setState({ modal: false });
- this.props.onEdit();
+ if (!isManaged && !group.default) {
+ this.props.onEdit();
+ }
}
};
render() {
+ const { isManaged, group } = this.props;
return (
<>
<ButtonIcon
- aria-label={translateWithParameters('groups.users.edit', this.props.group.name)}
+ aria-label={translateWithParameters(
+ isManaged || group.default ? 'groups.users.view' : 'groups.users.edit',
+ group.name
+ )}
className="button-small little-spacer-left little-padded"
onClick={this.handleMembersClick}
- title={translateWithParameters('groups.users.edit', this.props.group.name)}
+ title={translateWithParameters('groups.users.edit', group.name)}
>
<BulletListIcon />
</ButtonIcon>
- {this.state.modal && (
- <EditMembersModal group={this.props.group} onClose={this.handleModalClose} />
- )}
+ {this.state.modal &&
+ (isManaged || group.default ? (
+ <ViewMembersModal isManaged={isManaged} group={group} onClose={this.handleModalClose} />
+ ) : (
+ <EditMembersModal group={group} onClose={this.handleModalClose} />
+ ))}
</>
);
}
diff --git a/server/sonar-web/src/main/js/apps/groups/components/ViewMembersModal.tsx b/server/sonar-web/src/main/js/apps/groups/components/ViewMembersModal.tsx
new file mode 100644
index 00000000000..799dc108c9f
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/groups/components/ViewMembersModal.tsx
@@ -0,0 +1,119 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 { DeferredSpinner } from 'design-system/lib';
+import * as React from 'react';
+import { getUsersInGroup } from '../../../api/user_groups';
+import { ResetButtonLink } from '../../../components/controls/buttons';
+import ListFooter from '../../../components/controls/ListFooter';
+import Modal from '../../../components/controls/Modal';
+import SearchBox from '../../../components/controls/SearchBox';
+import { SelectListFilter } from '../../../components/controls/SelectList';
+import { translate } from '../../../helpers/l10n';
+import { Group, UserGroupMember } from '../../../types/types';
+
+interface Props {
+ isManaged: boolean;
+ group: Group;
+ onClose: () => void;
+}
+
+export default function ViewMembersModal(props: Props) {
+ const { isManaged, group } = props;
+
+ const [loading, setLoading] = React.useState(false);
+ const [page, setPage] = React.useState(1);
+ const [query, setQuery] = React.useState<string>();
+ const [total, setTotal] = React.useState<number>();
+ const [users, setUsers] = React.useState<UserGroupMember[]>([]);
+
+ React.useEffect(() => {
+ (async () => {
+ setLoading(true);
+ const data = await getUsersInGroup({
+ name: group.name,
+ p: page,
+ q: query,
+ selected: SelectListFilter.Selected,
+ });
+ if (page > 1) {
+ setUsers([...users, ...data.users]);
+ } else {
+ setUsers(data.users);
+ }
+ setTotal(data.total);
+ setLoading(false);
+ })();
+ }, [query, page]);
+
+ const modalHeader = translate('users.list');
+ return (
+ <Modal
+ className="group-menbers-modal"
+ contentLabel={modalHeader}
+ onRequestClose={props.onClose}
+ >
+ <header className="modal-head">
+ <h2>{modalHeader}</h2>
+ </header>
+
+ <div className="modal-body modal-container">
+ <SearchBox
+ className="view-search-box"
+ loading={loading}
+ onChange={(q) => {
+ setQuery(q);
+ setPage(1);
+ }}
+ placeholder={translate('search_verb')}
+ value={query}
+ />
+ <div className="select-list-list-container spacer-top">
+ <DeferredSpinner loading={loading}>
+ <ul className="menu">
+ {users.map((user) => (
+ <li key={user.login} className="display-flex-center">
+ <span className="little-spacer-left width-100">
+ <span className="select-list-list-item display-flex-center display-flex-space-between">
+ <span className="spacer-right">
+ {user.name}
+ <br />
+ <span className="note">{user.login}</span>
+ </span>
+ {!user.managed && isManaged && (
+ <span className="badge">{translate('local')}</span>
+ )}
+ </span>
+ </span>
+ </li>
+ ))}
+ </ul>
+ </DeferredSpinner>
+ </div>
+ {total !== undefined && (
+ <ListFooter count={users.length} loadMore={() => setPage((p) => p + 1)} total={total} />
+ )}
+ </div>
+
+ <footer className="modal-foot">
+ <ResetButtonLink onClick={props.onClose}>{translate('done')}</ResetButtonLink>
+ </footer>
+ </Modal>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembers-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembers-test.tsx
deleted file mode 100644
index cce45974640..00000000000
--- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembers-test.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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 { shallow } from 'enzyme';
-import * as React from 'react';
-import { mockGroup } from '../../../../helpers/testMocks';
-import { click } from '../../../../helpers/testUtils';
-import EditMembers from '../EditMembers';
-
-it('should edit members', () => {
- const group = mockGroup({ name: 'Foo', membersCount: 5 });
- const onEdit = jest.fn();
-
- const wrapper = shallow(<EditMembers group={group} onEdit={onEdit} />);
- expect(wrapper).toMatchSnapshot();
-
- click(wrapper.find('ButtonIcon'));
- expect(wrapper).toMatchSnapshot();
-
- wrapper.find('EditMembersModal').prop<Function>('onClose')();
- expect(onEdit).toHaveBeenCalled();
- expect(wrapper).toMatchSnapshot();
-});
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/GroupsApp-it.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/GroupsApp-it.tsx
index cd66848bd95..28580e632e2 100644
--- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/GroupsApp-it.tsx
+++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/GroupsApp-it.tsx
@@ -54,10 +54,17 @@ const ui = {
editGroupDialogButton: byRole('button', { name: 'groups.create_group' }),
createGroupDialog: byRole('dialog', { name: 'groups.create_group' }),
+ membersViewDialog: byRole('dialog', { name: 'users.list' }),
membersDialog: byRole('dialog', { name: 'users.update' }),
managedGroupRow: byRole('row', { name: 'managed-group 1' }),
managedGroupEditMembersButton: byRole('button', { name: 'groups.users.edit.managed-group' }),
+ managedGroupViewMembersButton: byRole('button', { name: 'groups.users.view.managed-group' }),
+
+ memberAliceUser: byText('alice'),
+ memberBobUser: byText('bob'),
+ memberSearchInput: byRole('searchbox', { name: 'search_verb' }),
+
managedEditButton: byRole('button', { name: 'groups.edit.managed-group' }),
localGroupRow: byRole('row', { name: 'local-group 1' }),
@@ -236,6 +243,17 @@ describe('in manage mode', () => {
expect(ui.managedEditButton.query()).not.toBeInTheDocument();
expect(ui.managedGroupEditMembersButton.query()).not.toBeInTheDocument();
+
+ await userEvent.click(ui.managedGroupViewMembersButton.get());
+ expect(await ui.membersViewDialog.find()).toBeInTheDocument();
+
+ expect(ui.memberAliceUser.get()).toBeInTheDocument();
+ expect(ui.memberBobUser.get()).toBeInTheDocument();
+
+ await userEvent.type(ui.memberSearchInput.get(), 'b');
+
+ expect(await ui.memberBobUser.find()).toBeInTheDocument();
+ expect(ui.memberAliceUser.query()).not.toBeInTheDocument();
});
it('should render list of all groups', async () => {
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/List-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/List-test.tsx
deleted file mode 100644
index d40207a484a..00000000000
--- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/List-test.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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 { shallow } from 'enzyme';
-import * as React from 'react';
-import { mockGroup } from '../../../../helpers/testMocks';
-import List from '../List';
-
-it('should render', () => {
- expect(shallowRender()).toMatchSnapshot();
-});
-
-function shallowRender() {
- const groups = [
- mockGroup({ name: 'sonar-users', description: '', membersCount: 55, default: true }),
- mockGroup({ name: 'foo', description: 'foobar', membersCount: 0, default: false }),
- mockGroup({ name: 'bar', description: 'barbar', membersCount: 1, default: false }),
- ];
- return shallow(<List groups={groups} manageProvider={undefined} reload={jest.fn()} />);
-}
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/ListItem-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/ListItem-test.tsx
deleted file mode 100644
index e51bc5eabd9..00000000000
--- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/ListItem-test.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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 { shallow } from 'enzyme';
-import * as React from 'react';
-import { mockGroup } from '../../../../helpers/testMocks';
-import ListItem, { ListItemProps } from '../ListItem';
-
-it('should render correctly', () => {
- expect(shallowRender()).toMatchSnapshot();
- expect(shallowRender({ group: mockGroup({ default: true }) })).toMatchSnapshot('default group');
-});
-
-function shallowRender(overrides: Partial<ListItemProps> = {}) {
- return shallow(
- <ListItem group={mockGroup()} reload={jest.fn()} manageProvider={undefined} {...overrides} />
- );
-}
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembers-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembers-test.tsx.snap
deleted file mode 100644
index 6378ecc8307..00000000000
--- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembers-test.tsx.snap
+++ /dev/null
@@ -1,50 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should edit members 1`] = `
-<Fragment>
- <ButtonIcon
- aria-label="groups.users.edit.Foo"
- className="button-small little-spacer-left little-padded"
- onClick={[Function]}
- title="groups.users.edit.Foo"
- >
- <BulletListIcon />
- </ButtonIcon>
-</Fragment>
-`;
-
-exports[`should edit members 2`] = `
-<Fragment>
- <ButtonIcon
- aria-label="groups.users.edit.Foo"
- className="button-small little-spacer-left little-padded"
- onClick={[Function]}
- title="groups.users.edit.Foo"
- >
- <BulletListIcon />
- </ButtonIcon>
- <EditMembersModal
- group={
- {
- "managed": false,
- "membersCount": 5,
- "name": "Foo",
- }
- }
- onClose={[Function]}
- />
-</Fragment>
-`;
-
-exports[`should edit members 3`] = `
-<Fragment>
- <ButtonIcon
- aria-label="groups.users.edit.Foo"
- className="button-small little-spacer-left little-padded"
- onClick={[Function]}
- title="groups.users.edit.Foo"
- >
- <BulletListIcon />
- </ButtonIcon>
-</Fragment>
-`;
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembersModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembersModal-test.tsx.snap
index ecc4e6f75b2..35cc168d15e 100644
--- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembersModal-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembersModal-test.tsx.snap
@@ -2,6 +2,7 @@
exports[`should render modal properly 1`] = `
<Modal
+ className="group-menbers-modal"
contentLabel="users.update"
onRequestClose={[MockFunction]}
>
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/List-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/List-test.tsx.snap
deleted file mode 100644
index f79f1f69b91..00000000000
--- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/List-test.tsx.snap
+++ /dev/null
@@ -1,80 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render 1`] = `
-<div
- className="boxed-group boxed-group-inner"
->
- <table
- className="data zebra zebra-hover"
- id="groups-list"
- >
- <thead>
- <tr>
- <th
- id="list-group-name"
- >
- user_groups.page.group_header
- </th>
- <th
- className="nowrap width-10"
- id="list-group-member"
- >
- members
- </th>
- <th
- className="nowrap"
- id="list-group-description"
- >
- description
- </th>
- <th
- id="list-group-actions"
- >
- actions
- </th>
- </tr>
- </thead>
- <tbody>
- <ListItem
- group={
- {
- "default": false,
- "description": "barbar",
- "managed": false,
- "membersCount": 1,
- "name": "bar",
- }
- }
- key="bar"
- reload={[MockFunction]}
- />
- <ListItem
- group={
- {
- "default": false,
- "description": "foobar",
- "managed": false,
- "membersCount": 0,
- "name": "foo",
- }
- }
- key="foo"
- reload={[MockFunction]}
- />
- <ListItem
- group={
- {
- "default": true,
- "description": "",
- "managed": false,
- "membersCount": 55,
- "name": "sonar-users",
- }
- }
- key="sonar-users"
- reload={[MockFunction]}
- />
- </tbody>
- </table>
-</div>
-`;
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/ListItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/ListItem-test.tsx.snap
deleted file mode 100644
index 5e4a0009aad..00000000000
--- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/ListItem-test.tsx.snap
+++ /dev/null
@@ -1,109 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<tr
- data-id="Foo"
->
- <td
- className="width-20"
- headers="list-group-name"
- >
- <strong>
- Foo
- </strong>
- </td>
- <td
- className="group-members display-flex-justify-end"
- headers="list-group-member"
- >
- <span>
- 1
- </span>
- <EditMembers
- group={
- {
- "managed": false,
- "membersCount": 1,
- "name": "Foo",
- }
- }
- onEdit={[MockFunction]}
- />
- </td>
- <td
- className="width-40"
- headers="list-group-description"
- >
- <span
- className="js-group-description"
- />
- </td>
- <td
- className="thin nowrap text-right"
- headers="list-group-actions"
- >
- <ActionsDropdown
- label="groups.edit.Foo"
- >
- <ActionsDropdownItem
- className="js-group-update"
- onClick={[Function]}
- >
- update_details
- </ActionsDropdownItem>
- <ActionsDropdownDivider />
- <ActionsDropdownItem
- className="js-group-delete"
- destructive={true}
- onClick={[Function]}
- >
- delete
- </ActionsDropdownItem>
- </ActionsDropdown>
- </td>
-</tr>
-`;
-
-exports[`should render correctly: default group 1`] = `
-<tr
- data-id="Foo"
->
- <td
- className="width-20"
- headers="list-group-name"
- >
- <strong>
- Foo
- </strong>
- <span
- className="little-spacer-left"
- >
- (
- default
- )
- </span>
- </td>
- <td
- className="group-members display-flex-justify-end"
- headers="list-group-member"
- >
- <span
- className="big-padded-right spacer-right"
- >
- 1
- </span>
- </td>
- <td
- className="width-40"
- headers="list-group-description"
- >
- <span
- className="js-group-description"
- />
- </td>
- <td
- className="thin nowrap text-right"
- headers="list-group-actions"
- />
-</tr>
-`;
diff --git a/server/sonar-web/src/main/js/apps/groups/groups.css b/server/sonar-web/src/main/js/apps/groups/groups.css
index 1bec59205ae..6fa08f30676 100644
--- a/server/sonar-web/src/main/js/apps/groups/groups.css
+++ b/server/sonar-web/src/main/js/apps/groups/groups.css
@@ -21,3 +21,19 @@
#groups-page .group-members {
padding-right: 50%;
}
+
+.group-menbers-modal .modal-container > :last-child {
+ margin-bottom: 0;
+}
+
+.group-menbers-modal .select-list-list-container {
+ height: 350px;
+}
+
+.group-menbers-modal .modal-body {
+ padding: 12px 32px;
+}
+
+.group-members-modal .view-search-box.search-box {
+ max-width: 100%;
+}
diff --git a/server/sonar-web/src/main/js/components/controls/buttons.css b/server/sonar-web/src/main/js/components/controls/buttons.css
index eb189f27e98..e2ef9955112 100644
--- a/server/sonar-web/src/main/js/components/controls/buttons.css
+++ b/server/sonar-web/src/main/js/components/controls/buttons.css
@@ -219,12 +219,12 @@
}
.button-icon:hover,
-.button-icon:focus {
+.button-icon:focus-visible {
background-color: currentColor;
}
.button-icon:not(.disabled):hover svg,
-.button-icon:not(.disabled):focus svg {
+.button-icon:not(.disabled):focus-visible svg {
color: var(--white);
}
diff --git a/server/sonar-web/src/main/js/types/types.ts b/server/sonar-web/src/main/js/types/types.ts
index 36cc2434a55..8ee9858b8f1 100644
--- a/server/sonar-web/src/main/js/types/types.ts
+++ b/server/sonar-web/src/main/js/types/types.ts
@@ -772,6 +772,13 @@ export interface UserSelected extends UserActive {
selected: boolean;
}
+export interface UserGroupMember {
+ selected: boolean;
+ login: string;
+ name: string;
+ managed: boolean;
+}
+
export namespace WebApi {
export interface Action {
key: string;