summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2018-09-28 11:51:12 +0200
committerSonarTech <sonartech@sonarsource.com>2018-10-10 20:20:57 +0200
commit7f339fd2f1f9f3b587b0ccb1860f6f30ff7902d0 (patch)
tree331485689cfa4e5a769e63cc0d6b69798f994f38
parent5f7784e32941dcc2b3c549b74bdc084c2d6b4b2c (diff)
downloadsonarqube-7f339fd2f1f9f3b587b0ccb1860f6f30ff7902d0.tar.gz
sonarqube-7f339fd2f1f9f3b587b0ccb1860f6f30ff7902d0.zip
SONAR-11271 Add new permissions and update layout to group them
-rw-r--r--server/sonar-web/src/main/js/app/types.ts15
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/Template.js15
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.tsx78
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx51
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/project/components/AllHoldersList.tsx44
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx120
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/project/constants.tsx39
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/shared/components/GroupHolder.tsx38
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.tsx98
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/shared/components/PermissionCell.tsx74
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/shared/components/PermissionHeader.tsx88
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/shared/components/UserHolder.tsx40
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/GroupHolder-test.tsx18
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/HoldersList-test.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/PermissionCell-test.tsx89
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/UserHolder-test.tsx18
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/GroupHolder-test.tsx.snap196
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/HoldersList-test.tsx.snap239
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/PermissionCell-test.tsx.snap84
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/UserHolder-test.tsx.snap196
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/styles.css20
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/utils.ts87
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties14
23 files changed, 946 insertions, 726 deletions
diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts
index 13d556d491b..658d67ebed8 100644
--- a/server/sonar-web/src/main/js/app/types.ts
+++ b/server/sonar-web/src/main/js/app/types.ts
@@ -537,25 +537,32 @@ export enum PeriodMode {
PreviousVersion = 'previous_version'
}
-export interface Permission {
+export interface PermissionDefinition {
key: string;
name: string;
description: string;
}
+export type PermissionDefinitions = Array<PermissionDefinition | PermissionDefinitionGroup>;
+
+export interface PermissionDefinitionGroup {
+ category: string;
+ permissions: PermissionDefinition[];
+}
+
export interface PermissionGroup {
+ description?: string;
id?: string;
name: string;
- description?: string;
permissions: string[];
}
export interface PermissionUser {
+ avatar?: string;
+ email?: string;
login: string;
name: string;
- email?: string;
permissions: string[];
- avatar?: string;
}
export interface PermissionTemplate {
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/Template.js b/server/sonar-web/src/main/js/apps/permission-templates/components/Template.js
index cb0dbd6680a..9b1aee495cd 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/components/Template.js
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/Template.js
@@ -20,12 +20,14 @@
import React from 'react';
import PropTypes from 'prop-types';
import Helmet from 'react-helmet';
-import { debounce } from 'lodash';
import TemplateHeader from './TemplateHeader';
import TemplateDetails from './TemplateDetails';
import HoldersList from '../../permissions/shared/components/HoldersList';
import SearchForm from '../../permissions/shared/components/SearchForm';
-import { PERMISSIONS_ORDER_FOR_PROJECT } from '../../permissions/project/constants';
+import {
+ convertToPermissionDefinitions,
+ PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE
+} from '../../permissions/utils';
import * as api from '../../../api/permissions';
import { translate } from '../../../helpers/l10n';
@@ -165,11 +167,10 @@ export default class Template extends React.PureComponent {
};
render() {
- const permissions = PERMISSIONS_ORDER_FOR_PROJECT.map(p => ({
- key: p,
- name: translate('projects_role', p),
- description: translate('projects_role', p, 'desc')
- }));
+ const permissions = convertToPermissionDefinitions(
+ PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE,
+ 'projects_role'
+ );
const allUsers = [...this.state.users];
diff --git a/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.tsx b/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.tsx
index 85a1946055f..9adccff9343 100644
--- a/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.tsx
+++ b/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.tsx
@@ -18,36 +18,50 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { connect } from 'react-redux';
import SearchForm from '../../shared/components/SearchForm';
import HoldersList from '../../shared/components/HoldersList';
-import { translate } from '../../../../helpers/l10n';
-import { Organization, Paging, PermissionGroup, PermissionUser } from '../../../../app/types';
import ListFooter from '../../../../components/controls/ListFooter';
+import {
+ AppState,
+ Organization,
+ Paging,
+ PermissionGroup,
+ PermissionUser
+} from '../../../../app/types';
+import {
+ PERMISSIONS_ORDER_GLOBAL,
+ convertToPermissionDefinitions,
+ PERMISSIONS_ORDER_GLOBAL_GOV
+} from '../../utils';
+import { Store, getAppState } from '../../../../store/rootReducer';
-const PERMISSIONS_ORDER = ['admin', 'profileadmin', 'gateadmin', 'scan', 'provisioning'];
+interface StateProps {
+ appState: Pick<AppState, 'qualifiers'>;
+}
-interface Props {
+interface OwnProps {
filter: string;
grantPermissionToGroup: (groupName: string, permission: string) => Promise<void>;
grantPermissionToUser: (login: string, permission: string) => Promise<void>;
groups: PermissionGroup[];
- groupsPaging: Paging;
+ groupsPaging?: Paging;
loadHolders: () => void;
loading?: boolean;
- onLoadMore: (usersPageIndex: number, groupsPageIndex: number) => void;
+ onLoadMore: () => void;
onFilter: (filter: string) => void;
onSearch: (query: string) => void;
- onSelectPermission: (permission: string) => void;
organization?: Organization;
query: string;
revokePermissionFromGroup: (groupName: string, permission: string) => Promise<void>;
revokePermissionFromUser: (login: string, permission: string) => Promise<void>;
- selectedPermission?: string;
users: PermissionUser[];
- usersPaging: Paging;
+ usersPaging?: Paging;
}
-export default class AllHoldersList extends React.PureComponent<Props> {
+type Props = StateProps & OwnProps;
+
+export class AllHoldersList extends React.PureComponent<Props> {
handleToggleUser = (user: PermissionUser, permission: string) => {
const hasPermission = user.permissions.includes(permission);
if (hasPermission) {
@@ -67,38 +81,34 @@ export default class AllHoldersList extends React.PureComponent<Props> {
}
};
- handleLoadMore = () => {
- this.props.onLoadMore(
- this.props.usersPaging.pageIndex + 1,
- this.props.groupsPaging.pageIndex + 1
- );
- };
-
render() {
+ const { filter, groups, groupsPaging, users, usersPaging } = this.props;
const l10nPrefix = this.props.organization ? 'organizations_permissions' : 'global_permissions';
- const permissions = PERMISSIONS_ORDER.map(p => ({
- key: p,
- name: translate(l10nPrefix, p),
- description: translate(l10nPrefix, p, 'desc')
- }));
+ const governanceInstalled = this.props.appState.qualifiers.includes('VW');
+ const permissions = convertToPermissionDefinitions(
+ governanceInstalled ? PERMISSIONS_ORDER_GLOBAL_GOV : PERMISSIONS_ORDER_GLOBAL,
+ l10nPrefix
+ );
- const count =
- (this.props.filter !== 'users' ? this.props.groups.length : 0) +
- (this.props.filter !== 'groups' ? this.props.users.length : 0);
- const total =
- (this.props.filter !== 'users' ? this.props.groupsPaging.total : 0) +
- (this.props.filter !== 'groups' ? this.props.usersPaging.total : 0);
+ let count = 0;
+ let total = 0;
+ if (filter !== 'users') {
+ count += groups.length;
+ total += groupsPaging ? groupsPaging.total : groups.length;
+ }
+ if (filter !== 'groups') {
+ count += users.length;
+ total += usersPaging ? usersPaging.total : users.length;
+ }
return (
<>
<HoldersList
groups={this.props.groups}
loading={this.props.loading}
- onSelectPermission={this.props.onSelectPermission}
onToggleGroup={this.handleToggleGroup}
onToggleUser={this.handleToggleUser}
permissions={permissions}
- selectedPermission={this.props.selectedPermission}
users={this.props.users}>
<SearchForm
filter={this.props.filter}
@@ -107,8 +117,14 @@ export default class AllHoldersList extends React.PureComponent<Props> {
query={this.props.query}
/>
</HoldersList>
- <ListFooter count={count} loadMore={this.handleLoadMore} total={total} />
+ <ListFooter count={count} loadMore={this.props.onLoadMore} total={total} />
</>
);
}
}
+
+const mapStateToProps = (state: Store): StateProps => ({
+ appState: getAppState(state)
+});
+
+export default connect(mapStateToProps)(AllHoldersList);
diff --git a/server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx b/server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx
index fa13917f4ea..f34942f8cc2 100644
--- a/server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx
@@ -34,14 +34,13 @@ interface Props {
}
interface State {
- filter: string;
+ filter: 'all' | 'groups' | 'users';
groups: PermissionGroup[];
- groupsPaging: Paging;
+ groupsPaging?: Paging;
loading: boolean;
query: string;
- selectedPermission?: string;
users: PermissionUser[];
- usersPaging: Paging;
+ usersPaging?: Paging;
}
export class App extends React.PureComponent<Props, State> {
@@ -52,11 +51,9 @@ export class App extends React.PureComponent<Props, State> {
this.state = {
filter: 'all',
groups: [],
- groupsPaging: { pageIndex: 1, pageSize: 100, total: 0 },
loading: true,
query: '',
- users: [],
- usersPaging: { pageIndex: 1, pageSize: 100, total: 0 }
+ users: []
};
}
@@ -71,37 +68,25 @@ export class App extends React.PureComponent<Props, State> {
loadUsersAndGroups = (userPage?: number, groupsPage?: number) => {
const { organization } = this.props;
- const { filter, query, selectedPermission } = this.state;
+ const { filter, query } = this.state;
- const getUsers =
+ const getUsers: Promise<{ paging?: Paging; users: PermissionUser[] }> =
filter !== 'groups'
? api.getGlobalPermissionsUsers({
q: query || undefined,
- permission: selectedPermission,
organization: organization && organization.key,
p: userPage
})
- : Promise.resolve({
- paging: {
- pageIndex: 1,
- pageSize: 100,
- total: 0
- },
- users: []
- });
+ : Promise.resolve({ paging: undefined, users: [] });
- const getGroups =
+ const getGroups: Promise<{ paging?: Paging; groups: PermissionGroup[] }> =
filter !== 'users'
? api.getGlobalPermissionsGroups({
q: query || undefined,
- permission: selectedPermission,
organization: organization && organization.key,
p: groupsPage
})
- : Promise.resolve({
- paging: { pageIndex: 1, pageSize: 100, total: 0 },
- groups: []
- });
+ : Promise.resolve({ paging: undefined, groups: [] });
return Promise.all([getUsers, getGroups]);
};
@@ -122,10 +107,11 @@ export class App extends React.PureComponent<Props, State> {
};
onLoadMore = () => {
+ const { usersPaging, groupsPaging } = this.state;
this.setState({ loading: true });
return this.loadUsersAndGroups(
- this.state.usersPaging.pageIndex + 1,
- this.state.groupsPaging.pageIndex + 1
+ usersPaging ? usersPaging.pageIndex + 1 : 1,
+ groupsPaging ? groupsPaging.pageIndex + 1 : 1
).then(([usersResponse, groupsResponse]) => {
if (this.mounted) {
this.setState(({ groups, users }) => ({
@@ -139,7 +125,7 @@ export class App extends React.PureComponent<Props, State> {
}, this.stopLoading);
};
- onFilter = (filter: string) => {
+ onFilter = (filter: 'all' | 'groups' | 'users') => {
this.setState({ filter }, this.loadHolders);
};
@@ -147,15 +133,6 @@ export class App extends React.PureComponent<Props, State> {
this.setState({ query }, this.loadHolders);
};
- onSelectPermission = (permission: string) => {
- this.setState(
- ({ selectedPermission }) => ({
- selectedPermission: selectedPermission !== permission ? permission : undefined
- }),
- this.loadHolders
- );
- };
-
addPermissionToGroup = (groups: PermissionGroup[], group: string, permission: string) => {
return groups.map(
candidate =>
@@ -315,11 +292,9 @@ export class App extends React.PureComponent<Props, State> {
onFilter={this.onFilter}
onLoadMore={this.onLoadMore}
onSearch={this.onSearch}
- onSelectPermission={this.onSelectPermission}
query={this.state.query}
revokePermissionFromGroup={this.revokePermissionFromGroup}
revokePermissionFromUser={this.revokePermissionFromUser}
- selectedPermission={this.state.selectedPermission}
users={this.state.users}
usersPaging={this.state.usersPaging}
/>
diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/AllHoldersList.tsx b/server/sonar-web/src/main/js/apps/permissions/project/components/AllHoldersList.tsx
index ea9119ad4c3..f03354dad35 100644
--- a/server/sonar-web/src/main/js/apps/permissions/project/components/AllHoldersList.tsx
+++ b/server/sonar-web/src/main/js/apps/permissions/project/components/AllHoldersList.tsx
@@ -21,8 +21,8 @@ import * as React from 'react';
import { without } from 'lodash';
import SearchForm from '../../shared/components/SearchForm';
import HoldersList from '../../shared/components/HoldersList';
-import { translate } from '../../../../helpers/l10n';
-import { PERMISSIONS_ORDER_BY_QUALIFIER } from '../constants';
+import ListFooter from '../../../../components/controls/ListFooter';
+import { PERMISSIONS_ORDER_BY_QUALIFIER, convertToPermissionDefinitions } from '../../utils';
import {
Component,
Paging,
@@ -30,7 +30,6 @@ import {
PermissionUser,
Visibility
} from '../../../../app/types';
-import ListFooter from '../../../../components/controls/ListFooter';
interface Props {
component: Component;
@@ -38,8 +37,8 @@ interface Props {
grantPermissionToGroup: (group: string, permission: string) => Promise<void>;
grantPermissionToUser: (user: string, permission: string) => Promise<void>;
groups: PermissionGroup[];
- groupsPaging: Paging;
- onLoadMore: (usersPageIndex: number, groupsPageIndex: number) => void;
+ groupsPaging?: Paging;
+ onLoadMore: () => void;
onFilterChange: (filter: string) => void;
onPermissionSelect: (permissions?: string) => void;
onQueryChange: (query: string) => void;
@@ -48,7 +47,7 @@ interface Props {
revokePermissionFromUser: (user: string, permission: string) => Promise<void>;
selectedPermission?: string;
users: PermissionUser[];
- usersPaging: Paging;
+ usersPaging?: Paging;
visibility?: Visibility;
}
@@ -77,31 +76,24 @@ export default class AllHoldersList extends React.PureComponent<Props> {
this.props.onPermissionSelect(permission);
};
- handleLoadMore = () => {
- this.props.onLoadMore(
- this.props.usersPaging.pageIndex + 1,
- this.props.groupsPaging.pageIndex + 1
- );
- };
-
render() {
+ const { filter, groups, groupsPaging, users, usersPaging } = this.props;
let order = PERMISSIONS_ORDER_BY_QUALIFIER[this.props.component.qualifier];
if (this.props.visibility === Visibility.Public) {
order = without(order, 'user', 'codeviewer');
}
+ const permissions = convertToPermissionDefinitions(order, 'projects_role');
- const permissions = order.map(p => ({
- key: p,
- name: translate('projects_role', p),
- description: translate('projects_role', p, 'desc')
- }));
-
- const count =
- (this.props.filter !== 'users' ? this.props.groups.length : 0) +
- (this.props.filter !== 'groups' ? this.props.users.length : 0);
- const total =
- (this.props.filter !== 'users' ? this.props.groupsPaging.total : 0) +
- (this.props.filter !== 'groups' ? this.props.usersPaging.total : 0);
+ let count = 0;
+ let total = 0;
+ if (filter !== 'users') {
+ count += groups.length;
+ total += groupsPaging ? groupsPaging.total : groups.length;
+ }
+ if (filter !== 'groups') {
+ count += users.length;
+ total += usersPaging ? usersPaging.total : users.length;
+ }
return (
<>
@@ -120,7 +112,7 @@ export default class AllHoldersList extends React.PureComponent<Props> {
query={this.props.query}
/>
</HoldersList>
- <ListFooter count={count} loadMore={this.handleLoadMore} total={total} />
+ <ListFooter count={count} loadMore={this.props.onLoadMore} total={total} />
</>
);
}
diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx b/server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx
index a67598d44c6..9e9f7678116 100644
--- a/server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx
@@ -45,12 +45,12 @@ interface State {
disclaimer: boolean;
filter: string;
groups: PermissionGroup[];
- groupsPaging: Paging;
+ groupsPaging?: Paging;
loading: boolean;
query: string;
selectedPermission?: string;
users: PermissionUser[];
- usersPaging: Paging;
+ usersPaging?: Paging;
}
export default class App extends React.PureComponent<Props, State> {
@@ -62,11 +62,9 @@ export default class App extends React.PureComponent<Props, State> {
disclaimer: false,
filter: 'all',
groups: [],
- groupsPaging: { pageIndex: 1, pageSize: 100, total: 0 },
loading: true,
query: '',
- users: [],
- usersPaging: { pageIndex: 1, pageSize: 100, total: 0 }
+ users: []
};
}
@@ -85,55 +83,67 @@ export default class App extends React.PureComponent<Props, State> {
}
};
- loadHolders = (usersPageIndex?: number, groupsPageIndex?: number) => {
- if (this.mounted) {
- this.setState({ loading: true });
-
- const { component } = this.props;
- const { filter, query, selectedPermission } = this.state;
-
- const getUsers =
- filter !== 'groups'
- ? api.getPermissionsUsersForComponent({
- projectKey: component.key,
- q: query || undefined,
- permission: selectedPermission,
- organization: component.organization,
- p: usersPageIndex
- })
- : Promise.resolve({
- paging: { pageIndex: 1, pageSize: 100, total: 0 },
- users: []
- });
+ 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,
+ organization: component.organization,
+ 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,
+ organization: component.organization,
+ p: groupsPage
+ })
+ : Promise.resolve({ paging: undefined, groups: [] });
+
+ return Promise.all([getUsers, getGroups]);
+ };
- const getGroups =
- filter !== 'users'
- ? api.getPermissionsGroupsForComponent({
- projectKey: component.key,
- q: query || undefined,
- permission: selectedPermission,
- organization: component.organization,
- p: groupsPageIndex
- })
- : Promise.resolve({
- paging: { pageIndex: 1, pageSize: 100, total: 0 },
- groups: []
- });
+ 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);
+ };
- Promise.all([getUsers, getGroups]).then(responses => {
- if (this.mounted) {
- this.setState(state => ({
- loading: false,
- groups: groupsPageIndex
- ? [...state.groups, ...responses[1].groups]
- : responses[1].groups,
- groupsPaging: responses[1].paging,
- users: usersPageIndex ? [...state.users, ...responses[0].users] : responses[0].users,
- usersPaging: responses[0].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: string) => {
@@ -344,10 +354,6 @@ export default class App extends React.PureComponent<Props, State> {
}
};
- handleLoadMore = (usersPageIndex: number, groupsPageIndex: number) => {
- this.loadHolders(usersPageIndex, groupsPageIndex);
- };
-
render() {
const canTurnToPrivate =
this.props.component.configuration != null &&
@@ -389,7 +395,7 @@ export default class App extends React.PureComponent<Props, State> {
groups={this.state.groups}
groupsPaging={this.state.groupsPaging}
onFilterChange={this.handleFilterChange}
- onLoadMore={this.handleLoadMore}
+ onLoadMore={this.onLoadMore}
onPermissionSelect={this.handlePermissionSelect}
onQueryChange={this.handleQueryChange}
query={this.state.query}
diff --git a/server/sonar-web/src/main/js/apps/permissions/project/constants.tsx b/server/sonar-web/src/main/js/apps/permissions/project/constants.tsx
deleted file mode 100644
index 709fd13ab73..00000000000
--- a/server/sonar-web/src/main/js/apps/permissions/project/constants.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-export const PERMISSIONS_ORDER_FOR_PROJECT = [
- 'user',
- 'codeviewer',
- 'issueadmin',
- 'securityhotspotadmin',
- 'admin',
- 'scan'
-];
-
-export const PERMISSIONS_ORDER_FOR_VIEW = ['user', 'admin'];
-
-export const PERMISSIONS_ORDER_FOR_DEV = ['user', 'admin'];
-
-export const PERMISSIONS_ORDER_BY_QUALIFIER: { [index: string]: string[] } = {
- TRK: PERMISSIONS_ORDER_FOR_PROJECT,
- VW: PERMISSIONS_ORDER_FOR_VIEW,
- SVW: PERMISSIONS_ORDER_FOR_VIEW,
- APP: PERMISSIONS_ORDER_FOR_VIEW,
- DEV: PERMISSIONS_ORDER_FOR_DEV
-};
diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/GroupHolder.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/GroupHolder.tsx
index dac80f50c86..54b3c93a865 100644
--- a/server/sonar-web/src/main/js/apps/permissions/shared/components/GroupHolder.tsx
+++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/GroupHolder.tsx
@@ -19,16 +19,16 @@
*/
import * as React from 'react';
import { without } from 'lodash';
-import Checkbox from '../../../../components/controls/Checkbox';
+import PermissionCell from './PermissionCell';
import GroupIcon from '../../../../components/icons-components/GroupIcon';
-import { PermissionGroup } from '../../../../app/types';
+import { PermissionDefinitions, PermissionGroup } from '../../../../app/types';
+import { isPermissionDefinitionGroup } from '../../utils';
interface Props {
group: PermissionGroup;
- permissions: string[];
- selectedPermission?: string;
- permissionsOrder: string[];
onToggle: (group: PermissionGroup, permission: string) => Promise<void>;
+ permissions: PermissionDefinitions;
+ selectedPermission?: string;
}
interface State {
@@ -63,26 +63,11 @@ export default class GroupHolder extends React.PureComponent<Props, State> {
};
render() {
- const { selectedPermission } = this.props;
- const permissionCells = this.props.permissionsOrder.map(permission => (
- <td
- className="text-center text-middle"
- key={permission}
- style={{ backgroundColor: permission === selectedPermission ? '#d9edf7' : 'transparent' }}>
- <Checkbox
- checked={this.props.permissions.includes(permission)}
- disabled={this.state.loading.includes(permission)}
- id={permission}
- onCheck={this.handleCheck}
- />
- </td>
- ));
-
const { group } = this.props;
return (
<tr>
- <td className="nowrap">
+ <td className="nowrap text-middle">
<div className="display-inline-block text-middle big-spacer-right">
<GroupIcon />
</div>
@@ -95,7 +80,16 @@ export default class GroupHolder extends React.PureComponent<Props, State> {
</div>
</div>
</td>
- {permissionCells}
+ {this.props.permissions.map(permission => (
+ <PermissionCell
+ key={isPermissionDefinitionGroup(permission) ? permission.category : permission.key}
+ loading={this.state.loading}
+ onCheck={this.handleCheck}
+ permission={permission}
+ permissionItem={group}
+ selectedPermission={this.props.selectedPermission}
+ />
+ ))}
</tr>
);
}
diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.tsx
index 791c42a1760..5dd7cbd16da 100644
--- a/server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.tsx
+++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.tsx
@@ -18,20 +18,21 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { groupBy } from 'lodash';
+import { partition, sortBy } from 'lodash';
import UserHolder from './UserHolder';
import GroupHolder from './GroupHolder';
import PermissionHeader from './PermissionHeader';
import { translate } from '../../../../helpers/l10n';
-import { Permission, PermissionGroup, PermissionUser } from '../../../../app/types';
+import { PermissionGroup, PermissionUser, PermissionDefinitions } from '../../../../app/types';
+import { isPermissionDefinitionGroup } from '../../utils';
interface Props {
loading?: boolean;
groups: PermissionGroup[];
- onSelectPermission: (permission: string) => void;
+ onSelectPermission?: (permission: string) => void;
onToggleGroup: (group: PermissionGroup, permission: string) => Promise<void>;
onToggleUser: (user: PermissionUser, permission: string) => Promise<void>;
- permissions: Permission[];
+ permissions: PermissionDefinitions;
selectedPermission?: string;
showPublicProjectsWarning?: boolean;
users: PermissionUser[];
@@ -42,27 +43,6 @@ export default class HoldersList extends React.PureComponent<Props> {
return (item as PermissionUser).login !== undefined;
}
- renderTableHeader() {
- const { onSelectPermission, selectedPermission, showPublicProjectsWarning } = this.props;
- const cells = this.props.permissions.map(p => (
- <PermissionHeader
- key={p.key}
- onSelectPermission={onSelectPermission}
- permission={p}
- selectedPermission={selectedPermission}
- showPublicProjectsWarning={showPublicProjectsWarning}
- />
- ));
- return (
- <thead>
- <tr>
- <td className="nowrap bordered-bottom">{this.props.children}</td>
- {cells}
- </tr>
- </thead>
- );
- }
-
renderEmpty() {
const columns = this.props.permissions.length + 1;
return (
@@ -72,65 +52,71 @@ export default class HoldersList extends React.PureComponent<Props> {
);
}
- renderItem(item: PermissionUser | PermissionGroup, permissionsOrder: string[]) {
- return this.isPermissionUser(item)
- ? this.renderUser(item, permissionsOrder)
- : this.renderGroup(item, permissionsOrder);
- }
-
- renderUser(user: PermissionUser, permissionsOrder: string[]) {
- return (
+ renderItem(item: PermissionUser | PermissionGroup, permissions: PermissionDefinitions) {
+ return this.isPermissionUser(item) ? (
<UserHolder
- key={'user-' + user.login}
+ key={'user-' + item.login}
onToggle={this.props.onToggleUser}
- permissions={user.permissions}
- permissionsOrder={permissionsOrder}
+ permissions={permissions}
selectedPermission={this.props.selectedPermission}
- user={user}
+ user={item}
/>
- );
- }
-
- renderGroup(group: PermissionGroup, permissionsOrder: string[]) {
- return (
+ ) : (
<GroupHolder
- group={group}
- key={'group-' + group.id}
+ group={item}
+ key={'group-' + item.id}
onToggle={this.props.onToggleGroup}
- permissions={group.permissions}
- permissionsOrder={permissionsOrder}
+ permissions={permissions}
selectedPermission={this.props.selectedPermission}
/>
);
}
render() {
- const permissionsOrder = this.props.permissions.map(p => p.key);
- const items = [...this.props.users, ...this.props.groups].sort((a, b) => {
- return a.name < b.name ? -1 : 1;
+ const { permissions } = this.props;
+ const items = sortBy([...this.props.users, ...this.props.groups], item => {
+ if (this.isPermissionUser(item) && item.login === '<creator>') {
+ return 0;
+ }
+ return item.name;
});
-
- const { true: itemWithPermissions = [], false: itemWithoutPermissions = [] } = groupBy(
+ const [itemWithPermissions, itemWithoutPermissions] = partition(
items,
item => item.permissions.length > 0
);
return (
<div className="boxed-group boxed-group-inner">
<table className="data zebra permissions-table">
- {this.renderTableHeader()}
+ <thead>
+ <tr>
+ <td className="nowrap bordered-bottom">{this.props.children}</td>
+ {permissions.map(permission => (
+ <PermissionHeader
+ key={
+ isPermissionDefinitionGroup(permission) ? permission.category : permission.key
+ }
+ onSelectPermission={this.props.onSelectPermission}
+ permission={permission}
+ selectedPermission={this.props.selectedPermission}
+ showPublicProjectsWarning={this.props.showPublicProjectsWarning}
+ />
+ ))}
+ </tr>
+ </thead>
<tbody>
{items.length === 0 && !this.props.loading && this.renderEmpty()}
- {itemWithPermissions.map(item => this.renderItem(item, permissionsOrder))}
+ {itemWithPermissions.map(item => this.renderItem(item, permissions))}
{itemWithPermissions.length > 0 &&
itemWithoutPermissions.length > 0 && (
<>
<tr>
- <td className="divider" colSpan={6} />
+ <td className="divider" colSpan={20} />
</tr>
- <tr /> {/* Keep correct zebra colors in the table */}
+ <tr />
+ {/* Keep correct zebra colors in the table */}
</>
)}
- {itemWithoutPermissions.map(item => this.renderItem(item, permissionsOrder))}
+ {itemWithoutPermissions.map(item => this.renderItem(item, permissions))}
</tbody>
</table>
</div>
diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/PermissionCell.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/PermissionCell.tsx
new file mode 100644
index 00000000000..30bebc26c25
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/PermissionCell.tsx
@@ -0,0 +1,74 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import {
+ PermissionDefinition,
+ PermissionDefinitionGroup,
+ PermissionGroup,
+ PermissionUser
+} from '../../../../app/types';
+import { isPermissionDefinitionGroup } from '../../utils';
+import Checkbox from '../../../../components/controls/Checkbox';
+
+interface Props {
+ loading: string[];
+ onCheck: (checked: boolean, permission?: string) => void;
+ permission: PermissionDefinition | PermissionDefinitionGroup;
+ permissionItem: PermissionGroup | PermissionUser;
+ selectedPermission?: string;
+}
+
+export default class PermissionCell extends React.PureComponent<Props> {
+ render() {
+ const { loading, onCheck, permission, permissionItem, selectedPermission } = this.props;
+ if (isPermissionDefinitionGroup(permission)) {
+ return (
+ <td className="text-middle">
+ {permission.permissions.map(permission => (
+ <div key={permission.key}>
+ <Checkbox
+ checked={permissionItem.permissions.includes(permission.key)}
+ disabled={loading.includes(permission.key)}
+ id={permission.key}
+ onCheck={onCheck}>
+ <span className="little-spacer-left">{permission.name}</span>
+ </Checkbox>
+ </div>
+ ))}
+ </td>
+ );
+ } else {
+ return (
+ <td
+ className={classNames('permission-column text-center text-middle', {
+ selected: permission.key === selectedPermission
+ })}>
+ <Checkbox
+ checked={permissionItem.permissions.includes(permission.key)}
+ disabled={loading.includes(permission.key)}
+ id={permission.key}
+ onCheck={onCheck}
+ />
+ </td>
+ );
+ }
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/PermissionHeader.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/PermissionHeader.tsx
index a353a5fe549..35fd48be2ff 100644
--- a/server/sonar-web/src/main/js/apps/permissions/shared/components/PermissionHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/PermissionHeader.tsx
@@ -18,15 +18,17 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import * as classNames from 'classnames';
import HelpTooltip from '../../../../components/controls/HelpTooltip';
import InstanceMessage from '../../../../components/common/InstanceMessage';
-import Tooltip from '../../../../components/controls/Tooltip';
import { translate, translateWithParameters } from '../../../../helpers/l10n';
-import { Permission } from '../../../../app/types';
+import { PermissionDefinition, PermissionDefinitionGroup } from '../../../../app/types';
+import { isPermissionDefinitionGroup } from '../../utils';
+import Tooltip from '../../../../components/controls/Tooltip';
interface Props {
- onSelectPermission: (permission: string) => void;
- permission: Permission;
+ onSelectPermission?: (permission: string) => void;
+ permission: PermissionDefinition | PermissionDefinitionGroup;
selectedPermission?: string;
showPublicProjectsWarning?: boolean;
}
@@ -35,42 +37,68 @@ export default class PermissionHeader extends React.PureComponent<Props> {
handlePermissionClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
event.preventDefault();
event.currentTarget.blur();
- this.props.onSelectPermission(this.props.permission.key);
+ const { permission } = this.props;
+ if (this.props.onSelectPermission && !isPermissionDefinitionGroup(permission)) {
+ this.props.onSelectPermission(permission.key);
+ }
};
- renderTooltip = (permission: Permission) => {
- if (this.props.showPublicProjectsWarning && ['user', 'codeviewer'].includes(permission.key)) {
- return (
- <div>
- <InstanceMessage message={permission.description} />
- <div className="alert alert-warning spacer-top">
- {translate('projects_role.public_projects_warning')}
+ getTooltipOverlay = () => {
+ const { permission } = this.props;
+
+ if (isPermissionDefinitionGroup(permission)) {
+ return permission.permissions.map(permission => (
+ <>
+ <b className="little-spacer-right">{permission.name}:</b>
+ <InstanceMessage key={permission.key} message={permission.description} />
+ <br />
+ </>
+ ));
+ } else {
+ if (this.props.showPublicProjectsWarning && ['user', 'codeviewer'].includes(permission.key)) {
+ return (
+ <div>
+ <InstanceMessage message={permission.description} />
+ <div className="alert alert-warning spacer-top">
+ {translate('projects_role.public_projects_warning')}
+ </div>
</div>
- </div>
- );
+ );
+ }
+ return <InstanceMessage message={permission.description} />;
}
- return <InstanceMessage message={permission.description} />;
};
render() {
- const { permission, selectedPermission } = this.props;
+ const { onSelectPermission, permission } = this.props;
+ let name;
+ if (isPermissionDefinitionGroup(permission)) {
+ name = translate('global_permissions', permission.category);
+ } else {
+ name = onSelectPermission ? (
+ <Tooltip
+ overlay={translateWithParameters(
+ 'global_permissions.filter_by_x_permission',
+ permission.name
+ )}>
+ <a href="#" onClick={this.handlePermissionClick}>
+ {permission.name}
+ </a>
+ </Tooltip>
+ ) : (
+ permission.name
+ );
+ }
return (
<th
- className="permission-column text-center"
- style={{
- backgroundColor: permission.key === selectedPermission ? '#d9edf7' : 'transparent'
- }}>
+ className={classNames('permission-column text-center text-middle', {
+ selected:
+ !isPermissionDefinitionGroup(permission) &&
+ permission.key === this.props.selectedPermission
+ })}>
<div className="permission-column-inner">
- <Tooltip
- overlay={translateWithParameters(
- 'global_permissions.filter_by_x_permission',
- permission.name
- )}>
- <a className="text-middle" href="#" onClick={this.handlePermissionClick}>
- {permission.name}
- </a>
- </Tooltip>
- <HelpTooltip className="spacer-left" overlay={this.renderTooltip(permission)} />
+ {name}
+ <HelpTooltip className="spacer-left" overlay={this.getTooltipOverlay()} />
</div>
</th>
);
diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/UserHolder.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/UserHolder.tsx
index e3a775b02b5..728fe12cd98 100644
--- a/server/sonar-web/src/main/js/apps/permissions/shared/components/UserHolder.tsx
+++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/UserHolder.tsx
@@ -19,17 +19,17 @@
*/
import * as React from 'react';
import { without } from 'lodash';
+import PermissionCell from './PermissionCell';
import Avatar from '../../../../components/ui/Avatar';
-import Checkbox from '../../../../components/controls/Checkbox';
import { translate } from '../../../../helpers/l10n';
-import { PermissionUser } from '../../../../app/types';
+import { PermissionDefinitions, PermissionUser } from '../../../../app/types';
+import { isPermissionDefinitionGroup } from '../../utils';
interface Props {
- user: PermissionUser;
- permissions: string[];
- selectedPermission?: string;
- permissionsOrder: string[];
onToggle: (user: PermissionUser, permission: string) => Promise<void>;
+ permissions: PermissionDefinitions;
+ selectedPermission?: string;
+ user: PermissionUser;
}
interface State {
@@ -64,26 +64,22 @@ export default class UserHolder extends React.PureComponent<Props, State> {
};
render() {
- const { selectedPermission } = this.props;
- const permissionCells = this.props.permissionsOrder.map(permission => (
- <td
- className="text-center text-middle"
- key={permission}
- style={{ backgroundColor: permission === selectedPermission ? '#d9edf7' : 'transparent' }}>
- <Checkbox
- checked={this.props.permissions.includes(permission)}
- disabled={this.state.loading.includes(permission)}
- id={permission}
- onCheck={this.handleCheck}
- />
- </td>
+ const { user } = this.props;
+ const permissionCells = this.props.permissions.map(permission => (
+ <PermissionCell
+ key={isPermissionDefinitionGroup(permission) ? permission.category : permission.key}
+ loading={this.state.loading}
+ onCheck={this.handleCheck}
+ permission={permission}
+ permissionItem={user}
+ selectedPermission={this.props.selectedPermission}
+ />
));
- const { user } = this.props;
if (user.login === '<creator>') {
return (
<tr>
- <td className="nowrap">
+ <td className="nowrap text-middle">
<div className="display-inline-block text-middle">
<div>
<strong>{user.name}</strong>
@@ -100,7 +96,7 @@ export default class UserHolder extends React.PureComponent<Props, State> {
return (
<tr>
- <td className="nowrap">
+ <td className="nowrap text-middle">
<Avatar
className="text-middle big-spacer-right"
hash={user.avatar}
diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/GroupHolder-test.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/GroupHolder-test.tsx
index ce27bced242..9776e533260 100644
--- a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/GroupHolder-test.tsx
+++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/GroupHolder-test.tsx
@@ -33,29 +33,35 @@ const groupHolder = (
group={group}
key="foo"
onToggle={jest.fn(() => Promise.resolve())}
- permissions={['bar']}
- permissionsOrder={['bar', 'baz']}
+ permissions={[
+ {
+ category: 'admin',
+ permissions: [
+ { key: 'foo', name: 'Foo', description: '' },
+ { key: 'bar', name: 'Bar', description: '' }
+ ]
+ },
+ { key: 'baz', name: 'Baz', description: '' }
+ ]}
selectedPermission={'bar'}
/>
);
-it('should display checkboxes for permissions', () => {
+it('should render correctly', () => {
expect(shallow(groupHolder)).toMatchSnapshot();
});
-it('should disabled checkboxes when waiting for promise to return', async () => {
+it('should disabled PermissionCell checkboxes when waiting for promise to return', async () => {
const wrapper = shallow(groupHolder);
expect(wrapper.state().loading).toEqual([]);
(wrapper.instance() as GroupHolder).handleCheck(true, 'baz');
wrapper.update();
expect(wrapper.state().loading).toEqual(['baz']);
- expect(wrapper).toMatchSnapshot();
(wrapper.instance() as GroupHolder).handleCheck(true, 'bar');
wrapper.update();
expect(wrapper.state().loading).toEqual(['baz', 'bar']);
- expect(wrapper).toMatchSnapshot();
await waitAndUpdate(wrapper);
expect(wrapper.state().loading).toEqual([]);
diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/HoldersList-test.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/HoldersList-test.tsx
index 05def6fb2c7..e8e20d6bc31 100644
--- a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/HoldersList-test.tsx
+++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/HoldersList-test.tsx
@@ -21,7 +21,16 @@ import * as React from 'react';
import { shallow } from 'enzyme';
import HoldersList from '../HoldersList';
-const permissions = [{ key: 'bar', name: 'bar', description: 'foo' }];
+const permissions = [
+ { key: 'foo', name: 'Foo', description: '' },
+ {
+ category: 'admin',
+ permissions: [
+ { key: 'bar', name: 'Bar', description: '' },
+ { key: 'baz', name: 'Baz', description: '' }
+ ]
+ }
+];
const groups = [
{ id: 'foobar', name: 'Foobar', permissions: ['bar'] },
diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/PermissionCell-test.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/PermissionCell-test.tsx
new file mode 100644
index 00000000000..1abf375540e
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/PermissionCell-test.tsx
@@ -0,0 +1,89 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import PermissionCell from '../PermissionCell';
+
+const permissionItem = {
+ id: 'foobar',
+ name: 'Foobar',
+ permissions: ['bar']
+};
+
+const permission = { key: 'baz', name: 'Baz', description: '' };
+const permissionGroup = {
+ category: 'admin',
+ permissions: [
+ { key: 'foo', name: 'Foo', description: '' },
+ { key: 'bar', name: 'Bar', description: '' }
+ ]
+};
+it('should display unchecked checkbox', () => {
+ expect(
+ shallow(
+ <PermissionCell
+ loading={[]}
+ onCheck={jest.fn()}
+ permission={permission}
+ permissionItem={permissionItem}
+ />
+ )
+ ).toMatchSnapshot();
+});
+
+it('should display multiple checkboxes with one checked', () => {
+ expect(
+ shallow(
+ <PermissionCell
+ loading={[]}
+ onCheck={jest.fn()}
+ permission={permissionGroup}
+ permissionItem={permissionItem}
+ />
+ )
+ ).toMatchSnapshot();
+});
+
+it('should display disabled checkbox', () => {
+ expect(
+ shallow(
+ <PermissionCell
+ loading={['baz']}
+ onCheck={jest.fn()}
+ permission={permission}
+ permissionItem={permissionItem}
+ />
+ )
+ ).toMatchSnapshot();
+});
+
+it('should display selected checkbox', () => {
+ expect(
+ shallow(
+ <PermissionCell
+ loading={[]}
+ onCheck={jest.fn()}
+ permission={permission}
+ permissionItem={permissionItem}
+ selectedPermission="baz"
+ />
+ )
+ ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/UserHolder-test.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/UserHolder-test.tsx
index f3164e608dd..c04db2be6b0 100644
--- a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/UserHolder-test.tsx
+++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/UserHolder-test.tsx
@@ -32,30 +32,36 @@ const userHolder = (
<UserHolder
key="foo"
onToggle={jest.fn(() => Promise.resolve())}
- permissions={['bar']}
- permissionsOrder={['bar', 'baz']}
+ permissions={[
+ {
+ category: 'admin',
+ permissions: [
+ { key: 'foo', name: 'Foo', description: '' },
+ { key: 'bar', name: 'Bar', description: '' }
+ ]
+ },
+ { key: 'baz', name: 'Baz', description: '' }
+ ]}
selectedPermission={'bar'}
user={user}
/>
);
-it('should display checkboxes for permissions', () => {
+it('should render correctly', () => {
expect(shallow(userHolder)).toMatchSnapshot();
});
-it('should disabled checkboxes when waiting for promise to return', async () => {
+it('should disabled PermissionCell checkboxes when waiting for promise to return', async () => {
const wrapper = shallow(userHolder);
expect(wrapper.state().loading).toEqual([]);
(wrapper.instance() as UserHolder).handleCheck(true, 'baz');
wrapper.update();
expect(wrapper.state().loading).toEqual(['baz']);
- expect(wrapper).toMatchSnapshot();
(wrapper.instance() as UserHolder).handleCheck(true, 'bar');
wrapper.update();
expect(wrapper.state().loading).toEqual(['baz', 'bar']);
- expect(wrapper).toMatchSnapshot();
await waitAndUpdate(wrapper);
expect(wrapper.state().loading).toEqual([]);
diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/GroupHolder-test.tsx.snap b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/GroupHolder-test.tsx.snap
index 170c88a26a3..25b18f84153 100644
--- a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/GroupHolder-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/GroupHolder-test.tsx.snap
@@ -1,9 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should disabled checkboxes when waiting for promise to return 1`] = `
+exports[`should render correctly 1`] = `
<tr>
<td
- className="nowrap"
+ className="nowrap text-middle"
>
<div
className="display-inline-block text-middle big-spacer-right"
@@ -28,169 +28,59 @@ exports[`should disabled checkboxes when waiting for promise to return 1`] = `
/>
</div>
</td>
- <td
- className="text-center text-middle"
- key="bar"
- style={
+ <PermissionCell
+ key="admin"
+ loading={Array []}
+ onCheck={[Function]}
+ permission={
Object {
- "backgroundColor": "#d9edf7",
- }
- }
- >
- <Checkbox
- checked={true}
- disabled={false}
- id="bar"
- onCheck={[Function]}
- thirdState={false}
- />
- </td>
- <td
- className="text-center text-middle"
- key="baz"
- style={
- Object {
- "backgroundColor": "transparent",
- }
- }
- >
- <Checkbox
- checked={false}
- disabled={true}
- id="baz"
- onCheck={[Function]}
- thirdState={false}
- />
- </td>
-</tr>
-`;
-
-exports[`should disabled checkboxes when waiting for promise to return 2`] = `
-<tr>
- <td
- className="nowrap"
- >
- <div
- className="display-inline-block text-middle big-spacer-right"
- >
- <GroupIcon />
- </div>
- <div
- className="display-inline-block text-middle"
- >
- <div>
- <strong>
- Foobar
- </strong>
- </div>
- <div
- className="little-spacer-top"
- style={
+ "category": "admin",
+ "permissions": Array [
Object {
- "whiteSpace": "normal",
- }
- }
- />
- </div>
- </td>
- <td
- className="text-center text-middle"
- key="bar"
- style={
- Object {
- "backgroundColor": "#d9edf7",
+ "description": "",
+ "key": "foo",
+ "name": "Foo",
+ },
+ Object {
+ "description": "",
+ "key": "bar",
+ "name": "Bar",
+ },
+ ],
}
}
- >
- <Checkbox
- checked={true}
- disabled={true}
- id="bar"
- onCheck={[Function]}
- thirdState={false}
- />
- </td>
- <td
- className="text-center text-middle"
- key="baz"
- style={
+ permissionItem={
Object {
- "backgroundColor": "transparent",
+ "id": "foobar",
+ "name": "Foobar",
+ "permissions": Array [
+ "bar",
+ ],
}
}
- >
- <Checkbox
- checked={false}
- disabled={true}
- id="baz"
- onCheck={[Function]}
- thirdState={false}
- />
- </td>
-</tr>
-`;
-
-exports[`should display checkboxes for permissions 1`] = `
-<tr>
- <td
- className="nowrap"
- >
- <div
- className="display-inline-block text-middle big-spacer-right"
- >
- <GroupIcon />
- </div>
- <div
- className="display-inline-block text-middle"
- >
- <div>
- <strong>
- Foobar
- </strong>
- </div>
- <div
- className="little-spacer-top"
- style={
- Object {
- "whiteSpace": "normal",
- }
- }
- />
- </div>
- </td>
- <td
- className="text-center text-middle"
- key="bar"
- style={
+ selectedPermission="bar"
+ />
+ <PermissionCell
+ key="baz"
+ loading={Array []}
+ onCheck={[Function]}
+ permission={
Object {
- "backgroundColor": "#d9edf7",
+ "description": "",
+ "key": "baz",
+ "name": "Baz",
}
}
- >
- <Checkbox
- checked={true}
- disabled={false}
- id="bar"
- onCheck={[Function]}
- thirdState={false}
- />
- </td>
- <td
- className="text-center text-middle"
- key="baz"
- style={
+ permissionItem={
Object {
- "backgroundColor": "transparent",
+ "id": "foobar",
+ "name": "Foobar",
+ "permissions": Array [
+ "bar",
+ ],
}
}
- >
- <Checkbox
- checked={false}
- disabled={false}
- id="baz"
- onCheck={[Function]}
- thirdState={false}
- />
- </td>
+ selectedPermission="bar"
+ />
</tr>
`;
diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/HoldersList-test.tsx.snap b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/HoldersList-test.tsx.snap
index 811717f06f4..df11bc04eb6 100644
--- a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/HoldersList-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/HoldersList-test.tsx.snap
@@ -13,13 +13,35 @@ exports[`should display users and groups 1`] = `
className="nowrap bordered-bottom"
/>
<PermissionHeader
- key="bar"
+ key="foo"
onSelectPermission={[MockFunction]}
permission={
Object {
- "description": "foo",
- "key": "bar",
- "name": "bar",
+ "description": "",
+ "key": "foo",
+ "name": "Foo",
+ }
+ }
+ selectedPermission="bar"
+ />
+ <PermissionHeader
+ key="admin"
+ onSelectPermission={[MockFunction]}
+ permission={
+ Object {
+ "category": "admin",
+ "permissions": Array [
+ Object {
+ "description": "",
+ "key": "bar",
+ "name": "Bar",
+ },
+ Object {
+ "description": "",
+ "key": "baz",
+ "name": "Baz",
+ },
+ ],
}
}
selectedPermission="bar"
@@ -27,41 +49,31 @@ exports[`should display users and groups 1`] = `
</tr>
</thead>
<tbody>
- <GroupHolder
- group={
- Object {
- "id": "barbaz",
- "name": "Barbaz",
- "permissions": Array [
- "bar",
- ],
- }
- }
- key="group-barbaz"
- onToggle={[MockFunction]}
- permissions={
- Array [
- "bar",
- ]
- }
- permissionsOrder={
- Array [
- "bar",
- ]
- }
- selectedPermission="bar"
- />
<UserHolder
key="user-barbaz"
onToggle={[MockFunction]}
permissions={
Array [
- "bar",
- ]
- }
- permissionsOrder={
- Array [
- "bar",
+ Object {
+ "description": "",
+ "key": "foo",
+ "name": "Foo",
+ },
+ Object {
+ "category": "admin",
+ "permissions": Array [
+ Object {
+ "description": "",
+ "key": "bar",
+ "name": "Bar",
+ },
+ Object {
+ "description": "",
+ "key": "baz",
+ "name": "Baz",
+ },
+ ],
+ },
]
}
selectedPermission="bar"
@@ -78,23 +90,37 @@ exports[`should display users and groups 1`] = `
<GroupHolder
group={
Object {
- "id": "foobar",
- "name": "Foobar",
+ "id": "barbaz",
+ "name": "Barbaz",
"permissions": Array [
"bar",
],
}
}
- key="group-foobar"
+ key="group-barbaz"
onToggle={[MockFunction]}
permissions={
Array [
- "bar",
- ]
- }
- permissionsOrder={
- Array [
- "bar",
+ Object {
+ "description": "",
+ "key": "foo",
+ "name": "Foo",
+ },
+ Object {
+ "category": "admin",
+ "permissions": Array [
+ Object {
+ "description": "",
+ "key": "bar",
+ "name": "Bar",
+ },
+ Object {
+ "description": "",
+ "key": "baz",
+ "name": "Baz",
+ },
+ ],
+ },
]
}
selectedPermission="bar"
@@ -104,12 +130,26 @@ exports[`should display users and groups 1`] = `
onToggle={[MockFunction]}
permissions={
Array [
- "bar",
- ]
- }
- permissionsOrder={
- Array [
- "bar",
+ Object {
+ "description": "",
+ "key": "foo",
+ "name": "Foo",
+ },
+ Object {
+ "category": "admin",
+ "permissions": Array [
+ Object {
+ "description": "",
+ "key": "bar",
+ "name": "Bar",
+ },
+ Object {
+ "description": "",
+ "key": "baz",
+ "name": "Baz",
+ },
+ ],
+ },
]
}
selectedPermission="bar"
@@ -123,12 +163,53 @@ exports[`should display users and groups 1`] = `
}
}
/>
- <tr>
- <td
- className="divider"
- colSpan={6}
- />
- </tr>
+ <GroupHolder
+ group={
+ Object {
+ "id": "foobar",
+ "name": "Foobar",
+ "permissions": Array [
+ "bar",
+ ],
+ }
+ }
+ key="group-foobar"
+ onToggle={[MockFunction]}
+ permissions={
+ Array [
+ Object {
+ "description": "",
+ "key": "foo",
+ "name": "Foo",
+ },
+ Object {
+ "category": "admin",
+ "permissions": Array [
+ Object {
+ "description": "",
+ "key": "bar",
+ "name": "Bar",
+ },
+ Object {
+ "description": "",
+ "key": "baz",
+ "name": "Baz",
+ },
+ ],
+ },
+ ]
+ }
+ selectedPermission="bar"
+ />
+ <React.Fragment>
+ <tr>
+ <td
+ className="divider"
+ colSpan={20}
+ />
+ </tr>
+ <tr />
+ </React.Fragment>
<GroupHolder
group={
Object {
@@ -139,10 +220,28 @@ exports[`should display users and groups 1`] = `
}
key="group-abc"
onToggle={[MockFunction]}
- permissions={Array []}
- permissionsOrder={
+ permissions={
Array [
- "bar",
+ Object {
+ "description": "",
+ "key": "foo",
+ "name": "Foo",
+ },
+ Object {
+ "category": "admin",
+ "permissions": Array [
+ Object {
+ "description": "",
+ "key": "bar",
+ "name": "Bar",
+ },
+ Object {
+ "description": "",
+ "key": "baz",
+ "name": "Baz",
+ },
+ ],
+ },
]
}
selectedPermission="bar"
@@ -150,10 +249,28 @@ exports[`should display users and groups 1`] = `
<UserHolder
key="user-bcd"
onToggle={[MockFunction]}
- permissions={Array []}
- permissionsOrder={
+ permissions={
Array [
- "bar",
+ Object {
+ "description": "",
+ "key": "foo",
+ "name": "Foo",
+ },
+ Object {
+ "category": "admin",
+ "permissions": Array [
+ Object {
+ "description": "",
+ "key": "bar",
+ "name": "Bar",
+ },
+ Object {
+ "description": "",
+ "key": "baz",
+ "name": "Baz",
+ },
+ ],
+ },
]
}
selectedPermission="bar"
diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/PermissionCell-test.tsx.snap b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/PermissionCell-test.tsx.snap
new file mode 100644
index 00000000000..2972dee2385
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/PermissionCell-test.tsx.snap
@@ -0,0 +1,84 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display disabled checkbox 1`] = `
+<td
+ className="permission-column text-center text-middle"
+>
+ <Checkbox
+ checked={false}
+ disabled={true}
+ id="baz"
+ onCheck={[MockFunction]}
+ thirdState={false}
+ />
+</td>
+`;
+
+exports[`should display multiple checkboxes with one checked 1`] = `
+<td
+ className="text-middle"
+>
+ <div
+ key="foo"
+ >
+ <Checkbox
+ checked={false}
+ disabled={false}
+ id="foo"
+ onCheck={[MockFunction]}
+ thirdState={false}
+ >
+ <span
+ className="little-spacer-left"
+ >
+ Foo
+ </span>
+ </Checkbox>
+ </div>
+ <div
+ key="bar"
+ >
+ <Checkbox
+ checked={true}
+ disabled={false}
+ id="bar"
+ onCheck={[MockFunction]}
+ thirdState={false}
+ >
+ <span
+ className="little-spacer-left"
+ >
+ Bar
+ </span>
+ </Checkbox>
+ </div>
+</td>
+`;
+
+exports[`should display selected checkbox 1`] = `
+<td
+ className="permission-column text-center text-middle selected"
+>
+ <Checkbox
+ checked={false}
+ disabled={false}
+ id="baz"
+ onCheck={[MockFunction]}
+ thirdState={false}
+ />
+</td>
+`;
+
+exports[`should display unchecked checkbox 1`] = `
+<td
+ className="permission-column text-center text-middle"
+>
+ <Checkbox
+ checked={false}
+ disabled={false}
+ id="baz"
+ onCheck={[MockFunction]}
+ thirdState={false}
+ />
+</td>
+`;
diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/UserHolder-test.tsx.snap b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/UserHolder-test.tsx.snap
index 6288d3a1f77..1adb2bb5f86 100644
--- a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/UserHolder-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/UserHolder-test.tsx.snap
@@ -1,9 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should disabled checkboxes when waiting for promise to return 1`] = `
+exports[`should render correctly 1`] = `
<tr>
<td
- className="nowrap"
+ className="nowrap text-middle"
>
<Connect(Avatar)
className="text-middle big-spacer-right"
@@ -28,169 +28,59 @@ exports[`should disabled checkboxes when waiting for promise to return 1`] = `
/>
</div>
</td>
- <td
- className="text-center text-middle"
- key="bar"
- style={
+ <PermissionCell
+ key="admin"
+ loading={Array []}
+ onCheck={[Function]}
+ permission={
Object {
- "backgroundColor": "#d9edf7",
+ "category": "admin",
+ "permissions": Array [
+ Object {
+ "description": "",
+ "key": "foo",
+ "name": "Foo",
+ },
+ Object {
+ "description": "",
+ "key": "bar",
+ "name": "Bar",
+ },
+ ],
}
}
- >
- <Checkbox
- checked={true}
- disabled={false}
- id="bar"
- onCheck={[Function]}
- thirdState={false}
- />
- </td>
- <td
- className="text-center text-middle"
- key="baz"
- style={
+ permissionItem={
Object {
- "backgroundColor": "transparent",
+ "login": "john doe",
+ "name": "John Doe",
+ "permissions": Array [
+ "bar",
+ ],
}
}
- >
- <Checkbox
- checked={false}
- disabled={true}
- id="baz"
- onCheck={[Function]}
- thirdState={false}
- />
- </td>
-</tr>
-`;
-
-exports[`should disabled checkboxes when waiting for promise to return 2`] = `
-<tr>
- <td
- className="nowrap"
- >
- <Connect(Avatar)
- className="text-middle big-spacer-right"
- name="John Doe"
- size={36}
- />
- <div
- className="display-inline-block text-middle"
- >
- <div>
- <strong>
- John Doe
- </strong>
- <span
- className="note spacer-left"
- >
- john doe
- </span>
- </div>
- <div
- className="little-spacer-top"
- />
- </div>
- </td>
- <td
- className="text-center text-middle"
- key="bar"
- style={
- Object {
- "backgroundColor": "#d9edf7",
- }
- }
- >
- <Checkbox
- checked={true}
- disabled={true}
- id="bar"
- onCheck={[Function]}
- thirdState={false}
- />
- </td>
- <td
- className="text-center text-middle"
+ selectedPermission="bar"
+ />
+ <PermissionCell
key="baz"
- style={
+ loading={Array []}
+ onCheck={[Function]}
+ permission={
Object {
- "backgroundColor": "transparent",
+ "description": "",
+ "key": "baz",
+ "name": "Baz",
}
}
- >
- <Checkbox
- checked={false}
- disabled={true}
- id="baz"
- onCheck={[Function]}
- thirdState={false}
- />
- </td>
-</tr>
-`;
-
-exports[`should display checkboxes for permissions 1`] = `
-<tr>
- <td
- className="nowrap"
- >
- <Connect(Avatar)
- className="text-middle big-spacer-right"
- name="John Doe"
- size={36}
- />
- <div
- className="display-inline-block text-middle"
- >
- <div>
- <strong>
- John Doe
- </strong>
- <span
- className="note spacer-left"
- >
- john doe
- </span>
- </div>
- <div
- className="little-spacer-top"
- />
- </div>
- </td>
- <td
- className="text-center text-middle"
- key="bar"
- style={
+ permissionItem={
Object {
- "backgroundColor": "#d9edf7",
+ "login": "john doe",
+ "name": "John Doe",
+ "permissions": Array [
+ "bar",
+ ],
}
}
- >
- <Checkbox
- checked={true}
- disabled={false}
- id="bar"
- onCheck={[Function]}
- thirdState={false}
- />
- </td>
- <td
- className="text-center text-middle"
- key="baz"
- style={
- Object {
- "backgroundColor": "transparent",
- }
- }
- >
- <Checkbox
- checked={false}
- disabled={false}
- id="baz"
- onCheck={[Function]}
- thirdState={false}
- />
- </td>
+ selectedPermission="bar"
+ />
</tr>
`;
diff --git a/server/sonar-web/src/main/js/apps/permissions/styles.css b/server/sonar-web/src/main/js/apps/permissions/styles.css
index 59ba1212b87..a99ab42b908 100644
--- a/server/sonar-web/src/main/js/apps/permissions/styles.css
+++ b/server/sonar-web/src/main/js/apps/permissions/styles.css
@@ -17,22 +17,24 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-.permissions-table > tbody > tr > td {
- border-bottom: 10px solid #fff !important;
-}
-.permissions-table .permission-column {
- width: 1px;
+.permissions-table .permission-column.selected {
+ background-color: #d9edf7;
}
.permissions-table .permission-column-inner {
+ display: inline-block;
width: 100px;
}
.permissions-table .divider {
- background: #e6e6e6;
- border-bottom: 20px solid #fff !important;
- border-top: 20px solid #fff !important;
+ background: #fff;
+ padding: 16px 0;
+}
+.permissions-table .divider::after {
+ display: block;
+ content: '';
+ background: var(--barBorderColor);
height: 1px;
- padding: 0;
+ width: 100%;
}
diff --git a/server/sonar-web/src/main/js/apps/permissions/utils.ts b/server/sonar-web/src/main/js/apps/permissions/utils.ts
new file mode 100644
index 00000000000..98de45fa190
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permissions/utils.ts
@@ -0,0 +1,87 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { translate } from '../../helpers/l10n';
+import { PermissionDefinition, PermissionDefinitionGroup } from '../../app/types';
+
+export const PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE = [
+ 'user',
+ 'codeviewer',
+ 'issueadmin',
+ 'securityhotspotadmin',
+ 'admin',
+ 'scan'
+];
+
+export const PERMISSIONS_ORDER_GLOBAL = [
+ 'admin',
+ { category: 'administer', permissions: ['gateadmin', 'profileadmin'] },
+ 'scan',
+ { category: 'creator', permissions: ['provisioning'] }
+];
+
+export const PERMISSIONS_ORDER_GLOBAL_GOV = [
+ 'admin',
+ { category: 'administer', permissions: ['gateadmin', 'profileadmin'] },
+ 'scan',
+ { category: 'creator', permissions: ['provisioning', 'applicationcreator', 'portfoliocreator'] }
+];
+
+export const PERMISSIONS_ORDER_FOR_VIEW = ['user', 'admin'];
+
+export const PERMISSIONS_ORDER_FOR_DEV = ['user', 'admin'];
+
+export const PERMISSIONS_ORDER_BY_QUALIFIER: { [index: string]: 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) {
+ return {
+ key: permission,
+ name: translate(l10nPrefix, permission),
+ description: translate(l10nPrefix, permission, 'desc')
+ };
+}
+
+export function convertToPermissionDefinitions(
+ permissions: Array<string | { category: string; permissions: string[] }>,
+ l10nPrefix: string
+): Array<PermissionDefinition | PermissionDefinitionGroup> {
+ return permissions.map(permission => {
+ if (typeof permission === 'object') {
+ return {
+ category: permission.category,
+ permissions: permission.permissions.map(permission =>
+ convertToPermissionDefinition(permission, l10nPrefix)
+ )
+ };
+ }
+ return convertToPermissionDefinition(permission, l10nPrefix);
+ });
+}
+
+export function isPermissionDefinitionGroup(
+ permission?: PermissionDefinition | PermissionDefinitionGroup
+): permission is PermissionDefinitionGroup {
+ return Boolean(permission && (permission as PermissionDefinitionGroup).category);
+}
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index a20dc46664d..aaac8fa91d7 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -1998,21 +1998,25 @@ metric.wont_fix_issues.name=Won't Fix Issues
global_permissions.permission=Permission
global_permissions.users=Users
global_permissions.groups=Groups
+global_permissions.administer=Administer
+global_permissions.creator=Create
global_permissions.admin=Administer System
global_permissions.admin.desc=Ability to perform all administration functions for the instance.
-global_permissions.profileadmin=Administer Quality Profiles
+global_permissions.profileadmin=Quality Profiles
global_permissions.profileadmin.desc=Ability to perform any action on quality profiles.
-global_permissions.gateadmin=Administer Quality Gates
+global_permissions.gateadmin=Quality Gates
global_permissions.gateadmin.desc=Ability to perform any action on quality gates.
global_permissions.scan=Execute Analysis
global_permissions.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.
-global_permissions.provisioning=Create Projects
+global_permissions.provisioning=Projects
global_permissions.provisioning.desc=Ability to initialize a project so its settings can be configured before the first analysis.
global_permissions.filter_by_x_permission=Filter by "{0}" permission
global_permissions.restore_access=Restore Access
global_permissions.restore_access.message=You will receive {browse} and {administer} permissions on the project. Do you want to continue?
-
-
+global_permissions.applicationcreator=Applications
+global_permissions.applicationcreator.desc=Ability to create an application.
+global_permissions.portfoliocreator=Portfolios
+global_permissions.portfoliocreator.desc=Ability to create a portfolio.
#------------------------------------------------------------------------------
#