]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8658 Create organization's permission templates page
authorStas Vilchik <vilchiks@gmail.com>
Tue, 31 Jan 2017 12:46:14 +0000 (13:46 +0100)
committerStas Vilchik <stas-vilchik@users.noreply.github.com>
Wed, 1 Feb 2017 08:43:03 +0000 (09:43 +0100)
16 files changed:
server/sonar-web/src/main/js/api/permissions.js
server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.js
server/sonar-web/src/main/js/apps/organizations/components/OrganizationPermissionTemplates.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/permission-templates/components/ActionsCell.js
server/sonar-web/src/main/js/apps/permission-templates/components/App.js
server/sonar-web/src/main/js/apps/permission-templates/components/Header.js
server/sonar-web/src/main/js/apps/permission-templates/components/Home.js
server/sonar-web/src/main/js/apps/permission-templates/components/List.js
server/sonar-web/src/main/js/apps/permission-templates/components/ListItem.js
server/sonar-web/src/main/js/apps/permission-templates/components/NameCell.js
server/sonar-web/src/main/js/apps/permission-templates/components/Template.js
server/sonar-web/src/main/js/apps/permission-templates/components/TemplateHeader.js
server/sonar-web/src/main/js/apps/permission-templates/views/CreateView.js

index e4d7f9b9b09e5e4ed07e9b9b42a682c937d3645f..f498c3612d98a5b4e3b10b63f7021dc29f0fb1d4 100644 (file)
@@ -71,9 +71,9 @@ export function revokePermissionFromGroup (projectKey, groupName, permission) {
  * Get list of permission templates
  * @returns {Promise}
  */
-export function getPermissionTemplates () {
+export function getPermissionTemplates (organization) {
   const url = '/api/permissions/search_templates';
-  return getJSON(url);
+  return organization ? getJSON(url, { organization }) : getJSON(url);
 }
 
 export const createPermissionTemplate = data => (
@@ -90,13 +90,13 @@ export const deletePermissionTemplate = data => (
 
 /**
  * Set default permission template for a given qualifier
- * @param {string} templateName
+ * @param {string} templateId
  * @param {string} qualifier
  * @returns {Promise}
  */
-export function setDefaultPermissionTemplate (templateName, qualifier) {
+export function setDefaultPermissionTemplate (templateId, qualifier) {
   const url = '/api/permissions/set_default_template';
-  const data = { templateName, qualifier };
+  const data = { templateId, qualifier };
   return post(url, data);
 }
 
@@ -110,39 +110,37 @@ export function bulkApplyTemplate (data) {
   return post(url, data);
 }
 
-export function grantTemplatePermissionToUser (templateName, login, permission) {
+export function grantTemplatePermissionToUser (templateId, login, permission) {
   const url = '/api/permissions/add_user_to_template';
-  const data = { templateName, login, permission };
+  const data = { templateId, login, permission };
   return post(url, data);
 }
 
-export function revokeTemplatePermissionFromUser (templateName, login, permission) {
+export function revokeTemplatePermissionFromUser (templateId, login, permission) {
   const url = '/api/permissions/remove_user_from_template';
-  const data = { templateName, login, permission };
+  const data = { templateId, login, permission };
   return post(url, data);
 }
 
-export function grantTemplatePermissionToGroup (templateName, groupName, permission) {
+export function grantTemplatePermissionToGroup (data) {
   const url = '/api/permissions/add_group_to_template';
-  const data = { templateName, groupName, permission };
   return post(url, data);
 }
 
-export function revokeTemplatePermissionFromGroup (templateName, groupName, permission) {
+export function revokeTemplatePermissionFromGroup (data) {
   const url = '/api/permissions/remove_group_from_template';
-  const data = { templateName, groupName, permission };
   return post(url, data);
 }
 
-export function addProjectCreatorToTemplate (templateName, permission) {
+export function addProjectCreatorToTemplate (templateId, permission) {
   const url = '/api/permissions/add_project_creator_to_template';
-  const data = { templateName, permission };
+  const data = { templateId, permission };
   return post(url, data);
 }
 
-export function removeProjectCreatorFromTemplate (templateName, permission) {
+export function removeProjectCreatorFromTemplate (templateId, permission) {
   const url = '/api/permissions/remove_project_creator_from_template';
-  const data = { templateName, permission };
+  const data = { templateId, permission };
   return post(url, data);
 }
 
@@ -194,7 +192,7 @@ export function getGlobalPermissionsGroups (query = '', permission = null) {
   return getJSON(url, data).then(r => r.groups);
 }
 
-export function getPermissionTemplateUsers (templateId, query = '', permission = null) {
+export function getPermissionTemplateUsers (templateId, query = '', permission = null, organization = null) {
   const url = '/api/permissions/template_users';
   const data = { templateId, ps: PAGE_SIZE };
   if (query) {
@@ -203,10 +201,13 @@ export function getPermissionTemplateUsers (templateId, query = '', permission =
   if (permission) {
     data.permission = permission;
   }
+  if (organization) {
+    Object.assign(data, { organization });
+  }
   return getJSON(url, data).then(r => r.users);
 }
 
-export function getPermissionTemplateGroups (templateId, query = '', permission = null) {
+export function getPermissionTemplateGroups (templateId, query = '', permission = null, organization = null) {
   const url = '/api/permissions/template_groups';
   const data = { templateId, ps: PAGE_SIZE };
   if (query) {
@@ -215,5 +216,8 @@ export function getPermissionTemplateGroups (templateId, query = '', permission
   if (permission) {
     data.permission = permission;
   }
+  if (organization) {
+    Object.assign(data, { organization });
+  }
   return getJSON(url, data).then(r => r.groups);
 }
index 417f89a15536ec3365cf8b1cdbd09afa6432d074..9879cec41d5ce5b53be3cee4e6ed4fcf9b3bc896 100644 (file)
@@ -75,9 +75,9 @@ class SettingsNav extends React.Component {
             <div className="container">
               <ul className="nav navbar-nav nav-crumbs">
                 <li>
-                <IndexLink to="/settings">
-                  {translate('layout.settings')}
-                </IndexLink>
+                  <IndexLink to="/settings">
+                    {translate('layout.settings')}
+                  </IndexLink>
                 </li>
               </ul>
 
@@ -138,11 +138,13 @@ class SettingsNav extends React.Component {
                         {translate('global_permissions.page')}
                       </IndexLink>
                     </li>
-                    <li>
-                      <IndexLink to="/permission_templates" activeClassName="active">
-                        {translate('permission_templates')}
-                      </IndexLink>
-                    </li>
+                    {!this.props.customOrganizations && (
+                        <li>
+                          <IndexLink to="/permission_templates" activeClassName="active">
+                            {translate('permission_templates')}
+                          </IndexLink>
+                        </li>
+                    )}
                   </ul>
                 </li>
 
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPermissionTemplates.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPermissionTemplates.js
new file mode 100644 (file)
index 0000000..614e2ec
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * 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 '../../permission-templates/components/AppContainer';
+import { getOrganizationByKey } from '../../../store/rootReducer';
+import type { Organization } from '../../../store/organizations/duck';
+
+class OrganizationPermissionTemplates extends React.Component {
+  props: {
+    location: {},
+    organization: Organization
+  };
+
+  render () {
+    return (
+        <AppContainer
+            location={this.props.location}
+            organization={this.props.organization}/>
+    );
+  }
+}
+
+const mapStateToProps = (state, ownProps) => ({
+  organization: getOrganizationByKey(state, ownProps.params.organizationKey)
+});
+
+export default connect(mapStateToProps)(OrganizationPermissionTemplates);
index d9cfafeb5eb3164c9a5857d0951e3cd6c00e33b7..8590e0e192fa307f49871d87396410fcc5881d6b 100644 (file)
@@ -25,7 +25,8 @@ import { translate } from '../../../helpers/l10n';
 const ADMIN_PATHS = [
   'edit',
   'groups',
-  'delete'
+  'delete',
+  'permission_templates'
 ];
 
 export default class OrganizationNavigation extends React.Component {
@@ -56,6 +57,11 @@ export default class OrganizationNavigation extends React.Component {
                 {translate('user_groups.page')}
               </Link>
             </li>
+            <li>
+              <Link to={`/organizations/${organization.key}/permission_templates`} activeClassName="active">
+                {translate('permission_templates')}
+              </Link>
+            </li>
             <li>
               <Link to={`/organizations/${organization.key}/edit`} activeClassName="active">
                 {translate('edit')}
index cd5ffee80cf5d2d8622b8666737c402bfeb048a8..af802f0809d07f06a910988476fe3f899a7884c4 100644 (file)
@@ -49,6 +49,15 @@ exports[`test admin 1`] = `
                 user_groups.page
               </Link>
             </li>
+            <li>
+              <Link
+                activeClassName="active"
+                onlyActiveOnIndex={false}
+                style={Object {}}
+                to="/organizations/foo/permission_templates">
+                permission_templates
+              </Link>
+            </li>
             <li>
               <Link
                 activeClassName="active"
index 3cf6dc9b3b1850bca2ef59c7174336c71c1d9c73..909ffa400e3a1696d5a453d5ef15b99a1ceebd77 100644 (file)
@@ -23,6 +23,7 @@ import OrganizationPage from './components/OrganizationPage';
 import OrganizationAdmin from './components/OrganizationAdmin';
 import OrganizationEdit from './components/OrganizationEdit';
 import OrganizationGroups from './components/OrganizationGroups';
+import OrganizationPermissionTemplates from './components/OrganizationPermissionTemplates';
 import OrganizationDelete from './components/OrganizationDelete';
 
 export default (
@@ -31,6 +32,7 @@ export default (
         <Route path="delete" component={OrganizationDelete}/>
         <Route path="edit" component={OrganizationEdit}/>
         <Route path="groups" component={OrganizationGroups}/>
+        <Route path="permission_templates" component={OrganizationPermissionTemplates}/>
       </Route>
     </Route>
 );
index 88c034de322fc3cf15d349d6097bd583e5117c76..1a0a97e38cfcc8faf172da4644227babf63a0210 100644 (file)
@@ -30,6 +30,7 @@ import { setDefaultPermissionTemplate } from '../../../api/permissions';
 
 export default class ActionsCell extends React.Component {
   static propTypes = {
+    organization: React.PropTypes.object,
     permissionTemplate: PermissionTemplateType.isRequired,
     topQualifiers: React.PropTypes.array.isRequired,
     refresh: CallbackType,
@@ -57,7 +58,10 @@ export default class ActionsCell extends React.Component {
     new DeleteView({
       model: new Backbone.Model(this.props.permissionTemplate)
     }).on('done', () => {
-      this.context.router.replace('/permission_templates');
+      const pathname = this.props.organization ?
+          `/organizations/${this.props.organization.key}/permission_templates` :
+          '/permission_templates';
+      this.context.router.replace(pathname);
       this.props.refresh();
     }).render();
   }
@@ -65,7 +69,7 @@ export default class ActionsCell extends React.Component {
   setDefault (qualifier, e) {
     e.preventDefault();
     setDefaultPermissionTemplate(
-        this.props.permissionTemplate.name,
+        this.props.permissionTemplate.id,
         qualifier
     ).then(this.props.refresh);
   }
@@ -137,7 +141,11 @@ export default class ActionsCell extends React.Component {
   }
 
   render () {
-    const { permissionTemplate: t } = this.props;
+    const { permissionTemplate: t, organization } = this.props;
+
+    const pathname = organization ?
+        `/organizations/${organization.key}/permission_templates` :
+        '/permission_templates';
 
     return (
         <div className="dropdown">
@@ -151,12 +159,12 @@ export default class ActionsCell extends React.Component {
             {this.renderSetDefaultsControl()}
 
             {!this.props.fromDetails && (
-              <li>
-                <Link to={{ pathname: '/permission_templates', query: { id: t.id } }}>
-                  {this.renderDropdownIcon(<i className="icon-edit"/>)}
-                  Edit Permissions
-                </Link>
-              </li>
+                <li>
+                  <Link to={{ pathname, query: { id: t.id } }}>
+                    {this.renderDropdownIcon(<i className="icon-edit"/>)}
+                    Edit Permissions
+                  </Link>
+                </li>
             )}
 
             <li>
index 2263339753b444e5f8cdd0a42383bea8ae0422bc..b5e494aa2ed3c6873bdac2a058c25a921349d763 100644 (file)
@@ -30,6 +30,8 @@ import '../../permissions/styles.css';
 
 export default class App extends React.Component {
   static propTypes = {
+    location: React.PropTypes.object.isRequired,
+    organization: React.PropTypes.object,
     topQualifiers: React.PropTypes.array.isRequired
   };
 
@@ -53,7 +55,9 @@ export default class App extends React.Component {
   }
 
   requestPermissions () {
-    return getPermissionTemplates().then(r => {
+    const { organization } = this.props;
+    const request = organization ? getPermissionTemplates(organization.key) : getPermissionTemplates();
+    return request.then(r => {
       if (this.mounted) {
         const permissions = sortPermissions(r.permissions);
         const permissionTemplates = mergeDefaultsToTemplates(
@@ -77,6 +81,7 @@ export default class App extends React.Component {
     const template = this.state.permissionTemplates.find(t => t.id === id);
     return (
         <Template
+            organization={this.props.organization}
             template={template}
             refresh={this.requestPermissions}
             topQualifiers={this.props.topQualifiers}/>
@@ -92,6 +97,7 @@ export default class App extends React.Component {
 
     return (
         <Home
+            organization={this.props.organization}
             topQualifiers={this.props.topQualifiers}
             permissions={this.state.permissions}
             permissionTemplates={this.state.permissionTemplates}
index b0f66504eddaaab01a50e5717befcd30f078640b..e36a7e5157050003febddf0f9b08ec27aeff8fe6 100644 (file)
@@ -24,6 +24,7 @@ import { CallbackType } from '../propTypes';
 
 export default class Header extends React.Component {
   static propTypes = {
+    organization: React.PropTypes.object,
     ready: React.PropTypes.bool.isRequired,
     refresh: CallbackType
   };
@@ -38,11 +39,15 @@ export default class Header extends React.Component {
 
   handleCreateClick (e) {
     e.preventDefault();
+    const { organization } = this.props;
 
-    new CreateView().on('done', r => {
+    new CreateView({ organization }).on('done', r => {
       this.props.refresh().then(() => {
+        const pathname = organization ?
+            `/organizations/${organization.key}/permission_templates` :
+            '/permission_templates';
         this.context.router.push({
-          pathname: '/permission_templates',
+          pathname,
           query: { id: r.permissionTemplate.id }
         });
       });
index c8871e2c4e7dc954da89a9ddb34f1ad44b89901e..516e1f28f76504c9db0ad43d051b7aa315f470b8 100644 (file)
@@ -26,6 +26,7 @@ import { translate } from '../../../helpers/l10n';
 
 export default class Home extends React.Component {
   static propTypes = {
+    organization: React.PropTypes.object,
     topQualifiers: React.PropTypes.array.isRequired,
     permissions: React.PropTypes.array.isRequired,
     permissionTemplates: React.PropTypes.array.isRequired,
@@ -41,11 +42,13 @@ export default class Home extends React.Component {
               titleTemplate="SonarQube - %s"/>
 
           <Header
+              organization={this.props.organization}
               ready={this.props.ready}
               refresh={this.props.refresh}/>
 
           <TooltipsContainer>
             <List
+                organization={this.props.organization}
                 permissionTemplates={this.props.permissionTemplates}
                 permissions={this.props.permissions}
                 topQualifiers={this.props.topQualifiers}
index 11ad4cd42a996c20619abe0f888b3a36a5bbc1af..8fda434904abdaeebadd9262a458f266e75de342 100644 (file)
@@ -24,6 +24,7 @@ import { PermissionTemplateType, CallbackType } from '../propTypes';
 
 export default class List extends React.Component {
   static propTypes = {
+    organization: React.PropTypes.object,
     permissionTemplates: React.PropTypes.arrayOf(
         PermissionTemplateType).isRequired,
     permissions: React.PropTypes.array.isRequired,
@@ -35,14 +36,14 @@ export default class List extends React.Component {
     const permissionTemplates = this.props.permissionTemplates.map(p => (
         <ListItem
             key={p.id}
+            organization={this.props.organization}
             permissionTemplate={p}
             topQualifiers={this.props.topQualifiers}
             refresh={this.props.refresh}/>
     ));
 
     return (
-        <table id="permission-templates"
-               className="data zebra permissions-table">
+        <table id="permission-templates" className="data zebra permissions-table">
           <ListHeader permissions={this.props.permissions}/>
           <tbody>{permissionTemplates}</tbody>
         </table>
index 6c27462183b670e849d5c9fa0611eda2ea040550..3dd1986efeda8b73faa2bd924f69e03155631596 100644 (file)
@@ -27,6 +27,7 @@ import { PermissionTemplateType, CallbackType } from '../propTypes';
 
 export default class ListItem extends React.Component {
   static propTypes = {
+    organization: React.PropTypes.object,
     permissionTemplate: PermissionTemplateType.isRequired,
     topQualifiers: React.PropTypes.array.isRequired,
     refresh: CallbackType
@@ -63,6 +64,7 @@ export default class ListItem extends React.Component {
             data-id={this.props.permissionTemplate.id}
             data-name={this.props.permissionTemplate.name}>
           <NameCell
+              organization={this.props.organization}
               permissionTemplate={this.props.permissionTemplate}
               topQualifiers={this.props.topQualifiers}/>
 
@@ -70,6 +72,7 @@ export default class ListItem extends React.Component {
 
           <td className="nowrap thin text-right">
             <ActionsCell
+                organization={this.props.organization}
                 permissionTemplate={this.props.permissionTemplate}
                 topQualifiers={this.props.topQualifiers}
                 refresh={this.props.refresh}/>
index 78d1ded45ab9223f457a74b4d14efe17972b5616..41379178da2d1b525178c369b8d093d6802377db 100644 (file)
@@ -24,15 +24,20 @@ import { PermissionTemplateType } from '../propTypes';
 
 export default class NameCell extends React.Component {
   static propTypes = {
+    organization: React.PropTypes.object,
     permissionTemplate: PermissionTemplateType.isRequired
   };
 
   render () {
-    const { permissionTemplate: t } = this.props;
+    const { permissionTemplate: t, organization } = this.props;
+
+    const pathname = organization ?
+        `/organizations/${organization.key}/permission_templates` :
+        '/permission_templates';
 
     return (
         <td>
-          <Link to={{ pathname: '/permission_templates', query: { id: t.id } }}>
+          <Link to={{ pathname, query: { id: t.id } }}>
             <strong className="js-name">{t.name}</strong>
           </Link>
 
index 1639f5cb6c3206ae76309a9f25b91600bc4209df..c37066308dce63e89eeeab7ca424edcb509d2407 100644 (file)
@@ -31,6 +31,7 @@ import withStore from '../../../store/utils/withStore';
 
 class Template extends React.Component {
   static propTypes = {
+    organization: React.PropTypes.object,
     template: React.PropTypes.object.isRequired,
     refresh: React.PropTypes.func.isRequired,
     topQualifiers: React.PropTypes.array.isRequired
@@ -95,9 +96,9 @@ class Template extends React.Component {
     const hasPermission = user.permissions.includes(permission);
     const request = hasPermission ?
         api.revokeTemplatePermissionFromUser(
-            template.name, user.login, permission) :
+            template.id, user.login, permission) :
         api.grantTemplatePermissionToUser(
-            template.name, user.login, permission);
+            template.id, user.login, permission);
     request.then(() => this.requestHolders()).then(this.props.refresh);
   }
 
@@ -105,19 +106,25 @@ class Template extends React.Component {
     const { template } = this.props;
     const hasPermission = user.permissions.includes(permission);
     const request = hasPermission ?
-        api.removeProjectCreatorFromTemplate(template.name, permission) :
-        api.addProjectCreatorToTemplate(template.name, permission);
+        api.removeProjectCreatorFromTemplate(template.id, permission) :
+        api.addProjectCreatorToTemplate(template.id, permission);
     request.then(() => this.requestHolders()).then(this.props.refresh);
   }
 
   handleToggleGroup (group, permission) {
-    const { template } = this.props;
+    const { template, organization } = this.props;
     const hasPermission = group.permissions.includes(permission);
+    const data = {
+      templateId: template.id,
+      groupName: group.name,
+      permission
+    };
+    if (organization) {
+      Object.assign(data, { organization: organization.key });
+    }
     const request = hasPermission ?
-        api.revokeTemplatePermissionFromGroup(
-            template.name, group.name, permission) :
-        api.grantTemplatePermissionToGroup(
-            template.name, group.name, permission);
+        api.revokeTemplatePermissionFromGroup(data) :
+        api.grantTemplatePermissionToGroup(data);
     request.then(() => this.requestHolders()).then(this.props.refresh);
   }
 
@@ -194,6 +201,7 @@ class Template extends React.Component {
               titleTemplate="SonarQube - %s"/>
 
           <TemplateHeader
+              organization={this.props.organization}
               template={this.props.template}
               loading={store.loading}
               refresh={this.props.refresh}
index 57dd6349760cb612f978fea9af10a09550997db7..3de1362b3a49ab2e1b7fcb28ec632c22964ee3fc 100644 (file)
@@ -24,6 +24,7 @@ import { translate } from '../../../helpers/l10n';
 
 export default class TemplateHeader extends React.Component {
   static propTypes = {
+    organization: React.PropTypes.object,
     template: React.PropTypes.object.isRequired,
     loading: React.PropTypes.bool.isRequired,
     refresh: React.PropTypes.func.isRequired,
@@ -31,12 +32,16 @@ export default class TemplateHeader extends React.Component {
   };
 
   render () {
-    const { template } = this.props;
+    const { template, organization } = this.props;
+
+    const pathname = organization ?
+        `/organizations/${organization.key}/permission_templates` :
+        '/permission_templates';
 
     return (
         <header id="project-permissions-header" className="page-header">
           <div className="note spacer-bottom">
-            <Link to="/permission_templates" className="text-muted">
+            <Link to={pathname} className="text-muted">
               {translate('permission_templates.page')}
             </Link>
           </div>
@@ -51,6 +56,7 @@ export default class TemplateHeader extends React.Component {
 
           <div className="pull-right">
             <ActionsCell
+                organization={this.props.organization}
                 permissionTemplate={this.props.template}
                 topQualifiers={this.props.topQualifiers}
                 refresh={this.props.refresh}
index c444d48705dbb280ca53fe145a351a56f9e781e8..a0cfc34da3ae0d7f0b0ae0e96c67fc319099ee90 100644 (file)
@@ -24,11 +24,15 @@ import { parseError } from '../../code/utils';
 export default FormView.extend({
   sendRequest () {
     this.disableForm();
-    createPermissionTemplate({
+    const data = {
       name: this.$('#permission-template-name').val(),
       description: this.$('#permission-template-description').val(),
       projectKeyPattern: this.$('#permission-template-project-key-pattern').val()
-    }).then(
+    };
+    if (this.options.organization) {
+      Object.assign(data, { organization: this.options.organization.key });
+    }
+    createPermissionTemplate(data).then(
         r => {
           this.trigger('done', r);
           this.destroy();