diff options
author | Stas Vilchik <vilchiks@gmail.com> | 2015-09-14 14:53:09 +0200 |
---|---|---|
committer | Stas Vilchik <vilchiks@gmail.com> | 2015-09-14 15:00:24 +0200 |
commit | a997f8804af48e789e7554405b01ca27c0daaae6 (patch) | |
tree | 2be731299d6529c7b9271076d4e6e7a6a86323c0 | |
parent | fd64661844eadbce52506f169ad8d396905da15b (diff) | |
download | sonarqube-a997f8804af48e789e7554405b01ca27c0daaae6.tar.gz sonarqube-a997f8804af48e789e7554405b01ca27c0daaae6.zip |
SONAR-6802 SONAR-6803 add an ability to apply a permission template to projects
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%' }}> </th> {cells} + <th className="thin"> </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'); + }); }); }); |