Browse Source

SONAR-7880 Put actions on permissions on the Projects Management page

tags/6.0-RC1
Stas Vilchik 8 years ago
parent
commit
75efda3dd8

+ 3
- 3
server/sonar-web/src/main/js/api/permissions.js View File

@@ -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) {

+ 28
- 17
server/sonar-web/src/main/js/apps/permissions/project/templates/ApplyTemplateTemplate.hbs View File

@@ -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>

+ 5
- 2
server/sonar-web/src/main/js/apps/permissions/project/views/ApplyTemplateView.js View File

@@ -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
};
}
});

+ 42
- 11
server/sonar-web/src/main/js/apps/projects/header.js View File

@@ -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>
);
}
});
}

+ 13
- 3
server/sonar-web/src/main/js/apps/projects/main.js View File

@@ -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}

+ 60
- 17
server/sonar-web/src/main/js/apps/projects/projects.js View File

@@ -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>
);
}
});
}

+ 68
- 0
server/sonar-web/src/main/js/apps/projects/templates/BulkApplyTemplateTemplate.hbs View File

@@ -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>

+ 108
- 0
server/sonar-web/src/main/js/apps/projects/views/BulkApplyTemplateView.js View File

@@ -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
};
}
});

+ 9
- 0
server/sonar-web/src/main/js/helpers/urls.js View File

@@ -84,6 +84,15 @@ export function getComponentDashboardManagementUrl (componentKey) {
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

+ 3
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -207,6 +207,7 @@ added_since_previous_version_detailed=Added since previous version ({0})
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
@@ -221,6 +222,7 @@ default_error_message=The request cannot be processed. Try again later.
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
@@ -2695,6 +2697,7 @@ projects_role.scan.desc=Ability to get all settings required to perform an analy
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.



Loading…
Cancel
Save