]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8659 Create organization's projects management page
authorStas Vilchik <vilchiks@gmail.com>
Wed, 1 Feb 2017 18:05:48 +0000 (19:05 +0100)
committerStas Vilchik <stas-vilchik@users.noreply.github.com>
Tue, 7 Feb 2017 10:07:02 +0000 (11:07 +0100)
13 files changed:
server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.js
server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjectsManagement.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap
server/sonar-web/src/main/js/apps/organizations/routes.js
server/sonar-web/src/main/js/apps/permissions/project/views/ApplyTemplateView.js
server/sonar-web/src/main/js/apps/projects-admin/AppContainer.js
server/sonar-web/src/main/js/apps/projects-admin/create-view.js
server/sonar-web/src/main/js/apps/projects-admin/header.js
server/sonar-web/src/main/js/apps/projects-admin/main.js
server/sonar-web/src/main/js/apps/projects-admin/projects.js
server/sonar-web/src/main/js/apps/projects-admin/views/BulkApplyTemplateView.js
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 64d431e88e3b52c428ba7e3627871d22ade85198..8181ccd9379d4bee22c424b333f9ff5c29e5cdab 100644 (file)
@@ -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 (file)
index 0000000..5cfdcbf
--- /dev/null
@@ -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);
index 6b585676a77dd5f6df4893ecdeba7583417bca9b..b0a213f3cdb232e1f7a924638f967ffb228425d6 100644 (file)
@@ -28,7 +28,8 @@ const ADMIN_PATHS = [
   'groups',
   'delete',
   'permissions',
-  'permission_templates'
+  'permission_templates',
+  'projects_management'
 ];
 
 export default class OrganizationNavigation extends React.Component {
@@ -72,6 +73,11 @@ export default class OrganizationNavigation extends React.Component {
                 {translate('permission_templates')}
               </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')}
@@ -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">
index e781526e320b49c90b536a276a5232677f64ea5b..62d369ad1196c2714525948a319bbc8064070774 100644 (file)
@@ -75,6 +75,15 @@ exports[`test admin 1`] = `
                 permission_templates
               </Link>
             </li>
+            <li>
+              <Link
+                activeClassName="active"
+                onlyActiveOnIndex={false}
+                style={Object {}}
+                to="/organizations/foo/projects_management">
+                projects_management
+              </Link>
+            </li>
             <li>
               <Link
                 activeClassName="active"
index 3885eb9ddf7c8024076e3b46f449c34ec4dcbc1b..8057ebe2320f7ece85cd0c93d0e1b25a2ad5c11f 100644 (file)
@@ -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>
 );
index a22e93f8740141a4abada80b013760ea19e3eead..487fb2c38226fa94741c46ed88dceabe9f08ff98 100644 (file)
@@ -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();
index ebf5317536fab0a651cfa670627dc0cd884623ef..6f3e5c141a10ec9c056dc47017da8963b1d07745 100644 (file)
@@ -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}/>
     );
   }
 }
index 3f1662349bc07004f55abadcf7684fdadcdf9ad7..553182105e9d4c982a93895f405c0d6e53b12cb8 100644 (file)
@@ -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));
         });
   },
 
index 1d4200e3aa59b540fea9bf115d7f4cff83e0a43e..68e964de07aa1d52fcd54591fb95b106b6aa8318 100644 (file)
@@ -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();
   }
 
index 749e4b7bcb175dc16a20fdcdb1f2234144ee75b0..3bef593f74d64edd7f5734514911c2827d7a0516 100644 (file)
@@ -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}
index d6b002248a4b937afd434f9e17cce8606e5f4acd..e2bd4690713a972debe3606fbabdf799b450b3a5 100644 (file)
@@ -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) {
index c2f216edf356f63737431657169255b2b78c8cd5..3dc188ff1e1faedc83ea4f27e8c5387e88278023 100644 (file)
@@ -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();
index a16c62820c0c993d8cf93881562e31cafc17ce9e..8f7ac899613eddc01ad103713b06cb1afaf611f6 100644 (file)
@@ -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