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) {
<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>
initialize () {
this.loadPermissionTemplates();
+ this.done = false;
},
loadPermissionTemplates () {
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);
serializeData () {
return {
permissionTemplates: this.permissionTemplates,
- project: this.options.project
+ project: this.options.project,
+ done: this.done
};
}
});
*/
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>
);
}
-});
+}
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({
break;
default:
- // should never happen
+ // should never happen
}
},
},
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) {
<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}
*/
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">
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>
);
}
-});
+}
--- /dev/null
+<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>
--- /dev/null
+/*
+ * 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
+ };
+ }
+});
return window.baseUrl + '/dashboards?resource=' + encodeURIComponent(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
added_since_version=Added since version {0}
all_violations=All violations
all_issues=All issues
+apply_template
are_you_sure=Are you sure?
assigned_to=Assigned to
bulk_change=Bulk Change
default_severity=Default severity
default_sort_on=Default sort on
disable_treemap=Disable treemap
+edit_permissions=Edit Permissions
enable_treemap=Enable treemap
equals=Equals
false_positive=False positive
projects_role.bulk_change=Bulk Change
projects_role.apply_template=Apply Permission Template
projects_role.apply_template_to_xxx=Apply Permission Template To "{0}"
+projects_role.apply_template.success=Permission template was successfully applied.
projects_role.no_projects=There are currently no results to apply the permission template to.