]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10186 fetch user organizations when app starts
authorStas Vilchik <stas.vilchik@sonarsource.com>
Mon, 11 Dec 2017 16:58:18 +0000 (17:58 +0100)
committerStas Vilchik <stas.vilchik@sonarsource.com>
Tue, 2 Jan 2018 09:38:10 +0000 (10:38 +0100)
server/sonar-web/src/main/js/app/components/App.tsx
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.tsx
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUserContainer.tsx
server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.tsx
server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavUser-test.tsx.snap
server/sonar-web/src/main/js/app/types.ts
server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.tsx

index 44528fc0b4dbf9b0b0b13bd1bc658392b941dbb9..9dd53d133691e14d02cddbe71957e352cdef7c3c 100644 (file)
@@ -23,12 +23,14 @@ import * as PropTypes from 'prop-types';
 import GlobalLoading from './GlobalLoading';
 import { fetchCurrentUser } from '../../store/users/actions';
 import { fetchLanguages, fetchAppState } from '../../store/rootActions';
+import { fetchMyOrganizations } from '../../apps/account/organizations/actions';
 
 interface Props {
   children: JSX.Element;
   fetchAppState: () => Promise<any>;
   fetchCurrentUser: () => Promise<void>;
   fetchLanguages: () => Promise<void>;
+  fetchMyOrganizations: () => Promise<void>;
 }
 
 interface State {
@@ -66,7 +68,17 @@ class App extends React.PureComponent<Props, State> {
     this.props
       .fetchCurrentUser()
       .then(() => Promise.all([this.fetchAppState(), this.props.fetchLanguages()]))
-      .then(this.finishLoading, () => {});
+      .then(
+        ([appState]) => {
+          if (this.mounted) {
+            if (appState.organizationsEnabled) {
+              this.props.fetchMyOrganizations();
+            }
+            this.setState({ loading: false });
+          }
+        },
+        () => {}
+      );
   }
 
   componentWillUnmount() {
@@ -76,24 +88,18 @@ class App extends React.PureComponent<Props, State> {
   fetchAppState = () => {
     return this.props.fetchAppState().then(appState => {
       if (this.mounted) {
-        const onSonarCloud =
-          appState.settings !== undefined &&
-          appState.settings['sonar.sonarcloud.enabled'] === 'true';
         this.setState({
           branchesEnabled: appState.branchesEnabled,
           canAdmin: appState.canAdmin,
-          onSonarCloud
+          onSonarCloud:
+            appState.settings !== undefined &&
+            appState.settings['sonar.sonarcloud.enabled'] === 'true'
         });
       }
+      return appState;
     });
   };
 
-  finishLoading = () => {
-    if (this.mounted) {
-      this.setState({ loading: false });
-    }
-  };
-
   render() {
     if (this.state.loading) {
       return <GlobalLoading />;
@@ -102,4 +108,9 @@ class App extends React.PureComponent<Props, State> {
   }
 }
 
-export default connect(null, { fetchAppState, fetchCurrentUser, fetchLanguages })(App as any);
+export default connect(null, {
+  fetchAppState,
+  fetchCurrentUser,
+  fetchLanguages,
+  fetchMyOrganizations
+})(App as any);
index 3a5a143f96348abe6724a0909198d35a8f8503d9..1649a5e370e80ae251c3eb8983f49cf90e113cff 100644 (file)
@@ -33,7 +33,6 @@ import OrganizationAvatar from '../../../../components/common/OrganizationAvatar
 interface Props {
   appState: { organizationsEnabled: boolean };
   currentUser: CurrentUser;
-  fetchMyOrganizations: () => Promise<void>;
   organizations: Organization[];
 }
 
@@ -91,10 +90,8 @@ export default class GlobalNavUser extends React.PureComponent<Props, State> {
   };
 
   openDropdown = () => {
-    this.fetchMyOrganizations().then(() => {
-      window.addEventListener('click', this.handleClickOutside, true);
-      this.setState({ open: true });
-    });
+    window.addEventListener('click', this.handleClickOutside, true);
+    this.setState({ open: true });
   };
 
   closeDropdown = () => {
@@ -102,13 +99,6 @@ export default class GlobalNavUser extends React.PureComponent<Props, State> {
     this.setState({ open: false });
   };
 
-  fetchMyOrganizations = () => {
-    if (this.props.appState.organizationsEnabled) {
-      return this.props.fetchMyOrganizations();
-    }
-    return Promise.resolve();
-  };
-
   renderAuthenticated() {
     const { organizations } = this.props;
     const currentUser = this.props.currentUser as LoggedInUser;
@@ -163,7 +153,7 @@ export default class GlobalNavUser extends React.PureComponent<Props, State> {
                       <OrganizationAvatar organization={organization} small={true} />
                       <span className="spacer-left">{organization.name}</span>
                     </div>
-                    {organization.canAdmin && (
+                    {organization.isAdmin && (
                       <span className="outline-badge spacer-left">{translate('admin')}</span>
                     )}
                   </OrganizationLink>
index f73200791a075cc9f939ae346b2ec3ad042e7250..eaff69f9a86bff3c3b8f1c0d2b5cb65c17d67fec 100644 (file)
@@ -20,7 +20,6 @@
 import { connect } from 'react-redux';
 import GlobalNavUser from './GlobalNavUser';
 import { Organization } from '../../../types';
-import { fetchMyOrganizations } from '../../../../apps/account/organizations/actions';
 import { getMyOrganizations } from '../../../../store/rootReducer';
 
 interface StateProps {
@@ -31,12 +30,4 @@ const mapStateToProps = (state: any): StateProps => ({
   organizations: getMyOrganizations(state)
 });
 
-interface DispatchProps {
-  fetchMyOrganizations: () => Promise<void>;
-}
-
-const mapDispatchToProps = {
-  fetchMyOrganizations: fetchMyOrganizations as any
-} as DispatchProps;
-
-export default connect(mapStateToProps, mapDispatchToProps)(GlobalNavUser);
+export default connect(mapStateToProps)(GlobalNavUser);
index 90cf03932935879cf142db57d22468eb608843f8..659fe6def1659fb20a28dbfb16ebe440d7d9a93e 100644 (file)
@@ -33,104 +33,35 @@ const appState = { organizationsEnabled: true };
 it('should render the right interface for anonymous user', () => {
   const currentUser = { isLoggedIn: false };
   const wrapper = shallow(
-    <GlobalNavUser
-      appState={appState}
-      currentUser={currentUser}
-      fetchMyOrganizations={jest.fn()}
-      organizations={[]}
-    />
+    <GlobalNavUser appState={appState} currentUser={currentUser} organizations={[]} />
   );
   expect(wrapper).toMatchSnapshot();
 });
 
 it('should render the right interface for logged in user', () => {
   const wrapper = shallow(
-    <GlobalNavUser
-      appState={appState}
-      currentUser={currentUser}
-      fetchMyOrganizations={jest.fn()}
-      organizations={[]}
-    />
+    <GlobalNavUser appState={appState} currentUser={currentUser} organizations={[]} />
   );
   wrapper.setState({ open: true });
   expect(wrapper).toMatchSnapshot();
 });
 
-it('should render the users organizations', () => {
+it('should render user organizations', () => {
   const wrapper = shallow(
-    <GlobalNavUser
-      appState={appState}
-      currentUser={currentUser}
-      fetchMyOrganizations={jest.fn()}
-      organizations={organizations}
-    />
+    <GlobalNavUser appState={appState} currentUser={currentUser} organizations={organizations} />
   );
   wrapper.setState({ open: true });
   expect(wrapper).toMatchSnapshot();
 });
 
-it('should not render the users organizations when they are not activated', () => {
+it('should not render user organizations when they are not activated', () => {
   const wrapper = shallow(
     <GlobalNavUser
       appState={{ organizationsEnabled: false }}
       currentUser={currentUser}
-      fetchMyOrganizations={jest.fn()}
       organizations={organizations}
     />
   );
   wrapper.setState({ open: true });
   expect(wrapper).toMatchSnapshot();
 });
-
-it('should update the component correctly when the user changes to anonymous', () => {
-  const fetchMyOrganizations = jest.fn();
-  const wrapper = shallow(
-    <GlobalNavUser
-      appState={appState}
-      currentUser={currentUser}
-      fetchMyOrganizations={fetchMyOrganizations}
-      organizations={[]}
-    />
-  );
-  wrapper.setState({ open: true });
-  expect(wrapper).toMatchSnapshot();
-  wrapper.setProps({ currentUser: { isLoggedIn: false } });
-  expect(fetchMyOrganizations.mock.calls.length).toBe(0);
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('should lazyload the organizations when opening the dropdown', () => {
-  const fetchMyOrganizations = jest.fn(() => Promise.resolve());
-  const wrapper = shallow(
-    <GlobalNavUser
-      appState={appState}
-      currentUser={currentUser}
-      fetchMyOrganizations={fetchMyOrganizations}
-      organizations={organizations}
-    />
-  );
-  expect(fetchMyOrganizations.mock.calls.length).toBe(0);
-  (wrapper.instance() as GlobalNavUser).openDropdown();
-  expect(fetchMyOrganizations.mock.calls.length).toBe(1);
-  (wrapper.instance() as GlobalNavUser).openDropdown();
-  expect(fetchMyOrganizations.mock.calls.length).toBe(2);
-});
-
-it('should update the organizations when the user changes', () => {
-  const fetchMyOrganizations = jest.fn(() => Promise.resolve());
-  const wrapper = shallow(
-    <GlobalNavUser
-      appState={appState}
-      currentUser={currentUser}
-      fetchMyOrganizations={fetchMyOrganizations}
-      organizations={organizations}
-    />
-  );
-  (wrapper.instance() as GlobalNavUser).openDropdown();
-  expect(fetchMyOrganizations.mock.calls.length).toBe(1);
-  wrapper.setProps({
-    currentUser: { isLoggedIn: true, name: 'test', email: 'test@sonarsource.com' }
-  });
-  (wrapper.instance() as GlobalNavUser).openDropdown();
-  expect(fetchMyOrganizations.mock.calls.length).toBe(2);
-});
index 3bb56aac6754582c8016448f0175b5013bc7493d..23fef6f2d99da12580e6da8771fd6ae7cc78030c 100644 (file)
@@ -1,6 +1,6 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`should not render the users organizations when they are not activated 1`] = `
+exports[`should not render user organizations when they are not activated 1`] = `
 <li
   className="dropdown js-user-authenticated open"
 >
@@ -134,7 +134,7 @@ exports[`should render the right interface for logged in user 1`] = `
 </li>
 `;
 
-exports[`should render the users organizations 1`] = `
+exports[`should render user organizations 1`] = `
 <li
   className="dropdown js-user-authenticated open"
 >
@@ -311,76 +311,3 @@ exports[`should render the users organizations 1`] = `
   </ul>
 </li>
 `;
-
-exports[`should update the component correctly when the user changes to anonymous 1`] = `
-<li
-  className="dropdown js-user-authenticated open"
->
-  <a
-    className="dropdown-toggle navbar-avatar"
-    href="#"
-    onClick={[Function]}
-  >
-    <Connect(Avatar)
-      hash="abcd1234"
-      name="foo"
-      size={32}
-    />
-  </a>
-  <ul
-    className="dropdown-menu dropdown-menu-right"
-  >
-    <li
-      className="dropdown-item"
-    >
-      <div
-        className="text-ellipsis text-muted"
-        title="foo"
-      >
-        <strong>
-          foo
-        </strong>
-      </div>
-      <div
-        className="little-spacer-top text-ellipsis text-muted"
-        title="foo@bar.baz"
-      >
-        foo@bar.baz
-      </div>
-    </li>
-    <li
-      className="divider"
-    />
-    <li>
-      <Link
-        onClick={[Function]}
-        onlyActiveOnIndex={false}
-        style={Object {}}
-        to="/account"
-      >
-        my_account.page
-      </Link>
-    </li>
-    <li>
-      <a
-        href="#"
-        onClick={[Function]}
-      >
-        layout.logout
-      </a>
-    </li>
-  </ul>
-</li>
-`;
-
-exports[`should update the component correctly when the user changes to anonymous 2`] = `
-<li>
-  <a
-    className="navbar-login"
-    href="#"
-    onClick={[Function]}
-  >
-    layout.login
-  </a>
-</li>
-`;
index a180856c47ad00315c1fa5e6b6634b3b3e31d379..af3c21a26db528689577e5d271f6a3ffd44906c9 100644 (file)
@@ -115,6 +115,7 @@ export interface Organization {
   canProvisionProjects?: boolean;
   canUpdateProjectsVisibilityToPrivate?: boolean;
   description?: string;
+  isAdmin?: boolean;
   isDefault?: boolean;
   key: string;
   name: string;
index c33f2be07eb865b9085a899ef005bb924bc91f32..e6f34fbd8e2a6658cccdf5dca46f6ce1a438ffbe 100644 (file)
@@ -23,7 +23,7 @@ import { connect } from 'react-redux';
 import OrganizationsList from './OrganizationsList';
 import CreateOrganizationForm from './CreateOrganizationForm';
 import { translate } from '../../../helpers/l10n';
-import { fetchIfAnyoneCanCreateOrganizations, fetchMyOrganizations } from './actions';
+import { fetchIfAnyoneCanCreateOrganizations } from './actions';
 import { getAppState, getMyOrganizations, getGlobalSettingValue } from '../../../store/rootReducer';
 import { Organization } from '../../../app/types';
 
@@ -35,7 +35,6 @@ interface StateProps {
 
 interface DispatchProps {
   fetchIfAnyoneCanCreateOrganizations: () => Promise<void>;
-  fetchMyOrganizations: () => Promise<void>;
 }
 
 interface Props extends StateProps, DispatchProps {}
@@ -51,10 +50,7 @@ class UserOrganizations extends React.PureComponent<Props, State> {
 
   componentDidMount() {
     this.mounted = true;
-    Promise.all([
-      this.props.fetchMyOrganizations(),
-      this.props.fetchIfAnyoneCanCreateOrganizations()
-    ]).then(this.stopLoading, this.stopLoading);
+    this.props.fetchIfAnyoneCanCreateOrganizations().then(this.stopLoading, this.stopLoading);
   }
 
   componentWillUnmount() {
@@ -133,7 +129,6 @@ const mapStateToProps = (state: any): StateProps => ({
 });
 
 const mapDispatchToProps = {
-  fetchMyOrganizations: fetchMyOrganizations as any,
   fetchIfAnyoneCanCreateOrganizations: fetchIfAnyoneCanCreateOrganizations as any
 } as DispatchProps;