]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9000 display all organizations a user is a member of
authorStas Vilchik <stas.vilchik@sonarsource.com>
Tue, 28 Nov 2017 14:56:44 +0000 (15:56 +0100)
committerTeryk Bellahsene <teryk@users.noreply.github.com>
Wed, 29 Nov 2017 16:27:53 +0000 (17:27 +0100)
13 files changed:
server/sonar-web/src/main/js/api/organizations.ts
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.js
server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavUser-test.js.snap
server/sonar-web/src/main/js/app/styles/components/dropdowns.css
server/sonar-web/src/main/js/apps/account/organizations/OrganizationCard.js
server/sonar-web/src/main/js/apps/account/organizations/actions.js
server/sonar-web/src/main/js/apps/issues/components/AppContainer.js
server/sonar-web/src/main/js/apps/projects/utils.ts
server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationStep.js
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationStep-test.js
server/sonar-web/src/main/js/store/organizations/duck.js
server/sonar-web/src/main/js/store/rootActions.js
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 408f6b65cb2dfb347301fcf03a81959f47814c4e..f2ffd3f6fe78f77e78e9f7ddb707c0db9ea1ffd8 100644 (file)
 import { getJSON, post, postJSON, RequestData } from '../helpers/request';
 import throwGlobalError from '../app/utils/throwGlobalError';
 
+interface GetOrganizationsParameters {
+  organizations?: string;
+  member?: boolean;
+}
+
 interface GetOrganizationsResponse {
   organizations: Array<{
     avatar?: string;
     description?: string;
     guarded: boolean;
+    isAdmin: boolean;
     key: string;
     name: string;
     url?: string;
@@ -36,20 +42,14 @@ interface GetOrganizationsResponse {
   };
 }
 
-export function getOrganizations(organizations?: string[]): Promise<GetOrganizationsResponse> {
-  const data: RequestData = {};
-  if (organizations) {
-    Object.assign(data, { organizations: organizations.join() });
-  }
+export function getOrganizations(
+  data: GetOrganizationsParameters
+): Promise<GetOrganizationsResponse> {
   return getJSON('/api/organizations/search', data);
 }
 
-export function getMyOrganizations(): Promise<any> {
-  return getJSON('/api/organizations/search_my_organizations').then(r => r.organizations);
-}
-
 export function getOrganization(key: string): Promise<any> {
-  return getOrganizations([key])
+  return getOrganizations({ organizations: key })
     .then(r => r.organizations.find((o: any) => o.key === key))
     .catch(throwGlobalError);
 }
index 761cff9cde4cd1b0745d8e167ea0195a768bafb4..cb70ab0570991113553ad6c7c2d7d1c2c4ee723d 100644 (file)
@@ -44,7 +44,7 @@ type Props = {
   currentUser: CurrentUser,
   fetchMyOrganizations: () => Promise<*>,
   location: Object,
-  organizations: Array<{ key: string, name: string }>,
+  organizations: Array<{ isAdmin: bool, key: string, name: string }>,
   router: { push: string => void }
 };
 */
@@ -157,9 +157,17 @@ export default class GlobalNavUser extends React.PureComponent {
             {hasOrganizations &&
               sortBy(organizations, org => org.name.toLowerCase()).map(organization => (
                 <li key={organization.key}>
-                  <OrganizationLink organization={organization} onClick={this.closeDropdown}>
-                    <OrganizationIcon />
-                    <span className="spacer-left">{organization.name}</span>
+                  <OrganizationLink
+                    className="dropdown-item-flex"
+                    organization={organization}
+                    onClick={this.closeDropdown}>
+                    <div>
+                      <OrganizationIcon />
+                      <span className="spacer-left">{organization.name}</span>
+                    </div>
+                    {organization.isAdmin && (
+                      <span className="outline-badge spacer-left">{translate('admin')}</span>
+                    )}
                   </OrganizationLink>
                 </li>
               ))}
index d603a20a60f11c463b6d5abbd1968b24560b227f..fb7c3c4e59ea42bffc2502fddcd02b96d99360be 100644 (file)
@@ -201,6 +201,7 @@ exports[`should render the users organizations 1`] = `
       key="bar"
     >
       <OrganizationLink
+        className="dropdown-item-flex"
         onClick={[Function]}
         organization={
           Object {
@@ -209,18 +210,21 @@ exports[`should render the users organizations 1`] = `
           }
         }
       >
-        <OrganizationIcon />
-        <span
-          className="spacer-left"
-        >
-          bar
-        </span>
+        <div>
+          <OrganizationIcon />
+          <span
+            className="spacer-left"
+          >
+            bar
+          </span>
+        </div>
       </OrganizationLink>
     </li>
     <li
       key="foo"
     >
       <OrganizationLink
+        className="dropdown-item-flex"
         onClick={[Function]}
         organization={
           Object {
@@ -229,18 +233,21 @@ exports[`should render the users organizations 1`] = `
           }
         }
       >
-        <OrganizationIcon />
-        <span
-          className="spacer-left"
-        >
-          Foo
-        </span>
+        <div>
+          <OrganizationIcon />
+          <span
+            className="spacer-left"
+          >
+            Foo
+          </span>
+        </div>
       </OrganizationLink>
     </li>
     <li
       key="myorg"
     >
       <OrganizationLink
+        className="dropdown-item-flex"
         onClick={[Function]}
         organization={
           Object {
@@ -249,12 +256,14 @@ exports[`should render the users organizations 1`] = `
           }
         }
       >
-        <OrganizationIcon />
-        <span
-          className="spacer-left"
-        >
-          MyOrg
-        </span>
+        <div>
+          <OrganizationIcon />
+          <span
+            className="spacer-left"
+          >
+            MyOrg
+          </span>
+        </div>
       </OrganizationLink>
     </li>
     <li
index 6de1a659782332e1fb21cda49177d0b74758169f..3d7e09d3457b4e4fb10b0f726ecadba9ad73c1ce 100644 (file)
   color: var(--secondFontColor);
   font-size: 11px;
 }
+
+.dropdown-item-flex {
+  display: flex !important;
+  justify-content: space-between;
+  align-items: center;
+}
index a76d12612b115c17847a7760613c727126bbc6b9..1bded7bb20f6c66ebea5f8bccc614271ad1be0fc 100644 (file)
@@ -51,6 +51,9 @@ export default function OrganizationCard(props /*: Props */) {
 
       <h3 className="account-project-name">
         <OrganizationLink organization={organization}>{organization.name}</OrganizationLink>
+        {organization.isAdmin && (
+          <span className="outline-badge spacer-left">{translate('admin')}</span>
+        )}
       </h3>
 
       {!!organization.description && (
index da353d90380524812ea27e4f563d29255b1a5962..38f30b159b09f8d13785f958f42ae3c849c172ae 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import * as api from '../../../api/organizations';
+import { getOrganizations } from '../../../api/organizations';
 import { receiveMyOrganizations } from '../../../store/organizations/duck';
 import { getValues } from '../../../api/settings';
 import { receiveValues } from '../../settings/store/values/actions';
 
 export const fetchMyOrganizations = () => dispatch => {
-  return api.getMyOrganizations().then(keys => {
-    if (keys.length > 0) {
-      return api.getOrganizations(keys).then(({ organizations }) => {
-        return dispatch(receiveMyOrganizations(organizations));
-      });
-    } else {
-      return dispatch(receiveMyOrganizations([]));
-    }
+  return getOrganizations({ member: true }).then(({ organizations }) => {
+    return dispatch(receiveMyOrganizations(organizations));
   });
 };
 
index c7c6608573f9d547695d2d48ec4d1f44e49bcf1a..d3af96718f682d9ebab373f9d547c24d7c032323 100644 (file)
@@ -41,7 +41,7 @@ const fetchIssueOrganizations = issues => dispatch => {
   }
 
   const organizationKeys = uniq(issues.map(issue => issue.organization));
-  return getOrganizations(organizationKeys).then(
+  return getOrganizations({ organizations: organizationKeys.join() }).then(
     response => dispatch(receiveOrganizations(response.organizations)),
     throwGlobalError
   );
index 7ad9dd4e7c010bc45c65989ac3c5d57fa7f200ca..daa94d94a4d6f2215af584b896055e00c71f4434 100644 (file)
@@ -270,7 +270,7 @@ function fetchProjectOrganizations(projects: Array<{ organization: string }>) {
   }
 
   const organizations = uniq(projects.map(project => project.organization));
-  return getOrganizations(organizations).then(r => r.organizations);
+  return getOrganizations({ organizations: organizations.join() }).then(r => r.organizations);
 }
 
 function mapFacetValues(values: Array<{ val: string; count: number }>) {
index e5b5c49ed82c4aff7b79a6b75e8742ac60511875..4db0fec476c57548aa887987ae5c2a6b8cff8dc6 100644 (file)
@@ -23,7 +23,7 @@ import classNames from 'classnames';
 import { sortBy } from 'lodash';
 import Step from './Step';
 import NewOrganizationForm from './NewOrganizationForm';
-import { getMyOrganizations } from '../../../api/organizations';
+import { getOrganizations } from '../../../api/organizations';
 import Select from '../../../components/controls/Select';
 import { translate } from '../../../helpers/l10n';
 
@@ -68,13 +68,15 @@ export default class OrganizationStep extends React.PureComponent {
   }
 
   fetchOrganizations = () => {
-    getMyOrganizations().then(
-      organizations => {
+    getOrganizations({ member: true }).then(
+      ({ organizations }) => {
         if (this.mounted) {
+          const organizationKeys = organizations.map(o => o.key);
           // best guess: if there is only one organization, then it is personal
           // otherwise, we can't guess, let's display them all as just "existing organizations"
-          const personalOrganization = organizations.length === 1 ? organizations[0] : undefined;
-          const existingOrganizations = organizations.length > 1 ? sortBy(organizations) : [];
+          const personalOrganization =
+            organizationKeys.length === 1 ? organizationKeys[0] : undefined;
+          const existingOrganizations = organizationKeys.length > 1 ? sortBy(organizationKeys) : [];
           const selection = personalOrganization
             ? 'personal'
             : existingOrganizations.length > 0 ? 'existing' : 'new';
index 6bc5530cd54b02a06e475433470729f23b86f2f2..be9c2542a870b349283a7cb8a10797815d41fbdd 100644 (file)
@@ -22,16 +22,18 @@ import React from 'react';
 import { mount } from 'enzyme';
 import OrganizationStep from '../OrganizationStep';
 import { click } from '../../../../helpers/testUtils';
-import { getMyOrganizations } from '../../../../api/organizations';
+import { getOrganizations } from '../../../../api/organizations';
 
 jest.mock('../../../../api/organizations', () => ({
-  getMyOrganizations: jest.fn(() => Promise.resolve(['user', 'another']))
+  getOrganizations: jest.fn(() =>
+    Promise.resolve({ organizations: [{ key: 'user' }, { key: 'another' }] })
+  )
 }));
 
 const currentUser = { isLoggedIn: true, login: 'user' };
 
 beforeEach(() => {
-  getMyOrganizations.mockClear();
+  getOrganizations.mockClear();
 });
 
 // FIXME
index 77ebd43e2f38ed0e6970c350764e976bc11ca536..4b4023f97cebc2e5a86ddef9fe39764d6fb2b35a 100644 (file)
@@ -30,6 +30,7 @@ export type Organization = {
   canProvisionProjects?: boolean,
   canUpdateProjectsVisibilityToPrivate?: boolean,
   description?: string,
+  isAdmin: bool,
   key: string,
   name: string,
   pages?: Array<{ key: string, name: string }>,
index 3775e1d039155cd03bb13d5f7aba78a7f552f123..00836bfe58e8bf4d3e4eb5a6d794fc4998033343 100644 (file)
@@ -45,7 +45,7 @@ export const fetchMetrics = () => dispatch =>
   getAllMetrics().then(metrics => dispatch(receiveMetrics(metrics)), onFail(dispatch));
 
 export const fetchOrganizations = (organizations /*: Array<string> | void */) => dispatch =>
-  getOrganizations(organizations).then(
+  getOrganizations({ organizations: organizations && organizations.join() }).then(
     r => dispatch(receiveOrganizations(r.organizations)),
     onFail(dispatch)
   );
index 32946f38725a1a8d52acab224e64bf5458d832bf..cc182652392838ce14c2da90ab7dec494fe6f668 100644 (file)
@@ -8,6 +8,7 @@ action=Action
 actions=Actions
 active=Active
 add_verb=Add
+admin=Admin
 apply=Apply
 all=All
 and=And