import { throwGlobalError } from '../helpers/error';
import { getJSON, post, postJSON, RequestData } from '../helpers/request';
import { BranchParameters } from '../types/branch-like';
-import { ComponentQualifier, TreeComponent, TreeComponentWithPath } from '../types/component';
+import {
+ ComponentQualifier,
+ TreeComponent,
+ TreeComponentWithPath,
+ Visibility,
+} from '../types/component';
import {
ComponentMeasure,
Dict,
Paging,
SourceLine,
SourceViewerFile,
- Visibility,
} from '../types/types';
export interface BaseSearchProjectsParameters {
+++ /dev/null
-/*
- * 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 { chunk, cloneDeep } from 'lodash';
-import {
- mockPermissionTemplate,
- mockTemplateGroup,
- mockTemplateUser,
-} from '../../helpers/testMocks';
-import { PermissionTemplate } from '../../types/types';
-import { BaseSearchProjectsParameters } from '../components';
-import {
- addProjectCreatorToTemplate,
- bulkApplyTemplate,
- getPermissionTemplateGroups,
- getPermissionTemplates,
- getPermissionTemplateUsers,
- grantTemplatePermissionToGroup,
- grantTemplatePermissionToUser,
- removeProjectCreatorFromTemplate,
- revokeTemplatePermissionFromGroup,
- revokeTemplatePermissionFromUser,
-} from '../permissions';
-
-const MAX_PROJECTS_TO_APPLY_PERMISSION_TEMPLATE = 10;
-
-const defaultPermissionTemplates: PermissionTemplate[] = [
- mockPermissionTemplate(),
- mockPermissionTemplate({
- id: 'template2',
- name: 'Permission Template 2',
- }),
-];
-
-const templateUsers = [
- mockTemplateUser(),
- mockTemplateUser({
- login: 'gooduser1',
- name: 'John',
- permissions: ['issueadmin', 'securityhotspotadmin', 'user'],
- }),
- mockTemplateUser({
- login: 'gooduser2',
- name: 'Alexa',
- permissions: ['issueadmin', 'user'],
- }),
- mockTemplateUser({
- name: 'Siri',
- login: 'gooduser3',
- }),
- mockTemplateUser({
- login: 'gooduser4',
- name: 'Cool',
- permissions: ['user'],
- }),
- mockTemplateUser({
- name: 'White',
- login: 'baduser1',
- }),
- mockTemplateUser({
- name: 'Green',
- login: 'baduser2',
- }),
-];
-
-const templateGroups = [
- mockTemplateGroup(),
- mockTemplateGroup({ id: 'admins', name: 'admins', permissions: [] }),
-];
-
-const PAGE_SIZE = 5;
-const MIN_QUERY_LENGTH = 3;
-const DEFAULT_PAGE = 1;
-
-jest.mock('../permissions');
-
-export default class PermissionTemplateServiceMock {
- permissionTemplates: PermissionTemplate[] = [];
- isAllowedPermissionChange = true;
-
- constructor() {
- this.permissionTemplates = cloneDeep(defaultPermissionTemplates);
- (getPermissionTemplates as jest.Mock).mockImplementation(this.handleGetPermissionTemplates);
- (bulkApplyTemplate as jest.Mock).mockImplementation(this.handleBulkApplyTemplate);
- (getPermissionTemplateUsers as jest.Mock).mockImplementation(
- this.handleGetPermissionTemplateUsers
- );
- (getPermissionTemplateGroups as jest.Mock).mockImplementation(
- this.handleGetPermissionTemplateGroups
- );
- (addProjectCreatorToTemplate as jest.Mock).mockImplementation(this.handlePermissionChange);
- (removeProjectCreatorFromTemplate as jest.Mock).mockImplementation(this.handlePermissionChange);
- (grantTemplatePermissionToGroup as jest.Mock).mockImplementation(this.handlePermissionChange);
- (revokeTemplatePermissionFromGroup as jest.Mock).mockImplementation(
- this.handlePermissionChange
- );
- (grantTemplatePermissionToUser as jest.Mock).mockImplementation(this.handlePermissionChange);
- (revokeTemplatePermissionFromUser as jest.Mock).mockImplementation(this.handlePermissionChange);
- }
-
- handleGetPermissionTemplates = () => {
- return this.reply({ permissionTemplates: this.permissionTemplates });
- };
-
- handleBulkApplyTemplate = (params: BaseSearchProjectsParameters) => {
- if (
- params.projects &&
- params.projects.split(',').length > MAX_PROJECTS_TO_APPLY_PERMISSION_TEMPLATE
- ) {
- const response = new Response(
- JSON.stringify({ errors: [{ msg: 'bulk apply permission template error message' }] })
- );
- return Promise.reject(response);
- }
-
- return Promise.resolve();
- };
-
- handleGetPermissionTemplateUsers = (data: { q?: string | null; p?: number; ps?: number }) => {
- const { ps = PAGE_SIZE, p = DEFAULT_PAGE, q } = data;
-
- const users =
- q && q.length >= MIN_QUERY_LENGTH
- ? templateUsers.filter((user) =>
- [user.login, user.name].some((key) => key.toLowerCase().includes(q.toLowerCase()))
- )
- : templateUsers;
-
- const usersChunks = chunk(users, ps);
-
- return this.reply({
- paging: { pageSize: ps, total: users.length, pageIndex: p },
- users: usersChunks[p - 1] ?? [],
- });
- };
-
- handleGetPermissionTemplateGroups = (data: {
- templateId: string;
- q?: string | null;
- permission?: string;
- p?: number;
- ps?: number;
- }) => {
- const { ps = PAGE_SIZE, p = DEFAULT_PAGE, q } = data;
-
- const groups =
- q && q.length >= MIN_QUERY_LENGTH
- ? templateGroups.filter((group) => group.name.toLowerCase().includes(q.toLowerCase()))
- : templateGroups;
-
- const groupsChunks = chunk(groups, ps);
-
- return this.reply({
- paging: { pageSize: ps, total: groups.length, pageIndex: p },
- groups: groupsChunks[p - 1] ?? [],
- });
- };
-
- handlePermissionChange = () => {
- return this.isAllowedPermissionChange ? Promise.resolve() : Promise.reject();
- };
-
- updatePermissionChangeAllowance = (val: boolean) => {
- this.isAllowedPermissionChange = val;
- };
-
- reset = () => {
- this.permissionTemplates = cloneDeep(defaultPermissionTemplates);
- this.updatePermissionChangeAllowance(true);
- };
-
- reply<T>(response: T): Promise<T> {
- return Promise.resolve(cloneDeep(response));
- }
-}
--- /dev/null
+/*
+ * 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 { chunk, cloneDeep, remove, uniq } from 'lodash';
+import {
+ mockPermission,
+ mockPermissionGroup,
+ mockPermissionTemplate,
+ mockPermissionUser,
+ mockTemplateGroup,
+ mockTemplateUser,
+} from '../../helpers/mocks/permissions';
+import { ComponentQualifier, Visibility } from '../../types/component';
+import { Permissions } from '../../types/permissions';
+import { Permission, PermissionGroup, PermissionTemplate, PermissionUser } from '../../types/types';
+import { BaseSearchProjectsParameters } from '../components';
+import {
+ addProjectCreatorToTemplate,
+ applyTemplateToProject,
+ bulkApplyTemplate,
+ changeProjectVisibility,
+ getPermissionsGroupsForComponent,
+ getPermissionsUsersForComponent,
+ getPermissionTemplateGroups,
+ getPermissionTemplates,
+ getPermissionTemplateUsers,
+ grantPermissionToGroup,
+ grantPermissionToUser,
+ grantTemplatePermissionToGroup,
+ grantTemplatePermissionToUser,
+ removeProjectCreatorFromTemplate,
+ revokePermissionFromGroup,
+ revokePermissionFromUser,
+ revokeTemplatePermissionFromGroup,
+ revokeTemplatePermissionFromUser,
+} from '../permissions';
+
+const MAX_PROJECTS_TO_APPLY_PERMISSION_TEMPLATE = 10;
+
+const defaultPermissionTemplates: PermissionTemplate[] = [
+ mockPermissionTemplate(),
+ mockPermissionTemplate({
+ id: 'template2',
+ name: 'Permission Template 2',
+ }),
+];
+
+const templateUsers = [
+ mockTemplateUser(),
+ mockTemplateUser({
+ login: 'gooduser1',
+ name: 'John',
+ permissions: ['issueadmin', 'securityhotspotadmin', 'user'],
+ }),
+ mockTemplateUser({
+ login: 'gooduser2',
+ name: 'Alexa',
+ permissions: ['issueadmin', 'user'],
+ }),
+ mockTemplateUser({
+ name: 'Siri',
+ login: 'gooduser3',
+ }),
+ mockTemplateUser({
+ login: 'gooduser4',
+ name: 'Cool',
+ permissions: ['user'],
+ }),
+ mockTemplateUser({
+ name: 'White',
+ login: 'baduser1',
+ }),
+ mockTemplateUser({
+ name: 'Green',
+ login: 'baduser2',
+ }),
+];
+
+const templateGroups = [
+ mockTemplateGroup(),
+ mockTemplateGroup({ id: 'admins', name: 'admins', permissions: [] }),
+];
+
+const defaultUsers = [mockPermissionUser()];
+
+const defaultGroups = [
+ mockPermissionGroup({ name: 'sonar-users', permissions: [Permissions.Browse] }),
+ mockPermissionGroup({
+ name: 'sonar-admins',
+ permissions: [Permissions.Admin, Permissions.Browse],
+ }),
+ mockPermissionGroup({ name: 'sonar-losers', permissions: [] }),
+];
+
+const PAGE_SIZE = 5;
+const MIN_QUERY_LENGTH = 3;
+const DEFAULT_PAGE = 1;
+
+jest.mock('../permissions');
+
+export default class PermissionsServiceMock {
+ permissionTemplates: PermissionTemplate[] = [];
+ permissions: Permission[];
+ defaultTemplates: Array<{ templateId: string; qualifier: string }>;
+ groups: PermissionGroup[];
+ users: PermissionUser[];
+ isAllowedPermissionChange = true;
+
+ constructor() {
+ this.permissionTemplates = cloneDeep(defaultPermissionTemplates);
+ this.defaultTemplates = [
+ ComponentQualifier.Project,
+ ComponentQualifier.Application,
+ ComponentQualifier.Portfolio,
+ ].map((qualifier) => ({ templateId: this.permissionTemplates[0].id, qualifier }));
+ this.permissions = [
+ Permissions.Admin,
+ Permissions.CodeViewer,
+ Permissions.IssueAdmin,
+ Permissions.SecurityHotspotAdmin,
+ Permissions.Scan,
+ Permissions.Browse,
+ ].map((key) => mockPermission({ key, name: key }));
+ this.groups = cloneDeep(defaultGroups);
+ this.users = cloneDeep(defaultUsers);
+
+ jest.mocked(getPermissionTemplates).mockImplementation(this.handleGetPermissionTemplates);
+ jest.mocked(bulkApplyTemplate).mockImplementation(this.handleBulkApplyTemplate);
+ jest.mocked(applyTemplateToProject).mockImplementation(this.handleApplyTemplateToProject);
+ jest
+ .mocked(getPermissionTemplateUsers)
+ .mockImplementation(this.handleGetPermissionTemplateUsers);
+ jest
+ .mocked(getPermissionTemplateGroups)
+ .mockImplementation(this.handleGetPermissionTemplateGroups);
+ jest.mocked(addProjectCreatorToTemplate).mockImplementation(this.handlePermissionChange);
+ jest.mocked(removeProjectCreatorFromTemplate).mockImplementation(this.handlePermissionChange);
+ jest.mocked(grantTemplatePermissionToGroup).mockImplementation(this.handlePermissionChange);
+ jest.mocked(revokeTemplatePermissionFromGroup).mockImplementation(this.handlePermissionChange);
+ jest.mocked(grantTemplatePermissionToUser).mockImplementation(this.handlePermissionChange);
+ jest.mocked(revokeTemplatePermissionFromUser).mockImplementation(this.handlePermissionChange);
+ jest.mocked(changeProjectVisibility).mockImplementation(this.handleChangeProjectVisibility);
+ jest
+ .mocked(getPermissionsGroupsForComponent)
+ .mockImplementation(this.handleGetPermissionGroupsForComponent);
+ jest
+ .mocked(getPermissionsUsersForComponent)
+ .mockImplementation(this.handleGetPermissionUsersForComponent);
+ jest.mocked(grantPermissionToGroup).mockImplementation(this.handleGrantPermissionToGroup);
+ jest.mocked(revokePermissionFromGroup).mockImplementation(this.handleRevokePermissionFromGroup);
+ jest.mocked(grantPermissionToUser).mockImplementation(this.handleGrantPermissionToUser);
+ jest.mocked(revokePermissionFromUser).mockImplementation(this.handleRevokePermissionFromUser);
+ }
+
+ handleGetPermissionTemplates = () => {
+ return this.reply({
+ permissionTemplates: this.permissionTemplates,
+ defaultTemplates: this.defaultTemplates,
+ permissions: this.permissions,
+ });
+ };
+
+ handleApplyTemplateToProject = (_data: { projectKey: string; templateId: string }) => {
+ return this.reply(undefined);
+ };
+
+ handleBulkApplyTemplate = (params: BaseSearchProjectsParameters) => {
+ if (
+ params.projects &&
+ params.projects.split(',').length > MAX_PROJECTS_TO_APPLY_PERMISSION_TEMPLATE
+ ) {
+ const response = new Response(
+ JSON.stringify({ errors: [{ msg: 'bulk apply permission template error message' }] })
+ );
+ return Promise.reject(response);
+ }
+
+ return Promise.resolve();
+ };
+
+ handleGetPermissionTemplateUsers = (data: { q?: string | null; p?: number; ps?: number }) => {
+ const { ps = PAGE_SIZE, p = DEFAULT_PAGE, q } = data;
+
+ const users =
+ q && q.length >= MIN_QUERY_LENGTH
+ ? templateUsers.filter((user) =>
+ [user.login, user.name].some((key) => key.toLowerCase().includes(q.toLowerCase()))
+ )
+ : templateUsers;
+
+ const usersChunks = chunk(users, ps);
+
+ return this.reply({
+ paging: { pageSize: ps, total: users.length, pageIndex: p },
+ users: usersChunks[p - 1] ?? [],
+ });
+ };
+
+ handleGetPermissionTemplateGroups = (data: {
+ templateId: string;
+ q?: string | null;
+ permission?: string;
+ p?: number;
+ ps?: number;
+ }) => {
+ const { ps = PAGE_SIZE, p = DEFAULT_PAGE, q } = data;
+
+ const groups =
+ q && q.length >= MIN_QUERY_LENGTH
+ ? templateGroups.filter((group) => group.name.toLowerCase().includes(q.toLowerCase()))
+ : templateGroups;
+
+ const groupsChunks = chunk(groups, ps);
+
+ return this.reply({
+ paging: { pageSize: ps, total: groups.length, pageIndex: p },
+ groups: groupsChunks[p - 1] ?? [],
+ });
+ };
+
+ handleChangeProjectVisibility = (_project: string, _visibility: Visibility) => {
+ return this.reply(undefined);
+ };
+
+ handleGetPermissionGroupsForComponent = (data: {
+ projectKey: string;
+ q?: string;
+ permission?: string;
+ p?: number;
+ ps?: number;
+ }) => {
+ const { ps = PAGE_SIZE, p = DEFAULT_PAGE, q, permission } = data;
+
+ const groups =
+ q && q.length >= MIN_QUERY_LENGTH
+ ? this.groups.filter((group) => group.name.toLowerCase().includes(q.toLowerCase()))
+ : this.groups;
+
+ const groupsChunked = chunk(
+ permission ? groups.filter((g) => g.permissions.includes(permission)) : groups,
+ ps
+ );
+
+ return this.reply({
+ paging: { pageSize: ps, total: groups.length, pageIndex: p },
+ groups: groupsChunked[p - 1] ?? [],
+ });
+ };
+
+ handleGetPermissionUsersForComponent = (data: {
+ projectKey: string;
+ q?: string;
+ permission?: string;
+ p?: number;
+ ps?: number;
+ }) => {
+ const { ps = PAGE_SIZE, p = DEFAULT_PAGE, q, permission } = data;
+
+ const users =
+ q && q.length >= MIN_QUERY_LENGTH
+ ? this.users.filter((user) => user.name.toLowerCase().includes(q.toLowerCase()))
+ : this.users;
+
+ const usersChunked = chunk(
+ permission ? users.filter((u) => u.permissions.includes(permission)) : users,
+ ps
+ );
+
+ return this.reply({
+ paging: { pageSize: ps, total: users.length, pageIndex: p },
+ users: usersChunked[p - 1] ?? [],
+ });
+ };
+
+ handleGrantPermissionToGroup = (data: {
+ projectKey?: string;
+ groupName: string;
+ permission: string;
+ }) => {
+ if (!this.isAllowedPermissionChange) {
+ return Promise.reject();
+ }
+
+ const { groupName, permission } = data;
+ const group = this.groups.find((g) => g.name === groupName);
+ if (group === undefined) {
+ throw new Error(`Could not find group with name ${groupName}`);
+ }
+ group.permissions = uniq([...group.permissions, permission]);
+ return this.reply(undefined);
+ };
+
+ handleRevokePermissionFromGroup = (data: {
+ projectKey?: string;
+ groupName: string;
+ permission: string;
+ }) => {
+ if (!this.isAllowedPermissionChange) {
+ return Promise.reject();
+ }
+
+ const { groupName, permission } = data;
+ const group = this.groups.find((g) => g.name === groupName);
+ if (group === undefined) {
+ throw new Error(`Could not find group with name ${groupName}`);
+ }
+ group.permissions = remove(group.permissions, permission);
+ return this.reply(undefined);
+ };
+
+ handleGrantPermissionToUser = (data: {
+ projectKey?: string;
+ login: string;
+ permission: string;
+ }) => {
+ if (!this.isAllowedPermissionChange) {
+ return Promise.reject();
+ }
+
+ const { login, permission } = data;
+ const user = this.users.find((u) => u.login === login);
+ if (user === undefined) {
+ throw new Error(`Could not find user with login ${login}`);
+ }
+ user.permissions = uniq([...user.permissions, permission]);
+ return this.reply(undefined);
+ };
+
+ handleRevokePermissionFromUser = (data: {
+ projectKey?: string;
+ login: string;
+ permission: string;
+ }) => {
+ if (!this.isAllowedPermissionChange) {
+ return Promise.reject();
+ }
+
+ const { login, permission } = data;
+ const user = this.users.find((u) => u.login === login);
+ if (user === undefined) {
+ throw new Error(`Could not find user with name ${login}`);
+ }
+ user.permissions = remove(user.permissions, permission);
+ return this.reply(undefined);
+ };
+
+ handlePermissionChange = () => {
+ return this.isAllowedPermissionChange ? Promise.resolve() : Promise.reject();
+ };
+
+ updatePermissionChangeAllowance = (val: boolean) => {
+ this.isAllowedPermissionChange = val;
+ };
+
+ setGroups = (groups: PermissionGroup[]) => {
+ this.groups = groups;
+ };
+
+ setUsers = (users: PermissionUser[]) => {
+ this.users = users;
+ };
+
+ reset = () => {
+ this.permissionTemplates = cloneDeep(defaultPermissionTemplates);
+ this.groups = cloneDeep(defaultGroups);
+ this.users = cloneDeep(defaultUsers);
+ this.updatePermissionChangeAllowance(true);
+ };
+
+ reply<T>(response: T): Promise<T> {
+ return Promise.resolve(cloneDeep(response));
+ }
+}
*/
import { throwGlobalError } from '../helpers/error';
import { getJSON, post, postJSON, RequestData } from '../helpers/request';
+import { Visibility } from '../types/component';
import {
Paging,
Permission,
PermissionGroup,
PermissionTemplate,
PermissionUser,
- Visibility,
} from '../types/types';
import { BaseSearchProjectsParameters } from './components';
return post('/api/permissions/set_default_template', { templateId, qualifier });
}
-export function applyTemplateToProject(data: RequestData) {
+export function applyTemplateToProject(data: { projectKey: string; templateId: string }) {
return post('/api/permissions/apply_template', data).catch(throwGlobalError);
}
import { mockLocation, mockRouter } from '../../../helpers/testMocks';
import { waitAndUpdate } from '../../../helpers/testUtils';
import { AlmKeys } from '../../../types/alm-settings';
-import { ComponentQualifier } from '../../../types/component';
+import { ComponentQualifier, Visibility } from '../../../types/component';
import { TaskStatuses, TaskTypes } from '../../../types/tasks';
import { Component } from '../../../types/types';
import handleRequiredAuthorization from '../../utils/handleRequiredAuthorization';
const wrapper = shallowRender();
wrapper.setState({
branchLikes: [mockMainBranch()],
- component: { qualifier: 'TRK', visibility: 'public' } as Component,
+ component: {
+ qualifier: ComponentQualifier.Project,
+ visibility: Visibility.Public,
+ } as Component,
loading: false,
});
- wrapper.instance().handleComponentChange({ visibility: 'private' });
- expect(wrapper.state().component).toEqual({ qualifier: 'TRK', visibility: 'private' });
+ wrapper.instance().handleComponentChange({ visibility: Visibility.Private });
+ expect(wrapper.state().component).toEqual({
+ qualifier: ComponentQualifier.Project,
+ visibility: Visibility.Private,
+ });
});
it('loads the project binding, if any', async () => {
wrapper.setState({
branchLikes: [mockMainBranch()],
component: mockComponent({
- breadcrumbs: [{ key: 'projectKey', name: 'project', qualifier: 'TRK' }],
+ breadcrumbs: [{ key: 'projectKey', name: 'project', qualifier: ComponentQualifier.Project }],
}),
loading: false,
});
import { mockComponent } from '../../../../../../helpers/mocks/component';
import { mockCurrentUser, mockLoggedInUser, mockMetric } from '../../../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../../../helpers/testUtils';
-import { ComponentQualifier } from '../../../../../../types/component';
+import { ComponentQualifier, Visibility } from '../../../../../../types/component';
import ProjectBadges from '../badges/ProjectBadges';
import { ProjectInformation } from '../ProjectInformation';
import { ProjectInformationPages } from '../ProjectInformationPages';
it('should render correctly', async () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ currentUser: mockLoggedInUser() })).toMatchSnapshot('logged in user');
- expect(shallowRender({ component: mockComponent({ visibility: 'private' }) })).toMatchSnapshot(
- 'private'
- );
+ expect(
+ shallowRender({ component: mockComponent({ visibility: Visibility.Private }) })
+ ).toMatchSnapshot('private');
const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot('measures loaded');
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockComponent } from '../../../../../../helpers/mocks/component';
+import { ComponentQualifier, Visibility } from '../../../../../../types/component';
import {
ProjectInformationRenderer,
ProjectInformationRendererProps,
});
it('should render a private project correctly', () => {
- expect(shallowRender({ component: mockComponent({ visibility: 'private' }) })).toMatchSnapshot();
+ expect(
+ shallowRender({ component: mockComponent({ visibility: Visibility.Private }) })
+ ).toMatchSnapshot();
});
it('should render an app correctly', () => {
hasFeature={jest.fn().mockReturnValue(true)}
canConfigureNotifications={true}
canUseBadges={true}
- component={mockComponent({ qualifier: 'TRK', visibility: 'public' })}
+ component={mockComponent({
+ qualifier: ComponentQualifier.Project,
+ visibility: Visibility.Public,
+ })}
onComponentChange={jest.fn()}
onPageChange={jest.fn()}
{...props}
import userEvent from '@testing-library/user-event';
import React from 'react';
import { byRole } from 'testing-library-selector';
-import PermissionTemplateServiceMock from '../../../../api/mocks/PermissionTemplateServiceMock';
+import PermissionsServiceMock from '../../../../api/mocks/PermissionsServiceMock';
import { mockAppState } from '../../../../helpers/testMocks';
import { renderApp } from '../../../../helpers/testReactTestingUtils';
+import { ComponentQualifier } from '../../../../types/component';
+import { Permissions } from '../../../../types/permissions';
import PermissionTemplatesApp from '../PermissionTemplatesApp';
-const serviceMock = new PermissionTemplateServiceMock();
+const serviceMock = new PermissionsServiceMock();
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'`,
- }),
-
+ permissionCheckbox: (target: string, permission: Permissions) =>
+ byRole('checkbox', {
+ name: `permission.assign_x_to_y.projects_role.${permission}.${target}`,
+ }),
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 () => {
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.permissionCheckbox('Admin Admin', Permissions.Browse).get()).not.toBeChecked();
+ await user.click(ui.permissionCheckbox('Admin Admin', Permissions.Browse).get());
+ expect(ui.permissionCheckbox('Admin Admin', Permissions.Browse).get()).toBeChecked();
- expect(ui.adminUserAdministerCheckboxChecked.get()).toBeChecked();
- await user.click(ui.adminUserAdministerCheckboxChecked.get());
- expect(ui.adminUserAdministerCheckboxUnchecked.get()).not.toBeChecked();
+ expect(ui.permissionCheckbox('Admin Admin', Permissions.Admin).get()).toBeChecked();
+ await user.click(ui.permissionCheckbox('Admin Admin', Permissions.Admin).get());
+ expect(ui.permissionCheckbox('Admin Admin', Permissions.Admin).get()).not.toBeChecked();
// Group
- expect(ui.anyoneGroupBrowseCheckboxUnchecked.get()).not.toBeChecked();
- await user.click(ui.anyoneGroupBrowseCheckboxUnchecked.get());
- expect(ui.anyoneGroupBrowseCheckboxChecked.get()).toBeChecked();
+ expect(ui.permissionCheckbox('Anyone', Permissions.Browse).get()).not.toBeChecked();
+ await user.click(ui.permissionCheckbox('Anyone', Permissions.Browse).get());
+ expect(ui.permissionCheckbox('Anyone', Permissions.Browse).get()).toBeChecked();
- expect(ui.anyoneGroupCodeviewCheckboxChecked.get()).toBeChecked();
- await user.click(ui.anyoneGroupCodeviewCheckboxChecked.get());
- expect(ui.anyoneGroupCodeviewCheckboxUnchecked.get()).not.toBeChecked();
+ expect(ui.permissionCheckbox('Anyone', Permissions.CodeViewer).get()).toBeChecked();
+ await user.click(ui.permissionCheckbox('Anyone', Permissions.CodeViewer).get());
+ expect(ui.permissionCheckbox('Anyone', Permissions.CodeViewer).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.permissionCheckbox('Admin Admin', Permissions.Browse).get());
+ expect(ui.permissionCheckbox('Admin Admin', Permissions.Browse).get()).toBeChecked();
- await user.click(ui.adminUserBrowseCheckboxChecked.get());
- expect(ui.adminUserBrowseCheckboxChecked.get()).toBeChecked();
+ await user.click(ui.permissionCheckbox('Anyone', Permissions.CodeViewer).get());
+ expect(ui.permissionCheckbox('Anyone', Permissions.CodeViewer).get()).not.toBeChecked();
- await user.click(ui.adminUserAdministerCheckboxUnchecked.get());
- expect(ui.adminUserAdministerCheckboxUnchecked.get()).not.toBeChecked();
+ await user.click(ui.permissionCheckbox('Admin Admin', Permissions.Admin).get());
+ expect(ui.permissionCheckbox('Admin Admin', Permissions.Admin).get()).not.toBeChecked();
});
it('loads more items on Show More', async () => {
await user.click(await ui.templateLink1.find());
- expect(ui.whiteUserBrowseCheckbox.query()).not.toBeInTheDocument();
+ expect(ui.permissionCheckbox('White', Permissions.Browse).query()).not.toBeInTheDocument();
await user.click(ui.showMoreButton.get());
- expect(ui.whiteUserBrowseCheckbox.get()).toBeInTheDocument();
+ expect(ui.permissionCheckbox('White', Permissions.Browse).get()).toBeInTheDocument();
});
function renderPermissionTemplatesApp() {
renderApp('admin/permission_templates', <PermissionTemplatesApp />, {
- appState: mockAppState({ qualifiers: ['TRK'] }),
+ appState: mockAppState({ qualifiers: [ComponentQualifier.Project] }),
});
}
+++ /dev/null
-/*
- * 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 { without } from 'lodash';
-import * as React from 'react';
-import { Helmet } from 'react-helmet-async';
-import * as api from '../../../../api/permissions';
-import withComponentContext from '../../../../app/components/componentContext/withComponentContext';
-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 { convertToPermissionDefinitions, PERMISSIONS_ORDER_BY_QUALIFIER } from '../../utils';
-import PageHeader from './PageHeader';
-import PublicProjectDisclaimer from './PublicProjectDisclaimer';
-
-interface Props {
- component: Component;
- onComponentChange: (changes: Partial<Component>) => void;
-}
-
-interface State {
- disclaimer: boolean;
- filter: FilterOption;
- groups: PermissionGroup[];
- groupsPaging?: Paging;
- loading: boolean;
- query: string;
- selectedPermission?: string;
- users: PermissionUser[];
- usersPaging?: Paging;
-}
-
-export class App extends React.PureComponent<Props, State> {
- mounted = false;
-
- constructor(props: Props) {
- super(props);
- this.state = {
- disclaimer: false,
- filter: 'all',
- groups: [],
- loading: true,
- query: '',
- users: [],
- };
- }
-
- componentDidMount() {
- this.mounted = true;
- this.loadHolders();
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- stopLoading = () => {
- if (this.mounted) {
- this.setState({ loading: false });
- }
- };
-
- loadUsersAndGroups = (userPage?: number, groupsPage?: number) => {
- const { component } = this.props;
- const { filter, query, selectedPermission } = this.state;
-
- const getUsers: Promise<{ paging?: Paging; users: PermissionUser[] }> =
- filter !== 'groups'
- ? api.getPermissionsUsersForComponent({
- projectKey: component.key,
- q: query || undefined,
- permission: selectedPermission,
- p: userPage,
- })
- : Promise.resolve({ paging: undefined, users: [] });
-
- const getGroups: Promise<{ paging?: Paging; groups: PermissionGroup[] }> =
- filter !== 'users'
- ? api.getPermissionsGroupsForComponent({
- projectKey: component.key,
- q: query || undefined,
- permission: selectedPermission,
- p: groupsPage,
- })
- : Promise.resolve({ paging: undefined, groups: [] });
-
- return Promise.all([getUsers, getGroups]);
- };
-
- loadHolders = () => {
- this.setState({ loading: true });
- return this.loadUsersAndGroups().then(([usersResponse, groupsResponse]) => {
- if (this.mounted) {
- this.setState({
- groups: groupsResponse.groups,
- groupsPaging: groupsResponse.paging,
- loading: false,
- users: usersResponse.users,
- usersPaging: usersResponse.paging,
- });
- }
- }, this.stopLoading);
- };
-
- onLoadMore = () => {
- const { usersPaging, groupsPaging } = this.state;
- this.setState({ loading: true });
- return this.loadUsersAndGroups(
- usersPaging ? usersPaging.pageIndex + 1 : 1,
- groupsPaging ? groupsPaging.pageIndex + 1 : 1
- ).then(([usersResponse, groupsResponse]) => {
- if (this.mounted) {
- this.setState(({ groups, users }) => ({
- groups: [...groups, ...groupsResponse.groups],
- groupsPaging: groupsResponse.paging,
- loading: false,
- users: [...users, ...usersResponse.users],
- usersPaging: usersResponse.paging,
- }));
- }
- }, this.stopLoading);
- };
-
- handleFilterChange = (filter: FilterOption) => {
- if (this.mounted) {
- this.setState({ filter }, this.loadHolders);
- }
- };
-
- handleQueryChange = (query: string) => {
- if (this.mounted) {
- this.setState({ query }, this.loadHolders);
- }
- };
-
- handlePermissionSelect = (selectedPermission?: string) => {
- if (this.mounted) {
- this.setState(
- (state: State) => ({
- selectedPermission:
- state.selectedPermission === selectedPermission ? undefined : selectedPermission,
- }),
- this.loadHolders
- );
- }
- };
-
- addPermissionToGroup = (group: string, permission: string) => {
- return this.state.groups.map((candidate) =>
- candidate.name === group
- ? { ...candidate, permissions: [...candidate.permissions, permission] }
- : candidate
- );
- };
-
- addPermissionToUser = (user: string, permission: string) => {
- return this.state.users.map((candidate) =>
- candidate.login === user
- ? { ...candidate, permissions: [...candidate.permissions, permission] }
- : candidate
- );
- };
-
- removePermissionFromGroup = (group: string, permission: string) => {
- return this.state.groups.map((candidate) =>
- candidate.name === group
- ? { ...candidate, permissions: without(candidate.permissions, permission) }
- : candidate
- );
- };
-
- removePermissionFromUser = (user: string, permission: string) => {
- return this.state.users.map((candidate) =>
- candidate.login === user
- ? { ...candidate, permissions: without(candidate.permissions, permission) }
- : candidate
- );
- };
-
- grantPermissionToGroup = (group: string, permission: string) => {
- if (this.mounted) {
- this.setState({
- loading: true,
- groups: this.addPermissionToGroup(group, permission),
- });
- return api
- .grantPermissionToGroup({
- projectKey: this.props.component.key,
- groupName: group,
- permission,
- })
- .then(this.stopLoading, () => {
- if (this.mounted) {
- this.setState({
- loading: false,
- groups: this.removePermissionFromGroup(group, permission),
- });
- }
- });
- }
- return Promise.resolve();
- };
-
- grantPermissionToUser = (user: string, permission: string) => {
- if (this.mounted) {
- this.setState({
- loading: true,
- users: this.addPermissionToUser(user, permission),
- });
- return api
- .grantPermissionToUser({
- projectKey: this.props.component.key,
- login: user,
- permission,
- })
- .then(this.stopLoading, () => {
- if (this.mounted) {
- this.setState({
- loading: false,
- users: this.removePermissionFromUser(user, permission),
- });
- }
- });
- }
- return Promise.resolve();
- };
-
- revokePermissionFromGroup = (group: string, permission: string) => {
- if (this.mounted) {
- this.setState({
- loading: true,
- groups: this.removePermissionFromGroup(group, permission),
- });
- return api
- .revokePermissionFromGroup({
- projectKey: this.props.component.key,
- groupName: group,
- permission,
- })
- .then(this.stopLoading, () => {
- if (this.mounted) {
- this.setState({
- loading: false,
- groups: this.addPermissionToGroup(group, permission),
- });
- }
- });
- }
- return Promise.resolve();
- };
-
- revokePermissionFromUser = (user: string, permission: string) => {
- if (this.mounted) {
- this.setState({
- loading: true,
- users: this.removePermissionFromUser(user, permission),
- });
- return api
- .revokePermissionFromUser({
- projectKey: this.props.component.key,
- login: user,
- permission,
- })
- .then(this.stopLoading, () => {
- if (this.mounted) {
- this.setState({
- loading: false,
- users: this.addPermissionToUser(user, permission),
- });
- }
- });
- }
- return Promise.resolve();
- };
-
- handleVisibilityChange = (visibility: string) => {
- if (visibility === 'public') {
- this.openDisclaimer();
- } else {
- this.turnProjectToPrivate();
- }
- };
-
- turnProjectToPublic = () => {
- this.props.onComponentChange({ visibility: 'public' });
- api.changeProjectVisibility(this.props.component.key, 'public').then(
- () => {
- this.loadHolders();
- },
- () => {
- this.props.onComponentChange({
- visibility: 'private',
- });
- }
- );
- };
-
- turnProjectToPrivate = () => {
- this.props.onComponentChange({ visibility: 'private' });
- api.changeProjectVisibility(this.props.component.key, 'private').then(
- () => {
- this.loadHolders();
- },
- () => {
- this.props.onComponentChange({
- visibility: 'public',
- });
- }
- );
- };
-
- openDisclaimer = () => {
- if (this.mounted) {
- this.setState({ disclaimer: true });
- }
- };
-
- closeDisclaimer = () => {
- if (this.mounted) {
- this.setState({ disclaimer: false });
- }
- };
-
- 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={loading} />
- <div>
- <VisibilitySelector
- canTurnToPrivate={canTurnToPrivate}
- className="big-spacer-top big-spacer-bottom"
- onChange={this.handleVisibilityChange}
- visibility={component.visibility}
- />
- {disclaimer && (
- <PublicProjectDisclaimer
- component={component}
- onClose={this.closeDisclaimer}
- onConfirm={this.turnProjectToPublic}
- />
- )}
- </div>
- <AllHoldersList
- filter={filter}
- grantPermissionToGroup={this.grantPermissionToGroup}
- grantPermissionToUser={this.grantPermissionToUser}
- groups={groups}
- groupsPaging={groupsPaging}
- onFilter={this.handleFilterChange}
- onLoadMore={this.onLoadMore}
- onSelectPermission={this.handlePermissionSelect}
- onQuery={this.handleQueryChange}
- query={query}
- revokePermissionFromGroup={this.revokePermissionFromGroup}
- revokePermissionFromUser={this.revokePermissionFromUser}
- selectedPermission={selectedPermission}
- users={users}
- usersPaging={usersPaging}
- permissions={permissions}
- />
- </div>
- );
- }
-}
-
-export default withComponentContext(App);
this.setState({ done: true });
}
});
- } else {
- return Promise.reject(undefined);
}
};
render() {
const header = translateWithParameters(
- 'projects_role.apply_template_to_xxx',
+ 'projects_role.apply_template_to_x',
this.props.project.name
);
*/
import * as React from 'react';
import { Button } from '../../../../components/controls/buttons';
+import DeferredSpinner from '../../../../components/ui/DeferredSpinner';
import { translate } from '../../../../helpers/l10n';
-import { isApplication, isPortfolioLike } from '../../../../types/component';
+import { ComponentQualifier, isApplication, isPortfolioLike } from '../../../../types/component';
import { Component } from '../../../../types/types';
import ApplyTemplate from './ApplyTemplate';
}
const visibilityDescription =
- component.qualifier === 'TRK' && component.visibility
+ component.qualifier === ComponentQualifier.Project && component.visibility
? translate('visibility', component.visibility, 'description', component.qualifier)
: undefined;
<header className="page-header">
<h1 className="page-title">{translate('permissions.page')}</h1>
- {this.props.loading && <i className="spinner" />}
+ <DeferredSpinner loading={this.props.loading} />
{canApplyPermissionTemplate && (
<div className="page-actions">
--- /dev/null
+/*
+ * 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 { without } from 'lodash';
+import * as React from 'react';
+import { Helmet } from 'react-helmet-async';
+import * as api from '../../../../api/permissions';
+import withComponentContext from '../../../../app/components/componentContext/withComponentContext';
+import VisibilitySelector from '../../../../components/common/VisibilitySelector';
+import { translate } from '../../../../helpers/l10n';
+import { Visibility } from '../../../../types/component';
+import { Permissions } from '../../../../types/permissions';
+import { Component, Paging, PermissionGroup, PermissionUser } from '../../../../types/types';
+import AllHoldersList from '../../shared/components/AllHoldersList';
+import { FilterOption } from '../../shared/components/SearchForm';
+import '../../styles.css';
+import { convertToPermissionDefinitions, PERMISSIONS_ORDER_BY_QUALIFIER } from '../../utils';
+import PageHeader from './PageHeader';
+import PublicProjectDisclaimer from './PublicProjectDisclaimer';
+
+interface Props {
+ component: Component;
+ onComponentChange: (changes: Partial<Component>) => void;
+}
+
+interface State {
+ disclaimer: boolean;
+ filter: FilterOption;
+ groups: PermissionGroup[];
+ groupsPaging?: Paging;
+ loading: boolean;
+ query: string;
+ selectedPermission?: string;
+ users: PermissionUser[];
+ usersPaging?: Paging;
+}
+
+export class PermissionsProjectApp extends React.PureComponent<Props, State> {
+ mounted = false;
+
+ constructor(props: Props) {
+ super(props);
+ this.state = {
+ disclaimer: false,
+ filter: 'all',
+ groups: [],
+ loading: true,
+ query: '',
+ users: [],
+ };
+ }
+
+ componentDidMount() {
+ this.mounted = true;
+ this.loadHolders();
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ loadUsersAndGroups = (userPage?: number, groupsPage?: number) => {
+ const { component } = this.props;
+ const { filter, query, selectedPermission } = this.state;
+
+ const getUsers: Promise<{ paging?: Paging; users: PermissionUser[] }> =
+ filter !== 'groups'
+ ? api.getPermissionsUsersForComponent({
+ projectKey: component.key,
+ q: query || undefined,
+ permission: selectedPermission,
+ p: userPage,
+ })
+ : Promise.resolve({ paging: undefined, users: [] });
+
+ const getGroups: Promise<{ paging?: Paging; groups: PermissionGroup[] }> =
+ filter !== 'users'
+ ? api.getPermissionsGroupsForComponent({
+ projectKey: component.key,
+ q: query || undefined,
+ permission: selectedPermission,
+ p: groupsPage,
+ })
+ : Promise.resolve({ paging: undefined, groups: [] });
+
+ return Promise.all([getUsers, getGroups]);
+ };
+
+ loadHolders = () => {
+ this.setState({ loading: true });
+ return this.loadUsersAndGroups().then(([usersResponse, groupsResponse]) => {
+ if (this.mounted) {
+ this.setState({
+ groups: groupsResponse.groups,
+ groupsPaging: groupsResponse.paging,
+ loading: false,
+ users: usersResponse.users,
+ usersPaging: usersResponse.paging,
+ });
+ }
+ }, this.stopLoading);
+ };
+
+ onLoadMore = () => {
+ const { usersPaging, groupsPaging } = this.state;
+ this.setState({ loading: true });
+ return this.loadUsersAndGroups(
+ usersPaging ? usersPaging.pageIndex + 1 : 1,
+ groupsPaging ? groupsPaging.pageIndex + 1 : 1
+ ).then(([usersResponse, groupsResponse]) => {
+ if (this.mounted) {
+ this.setState(({ groups, users }) => ({
+ groups: [...groups, ...groupsResponse.groups],
+ groupsPaging: groupsResponse.paging,
+ loading: false,
+ users: [...users, ...usersResponse.users],
+ usersPaging: usersResponse.paging,
+ }));
+ }
+ }, this.stopLoading);
+ };
+
+ handleFilterChange = (filter: FilterOption) => {
+ if (this.mounted) {
+ this.setState({ filter }, this.loadHolders);
+ }
+ };
+
+ handleQueryChange = (query: string) => {
+ if (this.mounted) {
+ this.setState({ query }, this.loadHolders);
+ }
+ };
+
+ handlePermissionSelect = (selectedPermission?: string) => {
+ if (this.mounted) {
+ this.setState(
+ (state: State) => ({
+ selectedPermission:
+ state.selectedPermission === selectedPermission ? undefined : selectedPermission,
+ }),
+ this.loadHolders
+ );
+ }
+ };
+
+ addPermissionToGroup = (group: string, permission: string) => {
+ return this.state.groups.map((candidate) =>
+ candidate.name === group
+ ? { ...candidate, permissions: [...candidate.permissions, permission] }
+ : candidate
+ );
+ };
+
+ addPermissionToUser = (user: string, permission: string) => {
+ return this.state.users.map((candidate) =>
+ candidate.login === user
+ ? { ...candidate, permissions: [...candidate.permissions, permission] }
+ : candidate
+ );
+ };
+
+ removePermissionFromGroup = (group: string, permission: string) => {
+ return this.state.groups.map((candidate) =>
+ candidate.name === group
+ ? { ...candidate, permissions: without(candidate.permissions, permission) }
+ : candidate
+ );
+ };
+
+ removePermissionFromUser = (user: string, permission: string) => {
+ return this.state.users.map((candidate) =>
+ candidate.login === user
+ ? { ...candidate, permissions: without(candidate.permissions, permission) }
+ : candidate
+ );
+ };
+
+ grantPermissionToGroup = (group: string, permission: string) => {
+ if (this.mounted) {
+ this.setState({ loading: true });
+ return api
+ .grantPermissionToGroup({
+ projectKey: this.props.component.key,
+ groupName: group,
+ permission,
+ })
+ .then(() => {
+ if (this.mounted) {
+ this.setState({
+ loading: false,
+ groups: this.addPermissionToGroup(group, permission),
+ });
+ }
+ }, this.stopLoading);
+ }
+ return Promise.resolve();
+ };
+
+ grantPermissionToUser = (user: string, permission: string) => {
+ if (this.mounted) {
+ this.setState({ loading: true });
+ return api
+ .grantPermissionToUser({
+ projectKey: this.props.component.key,
+ login: user,
+ permission,
+ })
+ .then(() => {
+ if (this.mounted) {
+ this.setState({
+ loading: false,
+ users: this.addPermissionToUser(user, permission),
+ });
+ }
+ }, this.stopLoading);
+ }
+ return Promise.resolve();
+ };
+
+ revokePermissionFromGroup = (group: string, permission: string) => {
+ if (this.mounted) {
+ this.setState({ loading: true });
+ return api
+ .revokePermissionFromGroup({
+ projectKey: this.props.component.key,
+ groupName: group,
+ permission,
+ })
+ .then(() => {
+ if (this.mounted) {
+ this.setState({
+ loading: false,
+ groups: this.removePermissionFromGroup(group, permission),
+ });
+ }
+ }, this.stopLoading);
+ }
+ return Promise.resolve();
+ };
+
+ revokePermissionFromUser = (user: string, permission: string) => {
+ if (this.mounted) {
+ this.setState({ loading: true });
+ return api
+ .revokePermissionFromUser({
+ projectKey: this.props.component.key,
+ login: user,
+ permission,
+ })
+ .then(() => {
+ if (this.mounted) {
+ this.setState({
+ loading: false,
+ users: this.removePermissionFromUser(user, permission),
+ });
+ }
+ }, this.stopLoading);
+ }
+ return Promise.resolve();
+ };
+
+ handleVisibilityChange = (visibility: string) => {
+ if (visibility === Visibility.Public) {
+ this.openDisclaimer();
+ } else {
+ this.turnProjectToPrivate();
+ }
+ };
+
+ turnProjectToPublic = () => {
+ this.setState({ loading: true });
+ return api.changeProjectVisibility(this.props.component.key, Visibility.Public).then(() => {
+ this.props.onComponentChange({ visibility: Visibility.Public });
+ this.loadHolders();
+ });
+ };
+
+ turnProjectToPrivate = () => {
+ this.setState({ loading: true });
+ return api.changeProjectVisibility(this.props.component.key, Visibility.Private).then(() => {
+ this.props.onComponentChange({ visibility: Visibility.Private });
+ this.loadHolders();
+ });
+ };
+
+ openDisclaimer = () => {
+ if (this.mounted) {
+ this.setState({ disclaimer: true });
+ }
+ };
+
+ closeDisclaimer = () => {
+ if (this.mounted) {
+ this.setState({ disclaimer: false });
+ }
+ };
+
+ stopLoading = () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ };
+
+ 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 === Visibility.Public) {
+ order = without(order, Permissions.Browse, Permissions.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={loading} />
+ <div>
+ <VisibilitySelector
+ canTurnToPrivate={canTurnToPrivate}
+ className="big-spacer-top big-spacer-bottom"
+ onChange={this.handleVisibilityChange}
+ loading={loading}
+ visibility={component.visibility}
+ />
+ {disclaimer && (
+ <PublicProjectDisclaimer
+ component={component}
+ onClose={this.closeDisclaimer}
+ onConfirm={this.turnProjectToPublic}
+ />
+ )}
+ </div>
+ <AllHoldersList
+ filter={filter}
+ grantPermissionToGroup={this.grantPermissionToGroup}
+ grantPermissionToUser={this.grantPermissionToUser}
+ groups={groups}
+ groupsPaging={groupsPaging}
+ onFilter={this.handleFilterChange}
+ onLoadMore={this.onLoadMore}
+ onSelectPermission={this.handlePermissionSelect}
+ onQuery={this.handleQueryChange}
+ query={query}
+ revokePermissionFromGroup={this.revokePermissionFromGroup}
+ revokePermissionFromUser={this.revokePermissionFromUser}
+ selectedPermission={selectedPermission}
+ users={users}
+ usersPaging={usersPaging}
+ permissions={permissions}
+ />
+ </div>
+ );
+ }
+}
+
+export default withComponentContext(PermissionsProjectApp);
+++ /dev/null
-/*
- * 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 {
- grantPermissionToGroup,
- grantPermissionToUser,
- revokePermissionFromGroup,
- revokePermissionFromUser,
-} from '../../../../../api/permissions';
-import { mockComponent } from '../../../../../helpers/mocks/component';
-import { waitAndUpdate } from '../../../../../helpers/testUtils';
-import { App } from '../App';
-
-jest.mock('../../../../../api/permissions', () => ({
- getPermissionsGroupsForComponent: jest.fn().mockResolvedValue({
- paging: { pageIndex: 1, pageSize: 100, total: 2 },
- groups: [
- {
- id: '1',
- name: 'SonarSource',
- description: 'SonarSource team',
- permissions: ['admin', 'codeviewer', 'issueadmin'],
- },
- { name: 'Anyone', permissions: [] },
- ],
- }),
- getPermissionsUsersForComponent: jest.fn().mockResolvedValue({
- paging: { pageIndex: 1, pageSize: 100, total: 3 },
- users: [
- {
- avatar: 'admin-avatar',
- email: 'admin@gmail.com',
- login: 'admin',
- name: 'Admin Admin',
- permissions: ['admin'],
- },
- {
- avatar: 'user-avatar-1',
- email: 'user1@gmail.com',
- login: 'user1',
- name: 'User Number 1',
- permissions: [],
- },
- {
- avatar: 'user-avatar-2',
- email: 'user2@gmail.com',
- login: 'user2',
- name: 'User Number 2',
- permissions: [],
- },
- ],
- }),
- grantPermissionToGroup: jest.fn().mockResolvedValue({}),
- grantPermissionToUser: jest.fn().mockResolvedValue({}),
- revokePermissionFromGroup: jest.fn().mockResolvedValue({}),
- revokePermissionFromUser: jest.fn().mockResolvedValue({}),
-}));
-
-beforeEach(() => {
- jest.clearAllMocks();
-});
-
-it('should render correctly', async () => {
- const wrapper = shallowRender();
- expect(wrapper).toMatchSnapshot();
- await waitAndUpdate(wrapper);
- expect(wrapper).toMatchSnapshot();
-});
-
-describe('should manage state correctly', () => {
- it('should handle permission select', async () => {
- const wrapper = shallowRender();
- await waitAndUpdate(wrapper);
- const instance = wrapper.instance();
- instance.handlePermissionSelect('foo');
- expect(wrapper.state('selectedPermission')).toBe('foo');
- instance.handlePermissionSelect('foo');
- expect(wrapper.state('selectedPermission')).toBeUndefined();
- });
-
- it('should add and remove permission to a group', async () => {
- const wrapper = shallowRender();
- await waitAndUpdate(wrapper);
- const instance = wrapper.instance();
- const apiPayload = {
- projectKey: 'my-project',
- groupName: 'SonarSource',
- permission: 'foo',
- };
-
- instance.grantPermissionToGroup('SonarSource', 'foo');
- const groupState = wrapper.state('groups');
- expect(groupState[0].permissions).toHaveLength(4);
- expect(groupState[0].permissions).toContain('foo');
- await waitAndUpdate(wrapper);
- expect(grantPermissionToGroup).toHaveBeenCalledWith(apiPayload);
- expect(wrapper.state('groups')).toBe(groupState);
-
- (grantPermissionToGroup as jest.Mock).mockRejectedValueOnce({});
- instance.grantPermissionToGroup('SonarSource', 'bar');
- expect(wrapper.state('groups')[0].permissions).toHaveLength(5);
- expect(wrapper.state('groups')[0].permissions).toContain('bar');
- await waitAndUpdate(wrapper);
- expect(wrapper.state('groups')[0].permissions).toHaveLength(4);
- expect(wrapper.state('groups')[0].permissions).not.toContain('bar');
-
- instance.revokePermissionFromGroup('SonarSource', 'foo');
- expect(wrapper.state('groups')[0].permissions).toHaveLength(3);
- expect(wrapper.state('groups')[0].permissions).not.toContain('foo');
- await waitAndUpdate(wrapper);
- expect(revokePermissionFromGroup).toHaveBeenCalledWith(apiPayload);
- });
-
- it('should add and remove permission to a user', async () => {
- const wrapper = shallowRender();
- await waitAndUpdate(wrapper);
- const instance = wrapper.instance();
- const apiPayload = {
- projectKey: 'my-project',
- login: 'user1',
- permission: 'foo',
- };
-
- instance.grantPermissionToUser('user1', 'foo');
- expect(wrapper.state('users')[1].permissions).toHaveLength(1);
- expect(wrapper.state('users')[1].permissions).toContain('foo');
- await waitAndUpdate(wrapper);
- expect(grantPermissionToUser).toHaveBeenCalledWith(apiPayload);
-
- instance.revokePermissionFromUser('user1', 'foo');
- expect(wrapper.state('users')[1].permissions).toHaveLength(0);
- await waitAndUpdate(wrapper);
- expect(revokePermissionFromUser).toHaveBeenCalledWith(apiPayload);
- });
-});
-
-function shallowRender(props: Partial<App['props']> = {}) {
- return shallow<App>(<App component={mockComponent()} onComponentChange={jest.fn()} {...props} />);
-}
+++ /dev/null
-/*
- * 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 { waitAndUpdate } from '../../../../../helpers/testUtils';
-import ApplyTemplate from '../ApplyTemplate';
-
-jest.mock('../../../../../api/permissions', () => ({
- getPermissionTemplates: jest.fn().mockResolvedValue({
- permissionTemplates: [
- {
- id: 'tmp1',
- name: 'SonarSource projects',
- createdAt: '2015-11-27T15:20:32+0100',
- permissions: [
- { key: 'admin', usersCount: 0, groupsCount: 3 },
- { key: 'codeviewer', usersCount: 0, groupsCount: 6 },
- ],
- },
- ],
- defaultTemplates: [{ templateId: 'tmp1', qualifier: 'TRK' }],
- permissions: [
- { key: 'admin', name: 'Administer', description: 'Administer access' },
- { key: 'codeviewer', name: 'See Source Code', description: 'View code' },
- ],
- }),
-}));
-
-it('render correctly', async () => {
- const wrapper = shallow(
- <ApplyTemplate onClose={jest.fn()} project={{ key: 'foo', name: 'Foo' }} />
- );
- expect(wrapper).toMatchSnapshot();
- await waitAndUpdate(wrapper);
- expect(wrapper.dive()).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * 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 { act, screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup';
+import * as React from 'react';
+import selectEvent from 'react-select-event';
+import { byLabelText, byRole, byText } from 'testing-library-selector';
+import PermissionsServiceMock from '../../../../../api/mocks/PermissionsServiceMock';
+import { mockComponent } from '../../../../../helpers/mocks/component';
+import { renderApp } from '../../../../../helpers/testReactTestingUtils';
+import { ComponentQualifier, Visibility } from '../../../../../types/component';
+import { Permissions } from '../../../../../types/permissions';
+import { Component } from '../../../../../types/types';
+import { PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE, PERMISSIONS_ORDER_FOR_VIEW } from '../../../utils';
+import { PermissionsProjectApp } from '../PermissionsProjectApp';
+
+let serviceMock: PermissionsServiceMock;
+beforeAll(() => {
+ serviceMock = new PermissionsServiceMock();
+});
+
+afterEach(() => {
+ serviceMock.reset();
+});
+
+describe('rendering', () => {
+ it.each([
+ [ComponentQualifier.Project, 'roles.page.description2', PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE],
+ [ComponentQualifier.Portfolio, 'roles.page.description_portfolio', PERMISSIONS_ORDER_FOR_VIEW],
+ [
+ ComponentQualifier.Application,
+ 'roles.page.description_application',
+ PERMISSIONS_ORDER_FOR_VIEW,
+ ],
+ ])('should render correctly for %s', async (qualifier, description, permissions) => {
+ const user = userEvent.setup();
+ const ui = getPageObject(user);
+ renderPermissionsProjectApp({ qualifier, visibility: Visibility.Private });
+ await ui.appLoaded();
+
+ expect(screen.getByText(description)).toBeInTheDocument();
+ permissions.forEach((permission) => {
+ expect(ui.permissionCheckbox('johndoe', permission).get()).toBeInTheDocument();
+ });
+ });
+});
+
+describe('filtering', () => {
+ it('should allow to filter permission holders', async () => {
+ const user = userEvent.setup();
+ const ui = getPageObject(user);
+ renderPermissionsProjectApp();
+ await ui.appLoaded();
+
+ expect(screen.getByText('sonar-users')).toBeInTheDocument();
+ expect(screen.getByText('johndoe')).toBeInTheDocument();
+
+ await ui.showOnlyUsers();
+ expect(screen.queryByText('sonar-users')).not.toBeInTheDocument();
+ expect(screen.getByText('johndoe')).toBeInTheDocument();
+
+ await ui.showOnlyGroups();
+ expect(screen.getByText('sonar-users')).toBeInTheDocument();
+ expect(screen.queryByText('johndoe')).not.toBeInTheDocument();
+
+ await ui.showAll();
+ expect(screen.getByText('sonar-users')).toBeInTheDocument();
+ expect(screen.getByText('johndoe')).toBeInTheDocument();
+
+ await ui.searchFor('sonar-adm');
+ expect(screen.getByText('sonar-admins')).toBeInTheDocument();
+ expect(screen.queryByText('sonar-users')).not.toBeInTheDocument();
+ expect(screen.queryByText('johndoe')).not.toBeInTheDocument();
+
+ await ui.clearSearch();
+ expect(screen.getByText('sonar-users')).toBeInTheDocument();
+ expect(screen.getByText('johndoe')).toBeInTheDocument();
+ });
+
+ it('should allow to show only permission holders with a specific permission', async () => {
+ const user = userEvent.setup();
+ const ui = getPageObject(user);
+ renderPermissionsProjectApp();
+ await ui.appLoaded();
+
+ expect(screen.getAllByRole('row').length).toBe(7);
+ await ui.toggleFilterByPermission(Permissions.Admin);
+ expect(screen.getAllByRole('row').length).toBe(2);
+ });
+});
+
+describe('assigning/revoking permissions', () => {
+ it('should allow to apply a permission template', async () => {
+ const user = userEvent.setup();
+ const ui = getPageObject(user);
+ renderPermissionsProjectApp();
+ await ui.appLoaded();
+
+ await ui.openTemplateModal();
+ expect(ui.confirmApplyTemplateBtn.get()).toBeDisabled();
+ await ui.chooseTemplate('Permission Template 2');
+ expect(ui.templateSuccessfullyApplied.get()).toBeInTheDocument();
+ await ui.closeTemplateModal();
+ expect(ui.templateSuccessfullyApplied.query()).not.toBeInTheDocument();
+ });
+
+ it('should allow to turn a public project private (and vice-versa)', async () => {
+ const user = userEvent.setup();
+ const ui = getPageObject(user);
+ renderPermissionsProjectApp();
+ await ui.appLoaded();
+
+ expect(ui.visibilityRadio(Visibility.Public).get()).toBeChecked();
+ expect(
+ ui.permissionCheckbox('sonar-users', Permissions.Browse).query()
+ ).not.toBeInTheDocument();
+ await act(async () => {
+ await ui.turnProjectPrivate();
+ });
+ expect(ui.visibilityRadio(Visibility.Private).get()).toBeChecked();
+ expect(ui.permissionCheckbox('sonar-users', Permissions.Browse).get()).toBeInTheDocument();
+
+ await ui.turnProjectPublic();
+ expect(ui.makePublicDisclaimer.get()).toBeInTheDocument();
+ await act(async () => {
+ await ui.confirmTurnProjectPublic();
+ });
+ expect(ui.visibilityRadio(Visibility.Public).get()).toBeChecked();
+ });
+
+ it('should add and remove permissions to/from a group', async () => {
+ const user = userEvent.setup();
+ const ui = getPageObject(user);
+ renderPermissionsProjectApp();
+ await ui.appLoaded();
+
+ expect(ui.permissionCheckbox('sonar-users', Permissions.Admin).get()).not.toBeChecked();
+
+ await ui.togglePermission('sonar-users', Permissions.Admin);
+ await ui.appLoaded();
+ expect(ui.permissionCheckbox('sonar-users', Permissions.Admin).get()).toBeChecked();
+
+ await ui.togglePermission('sonar-users', Permissions.Admin);
+ await ui.appLoaded();
+ expect(ui.permissionCheckbox('sonar-users', Permissions.Admin).get()).not.toBeChecked();
+ });
+
+ it('should add and remove permissions to/from a user', async () => {
+ const user = userEvent.setup();
+ const ui = getPageObject(user);
+ renderPermissionsProjectApp();
+ await ui.appLoaded();
+
+ expect(ui.permissionCheckbox('johndoe', Permissions.Scan).get()).not.toBeChecked();
+
+ await ui.togglePermission('johndoe', Permissions.Scan);
+ await ui.appLoaded();
+ expect(ui.permissionCheckbox('johndoe', Permissions.Scan).get()).toBeChecked();
+
+ await ui.togglePermission('johndoe', Permissions.Scan);
+ await ui.appLoaded();
+ expect(ui.permissionCheckbox('johndoe', Permissions.Scan).get()).not.toBeChecked();
+ });
+});
+
+function getPageObject(user: UserEvent) {
+ const ui = {
+ loading: byLabelText('loading'),
+ permissionCheckbox: (target: string, permission: Permissions) =>
+ byRole('checkbox', {
+ name: `permission.assign_x_to_y.projects_role.${permission}.${target}`,
+ }),
+ visibilityRadio: (visibility: Visibility) =>
+ byRole('radio', { name: `visibility.${visibility}` }),
+ makePublicDisclaimer: byText(
+ 'projects_role.are_you_sure_to_turn_project_to_public.warning.TRK'
+ ),
+ confirmPublicBtn: byRole('button', { name: 'projects_role.turn_project_to_public.TRK' }),
+ openModalBtn: byRole('button', { name: 'projects_role.apply_template' }),
+ closeModalBtn: byRole('button', { name: 'close' }),
+ templateSelect: byRole('combobox', { name: /template/ }),
+ templateSuccessfullyApplied: byText('projects_role.apply_template.success'),
+ confirmApplyTemplateBtn: byRole('button', { name: 'apply' }),
+ tableHeaderFilter: (permission: Permissions) =>
+ byRole('link', { name: `projects_role.${permission}` }),
+ onlyUsersBtn: byRole('button', { name: 'users.page' }),
+ onlyGroupsBtn: byRole('button', { name: 'user_groups.page' }),
+ showAllBtn: byRole('button', { name: 'all' }),
+ searchInput: byRole('searchbox', { name: 'search.search_for_users_or_groups' }),
+ };
+
+ return {
+ ...ui,
+ async appLoaded() {
+ await waitFor(() => {
+ expect(ui.loading.query()).not.toBeInTheDocument();
+ });
+ },
+ async togglePermission(target: string, permission: Permissions) {
+ await user.click(ui.permissionCheckbox(target, permission).get());
+ },
+ async turnProjectPrivate() {
+ await user.click(ui.visibilityRadio(Visibility.Private).get());
+ },
+ async turnProjectPublic() {
+ await user.click(ui.visibilityRadio(Visibility.Public).get());
+ },
+ async confirmTurnProjectPublic() {
+ await user.click(ui.confirmPublicBtn.get());
+ },
+ async openTemplateModal() {
+ await user.click(ui.openModalBtn.get());
+ },
+ async closeTemplateModal() {
+ await user.click(ui.closeModalBtn.get());
+ },
+ async chooseTemplate(name: string) {
+ await selectEvent.select(ui.templateSelect.get(), [name]);
+ await user.click(ui.confirmApplyTemplateBtn.get());
+ },
+ async toggleFilterByPermission(permission: Permissions) {
+ await user.click(ui.tableHeaderFilter(permission).get());
+ },
+ async showOnlyUsers() {
+ await user.click(ui.onlyUsersBtn.get());
+ },
+ async showOnlyGroups() {
+ await user.click(ui.onlyGroupsBtn.get());
+ },
+ async showAll() {
+ await user.click(ui.showAllBtn.get());
+ },
+ async searchFor(name: string) {
+ await user.type(ui.searchInput.get(), name);
+ },
+ async clearSearch() {
+ await user.clear(ui.searchInput.get());
+ },
+ };
+}
+
+function renderPermissionsProjectApp(override?: Partial<Component>) {
+ function App({ component }: { component: Component }) {
+ const [realComponent, setRealComponent] = React.useState(component);
+ return (
+ <PermissionsProjectApp
+ component={realComponent}
+ onComponentChange={(changes: Partial<Component>) => {
+ setRealComponent({ ...realComponent, ...changes });
+ }}
+ />
+ );
+ }
+
+ return renderApp(
+ '/',
+ <App
+ component={mockComponent({
+ visibility: Visibility.Public,
+ configuration: {
+ canUpdateProjectVisibilityToPrivate: true,
+ canApplyPermissionTemplate: true,
+ },
+ ...override,
+ })}
+ />
+ );
+}
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<div
- className="page page-limited"
- id="project-permissions-page"
->
- <Helmet
- defer={false}
- encodeSpecialCharacters={true}
- prioritizeSeoTags={false}
- title="permissions.page"
- />
- <PageHeader
- 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": [],
- }
- }
- loadHolders={[Function]}
- loading={true}
- />
- <div>
- <VisibilitySelector
- className="big-spacer-top big-spacer-bottom"
- onChange={[Function]}
- />
- </div>
- <AllHoldersList
- filter="all"
- grantPermissionToGroup={[Function]}
- grantPermissionToUser={[Function]}
- groups={[]}
- onFilter={[Function]}
- onLoadMore={[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]}
- users={[]}
- />
-</div>
-`;
-
-exports[`should render correctly 2`] = `
-<div
- className="page page-limited"
- id="project-permissions-page"
->
- <Helmet
- defer={false}
- encodeSpecialCharacters={true}
- prioritizeSeoTags={false}
- title="permissions.page"
- />
- <PageHeader
- 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": [],
- }
- }
- loadHolders={[Function]}
- loading={false}
- />
- <div>
- <VisibilitySelector
- className="big-spacer-top big-spacer-bottom"
- onChange={[Function]}
- />
- </div>
- <AllHoldersList
- filter="all"
- grantPermissionToGroup={[Function]}
- grantPermissionToUser={[Function]}
- groups={
- [
- {
- "description": "SonarSource team",
- "id": "1",
- "name": "SonarSource",
- "permissions": [
- "admin",
- "codeviewer",
- "issueadmin",
- ],
- },
- {
- "name": "Anyone",
- "permissions": [],
- },
- ]
- }
- groupsPaging={
- {
- "pageIndex": 1,
- "pageSize": 100,
- "total": 2,
- }
- }
- onFilter={[Function]}
- onLoadMore={[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]}
- users={
- [
- {
- "avatar": "admin-avatar",
- "email": "admin@gmail.com",
- "login": "admin",
- "name": "Admin Admin",
- "permissions": [
- "admin",
- ],
- },
- {
- "avatar": "user-avatar-1",
- "email": "user1@gmail.com",
- "login": "user1",
- "name": "User Number 1",
- "permissions": [],
- },
- {
- "avatar": "user-avatar-2",
- "email": "user2@gmail.com",
- "login": "user2",
- "name": "User Number 2",
- "permissions": [],
- },
- ]
- }
- usersPaging={
- {
- "pageIndex": 1,
- "pageSize": 100,
- "total": 3,
- }
- }
- />
-</div>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`render correctly 1`] = `
-<SimpleModal
- header="projects_role.apply_template_to_xxx.Foo"
- onClose={[MockFunction]}
- onSubmit={[Function]}
- size="small"
->
- <Component />
-</SimpleModal>
-`;
-
-exports[`render correctly 2`] = `
-<Modal
- contentLabel="projects_role.apply_template_to_xxx.Foo"
- onRequestClose={[MockFunction]}
- size="small"
->
- <form
- id="project-permissions-apply-template-form"
- onSubmit={[Function]}
- >
- <header
- className="modal-head"
- >
- <h2>
- projects_role.apply_template_to_xxx.Foo
- </h2>
- </header>
- <div
- className="modal-body"
- >
- <MandatoryFieldsExplanation
- className="modal-field"
- />
- <div
- className="modal-field"
- >
- <label
- htmlFor="project-permissions-template-input"
- >
- template
- <MandatoryFieldMarker />
- </label>
- <Select
- className="Select"
- id="project-permissions-template"
- inputId="project-permissions-template-input"
- onChange={[Function]}
- options={
- [
- {
- "label": "SonarSource projects",
- "value": "tmp1",
- },
- ]
- }
- value={[]}
- />
- </div>
- </div>
- <footer
- className="modal-foot"
- >
- <DeferredSpinner
- className="spacer-right"
- loading={false}
- />
- <SubmitButton
- disabled={true}
- >
- apply
- </SubmitButton>
- <ResetButtonLink
- onClick={[Function]}
- >
- cancel
- </ResetButtonLink>
- </footer>
- </form>
-</Modal>
-`;
import React from 'react';
import { Route } from 'react-router-dom';
import GlobalPermissionsApp from './global/components/App';
-import ProjectPermissionsApp from './project/components/App';
+import PermissionsProjectApp from './project/components/PermissionsProjectApp';
export const globalPermissionsRoutes = () => (
<Route path="permissions" element={<GlobalPermissionsApp />} />
);
export const projectPermissionsRoutes = () => (
- <Route path="project_roles" element={<ProjectPermissionsApp />} />
+ <Route path="project_roles" element={<PermissionsProjectApp />} />
);
return (
<PermissionCell
disabled={group.name === ANYONE && (isComponentPrivate || isAdminPermission)}
- isGroupItem={true}
key={permissionKey}
loading={this.state.loading}
onCheck={this.handleCheck}
permissions: PermissionDefinitions;
query?: string;
selectedPermission?: string;
- showPublicProjectsWarning?: boolean;
users: PermissionUser[];
}
}
render() {
- const {
- permissions,
- users,
- groups,
- loading,
- children,
- selectedPermission,
- showPublicProjectsWarning,
- } = this.props;
+ const { permissions, users, groups, loading, children, selectedPermission } = this.props;
const items = [...groups, ...users];
const [itemWithPermissions, itemWithoutPermissions] = partition(items, (item) =>
this.getItemInitialPermissionsCount(item)
onSelectPermission={this.props.onSelectPermission}
permission={permission}
selectedPermission={selectedPermission}
- showPublicProjectsWarning={showPublicProjectsWarning}
/>
))}
</tr>
import classNames from 'classnames';
import * as React from 'react';
import Checkbox from '../../../../components/controls/Checkbox';
+import { translateWithParameters } from '../../../../helpers/l10n';
import {
PermissionDefinition,
PermissionDefinitionGroup,
} from '../../../../types/types';
import { isPermissionDefinitionGroup } from '../../utils';
-interface Props {
+export interface PermissionCellProps {
disabled?: boolean;
- isGroupItem?: boolean;
loading: string[];
onCheck: (checked: boolean, permission?: string) => void;
permission: PermissionDefinition | PermissionDefinitionGroup;
selectedPermission?: string;
}
-export default class PermissionCell extends React.PureComponent<Props> {
- render() {
- const {
- disabled,
- isGroupItem,
- loading,
- onCheck,
- permission,
- permissionItem,
- selectedPermission,
- } = this.props;
-
- const tenant = `${isGroupItem ? 'group' : 'user'} '${permissionItem.name}'`;
-
- if (isPermissionDefinitionGroup(permission)) {
- return (
- <td className="text-middle">
- {permission.permissions.map((permissionDefinition) => {
- const isChecked = permissionItem.permissions.includes(permissionDefinition.key);
- const isDisabled = disabled || loading.includes(permissionDefinition.key);
- let state = isChecked ? 'checked' : 'unchecked';
-
- if (isDisabled) {
- state = 'disabled';
- }
-
- return (
- <div key={permissionDefinition.key}>
- <Checkbox
- checked={isChecked}
- disabled={isDisabled}
- id={permissionDefinition.key}
- label={`${state} permission '${permissionDefinition.name}' for ${tenant}`}
- onCheck={onCheck}
- >
- <span className="little-spacer-left">{permissionDefinition.name}</span>
- </Checkbox>
- </div>
- );
- })}
- </td>
- );
- }
-
- const isChecked = permissionItem.permissions.includes(permission.key);
- const isDisabled = disabled || loading.includes(permission.key);
- let state = isChecked ? 'checked' : 'unchecked';
-
- if (isDisabled) {
- state = 'disabled';
- }
+export default function PermissionCell(props: PermissionCellProps) {
+ const { disabled, loading, onCheck, permission, permissionItem, selectedPermission } = props;
+ if (isPermissionDefinitionGroup(permission)) {
return (
- <td
- className={classNames('permission-column text-center text-middle', {
- selected: permission.key === selectedPermission,
+ <td className="text-middle">
+ {permission.permissions.map((permissionDefinition) => {
+ const isChecked = permissionItem.permissions.includes(permissionDefinition.key);
+ const isDisabled = disabled || loading.includes(permissionDefinition.key);
+
+ return (
+ <div key={permissionDefinition.key}>
+ <Checkbox
+ checked={isChecked}
+ disabled={isDisabled}
+ id={permissionDefinition.key}
+ label={translateWithParameters(
+ 'permission.assign_x_to_y',
+ permissionDefinition.name,
+ permissionItem.name
+ )}
+ onCheck={onCheck}
+ >
+ <span className="little-spacer-left">{permissionDefinition.name}</span>
+ </Checkbox>
+ </div>
+ );
})}
- >
- <Checkbox
- checked={isChecked}
- disabled={isDisabled}
- id={permission.key}
- label={`${state} permission '${permission.name}' for ${tenant}`}
- onCheck={onCheck}
- />
</td>
);
}
+
+ const isChecked = permissionItem.permissions.includes(permission.key);
+ const isDisabled = disabled || loading.includes(permission.key);
+
+ return (
+ <td
+ className={classNames('permission-column text-center text-middle', {
+ selected: permission.key === selectedPermission,
+ })}
+ >
+ <Checkbox
+ checked={isChecked}
+ disabled={isDisabled}
+ id={permission.key}
+ label={translateWithParameters(
+ 'permission.assign_x_to_y',
+ permission.name,
+ permissionItem.name
+ )}
+ onCheck={onCheck}
+ />
+ </td>
+ );
}
import InstanceMessage from '../../../../components/common/InstanceMessage';
import HelpTooltip from '../../../../components/controls/HelpTooltip';
import Tooltip from '../../../../components/controls/Tooltip';
-import { Alert } from '../../../../components/ui/Alert';
import { translate, translateWithParameters } from '../../../../helpers/l10n';
import { PermissionDefinition, PermissionDefinitionGroup } from '../../../../types/types';
import { isPermissionDefinitionGroup } from '../../utils';
onSelectPermission?: (permission: string) => void;
permission: PermissionDefinition | PermissionDefinitionGroup;
selectedPermission?: string;
- showPublicProjectsWarning?: boolean;
}
export default class PermissionHeader extends React.PureComponent<Props> {
<br />
</React.Fragment>
));
- } else {
- if (this.props.showPublicProjectsWarning && ['user', 'codeviewer'].includes(permission.key)) {
- return (
- <div>
- <InstanceMessage message={permission.description} />
- <Alert className="spacer-top" variant="warning">
- {translate('projects_role.public_projects_warning')}
- </Alert>
- </div>
- );
- }
- return <InstanceMessage message={permission.description} />;
}
+
+ return <InstanceMessage message={permission.description} />;
};
render() {
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { translate } from '../../helpers/l10n';
+import { Permissions } from '../../types/permissions';
import { Dict, PermissionDefinition, PermissionDefinitionGroup } from '../../types/types';
export const PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE = [
- 'user',
- 'codeviewer',
- 'issueadmin',
- 'securityhotspotadmin',
- 'admin',
- 'scan',
+ Permissions.Browse,
+ Permissions.CodeViewer,
+ Permissions.IssueAdmin,
+ Permissions.SecurityHotspotAdmin,
+ Permissions.Admin,
+ Permissions.Scan,
];
export const PERMISSIONS_ORDER_GLOBAL = [
- 'admin',
- { category: 'administer', permissions: ['gateadmin', 'profileadmin'] },
- 'scan',
- { category: 'creator', permissions: ['provisioning', 'applicationcreator', 'portfoliocreator'] },
+ Permissions.Admin,
+ {
+ category: 'administer',
+ permissions: [Permissions.QualityGateAdmin, Permissions.QualityProfileAdmin],
+ },
+ Permissions.Scan,
+ {
+ category: 'creator',
+ permissions: [
+ Permissions.ProjectCreation,
+ Permissions.ApplicationCreation,
+ Permissions.PortfolioCreation,
+ ],
+ },
];
-export const PERMISSIONS_ORDER_FOR_VIEW = ['user', 'admin'];
-
-export const PERMISSIONS_ORDER_FOR_DEV = ['user', 'admin'];
+export const PERMISSIONS_ORDER_FOR_VIEW = [Permissions.Browse, Permissions.Admin];
export const PERMISSIONS_ORDER_BY_QUALIFIER: Dict<string[]> = {
TRK: PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE,
VW: PERMISSIONS_ORDER_FOR_VIEW,
SVW: PERMISSIONS_ORDER_FOR_VIEW,
APP: PERMISSIONS_ORDER_FOR_VIEW,
- DEV: PERMISSIONS_ORDER_FOR_DEV,
};
function convertToPermissionDefinition(permission: string, l10nPrefix: string) {
}
export function filterPermissions(
- permissions: Array<string | { category: string; permissions: string[] }>,
+ permissions: Array<Permissions | { category: string; permissions: Permissions[] }>,
hasApplicationsEnabled: boolean,
hasPortfoliosEnabled: boolean
) {
...permission,
permissions: permission.permissions.filter((p) => {
return (
- p === 'provisioning' ||
- (p === 'portfoliocreator' && hasPortfoliosEnabled) ||
- (p === 'applicationcreator' && hasApplicationsEnabled)
+ p === Permissions.ProjectCreation ||
+ (p === Permissions.PortfolioCreation && hasPortfoliosEnabled) ||
+ (p === Permissions.ApplicationCreation && hasApplicationsEnabled)
);
}),
};
import * as React from 'react';
import { get, save } from '../../../../helpers/storage';
import { mockAppState, mockLocation } from '../../../../helpers/testMocks';
-import { ComponentQualifier } from '../../../../types/component';
+import { ComponentQualifier, Visibility } from '../../../../types/component';
import { AllProjects, LS_PROJECTS_SORT, LS_PROJECTS_VIEW } from '../AllProjects';
jest.mock(
name: 'Foo',
qualifier: ComponentQualifier.Project,
tags: [],
- visibility: 'public',
+ visibility: Visibility.Public,
},
],
total: 0,
import PrivacyBadgeContainer from '../../../../../components/common/PrivacyBadgeContainer';
import TagsList from '../../../../../components/tags/TagsList';
import { mockCurrentUser, mockLoggedInUser } from '../../../../../helpers/testMocks';
-import { ComponentQualifier } from '../../../../../types/component';
+import { ComponentQualifier, Visibility } from '../../../../../types/component';
import { CurrentUser } from '../../../../../types/users';
import { Project } from '../../../types';
import ProjectCard from '../ProjectCard';
name: 'Foo',
qualifier: ComponentQualifier.Project,
tags: [],
- visibility: 'public',
+ visibility: Visibility.Public,
};
const USER_LOGGED_OUT = mockCurrentUser();
});
it('should display private badge', () => {
- const project: Project = { ...PROJECT, visibility: 'private' };
+ const project: Project = { ...PROJECT, visibility: Visibility.Private };
expect(shallowRender(project).find(PrivacyBadgeContainer).exists()).toBe(true);
});
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { ComponentQualifier } from '../../types/component';
-import { Dict, Visibility } from '../../types/types';
+import { ComponentQualifier, Visibility } from '../../types/component';
+import { Dict } from '../../types/types';
export interface Project {
analysisDate?: string;
import Radio from '../../components/controls/Radio';
import { Alert } from '../../components/ui/Alert';
import { translate } from '../../helpers/l10n';
-import { Visibility } from '../../types/types';
+import { Visibility } from '../../types/component';
export interface Props {
defaultVisibility: Visibility;
</header>
<div className="modal-body">
- {['public', 'private'].map((visibility) => (
+ {Object.values(Visibility).map((visibility) => (
<div className="big-spacer-bottom" key={visibility}>
<Radio
value={visibility}
import MandatoryFieldsExplanation from '../../components/ui/MandatoryFieldsExplanation';
import { translate } from '../../helpers/l10n';
import { getProjectUrl } from '../../helpers/urls';
+import { Visibility } from '../../types/component';
import { GlobalSettingKeys } from '../../types/settings';
-import { Visibility } from '../../types/types';
interface Props {
defaultProjectVisibility?: Visibility;
import * as React from 'react';
import { Button, EditButton } from '../../components/controls/buttons';
import { translate } from '../../helpers/l10n';
-import { Visibility } from '../../types/types';
+import { Visibility } from '../../types/component';
import ChangeDefaultVisibilityForm from './ChangeDefaultVisibilityForm';
export interface Props {
{visibilityForm && (
<ChangeDefaultVisibilityForm
- defaultVisibility={defaultProjectVisibility || 'public'}
+ defaultVisibility={defaultProjectVisibility || Visibility.Public}
onClose={this.closeVisiblityForm}
onConfirm={this.props.onChangeDefaultProjectVisibility}
/>
import { throwGlobalError } from '../../helpers/error';
import { translate } from '../../helpers/l10n';
import { hasGlobalPermission } from '../../helpers/users';
+import { Visibility } from '../../types/component';
import { Permissions } from '../../types/permissions';
import { SettingsKey } from '../../types/settings';
-import { Visibility } from '../../types/types';
import { LoggedInUser } from '../../types/users';
import CreateProjectForm from './CreateProjectForm';
import Header from './Header';
import QualifierIcon from '../../components/icons/QualifierIcon';
import { translate } from '../../helpers/l10n';
import { AppState } from '../../types/appstate';
-import { Visibility } from '../../types/types';
+import { Visibility } from '../../types/component';
import BulkApplyTemplateModal from './BulkApplyTemplateModal';
import DeleteModal from './DeleteModal';
renderVisibilityFilter = () => {
const options = [
{ value: 'all', label: translate('visibility.both') },
- { value: 'public', label: translate('visibility.public') },
- { value: 'private', label: translate('visibility.private') },
+ { value: Visibility.Public, label: translate('visibility.public') },
+ { value: Visibility.Private, label: translate('visibility.private') },
];
return (
<td className="thin nowrap text-middle">
import * as React from 'react';
import Radio from '../../../components/controls/Radio';
import { click } from '../../../helpers/testUtils';
+import { Visibility } from '../../../types/component';
import ChangeDefaultVisibilityForm from '../ChangeDefaultVisibilityForm';
it('closes', () => {
const wrapper = shallowRender({ onConfirm });
expect(wrapper).toMatchSnapshot();
- wrapper.find(Radio).first().props().onCheck('private');
+ wrapper.find(Radio).first().props().onCheck(Visibility.Private);
expect(wrapper).toMatchSnapshot();
click(wrapper.find('.js-confirm'));
- expect(onConfirm).toHaveBeenCalledWith('private');
+ expect(onConfirm).toHaveBeenCalledWith(Visibility.Private);
});
function shallowRender(props: Partial<ChangeDefaultVisibilityForm['props']> = {}) {
return shallow(
<ChangeDefaultVisibilityForm
- defaultVisibility="public"
+ defaultVisibility={Visibility.Public}
onClose={jest.fn()}
onConfirm={jest.fn()}
{...props}
import { shallow } from 'enzyme';
import * as React from 'react';
import { click } from '../../../helpers/testUtils';
+import { Visibility } from '../../../types/component';
import Header, { Props } from '../Header';
jest.mock('../../../helpers/system', () => ({
}));
it('renders', () => {
- expect(shallowRender()).toMatchSnapshot('default');
- expect(shallowRender({ defaultProjectVisibility: undefined })).toMatchSnapshot(
- 'undefined visibility'
- );
+ expect(shallowRender()).toMatchSnapshot('undefined visibility');
+ expect(shallowRender({ defaultProjectVisibility: Visibility.Public })).toMatchSnapshot('default');
});
it('creates project', () => {
const modalWrapper = wrapper.find('ChangeDefaultVisibilityForm');
expect(modalWrapper).toMatchSnapshot();
- modalWrapper.prop<Function>('onConfirm')('private');
- expect(onChangeDefaultProjectVisibility).toHaveBeenCalledWith('private');
+ modalWrapper.prop<Function>('onConfirm')(Visibility.Private);
+ expect(onChangeDefaultProjectVisibility).toHaveBeenCalledWith(Visibility.Private);
modalWrapper.prop<Function>('onClose')();
wrapper.update();
function shallowRender(props?: { [P in keyof Props]?: Props[P] }) {
return shallow(
<Header
- defaultProjectVisibility="public"
hasProvisionPermission={true}
onChangeDefaultProjectVisibility={jest.fn()}
onProjectCreate={jest.fn()}
import { screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { getComponents, SearchProjectsParameters } from '../../../api/components';
-import PermissionTemplateServiceMock from '../../../api/mocks/PermissionTemplateServiceMock';
+import PermissionsServiceMock from '../../../api/mocks/PermissionsServiceMock';
import { renderAppWithAdminContext } from '../../../helpers/testReactTestingUtils';
import { ComponentQualifier, Visibility } from '../../../types/component';
import routes from '../routes';
const components = mockComponents(11);
-let permissionTemplateMock: PermissionTemplateServiceMock;
+let serviceMock: PermissionsServiceMock;
beforeAll(() => {
- permissionTemplateMock = new PermissionTemplateServiceMock();
+ serviceMock = new PermissionsServiceMock();
});
afterEach(() => {
- permissionTemplateMock.reset();
+ serviceMock.reset();
});
describe('Bulk Apply', () => {
import { getValue } from '../../../api/settings';
import { mockLoggedInUser } from '../../../helpers/testMocks';
import { waitAndUpdate } from '../../../helpers/testUtils';
+import { ComponentQualifier, Visibility } from '../../../types/component';
import { ProjectManagementApp, Props } from '../ProjectManagementApp';
jest.mock('lodash', () => {
it('fetches all projects on mount', async () => {
const wrapper = shallowRender();
await waitAndUpdate(wrapper);
- expect(getComponents).toHaveBeenLastCalledWith({ ...defaultSearchParameters, qualifiers: 'TRK' });
+ expect(getComponents).toHaveBeenLastCalledWith({
+ ...defaultSearchParameters,
+ qualifiers: ComponentQualifier.Project,
+ });
expect(getValue).toHaveBeenCalled();
- expect(wrapper.state().defaultProjectVisibility).toBe('public');
+ expect(wrapper.state().defaultProjectVisibility).toBe(Visibility.Public);
});
it('selects provisioned', () => {
await waitAndUpdate(wrapper);
- expect(wrapper.state().defaultProjectVisibility).toBe('public');
- wrapper.instance().handleDefaultProjectVisibilityChange('private');
+ expect(wrapper.state().defaultProjectVisibility).toBe(Visibility.Public);
+ wrapper.instance().handleDefaultProjectVisibilityChange(Visibility.Private);
- expect(changeProjectDefaultVisibility).toHaveBeenCalledWith('private');
+ expect(changeProjectDefaultVisibility).toHaveBeenCalledWith(Visibility.Private);
await waitAndUpdate(wrapper);
- expect(wrapper.state().defaultProjectVisibility).toBe('private');
+ expect(wrapper.state().defaultProjectVisibility).toBe(Visibility.Private);
});
it('loads more', () => {
import { getComponentNavigation } from '../../../api/navigation';
import { mockLoggedInUser } from '../../../helpers/testMocks';
import { click, waitAndUpdate } from '../../../helpers/testUtils';
+import { ComponentQualifier, Visibility } from '../../../types/component';
import ProjectRowActions, { Props } from '../ProjectRowActions';
jest.mock('../../../api/navigation', () => ({
id: 'foo',
key: 'foo',
name: 'Foo',
- qualifier: 'TRK',
- visibility: 'private',
+ qualifier: ComponentQualifier.Project,
+ visibility: Visibility.Private,
}}
{...props}
/>
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { ComponentQualifier, Visibility } from '../../../types/component';
import Projects from '../Projects';
const projects = [
- { key: 'a', name: 'A', qualifier: 'TRK', visibility: 'public' },
- { key: 'b', name: 'B', qualifier: 'TRK', visibility: 'public' },
+ { key: 'a', name: 'A', qualifier: ComponentQualifier.Project, visibility: Visibility.Public },
+ { key: 'b', name: 'B', qualifier: ComponentQualifier.Project, visibility: Visibility.Public },
];
const selection = ['a'];
import * as React from 'react';
import Tooltip from '../../components/controls/Tooltip';
import { translate } from '../../helpers/l10n';
-import { Visibility } from '../../types/types';
+import { Visibility } from '../../types/component';
interface PrivacyBadgeContainerProps {
className?: string;
qualifier,
visibility,
}: PrivacyBadgeContainerProps) {
- if (visibility !== 'private') {
+ if (visibility !== Visibility.Private) {
return null;
}
import * as React from 'react';
import Radio from '../../components/controls/Radio';
import { translate } from '../../helpers/l10n';
-import { Visibility } from '../../types/types';
+import { Visibility } from '../../types/component';
-interface Props {
+export interface VisibilitySelectorProps {
canTurnToPrivate?: boolean;
className?: string;
onChange: (visibility: Visibility) => void;
showDetails?: boolean;
visibility?: Visibility;
+ loading?: boolean;
}
-export default class VisibilitySelector extends React.PureComponent<Props> {
- render() {
- return (
- <div className={classNames(this.props.className)}>
- {['public', 'private'].map((visibility) => (
- <Radio
- className={`huge-spacer-right visibility-${visibility}`}
- key={visibility}
- value={visibility}
- checked={this.props.visibility === visibility}
- onCheck={this.props.onChange}
- disabled={visibility === 'private' && !this.props.canTurnToPrivate}
- >
- <div>
- {translate('visibility', visibility)}
- {this.props.showDetails && (
- <p className="note">{translate('visibility', visibility, 'description.long')}</p>
- )}
- </div>
- </Radio>
- ))}
- </div>
- );
- }
+export default function VisibilitySelector(props: VisibilitySelectorProps) {
+ const { className, canTurnToPrivate, visibility, showDetails, loading = false } = props;
+ return (
+ <div className={classNames(className)}>
+ {Object.values(Visibility).map((v) => (
+ <Radio
+ className={`huge-spacer-right visibility-${v}`}
+ key={v}
+ value={v}
+ checked={v === visibility}
+ onCheck={props.onChange}
+ disabled={(v === Visibility.Private && !canTurnToPrivate) || loading}
+ >
+ <div>
+ {translate('visibility', v)}
+ {showDetails && (
+ <p className="note">{translate('visibility', v, 'description.long')}</p>
+ )}
+ </div>
+ </Radio>
+ ))}
+ </div>
+ );
}
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { ComponentQualifier } from '../../../types/component';
+import { ComponentQualifier, Visibility } from '../../../types/component';
import PrivacyBadge from '../PrivacyBadgeContainer';
it('renders', () => {
});
it('do not render', () => {
- expect(getWrapper({ visibility: 'public' })).toMatchSnapshot();
+ expect(getWrapper({ visibility: Visibility.Public })).toMatchSnapshot();
});
function getWrapper(props = {}) {
return shallow(
- <PrivacyBadge qualifier={ComponentQualifier.Project} visibility="private" {...props} />
+ <PrivacyBadge
+ qualifier={ComponentQualifier.Project}
+ visibility={Visibility.Private}
+ {...props}
+ />
);
}
import { shallow } from 'enzyme';
import * as React from 'react';
import Radio from '../../../components/controls/Radio';
-import VisibilitySelector from '../VisibilitySelector';
+import { Visibility } from '../../../types/component';
+import VisibilitySelector, { VisibilitySelectorProps } from '../VisibilitySelector';
it('changes visibility', () => {
const onChange = jest.fn();
const wrapper = shallowRender({ onChange });
expect(wrapper).toMatchSnapshot();
- wrapper.find(Radio).first().props().onCheck('private');
- expect(onChange).toHaveBeenCalledWith('private');
+ wrapper.find(Radio).first().props().onCheck(Visibility.Private);
+ expect(onChange).toHaveBeenCalledWith(Visibility.Private);
- wrapper.setProps({ visibility: 'private' });
+ wrapper.setProps({ visibility: Visibility.Private });
expect(wrapper).toMatchSnapshot();
- wrapper.find(Radio).first().props().onCheck('public');
- expect(onChange).toHaveBeenCalledWith('public');
+ wrapper.find(Radio).first().props().onCheck(Visibility.Public);
+ expect(onChange).toHaveBeenCalledWith(Visibility.Public);
});
it('renders disabled', () => {
expect(shallowRender({ canTurnToPrivate: false })).toMatchSnapshot();
});
-function shallowRender(props?: Partial<VisibilitySelector['props']>) {
- return shallow<VisibilitySelector>(
+function shallowRender(props?: Partial<VisibilitySelectorProps>) {
+ return shallow<VisibilitySelectorProps>(
<VisibilitySelector
className="test-classname"
canTurnToPrivate={true}
onChange={jest.fn()}
- visibility="public"
+ visibility={Visibility.Public}
{...props}
/>
);
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { PermissionGroup, PermissionUser } from '../../types/types';
+import {
+ Permission,
+ PermissionGroup,
+ PermissionTemplate,
+ PermissionTemplateGroup,
+ PermissionUser,
+} from '../../types/types';
import { mockUser } from '../testMocks';
export function mockPermissionGroup(overrides: Partial<PermissionGroup> = {}): PermissionGroup {
...overrides,
};
}
+
+export function mockPermission(override: Partial<Permission> = {}) {
+ return {
+ key: 'admin',
+ name: 'Admin',
+ description: 'Can do anything he/she wants',
+ ...override,
+ };
+}
+
+export function mockPermissionTemplateGroup(override: Partial<PermissionTemplateGroup> = {}) {
+ return {
+ groupsCount: 1,
+ usersCount: 1,
+ key: 'admin',
+ withProjectCreator: true,
+ ...override,
+ };
+}
+
+export function mockPermissionTemplate(override: Partial<PermissionTemplate> = {}) {
+ return {
+ id: 'template1',
+ name: 'Permission Template 1',
+ createdAt: '',
+ defaultFor: [],
+ permissions: [mockPermissionTemplateGroup()],
+ ...override,
+ };
+}
+
+export function mockTemplateUser(override: Partial<PermissionUser> = {}) {
+ return {
+ login: 'admin',
+ name: 'Admin Admin',
+ permissions: ['admin', 'codeviewer'],
+ ...override,
+ };
+}
+
+export function mockTemplateGroup(override: Partial<PermissionGroup> = {}) {
+ return {
+ id: 'Anyone',
+ name: 'Anyone',
+ description: 'everyone',
+ permissions: ['admin', 'codeviewer'],
+ ...override,
+ };
+}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { Project } from '../../apps/projects/types';
-import { ComponentQualifier } from '../../types/component';
+import { ComponentQualifier, Visibility } from '../../types/component';
export function mockProject(overrides: Partial<Project> = {}): Project {
return {
measures: {},
qualifier: ComponentQualifier.Project,
tags: [],
- visibility: 'public',
+ visibility: Visibility.Public,
...overrides,
};
}
Metric,
Paging,
Period,
- Permission,
- PermissionGroup,
- PermissionTemplate,
- PermissionTemplateGroup,
- PermissionUser,
ProfileInheritanceDetails,
Rule,
RuleActivation,
export function mockRuleRepository(override: Partial<RuleRepository> = {}) {
return { key: 'css', language: 'css', name: 'SonarQube', ...override };
}
-
-export function mockPermission(override: Partial<Permission> = {}) {
- return {
- key: 'admin',
- name: 'Admin',
- description: 'Can do anything he/she wants',
- ...override,
- };
-}
-
-export function mockPermissionTemplateGroup(override: Partial<PermissionTemplateGroup> = {}) {
- return {
- groupsCount: 1,
- usersCount: 1,
- key: 'admin',
- withProjectCreator: true,
- ...override,
- };
-}
-
-export function mockPermissionTemplate(override: Partial<PermissionTemplate> = {}) {
- return {
- id: 'template1',
- name: 'Permission Template 1',
- createdAt: '',
- defaultFor: [],
- permissions: [mockPermissionTemplateGroup()],
- ...override,
- };
-}
-
-export function mockTemplateUser(override: Partial<PermissionUser> = {}) {
- return {
- login: 'admin',
- name: 'Admin Admin',
- permissions: ['admin', 'codeviewer'],
- ...override,
- };
-}
-
-export function mockTemplateGroup(override: Partial<PermissionGroup> = {}) {
- return {
- id: 'Anyone',
- name: 'Anyone',
- description: 'everyone',
- permissions: ['admin', 'codeviewer'],
- ...override,
- };
-}
*/
export enum Permissions {
Admin = 'admin',
+ Browse = 'user',
ProjectCreation = 'provisioning',
ApplicationCreation = 'applicationcreator',
+ PortfolioCreation = 'portfoliocreator',
QualityGateAdmin = 'gateadmin',
+ QualityProfileAdmin = 'profileadmin',
Scan = 'scan',
+ CodeViewer = 'codeviewer',
+ IssueAdmin = 'issueadmin',
+ SecurityHotspotAdmin = 'securityhotspotadmin',
}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { RuleDescriptionSection } from '../apps/coding-rules/rule';
-import { ComponentQualifier } from './component';
+import { ComponentQualifier, Visibility } from './component';
import { MessageFormatting } from './issues';
import { UserActive, UserBase } from './users';
selected: boolean;
}
-export type Visibility = 'public' | 'private';
-
export namespace WebApi {
export interface Action {
key: string;
metric.wont_fix_issues.description=Won't fix issues
metric.wont_fix_issues.name=Won't Fix Issues
+
+#------------------------------------------------------------------------------
+#
+# PERMISSIONS
+#
+#------------------------------------------------------------------------------
+permission.assign_x_to_y=Assign permission {0} to {1}
+
+
#------------------------------------------------------------------------------
#
# GLOBAL PERMISSIONS
projects_role.scan.desc=Ability to get all settings required to perform an analysis (including the secured settings like passwords) and to push analysis results to the {instance} server.
projects_role.bulk_change=Bulk Change
projects_role.apply_template=Apply Permission Template
-projects_role.apply_template_to_xxx=Apply Permission Template To "{0}"
+projects_role.apply_template_to_x=Apply Permission Template To "{0}"
projects_role.apply_template.success=Permission template was successfully applied.
projects_role.no_projects=There are currently no results to apply the permission template to.
projects_role.turn_x_to_public=Turn "{0}" to Public
projects_role.portfoliocreator.desc=Allow to create portfolios for non system administrator.
-
#------------------------------------------------------------------------------
#
# PERMISSION TEMPLATES
permission_templates.select_to_delete=You must select at least one item
permission_templates.delete_selected=Delete all selected items
+
#------------------------------------------------------------------------------
#
# Promotion