diff options
author | guillaume-peoch-sonarsource <guillaume.peoch@sonarsource.com> | 2023-03-14 12:30:42 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-03-22 20:04:08 +0000 |
commit | 37add481610d383abdac8982537c8d6370463b84 (patch) | |
tree | 65b38ca944e63bd450cd15d454a04646d715bf60 /server/sonar-web/src/main/js/apps/groups | |
parent | 0de4d1230e46f1ab72df252c1f17ec3fd0796132 (diff) | |
download | sonarqube-37add481610d383abdac8982537c8d6370463b84.tar.gz sonarqube-37add481610d383abdac8982537c8d6370463b84.zip |
SONAR-18657 Add Filters for all, local and managed groups on groups list
Diffstat (limited to 'server/sonar-web/src/main/js/apps/groups')
21 files changed, 413 insertions, 689 deletions
diff --git a/server/sonar-web/src/main/js/apps/groups/components/App.tsx b/server/sonar-web/src/main/js/apps/groups/components/App.tsx index 31cd060f6c9..847d29b9fb1 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/groups/components/App.tsx @@ -22,6 +22,7 @@ import * as React from 'react'; import { Helmet } from 'react-helmet-async'; import { getSystemInfo } from '../../../api/system'; import { createGroup, deleteGroup, searchUsersGroups, updateGroup } from '../../../api/user_groups'; +import ButtonToggle from '../../../components/controls/ButtonToggle'; import ListFooter from '../../../components/controls/ListFooter'; import SearchBox from '../../../components/controls/SearchBox'; import Suggestions from '../../../components/embed-docs-modal/Suggestions'; @@ -41,11 +42,17 @@ interface State { paging?: Paging; query: string; manageProvider?: string; + managed: boolean | undefined; } export default class App extends React.PureComponent<{}, State> { mounted = false; - state: State = { loading: true, query: '' }; + state: State = { + loading: true, + query: '', + managed: undefined, + paging: { pageIndex: 1, pageSize: 100, total: 1000 }, + }; componentDidMount() { this.mounted = true; @@ -53,18 +60,19 @@ export default class App extends React.PureComponent<{}, State> { this.fetchManageInstance(); } + componentDidUpdate(_prevProps: {}, prevState: State) { + if (prevState.query !== this.state.query || prevState.managed !== this.state.managed) { + this.fetchGroups(); + } + if (prevState !== undefined && prevState.paging?.pageIndex !== this.state.paging?.pageIndex) { + this.fetchMoreGroups(); + } + } + componentWillUnmount() { this.mounted = false; } - makeFetchGroupsRequest = (data?: { p?: number; q?: string }) => { - this.setState({ loading: true }); - return searchUsersGroups({ - q: this.state.query, - ...data, - }); - }; - async fetchManageInstance() { const info = (await getSystemInfo()) as SysInfoCluster; if (this.mounted) { @@ -80,9 +88,14 @@ export default class App extends React.PureComponent<{}, State> { } }; - fetchGroups = async (data?: { p?: number; q?: string }) => { + fetchGroups = async () => { + const { query: q, managed } = this.state; + this.setState({ loading: true }); try { - const { groups, paging } = await this.makeFetchGroupsRequest(data); + const { groups, paging } = await searchUsersGroups({ + q, + managed, + }); if (this.mounted) { this.setState({ groups, loading: false, paging }); } @@ -92,11 +105,13 @@ export default class App extends React.PureComponent<{}, State> { }; fetchMoreGroups = async () => { - const { paging: currentPaging } = this.state; + const { query: q, managed, paging: currentPaging } = this.state; if (currentPaging && currentPaging.total > currentPaging.pageIndex * currentPaging.pageSize) { try { - const { groups, paging } = await this.makeFetchGroupsRequest({ - p: currentPaging.pageIndex + 1, + const { groups, paging } = await searchUsersGroups({ + p: currentPaging.pageIndex, + q, + managed, }); if (this.mounted) { this.setState(({ groups: existingGroups = [] }) => ({ @@ -111,15 +126,10 @@ export default class App extends React.PureComponent<{}, State> { } }; - search = (query: string) => { - this.fetchGroups({ q: query }); - this.setState({ query }); - }; - refresh = async () => { - const { paging, query } = this.state; + const { paging } = this.state; - await this.fetchGroups({ q: query }); + await this.fetchGroups(); // reload all pages in order if (paging && paging.pageIndex > 1) { @@ -130,22 +140,6 @@ export default class App extends React.PureComponent<{}, State> { } }; - closeDeleteForm = () => { - this.setState({ groupToBeDeleted: undefined }); - }; - - closeEditForm = () => { - this.setState({ editedGroup: undefined }); - }; - - openDeleteForm = (group: Group) => { - this.setState({ groupToBeDeleted: group }); - }; - - openEditForm = (group: Group) => { - this.setState({ editedGroup: group }); - }; - handleCreate = async (data: { description: string; name: string }) => { await createGroup({ ...data }); @@ -200,8 +194,16 @@ export default class App extends React.PureComponent<{}, State> { }; render() { - const { editedGroup, groupToBeDeleted, groups, loading, paging, query, manageProvider } = - this.state; + const { + editedGroup, + groupToBeDeleted, + groups, + loading, + paging, + query, + manageProvider, + managed, + } = this.state; const showAnyone = 'anyone'.includes(query.toLowerCase()); @@ -212,22 +214,45 @@ export default class App extends React.PureComponent<{}, State> { <main className="page page-limited" id="groups-page"> <Header onCreate={this.handleCreate} manageProvider={manageProvider} /> - <SearchBox - className="big-spacer-bottom" - id="groups-search" - minLength={2} - onChange={this.search} - placeholder={translate('search.search_by_name')} - value={query} - /> + <div className="display-flex-justify-start big-spacer-bottom big-spacer-top"> + {manageProvider !== undefined && ( + <div className="big-spacer-right"> + <ButtonToggle + value={managed === undefined ? 'all' : managed} + disabled={loading} + options={[ + { label: translate('all'), value: 'all' }, + { label: translate('managed'), value: true }, + { label: translate('local'), value: false }, + ]} + onCheck={(filterOption) => { + if (filterOption === 'all') { + this.setState({ managed: undefined }); + } else { + this.setState({ managed: filterOption as boolean }); + } + }} + /> + </div> + )} + <SearchBox + className="big-spacer-bottom" + id="groups-search" + minLength={2} + onChange={(q) => this.setState({ query: q })} + placeholder={translate('search.search_by_name')} + value={query} + /> + </div> {groups !== undefined && ( <List groups={groups} - onDelete={this.openDeleteForm} - onEdit={this.openEditForm} + onDelete={(groupToBeDeleted) => this.setState({ groupToBeDeleted })} + onEdit={(editedGroup) => this.setState({ editedGroup })} onEditMembers={this.refresh} showAnyone={showAnyone} + manageProvider={manageProvider} /> )} @@ -236,7 +261,11 @@ export default class App extends React.PureComponent<{}, State> { <ListFooter count={showAnyone ? groups.length + 1 : groups.length} loading={loading} - loadMore={this.fetchMoreGroups} + loadMore={() => { + if (paging.total > paging.pageIndex * paging.pageSize) { + this.setState({ paging: { ...paging, pageIndex: paging.pageIndex + 1 } }); + } + }} ready={!loading} total={showAnyone ? paging.total + 1 : paging.total} /> @@ -246,7 +275,7 @@ export default class App extends React.PureComponent<{}, State> { {groupToBeDeleted && ( <DeleteForm group={groupToBeDeleted} - onClose={this.closeDeleteForm} + onClose={() => this.setState({ groupToBeDeleted: undefined })} onSubmit={this.handleDelete} /> )} @@ -256,7 +285,7 @@ export default class App extends React.PureComponent<{}, State> { confirmButtonText={translate('update_verb')} group={editedGroup} header={translate('groups.update_group')} - onClose={this.closeEditForm} + onClose={() => this.setState({ editedGroup: undefined })} onSubmit={this.handleEdit} /> )} diff --git a/server/sonar-web/src/main/js/apps/groups/components/EditMembers.tsx b/server/sonar-web/src/main/js/apps/groups/components/EditMembers.tsx index d22a55ee014..ca86ea9fa52 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/EditMembers.tsx +++ b/server/sonar-web/src/main/js/apps/groups/components/EditMembers.tsx @@ -20,7 +20,7 @@ import * as React from 'react'; import { ButtonIcon } from '../../../components/controls/buttons'; import BulletListIcon from '../../../components/icons/BulletListIcon'; -import { translate } from '../../../helpers/l10n'; +import { translateWithParameters } from '../../../helpers/l10n'; import { Group } from '../../../types/types'; import EditMembersModal from './EditMembersModal'; @@ -61,10 +61,10 @@ export default class EditMembers extends React.PureComponent<Props, State> { return ( <> <ButtonIcon - aria-label={translate('groups.users.edit')} + aria-label={translateWithParameters('groups.users.edit', this.props.group.name)} className="button-small" onClick={this.handleMembersClick} - title={translate('groups.users.edit')} + title={translateWithParameters('groups.users.edit', this.props.group.name)} > <BulletListIcon /> </ButtonIcon> 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 81e71d6cd8d..8a681c42b5e 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 @@ -25,12 +25,12 @@ import { Alert } from '../../../components/ui/Alert'; import { translate } from '../../../helpers/l10n'; import Form from './Form'; -interface Props { +interface HeaderProps { onCreate: (data: { description: string; name: string }) => Promise<void>; manageProvider?: string; } -export default function Header(props: Props) { +export default function Header(props: HeaderProps) { const { manageProvider } = props; const [createModal, setCreateModal] = React.useState(false); 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 74bd3440f5e..06b658e5724 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 @@ -29,9 +29,12 @@ interface Props { onEdit: (group: Group) => void; onEditMembers: () => void; showAnyone: boolean; + manageProvider: string | undefined; } export default function List(props: Props) { + const { groups, manageProvider, showAnyone } = props; + return ( <div className="boxed-group boxed-group-inner"> <table className="data zebra zebra-hover" id="groups-list"> @@ -46,7 +49,7 @@ export default function List(props: Props) { </tr> </thead> <tbody> - {props.showAnyone && ( + {showAnyone && ( <tr className="js-anyone" key="anyone"> <td className="width-20"> <strong className="js-group-name">{translate('groups.anyone')}</strong> @@ -61,13 +64,14 @@ export default function List(props: Props) { </tr> )} - {sortBy(props.groups, (group) => group.name.toLowerCase()).map((group) => ( + {sortBy(groups, (group) => group.name.toLowerCase()).map((group) => ( <ListItem group={group} key={group.name} onDelete={props.onDelete} onEdit={props.onEdit} onEditMembers={props.onEditMembers} + manageProvider={manageProvider} /> ))} </tbody> 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 f148746e94a..c631103f75a 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 @@ -22,7 +22,7 @@ import ActionsDropdown, { ActionsDropdownDivider, ActionsDropdownItem, } from '../../../components/controls/ActionsDropdown'; -import { translate } from '../../../helpers/l10n'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; import { Group } from '../../../types/types'; import EditMembers from './EditMembers'; @@ -31,41 +31,63 @@ export interface ListItemProps { onDelete: (group: Group) => void; onEdit: (group: Group) => void; onEditMembers: () => void; + manageProvider: string | undefined; } export default function ListItem(props: ListItemProps) { - const { group } = props; + const { manageProvider, group } = props; + const { name, managed, membersCount, description } = group; + + const isManaged = () => { + return manageProvider !== undefined; + }; + + const isGroupLocal = () => { + return isManaged() && !managed; + }; return ( - <tr data-id={group.name}> + <tr data-id={name}> <td className="width-20"> - <strong className="js-group-name">{group.name}</strong> + <strong className="js-group-name">{name}</strong> {group.default && <span className="little-spacer-left">({translate('default')})</span>} + {isGroupLocal() && <span className="little-spacer-left badge">{translate('local')}</span>} </td> - <td className="thin text-middle text-right little-padded-right">{group.membersCount}</td> + <td className="thin text-middle text-right little-padded-right">{membersCount}</td> <td className="little-padded-left"> - {!group.default && <EditMembers group={group} onEdit={props.onEditMembers} />} + {!group.default && !isManaged() && ( + <EditMembers group={group} onEdit={props.onEditMembers} /> + )} </td> <td className="width-40"> - <span className="js-group-description">{group.description}</span> + <span className="js-group-description">{description}</span> </td> <td className="thin nowrap text-right"> - {!group.default && ( - <ActionsDropdown> - <ActionsDropdownItem className="js-group-update" onClick={() => props.onEdit(group)}> - {translate('update_details')} - </ActionsDropdownItem> - <ActionsDropdownDivider /> - <ActionsDropdownItem - className="js-group-delete" - destructive={true} - onClick={() => props.onDelete(group)} - > - {translate('delete')} - </ActionsDropdownItem> + {!group.default && (!isManaged() || isGroupLocal()) && ( + <ActionsDropdown label={translateWithParameters('groups.edit', group.name)}> + {!isManaged() && ( + <> + <ActionsDropdownItem + className="js-group-update" + onClick={() => props.onEdit(group)} + > + {translate('update_details')} + </ActionsDropdownItem> + <ActionsDropdownDivider /> + </> + )} + {(!isManaged() || isGroupLocal()) && ( + <ActionsDropdownItem + className="js-group-delete" + destructive={true} + onClick={() => props.onDelete(group)} + > + {translate('delete')} + </ActionsDropdownItem> + )} </ActionsDropdown> )} </td> diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/App-test.tsx deleted file mode 100644 index 8858be2fd5e..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/App-test.tsx +++ /dev/null @@ -1,191 +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 { - createGroup, - deleteGroup, - searchUsersGroups, - updateGroup, -} from '../../../../api/user_groups'; -import { mockGroup } from '../../../../helpers/testMocks'; -import { waitAndUpdate } from '../../../../helpers/testUtils'; -import App from '../App'; - -jest.mock('../../../../api/user_groups', () => ({ - createGroup: jest.fn().mockResolvedValue({ - default: false, - description: 'Desc foo', - membersCount: 0, - name: 'Foo', - }), - deleteGroup: jest.fn().mockResolvedValue({}), - searchUsersGroups: jest.fn().mockResolvedValue({ - paging: { pageIndex: 1, pageSize: 2, total: 4 }, - groups: [ - { - default: false, - description: 'Owners of organization foo', - membersCount: 1, - name: 'Owners', - }, - { - default: true, - description: 'Members of organization foo', - membersCount: 2, - name: 'Members', - }, - ], - }), - updateGroup: jest.fn().mockResolvedValue({}), -})); - -jest.mock('../../../../api/system', () => ({ - getSystemInfo: jest.fn().mockResolvedValue({ System: {} }), -})); - -beforeEach(() => { - jest.clearAllMocks(); -}); - -it('should render correctly', async () => { - const wrapper = shallowRender(); - expect(wrapper).toMatchSnapshot(); - await waitAndUpdate(wrapper); - expect(searchUsersGroups).toHaveBeenCalledWith({ q: '' }); - expect(wrapper).toMatchSnapshot(); -}); - -it('should correctly handle creation', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - expect(wrapper.state('groups')).toHaveLength(2); - wrapper.instance().handleCreate({ description: 'Desc foo', name: 'foo' }); - await waitAndUpdate(wrapper); - expect(createGroup).toHaveBeenCalled(); -}); - -it('should correctly handle deletion', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - expect(wrapper.state('groups')).toHaveLength(2); - wrapper.setState({ groupToBeDeleted: mockGroup({ name: 'Members' }) }); - wrapper.instance().handleDelete(); - await waitAndUpdate(wrapper); - expect(deleteGroup).toHaveBeenCalled(); - expect(wrapper.state().groupToBeDeleted).toBeUndefined(); -}); - -it('should ignore deletion', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - wrapper.setState({ groupToBeDeleted: undefined }); - wrapper.instance().handleDelete(); - expect(deleteGroup).not.toHaveBeenCalled(); -}); - -it('should correctly handle edition', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - wrapper.setState({ editedGroup: mockGroup({ name: 'Owners' }) }); - wrapper.instance().handleEdit({ description: 'foo', name: 'bar' }); - await waitAndUpdate(wrapper); - expect(updateGroup).toHaveBeenCalled(); - expect(wrapper.state('groups')).toContainEqual({ - default: false, - description: 'foo', - membersCount: 1, - name: 'bar', - }); -}); - -it('should ignore edition', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - wrapper.setState({ editedGroup: undefined }); - wrapper.instance().handleEdit({ description: 'nope', name: 'nuhuh' }); - expect(updateGroup).not.toHaveBeenCalled(); -}); - -it('should fetch more groups', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - wrapper.find('ListFooter').prop<Function>('loadMore')(); - await waitAndUpdate(wrapper); - expect(searchUsersGroups).toHaveBeenCalledWith({ p: 2, q: '' }); - expect(wrapper.state('groups')).toHaveLength(4); -}); - -it('should search for groups', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - wrapper.find('SearchBox').prop<Function>('onChange')('foo'); - expect(searchUsersGroups).toHaveBeenCalledWith({ q: 'foo' }); - expect(wrapper.state('query')).toBe('foo'); -}); - -it('should handle edit modal', async () => { - const editedGroup = mockGroup(); - - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - expect(wrapper.state().editedGroup).toBeUndefined(); - - wrapper.instance().openEditForm(editedGroup); - expect(wrapper.state().editedGroup).toEqual(editedGroup); - - wrapper.instance().closeEditForm(); - expect(wrapper.state().editedGroup).toBeUndefined(); -}); - -it('should handle delete modal', async () => { - const groupToBeDeleted = mockGroup(); - - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - expect(wrapper.state().groupToBeDeleted).toBeUndefined(); - - wrapper.instance().openDeleteForm(groupToBeDeleted); - expect(wrapper.state().groupToBeDeleted).toEqual(groupToBeDeleted); - - wrapper.instance().closeDeleteForm(); - expect(wrapper.state().groupToBeDeleted).toBeUndefined(); -}); - -it('should refresh correctly', async () => { - const wrapper = shallowRender(); - - await waitAndUpdate(wrapper); - - const query = 'preserve me'; - wrapper.setState({ paging: { pageIndex: 2, pageSize: 2, total: 5 }, query }); - - (searchUsersGroups as jest.Mock).mockClear(); - - wrapper.instance().refresh(); - await waitAndUpdate(wrapper); - - expect(searchUsersGroups).toHaveBeenNthCalledWith(1, { q: query }); - expect(searchUsersGroups).toHaveBeenNthCalledWith(2, { q: query, p: 2 }); -}); - -function shallowRender(props: Partial<App['props']> = {}) { - return shallow<App>(<App {...props} />); -} diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/DeleteForm-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/DeleteForm-test.tsx deleted file mode 100644 index 0ae92dc74c6..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/DeleteForm-test.tsx +++ /dev/null @@ -1,29 +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 DeleteForm from '../DeleteForm'; - -it('should render', () => { - const group = { id: 3, name: 'Foo', membersCount: 5 }; - expect( - shallow(<DeleteForm group={group} onClose={jest.fn()} onSubmit={jest.fn()} />).dive() - ).toMatchSnapshot(); -}); 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 index 5c0e6a440b5..cce45974640 100644 --- 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 @@ -19,11 +19,12 @@ */ 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 = { id: 3, name: 'Foo', membersCount: 5 }; + const group = mockGroup({ name: 'Foo', membersCount: 5 }); const onEdit = jest.fn(); const wrapper = shallow(<EditMembers group={group} onEdit={onEdit} />); diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembersModal-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembersModal-test.tsx index a8b4cc49e0f..d0ba4a37412 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembersModal-test.tsx +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembersModal-test.tsx @@ -21,10 +21,11 @@ import { shallow } from 'enzyme'; import * as React from 'react'; import { addUserToGroup, getUsersInGroup, removeUserFromGroup } from '../../../../api/user_groups'; import SelectList, { SelectListFilter } from '../../../../components/controls/SelectList'; +import { mockGroup } from '../../../../helpers/testMocks'; import { waitAndUpdate } from '../../../../helpers/testUtils'; import EditMembersModal from '../EditMembersModal'; -const group = { id: 1, name: 'foo', membersCount: 1 }; +const group = mockGroup({ name: 'foo', membersCount: 1 }); jest.mock('../../../../api/user_groups', () => ({ getUsersInGroup: jest.fn().mockResolvedValue({ diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/Form-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/Form-test.tsx deleted file mode 100644 index 31992d1d140..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/Form-test.tsx +++ /dev/null @@ -1,49 +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 { change, click, submit } from '../../../../helpers/testUtils'; -import Form from '../Form'; - -it('should render form', async () => { - const onClose = jest.fn(); - const onSubmit = jest.fn(() => Promise.resolve()); - const wrapper = shallow( - <Form - confirmButtonText="confirmButtonText" - header="header" - onClose={onClose} - onSubmit={onSubmit} - /> - ).dive(); - expect(wrapper).toMatchSnapshot(); - - change(wrapper.find('[name="name"]'), 'foo'); - change(wrapper.find('[name="description"]'), 'bar'); - submit(wrapper.find('form')); - expect(onSubmit).toHaveBeenCalledWith({ description: 'bar', name: 'foo' }); - - await new Promise(setImmediate); - expect(onClose).toHaveBeenCalled(); - - onClose.mockClear(); - click(wrapper.find('ResetButtonLink')); - expect(onClose).toHaveBeenCalled(); -}); 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 new file mode 100644 index 00000000000..d705163afe5 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/GroupsApp-it.tsx @@ -0,0 +1,255 @@ +/* + * 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 { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import * as React from 'react'; +import { act } from 'react-dom/test-utils'; +import { byRole, byText } from 'testing-library-selector'; +import GroupsServiceMock from '../../../../api/mocks/GroupsServiceMock'; +import { renderApp } from '../../../../helpers/testReactTestingUtils'; +import App from '../App'; + +jest.mock('../../../../api/users'); +jest.mock('../../../../api/system'); +jest.mock('../../../../api/user_groups'); + +const handler = new GroupsServiceMock(); + +const ui = { + createGroupButton: byRole('button', { name: 'groups.create_group' }), + infoManageMode: byText(/groups\.page\.managed_description/), + description: byText('user_groups.page.description'), + allFilter: byRole('button', { name: 'all' }), + managedFilter: byRole('button', { name: 'managed' }), + localFilter: byRole('button', { name: 'local' }), + searchInput: byRole('searchbox', { name: 'search.search_by_name' }), + updateButton: byRole('button', { name: 'update_details' }), + updateDialog: byRole('dialog', { name: 'groups.update_group' }), + updateDialogButton: byRole('button', { name: 'update_verb' }), + deleteButton: byRole('button', { name: 'delete' }), + deleteDialog: byRole('dialog', { name: 'groups.delete_group' }), + deleteDialogButton: byRole('button', { name: 'delete' }), + showMore: byRole('button', { name: 'show_more' }), + nameInput: byRole('textbox', { name: 'name field_required' }), + descriptionInput: byRole('textbox', { name: 'description' }), + createGroupDialogButton: byRole('button', { name: 'create' }), + editGroupDialogButton: byRole('button', { name: 'groups.create_group' }), + + createGroupDialog: byRole('dialog', { name: 'groups.create_group' }), + membersDialog: byRole('dialog', { name: 'users.update' }), + + managedGroupRow: byRole('row', { name: 'managed-group 1' }), + managedGroupEditMembersButton: byRole('button', { name: 'groups.users.edit.managed-group' }), + managedEditButton: byRole('button', { name: 'groups.edit.managed-group' }), + + localGroupRow: byRole('row', { name: 'local-group 1' }), + 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!' }), + localEditButton: byRole('button', { name: 'groups.edit.local-group' }), + localGroupRowWithLocalBadge: byRole('row', { + name: 'local-group local 1', + }), +}; + +describe('in non managed mode', () => { + beforeEach(() => { + handler.setIsManaged(false); + handler.reset(); + }); + + it('should render all groups', async () => { + renderGroupsApp(); + + expect(await ui.localGroupRow.find()).toBeInTheDocument(); + expect(ui.managedGroupRow.get()).toBeInTheDocument(); + expect(ui.localGroupRowWithLocalBadge.query()).not.toBeInTheDocument(); + }); + + it('should be able to create a group', async () => { + const user = userEvent.setup(); + renderGroupsApp(); + + expect(await ui.description.find()).toBeInTheDocument(); + + await user.click(ui.createGroupButton.get()); + expect(ui.createGroupDialog.get()).toBeInTheDocument(); + + await user.type(ui.nameInput.get(), 'local-group 2'); + await user.type(ui.descriptionInput.get(), 'group 2 is loco!'); + + await act(async () => { + await user.click(ui.createGroupDialogButton.get()); + }); + + expect(await ui.localGroupRow2.find()).toBeInTheDocument(); + }); + + it('should be able to delete a group', async () => { + const user = userEvent.setup(); + renderGroupsApp(); + + await user.click(await ui.localEditButton.find()); + await user.click(await ui.deleteButton.find()); + + expect(await ui.deleteDialog.find()).toBeInTheDocument(); + await act(async () => { + await user.click(ui.deleteDialogButton.get()); + }); + + expect(await ui.managedGroupRow.find()).toBeInTheDocument(); + expect(ui.localGroupRow.query()).not.toBeInTheDocument(); + }); + + it('should be able to edit a group', async () => { + const user = userEvent.setup(); + renderGroupsApp(); + + await user.click(await ui.localEditButton.find()); + await user.click(await ui.updateButton.find()); + + expect(ui.updateDialog.get()).toBeInTheDocument(); + + await user.clear(ui.nameInput.get()); + await user.type(ui.nameInput.get(), 'local-group 3'); + await user.clear(ui.descriptionInput.get()); + await user.type(ui.descriptionInput.get(), 'group 3 rocks!'); + + expect(ui.updateDialog.get()).toBeInTheDocument(); + + await act(async () => { + await user.click(ui.updateDialogButton.get()); + }); + + expect(await ui.managedGroupRow.find()).toBeInTheDocument(); + expect(await ui.editedLocalGroupRow.find()).toBeInTheDocument(); + }); + + it('should be able to edit the members of a group', async () => { + const user = userEvent.setup(); + renderGroupsApp(); + + expect(await ui.localGroupRow.find()).toBeInTheDocument(); + expect(await ui.localGroupEditMembersButton.find()).toBeInTheDocument(); + + await user.click(ui.localGroupEditMembersButton.get()); + expect(await ui.membersDialog.find()).toBeInTheDocument(); + }); + + it('should be able search a group', async () => { + const user = userEvent.setup(); + renderGroupsApp(); + + expect(await ui.localGroupRow.find()).toBeInTheDocument(); + expect(ui.managedGroupRow.get()).toBeInTheDocument(); + + await user.type(await ui.searchInput.find(), 'local'); + + expect(await ui.localGroupRow.find()).toBeInTheDocument(); + expect(ui.managedGroupRow.query()).not.toBeInTheDocument(); + }); + + it('should be able load more group', async () => { + const user = userEvent.setup(); + renderGroupsApp(); + + // including the anyone (deprecated) group + expect(await screen.findAllByRole('row')).toHaveLength(4); + + await user.click(await ui.showMore.find()); + + expect(await screen.findAllByRole('row')).toHaveLength(6); + }); +}); + +describe('in manage mode', () => { + beforeEach(() => { + handler.setIsManaged(true); + handler.reset(); + }); + + it('should not be able to create a group', async () => { + renderGroupsApp(); + expect(await ui.createGroupButton.find()).toBeDisabled(); + expect(ui.infoManageMode.get()).toBeInTheDocument(); + }); + + it('should ONLY be able to delete a local group', async () => { + const user = userEvent.setup(); + renderGroupsApp(); + + expect(await ui.localGroupRowWithLocalBadge.find()).toBeInTheDocument(); + + await user.click(await ui.localFilter.find()); + await user.click(await ui.localEditButton.find()); + expect(ui.updateButton.query()).not.toBeInTheDocument(); + + await user.click(await ui.deleteButton.find()); + + expect(await ui.deleteDialog.find()).toBeInTheDocument(); + await act(async () => { + await user.click(ui.deleteDialogButton.get()); + }); + expect(ui.localGroupRowWithLocalBadge.query()).not.toBeInTheDocument(); + }); + + it('should not be able to delete or edit a managed group', async () => { + renderGroupsApp(); + + expect(await ui.managedGroupRow.find()).toBeInTheDocument(); + expect(ui.managedEditButton.query()).not.toBeInTheDocument(); + + expect(ui.managedGroupEditMembersButton.query()).not.toBeInTheDocument(); + }); + + it('should render list of all groups', async () => { + renderGroupsApp(); + + expect(await ui.allFilter.find()).toBeInTheDocument(); + + expect(ui.localGroupRowWithLocalBadge.get()).toBeInTheDocument(); + expect(ui.managedGroupRow.get()).toBeInTheDocument(); + }); + + it('should render list of managed groups', async () => { + const user = userEvent.setup(); + renderGroupsApp(); + + await user.click(await ui.managedFilter.find()); + + expect(ui.localGroupRow.query()).not.toBeInTheDocument(); + expect(ui.managedGroupRow.get()).toBeInTheDocument(); + }); + + it('should render list of local groups', async () => { + const user = userEvent.setup(); + renderGroupsApp(); + + await user.click(await ui.localFilter.find()); + + expect(ui.localGroupRowWithLocalBadge.get()).toBeInTheDocument(); + expect(ui.managedGroupRow.query()).not.toBeInTheDocument(); + }); +}); + +function renderGroupsApp() { + return renderApp('admin/groups', <App />); +} diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/Header-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/Header-test.tsx deleted file mode 100644 index d0e25d51f7c..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/Header-test.tsx +++ /dev/null @@ -1,35 +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 { click } from '../../../../helpers/testUtils'; -import Header from '../Header'; - -it('should create new group', () => { - const onCreate = jest.fn(() => Promise.resolve()); - const wrapper = shallow(<Header onCreate={onCreate} />); - expect(wrapper).toMatchSnapshot(); - - click(wrapper.find('[id="groups-create"]')); - expect(wrapper).toMatchSnapshot(); - - wrapper.find('Form').prop<Function>('onSubmit')({ name: 'foo', description: 'bar' }); - expect(onCreate).toHaveBeenCalledWith({ name: 'foo', description: 'bar' }); -}); 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 index 625a816334a..a003332f77b 100644 --- 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 @@ -19,6 +19,7 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; +import { mockGroup } from '../../../../helpers/testMocks'; import List from '../List'; it('should render', () => { @@ -31,9 +32,9 @@ it('should not render "Anyone"', () => { function shallowRender(showAnyone = true) { const groups = [ - { id: 1, name: 'sonar-users', description: '', membersCount: 55, default: true }, - { id: 2, name: 'foo', description: 'foobar', membersCount: 0, default: false }, - { id: 3, name: 'bar', description: 'barbar', membersCount: 1, default: false }, + 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 @@ -42,6 +43,7 @@ function shallowRender(showAnyone = true) { onEdit={jest.fn()} onEditMembers={jest.fn()} showAnyone={showAnyone} + manageProvider={undefined} /> ); } 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 index 7bcfafa6968..206257c15cd 100644 --- 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 @@ -34,6 +34,7 @@ function shallowRender(overrides: Partial<ListItemProps> = {}) { onDelete={jest.fn()} onEdit={jest.fn()} onEditMembers={jest.fn()} + manageProvider={undefined} {...overrides} /> ); diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/App-test.tsx.snap deleted file mode 100644 index a06d0810832..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/App-test.tsx.snap +++ /dev/null @@ -1,94 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -<Fragment> - <Suggestions - suggestions="user_groups" - /> - <Helmet - defer={false} - encodeSpecialCharacters={true} - prioritizeSeoTags={false} - title="user_groups.page" - /> - <main - className="page page-limited" - id="groups-page" - > - <Header - onCreate={[Function]} - /> - <SearchBox - className="big-spacer-bottom" - id="groups-search" - minLength={2} - onChange={[Function]} - placeholder="search.search_by_name" - value="" - /> - </main> -</Fragment> -`; - -exports[`should render correctly 2`] = ` -<Fragment> - <Suggestions - suggestions="user_groups" - /> - <Helmet - defer={false} - encodeSpecialCharacters={true} - prioritizeSeoTags={false} - title="user_groups.page" - /> - <main - className="page page-limited" - id="groups-page" - > - <Header - onCreate={[Function]} - /> - <SearchBox - className="big-spacer-bottom" - id="groups-search" - minLength={2} - onChange={[Function]} - placeholder="search.search_by_name" - value="" - /> - <List - groups={ - [ - { - "default": false, - "description": "Owners of organization foo", - "membersCount": 1, - "name": "Owners", - }, - { - "default": true, - "description": "Members of organization foo", - "membersCount": 2, - "name": "Members", - }, - ] - } - onDelete={[Function]} - onEdit={[Function]} - onEditMembers={[Function]} - showAnyone={true} - /> - <div - id="groups-list-footer" - > - <ListFooter - count={3} - loadMore={[Function]} - loading={false} - ready={true} - total={5} - /> - </div> - </main> -</Fragment> -`; diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/DeleteForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/DeleteForm-test.tsx.snap deleted file mode 100644 index 6f7b6125eff..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/DeleteForm-test.tsx.snap +++ /dev/null @@ -1,45 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render 1`] = ` -<Modal - contentLabel="groups.delete_group" - onRequestClose={[MockFunction]} -> - <form - onSubmit={[Function]} - > - <header - className="modal-head" - > - <h2> - groups.delete_group - </h2> - </header> - <div - className="modal-body" - > - groups.delete_group.confirmation.Foo - </div> - <footer - className="modal-foot" - > - <DeferredSpinner - className="spacer-right" - loading={false} - /> - <SubmitButton - className="button-red" - disabled={false} - > - delete - </SubmitButton> - <ResetButtonLink - disabled={false} - onClick={[Function]} - > - cancel - </ResetButtonLink> - </footer> - </form> -</Modal> -`; 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 index b074ac3da9b..044ea201e66 100644 --- 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 @@ -3,10 +3,10 @@ exports[`should edit members 1`] = ` <Fragment> <ButtonIcon - aria-label="groups.users.edit" + aria-label="groups.users.edit.Foo" className="button-small" onClick={[Function]} - title="groups.users.edit" + title="groups.users.edit.Foo" > <BulletListIcon /> </ButtonIcon> @@ -16,17 +16,17 @@ exports[`should edit members 1`] = ` exports[`should edit members 2`] = ` <Fragment> <ButtonIcon - aria-label="groups.users.edit" + aria-label="groups.users.edit.Foo" className="button-small" onClick={[Function]} - title="groups.users.edit" + title="groups.users.edit.Foo" > <BulletListIcon /> </ButtonIcon> <EditMembersModal group={ { - "id": 3, + "managed": false, "membersCount": 5, "name": "Foo", } @@ -39,10 +39,10 @@ exports[`should edit members 2`] = ` exports[`should edit members 3`] = ` <Fragment> <ButtonIcon - aria-label="groups.users.edit" + aria-label="groups.users.edit.Foo" className="button-small" onClick={[Function]} - title="groups.users.edit" + title="groups.users.edit.Foo" > <BulletListIcon /> </ButtonIcon> diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Form-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Form-test.tsx.snap deleted file mode 100644 index 1f5ac9699d7..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Form-test.tsx.snap +++ /dev/null @@ -1,82 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render form 1`] = ` -<Modal - contentLabel="header" - onRequestClose={[MockFunction]} - size="small" -> - <form - onSubmit={[Function]} - > - <header - className="modal-head" - > - <h2> - header - </h2> - </header> - <div - className="modal-body" - > - <MandatoryFieldsExplanation - className="modal-field" - /> - <div - className="modal-field" - > - <label - htmlFor="create-group-name" - > - name - <MandatoryFieldMarker /> - </label> - <input - autoFocus={true} - id="create-group-name" - maxLength={255} - name="name" - onChange={[Function]} - required={true} - size={50} - type="text" - value="" - /> - </div> - <div - className="modal-field" - > - <label - htmlFor="create-group-description" - > - description - </label> - <textarea - id="create-group-description" - name="description" - onChange={[Function]} - value="" - /> - </div> - </div> - <footer - className="modal-foot" - > - <DeferredSpinner - className="spacer-right" - loading={false} - /> - <SubmitButton - disabled={false} - > - confirmButtonText - </SubmitButton> - <ResetButtonLink - onClick={[Function]} - > - cancel - </ResetButtonLink> - </footer> - </form> -</Modal> -`; diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Header-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Header-test.tsx.snap deleted file mode 100644 index ba792ca89fc..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Header-test.tsx.snap +++ /dev/null @@ -1,69 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should create new group 1`] = ` -<Fragment> - <div - className="page-header" - id="groups-header" - > - <h2 - className="page-title" - > - user_groups.page - </h2> - <div - className="page-actions" - > - <Button - disabled={false} - id="groups-create" - onClick={[Function]} - > - groups.create_group - </Button> - </div> - <p - className="page-description" - > - user_groups.page.description - </p> - </div> -</Fragment> -`; - -exports[`should create new group 2`] = ` -<Fragment> - <div - className="page-header" - id="groups-header" - > - <h2 - className="page-title" - > - user_groups.page - </h2> - <div - className="page-actions" - > - <Button - disabled={false} - id="groups-create" - onClick={[Function]} - > - groups.create_group - </Button> - </div> - <p - className="page-description" - > - user_groups.page.description - </p> - </div> - <Form - confirmButtonText="create" - header="groups.create_group" - onClose={[Function]} - onSubmit={[MockFunction]} - /> -</Fragment> -`; 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 index 5d3a39ef7ca..19ddc35298e 100644 --- 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 @@ -64,7 +64,7 @@ exports[`should render 1`] = ` { "default": false, "description": "barbar", - "id": 3, + "managed": false, "membersCount": 1, "name": "bar", } @@ -79,7 +79,7 @@ exports[`should render 1`] = ` { "default": false, "description": "foobar", - "id": 2, + "managed": false, "membersCount": 0, "name": "foo", } @@ -94,7 +94,7 @@ exports[`should render 1`] = ` { "default": true, "description": "", - "id": 1, + "managed": false, "membersCount": 55, "name": "sonar-users", } 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 index 6e22bae2cf0..ca99c67a45f 100644 --- 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 @@ -24,6 +24,7 @@ exports[`should render correctly 1`] = ` <EditMembers group={ { + "managed": false, "membersCount": 1, "name": "Foo", } @@ -41,7 +42,9 @@ exports[`should render correctly 1`] = ` <td className="thin nowrap text-right" > - <ActionsDropdown> + <ActionsDropdown + label="groups.edit.Foo" + > <ActionsDropdownItem className="js-group-update" onClick={[Function]} |