diff options
13 files changed, 127 insertions, 25 deletions
diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.js b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.js index 64d431e88e3..8181ccd9379 100644 --- a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.js +++ b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.js @@ -155,11 +155,13 @@ class SettingsNav extends React.Component { {translate('sidebar.projects')} <i className="icon-dropdown"/> </a> <ul className="dropdown-menu"> - <li> - <IndexLink to="/projects_admin" activeClassName="active"> - Management - </IndexLink> - </li> + {!this.props.customOrganizations && ( + <li> + <IndexLink to="/projects_admin" activeClassName="active"> + Management + </IndexLink> + </li> + )} <li> <IndexLink to="/background_tasks" activeClassName="active"> {translate('background_tasks.page')} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjectsManagement.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjectsManagement.js new file mode 100644 index 00000000000..5cfdcbf949e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjectsManagement.js @@ -0,0 +1,43 @@ +/* + * 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. + */ +// @flow +import React from 'react'; +import { connect } from 'react-redux'; +import AppContainer from '../../projects-admin/AppContainer'; +import { getOrganizationByKey } from '../../../store/rootReducer'; +import type { Organization } from '../../../store/organizations/duck'; + +class OrganizationProjectsManagement extends React.Component { + props: { + organization: Organization + }; + + render () { + return ( + <AppContainer organization={this.props.organization}/> + ); + } +} + +const mapStateToProps = (state, ownProps) => ({ + organization: getOrganizationByKey(state, ownProps.params.organizationKey) +}); + +export default connect(mapStateToProps)(OrganizationProjectsManagement); diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js index 6b585676a77..b0a213f3cdb 100644 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js +++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js @@ -28,7 +28,8 @@ const ADMIN_PATHS = [ 'groups', 'delete', 'permissions', - 'permission_templates' + 'permission_templates', + 'projects_management' ]; export default class OrganizationNavigation extends React.Component { @@ -73,6 +74,11 @@ export default class OrganizationNavigation extends React.Component { </Link> </li> <li> + <Link to={`/organizations/${organization.key}/projects_management`} activeClassName="active"> + {translate('projects_management')} + </Link> + </li> + <li> <Link to={`/organizations/${organization.key}/edit`} activeClassName="active"> {translate('edit')} </Link> @@ -90,7 +96,9 @@ export default class OrganizationNavigation extends React.Component { render () { const { organization, location } = this.props; - const isHomeActive = location.pathname.startsWith(`organizations/${organization.key}/projects`); + const isHomeActive = + location.pathname === `organizations/${organization.key}/projects` || + location.pathname === `organizations/${organization.key}/projects/favorite`; return ( <nav className="navbar navbar-context page-container" id="context-navigation"> diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap index e781526e320..62d369ad119 100644 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap +++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap @@ -80,6 +80,15 @@ exports[`test admin 1`] = ` activeClassName="active" onlyActiveOnIndex={false} style={Object {}} + to="/organizations/foo/projects_management"> + projects_management + </Link> + </li> + <li> + <Link + activeClassName="active" + onlyActiveOnIndex={false} + style={Object {}} to="/organizations/foo/edit"> edit </Link> diff --git a/server/sonar-web/src/main/js/apps/organizations/routes.js b/server/sonar-web/src/main/js/apps/organizations/routes.js index 3885eb9ddf7..8057ebe2320 100644 --- a/server/sonar-web/src/main/js/apps/organizations/routes.js +++ b/server/sonar-web/src/main/js/apps/organizations/routes.js @@ -27,6 +27,7 @@ import OrganizationEdit from './components/OrganizationEdit'; import OrganizationGroups from './components/OrganizationGroups'; import OrganizationPermissions from './components/OrganizationPermissions'; import OrganizationPermissionTemplates from './components/OrganizationPermissionTemplates'; +import OrganizationProjectsManagement from './components/OrganizationProjectsManagement'; import OrganizationDelete from './components/OrganizationDelete'; export default ( @@ -40,6 +41,7 @@ export default ( <Route path="groups" component={OrganizationGroups}/> <Route path="permissions" component={OrganizationPermissions}/> <Route path="permission_templates" component={OrganizationPermissionTemplates}/> + <Route path="projects_management" component={OrganizationProjectsManagement}/> </Route> </Route> ); 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 a22e93f8740..487fb2c3822 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 @@ -33,7 +33,10 @@ export default ModalForm.extend({ }, loadPermissionTemplates () { - return getPermissionTemplates(this.options.project.organization).then(r => { + const request = this.options.organization ? + getPermissionTemplates(this.options.organization.key) : + getPermissionTemplates(); + return request.then(r => { this.permissionTemplates = r.permissionTemplates; this.render(); }); @@ -52,10 +55,14 @@ export default ModalForm.extend({ const permissionTemplate = this.$('#project-permissions-template').val(); this.disableForm(); - applyTemplateToProject({ + const data = { projectKey: this.options.project.key, templateId: permissionTemplate - }).then(() => { + }; + if (this.options.organization) { + data.organization = this.options.organization.key; + } + applyTemplateToProject(data).then(() => { this.trigger('done'); this.done = true; this.render(); diff --git a/server/sonar-web/src/main/js/apps/projects-admin/AppContainer.js b/server/sonar-web/src/main/js/apps/projects-admin/AppContainer.js index ebf5317536f..6f3e5c141a1 100644 --- a/server/sonar-web/src/main/js/apps/projects-admin/AppContainer.js +++ b/server/sonar-web/src/main/js/apps/projects-admin/AppContainer.js @@ -25,12 +25,15 @@ import { getRootQualifiers } from '../../store/appState/duck'; class AppContainer extends React.Component { render () { - const hasProvisionPermission = this.props.user.permissions.global.indexOf('provisioning') !== -1; + const hasProvisionPermission = this.props.organization ? + this.props.organization.canProvisionProjects : + this.props.user.permissions.global.indexOf('provisioning') !== -1; return ( <Main hasProvisionPermission={hasProvisionPermission} - topLevelQualifiers={this.props.rootQualifiers}/> + topLevelQualifiers={this.props.rootQualifiers} + organization={this.props.organization}/> ); } } diff --git a/server/sonar-web/src/main/js/apps/projects-admin/create-view.js b/server/sonar-web/src/main/js/apps/projects-admin/create-view.js index 3f1662349bc..553182105e9 100644 --- a/server/sonar-web/src/main/js/apps/projects-admin/create-view.js +++ b/server/sonar-web/src/main/js/apps/projects-admin/create-view.js @@ -45,6 +45,9 @@ export default ModalForm.extend({ branch: this.$('#create-project-branch').val(), key: this.$('#create-project-key').val() }; + if (this.options.organization) { + data.organization = this.options.organization.key; + } this.disableForm(); return createProject(data) .then(project => { @@ -57,9 +60,7 @@ export default ModalForm.extend({ }) .catch(error => { this.enableForm(); - if (error.response.status === 400) { - error.response.json().then(obj => this.showErrors([{ msg: obj.err_msg }])); - } + error.response.json().then(r => this.showErrors(r.errors, r.warnings)); }); }, diff --git a/server/sonar-web/src/main/js/apps/projects-admin/header.js b/server/sonar-web/src/main/js/apps/projects-admin/header.js index 1d4200e3aa5..68e964de07a 100644 --- a/server/sonar-web/src/main/js/apps/projects-admin/header.js +++ b/server/sonar-web/src/main/js/apps/projects-admin/header.js @@ -28,7 +28,8 @@ export default class Header extends React.Component { createProject () { new CreateView({ - refresh: this.props.refresh + refresh: this.props.refresh, + organization: this.props.organization }).render(); } @@ -37,7 +38,8 @@ export default class Header extends React.Component { total: this.props.total, selection: this.props.selection, query: this.props.query, - qualifier: this.props.qualifier + qualifier: this.props.qualifier, + organization: this.props.organization }).render(); } diff --git a/server/sonar-web/src/main/js/apps/projects-admin/main.js b/server/sonar-web/src/main/js/apps/projects-admin/main.js index 749e4b7bcb1..3bef593f74d 100644 --- a/server/sonar-web/src/main/js/apps/projects-admin/main.js +++ b/server/sonar-web/src/main/js/apps/projects-admin/main.js @@ -30,7 +30,8 @@ import ListFooter from '../../components/controls/ListFooter'; export default React.createClass({ propTypes: { - hasProvisionPermission: React.PropTypes.bool.isRequired + hasProvisionPermission: React.PropTypes.bool.isRequired, + organization: React.PropTypes.object }, getInitialState () { @@ -62,6 +63,9 @@ export default React.createClass({ if (this.state.query) { filters.q = this.state.query; } + if (this.props.organization) { + filters.organization = this.props.organization.key; + } return filters; }, @@ -182,7 +186,11 @@ export default React.createClass({ deleteProjects () { this.setState({ ready: false }); const ids = this.state.selection.join(','); - deleteComponents({ ids }).then(() => { + const data = { ids }; + if (this.props.organization) { + Object.assign(data, { organization: this.props.organization.key }); + } + deleteComponents(data).then(() => { this.setState({ page: 1, selection: [] }, this.requestProjects); }); }, @@ -196,7 +204,8 @@ export default React.createClass({ total={this.state.total} query={this.state.query} qualifier={this.state.qualifiers} - refresh={this.requestProjects}/> + refresh={this.requestProjects} + organization={this.props.organization}/> <Search {...this.props} {...this.state} onSearch={this.onSearch} @@ -212,7 +221,8 @@ export default React.createClass({ refresh={this.requestProjects} selection={this.state.selection} onProjectSelected={this.onProjectSelected} - onProjectDeselected={this.onProjectDeselected}/> + onProjectDeselected={this.onProjectDeselected} + organization={this.props.organization}/> <ListFooter ready={this.state.ready} diff --git a/server/sonar-web/src/main/js/apps/projects-admin/projects.js b/server/sonar-web/src/main/js/apps/projects-admin/projects.js index d6b002248a4..e2bd4690713 100644 --- a/server/sonar-web/src/main/js/apps/projects-admin/projects.js +++ b/server/sonar-web/src/main/js/apps/projects-admin/projects.js @@ -29,7 +29,8 @@ import { translate } from '../../helpers/l10n'; export default class Projects extends React.Component { static propTypes = { projects: React.PropTypes.array.isRequired, - selection: React.PropTypes.array.isRequired + selection: React.PropTypes.array.isRequired, + organization: React.PropTypes.object }; componentWillMount () { @@ -47,7 +48,10 @@ export default class Projects extends React.Component { onApplyTemplateClick (project, e) { e.preventDefault(); e.target.blur(); - new ApplyTemplateView({ project }).render(); + new ApplyTemplateView({ + project, + organization: this.props.organization + }).render(); } isProjectSelected (project) { diff --git a/server/sonar-web/src/main/js/apps/projects-admin/views/BulkApplyTemplateView.js b/server/sonar-web/src/main/js/apps/projects-admin/views/BulkApplyTemplateView.js index c2f216edf35..3dc188ff1e1 100644 --- a/server/sonar-web/src/main/js/apps/projects-admin/views/BulkApplyTemplateView.js +++ b/server/sonar-web/src/main/js/apps/projects-admin/views/BulkApplyTemplateView.js @@ -34,7 +34,10 @@ export default ModalForm.extend({ }, loadPermissionTemplates () { - return getPermissionTemplates().then(r => { + const request = this.options.organization ? + getPermissionTemplates(this.options.organization.key) : + getPermissionTemplates(); + return request.then(r => { this.permissionTemplates = r.permissionTemplates; this.render(); }); @@ -59,6 +62,10 @@ export default ModalForm.extend({ data.qualifier = this.options.qualifier; } + if (this.options.organization) { + data.organization = this.options.organization.key; + } + return bulkApplyTemplate(data); }, @@ -68,6 +75,9 @@ export default ModalForm.extend({ selection.forEach(projectId => { const data = { templateId: permissionTemplate, projectId }; + if (this.options.organization) { + data.organization = this.options.organization.key; + } lastRequest = lastRequest.then(() => applyTemplateToProject(data)); }); @@ -88,7 +98,7 @@ export default ModalForm.extend({ this.trigger('done'); this.done = true; this.render(); - }).catch(function (e) { + }).catch(e => { e.response.json().then(r => { this.showErrors(r.errors, r.warnings); this.enableForm(); diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index a16c62820c0..8f7ac899613 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -126,6 +126,7 @@ permalinks=Permalinks plugin=Plugin project=Project projects=Projects +projects_management=Projects Management quality_profile=Quality Profile raw=Raw recent_history=Recent History |