aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2015-09-14 14:53:09 +0200
committerStas Vilchik <vilchiks@gmail.com>2015-09-14 15:00:24 +0200
commita997f8804af48e789e7554405b01ca27c0daaae6 (patch)
tree2be731299d6529c7b9271076d4e6e7a6a86323c0
parentfd64661844eadbce52506f169ad8d396905da15b (diff)
downloadsonarqube-a997f8804af48e789e7554405b01ca27c0daaae6.tar.gz
sonarqube-a997f8804af48e789e7554405b01ca27c0daaae6.zip
SONAR-6802 SONAR-6803 add an ability to apply a permission template to projects
-rw-r--r--server/sonar-web/src/main/js/api/permissions.jsx6
-rw-r--r--server/sonar-web/src/main/js/apps/project-permissions/app.jsx18
-rw-r--r--server/sonar-web/src/main/js/apps/project-permissions/apply-template-view.jsx50
-rw-r--r--server/sonar-web/src/main/js/apps/project-permissions/main.jsx21
-rw-r--r--server/sonar-web/src/main/js/apps/project-permissions/permissions-header.jsx1
-rw-r--r--server/sonar-web/src/main/js/apps/project-permissions/permissions.jsx7
-rw-r--r--server/sonar-web/src/main/js/apps/project-permissions/project.jsx14
-rw-r--r--server/sonar-web/src/main/js/apps/project-permissions/templates/project-permissions-apply-template.hbs25
-rw-r--r--server/sonar-web/src/test/json/permissions/permission-templates.json89
-rw-r--r--server/sonar-web/src/test/json/permissions/project-permissions-changed.json55
-rw-r--r--server/sonar-web/test/medium/project-permissions.spec.js47
11 files changed, 325 insertions, 8 deletions
diff --git a/server/sonar-web/src/main/js/api/permissions.jsx b/server/sonar-web/src/main/js/api/permissions.jsx
index fb565c22c32..3f4fc520ba7 100644
--- a/server/sonar-web/src/main/js/api/permissions.jsx
+++ b/server/sonar-web/src/main/js/api/permissions.jsx
@@ -90,3 +90,9 @@ export function revokeFromGroup(permission, group, project) {
}
return _request({ type: 'POST', url: url, data: data });
}
+
+
+export function applyTemplateToProject(options) {
+ let url = _url('/api/permissions/apply_template');
+ return _request(_.extend({ type: 'POST', url: url }, options));
+}
diff --git a/server/sonar-web/src/main/js/apps/project-permissions/app.jsx b/server/sonar-web/src/main/js/apps/project-permissions/app.jsx
index 28dc73b7f42..85dc51eaf02 100644
--- a/server/sonar-web/src/main/js/apps/project-permissions/app.jsx
+++ b/server/sonar-web/src/main/js/apps/project-permissions/app.jsx
@@ -1,13 +1,23 @@
+import $ from 'jquery';
import React from 'react';
import Main from './main';
-const $ = jQuery;
+let permissionTemplates = [];
export default {
start(options) {
- window.requestMessages().done(() => {
- var el = document.querySelector(options.el);
- React.render(<Main/>, el);
+ $.when(
+ window.requestMessages(),
+ this.requestPermissionTemplates()
+ ).then(() => {
+ var el = document.querySelector(options.el);
+ React.render(<Main permissionTemplates={permissionTemplates}/>, el);
+ });
+ },
+
+ requestPermissionTemplates() {
+ return $.get(baseUrl + '/api/permissions/search_templates').done(r => {
+ permissionTemplates = r.permissionTemplates;
});
}
};
diff --git a/server/sonar-web/src/main/js/apps/project-permissions/apply-template-view.jsx b/server/sonar-web/src/main/js/apps/project-permissions/apply-template-view.jsx
new file mode 100644
index 00000000000..9bcd05d8e13
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/project-permissions/apply-template-view.jsx
@@ -0,0 +1,50 @@
+import $ from 'jquery';
+import ModalForm from '../../components/common/modal-form';
+import {applyTemplateToProject} from '../../api/permissions';
+import './templates';
+
+export default ModalForm.extend({
+ template: Templates['project-permissions-apply-template'],
+
+ onRender: function () {
+ ModalForm.prototype.onRender.apply(this, arguments);
+ this.$('#project-permissions-template').select2({
+ width: '250px',
+ minimumResultsForSearch: 20
+ });
+ },
+
+ onFormSubmit: function () {
+ ModalForm.prototype.onFormSubmit.apply(this, arguments);
+ var that = this;
+ this.disableForm();
+
+ var projects = this.options.project ? [this.options.project] : this.options.projects,
+ permissionTemplate = this.$('#project-permissions-template').val(),
+ looper = $.Deferred().resolve();
+
+ projects.forEach(function (project) {
+ looper = looper.then(function () {
+ return applyTemplateToProject({
+ data: { projectId: project.id, templateId: permissionTemplate }
+ });
+ });
+ });
+
+ looper.done(function () {
+ that.options.refresh();
+ that.destroy();
+ }).fail(function (jqXHR) {
+ that.enableForm();
+ that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
+ });
+ },
+
+ serializeData: function () {
+ return _.extend(ModalForm.prototype.serializeData.apply(this, arguments), {
+ permissionTemplates: this.options.permissionTemplates,
+ project: this.options.project,
+ projectsCount: _.size(this.options.projects)
+ });
+ }
+});
diff --git a/server/sonar-web/src/main/js/apps/project-permissions/main.jsx b/server/sonar-web/src/main/js/apps/project-permissions/main.jsx
index 7862f60f0b4..479b50fde46 100644
--- a/server/sonar-web/src/main/js/apps/project-permissions/main.jsx
+++ b/server/sonar-web/src/main/js/apps/project-permissions/main.jsx
@@ -1,14 +1,18 @@
+import $ from 'jquery';
import _ from 'underscore';
import React from 'react';
import Permissions from './permissions';
import PermissionsFooter from './permissions-footer';
import Search from './search';
-
-let $ = jQuery;
+import ApplyTemplateView from './apply-template-view';
const PERMISSIONS_ORDER = ['user', 'codeviewer', 'issueadmin', 'admin'];
export default React.createClass({
+ propTypes: {
+ permissionTemplates: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
+ },
+
getInitialState() {
return { permissions: [], projects: [], total: 0 };
},
@@ -59,11 +63,23 @@ export default React.createClass({
this.requestPermissions(1, query);
},
+ bulkApplyTemplate(e) {
+ e.preventDefault();
+ new ApplyTemplateView({
+ projects: this.state.projects,
+ permissionTemplates: this.props.permissionTemplates,
+ refresh: this.requestPermissions
+ }).render();
+ },
+
render() {
return (
<div className="page">
<header id="project-permissions-header" className="page-header">
<h1 className="page-title">{window.t('roles.page')}</h1>
+ <div className="page-actions">
+ <button onClick={this.bulkApplyTemplate} className="js-bulk-apply-template">Bulk Apply Template</button>
+ </div>
<p className="page-description">{window.t('roles.page.description2')}</p>
</header>
@@ -73,6 +89,7 @@ export default React.createClass({
<Permissions
projects={this.state.projects}
permissions={this.state.permissions}
+ permissionTemplates={this.props.permissionTemplates}
refresh={this.requestPermissions}/>
<PermissionsFooter
diff --git a/server/sonar-web/src/main/js/apps/project-permissions/permissions-header.jsx b/server/sonar-web/src/main/js/apps/project-permissions/permissions-header.jsx
index a68cca21fc2..7768f9ec414 100644
--- a/server/sonar-web/src/main/js/apps/project-permissions/permissions-header.jsx
+++ b/server/sonar-web/src/main/js/apps/project-permissions/permissions-header.jsx
@@ -19,6 +19,7 @@ export default React.createClass({
<tr>
<th style={{ width: '20%' }}>&nbsp;</th>
{cells}
+ <th className="thin">&nbsp;</th>
</tr>
</thead>
);
diff --git a/server/sonar-web/src/main/js/apps/project-permissions/permissions.jsx b/server/sonar-web/src/main/js/apps/project-permissions/permissions.jsx
index 4ae679f345c..26da7da40d6 100644
--- a/server/sonar-web/src/main/js/apps/project-permissions/permissions.jsx
+++ b/server/sonar-web/src/main/js/apps/project-permissions/permissions.jsx
@@ -6,12 +6,17 @@ export default React.createClass({
propTypes:{
projects: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
permissions: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
+ permissionTemplates: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
refresh: React.PropTypes.func.isRequired
},
render() {
let projects = this.props.projects.map(p => {
- return <Project key={p.id} project={p} refresh={this.props.refresh}/>
+ return <Project
+ key={p.id}
+ project={p}
+ permissionTemplates={this.props.permissionTemplates}
+ refresh={this.props.refresh}/>;
});
return (
<table id="projects" className="data zebra">
diff --git a/server/sonar-web/src/main/js/apps/project-permissions/project.jsx b/server/sonar-web/src/main/js/apps/project-permissions/project.jsx
index b0da14dcb18..b8f2d66ad31 100644
--- a/server/sonar-web/src/main/js/apps/project-permissions/project.jsx
+++ b/server/sonar-web/src/main/js/apps/project-permissions/project.jsx
@@ -1,11 +1,13 @@
import React from 'react';
import UsersView from './users-view';
import GroupsView from './groups-view';
+import ApplyTemplateView from './apply-template-view';
import {getProjectUrl} from '../../helpers/Url';
export default React.createClass({
propTypes: {
project: React.PropTypes.object.isRequired,
+ permissionTemplates: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
refresh: React.PropTypes.func.isRequired
},
@@ -29,6 +31,15 @@ export default React.createClass({
}).render();
},
+ applyTemplate(e) {
+ e.preventDefault();
+ new ApplyTemplateView({
+ permissionTemplates: this.props.permissionTemplates,
+ project: this.props.project,
+ refresh: this.props.refresh
+ }).render();
+ },
+
render() {
let permissions = this.props.project.permissions.map(p => {
return (
@@ -62,6 +73,9 @@ export default React.createClass({
</strong>
</td>
{permissions}
+ <td className="thin nowrap text-right">
+ <button onClick={this.applyTemplate} className="js-apply-template">Apply Template</button>
+ </td>
</tr>
);
}
diff --git a/server/sonar-web/src/main/js/apps/project-permissions/templates/project-permissions-apply-template.hbs b/server/sonar-web/src/main/js/apps/project-permissions/templates/project-permissions-apply-template.hbs
new file mode 100644
index 00000000000..25f494340e0
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/project-permissions/templates/project-permissions-apply-template.hbs
@@ -0,0 +1,25 @@
+<form id="project-permissions-apply-template-form" autocomplete="off">
+ <div class="modal-head">
+ {{#if project}}
+ <h2>Apply Permission Template to "{{project.name}}"</h2>
+ {{else}}
+ <h2>Bulk Apply Permission Template</h2>
+ {{/if}}
+ </div>
+ <div class="modal-body">
+ <div class="js-modal-messages"></div>
+
+ <div class="modal-field">
+ <label for="project-permissions-template">Template<em class="mandatory">*</em></label>
+ <select id="project-permissions-template">
+ {{#each permissionTemplates}}
+ <option value="{{id}}">{{name}}</option>
+ {{/each}}
+ </select>
+ </div>
+ </div>
+ <div class="modal-foot">
+ <button id="project-permissions-apply-template">Apply</button>
+ <a href="#" class="js-modal-close">Cancel</a>
+ </div>
+</form>
diff --git a/server/sonar-web/src/test/json/permissions/permission-templates.json b/server/sonar-web/src/test/json/permissions/permission-templates.json
new file mode 100644
index 00000000000..b34f812102d
--- /dev/null
+++ b/server/sonar-web/src/test/json/permissions/permission-templates.json
@@ -0,0 +1,89 @@
+{
+ "permissionTemplates": [
+ {
+ "id": "default_template",
+ "name": "Default template",
+ "description": "This permission template will be used as default when no other permission configuration is available",
+ "projectKeyPattern": "abc.*",
+ "createdAt": "2015-08-07T15:30:59+0200",
+ "updatedAt": "2015-09-14T12:56:21+0200",
+ "permissions": [
+ {
+ "key": "admin",
+ "usersCount": 0,
+ "groupsCount": 1
+ },
+ {
+ "key": "codeviewer",
+ "usersCount": 0,
+ "groupsCount": 1
+ },
+ {
+ "key": "issueadmin",
+ "usersCount": 0,
+ "groupsCount": 1
+ },
+ {
+ "key": "user",
+ "usersCount": 0,
+ "groupsCount": 1
+ }
+ ]
+ },
+ {
+ "id": "test_20150820_133222",
+ "name": "test",
+ "description": "",
+ "projectKeyPattern": "Javascript*",
+ "createdAt": "2015-08-20T13:32:22+0200",
+ "updatedAt": "2015-09-14T12:07:26+0200",
+ "permissions": [
+ {
+ "key": "user",
+ "usersCount": 1,
+ "groupsCount": 0
+ }
+ ]
+ },
+ {
+ "id": "AU_Lp7-59jciAH96Rd8H",
+ "name": "test2",
+ "description": "Default permission template",
+ "projectKeyPattern": "",
+ "createdAt": "2015-09-14T13:40:12+0200",
+ "updatedAt": "2015-09-14T13:41:23+0200"
+ }
+ ],
+ "defaultTemplates": [
+ {
+ "templateId": "test_20150820_133222",
+ "qualifier": "VW"
+ },
+ {
+ "templateId": "AU_Lp7-59jciAH96Rd8H",
+ "qualifier": "TRK"
+ }
+ ],
+ "permissions": [
+ {
+ "key": "user",
+ "name": "Browse",
+ "description": "Access a project, browse its measures, and create/edit issues for it."
+ },
+ {
+ "key": "admin",
+ "name": "Administer",
+ "description": "Access project settings and perform administration tasks. (Users will also need \"Browse\" permission)"
+ },
+ {
+ "key": "issueadmin",
+ "name": "Administer Issues",
+ "description": "Perform advanced editing on issues: marking an issue False Positive / Won't Fix, and changing an Issue's severity. (Users will also need \"Browse\" permission)"
+ },
+ {
+ "key": "codeviewer",
+ "name": "See Source Code",
+ "description": "View the project's source code. (Users will also need \"Browse\" permission)"
+ }
+ ]
+}
diff --git a/server/sonar-web/src/test/json/permissions/project-permissions-changed.json b/server/sonar-web/src/test/json/permissions/project-permissions-changed.json
new file mode 100644
index 00000000000..39ad975d06d
--- /dev/null
+++ b/server/sonar-web/src/test/json/permissions/project-permissions-changed.json
@@ -0,0 +1,55 @@
+{
+ "projects": [
+ {
+ "id": "10c394cc-c37c-4cf0-97b9-360165c47270",
+ "key": "my-project",
+ "name": "My Project",
+ "permissions": [
+ {
+ "key": "admin",
+ "usersCount": 11,
+ "groupsCount": 12
+ },
+ {
+ "key": "codeviewer",
+ "usersCount": 13,
+ "groupsCount": 14
+ }
+ ]
+ },
+ {
+ "id": "5d2408c9-b5c6-4426-8ee8-05be930a5f62",
+ "key": "another-project",
+ "name": "Another Project",
+ "permissions": [
+ {
+ "key": "admin",
+ "usersCount": 15,
+ "groupsCount": 16
+ },
+ {
+ "key": "codeviewer",
+ "usersCount": 17,
+ "groupsCount": 18
+ }
+ ]
+ }
+ ],
+ "permissions": [
+ {
+ "key": "admin",
+ "name": "Administer",
+ "description": "Ability to access project settings and perform administration tasks. (Users will also need \"Browse\" permission)"
+ },
+ {
+ "key": "codeviewer",
+ "name": "See Source Code",
+ "description": "Ability to view the project's source code. (Users will also need \"Browse\" permission)"
+ }
+ ],
+ "paging": {
+ "pageIndex": 1,
+ "pageSize": 25,
+ "total": 2
+ }
+}
diff --git a/server/sonar-web/test/medium/project-permissions.spec.js b/server/sonar-web/test/medium/project-permissions.spec.js
index f5f24fcec57..f26b9d1d53b 100644
--- a/server/sonar-web/test/medium/project-permissions.spec.js
+++ b/server/sonar-web/test/medium/project-permissions.spec.js
@@ -7,10 +7,11 @@ define(function (require) {
return this.remote
.open()
.mockFromFile('/api/permissions/search_project_permissions', 'permissions/project-permissions.json')
+ .mockFromFile('/api/permissions/search_templates', 'permissions/permission-templates.json')
.startApp('project-permissions')
.checkElementExist('#project-permissions-header')
.checkElementExist('#projects')
- .checkElementCount('#projects > thead > tr > th', 3)
+ .checkElementCount('#projects > thead > tr > th', 4)
.checkElementCount('#projects > tbody > tr', 2)
.checkElementInclude('#projects > tbody > tr:first-child td:nth-child(1)', 'My Project')
.checkElementInclude('#projects > tbody > tr:first-child td:nth-child(2)', '3')
@@ -18,5 +19,49 @@ define(function (require) {
.checkElementInclude('#projects > tbody > tr:first-child td:nth-child(3)', '1')
.checkElementInclude('#projects > tbody > tr:first-child td:nth-child(3)', '2');
});
+
+ bdd.it('should apply a permission template', function () {
+ return this.remote
+ .open()
+ .mockFromFile('/api/permissions/search_project_permissions', 'permissions/project-permissions.json')
+ .mockFromFile('/api/permissions/search_templates', 'permissions/permission-templates.json')
+ .startApp('project-permissions')
+ .checkElementInclude('#projects > tbody > tr:first-child td:nth-child(1)', 'My Project')
+ .checkElementInclude('#projects > tbody > tr:first-child td:nth-child(2)', '3')
+ .checkElementInclude('#projects > tbody > tr:first-child td:nth-child(2)', '4')
+ .checkElementInclude('#projects > tbody > tr:first-child td:nth-child(3)', '1')
+ .checkElementInclude('#projects > tbody > tr:first-child td:nth-child(3)', '2')
+ .clearMocks()
+ .mockFromFile('/api/permissions/search_project_permissions', 'permissions/project-permissions-changed.json')
+ .mockFromString('/api/permissions/apply_template', '{}')
+ .clickElement('#projects > tbody > tr:first-child .js-apply-template')
+ .clickElement('#project-permissions-apply-template')
+ .checkElementInclude('#projects > tbody > tr:first-child td:nth-child(2)', '13')
+ .checkElementInclude('#projects > tbody > tr:first-child td:nth-child(2)', '14')
+ .checkElementInclude('#projects > tbody > tr:first-child td:nth-child(3)', '11')
+ .checkElementInclude('#projects > tbody > tr:first-child td:nth-child(3)', '12');
+ });
+
+ bdd.it('should bulk apply a permission template', function () {
+ return this.remote
+ .open()
+ .mockFromFile('/api/permissions/search_project_permissions', 'permissions/project-permissions.json')
+ .mockFromFile('/api/permissions/search_templates', 'permissions/permission-templates.json')
+ .startApp('project-permissions')
+ .checkElementInclude('#projects > tbody > tr:first-child td:nth-child(1)', 'My Project')
+ .checkElementInclude('#projects > tbody > tr:first-child td:nth-child(2)', '3')
+ .checkElementInclude('#projects > tbody > tr:first-child td:nth-child(2)', '4')
+ .checkElementInclude('#projects > tbody > tr:first-child td:nth-child(3)', '1')
+ .checkElementInclude('#projects > tbody > tr:first-child td:nth-child(3)', '2')
+ .clearMocks()
+ .mockFromFile('/api/permissions/search_project_permissions', 'permissions/project-permissions-changed.json')
+ .mockFromString('/api/permissions/apply_template', '{}')
+ .clickElement('.js-bulk-apply-template')
+ .clickElement('#project-permissions-apply-template')
+ .checkElementInclude('#projects > tbody > tr:first-child td:nth-child(2)', '13')
+ .checkElementInclude('#projects > tbody > tr:first-child td:nth-child(2)', '14')
+ .checkElementInclude('#projects > tbody > tr:first-child td:nth-child(3)', '11')
+ .checkElementInclude('#projects > tbody > tr:first-child td:nth-child(3)', '12');
+ });
});
});