diff options
author | stanislavh <stanislav.honcharov@sonarsource.com> | 2022-12-22 12:29:28 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-12-30 20:02:50 +0000 |
commit | 959f6e157c0a6da4a09369ce0d78f5e916bb0232 (patch) | |
tree | 40873358ef33fa923609ee4defec40885a99d276 /server/sonar-web/src/main/js/apps | |
parent | 32ff596c042f0d95ad1b2a575a3867fd6fc775c9 (diff) | |
download | sonarqube-959f6e157c0a6da4a09369ce0d78f5e916bb0232.tar.gz sonarqube-959f6e157c0a6da4a09369ce0d78f5e916bb0232.zip |
SONAR-13167 Handle large lists of permission templates
Diffstat (limited to 'server/sonar-web/src/main/js/apps')
27 files changed, 600 insertions, 2173 deletions
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/PermissionTemplatesApp.tsx index fce2ef87a33..220da5a796d 100644 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/PermissionTemplatesApp.tsx @@ -42,7 +42,7 @@ interface State { permissionTemplates: PermissionTemplate[]; } -export class App extends React.PureComponent<Props, State> { +export class PermissionTemplatesApp extends React.PureComponent<Props, State> { mounted = false; state: State = { ready: false, @@ -121,4 +121,4 @@ export class App extends React.PureComponent<Props, State> { } } -export default withRouter(withAppStateContext(App)); +export default withRouter(withAppStateContext(PermissionTemplatesApp)); diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/Template.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/Template.tsx index 7e0ad42157c..9842655d55e 100644 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/Template.tsx +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/Template.tsx @@ -17,13 +17,14 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { without } from 'lodash'; import * as React from 'react'; import { Helmet } from 'react-helmet-async'; import * as api from '../../../api/permissions'; import { translate } from '../../../helpers/l10n'; -import { PermissionGroup, PermissionTemplate, PermissionUser } from '../../../types/types'; -import HoldersList from '../../permissions/shared/components/HoldersList'; -import SearchForm from '../../permissions/shared/components/SearchForm'; +import { Paging, PermissionGroup, PermissionTemplate, PermissionUser } from '../../../types/types'; +import AllHoldersList from '../../permissions/shared/components/AllHoldersList'; +import { FilterOption } from '../../permissions/shared/components/SearchForm'; import { convertToPermissionDefinitions, PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE, @@ -38,12 +39,14 @@ interface Props { } interface State { - filter: string; + filter: FilterOption; groups: PermissionGroup[]; + groupsPaging?: Paging; loading: boolean; query: string; selectedPermission?: string; users: PermissionUser[]; + usersPaging?: Paging; } export default class Template extends React.PureComponent<Props, State> { @@ -65,84 +68,185 @@ export default class Template extends React.PureComponent<Props, State> { this.mounted = false; } - requestHolders = (realQuery?: string) => { + loadUsersAndGroups = (usersPage?: number, groupsPage?: number) => { this.setState({ loading: true }); const { template } = this.props; const { query, filter, selectedPermission } = this.state; - const requests = []; - const finalQuery = realQuery != null ? realQuery : query; + const getUsers: Promise<{ paging?: Paging; users: PermissionUser[] }> = + filter !== 'groups' + ? api.getPermissionTemplateUsers({ + templateId: template.id, + q: query || null, + permission: selectedPermission, + p: usersPage, + }) + : Promise.resolve({ paging: undefined, users: [] }); - if (filter !== 'groups') { - requests.push(api.getPermissionTemplateUsers(template.id, finalQuery, selectedPermission)); - } else { - requests.push(Promise.resolve([])); + const getGroups: Promise<{ paging?: Paging; groups: PermissionGroup[] }> = + filter !== 'users' + ? api.getPermissionTemplateGroups({ + templateId: template.id, + q: query || null, + permission: selectedPermission, + p: groupsPage, + }) + : Promise.resolve({ paging: undefined, groups: [] }); + + return Promise.all([getUsers, getGroups]); + }; + + requestHolders = async () => { + const [{ users, paging: usersPaging }, { groups, paging: groupsPaging }] = + await this.loadUsersAndGroups(); + + if (this.mounted) { + this.setState({ + groups, + groupsPaging, + loading: false, + users, + usersPaging, + }); } + }; - if (filter !== 'users') { - requests.push(api.getPermissionTemplateGroups(template.id, finalQuery, selectedPermission)); - } else { - requests.push(Promise.resolve([])); + onLoadMore = async () => { + const { usersPaging, groupsPaging } = this.state; + this.setState({ + loading: true, + }); + const [usersResponse, groupsResponse] = await this.loadUsersAndGroups( + usersPaging ? usersPaging.pageIndex + 1 : 1, + groupsPaging ? groupsPaging.pageIndex + 1 : 1 + ); + if (this.mounted) { + this.setState(({ groups, users }) => ({ + groups: [...groups, ...groupsResponse.groups], + groupsPaging: groupsResponse.paging, + loading: false, + users: [...users, ...usersResponse.users], + usersPaging: usersResponse.paging, + })); } - return Promise.all(requests).then(([users, groups]) => { - if (this.mounted) { - this.setState({ - users, - groups, - loading: false, + }; + + removePermissionFromEntity = <T extends { login?: string; name: string; permissions: string[] }>( + entities: T[], + entity: string, + permission: string + ): T[] => + entities.map((candidate) => + candidate.name === entity || candidate.login === entity + ? { ...candidate, permissions: without(candidate.permissions, permission) } + : candidate + ); + + addPermissionToEntity = <T extends { login?: string; name: string; permissions: string[] }>( + entities: T[], + entity: string, + permission: string + ): T[] => + entities.map((candidate) => + candidate.name === entity || candidate.login === entity + ? { ...candidate, permissions: [...candidate.permissions, permission] } + : candidate + ); + + grantPermissionToUser = (login: string, permission: string) => { + const { template } = this.props; + const isProjectCreator = login === '<creator>'; + + this.setState(({ users }) => ({ + users: this.addPermissionToEntity(users, login, permission), + })); + + const request = isProjectCreator + ? api.addProjectCreatorToTemplate(template.id, permission) + : api.grantTemplatePermissionToUser({ + templateId: template.id, + login, + permission, }); - } + + return request.then(this.props.refresh).catch(() => { + this.setState(({ users }) => ({ + users: this.removePermissionFromEntity(users, login, permission), + })); }); }; - handleToggleUser = (user: PermissionUser, permission: string) => { - if (user.login === '<creator>') { - return this.handleToggleProjectCreator(user, permission); - } + revokePermissionFromUser = (login: string, permission: string) => { const { template } = this.props; - const hasPermission = user.permissions.includes(permission); - const data: { templateId: string; login: string; permission: string } = { - templateId: template.id, - login: user.login, - permission, - }; - - const request = hasPermission - ? api.revokeTemplatePermissionFromUser(data) - : api.grantTemplatePermissionToUser(data); - return request.then(() => this.requestHolders()).then(this.props.refresh); + const isProjectCreator = login === '<creator>'; + + this.setState(({ users }) => ({ + users: this.removePermissionFromEntity(users, login, permission), + })); + + const request = isProjectCreator + ? api.removeProjectCreatorFromTemplate(template.id, permission) + : api.revokeTemplatePermissionFromUser({ + templateId: template.id, + login, + permission, + }); + + return request.then(this.props.refresh).catch(() => { + this.setState(({ users }) => ({ + users: this.addPermissionToEntity(users, login, permission), + })); + }); }; - handleToggleProjectCreator = (user: PermissionUser, permission: string) => { + grantPermissionToGroup = (groupName: string, permission: string) => { const { template } = this.props; - const hasPermission = user.permissions.includes(permission); - const request = hasPermission - ? api.removeProjectCreatorFromTemplate(template.id, permission) - : api.addProjectCreatorToTemplate(template.id, permission); - return request.then(() => this.requestHolders()).then(this.props.refresh); + + this.setState(({ groups }) => ({ + groups: this.addPermissionToEntity(groups, groupName, permission), + })); + + return api + .grantTemplatePermissionToGroup({ + templateId: template.id, + groupName, + permission, + }) + .then(this.props.refresh) + .catch(() => { + this.setState(({ groups }) => ({ + groups: this.removePermissionFromEntity(groups, groupName, permission), + })); + }); }; - handleToggleGroup = (group: PermissionGroup, permission: string) => { + revokePermissionFromGroup = (groupName: string, permission: string) => { const { template } = this.props; - const hasPermission = group.permissions.includes(permission); - const data = { - templateId: template.id, - groupName: group.name, - permission, - }; - const request = hasPermission - ? api.revokeTemplatePermissionFromGroup(data) - : api.grantTemplatePermissionToGroup(data); - return request.then(() => this.requestHolders()).then(this.props.refresh); + + this.setState(({ groups }) => ({ + groups: this.removePermissionFromEntity(groups, groupName, permission), + })); + + return api + .revokeTemplatePermissionFromGroup({ + templateId: template.id, + groupName, + permission, + }) + .then(this.props.refresh) + .catch(() => { + this.setState(({ groups }) => ({ + groups: this.addPermissionToEntity(groups, groupName, permission), + })); + }); }; handleSearch = (query: string) => { - this.setState({ query }); - this.requestHolders(query); + this.setState({ query }, this.requestHolders); }; - handleFilter = (filter: string) => { + handleFilter = (filter: FilterOption) => { this.setState({ filter }, this.requestHolders); }; @@ -169,16 +273,21 @@ export default class Template extends React.PureComponent<Props, State> { }; render() { + const { template, topQualifiers } = this.props; + const { users, loading, groups, groupsPaging, usersPaging, selectedPermission, filter, query } = + this.state; const permissions = convertToPermissionDefinitions( PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE, 'projects_role' ); - const allUsers = [...this.state.users]; + const allUsers = [...users]; - const creatorPermissions = this.props.template.permissions + const creatorPermissions = template.permissions .filter((p) => p.withProjectCreator) .map((p) => p.key); + let usersPagingWithCreator = usersPaging; + if (this.shouldDisplayCreator(creatorPermissions)) { const creator = { login: '<creator>', @@ -187,38 +296,43 @@ export default class Template extends React.PureComponent<Props, State> { }; allUsers.unshift(creator); + usersPagingWithCreator = usersPaging + ? { ...usersPaging, total: usersPaging.total + 1 } + : undefined; } return ( <div className="page page-limited"> - <Helmet defer={false} title={this.props.template.name} /> + <Helmet defer={false} title={template.name} /> <TemplateHeader - loading={this.state.loading} + loading={loading} refresh={this.props.refresh} - template={this.props.template} - topQualifiers={this.props.topQualifiers} + template={template} + topQualifiers={topQualifiers} /> - <TemplateDetails template={this.props.template} /> + <TemplateDetails template={template} /> - <HoldersList - groups={this.state.groups} - onSelectPermission={this.handleSelectPermission} - onToggleGroup={this.handleToggleGroup} - onToggleUser={this.handleToggleUser} - permissions={permissions} - selectedPermission={this.state.selectedPermission} - showPublicProjectsWarning={true} + <AllHoldersList + filter={filter} + grantPermissionToGroup={this.grantPermissionToGroup} + grantPermissionToUser={this.grantPermissionToUser} + groups={groups} + groupsPaging={groupsPaging} + loading={loading} + onFilter={this.handleFilter} + onLoadMore={this.onLoadMore} + onQuery={this.handleSearch} + query={query} + revokePermissionFromGroup={this.revokePermissionFromGroup} + revokePermissionFromUser={this.revokePermissionFromUser} users={allUsers} - > - <SearchForm - filter={this.state.filter} - onFilter={this.handleFilter} - onSearch={this.handleSearch} - query={this.state.query} - /> - </HoldersList> + usersPaging={usersPagingWithCreator} + permissions={permissions} + selectedPermission={selectedPermission} + onSelectPermission={this.handleSelectPermission} + /> </div> ); } diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/App-test.tsx deleted file mode 100644 index 839206fa0a6..00000000000 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/App-test.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 { mockAppState, mockLocation } from '../../../../helpers/testMocks'; -import { waitAndUpdate } from '../../../../helpers/testUtils'; -import { App } from '../App'; - -jest.mock('../../../../api/permissions', () => ({ - getPermissionTemplates: jest.fn().mockResolvedValue({ - permissionTemplates: [ - { - id: '1', - name: 'Default template', - description: 'Default permission template', - createdAt: '2019-02-07T17:23:26+0100', - updatedAt: '2019-02-07T17:23:26+0100', - permissions: [ - { key: 'admin', usersCount: 0, groupsCount: 1, withProjectCreator: false }, - { key: 'codeviewer', usersCount: 0, groupsCount: 1, withProjectCreator: false }, - ], - }, - ], - defaultTemplates: [{ templateId: '1', qualifier: 'TRK' }], - permissions: [ - { key: 'admin', name: 'Administer', description: 'Admin permission' }, - { key: 'codeviewer', name: 'See Source Code', description: 'Code viewer permission' }, - ], - }), -})); - -it('should render correctly', async () => { - const wrapper = shallowRender(); - expect(wrapper).toMatchSnapshot(); - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot(); -}); - -function shallowRender(props: Partial<App['props']> = {}) { - return shallow( - <App location={mockLocation()} appState={mockAppState({ qualifiers: ['TRK'] })} {...props} /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/PermissionTemplatesApp-it.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/PermissionTemplatesApp-it.tsx new file mode 100644 index 00000000000..aef8dfe04ee --- /dev/null +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/PermissionTemplatesApp-it.tsx @@ -0,0 +1,124 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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 userEvent from '@testing-library/user-event'; +import React from 'react'; +import { byRole } from 'testing-library-selector'; +import PermissionTemplateServiceMock from '../../../../api/mocks/PermissionTemplateServiceMock'; +import { mockAppState } from '../../../../helpers/testMocks'; +import { renderApp } from '../../../../helpers/testReactTestingUtils'; +import PermissionTemplatesApp from '../PermissionTemplatesApp'; + +const serviceMock = new PermissionTemplateServiceMock(); + +beforeEach(() => { + serviceMock.reset(); +}); + +const ui = { + templateLink1: byRole('link', { name: 'Permission Template 1' }), + adminUserBrowseCheckboxChecked: byRole('checkbox', { + name: `checked permission 'projects_role.user' for user 'Admin Admin'`, + }), + adminUserBrowseCheckboxUnchecked: byRole('checkbox', { + name: `unchecked permission 'projects_role.user' for user 'Admin Admin'`, + }), + adminUserAdministerCheckboxChecked: byRole('checkbox', { + name: `checked permission 'projects_role.admin' for user 'Admin Admin'`, + }), + adminUserAdministerCheckboxUnchecked: byRole('checkbox', { + name: `unchecked permission 'projects_role.admin' for user 'Admin Admin'`, + }), + + anyoneGroupBrowseCheckboxChecked: byRole('checkbox', { + name: `checked permission 'projects_role.user' for group 'Anyone'`, + }), + anyoneGroupBrowseCheckboxUnchecked: byRole('checkbox', { + name: `unchecked permission 'projects_role.user' for group 'Anyone'`, + }), + + anyoneGroupCodeviewCheckboxChecked: byRole('checkbox', { + name: `checked permission 'projects_role.codeviewer' for group 'Anyone'`, + }), + anyoneGroupCodeviewCheckboxUnchecked: byRole('checkbox', { + name: `unchecked permission 'projects_role.codeviewer' for group 'Anyone'`, + }), + + showMoreButton: byRole('button', { name: 'show_more' }), + whiteUserBrowseCheckbox: byRole('checkbox', { + name: `unchecked permission 'projects_role.user' for user 'White'`, + }), +}; + +it('grants/revokes permission from users or groups', async () => { + const user = userEvent.setup(); + renderPermissionTemplatesApp(); + + await user.click(await ui.templateLink1.find()); + + // User + expect(ui.adminUserBrowseCheckboxUnchecked.get()).not.toBeChecked(); + await user.click(ui.adminUserBrowseCheckboxUnchecked.get()); + expect(ui.adminUserBrowseCheckboxChecked.get()).toBeChecked(); + + expect(ui.adminUserAdministerCheckboxChecked.get()).toBeChecked(); + await user.click(ui.adminUserAdministerCheckboxChecked.get()); + expect(ui.adminUserAdministerCheckboxUnchecked.get()).not.toBeChecked(); + + // Group + expect(ui.anyoneGroupBrowseCheckboxUnchecked.get()).not.toBeChecked(); + await user.click(ui.anyoneGroupBrowseCheckboxUnchecked.get()); + expect(ui.anyoneGroupBrowseCheckboxChecked.get()).toBeChecked(); + + expect(ui.anyoneGroupCodeviewCheckboxChecked.get()).toBeChecked(); + await user.click(ui.anyoneGroupCodeviewCheckboxChecked.get()); + expect(ui.anyoneGroupCodeviewCheckboxUnchecked.get()).not.toBeChecked(); + + // Handles error on permission change + serviceMock.updatePermissionChangeAllowance(false); + await user.click(ui.adminUserBrowseCheckboxChecked.get()); + expect(ui.adminUserBrowseCheckboxChecked.get()).toBeChecked(); + + await user.click(ui.anyoneGroupCodeviewCheckboxUnchecked.get()); + expect(ui.anyoneGroupCodeviewCheckboxUnchecked.get()).not.toBeChecked(); + + await user.click(ui.adminUserBrowseCheckboxChecked.get()); + expect(ui.adminUserBrowseCheckboxChecked.get()).toBeChecked(); + + await user.click(ui.adminUserAdministerCheckboxUnchecked.get()); + expect(ui.adminUserAdministerCheckboxUnchecked.get()).not.toBeChecked(); +}); + +it('loads more items on Show More', async () => { + const user = userEvent.setup(); + renderPermissionTemplatesApp(); + + await user.click(await ui.templateLink1.find()); + + expect(ui.whiteUserBrowseCheckbox.query()).not.toBeInTheDocument(); + await user.click(ui.showMoreButton.get()); + expect(ui.whiteUserBrowseCheckbox.get()).toBeInTheDocument(); +}); + +function renderPermissionTemplatesApp() { + renderApp('admin/permission_templates', <PermissionTemplatesApp />, { + appState: mockAppState({ qualifiers: ['TRK'] }), + }); +} diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/Template-test.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/Template-test.tsx deleted file mode 100644 index a06d0fb8bf3..00000000000 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/Template-test.tsx +++ /dev/null @@ -1,127 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 { - grantTemplatePermissionToGroup, - grantTemplatePermissionToUser, - revokeTemplatePermissionFromGroup, - revokeTemplatePermissionFromUser, -} from '../../../../api/permissions'; -import Template from '../Template'; - -jest.mock('../../../../api/permissions', () => ({ - revokeTemplatePermissionFromUser: jest.fn().mockResolvedValue({}), - grantTemplatePermissionToUser: jest.fn().mockResolvedValue({}), - grantTemplatePermissionToGroup: jest.fn().mockResolvedValue({}), - revokeTemplatePermissionFromGroup: jest.fn().mockResolvedValue({}), - getPermissionTemplateGroups: jest.fn().mockResolvedValue([]), - getPermissionTemplateUsers: jest.fn().mockResolvedValue([]), -})); - -it('render correctly', () => { - expect(shallowRender()).toMatchSnapshot(); -}); - -it('revoke group permission if granted', async () => { - const wrapper = shallowRender(); - const group = { - name: 'foo', - permissions: ['bar'], - }; - wrapper.setState({ - groups: [group], - }); - await wrapper.instance().handleToggleGroup(group, 'bar'); - expect(revokeTemplatePermissionFromGroup).toHaveBeenCalledWith({ - groupName: 'foo', - templateId: '1', - permission: 'bar', - }); -}); - -it('grant group permission', async () => { - const wrapper = shallowRender(); - const group = { - name: 'foo', - permissions: [], - }; - wrapper.setState({ - groups: [group], - }); - await wrapper.instance().handleToggleGroup(group, 'bar'); - expect(grantTemplatePermissionToGroup).toHaveBeenCalledWith({ - groupName: 'foo', - templateId: '1', - permission: 'bar', - }); -}); - -it('revoke user permission if granted', async () => { - const wrapper = shallowRender(); - const user = { - login: 'foo', - name: 'foo', - permissions: ['bar'], - }; - wrapper.setState({ - users: [user], - }); - await wrapper.instance().handleToggleUser(user, 'bar'); - expect(revokeTemplatePermissionFromUser).toHaveBeenCalledWith({ - templateId: '1', - login: 'foo', - permission: 'bar', - }); -}); - -it('grant user permission', async () => { - const wrapper = shallowRender(); - const user = { - login: 'foo', - name: 'foo', - permissions: [], - }; - wrapper.setState({ - users: [user], - }); - await wrapper.instance().handleToggleUser(user, 'bar'); - expect(grantTemplatePermissionToUser).toHaveBeenCalledWith({ - templateId: '1', - login: 'foo', - permission: 'bar', - }); -}); - -function shallowRender() { - return shallow<Template>( - <Template - refresh={async () => {}} - template={{ - id: '1', - createdAt: '2020-01-01', - name: 'test', - defaultFor: [], - permissions: [], - }} - topQualifiers={[]} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/App-test.tsx.snap deleted file mode 100644 index df6976323cd..00000000000 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/App-test.tsx.snap +++ /dev/null @@ -1,95 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -<div> - <Suggestions - suggestions="permission_templates" - /> - <Helmet - defer={false} - encodeSpecialCharacters={true} - prioritizeSeoTags={false} - title="permission_templates.page" - /> - <Home - permissionTemplates={[]} - permissions={[]} - ready={false} - refresh={[Function]} - topQualifiers={ - [ - "TRK", - ] - } - /> -</div> -`; - -exports[`should render correctly 2`] = ` -<div> - <Suggestions - suggestions="permission_templates" - /> - <Helmet - defer={false} - encodeSpecialCharacters={true} - prioritizeSeoTags={false} - title="permission_templates.page" - /> - <Home - permissionTemplates={ - [ - { - "createdAt": "2019-02-07T17:23:26+0100", - "defaultFor": [ - "TRK", - ], - "description": "Default permission template", - "id": "1", - "name": "Default template", - "permissions": [ - { - "description": "Code viewer permission", - "groupsCount": 1, - "key": "codeviewer", - "name": "See Source Code", - "usersCount": 0, - "withProjectCreator": false, - }, - { - "description": "Admin permission", - "groupsCount": 1, - "key": "admin", - "name": "Administer", - "usersCount": 0, - "withProjectCreator": false, - }, - ], - "updatedAt": "2019-02-07T17:23:26+0100", - }, - ] - } - permissions={ - [ - { - "description": "Code viewer permission", - "key": "codeviewer", - "name": "See Source Code", - }, - { - "description": "Admin permission", - "key": "admin", - "name": "Administer", - }, - ] - } - ready={true} - refresh={[Function]} - topQualifiers={ - [ - "TRK", - ] - } - /> -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/Template-test.tsx.snap b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/Template-test.tsx.snap deleted file mode 100644 index cc47b3c3113..00000000000 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/Template-test.tsx.snap +++ /dev/null @@ -1,96 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`render correctly 1`] = ` -<div - className="page page-limited" -> - <Helmet - defer={false} - encodeSpecialCharacters={true} - prioritizeSeoTags={false} - title="test" - /> - <TemplateHeader - loading={true} - refresh={[Function]} - template={ - { - "createdAt": "2020-01-01", - "defaultFor": [], - "id": "1", - "name": "test", - "permissions": [], - } - } - topQualifiers={[]} - /> - <TemplateDetails - template={ - { - "createdAt": "2020-01-01", - "defaultFor": [], - "id": "1", - "name": "test", - "permissions": [], - } - } - /> - <HoldersList - groups={[]} - onSelectPermission={[Function]} - onToggleGroup={[Function]} - onToggleUser={[Function]} - permissions={ - [ - { - "description": "projects_role.user.desc", - "key": "user", - "name": "projects_role.user", - }, - { - "description": "projects_role.codeviewer.desc", - "key": "codeviewer", - "name": "projects_role.codeviewer", - }, - { - "description": "projects_role.issueadmin.desc", - "key": "issueadmin", - "name": "projects_role.issueadmin", - }, - { - "description": "projects_role.securityhotspotadmin.desc", - "key": "securityhotspotadmin", - "name": "projects_role.securityhotspotadmin", - }, - { - "description": "projects_role.admin.desc", - "key": "admin", - "name": "projects_role.admin", - }, - { - "description": "projects_role.scan.desc", - "key": "scan", - "name": "projects_role.scan", - }, - ] - } - showPublicProjectsWarning={true} - users={ - [ - { - "login": "<creator>", - "name": "permission_templates.project_creators", - "permissions": [], - }, - ] - } - > - <SearchForm - filter="all" - onFilter={[Function]} - onSearch={[Function]} - query="" - /> - </HoldersList> -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/permission-templates/routes.tsx b/server/sonar-web/src/main/js/apps/permission-templates/routes.tsx index 5b57573de98..94c9098b655 100644 --- a/server/sonar-web/src/main/js/apps/permission-templates/routes.tsx +++ b/server/sonar-web/src/main/js/apps/permission-templates/routes.tsx @@ -19,8 +19,8 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import App from './components/App'; +import PermissionTemplatesApp from './components/PermissionTemplatesApp'; -const routes = () => <Route path="permission_templates" element={<App />} />; +const routes = () => <Route path="permission_templates" element={<PermissionTemplatesApp />} />; export default routes; diff --git a/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.tsx b/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.tsx deleted file mode 100644 index 03512413299..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.tsx +++ /dev/null @@ -1,123 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 * as React from 'react'; -import withAppStateContext from '../../../../app/components/app-state/withAppStateContext'; -import ListFooter from '../../../../components/controls/ListFooter'; -import { AppState } from '../../../../types/appstate'; -import { ComponentQualifier } from '../../../../types/component'; -import { Paging, PermissionGroup, PermissionUser } from '../../../../types/types'; -import HoldersList from '../../shared/components/HoldersList'; -import SearchForm from '../../shared/components/SearchForm'; -import { - convertToPermissionDefinitions, - filterPermissions, - PERMISSIONS_ORDER_GLOBAL, -} from '../../utils'; - -interface StateProps { - appState: AppState; -} - -interface OwnProps { - filter: string; - grantPermissionToGroup: (groupName: string, permission: string) => Promise<void>; - grantPermissionToUser: (login: string, permission: string) => Promise<void>; - groups: PermissionGroup[]; - groupsPaging?: Paging; - loading?: boolean; - onLoadMore: () => void; - onFilter: (filter: string) => void; - onSearch: (query: string) => void; - query: string; - revokePermissionFromGroup: (groupName: string, permission: string) => Promise<void>; - revokePermissionFromUser: (login: string, permission: string) => Promise<void>; - users: PermissionUser[]; - usersPaging?: Paging; -} - -type Props = StateProps & OwnProps; - -export class AllHoldersList extends React.PureComponent<Props> { - handleToggleUser = (user: PermissionUser, permission: string) => { - const hasPermission = user.permissions.includes(permission); - if (hasPermission) { - return this.props.revokePermissionFromUser(user.login, permission); - } - return this.props.grantPermissionToUser(user.login, permission); - }; - - handleToggleGroup = (group: PermissionGroup, permission: string) => { - const hasPermission = group.permissions.includes(permission); - - if (hasPermission) { - return this.props.revokePermissionFromGroup(group.name, permission); - } - return this.props.grantPermissionToGroup(group.name, permission); - }; - - render() { - const { appState, filter, groups, groupsPaging, users, usersPaging, loading, query } = - this.props; - const l10nPrefix = 'global_permissions'; - - const hasPortfoliosEnabled = appState.qualifiers.includes(ComponentQualifier.Portfolio); - const hasApplicationsEnabled = appState.qualifiers.includes(ComponentQualifier.Application); - const permissions = convertToPermissionDefinitions( - filterPermissions(PERMISSIONS_ORDER_GLOBAL, hasApplicationsEnabled, hasPortfoliosEnabled), - l10nPrefix - ); - - let count = 0; - let total = 0; - if (filter !== 'users') { - count += groups.length; - total += groupsPaging ? groupsPaging.total : groups.length; - } - if (filter !== 'groups') { - count += users.length; - total += usersPaging ? usersPaging.total : users.length; - } - - return ( - <> - <HoldersList - filter={filter} - groups={groups} - loading={loading} - onToggleGroup={this.handleToggleGroup} - onToggleUser={this.handleToggleUser} - permissions={permissions} - query={query} - users={users} - > - <SearchForm - filter={filter} - onFilter={this.props.onFilter} - onSearch={this.props.onSearch} - query={query} - /> - </HoldersList> - <ListFooter count={count} loadMore={this.props.onLoadMore} total={total} /> - </> - ); - } -} - -export default withAppStateContext(AllHoldersList); diff --git a/server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx b/server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx index a412b8f2db8..715cab70c88 100644 --- a/server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx @@ -21,15 +21,27 @@ import { without } from 'lodash'; import * as React from 'react'; import { Helmet } from 'react-helmet-async'; import * as api from '../../../../api/permissions'; +import withAppStateContext from '../../../../app/components/app-state/withAppStateContext'; import Suggestions from '../../../../components/embed-docs-modal/Suggestions'; import { translate } from '../../../../helpers/l10n'; +import { AppState } from '../../../../types/appstate'; +import { ComponentQualifier } from '../../../../types/component'; import { Paging, PermissionGroup, PermissionUser } from '../../../../types/types'; +import AllHoldersList from '../../shared/components/AllHoldersList'; +import { FilterOption } from '../../shared/components/SearchForm'; import '../../styles.css'; -import AllHoldersList from './AllHoldersList'; +import { + convertToPermissionDefinitions, + filterPermissions, + PERMISSIONS_ORDER_GLOBAL, +} from '../../utils'; import PageHeader from './PageHeader'; +interface Props { + appState: AppState; +} interface State { - filter: 'all' | 'groups' | 'users'; + filter: FilterOption; groups: PermissionGroup[]; groupsPaging?: Paging; loading: boolean; @@ -37,11 +49,10 @@ interface State { users: PermissionUser[]; usersPaging?: Paging; } - -export default class App extends React.PureComponent<{}, State> { +export class App extends React.PureComponent<Props, State> { mounted = false; - constructor(props: {}) { + constructor(props: Props) { super(props); this.state = { filter: 'all', @@ -117,7 +128,7 @@ export default class App extends React.PureComponent<{}, State> { }, this.stopLoading); }; - onFilter = (filter: 'all' | 'groups' | 'users') => { + onFilter = (filter: FilterOption) => { this.setState({ filter }, this.loadHolders); }; @@ -260,28 +271,40 @@ export default class App extends React.PureComponent<{}, State> { }; render() { + const { appState } = this.props; + const { filter, groups, groupsPaging, users, usersPaging, loading, query } = this.state; + + const hasPortfoliosEnabled = appState.qualifiers.includes(ComponentQualifier.Portfolio); + const hasApplicationsEnabled = appState.qualifiers.includes(ComponentQualifier.Application); + const permissions = convertToPermissionDefinitions( + filterPermissions(PERMISSIONS_ORDER_GLOBAL, hasApplicationsEnabled, hasPortfoliosEnabled), + 'global_permissions' + ); return ( <div className="page page-limited"> <Suggestions suggestions="global_permissions" /> <Helmet defer={false} title={translate('global_permissions.permission')} /> - <PageHeader loading={this.state.loading} /> + <PageHeader loading={loading} /> <AllHoldersList - filter={this.state.filter} + permissions={permissions} + filter={filter} grantPermissionToGroup={this.grantPermissionToGroup} grantPermissionToUser={this.grantPermissionToUser} - groups={this.state.groups} - groupsPaging={this.state.groupsPaging} - loading={this.state.loading} + groups={groups} + groupsPaging={groupsPaging} + loading={loading} onFilter={this.onFilter} onLoadMore={this.onLoadMore} - onSearch={this.onSearch} - query={this.state.query} + onQuery={this.onSearch} + query={query} revokePermissionFromGroup={this.revokePermissionFromGroup} revokePermissionFromUser={this.revokePermissionFromUser} - users={this.state.users} - usersPaging={this.state.usersPaging} + users={users} + usersPaging={usersPaging} /> </div> ); } } + +export default withAppStateContext(App); diff --git a/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/AllHoldersList-test.tsx b/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/AllHoldersList-test.tsx deleted file mode 100644 index 5a2f9686d8c..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/AllHoldersList-test.tsx +++ /dev/null @@ -1,97 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 { mockPermissionGroup, mockPermissionUser } from '../../../../../helpers/mocks/permissions'; -import { mockAppState } from '../../../../../helpers/testMocks'; -import { ComponentQualifier } from '../../../../../types/component'; -import { AllHoldersList } from '../AllHoldersList'; - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot('default'); - expect(shallowRender({ filter: 'users' })).toMatchSnapshot('filter users'); - expect(shallowRender({ filter: 'groups' })).toMatchSnapshot('filter groups'); - expect( - shallowRender({ - appState: mockAppState({ - qualifiers: [ComponentQualifier.Project, ComponentQualifier.Application], - }), - }) - ).toMatchSnapshot('applications available'); - expect( - shallowRender({ - appState: mockAppState({ - qualifiers: [ComponentQualifier.Project, ComponentQualifier.Portfolio], - }), - }) - ).toMatchSnapshot('portfolios available'); -}); - -it('should correctly toggle user permissions', () => { - const grantPermissionToUser = jest.fn(); - const revokePermissionFromUser = jest.fn(); - const grantPermission = 'applicationcreator'; - const revokePermission = 'provisioning'; - const user = mockPermissionUser(); - const wrapper = shallowRender({ grantPermissionToUser, revokePermissionFromUser }); - const instance = wrapper.instance(); - - instance.handleToggleUser(user, grantPermission); - expect(grantPermissionToUser).toHaveBeenCalledWith(user.login, grantPermission); - - instance.handleToggleUser(user, revokePermission); - expect(revokePermissionFromUser).toHaveBeenCalledWith(user.login, revokePermission); -}); - -it('should correctly toggle group permissions', () => { - const grantPermissionToGroup = jest.fn(); - const revokePermissionFromGroup = jest.fn(); - const grantPermission = 'applicationcreator'; - const revokePermission = 'provisioning'; - const group = mockPermissionGroup(); - const wrapper = shallowRender({ grantPermissionToGroup, revokePermissionFromGroup }); - const instance = wrapper.instance(); - - instance.handleToggleGroup(group, grantPermission); - expect(grantPermissionToGroup).toHaveBeenCalledWith(group.name, grantPermission); - - instance.handleToggleGroup(group, revokePermission); - expect(revokePermissionFromGroup).toHaveBeenCalledWith(group.name, revokePermission); -}); - -function shallowRender(props: Partial<AllHoldersList['props']> = {}) { - return shallow<AllHoldersList>( - <AllHoldersList - appState={mockAppState({ qualifiers: [ComponentQualifier.Project] })} - filter="" - grantPermissionToGroup={jest.fn()} - grantPermissionToUser={jest.fn()} - groups={[mockPermissionGroup()]} - onLoadMore={jest.fn()} - onFilter={jest.fn()} - onSearch={jest.fn()} - query="" - revokePermissionFromGroup={jest.fn()} - revokePermissionFromUser={jest.fn()} - users={[mockPermissionUser()]} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/App-test.tsx index fbece66cf41..11dbd9998e5 100644 --- a/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/App-test.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/App-test.tsx @@ -25,9 +25,10 @@ import { revokePermissionFromGroup, revokePermissionFromUser, } from '../../../../../api/permissions'; +import { mockAppState } from '../../../../../helpers/testMocks'; import { waitAndUpdate } from '../../../../../helpers/testUtils'; import { ANYONE } from '../../../shared/components/GroupHolder'; -import App from '../App'; +import { App } from '../App'; jest.mock('../../../../../api/permissions', () => ({ getGlobalPermissionsGroups: jest.fn().mockResolvedValue({ @@ -130,5 +131,5 @@ describe('should manage state correctly', () => { }); function shallowRender(props: Partial<App['props']> = {}) { - return shallow<App>(<App {...props} />); + return shallow<App>(<App appState={mockAppState()} {...props} />); } diff --git a/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/__snapshots__/AllHoldersList-test.tsx.snap b/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/__snapshots__/AllHoldersList-test.tsx.snap deleted file mode 100644 index 829c3a7c22d..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/__snapshots__/AllHoldersList-test.tsx.snap +++ /dev/null @@ -1,436 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly: applications available 1`] = ` -<Fragment> - <HoldersList - filter="" - groups={ - [ - { - "name": "sonar-admins", - "permissions": [ - "provisioning", - ], - }, - ] - } - onToggleGroup={[Function]} - onToggleUser={[Function]} - permissions={ - [ - { - "description": "global_permissions.admin.desc", - "key": "admin", - "name": "global_permissions.admin", - }, - { - "category": "administer", - "permissions": [ - { - "description": "global_permissions.gateadmin.desc", - "key": "gateadmin", - "name": "global_permissions.gateadmin", - }, - { - "description": "global_permissions.profileadmin.desc", - "key": "profileadmin", - "name": "global_permissions.profileadmin", - }, - ], - }, - { - "description": "global_permissions.scan.desc", - "key": "scan", - "name": "global_permissions.scan", - }, - { - "category": "creator", - "permissions": [ - { - "description": "global_permissions.provisioning.desc", - "key": "provisioning", - "name": "global_permissions.provisioning", - }, - { - "description": "global_permissions.applicationcreator.desc", - "key": "applicationcreator", - "name": "global_permissions.applicationcreator", - }, - ], - }, - ] - } - query="" - users={ - [ - { - "active": true, - "local": true, - "login": "john.doe", - "name": "johndoe", - "permissions": [ - "provisioning", - ], - }, - ] - } - > - <SearchForm - filter="" - onFilter={[MockFunction]} - onSearch={[MockFunction]} - query="" - /> - </HoldersList> - <ListFooter - count={2} - loadMore={[MockFunction]} - total={2} - /> -</Fragment> -`; - -exports[`should render correctly: default 1`] = ` -<Fragment> - <HoldersList - filter="" - groups={ - [ - { - "name": "sonar-admins", - "permissions": [ - "provisioning", - ], - }, - ] - } - onToggleGroup={[Function]} - onToggleUser={[Function]} - permissions={ - [ - { - "description": "global_permissions.admin.desc", - "key": "admin", - "name": "global_permissions.admin", - }, - { - "category": "administer", - "permissions": [ - { - "description": "global_permissions.gateadmin.desc", - "key": "gateadmin", - "name": "global_permissions.gateadmin", - }, - { - "description": "global_permissions.profileadmin.desc", - "key": "profileadmin", - "name": "global_permissions.profileadmin", - }, - ], - }, - { - "description": "global_permissions.scan.desc", - "key": "scan", - "name": "global_permissions.scan", - }, - { - "category": "creator", - "permissions": [ - { - "description": "global_permissions.provisioning.desc", - "key": "provisioning", - "name": "global_permissions.provisioning", - }, - ], - }, - ] - } - query="" - users={ - [ - { - "active": true, - "local": true, - "login": "john.doe", - "name": "johndoe", - "permissions": [ - "provisioning", - ], - }, - ] - } - > - <SearchForm - filter="" - onFilter={[MockFunction]} - onSearch={[MockFunction]} - query="" - /> - </HoldersList> - <ListFooter - count={2} - loadMore={[MockFunction]} - total={2} - /> -</Fragment> -`; - -exports[`should render correctly: filter groups 1`] = ` -<Fragment> - <HoldersList - filter="groups" - groups={ - [ - { - "name": "sonar-admins", - "permissions": [ - "provisioning", - ], - }, - ] - } - onToggleGroup={[Function]} - onToggleUser={[Function]} - permissions={ - [ - { - "description": "global_permissions.admin.desc", - "key": "admin", - "name": "global_permissions.admin", - }, - { - "category": "administer", - "permissions": [ - { - "description": "global_permissions.gateadmin.desc", - "key": "gateadmin", - "name": "global_permissions.gateadmin", - }, - { - "description": "global_permissions.profileadmin.desc", - "key": "profileadmin", - "name": "global_permissions.profileadmin", - }, - ], - }, - { - "description": "global_permissions.scan.desc", - "key": "scan", - "name": "global_permissions.scan", - }, - { - "category": "creator", - "permissions": [ - { - "description": "global_permissions.provisioning.desc", - "key": "provisioning", - "name": "global_permissions.provisioning", - }, - ], - }, - ] - } - query="" - users={ - [ - { - "active": true, - "local": true, - "login": "john.doe", - "name": "johndoe", - "permissions": [ - "provisioning", - ], - }, - ] - } - > - <SearchForm - filter="groups" - onFilter={[MockFunction]} - onSearch={[MockFunction]} - query="" - /> - </HoldersList> - <ListFooter - count={1} - loadMore={[MockFunction]} - total={1} - /> -</Fragment> -`; - -exports[`should render correctly: filter users 1`] = ` -<Fragment> - <HoldersList - filter="users" - groups={ - [ - { - "name": "sonar-admins", - "permissions": [ - "provisioning", - ], - }, - ] - } - onToggleGroup={[Function]} - onToggleUser={[Function]} - permissions={ - [ - { - "description": "global_permissions.admin.desc", - "key": "admin", - "name": "global_permissions.admin", - }, - { - "category": "administer", - "permissions": [ - { - "description": "global_permissions.gateadmin.desc", - "key": "gateadmin", - "name": "global_permissions.gateadmin", - }, - { - "description": "global_permissions.profileadmin.desc", - "key": "profileadmin", - "name": "global_permissions.profileadmin", - }, - ], - }, - { - "description": "global_permissions.scan.desc", - "key": "scan", - "name": "global_permissions.scan", - }, - { - "category": "creator", - "permissions": [ - { - "description": "global_permissions.provisioning.desc", - "key": "provisioning", - "name": "global_permissions.provisioning", - }, - ], - }, - ] - } - query="" - users={ - [ - { - "active": true, - "local": true, - "login": "john.doe", - "name": "johndoe", - "permissions": [ - "provisioning", - ], - }, - ] - } - > - <SearchForm - filter="users" - onFilter={[MockFunction]} - onSearch={[MockFunction]} - query="" - /> - </HoldersList> - <ListFooter - count={1} - loadMore={[MockFunction]} - total={1} - /> -</Fragment> -`; - -exports[`should render correctly: portfolios available 1`] = ` -<Fragment> - <HoldersList - filter="" - groups={ - [ - { - "name": "sonar-admins", - "permissions": [ - "provisioning", - ], - }, - ] - } - onToggleGroup={[Function]} - onToggleUser={[Function]} - permissions={ - [ - { - "description": "global_permissions.admin.desc", - "key": "admin", - "name": "global_permissions.admin", - }, - { - "category": "administer", - "permissions": [ - { - "description": "global_permissions.gateadmin.desc", - "key": "gateadmin", - "name": "global_permissions.gateadmin", - }, - { - "description": "global_permissions.profileadmin.desc", - "key": "profileadmin", - "name": "global_permissions.profileadmin", - }, - ], - }, - { - "description": "global_permissions.scan.desc", - "key": "scan", - "name": "global_permissions.scan", - }, - { - "category": "creator", - "permissions": [ - { - "description": "global_permissions.provisioning.desc", - "key": "provisioning", - "name": "global_permissions.provisioning", - }, - { - "description": "global_permissions.portfoliocreator.desc", - "key": "portfoliocreator", - "name": "global_permissions.portfoliocreator", - }, - ], - }, - ] - } - query="" - users={ - [ - { - "active": true, - "local": true, - "login": "john.doe", - "name": "johndoe", - "permissions": [ - "provisioning", - ], - }, - ] - } - > - <SearchForm - filter="" - onFilter={[MockFunction]} - onSearch={[MockFunction]} - query="" - /> - </HoldersList> - <ListFooter - count={2} - loadMore={[MockFunction]} - total={2} - /> -</Fragment> -`; diff --git a/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/__snapshots__/App-test.tsx.snap index d53531cfe3d..5017c675591 100644 --- a/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/__snapshots__/App-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/__snapshots__/App-test.tsx.snap @@ -16,7 +16,7 @@ exports[`should render correctly 1`] = ` <PageHeader loading={true} /> - <withAppStateContext(AllHoldersList) + <AllHoldersList filter="all" grantPermissionToGroup={[Function]} grantPermissionToUser={[Function]} @@ -24,7 +24,46 @@ exports[`should render correctly 1`] = ` loading={true} onFilter={[Function]} onLoadMore={[Function]} - onSearch={[Function]} + onQuery={[Function]} + permissions={ + [ + { + "description": "global_permissions.admin.desc", + "key": "admin", + "name": "global_permissions.admin", + }, + { + "category": "administer", + "permissions": [ + { + "description": "global_permissions.gateadmin.desc", + "key": "gateadmin", + "name": "global_permissions.gateadmin", + }, + { + "description": "global_permissions.profileadmin.desc", + "key": "profileadmin", + "name": "global_permissions.profileadmin", + }, + ], + }, + { + "description": "global_permissions.scan.desc", + "key": "scan", + "name": "global_permissions.scan", + }, + { + "category": "creator", + "permissions": [ + { + "description": "global_permissions.provisioning.desc", + "key": "provisioning", + "name": "global_permissions.provisioning", + }, + ], + }, + ] + } query="" revokePermissionFromGroup={[Function]} revokePermissionFromUser={[Function]} @@ -49,7 +88,7 @@ exports[`should render correctly 2`] = ` <PageHeader loading={false} /> - <withAppStateContext(AllHoldersList) + <AllHoldersList filter="all" grantPermissionToGroup={[Function]} grantPermissionToUser={[Function]} @@ -81,7 +120,46 @@ exports[`should render correctly 2`] = ` loading={false} onFilter={[Function]} onLoadMore={[Function]} - onSearch={[Function]} + onQuery={[Function]} + permissions={ + [ + { + "description": "global_permissions.admin.desc", + "key": "admin", + "name": "global_permissions.admin", + }, + { + "category": "administer", + "permissions": [ + { + "description": "global_permissions.gateadmin.desc", + "key": "gateadmin", + "name": "global_permissions.gateadmin", + }, + { + "description": "global_permissions.profileadmin.desc", + "key": "profileadmin", + "name": "global_permissions.profileadmin", + }, + ], + }, + { + "description": "global_permissions.scan.desc", + "key": "scan", + "name": "global_permissions.scan", + }, + { + "category": "creator", + "permissions": [ + { + "description": "global_permissions.provisioning.desc", + "key": "provisioning", + "name": "global_permissions.provisioning", + }, + ], + }, + ] + } query="" revokePermissionFromGroup={[Function]} revokePermissionFromUser={[Function]} diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx b/server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx index 692896eb771..5de217f0141 100644 --- a/server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx @@ -25,8 +25,10 @@ import withComponentContext from '../../../../app/components/componentContext/wi import VisibilitySelector from '../../../../components/common/VisibilitySelector'; import { translate } from '../../../../helpers/l10n'; import { Component, Paging, PermissionGroup, PermissionUser } from '../../../../types/types'; +import AllHoldersList from '../../shared/components/AllHoldersList'; +import { FilterOption } from '../../shared/components/SearchForm'; import '../../styles.css'; -import AllHoldersList from './AllHoldersList'; +import { convertToPermissionDefinitions, PERMISSIONS_ORDER_BY_QUALIFIER } from '../../utils'; import PageHeader from './PageHeader'; import PublicProjectDisclaimer from './PublicProjectDisclaimer'; @@ -37,7 +39,7 @@ interface Props { interface State { disclaimer: boolean; - filter: string; + filter: FilterOption; groups: PermissionGroup[]; groupsPaging?: Paging; loading: boolean; @@ -138,7 +140,7 @@ export class App extends React.PureComponent<Props, State> { }, this.stopLoading); }; - handleFilterChange = (filter: string) => { + handleFilterChange = (filter: FilterOption) => { if (this.mounted) { this.setState({ filter }, this.loadHolders); } @@ -340,18 +342,31 @@ export class App extends React.PureComponent<Props, State> { render() { const { component } = this.props; + const { + filter, + groups, + disclaimer, + loading, + selectedPermission, + query, + users, + usersPaging, + groupsPaging, + } = this.state; const canTurnToPrivate = component.configuration && component.configuration.canUpdateProjectVisibilityToPrivate; + let order = PERMISSIONS_ORDER_BY_QUALIFIER[component.qualifier]; + if (component.visibility === 'public') { + order = without(order, 'user', 'codeviewer'); + } + const permissions = convertToPermissionDefinitions(order, 'projects_role'); + return ( <div className="page page-limited" id="project-permissions-page"> <Helmet defer={false} title={translate('permissions.page')} /> - <PageHeader - component={component} - loadHolders={this.loadHolders} - loading={this.state.loading} - /> + <PageHeader component={component} loadHolders={this.loadHolders} loading={loading} /> <div> <VisibilitySelector canTurnToPrivate={canTurnToPrivate} @@ -359,7 +374,7 @@ export class App extends React.PureComponent<Props, State> { onChange={this.handleVisibilityChange} visibility={component.visibility} /> - {this.state.disclaimer && ( + {disclaimer && ( <PublicProjectDisclaimer component={component} onClose={this.closeDisclaimer} @@ -368,22 +383,22 @@ export class App extends React.PureComponent<Props, State> { )} </div> <AllHoldersList - component={component} - filter={this.state.filter} + filter={filter} grantPermissionToGroup={this.grantPermissionToGroup} grantPermissionToUser={this.grantPermissionToUser} - groups={this.state.groups} - groupsPaging={this.state.groupsPaging} - onFilterChange={this.handleFilterChange} + groups={groups} + groupsPaging={groupsPaging} + onFilter={this.handleFilterChange} onLoadMore={this.onLoadMore} - onPermissionSelect={this.handlePermissionSelect} - onQueryChange={this.handleQueryChange} - query={this.state.query} + onSelectPermission={this.handlePermissionSelect} + onQuery={this.handleQueryChange} + query={query} revokePermissionFromGroup={this.revokePermissionFromGroup} revokePermissionFromUser={this.revokePermissionFromUser} - selectedPermission={this.state.selectedPermission} - users={this.state.users} - usersPaging={this.state.usersPaging} + selectedPermission={selectedPermission} + users={users} + usersPaging={usersPaging} + permissions={permissions} /> </div> ); diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/__snapshots__/App-test.tsx.snap index 336c8df9469..de21fda0f30 100644 --- a/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/__snapshots__/App-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/__snapshots__/App-test.tsx.snap @@ -44,36 +44,48 @@ exports[`should render correctly 1`] = ` /> </div> <AllHoldersList - component={ - { - "breadcrumbs": [], - "key": "my-project", - "name": "MyProject", - "qualifier": "TRK", - "qualityGate": { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": [ - { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": [], - } - } filter="all" grantPermissionToGroup={[Function]} grantPermissionToUser={[Function]} groups={[]} - onFilterChange={[Function]} + onFilter={[Function]} onLoadMore={[Function]} - onPermissionSelect={[Function]} - onQueryChange={[Function]} + onQuery={[Function]} + onSelectPermission={[Function]} + permissions={ + [ + { + "description": "projects_role.user.desc", + "key": "user", + "name": "projects_role.user", + }, + { + "description": "projects_role.codeviewer.desc", + "key": "codeviewer", + "name": "projects_role.codeviewer", + }, + { + "description": "projects_role.issueadmin.desc", + "key": "issueadmin", + "name": "projects_role.issueadmin", + }, + { + "description": "projects_role.securityhotspotadmin.desc", + "key": "securityhotspotadmin", + "name": "projects_role.securityhotspotadmin", + }, + { + "description": "projects_role.admin.desc", + "key": "admin", + "name": "projects_role.admin", + }, + { + "description": "projects_role.scan.desc", + "key": "scan", + "name": "projects_role.scan", + }, + ] + } query="" revokePermissionFromGroup={[Function]} revokePermissionFromUser={[Function]} @@ -126,28 +138,6 @@ exports[`should render correctly 2`] = ` /> </div> <AllHoldersList - component={ - { - "breadcrumbs": [], - "key": "my-project", - "name": "MyProject", - "qualifier": "TRK", - "qualityGate": { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": [ - { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": [], - } - } filter="all" grantPermissionToGroup={[Function]} grantPermissionToUser={[Function]} @@ -176,10 +166,44 @@ exports[`should render correctly 2`] = ` "total": 2, } } - onFilterChange={[Function]} + onFilter={[Function]} onLoadMore={[Function]} - onPermissionSelect={[Function]} - onQueryChange={[Function]} + onQuery={[Function]} + onSelectPermission={[Function]} + permissions={ + [ + { + "description": "projects_role.user.desc", + "key": "user", + "name": "projects_role.user", + }, + { + "description": "projects_role.codeviewer.desc", + "key": "codeviewer", + "name": "projects_role.codeviewer", + }, + { + "description": "projects_role.issueadmin.desc", + "key": "issueadmin", + "name": "projects_role.issueadmin", + }, + { + "description": "projects_role.securityhotspotadmin.desc", + "key": "securityhotspotadmin", + "name": "projects_role.securityhotspotadmin", + }, + { + "description": "projects_role.admin.desc", + "key": "admin", + "name": "projects_role.admin", + }, + { + "description": "projects_role.scan.desc", + "key": "scan", + "name": "projects_role.scan", + }, + ] + } query="" revokePermissionFromGroup={[Function]} revokePermissionFromUser={[Function]} diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/AllHoldersList.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/AllHoldersList.tsx index 3da32067a8b..ba781321cc8 100644 --- a/server/sonar-web/src/main/js/apps/permissions/project/components/AllHoldersList.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/AllHoldersList.tsx @@ -17,31 +17,36 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { without } from 'lodash'; import * as React from 'react'; import ListFooter from '../../../../components/controls/ListFooter'; -import { Component, Paging, PermissionGroup, PermissionUser } from '../../../../types/types'; +import { + Paging, + PermissionDefinition, + PermissionDefinitionGroup, + PermissionGroup, + PermissionUser, +} from '../../../../types/types'; import HoldersList from '../../shared/components/HoldersList'; -import SearchForm from '../../shared/components/SearchForm'; -import { convertToPermissionDefinitions, PERMISSIONS_ORDER_BY_QUALIFIER } from '../../utils'; +import SearchForm, { FilterOption } from '../../shared/components/SearchForm'; interface Props { - component: Component; - filter: string; - grantPermissionToGroup: (group: string, permission: string) => Promise<void>; - grantPermissionToUser: (user: string, permission: string) => Promise<void>; + filter: FilterOption; + query: string; + onFilter: (filter: string) => void; + onQuery: (query: string) => void; groups: PermissionGroup[]; groupsPaging?: Paging; - onLoadMore: () => void; - onFilterChange: (filter: string) => void; - onPermissionSelect: (permissions?: string) => void; - onQueryChange: (query: string) => void; - query: string; revokePermissionFromGroup: (group: string, permission: string) => Promise<void>; - revokePermissionFromUser: (user: string, permission: string) => Promise<void>; - selectedPermission?: string; + grantPermissionToGroup: (group: string, permission: string) => Promise<void>; users: PermissionUser[]; usersPaging?: Paging; + revokePermissionFromUser: (user: string, permission: string) => Promise<void>; + grantPermissionToUser: (user: string, permission: string) => Promise<void>; + permissions: Array<PermissionDefinition | PermissionDefinitionGroup>; + onLoadMore: () => void; + selectedPermission?: string; + onSelectPermission?: (permissions?: string) => void; + loading?: boolean; } export default class AllHoldersList extends React.PureComponent<Props> { @@ -50,9 +55,8 @@ export default class AllHoldersList extends React.PureComponent<Props> { if (hasPermission) { return this.props.revokePermissionFromUser(user.login, permission); - } else { - return this.props.grantPermissionToUser(user.login, permission); } + return this.props.grantPermissionToUser(user.login, permission); }; handleToggleGroup = (group: PermissionGroup, permission: string) => { @@ -60,29 +64,13 @@ export default class AllHoldersList extends React.PureComponent<Props> { if (hasPermission) { return this.props.revokePermissionFromGroup(group.name, permission); - } else { - return this.props.grantPermissionToGroup(group.name, permission); } - }; - handleSelectPermission = (permission?: string) => { - this.props.onPermissionSelect(permission); + return this.props.grantPermissionToGroup(group.name, permission); }; - render() { - const { - component: { qualifier, visibility }, - filter, - groups, - groupsPaging, - users, - usersPaging, - } = this.props; - let order = PERMISSIONS_ORDER_BY_QUALIFIER[qualifier]; - if (visibility === 'public') { - order = without(order, 'user', 'codeviewer'); - } - const permissions = convertToPermissionDefinitions(order, 'projects_role'); + getPaging = () => { + const { filter, groups, groupsPaging, users, usersPaging } = this.props; let count = 0; let total = 0; @@ -95,25 +83,32 @@ export default class AllHoldersList extends React.PureComponent<Props> { total += usersPaging ? usersPaging.total : users.length; } + return { count, total }; + }; + + render() { + const { filter, query, groups, users, permissions, selectedPermission, loading } = this.props; + const { count, total } = this.getPaging(); + return ( <> <HoldersList - filter={this.props.filter} - groups={this.props.groups} - isComponentPrivate={visibility !== 'public'} - onSelectPermission={this.handleSelectPermission} + loading={loading} + filter={filter} + groups={groups} + onSelectPermission={this.props.onSelectPermission} onToggleGroup={this.handleToggleGroup} onToggleUser={this.handleToggleUser} permissions={permissions} - query={this.props.query} - selectedPermission={this.props.selectedPermission} - users={this.props.users} + query={query} + selectedPermission={selectedPermission} + users={users} > <SearchForm - filter={this.props.filter} - onFilter={this.props.onFilterChange} - onSearch={this.props.onQueryChange} - query={this.props.query} + filter={filter} + onFilter={this.props.onFilter} + onSearch={this.props.onQuery} + query={query} /> </HoldersList> <ListFooter count={count} loadMore={this.props.onLoadMore} total={total} /> diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.tsx index 77336553bf5..3699486e725 100644 --- a/server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.tsx @@ -131,6 +131,7 @@ export default class HoldersList extends React.PureComponent<Props, State> { } return item.name; }); + const [itemWithPermissions, itemWithoutPermissions] = partition(items, (item) => this.getItemInitialPermissionsCount(item) ); diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/SearchForm.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/SearchForm.tsx index e46ed4a4cb6..b4339b45643 100644 --- a/server/sonar-web/src/main/js/apps/permissions/shared/components/SearchForm.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/SearchForm.tsx @@ -22,9 +22,10 @@ import ButtonToggle from '../../../../components/controls/ButtonToggle'; import SearchBox from '../../../../components/controls/SearchBox'; import { translate } from '../../../../helpers/l10n'; +export type FilterOption = 'all' | 'users' | 'groups'; interface Props { - filter: string; - onFilter: (value: string) => void; + filter: FilterOption; + onFilter: (value: FilterOption) => void; onSearch: (value: string) => void; query: string; } diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/GroupHolder-test.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/GroupHolder-test.tsx deleted file mode 100644 index 3f4a2f82ff2..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/GroupHolder-test.tsx +++ /dev/null @@ -1,93 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 { render, screen, waitFor } from '@testing-library/react'; -import * as React from 'react'; -import { mockPermissionGroup } from '../../../../../helpers/mocks/permissions'; -import { Permissions } from '../../../../../types/permissions'; -import GroupHolder, { ANYONE } from '../GroupHolder'; - -it('should disable PermissionCell checkboxes when waiting for the promise to return', async () => { - renderComponent(); - - const checkbox = screen.getAllByRole('checkbox')[0]; - expect(checkbox).not.toHaveClass('disabled'); - checkbox.click(); - - await waitFor(() => { - expect(checkbox).toHaveClass('disabled'); - }); - - await waitFor(() => { - expect(checkbox).not.toHaveClass('disabled'); - }); -}); - -it('should disable all PermissionCell checkboxes for group "Anyone" for a private project', () => { - renderComponent({ isComponentPrivate: true }); - - const checkboxes = screen.getAllByRole('checkbox'); - - ['Foo', 'Bar', 'Admin'].forEach((permission, idx) => { - expect(checkboxes[idx]).toHaveAttribute( - 'aria-label', - `disabled permission '${permission}' for group 'Anyone'` - ); - }); -}); - -it('should disable the "admin" PermissionCell checkbox for group "Anyone" for a public project', () => { - renderComponent(); - - expect( - screen.getByLabelText("unchecked permission 'Foo' for group 'Anyone'") - ).toBeInTheDocument(); - - expect( - screen.getByLabelText("unchecked permission 'Bar' for group 'Anyone'") - ).toBeInTheDocument(); - - expect( - screen.getByLabelText("disabled permission 'Admin' for group 'Anyone'") - ).toBeInTheDocument(); -}); - -const renderComponent = (props: Partial<GroupHolder['props']> = {}) => - render( - <table> - <tbody> - <GroupHolder - group={mockPermissionGroup({ id: 'foobar', name: ANYONE })} - onToggle={jest.fn().mockResolvedValue(null)} - permissions={[ - { - category: 'baz', - permissions: [ - { key: 'foo', name: 'Foo', description: '' }, - { key: 'bar', name: 'Bar', description: '' }, - ], - }, - { key: Permissions.Admin, name: 'Admin', description: '' }, - ]} - selectedPermission="bar" - {...props} - /> - </tbody> - </table> - ); diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/HoldersList-test.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/HoldersList-test.tsx deleted file mode 100644 index 4a575c6ffe3..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/HoldersList-test.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 { ANYONE } from '../GroupHolder'; -import HoldersList from '../HoldersList'; - -const permissions = [ - { key: 'foo', name: 'Foo', description: '' }, - { - category: 'admin', - permissions: [ - { key: 'bar', name: 'Bar', description: '' }, - { key: 'baz', name: 'Baz', description: '' }, - ], - }, -]; - -const groups = [ - { id: 'foobar', name: 'Foobar', permissions: ['bar'] }, - { id: 'barbaz', name: 'Barbaz', permissions: ['bar'] }, - { id: 'anyone', name: ANYONE, permissions: ['bar'] }, - { id: 'abc', name: 'abc', permissions: [] }, -]; - -const users = [ - { login: 'foobar', name: 'Foobar', permissions: ['bar'] }, - { login: 'barbaz', name: 'Barbaz', permissions: ['bar'] }, - { login: 'bcd', name: 'bcd', permissions: [] }, -]; - -const elementsContainer = ( - <HoldersList - groups={groups} - isComponentPrivate={true} - onSelectPermission={jest.fn(() => Promise.resolve())} - onToggleGroup={jest.fn(() => Promise.resolve())} - onToggleUser={jest.fn(() => Promise.resolve())} - permissions={permissions} - selectedPermission="bar" - users={users} - /> -); - -it('should display users and groups', () => { - expect(shallow(elementsContainer)).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/PermissionCell-test.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/PermissionCell-test.tsx deleted file mode 100644 index c3d96bc4414..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/PermissionCell-test.tsx +++ /dev/null @@ -1,89 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 PermissionCell from '../PermissionCell'; - -const permissionItem = { - id: 'baz', - name: 'Baz', - permissions: ['baz'], -}; - -const permission = { key: 'baz', name: 'Baz', description: '' }; -const permissionGroup = { - category: 'admin', - permissions: [ - { key: 'foo', name: 'Foo', description: '' }, - { key: 'baz', name: 'Baz', description: '' }, - ], -}; -it('should display an unchecked checkbox', () => { - expect( - shallow( - <PermissionCell - loading={[]} - onCheck={jest.fn()} - permission={{ ...permission, key: 'bar' }} - permissionItem={permissionItem} - /> - ) - ).toMatchSnapshot(); -}); - -it('should display multiple checkboxes with one checked', () => { - expect( - shallow( - <PermissionCell - loading={[]} - onCheck={jest.fn()} - permission={permissionGroup} - permissionItem={permissionItem} - /> - ) - ).toMatchSnapshot(); -}); - -it('should display a disabled checkbox', () => { - expect( - shallow( - <PermissionCell - loading={['baz']} - onCheck={jest.fn()} - permission={permission} - permissionItem={permissionItem} - /> - ) - ).toMatchSnapshot(); -}); - -it('should display a checked checkbox', () => { - expect( - shallow( - <PermissionCell - loading={[]} - onCheck={jest.fn()} - permission={permission} - permissionItem={permissionItem} - selectedPermission="baz" - /> - ) - ).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/UserHolder-test.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/UserHolder-test.tsx deleted file mode 100644 index cca4b7ff392..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/UserHolder-test.tsx +++ /dev/null @@ -1,68 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 { mockPermissionUser } from '../../../../../helpers/mocks/permissions'; -import { waitAndUpdate } from '../../../../../helpers/testUtils'; -import UserHolder from '../UserHolder'; - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot('default'); - expect(shallowRender({ user: mockPermissionUser({ login: '<creator>' }) })).toMatchSnapshot( - 'creator' - ); -}); - -it('should disabled PermissionCell checkboxes when waiting for promise to return', async () => { - const wrapper = shallowRender(); - expect(wrapper.state().loading).toEqual([]); - - wrapper.instance().handleCheck(true, 'baz'); - wrapper.update(); - expect(wrapper.state().loading).toEqual(['baz']); - - wrapper.instance().handleCheck(true, 'bar'); - wrapper.update(); - expect(wrapper.state().loading).toEqual(['baz', 'bar']); - - await waitAndUpdate(wrapper); - expect(wrapper.state().loading).toEqual([]); -}); - -function shallowRender(props: Partial<UserHolder['props']> = {}) { - return shallow<UserHolder>( - <UserHolder - onToggle={jest.fn().mockResolvedValue(null)} - permissions={[ - { - category: 'admin', - permissions: [ - { key: 'foo', name: 'Foo', description: '' }, - { key: 'bar', name: 'Bar', description: '' }, - ], - }, - { key: 'baz', name: 'Baz', description: '' }, - ]} - selectedPermission="bar" - user={mockPermissionUser({ email: 'john.doe@sonarsource.com', name: 'John Doe' })} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/HoldersList-test.tsx.snap b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/HoldersList-test.tsx.snap deleted file mode 100644 index d792e9b5e58..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/HoldersList-test.tsx.snap +++ /dev/null @@ -1,328 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should display users and groups 1`] = ` -<div - className="boxed-group boxed-group-inner" -> - <table - className="data zebra permissions-table" - > - <thead> - <tr> - <td - className="nowrap bordered-bottom" - /> - <PermissionHeader - key="foo" - onSelectPermission={[MockFunction]} - permission={ - { - "description": "", - "key": "foo", - "name": "Foo", - } - } - selectedPermission="bar" - /> - <PermissionHeader - key="admin" - onSelectPermission={[MockFunction]} - permission={ - { - "category": "admin", - "permissions": [ - { - "description": "", - "key": "bar", - "name": "Bar", - }, - { - "description": "", - "key": "baz", - "name": "Baz", - }, - ], - } - } - selectedPermission="bar" - /> - </tr> - </thead> - <tbody> - <GroupHolder - group={ - { - "id": "anyone", - "name": "Anyone", - "permissions": [ - "bar", - ], - } - } - isComponentPrivate={true} - key="group-anyone" - onToggle={[Function]} - permissions={ - [ - { - "description": "", - "key": "foo", - "name": "Foo", - }, - { - "category": "admin", - "permissions": [ - { - "description": "", - "key": "bar", - "name": "Bar", - }, - { - "description": "", - "key": "baz", - "name": "Baz", - }, - ], - }, - ] - } - selectedPermission="bar" - /> - <UserHolder - key="user-barbaz" - onToggle={[Function]} - permissions={ - [ - { - "description": "", - "key": "foo", - "name": "Foo", - }, - { - "category": "admin", - "permissions": [ - { - "description": "", - "key": "bar", - "name": "Bar", - }, - { - "description": "", - "key": "baz", - "name": "Baz", - }, - ], - }, - ] - } - selectedPermission="bar" - user={ - { - "login": "barbaz", - "name": "Barbaz", - "permissions": [ - "bar", - ], - } - } - /> - <GroupHolder - group={ - { - "id": "barbaz", - "name": "Barbaz", - "permissions": [ - "bar", - ], - } - } - isComponentPrivate={true} - key="group-barbaz" - onToggle={[Function]} - permissions={ - [ - { - "description": "", - "key": "foo", - "name": "Foo", - }, - { - "category": "admin", - "permissions": [ - { - "description": "", - "key": "bar", - "name": "Bar", - }, - { - "description": "", - "key": "baz", - "name": "Baz", - }, - ], - }, - ] - } - selectedPermission="bar" - /> - <UserHolder - key="user-foobar" - onToggle={[Function]} - permissions={ - [ - { - "description": "", - "key": "foo", - "name": "Foo", - }, - { - "category": "admin", - "permissions": [ - { - "description": "", - "key": "bar", - "name": "Bar", - }, - { - "description": "", - "key": "baz", - "name": "Baz", - }, - ], - }, - ] - } - selectedPermission="bar" - user={ - { - "login": "foobar", - "name": "Foobar", - "permissions": [ - "bar", - ], - } - } - /> - <GroupHolder - group={ - { - "id": "foobar", - "name": "Foobar", - "permissions": [ - "bar", - ], - } - } - isComponentPrivate={true} - key="group-foobar" - onToggle={[Function]} - permissions={ - [ - { - "description": "", - "key": "foo", - "name": "Foo", - }, - { - "category": "admin", - "permissions": [ - { - "description": "", - "key": "bar", - "name": "Bar", - }, - { - "description": "", - "key": "baz", - "name": "Baz", - }, - ], - }, - ] - } - selectedPermission="bar" - /> - <tr> - <td - className="divider" - colSpan={20} - /> - </tr> - <tr /> - <GroupHolder - group={ - { - "id": "abc", - "name": "abc", - "permissions": [], - } - } - isComponentPrivate={true} - key="group-abc" - onToggle={[Function]} - permissions={ - [ - { - "description": "", - "key": "foo", - "name": "Foo", - }, - { - "category": "admin", - "permissions": [ - { - "description": "", - "key": "bar", - "name": "Bar", - }, - { - "description": "", - "key": "baz", - "name": "Baz", - }, - ], - }, - ] - } - selectedPermission="bar" - /> - <UserHolder - key="user-bcd" - onToggle={[Function]} - permissions={ - [ - { - "description": "", - "key": "foo", - "name": "Foo", - }, - { - "category": "admin", - "permissions": [ - { - "description": "", - "key": "bar", - "name": "Bar", - }, - { - "description": "", - "key": "baz", - "name": "Baz", - }, - ], - }, - ] - } - selectedPermission="bar" - user={ - { - "login": "bcd", - "name": "bcd", - "permissions": [], - } - } - /> - </tbody> - </table> -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/PermissionCell-test.tsx.snap b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/PermissionCell-test.tsx.snap deleted file mode 100644 index 9f7c78df5a5..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/PermissionCell-test.tsx.snap +++ /dev/null @@ -1,89 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should display a checked checkbox 1`] = ` -<td - className="permission-column text-center text-middle selected" -> - <Checkbox - checked={true} - disabled={false} - id="baz" - label="checked permission 'Baz' for user 'Baz'" - onCheck={[MockFunction]} - thirdState={false} - /> -</td> -`; - -exports[`should display a disabled checkbox 1`] = ` -<td - className="permission-column text-center text-middle" -> - <Checkbox - checked={true} - disabled={true} - id="baz" - label="disabled permission 'Baz' for user 'Baz'" - onCheck={[MockFunction]} - thirdState={false} - /> -</td> -`; - -exports[`should display an unchecked checkbox 1`] = ` -<td - className="permission-column text-center text-middle" -> - <Checkbox - checked={false} - disabled={false} - id="bar" - label="unchecked permission 'Baz' for user 'Baz'" - onCheck={[MockFunction]} - thirdState={false} - /> -</td> -`; - -exports[`should display multiple checkboxes with one checked 1`] = ` -<td - className="text-middle" -> - <div - key="foo" - > - <Checkbox - checked={false} - disabled={false} - id="foo" - label="unchecked permission 'Foo' for user 'Baz'" - onCheck={[MockFunction]} - thirdState={false} - > - <span - className="little-spacer-left" - > - Foo - </span> - </Checkbox> - </div> - <div - key="baz" - > - <Checkbox - checked={true} - disabled={false} - id="baz" - label="checked permission 'Baz' for user 'Baz'" - onCheck={[MockFunction]} - thirdState={false} - > - <span - className="little-spacer-left" - > - Baz - </span> - </Checkbox> - </div> -</td> -`; diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/UserHolder-test.tsx.snap b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/UserHolder-test.tsx.snap deleted file mode 100644 index 1133c690cb2..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/UserHolder-test.tsx.snap +++ /dev/null @@ -1,182 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly: creator 1`] = ` -<tr> - <td - className="nowrap text-middle" - > - <div> - <strong> - johndoe - </strong> - </div> - <div - className="little-spacer-top" - style={ - { - "whiteSpace": "normal", - } - } - > - permission_templates.project_creators.explanation - </div> - </td> - <PermissionCell - key="admin" - loading={[]} - onCheck={[Function]} - permission={ - { - "category": "admin", - "permissions": [ - { - "description": "", - "key": "foo", - "name": "Foo", - }, - { - "description": "", - "key": "bar", - "name": "Bar", - }, - ], - } - } - permissionItem={ - { - "active": true, - "local": true, - "login": "<creator>", - "name": "johndoe", - "permissions": [ - "provisioning", - ], - } - } - selectedPermission="bar" - /> - <PermissionCell - key="baz" - loading={[]} - onCheck={[Function]} - permission={ - { - "description": "", - "key": "baz", - "name": "Baz", - } - } - permissionItem={ - { - "active": true, - "local": true, - "login": "<creator>", - "name": "johndoe", - "permissions": [ - "provisioning", - ], - } - } - selectedPermission="bar" - /> -</tr> -`; - -exports[`should render correctly: default 1`] = ` -<tr> - <td - className="nowrap text-middle" - > - <div - className="display-flex-center" - > - <withAppStateContext(Avatar) - className="text-middle big-spacer-right flex-0" - name="John Doe" - size={36} - /> - <div - className="max-width-100" - > - <div - className="max-width-100 text-ellipsis" - > - <strong> - John Doe - </strong> - <span - className="note spacer-left" - > - john.doe - </span> - </div> - <div - className="little-spacer-top max-width-100 text-ellipsis" - > - john.doe@sonarsource.com - </div> - </div> - </div> - </td> - <PermissionCell - key="admin" - loading={[]} - onCheck={[Function]} - permission={ - { - "category": "admin", - "permissions": [ - { - "description": "", - "key": "foo", - "name": "Foo", - }, - { - "description": "", - "key": "bar", - "name": "Bar", - }, - ], - } - } - permissionItem={ - { - "active": true, - "email": "john.doe@sonarsource.com", - "local": true, - "login": "john.doe", - "name": "John Doe", - "permissions": [ - "provisioning", - ], - } - } - selectedPermission="bar" - /> - <PermissionCell - key="baz" - loading={[]} - onCheck={[Function]} - permission={ - { - "description": "", - "key": "baz", - "name": "Baz", - } - } - permissionItem={ - { - "active": true, - "email": "john.doe@sonarsource.com", - "local": true, - "login": "john.doe", - "name": "John Doe", - "permissions": [ - "provisioning", - ], - } - } - selectedPermission="bar" - /> -</tr> -`; diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectManagementApp-it.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectManagementApp-it.tsx index 4255f24f0af..3810e34f325 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectManagementApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectManagementApp-it.tsx @@ -26,8 +26,6 @@ import { renderAppWithAdminContext } from '../../../helpers/testReactTestingUtil import { ComponentQualifier, Visibility } from '../../../types/component'; import routes from '../routes'; -jest.mock('../../../api/permissions'); - jest.mock('../../../api/components', () => ({ getComponents: jest.fn().mockResolvedValue({ paging: { total: 0 }, |