aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2016-06-09 14:49:05 +0200
committerStas Vilchik <vilchiks@gmail.com>2016-06-09 14:49:05 +0200
commit9ddbcb00f8f3a6b6a85d4e1bf68d0edea0d45df5 (patch)
tree2e188bff86ed45b3a31b3c414b9862a90dc1d8e0 /server/sonar-web
parent6b35da5b5dc82f89d62109a2556ffdc520e7e08e (diff)
downloadsonarqube-9ddbcb00f8f3a6b6a85d4e1bf68d0edea0d45df5.tar.gz
sonarqube-9ddbcb00f8f3a6b6a85d4e1bf68d0edea0d45df5.zip
refactor permission templates page (#1025)
Diffstat (limited to 'server/sonar-web')
-rw-r--r--server/sonar-web/package.json1
-rw-r--r--server/sonar-web/src/main/js/api/permissions.js29
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/__tests__/permission-templates-test.js114
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/app.js8
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.js159
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/App.js92
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/Defaults.js43
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/Header.js (renamed from server/sonar-web/src/main/js/apps/permission-templates/header.js)51
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/List.js (renamed from server/sonar-web/src/main/js/apps/permission-templates/permission-templates.js)45
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/ListHeader.js (renamed from server/sonar-web/src/main/js/apps/permission-templates/permissions-header.js)34
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/ListItem.js101
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/NameCell.js57
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/PermissionCell.js86
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/ActionsCell-test.js103
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/Defaults-test.js43
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/main.js96
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/permission-template-defaults.js59
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/permission-template-set-defaults.js85
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/permission-template.js140
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/propTypes.js40
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/styles.css17
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/templates/permission-templates-form.hbs6
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/utils.js71
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/views/CreateView.js (renamed from server/sonar-web/src/main/js/apps/permission-templates/create-view.js)4
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/views/DeleteView.js (renamed from server/sonar-web/src/main/js/apps/permission-templates/delete-view.js)6
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/views/FormView.js (renamed from server/sonar-web/src/main/js/apps/permission-templates/form-view.js)4
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/views/GroupsView.js (renamed from server/sonar-web/src/main/js/apps/permission-templates/groups-view.js)6
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/views/UpdateView.js (renamed from server/sonar-web/src/main/js/apps/permission-templates/update-view.js)4
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/views/UsersView.js (renamed from server/sonar-web/src/main/js/apps/permission-templates/users-view.js)6
-rw-r--r--server/sonar-web/src/main/js/apps/project-permissions/main.js62
-rw-r--r--server/sonar-web/src/main/js/apps/project-permissions/permissions-header.js28
-rw-r--r--server/sonar-web/src/main/js/apps/project-permissions/permissions.js4
-rw-r--r--server/sonar-web/src/main/less/init/icons.less2
33 files changed, 982 insertions, 624 deletions
diff --git a/server/sonar-web/package.json b/server/sonar-web/package.json
index 98bc1baba05..3dd9cbad49b 100644
--- a/server/sonar-web/package.json
+++ b/server/sonar-web/package.json
@@ -59,6 +59,7 @@
"react-addons-shallow-compare": "15.0.1",
"react-addons-test-utils": "15.0.1",
"react-dom": "15.0.1",
+ "react-helmet": "3.1.0",
"react-redux": "4.4.1",
"react-router": "2.0.1",
"react-router-redux": "4.0.2",
diff --git a/server/sonar-web/src/main/js/api/permissions.js b/server/sonar-web/src/main/js/api/permissions.js
index 03f723e56d1..4139c0f7c56 100644
--- a/server/sonar-web/src/main/js/api/permissions.js
+++ b/server/sonar-web/src/main/js/api/permissions.js
@@ -19,6 +19,7 @@
*/
import $ from 'jquery';
import _ from 'underscore';
+import { getJSON, post } from '../helpers/request';
function request (options) {
return $.ajax(options);
@@ -102,13 +103,13 @@ export function revokeFromGroup (permission, group, project) {
return request({ type: 'POST', url, data });
}
-export function getPermissionTemplates (query) {
+/**
+ * Get list of permission templates
+ * @returns {Promise}
+ */
+export function getPermissionTemplates () {
const url = window.baseUrl + '/api/permissions/search_templates';
- const data = { };
- if (query) {
- data.q = query;
- }
- return request({ type: 'GET', url, data });
+ return getJSON(url);
}
export function createPermissionTemplate (options) {
@@ -126,14 +127,16 @@ export function deletePermissionTemplate (options) {
return request(_.extend({ type: 'POST', url }, options));
}
-export function setDefaultPermissionTemplate (template, qualifier) {
- if (typeof template !== 'string' || !template.length) {
- return typeError('setDefaultPermissionTemplate', 'please provide permission template ID');
- }
-
+/**
+ * Set default permission template for a given qualifier
+ * @param {string} templateName
+ * @param {string} qualifier
+ * @returns {Promise}
+ */
+export function setDefaultPermissionTemplate (templateName, qualifier) {
const url = window.baseUrl + '/api/permissions/set_default_template';
- const data = { templateId: template, qualifier };
- return request({ type: 'POST', url, data });
+ const data = { templateName, qualifier };
+ return post(url, data);
}
export function applyTemplateToProject (options) {
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/__tests__/permission-templates-test.js b/server/sonar-web/src/main/js/apps/permission-templates/__tests__/permission-templates-test.js
deleted file mode 100644
index ade4d7b5872..00000000000
--- a/server/sonar-web/src/main/js/apps/permission-templates/__tests__/permission-templates-test.js
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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.
- */
-/* eslint no-unused-expressions: 0 */
-import React from 'react';
-import TestUtils from 'react-addons-test-utils';
-import { expect } from 'chai';
-import sinon from 'sinon';
-
-import Defaults from '../permission-template-defaults';
-import SetDefaults from '../permission-template-set-defaults';
-
-describe('Permission Templates', function () {
- describe('Defaults', () => {
- it('should display one qualifier', () => {
- const permissionTemplate = { defaultFor: ['VW'] };
- const topQualifiers = ['TRK', 'VW'];
- const result = TestUtils.renderIntoDocument(
- <Defaults permissionTemplate={permissionTemplate} topQualifiers={topQualifiers}/>
- );
- expect(TestUtils.scryRenderedDOMComponentsWithClass(result, 'icon-qualifier-trk')).to.be.empty;
- expect(TestUtils.scryRenderedDOMComponentsWithClass(result, 'icon-qualifier-vw')).to.have.length(1);
- });
-
- it('should display two qualifiers', () => {
- const permissionTemplate = { defaultFor: ['TRK', 'VW'] };
- const topQualifiers = ['TRK', 'VW'];
- const result = TestUtils.renderIntoDocument(
- <Defaults permissionTemplate={permissionTemplate} topQualifiers={topQualifiers}/>
- );
- expect(TestUtils.scryRenderedDOMComponentsWithClass(result, 'icon-qualifier-trk')).to.have.length(1);
- expect(TestUtils.scryRenderedDOMComponentsWithClass(result, 'icon-qualifier-vw')).to.have.length(1);
- });
-
- it('should not display qualifiers', () => {
- const permissionTemplate = { defaultFor: [] };
- const topQualifiers = ['TRK', 'VW'];
- const result = TestUtils.renderIntoDocument(
- <Defaults permissionTemplate={permissionTemplate} topQualifiers={topQualifiers}/>
- );
- expect(TestUtils.scryRenderedDOMComponentsWithClass(result, 'icon-qualifier-trk')).to.be.empty;
- expect(TestUtils.scryRenderedDOMComponentsWithClass(result, 'icon-qualifier-vw')).to.be.empty;
- });
-
- it('should omit "project" if there is only one qualifier', () => {
- const permissionTemplate = { defaultFor: ['TRK'] };
- const topQualifiers = ['TRK'];
- const result = TestUtils.renderIntoDocument(
- <Defaults permissionTemplate={permissionTemplate} topQualifiers={topQualifiers}/>
- );
- expect(TestUtils.scryRenderedDOMComponentsWithClass(result, 'icon-qualifier-trk')).to.be.empty;
- });
- });
-
- describe('SetDefaults', () => {
- const refresh = sinon.spy();
-
- it('should display a dropdown with one option', () => {
- const permissionTemplate = { defaultFor: ['VW'] };
- const topQualifiers = ['TRK', 'VW'];
- const result = TestUtils.renderIntoDocument(
- <SetDefaults permissionTemplate={permissionTemplate} topQualifiers={topQualifiers} refresh={refresh}/>
- );
- expect(TestUtils.scryRenderedDOMComponentsWithClass(result, 'dropdown')).to.have.length(1);
- expect(TestUtils.scryRenderedDOMComponentsWithTag(result, 'a')).to.have.length(1);
- });
-
- it('should display a dropdown with two options', () => {
- const permissionTemplate = { defaultFor: [] };
- const topQualifiers = ['TRK', 'VW'];
- const result = TestUtils.renderIntoDocument(
- <SetDefaults permissionTemplate={permissionTemplate} topQualifiers={topQualifiers} refresh={refresh}/>
- );
- expect(TestUtils.scryRenderedDOMComponentsWithClass(result, 'dropdown')).to.have.length(1);
- expect(TestUtils.scryRenderedDOMComponentsWithTag(result, 'a')).to.have.length(2);
- });
-
- it('should not display a dropdown', () => {
- const permissionTemplate = { defaultFor: ['TRK', 'VW'] };
- const topQualifiers = ['TRK', 'VW'];
- const result = TestUtils.renderIntoDocument(
- <SetDefaults permissionTemplate={permissionTemplate} topQualifiers={topQualifiers} refresh={refresh}/>
- );
- expect(TestUtils.scryRenderedDOMComponentsWithClass(result, 'dropdown')).to.be.empty;
- expect(TestUtils.scryRenderedDOMComponentsWithTag(result, 'a')).to.be.empty;
- });
-
- it('should omit dropdown if there is only one qualifier', () => {
- const permissionTemplate = { defaultFor: [] };
- const topQualifiers = ['TRK'];
- const result = TestUtils.renderIntoDocument(
- <SetDefaults permissionTemplate={permissionTemplate} topQualifiers={topQualifiers} refresh={refresh}/>
- );
- expect(TestUtils.scryRenderedDOMComponentsWithClass(result, 'dropdown')).to.be.empty;
- expect(TestUtils.scryRenderedDOMComponentsWithTag(result, 'a')).to.have.length(1);
- });
- });
-});
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/app.js b/server/sonar-web/src/main/js/apps/permission-templates/app.js
index f257ec4a95c..d3747022dc1 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/app.js
+++ b/server/sonar-web/src/main/js/apps/permission-templates/app.js
@@ -18,10 +18,12 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
-import ReactDOM from 'react-dom';
-import Main from './main';
+import { render } from 'react-dom';
+import App from './components/App';
window.sonarqube.appStarted.then(options => {
const el = document.querySelector(options.el);
- ReactDOM.render(<Main topQualifiers={options.rootQualifiers}/>, el);
+ render((
+ <App topQualifiers={options.rootQualifiers}/>
+ ), el);
});
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.js b/server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.js
new file mode 100644
index 00000000000..46a1e0e4f6c
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.js
@@ -0,0 +1,159 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 React from 'react';
+import difference from 'lodash/difference';
+import { PermissionTemplateType, CallbackType } from '../propTypes';
+import QualifierIcon from '../../../components/shared/qualifier-icon';
+import { translate } from '../../../helpers/l10n';
+import { setDefaultPermissionTemplate } from '../../../api/permissions';
+
+export default class ActionsCell extends React.Component {
+ static propTypes = {
+ permissionTemplate: PermissionTemplateType.isRequired,
+ topQualifiers: React.PropTypes.array.isRequired,
+ onUpdate: CallbackType,
+ onDelete: CallbackType,
+ refresh: CallbackType
+ };
+
+ handleUpdateClick (e) {
+ e.preventDefault();
+ this.props.onUpdate();
+ }
+
+ handleDeleteClick (e) {
+ e.preventDefault();
+ this.props.onDelete();
+ }
+
+ setDefault (qualifier, e) {
+ e.preventDefault();
+ setDefaultPermissionTemplate(
+ this.props.permissionTemplate.name,
+ qualifier
+ ).then(this.props.refresh);
+ }
+
+ getAvailableQualifiers () {
+ return difference(
+ this.props.topQualifiers,
+ this.props.permissionTemplate.defaultFor);
+ }
+
+ renderDropdownIcon (icon) {
+ const style = {
+ display: 'inline-block',
+ width: 16,
+ marginRight: 4,
+ textAlign: 'center'
+ };
+ return (
+ <div style={style}>{icon}</div>
+ );
+ }
+
+ renderSetDefaultsControl () {
+ const availableQualifiers = this.getAvailableQualifiers();
+
+ if (availableQualifiers.length === 0) {
+ return null;
+ }
+
+ return this.props.topQualifiers.length === 1 ?
+ this.renderIfSingleTopQualifier(availableQualifiers) :
+ this.renderIfMultipleTopQualifiers(availableQualifiers);
+ }
+
+ renderSetDefaultLink (qualifier, child) {
+ return (
+ <li key={qualifier}>
+ <a href="#"
+ className="js-set-default"
+ data-qualifier={qualifier}
+ onClick={this.setDefault.bind(this, qualifier)}>
+ {this.renderDropdownIcon(<i className="icon-check"/>)}
+ {child}
+ </a>
+ </li>
+ );
+ }
+
+ renderIfSingleTopQualifier (availableQualifiers) {
+ return availableQualifiers.map(qualifier => (
+ this.renderSetDefaultLink(qualifier, (
+ <span>{translate('permission_templates.set_default')}</span>
+ )))
+ );
+ }
+
+ renderIfMultipleTopQualifiers (availableQualifiers) {
+ return availableQualifiers.map(qualifier => (
+ this.renderSetDefaultLink(qualifier, (
+ <span>
+ {translate('permission_templates.set_default_for')}
+ {' '}
+ <QualifierIcon qualifier={qualifier}/>
+ {' '}
+ {translate('qualifiers', qualifier)}
+ </span>
+ )))
+ );
+ }
+
+ render () {
+ const { permissionTemplate: t } = this.props;
+
+ return (
+ <td className="actions-column">
+ <div className="dropdown">
+ <button className="dropdown-toggle" data-toggle="dropdown">
+ {translate('actions')}
+ {' '}
+ <i className="icon-dropdown"></i>
+ </button>
+
+ <ul className="dropdown-menu dropdown-menu-right">
+ {this.renderSetDefaultsControl()}
+
+ <li>
+ <a href="#"
+ className="js-update"
+ onClick={this.handleUpdateClick.bind(this)}>
+ {this.renderDropdownIcon(<i className="icon-edit"/>)}
+ {translate('update_verb')}
+ </a>
+ </li>
+
+ {t.defaultFor.length === 0 && (
+ <li>
+ <a href="#"
+ className="js-delete"
+ onClick={this.handleDeleteClick.bind(this)}>
+ {this.renderDropdownIcon(<i className="icon-delete"/>)}
+ {translate('delete')}
+ </a>
+ </li>
+ )}
+ </ul>
+ </div>
+ </td>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/App.js b/server/sonar-web/src/main/js/apps/permission-templates/components/App.js
new file mode 100644
index 00000000000..bbb92be633c
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/App.js
@@ -0,0 +1,92 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 React from 'react';
+import Helmet from 'react-helmet';
+import Header from './Header';
+import List from './List';
+import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin';
+import { getPermissionTemplates } from '../../../api/permissions';
+import { sortPermissions, mergePermissionsToTemplates, mergeDefaultsToTemplates } from '../utils';
+import { translate } from '../../../helpers/l10n';
+import '../styles.css';
+
+export default class App extends React.Component {
+ static propTypes = {
+ topQualifiers: React.PropTypes.array.isRequired
+ };
+
+ state = {
+ ready: false,
+ permissions: [],
+ permissionTemplates: []
+ };
+
+ componentWillMount () {
+ this.requestPermissions = this.requestPermissions.bind(this);
+ }
+
+ componentDidMount () {
+ this.mounted = true;
+ this.requestPermissions();
+ }
+
+ componentWillUnmount () {
+ this.mounted = false;
+ }
+
+ requestPermissions () {
+ getPermissionTemplates().then(r => {
+ if (this.mounted) {
+ const permissions = sortPermissions(r.permissions);
+ const permissionTemplates = mergeDefaultsToTemplates(
+ mergePermissionsToTemplates(r.permissionTemplates, permissions),
+ r.defaultTemplates
+ );
+ this.setState({
+ ready: true,
+ permissionTemplates,
+ permissions
+ });
+ }
+ });
+ }
+
+ render () {
+ return (
+ <div className="page page-limited">
+ <Helmet
+ title={translate('permission_templates.page')}
+ titleTemplate="SonarQube - %s"/>
+
+ <Header
+ ready={this.state.ready}
+ refresh={this.requestPermissions}/>
+
+ <TooltipsContainer>
+ <List
+ permissionTemplates={this.state.permissionTemplates}
+ permissions={this.state.permissions}
+ topQualifiers={this.props.topQualifiers}
+ refresh={this.requestPermissions}/>
+ </TooltipsContainer>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/Defaults.js b/server/sonar-web/src/main/js/apps/permission-templates/components/Defaults.js
new file mode 100644
index 00000000000..35776605f97
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/Defaults.js
@@ -0,0 +1,43 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 React from 'react';
+import sortBy from 'lodash/sortBy';
+import { translate } from '../../../helpers/l10n';
+import { PermissionTemplateType } from '../propTypes';
+
+export default class Defaults extends React.Component {
+ static propTypes = {
+ permissionTemplate: PermissionTemplateType.isRequired
+ };
+
+ render () {
+ const qualifiers = sortBy(this.props.permissionTemplate.defaultFor)
+ .map(qualifier => translate('qualifiers', qualifier))
+ .join(', ');
+
+ return (
+ <div>
+ <span className="badge spacer-right">
+ {translate('default')} for {qualifiers}
+ </span>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/header.js b/server/sonar-web/src/main/js/apps/permission-templates/components/Header.js
index fb42ae354ef..b22b9387f3e 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/header.js
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/Header.js
@@ -18,34 +18,49 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
-import CreateView from './create-view';
-import { translate } from '../../helpers/l10n';
+import CreateView from '../views/CreateView';
+import { translate } from '../../../helpers/l10n';
+import { CallbackType } from '../propTypes';
-export default React.createClass({
- onCreate(e) {
+export default class Header extends React.Component {
+ static propTypes = {
+ ready: React.PropTypes.bool.isRequired,
+ refresh: CallbackType
+ };
+
+ componentWillMount () {
+ this.handleCreateClick = this.handleCreateClick.bind(this);
+ }
+
+ handleCreateClick (e) {
e.preventDefault();
+
new CreateView({
refresh: this.props.refresh
}).render();
- },
-
- renderSpinner () {
- if (this.props.ready) {
- return null;
- }
- return <i className="spinner"/>;
- },
+ }
- render() {
+ render () {
return (
<header id="project-permissions-header" className="page-header">
- <h1 className="page-title">{translate('permission_templates.page')}</h1>
- {this.renderSpinner()}
+ <h1 className="page-title">
+ {translate('permission_templates.page')}
+ </h1>
+
+ {!this.props.ready && (
+ <i className="spinner"/>
+ )}
+
<div className="page-actions">
- <button onClick={this.onCreate}>Create</button>
+ <button onClick={this.handleCreateClick}>
+ {translate('create')}
+ </button>
</div>
- <p className="page-description">{translate('roles.page.description')}</p>
+
+ <p className="page-description">
+ {translate('permission_templates.page.description')}
+ </p>
</header>
);
}
-});
+}
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/permission-templates.js b/server/sonar-web/src/main/js/apps/permission-templates/components/List.js
index 2a6aff49bd6..11ad4cd42a9 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/permission-templates.js
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/List.js
@@ -17,34 +17,35 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import classNames from 'classnames';
import React from 'react';
+import ListHeader from './ListHeader';
+import ListItem from './ListItem';
+import { PermissionTemplateType, CallbackType } from '../propTypes';
-import PermissionsHeader from './permissions-header';
-import PermissionTemplate from './permission-template';
-
-export default React.createClass({
- propTypes: {
- permissionTemplates: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
- permissions: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
+export default class List extends React.Component {
+ static propTypes = {
+ permissionTemplates: React.PropTypes.arrayOf(
+ PermissionTemplateType).isRequired,
+ permissions: React.PropTypes.array.isRequired,
topQualifiers: React.PropTypes.array.isRequired,
- refresh: React.PropTypes.func.isRequired
- },
+ refresh: CallbackType
+ };
+
+ render () {
+ const permissionTemplates = this.props.permissionTemplates.map(p => (
+ <ListItem
+ key={p.id}
+ permissionTemplate={p}
+ topQualifiers={this.props.topQualifiers}
+ refresh={this.props.refresh}/>
+ ));
- render() {
- const permissionTemplates = this.props.permissionTemplates.map(p => {
- return <PermissionTemplate
- key={p.id}
- permissionTemplate={p}
- topQualifiers={this.props.topQualifiers}
- refresh={this.props.refresh}/>;
- });
- const className = classNames('data zebra', { 'new-loading': !this.props.ready });
return (
- <table id="permission-templates" className={className}>
- <PermissionsHeader permissions={this.props.permissions}/>
+ <table id="permission-templates"
+ className="data zebra permissions-table">
+ <ListHeader permissions={this.props.permissions}/>
<tbody>{permissionTemplates}</tbody>
</table>
);
}
-});
+}
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/permissions-header.js b/server/sonar-web/src/main/js/apps/permission-templates/components/ListHeader.js
index a459d2f60c1..5a820ef0d38 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/permissions-header.js
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/ListHeader.js
@@ -19,28 +19,30 @@
*/
import React from 'react';
-export default React.createClass({
- propTypes: {
- permissions: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
- },
+export default class ListHeader extends React.Component {
+ static propTypes = {
+ permissions: React.PropTypes.array.isRequired
+ };
+
+ render () {
+ const cells = this.props.permissions.map(p => (
+ <th key={p.key} className="permission-column">
+ {p.name}
+ <i
+ className="icon-help little-spacer-left"
+ title={p.description}
+ data-toggle="tooltip"/>
+ </th>
+ ));
- render() {
- const cellWidth = (80 / this.props.permissions.length) + '%';
- const cells = this.props.permissions.map(p => {
- return (
- <th key={p.key} style={{ width: cellWidth }}>
- {p.name}<br/><span className="small">{p.description}</span>
- </th>
- );
- });
return (
<thead>
<tr>
- <th style={{ width: '20%' }}>&nbsp;</th>
+ <th>&nbsp;</th>
{cells}
- <th className="thin">&nbsp;</th>
+ <th className="actions-column">&nbsp;</th>
</tr>
</thead>
);
}
-});
+}
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/ListItem.js b/server/sonar-web/src/main/js/apps/permission-templates/components/ListItem.js
new file mode 100644
index 00000000000..0e659a14f47
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/ListItem.js
@@ -0,0 +1,101 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 Backbone from 'backbone';
+import React from 'react';
+import NameCell from './NameCell';
+import PermissionCell from './PermissionCell';
+import ActionsCell from './ActionsCell';
+import UsersView from '../views/UsersView';
+import GroupsView from '../views/GroupsView';
+import UpdateView from '../views/UpdateView';
+import DeleteView from '../views/DeleteView';
+import { PermissionTemplateType, CallbackType } from '../propTypes';
+
+export default class ListItem extends React.Component {
+ static propTypes = {
+ permissionTemplate: PermissionTemplateType.isRequired,
+ topQualifiers: React.PropTypes.array.isRequired,
+ refresh: CallbackType
+ };
+
+ componentWillMount () {
+ this.handleShowGroups = this.handleShowGroups.bind(this);
+ this.handleShowUsers = this.handleShowUsers.bind(this);
+ }
+
+ handleShowGroups (permission) {
+ new GroupsView({
+ permission,
+ permissionTemplate: this.props.permissionTemplate,
+ refresh: this.props.refresh
+ }).render();
+ }
+
+ handleShowUsers (permission) {
+ new UsersView({
+ permission,
+ permissionTemplate: this.props.permissionTemplate,
+ refresh: this.props.refresh
+ }).render();
+ }
+
+ onUpdate () {
+ new UpdateView({
+ model: new Backbone.Model(this.props.permissionTemplate),
+ refresh: this.props.refresh
+ }).render();
+ }
+
+ onDelete () {
+ new DeleteView({
+ model: new Backbone.Model(this.props.permissionTemplate),
+ refresh: this.props.refresh
+ }).render();
+ }
+
+ render () {
+ const permissions = this.props.permissionTemplate.permissions.map(p => (
+ <PermissionCell
+ key={p.key}
+ permission={p}
+ onShowUsers={this.handleShowUsers}
+ onShowGroups={this.handleShowGroups}/>
+ ));
+
+ return (
+ <tr
+ data-id={this.props.permissionTemplate.id}
+ data-name={this.props.permissionTemplate.name}>
+ <NameCell
+ permissionTemplate={this.props.permissionTemplate}
+ topQualifiers={this.props.topQualifiers}/>
+
+ {permissions}
+
+ <ActionsCell
+ permissionTemplate={this.props.permissionTemplate}
+ topQualifiers={this.props.topQualifiers}
+ onUpdate={this.onUpdate.bind(this)}
+ onDelete={this.onDelete.bind(this)}
+ refresh={this.props.refresh}/>
+ </tr>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/NameCell.js b/server/sonar-web/src/main/js/apps/permission-templates/components/NameCell.js
new file mode 100644
index 00000000000..6591f1c7020
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/NameCell.js
@@ -0,0 +1,57 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 React from 'react';
+import Defaults from './Defaults';
+import { PermissionTemplateType } from '../propTypes';
+
+export default class NameCell extends React.Component {
+ static propTypes = {
+ permissionTemplate: PermissionTemplateType.isRequired,
+ topQualifiers: React.PropTypes.array.isRequired
+ };
+
+ render () {
+ const { permissionTemplate: t } = this.props;
+
+ return (
+ <td>
+ <strong className="js-name">{t.name}</strong>
+
+ {t.defaultFor.length > 0 && (
+ <div className="spacer-top js-defaults">
+ <Defaults permissionTemplate={this.props.permissionTemplate}/>
+ </div>
+ )}
+
+ {!!t.description && (
+ <div className="spacer-top js-description">
+ {t.description}
+ </div>
+ )}
+
+ {!!t.projectKeyPattern && (
+ <div className="spacer-top js-project-key-pattern">
+ Project Key Pattern: <code>{t.projectKeyPattern}</code>
+ </div>
+ )}
+ </td>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/PermissionCell.js b/server/sonar-web/src/main/js/apps/permission-templates/components/PermissionCell.js
new file mode 100644
index 00000000000..44bf5183445
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/PermissionCell.js
@@ -0,0 +1,86 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 React from 'react';
+import { PermissionType, CallbackType } from '../propTypes';
+
+export default class PermissionCell extends React.Component {
+ static propTypes = {
+ permission: PermissionType.isRequired,
+ onShowUsers: CallbackType,
+ onShowGroups: CallbackType
+ };
+
+ handleShowUsers (e) {
+ e.preventDefault();
+ this.props.onShowUsers(this.props.permission);
+ }
+
+ handleShowGroups (e) {
+ e.preventDefault();
+ this.props.onShowGroups(this.props.permission);
+ }
+
+ render () {
+ const { permission: p } = this.props;
+
+ return (
+ <td
+ className="permission-column"
+ data-permission={p.key}>
+ <table>
+ <tbody>
+ <tr>
+ <td className="spacer-right">
+ Users
+ </td>
+ <td className="spacer-left bordered-left">
+ {p.usersCount}
+ </td>
+ <td className="spacer-left">
+ <a
+ onClick={this.handleShowUsers.bind(this)}
+ className="icon-bullet-list"
+ title="Update Users"
+ data-toggle="tooltip"
+ href="#"/>
+ </td>
+ </tr>
+ <tr>
+ <td className="spacer-right">
+ Groups
+ </td>
+ <td className="spacer-left bordered-left">
+ {p.groupsCount}
+ </td>
+ <td className="spacer-left">
+ <a
+ onClick={this.handleShowGroups.bind(this)}
+ className="icon-bullet-list"
+ title="Update Users"
+ data-toggle="tooltip"
+ href="#"/>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/ActionsCell-test.js b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/ActionsCell-test.js
new file mode 100644
index 00000000000..09f83fd29ce
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/ActionsCell-test.js
@@ -0,0 +1,103 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 chai, { expect } from 'chai';
+import sinon from 'sinon';
+import sinonChai from 'sinon-chai';
+import { shallow } from 'enzyme';
+import React from 'react';
+import ActionsCell from '../ActionsCell';
+
+chai.use(sinonChai);
+
+const SAMPLE = {
+ id: 'id',
+ name: 'name',
+ permissions: [],
+ defaultFor: []
+};
+
+function renderActionsCell (props) {
+ return shallow(
+ <ActionsCell
+ permissionTemplate={SAMPLE}
+ topQualifiers={['TRK', 'VW']}
+ onUpdate={() => true}
+ onDelete={() => true}
+ refresh={() => true}
+ {...props}/>
+ );
+}
+
+function simulateClick (element) {
+ element.simulate('click', {
+ preventDefault() {}
+ });
+}
+
+describe('Permission Templates :: ActionsCell', () => {
+ it('should update', () => {
+ const onUpdate = sinon.spy();
+ const updateButton = renderActionsCell({ onUpdate }).find('.js-update');
+
+ expect(updateButton).have.length(1);
+ expect(onUpdate).to.not.have.been.called;
+
+ simulateClick(updateButton);
+
+ expect(onUpdate).to.have.been.called;
+ });
+
+ it('should delete', () => {
+ const onDelete = sinon.spy();
+ const deleteButton = renderActionsCell({ onDelete }).find('.js-delete');
+
+ expect(deleteButton).have.length(1);
+ expect(onDelete).to.not.have.been.called;
+
+ simulateClick(deleteButton);
+
+ expect(onDelete).to.have.been.called;
+ });
+
+ it('should not delete', () => {
+ const permissionTemplate = { ...SAMPLE, defaultFor: ['VW'] };
+ const deleteButton = renderActionsCell({ permissionTemplate })
+ .find('.js-delete');
+
+ expect(deleteButton).to.have.length(0);
+ });
+
+ it('should set default', () => {
+ const setDefault = renderActionsCell()
+ .find('.js-set-default');
+
+ expect(setDefault).to.have.length(2);
+ expect(setDefault.at(0).prop('data-qualifier')).to.equal('TRK');
+ expect(setDefault.at(1).prop('data-qualifier')).to.equal('VW');
+ });
+
+ it('should not set default', () => {
+ const permissionTemplate = { ...SAMPLE, defaultFor: ['TRK', 'VW'] };
+ const setDefault = renderActionsCell({ permissionTemplate })
+ .find('.js-set-default');
+
+ expect(setDefault).to.have.length(0);
+ });
+});
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/Defaults-test.js b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/Defaults-test.js
new file mode 100644
index 00000000000..c74885f8ad4
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/Defaults-test.js
@@ -0,0 +1,43 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 { expect } from 'chai';
+import { shallow } from 'enzyme';
+import React from 'react';
+import Defaults from '../Defaults';
+
+const SAMPLE = {
+ id: 'id',
+ name: 'name',
+ permissions: []
+};
+
+describe('Permission Templates :: Defaults', () => {
+ it('should render one qualifier', () => {
+ const sample = { ...SAMPLE, defaultFor: ['DEV'] };
+ const output = shallow(<Defaults permissionTemplate={sample}/>);
+ expect(output.text()).to.contain('DEV');
+ });
+
+ it('should render several qualifiers', () => {
+ const sample = { ...SAMPLE, defaultFor: ['TRK', 'VW'] };
+ const output = shallow(<Defaults permissionTemplate={sample}/>);
+ expect(output.text()).to.contain('TRK');
+ });
+});
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/main.js b/server/sonar-web/src/main/js/apps/permission-templates/main.js
deleted file mode 100644
index d20b26aabcf..00000000000
--- a/server/sonar-web/src/main/js/apps/permission-templates/main.js
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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 _ from 'underscore';
-import React from 'react';
-import Header from './header';
-import PermissionTemplates from './permission-templates';
-import { getPermissionTemplates } from '../../api/permissions';
-
-const PERMISSIONS_ORDER = ['user', 'codeviewer', 'issueadmin', 'admin', 'scan'];
-
-export default React.createClass({
- propTypes: {
- topQualifiers: React.PropTypes.array.isRequired
- },
-
- getInitialState() {
- return { ready: false, permissions: [], permissionTemplates: [] };
- },
-
- componentDidMount() {
- this.requestPermissions();
- },
-
- sortPermissions(permissions) {
- return _.sortBy(permissions, p => PERMISSIONS_ORDER.indexOf(p.key));
- },
-
- mergePermissionsToTemplates(permissionTemplates, basePermissions) {
- return permissionTemplates.map(permissionTemplate => {
- // it's important to keep the order of the permission template's permissions
- // the same as the order of base permissions
- const permissions = basePermissions.map(basePermission => {
- const projectPermission = _.findWhere(permissionTemplate.permissions, { key: basePermission.key });
- return _.extend({ usersCount: 0, groupsCount: 0 }, basePermission, projectPermission);
- });
- return _.extend({}, permissionTemplate, { permissions });
- });
- },
-
- mergeDefaultsToTemplates(permissionTemplates, defaultTemplates = []) {
- return permissionTemplates.map(permissionTemplate => {
- const defaultFor = [];
- defaultTemplates.forEach(defaultTemplate => {
- if (defaultTemplate.templateId === permissionTemplate.id) {
- defaultFor.push(defaultTemplate.qualifier);
- }
- });
- return _.extend({}, permissionTemplate, { defaultFor });
- });
- },
-
- requestPermissions() {
- getPermissionTemplates().done(r => {
- const permissions = this.sortPermissions(r.permissions);
- const permissionTemplates = this.mergePermissionsToTemplates(r.permissionTemplates, permissions);
- const permissionTemplatesWithDefaults = this.mergeDefaultsToTemplates(permissionTemplates, r.defaultTemplates);
- this.setState({
- ready: true,
- permissionTemplates: permissionTemplatesWithDefaults,
- permissions
- });
- });
- },
-
- render() {
- return (
- <div className="page">
- <Header ready={this.state.ready} refresh={this.requestPermissions}/>
-
- <PermissionTemplates
- ready={this.state.ready}
- permissionTemplates={this.state.permissionTemplates}
- permissions={this.state.permissions}
- topQualifiers={this.props.topQualifiers}
- refresh={this.requestPermissions}/>
- </div>
- );
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/permission-template-defaults.js b/server/sonar-web/src/main/js/apps/permission-templates/permission-template-defaults.js
deleted file mode 100644
index 4ca60b3d4db..00000000000
--- a/server/sonar-web/src/main/js/apps/permission-templates/permission-template-defaults.js
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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 _ from 'underscore';
-import React from 'react';
-import QualifierIcon from '../../components/shared/qualifier-icon';
-import { translate } from '../../helpers/l10n';
-
-export default React.createClass({
- propTypes: {
- permissionTemplate: React.PropTypes.object.isRequired,
- topQualifiers: React.PropTypes.array.isRequired
- },
-
- renderIfSingleTopQualifier() {
- return (
- <ul className="list-inline nowrap spacer-bottom">
- <li>Default</li>
- </ul>
- );
- },
-
- renderIfMultipleTopQualifiers() {
- const defaults = this.props.permissionTemplate.defaultFor.map(qualifier => {
- return <li key={qualifier}><QualifierIcon qualifier={qualifier}/>&nbsp;{translate('qualifier', qualifier)}</li>;
- });
- return (
- <ul className="list-inline nowrap spacer-bottom">
- <li>Default for</li>
- {defaults}
- </ul>
- );
- },
-
- render() {
- if (_.size(this.props.permissionTemplate.defaultFor) === 0) {
- return null;
- }
- return this.props.topQualifiers.length === 1 ?
- this.renderIfSingleTopQualifier() :
- this.renderIfMultipleTopQualifiers();
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/permission-template-set-defaults.js b/server/sonar-web/src/main/js/apps/permission-templates/permission-template-set-defaults.js
deleted file mode 100644
index 27be58a70e2..00000000000
--- a/server/sonar-web/src/main/js/apps/permission-templates/permission-template-set-defaults.js
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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 _ from 'underscore';
-import React from 'react';
-import { setDefaultPermissionTemplate } from '../../api/permissions';
-import QualifierIcon from '../../components/shared/qualifier-icon';
-import { translate } from '../../helpers/l10n';
-
-export default React.createClass({
- propTypes: {
- permissionTemplate: React.PropTypes.object.isRequired,
- topQualifiers: React.PropTypes.array.isRequired,
- refresh: React.PropTypes.func.isRequired
- },
-
- getAvailableQualifiers() {
- return _.difference(this.props.topQualifiers, this.props.permissionTemplate.defaultFor);
- },
-
- setDefault(qualifier, e) {
- e.preventDefault();
- setDefaultPermissionTemplate(this.props.permissionTemplate.id, qualifier).done(() => this.props.refresh());
- },
-
- renderIfSingleTopQualifier(availableQualifiers) {
- const qualifiers = availableQualifiers.map(qualifier => {
- return (
- <span key={qualifier} className="text-middle">
- <a onClick={this.setDefault.bind(this, qualifier)} className="button" href="#">Set Default</a>
- </span>
- );
- });
-
- return <span className="little-spacer-right">{qualifiers}</span>;
- },
-
- renderIfMultipleTopQualifiers(availableQualifiers) {
- const qualifiers = availableQualifiers.map(qualifier => {
- return (
- <li key={qualifier}>
- <a onClick={this.setDefault.bind(this, qualifier)} href="#">
- Set Default for <QualifierIcon qualifier={qualifier}/> {translate('qualifier', qualifier)}
- </a>
- </li>
- );
- });
-
- return (
- <span className="dropdown little-spacer-right">
- <button className="dropdown-toggle" data-toggle="dropdown">
- Set Default <i className="icon-dropdown"></i>
- </button>
- <ul className="dropdown-menu">{qualifiers}</ul>
- </span>
- );
- },
-
- render() {
- const availableQualifiers = this.getAvailableQualifiers();
- if (availableQualifiers.length === 0) {
- return null;
- }
-
- return this.props.topQualifiers.length === 1 ?
- this.renderIfSingleTopQualifier(availableQualifiers) :
- this.renderIfMultipleTopQualifiers(availableQualifiers);
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/permission-template.js b/server/sonar-web/src/main/js/apps/permission-templates/permission-template.js
deleted file mode 100644
index c2a3e2cb9c3..00000000000
--- a/server/sonar-web/src/main/js/apps/permission-templates/permission-template.js
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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 _ from 'underscore';
-import Backbone from 'backbone';
-import React from 'react';
-import Defaults from './permission-template-defaults';
-import SetDefaults from './permission-template-set-defaults';
-import UsersView from './users-view';
-import GroupsView from './groups-view';
-import UpdateView from './update-view';
-import DeleteView from './delete-view';
-
-export default React.createClass({
- propTypes: {
- permissionTemplate: React.PropTypes.object.isRequired,
- topQualifiers: React.PropTypes.array.isRequired,
- refresh: React.PropTypes.func.isRequired
- },
-
- showGroups(permission, e) {
- e.preventDefault();
- new GroupsView({
- permission,
- permissionTemplate: this.props.permissionTemplate,
- refresh: this.props.refresh
- }).render();
- },
-
- showUsers(permission, e) {
- e.preventDefault();
- new UsersView({
- permission,
- permissionTemplate: this.props.permissionTemplate,
- refresh: this.props.refresh
- }).render();
- },
-
- onUpdate(e) {
- e.preventDefault();
- new UpdateView({
- model: new Backbone.Model(this.props.permissionTemplate),
- refresh: this.props.refresh
- }).render();
- },
-
- onDelete(e) {
- e.preventDefault();
- new DeleteView({
- model: new Backbone.Model(this.props.permissionTemplate),
- refresh: this.props.refresh
- }).render();
- },
-
- renderAssociation() {
- const projectKeyPattern = this.props.permissionTemplate.projectKeyPattern;
- if (!projectKeyPattern) {
- return null;
- }
- return <div className="spacer-bottom">Project Key Pattern: <code>{projectKeyPattern}</code></div>;
- },
-
- renderDeleteButton() {
- if (_.size(this.props.permissionTemplate.defaultFor) > 0) {
- return null;
- }
- return <button onClick={this.onDelete} className="button-red">Delete</button>;
- },
-
- render() {
- const permissions = this.props.permissionTemplate.permissions.map(p => {
- return (
- <td key={p.key}>
- <table>
- <tbody>
- <tr>
- <td className="spacer-right">Users</td>
- <td className="spacer-left bordered-left">{p.usersCount}</td>
- <td className="spacer-left">
- <a onClick={this.showUsers.bind(this, p)} className="icon-bullet-list" title="Update Users"
- data-toggle="tooltip" href="#"></a>
- </td>
- </tr>
- <tr>
- <td className="spacer-right">Groups</td>
- <td className="spacer-left bordered-left">{p.groupsCount}</td>
- <td className="spacer-left">
- <a onClick={this.showGroups.bind(this, p)} className="icon-bullet-list" title="Update Users"
- data-toggle="tooltip" href="#"></a>
- </td>
- </tr>
- </tbody>
- </table>
- </td>
- );
- });
- return (
- <tr>
- <td>
- <strong>{this.props.permissionTemplate.name}</strong>
- <p className="note little-spacer-top">{this.props.permissionTemplate.description}</p>
- </td>
- {permissions}
- <td className="thin text-right">
- {this.renderAssociation()}
- <Defaults
- permissionTemplate={this.props.permissionTemplate}
- topQualifiers={this.props.topQualifiers}/>
- <div className="nowrap">
- <SetDefaults
- permissionTemplate={this.props.permissionTemplate}
- topQualifiers={this.props.topQualifiers}
- refresh={this.props.refresh}/>
-
- <div className="button-group">
- <button onClick={this.onUpdate}>Update</button>
- {this.renderDeleteButton()}
- </div>
- </div>
- </td>
- </tr>
- );
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/propTypes.js b/server/sonar-web/src/main/js/apps/permission-templates/propTypes.js
new file mode 100644
index 00000000000..5bc6119de09
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permission-templates/propTypes.js
@@ -0,0 +1,40 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 { PropTypes } from 'react';
+
+const { shape, arrayOf, string, number, func } = PropTypes;
+
+export const PermissionType = shape({
+ key: string.isRequired,
+ name: string.isRequired,
+ description: string.isRequired,
+ usersCount: number.isRequired,
+ groupsCount: number.isRequired
+});
+
+export const PermissionTemplateType = shape({
+ id: string.isRequired,
+ name: string.isRequired,
+ description: string,
+ permissions: arrayOf(PermissionType).isRequired,
+ defaultFor: arrayOf(string).isRequired
+});
+
+export const CallbackType = func.isRequired;
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/styles.css b/server/sonar-web/src/main/js/apps/permission-templates/styles.css
new file mode 100644
index 00000000000..a19119a6da3
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permission-templates/styles.css
@@ -0,0 +1,17 @@
+.permissions-table {
+ table-layout: fixed;
+}
+
+.permissions-table > tbody > tr > td {
+ border-bottom: 20px solid #fff !important;
+}
+
+.permissions-table .permission-column {
+ width: 130px;
+ white-space: nowrap;
+}
+
+.permissions-table .actions-column {
+ width: 130px;
+ text-align: right;
+}
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/templates/permission-templates-form.hbs b/server/sonar-web/src/main/js/apps/permission-templates/templates/permission-templates-form.hbs
index 3478f988bdd..02ef4004747 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/templates/permission-templates-form.hbs
+++ b/server/sonar-web/src/main/js/apps/permission-templates/templates/permission-templates-form.hbs
@@ -8,6 +8,9 @@
<div class="modal-field">
<label for="permission-template-name">Name<em class="mandatory">*</em></label>
<input id="permission-template-name" name="name" type="text" maxlength="256" required value="{{name}}">
+ <div class="modal-field-description">
+ Should be unique.
+ </div>
</div>
<div class="modal-field">
@@ -19,6 +22,9 @@
<label for="permission-template-project-key-pattern">Project Key Pattern</label>
<input id="permission-template-project-key-pattern" name="keyPattern" type="text" maxlength="500"
value="{{projectKeyPattern}}">
+ <div class="modal-field-description">
+ Should be a valid regular expression.
+ </div>
</div>
</div>
<div class="modal-foot">
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/utils.js b/server/sonar-web/src/main/js/apps/permission-templates/utils.js
new file mode 100644
index 00000000000..c475b39b3a7
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permission-templates/utils.js
@@ -0,0 +1,71 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 sortBy from 'lodash/sortBy';
+
+export const PERMISSIONS_ORDER = ['user', 'codeviewer', 'issueadmin', 'admin', 'scan'];
+
+/**
+ * Sort list of permissions based on predefined order
+ * @param {Array} permissions
+ * @returns {Array}
+ */
+export function sortPermissions (permissions) {
+ return sortBy(permissions, p => PERMISSIONS_ORDER.indexOf(p.key));
+}
+
+/**
+ * Populate permissions' details in the list of permission templates
+ * @param {Array} permissionTemplates
+ * @param {Array} basePermissions
+ * @returns {Array}
+ */
+export function mergePermissionsToTemplates (permissionTemplates, basePermissions) {
+ return permissionTemplates.map(permissionTemplate => {
+
+ // it's important to keep the order of the permission template's permissions
+ // the same as the order of base permissions
+ const permissions = basePermissions.map(basePermission => {
+ const projectPermission = permissionTemplate.permissions.find(p => p.key === basePermission.key);
+ return { usersCount: 0, groupsCount: 0, ...basePermission, ...projectPermission };
+ });
+
+ return { ...permissionTemplate, permissions };
+ });
+}
+
+/**
+ * Mark default templates
+ * @param {Array} permissionTemplates
+ * @param {Array} defaultTemplates
+ * @returns {Array}
+ */
+export function mergeDefaultsToTemplates (permissionTemplates, defaultTemplates = []) {
+ return permissionTemplates.map(permissionTemplate => {
+ const defaultFor = [];
+
+ defaultTemplates.forEach(defaultTemplate => {
+ if (defaultTemplate.templateId === permissionTemplate.id) {
+ defaultFor.push(defaultTemplate.qualifier);
+ }
+ });
+
+ return { ...permissionTemplate, defaultFor };
+ });
+}
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/create-view.js b/server/sonar-web/src/main/js/apps/permission-templates/views/CreateView.js
index 9e35862878f..516373a0aa9 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/create-view.js
+++ b/server/sonar-web/src/main/js/apps/permission-templates/views/CreateView.js
@@ -17,8 +17,8 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import FormView from './form-view';
-import { createPermissionTemplate } from '../../api/permissions';
+import FormView from './FormView';
+import { createPermissionTemplate } from '../../../api/permissions';
export default FormView.extend({
sendRequest () {
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/delete-view.js b/server/sonar-web/src/main/js/apps/permission-templates/views/DeleteView.js
index 7c7dfac5593..84b645e4971 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/delete-view.js
+++ b/server/sonar-web/src/main/js/apps/permission-templates/views/DeleteView.js
@@ -17,9 +17,9 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import ModalForm from '../../components/common/modal-form';
-import { deletePermissionTemplate } from '../../api/permissions';
-import Template from './templates/permission-templates-delete.hbs';
+import ModalForm from '../../../components/common/modal-form';
+import { deletePermissionTemplate } from '../../../api/permissions';
+import Template from '../templates/permission-templates-delete.hbs';
export default ModalForm.extend({
template: Template,
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/form-view.js b/server/sonar-web/src/main/js/apps/permission-templates/views/FormView.js
index a94290e1604..40e77062e8e 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/form-view.js
+++ b/server/sonar-web/src/main/js/apps/permission-templates/views/FormView.js
@@ -17,8 +17,8 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import ModalForm from '../../components/common/modal-form';
-import Template from './templates/permission-templates-form.hbs';
+import ModalForm from '../../../components/common/modal-form';
+import Template from '../templates/permission-templates-form.hbs';
export default ModalForm.extend({
template: Template,
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/groups-view.js b/server/sonar-web/src/main/js/apps/permission-templates/views/GroupsView.js
index 9c8f359aedc..56ad56e4a41 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/groups-view.js
+++ b/server/sonar-web/src/main/js/apps/permission-templates/views/GroupsView.js
@@ -18,9 +18,9 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import _ from 'underscore';
-import Modal from '../../components/common/modals';
-import '../../components/SelectList';
-import Template from './templates/permission-templates-groups.hbs';
+import Modal from '../../../components/common/modals';
+import Template from '../templates/permission-templates-groups.hbs';
+import '../../../components/SelectList';
function getSearchUrl (permission, permissionTemplate) {
return window.baseUrl +
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/update-view.js b/server/sonar-web/src/main/js/apps/permission-templates/views/UpdateView.js
index d738e9286e2..99acd983e5f 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/update-view.js
+++ b/server/sonar-web/src/main/js/apps/permission-templates/views/UpdateView.js
@@ -17,8 +17,8 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import FormView from './form-view';
-import { updatePermissionTemplate } from '../../api/permissions';
+import FormView from './FormView';
+import { updatePermissionTemplate } from '../../../api/permissions';
export default FormView.extend({
sendRequest () {
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/users-view.js b/server/sonar-web/src/main/js/apps/permission-templates/views/UsersView.js
index b14fbdef464..c54b673c981 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/users-view.js
+++ b/server/sonar-web/src/main/js/apps/permission-templates/views/UsersView.js
@@ -18,9 +18,9 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import _ from 'underscore';
-import Modal from '../../components/common/modals';
-import '../../components/SelectList';
-import Template from './templates/permission-templates-users.hbs';
+import Modal from '../../../components/common/modals';
+import Template from '../templates/permission-templates-users.hbs';
+import '../../../components/SelectList';
export default Modal.extend({
template: Template,
diff --git a/server/sonar-web/src/main/js/apps/project-permissions/main.js b/server/sonar-web/src/main/js/apps/project-permissions/main.js
index 499cd2f649e..eb947f85776 100644
--- a/server/sonar-web/src/main/js/apps/project-permissions/main.js
+++ b/server/sonar-web/src/main/js/apps/project-permissions/main.js
@@ -26,6 +26,8 @@ import PermissionsFooter from './permissions-footer';
import Search from './search';
import ApplyTemplateView from './apply-template-view';
import { translate } from '../../helpers/l10n';
+import { TooltipsContainer } from '../../components/mixins/tooltips-mixin';
+import '../permission-templates/styles.css';
const PERMISSIONS_ORDER = ['user', 'codeviewer', 'issueadmin', 'admin', 'scan'];
@@ -130,34 +132,38 @@ export default React.createClass({
render() {
return (
- <div className="page">
- <header id="project-permissions-header" className="page-header">
- <h1 className="page-title">{translate('roles.page')}</h1>
- {this.renderSpinner()}
- <div className="page-actions">
- {this.renderBulkApplyButton()}
- </div>
- <p className="page-description">{translate('roles.page.description2')}</p>
- </header>
-
- <Search {...this.props}
- filter={this.state.filter}
- search={this.search}
- onFilter={this.handleFilter}/>
-
- <Permissions
- ready={this.state.ready}
- projects={this.state.projects}
- permissions={this.state.permissions}
- permissionTemplates={this.props.permissionTemplates}
- refresh={this.refresh}/>
-
- <PermissionsFooter {...this.props}
- ready={this.state.ready}
- count={this.state.projects.length}
- total={this.state.total}
- loadMore={this.loadMore}/>
- </div>
+ <TooltipsContainer>
+ <div className="page">
+ <header id="project-permissions-header" className="page-header">
+ <h1 className="page-title">{translate('roles.page')}</h1>
+ {this.renderSpinner()}
+ <div className="page-actions">
+ {this.renderBulkApplyButton()}
+ </div>
+ <p className="page-description">
+ {translate('roles.page.description2')}
+ </p>
+ </header>
+
+ <Search {...this.props}
+ filter={this.state.filter}
+ search={this.search}
+ onFilter={this.handleFilter}/>
+
+ <Permissions
+ ready={this.state.ready}
+ projects={this.state.projects}
+ permissions={this.state.permissions}
+ permissionTemplates={this.props.permissionTemplates}
+ refresh={this.refresh}/>
+
+ <PermissionsFooter {...this.props}
+ ready={this.state.ready}
+ count={this.state.projects.length}
+ total={this.state.total}
+ loadMore={this.loadMore}/>
+ </div>
+ </TooltipsContainer>
);
}
});
diff --git a/server/sonar-web/src/main/js/apps/project-permissions/permissions-header.js b/server/sonar-web/src/main/js/apps/project-permissions/permissions-header.js
index a459d2f60c1..281616aaea9 100644
--- a/server/sonar-web/src/main/js/apps/project-permissions/permissions-header.js
+++ b/server/sonar-web/src/main/js/apps/project-permissions/permissions-header.js
@@ -25,21 +25,23 @@ export default React.createClass({
},
render() {
- const cellWidth = (80 / this.props.permissions.length) + '%';
- const cells = this.props.permissions.map(p => {
- return (
- <th key={p.key} style={{ width: cellWidth }}>
- {p.name}<br/><span className="small">{p.description}</span>
- </th>
- );
- });
+ const cells = this.props.permissions.map(p => (
+ <th key={p.key} className="permission-column">
+ {p.name}
+ <i
+ className="icon-help little-spacer-left"
+ title={p.description}
+ data-toggle="tooltip"/>
+ </th>
+ ));
+
return (
<thead>
- <tr>
- <th style={{ width: '20%' }}>&nbsp;</th>
- {cells}
- <th className="thin">&nbsp;</th>
- </tr>
+ <tr>
+ <th>&nbsp;</th>
+ {cells}
+ <th className="actions-column">&nbsp;</th>
+ </tr>
</thead>
);
}
diff --git a/server/sonar-web/src/main/js/apps/project-permissions/permissions.js b/server/sonar-web/src/main/js/apps/project-permissions/permissions.js
index 16789fce35d..c10323e8419 100644
--- a/server/sonar-web/src/main/js/apps/project-permissions/permissions.js
+++ b/server/sonar-web/src/main/js/apps/project-permissions/permissions.js
@@ -39,7 +39,9 @@ export default React.createClass({
permissionTemplates={this.props.permissionTemplates}
refresh={this.props.refresh}/>;
});
- const className = classNames('data zebra', { 'new-loading': !this.props.ready });
+ const className = classNames(
+ 'data zebra permissions-table',
+ { 'new-loading': !this.props.ready });
return (
<table id="projects" className={className}>
<PermissionsHeader permissions={this.props.permissions}/>
diff --git a/server/sonar-web/src/main/less/init/icons.less b/server/sonar-web/src/main/less/init/icons.less
index 375d4170c05..190fcba48c2 100644
--- a/server/sonar-web/src/main/less/init/icons.less
+++ b/server/sonar-web/src/main/less/init/icons.less
@@ -548,7 +548,7 @@ a[class^="icon-"], a[class*=" icon-"] {
}
.icon-edit:before {
content: "\f040";
- font-size: @iconFontSize;
+ font-size: @iconSmallFontSize;
}
.icon-ellipsis-h:before {
position: relative;