diff options
author | Mathieu Suen <mathieu.suen@sonarsource.com> | 2023-03-29 10:01:45 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-03-30 20:03:08 +0000 |
commit | 4a890c4af46a720b7da263b7b9d46712242220b7 (patch) | |
tree | ef27ee225e4ab4dec3bdffc9c6d2bf4dbb3a0b86 /server/sonar-web/src | |
parent | 5bc7a4f08642272126c5bc29787fed8b9c24ead8 (diff) | |
download | sonarqube-4a890c4af46a720b7da263b7b9d46712242220b7.tar.gz sonarqube-4a890c4af46a720b7da263b7b9d46712242220b7.zip |
SONAR-18657 Add members view for groups in a managed instance
Diffstat (limited to 'server/sonar-web/src')
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; |