diff options
author | Stas Vilchik <vilchiks@gmail.com> | 2016-07-11 17:06:07 +0200 |
---|---|---|
committer | Stas Vilchik <vilchiks@gmail.com> | 2016-07-13 18:12:50 +0200 |
commit | 75efda3dd83b392b39a09ccf61b848a141fec9e2 (patch) | |
tree | fa7a3cde5a872b9d774d2d5ff0cd7ec7b65b3a4f /server/sonar-web/src/main/js | |
parent | 10c0d319d02d984c2a075311006434ff3c8251f5 (diff) | |
download | sonarqube-75efda3dd83b392b39a09ccf61b848a141fec9e2.tar.gz sonarqube-75efda3dd83b392b39a09ccf61b848a141fec9e2.zip |
SONAR-7880 Put actions on permissions on the Projects Management page
Diffstat (limited to 'server/sonar-web/src/main/js')
9 files changed, 336 insertions, 53 deletions
diff --git a/server/sonar-web/src/main/js/api/permissions.js b/server/sonar-web/src/main/js/api/permissions.js index 80a77e56539..e7ea01ed4b7 100644 --- a/server/sonar-web/src/main/js/api/permissions.js +++ b/server/sonar-web/src/main/js/api/permissions.js @@ -114,9 +114,9 @@ export function applyTemplateToProject (data) { return post(url, data); } -export function bulkApplyTemplateToProject (options) { - const url = window.baseUrl + '/api/permissions/bulk_apply_template'; - return request(_.extend({ type: 'POST', url }, options)); +export function bulkApplyTemplate (data) { + const url = '/api/permissions/bulk_apply_template'; + return post(url, data); } export function addProjectCreatorToTemplate (templateName, permission) { diff --git a/server/sonar-web/src/main/js/apps/permissions/project/templates/ApplyTemplateTemplate.hbs b/server/sonar-web/src/main/js/apps/permissions/project/templates/ApplyTemplateTemplate.hbs index c7307670cbe..82c157e5d53 100644 --- a/server/sonar-web/src/main/js/apps/permissions/project/templates/ApplyTemplateTemplate.hbs +++ b/server/sonar-web/src/main/js/apps/permissions/project/templates/ApplyTemplateTemplate.hbs @@ -5,26 +5,37 @@ <div class="modal-body"> <div class="js-modal-messages"></div> - {{#notNull permissionTemplates}} - <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> + + {{#if done}} + <div class="alert alert-success"> + {{t 'projects_role.apply_template.success'}} </div> - {{else}} - <i class="spinner"></i> - {{/notNull}} + {{/if}} + + {{#unless done}} + {{#notNull permissionTemplates}} + <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> + {{else}} + <i class="spinner"></i> + {{/notNull}} + {{/unless}} </div> <div class="modal-foot"> - {{#notNull permissionTemplates}} - <button id="project-permissions-apply-template">Apply</button> - {{/notNull}} - <a href="#" class="js-modal-close">Cancel</a> + {{#unless done}} + {{#notNull permissionTemplates}} + <button id="project-permissions-apply-template">Apply</button> + {{/notNull}} + {{/unless}} + <a href="#" class="js-modal-close">Close</a> </div> </form> diff --git a/server/sonar-web/src/main/js/apps/permissions/project/views/ApplyTemplateView.js b/server/sonar-web/src/main/js/apps/permissions/project/views/ApplyTemplateView.js index 6aa2537c3f6..14817206db7 100644 --- a/server/sonar-web/src/main/js/apps/permissions/project/views/ApplyTemplateView.js +++ b/server/sonar-web/src/main/js/apps/permissions/project/views/ApplyTemplateView.js @@ -29,6 +29,7 @@ export default ModalForm.extend({ initialize () { this.loadPermissionTemplates(); + this.done = false; }, loadPermissionTemplates () { @@ -56,7 +57,8 @@ export default ModalForm.extend({ templateId: permissionTemplate }).then(() => { this.trigger('done'); - this.destroy(); + this.done = true; + this.render(); }).catch(function (e) { e.response.json().then(r => { this.showErrors(r.errors, r.warnings); @@ -68,7 +70,8 @@ export default ModalForm.extend({ serializeData () { return { permissionTemplates: this.permissionTemplates, - project: this.options.project + project: this.options.project, + done: this.done }; } }); diff --git a/server/sonar-web/src/main/js/apps/projects/header.js b/server/sonar-web/src/main/js/apps/projects/header.js index eb11aceb0c2..1d4200e3aa5 100644 --- a/server/sonar-web/src/main/js/apps/projects/header.js +++ b/server/sonar-web/src/main/js/apps/projects/header.js @@ -19,34 +19,65 @@ */ import React from 'react'; import CreateView from './create-view'; +import BulkApplyTemplateView from './views/BulkApplyTemplateView'; -export default React.createClass({ - propTypes: { +export default class Header extends React.Component { + static propTypes = { hasProvisionPermission: React.PropTypes.bool.isRequired - }, + }; - createProject() { + createProject () { new CreateView({ refresh: this.props.refresh }).render(); - }, + } + + bulkApplyTemplate () { + new BulkApplyTemplateView({ + total: this.props.total, + selection: this.props.selection, + query: this.props.query, + qualifier: this.props.qualifier + }).render(); + } - renderCreateButton() { + renderCreateButton () { if (!this.props.hasProvisionPermission) { return null; } - return <button onClick={this.createProject}>Create Project</button>; - }, + return ( + <li> + <button onClick={this.createProject.bind(this)}> + Create Project + </button> + </li> + ); + } + + renderBulkApplyTemplateButton () { + return ( + <li> + <button onClick={this.bulkApplyTemplate.bind(this)}> + Bulk Apply Permission Template + </button> + </li> + ); + } - render() { + render () { return ( <header className="page-header"> <h1 className="page-title">Projects Management</h1> - <div className="page-actions">{this.renderCreateButton()}</div> + <div className="page-actions"> + <ul className="list-inline"> + {this.renderCreateButton()} + {this.renderBulkApplyTemplateButton()} + </ul> + </div> <p className="page-description">Use this page to delete multiple projects at once, or to provision projects if you would like to configure them before the first analysis. Note that once a project is provisioned, you have access to perform all project configurations on it.</p> </header> ); } -}); +} diff --git a/server/sonar-web/src/main/js/apps/projects/main.js b/server/sonar-web/src/main/js/apps/projects/main.js index adfc332c8d1..900cfba2502 100644 --- a/server/sonar-web/src/main/js/apps/projects/main.js +++ b/server/sonar-web/src/main/js/apps/projects/main.js @@ -23,7 +23,12 @@ import Header from './header'; import Search from './search'; import Projects from './projects'; import { PAGE_SIZE, TYPE } from './constants'; -import { getComponents, getProvisioned, getGhosts, deleteComponents } from '../../api/components'; +import { + getComponents, + getProvisioned, + getGhosts, + deleteComponents +} from '../../api/components'; import ListFooter from '../../components/controls/ListFooter'; export default React.createClass({ @@ -77,7 +82,7 @@ export default React.createClass({ break; default: - // should never happen + // should never happen } }, @@ -120,7 +125,8 @@ export default React.createClass({ }, loadMore() { - this.setState({ ready: false, page: this.state.page + 1 }, this.requestProjects); + this.setState({ ready: false, page: this.state.page + 1 }, + this.requestProjects); }, onSearch(query) { @@ -187,6 +193,10 @@ export default React.createClass({ <div className="page"> <Header hasProvisionPermission={this.props.hasProvisionPermission} + selection={this.state.selection} + total={this.state.total} + query={this.state.query} + qualifier={this.state.qualifiers} refresh={this.requestProjects}/> <Search {...this.props} {...this.state} diff --git a/server/sonar-web/src/main/js/apps/projects/projects.js b/server/sonar-web/src/main/js/apps/projects/projects.js index a6c0949f642..140471de317 100644 --- a/server/sonar-web/src/main/js/apps/projects/projects.js +++ b/server/sonar-web/src/main/js/apps/projects/projects.js @@ -19,30 +19,47 @@ */ import classNames from 'classnames'; import React from 'react'; -import { getComponentUrl } from '../../helpers/urls'; +import { + getComponentUrl, + getComponentPermissionsUrl +} from '../../helpers/urls'; +import ApplyTemplateView from '../permissions/project/views/ApplyTemplateView'; import Checkbox from '../../components/controls/Checkbox'; import QualifierIcon from '../../components/shared/qualifier-icon'; +import { translate } from '../../helpers/l10n'; -export default React.createClass({ - propTypes: { +export default class Projects extends React.Component { + static propTypes = { projects: React.PropTypes.array.isRequired, selection: React.PropTypes.array.isRequired, refresh: React.PropTypes.func.isRequired - }, + }; - onProjectCheck(project, checked) { + componentWillMount () { + this.renderProject = this.renderProject.bind(this); + } + + onProjectCheck (project, checked) { if (checked) { this.props.onProjectSelected(project); } else { this.props.onProjectDeselected(project); } - }, + } - isProjectSelected(project) { + onApplyTemplateClick (project, e) { + e.preventDefault(); + e.target.blur(); + new ApplyTemplateView({ project }).render(); + } + + isProjectSelected (project) { return this.props.selection.indexOf(project.id) !== -1; - }, + } + + renderProject (project) { + const permissionsUrl = getComponentPermissionsUrl(project.key); - renderProject(project) { return ( <tr key={project.id}> <td className="thin"> @@ -50,25 +67,51 @@ export default React.createClass({ checked={this.isProjectSelected(project)} onCheck={this.onProjectCheck.bind(this, project)}/> </td> - <td className="thin"> - <QualifierIcon qualifier={project.qualifier}/> - </td> <td className="nowrap"> - <a href={getComponentUrl(project.key)}>{project.name}</a> + <a className="link-with-icon" href={getComponentUrl(project.key)}> + <QualifierIcon qualifier={project.qualifier}/> + {' '} + <span>{project.name}</span> + </a> </td> <td className="nowrap"> <span className="note">{project.key}</span> </td> + <td className="thin nowrap"> + <div className="dropdown"> + <button className="dropdown-toggle" data-toggle="dropdown"> + {translate('actions')} + {' '} + <i className="icon-dropdown"/> + </button> + <ul className="dropdown-menu dropdown-menu-right"> + <li> + <a href={permissionsUrl}> + {translate('edit_permissions')} + </a> + </li> + <li> + <a href={permissionsUrl} + onClick={this.onApplyTemplateClick.bind(this, project)}> + {translate('projects_role.apply_template')} + </a> + </li> + </ul> + </div> + </td> </tr> ); - }, + } + + render () { + const className = classNames('data', 'zebra', + { 'new-loading': !this.props.ready } + ); - render() { - const className = classNames('data', 'zebra', { 'new-loading': !this.props.ready }); return ( <table className={className}> <tbody>{this.props.projects.map(this.renderProject)}</tbody> </table> ); } -}); +} diff --git a/server/sonar-web/src/main/js/apps/projects/templates/BulkApplyTemplateTemplate.hbs b/server/sonar-web/src/main/js/apps/projects/templates/BulkApplyTemplateTemplate.hbs new file mode 100644 index 00000000000..b4933eb193c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/templates/BulkApplyTemplateTemplate.hbs @@ -0,0 +1,68 @@ +<form class="js-form" autocomplete="off"> + <div class="modal-head"> + <h2>Bulk Apply Permission Template</h2> + </div> + + <div class="modal-body"> + <div class="js-modal-messages"></div> + + {{#if done}} + <div class="alert alert-success"> + {{t 'projects_role.apply_template.success'}} + </div> + {{/if}} + + {{#unless done}} + {{#notNull permissionTemplates}} + <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> + {{else}} + <i class="spinner"></i> + {{/notNull}} + + + <div class="modal-field"> + <label>Apply To</label> + <ul style="padding-top: 4px;"> + {{#if selectionTotal}} + <li> + <input value="selected" id="apply-to-selected" name="apply-to" + type="radio" checked> + <label + for="apply-to-selected" + style="float: none; left: 0; display: inline; padding: 0;"> + Only Selected ({{selectionTotal}}) + </label> + </li> + {{/if}} + <li> + <input value="all" id="apply-to-all" name="apply-to" type="radio" + {{#unless selectionTotal}}checked{{/unless}}> + <label + for="apply-to-all" + style="float: none; left: 0; display: inline; padding: 0;"> + All ({{total}}) + </label> + </li> + </ul> + </div> + {{/unless}} + </div> + + <div class="modal-foot"> + {{#unless done}} + {{#notNull permissionTemplates}} + <button class="js-apply">Apply</button> + {{/notNull}} + {{/unless}} + <a href="#" class="js-modal-close">Close</a> + </div> +</form> diff --git a/server/sonar-web/src/main/js/apps/projects/views/BulkApplyTemplateView.js b/server/sonar-web/src/main/js/apps/projects/views/BulkApplyTemplateView.js new file mode 100644 index 00000000000..d2fc1aba72e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/views/BulkApplyTemplateView.js @@ -0,0 +1,108 @@ +/* + * 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 ModalForm from '../../../components/common/modal-form'; +import { + applyTemplateToProject, + bulkApplyTemplate, + getPermissionTemplates +} from '../../../api/permissions'; +import Template from '../templates/BulkApplyTemplateTemplate.hbs'; + +export default ModalForm.extend({ + template: Template, + + initialize () { + this.loadPermissionTemplates(); + this.done = false; + }, + + loadPermissionTemplates () { + return getPermissionTemplates().then(r => { + this.permissionTemplates = r.permissionTemplates; + this.render(); + }); + }, + + onRender () { + ModalForm.prototype.onRender.apply(this, arguments); + this.$('#project-permissions-template').select2({ + width: '250px', + minimumResultsForSearch: 20 + }); + }, + + bulkApplyToAll (permissionTemplate) { + const data = { templateId: permissionTemplate }; + + if (this.options.query) { + data.q = this.options.query; + } + + if (this.options.qualifier) { + data.qualifier = this.options.qualifier; + } + + return bulkApplyTemplate(data); + }, + + bulkApplyToSelected(permissionTemplate) { + const { selection } = this.options; + let lastRequest = Promise.resolve(); + + selection.forEach(projectId => { + const data = { templateId: permissionTemplate, projectId }; + lastRequest = lastRequest.then(() => applyTemplateToProject(data)); + }); + + return lastRequest; + }, + + onFormSubmit () { + ModalForm.prototype.onFormSubmit.apply(this, arguments); + const permissionTemplate = this.$('#project-permissions-template').val(); + const applyTo = this.$('[name="apply-to"]:checked').val(); + this.disableForm(); + + const request = applyTo === 'all' ? + this.bulkApplyToAll(permissionTemplate) : + this.bulkApplyToSelected(permissionTemplate); + + request.then(() => { + this.trigger('done'); + this.done = true; + this.render(); + }).catch(function (e) { + e.response.json().then(r => { + this.showErrors(r.errors, r.warnings); + this.enableForm(); + }); + }); + }, + + serializeData () { + return { + permissionTemplates: this.permissionTemplates, + selection: this.options.selection, + selectionTotal: this.options.selection.length, + total: this.options.total, + done: this.done + }; + } +}); diff --git a/server/sonar-web/src/main/js/helpers/urls.js b/server/sonar-web/src/main/js/helpers/urls.js index 17c424ab9e7..ef25fe9a952 100644 --- a/server/sonar-web/src/main/js/helpers/urls.js +++ b/server/sonar-web/src/main/js/helpers/urls.js @@ -85,6 +85,15 @@ export function getComponentDashboardManagementUrl (componentKey) { } /** + * Generate URL for a component's permissions page + * @param {string} componentKey + * @returns {string} + */ +export function getComponentPermissionsUrl (componentKey) { + return window.baseUrl + '/project_roles?id=' + encodeURIComponent(componentKey); +} + +/** * Generate URL for a quality profile * @param {string} key * @returns {string} |