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