]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10601 refresh the page when the web app can not load a js bundle
authorStas Vilchik <stas.vilchik@sonarsource.com>
Wed, 18 Apr 2018 15:10:29 +0000 (17:10 +0200)
committerSonarTech <sonartech@sonarsource.com>
Thu, 10 May 2018 18:20:54 +0000 (20:20 +0200)
41 files changed:
server/sonar-web/src/main/js/app/components/AdminContainer.tsx
server/sonar-web/src/main/js/app/utils/startReactApp.js
server/sonar-web/src/main/js/apps/about/routes.ts
server/sonar-web/src/main/js/apps/account/routes.ts
server/sonar-web/src/main/js/apps/background-tasks/routes.ts
server/sonar-web/src/main/js/apps/code/routes.ts
server/sonar-web/src/main/js/apps/coding-rules/routes.ts
server/sonar-web/src/main/js/apps/component-measures/routes.ts
server/sonar-web/src/main/js/apps/component/routes.ts
server/sonar-web/src/main/js/apps/custom-measures/routes.ts
server/sonar-web/src/main/js/apps/custom-metrics/routes.ts
server/sonar-web/src/main/js/apps/groups/components/App.tsx
server/sonar-web/src/main/js/apps/groups/routes.ts
server/sonar-web/src/main/js/apps/marketplace/routes.ts
server/sonar-web/src/main/js/apps/organizations/forSingleOrganization.js [deleted file]
server/sonar-web/src/main/js/apps/organizations/forSingleOrganization.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/routes.ts
server/sonar-web/src/main/js/apps/permission-templates/components/AppContainer.js
server/sonar-web/src/main/js/apps/permission-templates/routes.ts
server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx
server/sonar-web/src/main/js/apps/permissions/routes.ts
server/sonar-web/src/main/js/apps/portfolio/routes.ts
server/sonar-web/src/main/js/apps/projectActivity/routes.ts
server/sonar-web/src/main/js/apps/projectBranches/routes.ts
server/sonar-web/src/main/js/apps/projectQualityGate/routes.ts
server/sonar-web/src/main/js/apps/projectQualityProfiles/routes.ts
server/sonar-web/src/main/js/apps/projectsManagement/AppContainer.tsx
server/sonar-web/src/main/js/apps/projectsManagement/routes.ts
server/sonar-web/src/main/js/apps/quality-gates/routes.ts
server/sonar-web/src/main/js/apps/quality-profiles/components/App.tsx
server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.tsx
server/sonar-web/src/main/js/apps/quality-profiles/routes.ts
server/sonar-web/src/main/js/apps/sessions/routes.ts
server/sonar-web/src/main/js/apps/settings/routes.ts
server/sonar-web/src/main/js/apps/system/routes.ts
server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.js [deleted file]
server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/users/routes.ts
server/sonar-web/src/main/js/apps/web-api/routes.ts
server/sonar-web/src/main/js/apps/webhooks/routes.ts
server/sonar-web/src/main/js/components/lazyLoad.tsx

index 173efed4d1dd4270830cd7c9b90f3f1e0bf3fbe6..20284555423425822cc6e022fde1ceaad2fe9646 100644 (file)
@@ -39,6 +39,7 @@ import {
 import { translate } from '../../helpers/l10n';
 import { Extension } from '../types';
 import { PluginPendingResult } from '../../api/plugins';
+import handleRequiredAuthorization from '../utils/handleRequiredAuthorization';
 
 interface Props {
   appState: {
@@ -63,10 +64,7 @@ class AdminContainer extends React.PureComponent<Props> {
 
   componentDidMount() {
     if (!this.context.canAdmin) {
-      // workaround cyclic dependencies
-      import('../utils/handleRequiredAuthorization').then(handleRequredAuthorization =>
-        handleRequredAuthorization.default()
-      );
+      handleRequiredAuthorization();
     } else {
       this.fetchNavigationSettings();
       this.props.fetchEditions(this.props.editionsUrl, this.props.appState.version);
index 35ba6778ddfda50b1e77f8685e84d313b9a02caf..1b0d9750a00a8c1af8e0bc1561162b812589692c 100644 (file)
@@ -79,6 +79,7 @@ import documentationRoutes from '../../apps/documentation/routes';
 import webhooksRoutes from '../../apps/webhooks/routes';
 import { maintenanceRoutes, setupRoutes } from '../../apps/maintenance/routes';
 import { globalPermissionsRoutes, projectPermissionsRoutes } from '../../apps/permissions/routes';
+import { lazyLoad } from '../../components/lazyLoad';
 
 function handleUpdate() {
   const { action } = this.state.location;
@@ -185,10 +186,7 @@ const startReactApp = () => {
                 <Route path="profiles" childRoutes={qualityProfilesRoutes} />
                 <Route path="web_api" childRoutes={webAPIRoutes} />
 
-                <Route
-                  getComponent={() =>
-                    import('../components/ComponentContainer').then(i => i.default)
-                  }>
+                <Route component={lazyLoad(() => import('../components/ComponentContainer'))}>
                   <Route path="code" childRoutes={codeRoutes} />
                   <Route path="component_measures" childRoutes={componentMeasuresRoutes} />
                   <Route path="dashboard" childRoutes={overviewRoutes} />
index f3d76997780964e4611d72951d2661d7d313cb59..6d586df600f29412e8d98bb9c071ab41e8e1eeec 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 { RouterState, IndexRouteProps } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      import('./components/AboutApp').then(i => callback(null, { component: i.default }));
-    }
+    indexRoute: { component: lazyLoad(() => import('./components/AboutApp')) }
   }
 ];
 
index d72da0808012a9b587f914eeebd72406b092132e..2683c0988f69526cdaf3e98f0a9b9c71bf48b766 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 { RouterState, IndexRouteProps, RouteComponent } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
-      import('./components/Account').then(i => callback(null, i.default));
-    },
+    component: lazyLoad(() => import('./components/Account')),
     childRoutes: [
       {
-        getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-          import('./profile/Profile').then(i => callback(null, { component: i.default }));
-        }
+        indexRoute: { component: lazyLoad(() => import('./profile/Profile')) }
       },
       {
         path: 'security',
-        getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
-          import('./components/Security').then(i => callback(null, i.default));
-        }
+        component: lazyLoad(() => import('./components/Security'))
       },
       {
         path: 'projects',
-        getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
-          import('./projects/ProjectsContainer').then(i => callback(null, i.default));
-        }
+        component: lazyLoad(() => import('./projects/ProjectsContainer'))
       },
       {
         path: 'notifications',
-        getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
-          import('./notifications/Notifications').then(i => callback(null, i.default));
-        }
+        component: lazyLoad(() => import('./notifications/Notifications'))
       },
       {
         path: 'organizations',
-        getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
-          import('./organizations/UserOrganizations').then(i => callback(null, i.default));
-        }
+        component: lazyLoad(() => import('./organizations/UserOrganizations'))
       }
     ]
   }
index 6035d85f94a529f031f36cda2817837f39680c50..44d7b94e21d2de1a4861cbbfedd2b723e9cc1ac6 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 { RouterState, IndexRouteProps } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      import('./components/BackgroundTasksApp').then(i => callback(null, { component: i.default }));
-    }
+    indexRoute: { component: lazyLoad(() => import('./components/BackgroundTasksApp')) }
   }
 ];
 
index ad7784f990670d20aca71c24d4bc28f8c57111d2..75753a6139a3f5a989610a6df5a56257af1933ce 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 { RouterState, IndexRouteProps } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      import('./components/App').then(i => callback(null, { component: (i as any).default }));
-    }
+    indexRoute: { component: lazyLoad(() => import('./components/App')) }
   }
 ];
 
index 260a9db516a47f987b34a7f87fa51d0a4923708f..3a081ad09343ef6c9be2a699953555ac7d4e3f42 100644 (file)
@@ -17,8 +17,9 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { RouterState, RouteComponent, RedirectFunction } from 'react-router';
+import { RouterState, RedirectFunction } from 'react-router';
 import { parseQuery, serializeQuery } from './query';
+import { lazyLoad } from '../../components/lazyLoad';
 import { RawQuery } from '../../helpers/query';
 
 function parseHash(hash: string): RawQuery {
@@ -47,9 +48,7 @@ const routes = [
           replace({ pathname: nextState.location.pathname, query: normalizedQuery });
         }
       },
-      getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
-        import('./components/App').then(i => callback(null, i.default));
-      }
+      component: lazyLoad(() => import('./components/App'))
     }
   }
 ];
index c5e8c511399a1ffb49ae0ff3f5b3adb8e9d8937d..f0f6cc9101cae538c9bc8e87a2dc8f15c9403453 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 { RouterState, IndexRouteProps, RedirectFunction } from 'react-router';
+import { RouterState, RedirectFunction } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      import('./components/AppContainer').then(i => callback(null, { component: i.default }));
-    }
+    indexRoute: { component: lazyLoad(() => import('./components/AppContainer')) }
   },
   {
     path: 'domain/:domainName',
index fbd6fae2b97d72a07414516d448ff2674d3e0c62..75753a6139a3f5a989610a6df5a56257af1933ce 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 { RouterState, RouteComponent } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    indexRoute: {
-      getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
-        import('./components/App').then(i => callback(null, i.default));
-      }
-    }
+    indexRoute: { component: lazyLoad(() => import('./components/App')) }
   }
 ];
 
index ad7784f990670d20aca71c24d4bc28f8c57111d2..75753a6139a3f5a989610a6df5a56257af1933ce 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 { RouterState, IndexRouteProps } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      import('./components/App').then(i => callback(null, { component: (i as any).default }));
-    }
+    indexRoute: { component: lazyLoad(() => import('./components/App')) }
   }
 ];
 
index 9397beea9d1e16c7fd367940f3ad2952cfe7c2c6..75753a6139a3f5a989610a6df5a56257af1933ce 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 { RouterState, IndexRouteProps } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      import('./components/App').then(i => callback(null, { component: i.default }));
-    }
+    indexRoute: { component: lazyLoad(() => import('./components/App')) }
   }
 ];
 
index e44998e4bde87ca4eca32256ad161bf175a72dcf..3742549abdb777d26975de995f944e61e6771ff3 100644 (file)
@@ -21,6 +21,7 @@ import * as React from 'react';
 import { Helmet } from 'react-helmet';
 import Header from './Header';
 import List from './List';
+import forSingleOrganization from '../../organizations/forSingleOrganization';
 import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
 import { searchUsersGroups, deleteGroup, updateGroup, createGroup } from '../../../api/user_groups';
 import { Group, Paging } from '../../../app/types';
@@ -39,146 +40,148 @@ interface State {
   query: string;
 }
 
-export default class App extends React.PureComponent<Props, State> {
-  mounted = false;
-  state: State = { loading: true, query: '' };
+export default forSingleOrganization(
+  class App extends React.PureComponent<Props, State> {
+    mounted = false;
+    state: State = { loading: true, query: '' };
 
-  componentDidMount() {
-    this.mounted = true;
-    this.fetchGroups();
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
+    componentDidMount() {
+      this.mounted = true;
+      this.fetchGroups();
+    }
 
-  get organization() {
-    return this.props.organization && this.props.organization.key;
-  }
+    componentWillUnmount() {
+      this.mounted = false;
+    }
 
-  makeFetchGroupsRequest = (data?: { p?: number; q?: string }) => {
-    this.setState({ loading: true });
-    return searchUsersGroups({
-      organization: this.organization,
-      q: this.state.query,
-      ...data
-    });
-  };
-
-  stopLoading = () => {
-    if (this.mounted) {
-      this.setState({ loading: false });
+    get organization() {
+      return this.props.organization && this.props.organization.key;
     }
-  };
 
-  fetchGroups = (data?: { p?: number; q?: string }) => {
-    this.makeFetchGroupsRequest(data).then(({ groups, paging }) => {
+    makeFetchGroupsRequest = (data?: { p?: number; q?: string }) => {
+      this.setState({ loading: true });
+      return searchUsersGroups({
+        organization: this.organization,
+        q: this.state.query,
+        ...data
+      });
+    };
+
+    stopLoading = () => {
       if (this.mounted) {
-        this.setState({ groups, loading: false, paging });
+        this.setState({ loading: false });
       }
-    }, this.stopLoading);
-  };
+    };
 
-  fetchMoreGroups = () => {
-    const { paging } = this.state;
-    if (paging && paging.total > paging.pageIndex * paging.pageSize) {
-      this.makeFetchGroupsRequest({ p: paging.pageIndex + 1 }).then(({ groups, paging }) => {
+    fetchGroups = (data?: { p?: number; q?: string }) => {
+      this.makeFetchGroupsRequest(data).then(({ groups, paging }) => {
         if (this.mounted) {
-          this.setState(({ groups: existingGroups = [] }) => ({
-            groups: [...existingGroups, ...groups],
-            loading: false,
-            paging
-          }));
+          this.setState({ groups, loading: false, paging });
         }
       }, this.stopLoading);
-    }
-  };
+    };
+
+    fetchMoreGroups = () => {
+      const { paging } = this.state;
+      if (paging && paging.total > paging.pageIndex * paging.pageSize) {
+        this.makeFetchGroupsRequest({ p: paging.pageIndex + 1 }).then(({ groups, paging }) => {
+          if (this.mounted) {
+            this.setState(({ groups: existingGroups = [] }) => ({
+              groups: [...existingGroups, ...groups],
+              loading: false,
+              paging
+            }));
+          }
+        }, this.stopLoading);
+      }
+    };
 
-  search = (query: string) => {
-    this.fetchGroups({ q: query });
-    this.setState({ query });
-  };
+    search = (query: string) => {
+      this.fetchGroups({ q: query });
+      this.setState({ query });
+    };
 
-  refresh = () => {
-    this.fetchGroups({ q: this.state.query });
-  };
+    refresh = () => {
+      this.fetchGroups({ q: this.state.query });
+    };
 
-  handleCreate = (data: { description: string; name: string }) => {
-    return createGroup({ ...data, organization: this.organization }).then(group => {
-      if (this.mounted) {
-        this.setState(({ groups = [] }: State) => ({
-          groups: [...groups, group]
-        }));
-      }
-    });
-  };
+    handleCreate = (data: { description: string; name: string }) => {
+      return createGroup({ ...data, organization: this.organization }).then(group => {
+        if (this.mounted) {
+          this.setState(({ groups = [] }: State) => ({
+            groups: [...groups, group]
+          }));
+        }
+      });
+    };
 
-  handleDelete = (name: string) => {
-    return deleteGroup({ name, organization: this.organization }).then(() => {
-      if (this.mounted) {
-        this.setState(({ groups = [] }: State) => ({
-          groups: groups.filter(group => group.name !== name)
-        }));
-      }
-    });
-  };
+    handleDelete = (name: string) => {
+      return deleteGroup({ name, organization: this.organization }).then(() => {
+        if (this.mounted) {
+          this.setState(({ groups = [] }: State) => ({
+            groups: groups.filter(group => group.name !== name)
+          }));
+        }
+      });
+    };
 
-  handleEdit = (data: { description?: string; id: number; name?: string }) => {
-    return updateGroup(data).then(() => {
-      if (this.mounted) {
-        this.setState(({ groups = [] }: State) => ({
-          groups: groups.map(group => (group.id === data.id ? { ...group, ...data } : group))
-        }));
-      }
-    });
-  };
-
-  render() {
-    const { groups, loading, paging, query } = this.state;
-
-    const showAnyone =
-      this.props.organization === undefined && 'anyone'.includes(query.toLowerCase());
-
-    return (
-      <>
-        <Suggestions suggestions="user_groups" />
-        <Helmet title={translate('user_groups.page')} />
-        <div className="page page-limited" id="groups-page">
-          <Header loading={loading} onCreate={this.handleCreate} />
-
-          <SearchBox
-            className="big-spacer-bottom"
-            id="groups-search"
-            minLength={2}
-            onChange={this.search}
-            placeholder={translate('search.search_by_name')}
-            value={query}
-          />
-
-          {groups !== undefined && (
-            <List
-              groups={groups}
-              onDelete={this.handleDelete}
-              onEdit={this.handleEdit}
-              onEditMembers={this.refresh}
-              organization={this.organization}
-              showAnyone={showAnyone}
+    handleEdit = (data: { description?: string; id: number; name?: string }) => {
+      return updateGroup(data).then(() => {
+        if (this.mounted) {
+          this.setState(({ groups = [] }: State) => ({
+            groups: groups.map(group => (group.id === data.id ? { ...group, ...data } : group))
+          }));
+        }
+      });
+    };
+
+    render() {
+      const { groups, loading, paging, query } = this.state;
+
+      const showAnyone =
+        this.props.organization === undefined && 'anyone'.includes(query.toLowerCase());
+
+      return (
+        <>
+          <Suggestions suggestions="user_groups" />
+          <Helmet title={translate('user_groups.page')} />
+          <div className="page page-limited" id="groups-page">
+            <Header loading={loading} onCreate={this.handleCreate} />
+
+            <SearchBox
+              className="big-spacer-bottom"
+              id="groups-search"
+              minLength={2}
+              onChange={this.search}
+              placeholder={translate('search.search_by_name')}
+              value={query}
             />
-          )}
-
-          {groups !== undefined &&
-            paging !== undefined && (
-              <div id="groups-list-footer">
-                <ListFooter
-                  count={showAnyone ? groups.length + 1 : groups.length}
-                  loadMore={this.fetchMoreGroups}
-                  ready={!loading}
-                  total={showAnyone ? paging.total + 1 : paging.total}
-                />
-              </div>
+
+            {groups !== undefined && (
+              <List
+                groups={groups}
+                onDelete={this.handleDelete}
+                onEdit={this.handleEdit}
+                onEditMembers={this.refresh}
+                organization={this.organization}
+                showAnyone={showAnyone}
+              />
             )}
-        </div>
-      </>
-    );
+
+            {groups !== undefined &&
+              paging !== undefined && (
+                <div id="groups-list-footer">
+                  <ListFooter
+                    count={showAnyone ? groups.length + 1 : groups.length}
+                    loadMore={this.fetchMoreGroups}
+                    ready={!loading}
+                    total={showAnyone ? paging.total + 1 : paging.total}
+                  />
+                </div>
+              )}
+          </div>
+        </>
+      );
+    }
   }
-}
+);
index 88584bbe9df5eb8878ae55bed9f89f464319071a..75753a6139a3f5a989610a6df5a56257af1933ce 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 { RouterState, IndexRouteProps } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      Promise.all([
-        import('./components/App').then(i => i.default),
-        import('../organizations/forSingleOrganization').then(i => i.default)
-      ]).then(([App, forSingleOrganization]) =>
-        callback(null, { component: forSingleOrganization(App) })
-      );
-    }
+    indexRoute: { component: lazyLoad(() => import('./components/App')) }
   }
 ];
 
index 40cc5e0ded659b6bc00d0f442757ebc1873d8d41..98bf9bf6c67c5980de4987b0eaf74adfff23d848 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 { RouterState, IndexRouteProps } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      import('./AppContainer').then(i => callback(null, { component: i.default }));
-    }
+    indexRoute: { component: lazyLoad(() => import('./AppContainer')) }
   }
 ];
 
diff --git a/server/sonar-web/src/main/js/apps/organizations/forSingleOrganization.js b/server/sonar-web/src/main/js/apps/organizations/forSingleOrganization.js
deleted file mode 100644 (file)
index 013ec53..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info 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 { withRouter } from 'react-router';
-import { areThereCustomOrganizations } from '../../store/rootReducer';
-
-const forSingleOrganization = (ComposedComponent /*: Object */) => {
-  class X extends React.PureComponent {
-    static displayName = `forSingleOrganization(${ComposedComponent.displayName})}`;
-
-    render() {
-      const { customOrganizations, router, ...other } = this.props;
-
-      if (customOrganizations) {
-        router.replace('/not_found');
-        return null;
-      }
-
-      return <ComposedComponent {...other} />;
-    }
-  }
-
-  const mapStateToProps = state => ({
-    customOrganizations: areThereCustomOrganizations(state)
-  });
-
-  return connect(mapStateToProps)(withRouter(X));
-};
-
-export default forSingleOrganization;
diff --git a/server/sonar-web/src/main/js/apps/organizations/forSingleOrganization.tsx b/server/sonar-web/src/main/js/apps/organizations/forSingleOrganization.tsx
new file mode 100644 (file)
index 0000000..4ec2c2a
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.
+ */
+import * as React from 'react';
+import { connect } from 'react-redux';
+import { withRouter, WithRouterProps } from 'react-router';
+import { areThereCustomOrganizations } from '../../store/rootReducer';
+
+type ReactComponent<P> = React.ComponentClass<P> | React.StatelessComponent<P>;
+
+export default function forSingleOrganization<P>(ComposedComponent: ReactComponent<P>) {
+  interface StateProps {
+    customOrganizations: boolean | undefined;
+  }
+
+  class ForSingleOrganization extends React.Component<StateProps & WithRouterProps> {
+    static displayName = `forSingleOrganization(${ComposedComponent.displayName})}`;
+
+    render() {
+      const { customOrganizations, router, ...other } = this.props;
+
+      if (!other.params.organizationKey && customOrganizations) {
+        router.replace('/not_found');
+        return null;
+      }
+
+      return <ComposedComponent {...other} />;
+    }
+  }
+
+  const mapStateToProps = (state: any) => ({
+    customOrganizations: areThereCustomOrganizations(state)
+  });
+
+  return connect(mapStateToProps)(withRouter(ForSingleOrganization));
+}
index ad7784f990670d20aca71c24d4bc28f8c57111d2..75753a6139a3f5a989610a6df5a56257af1933ce 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 { RouterState, IndexRouteProps } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      import('./components/App').then(i => callback(null, { component: (i as any).default }));
-    }
+    indexRoute: { component: lazyLoad(() => import('./components/App')) }
   }
 ];
 
index f9c1134cfbde18165fac08e46c274535f16571d7..6a8777641c834588494b552c131827092324455a 100644 (file)
@@ -19,6 +19,7 @@
  */
 import { connect } from 'react-redux';
 import App from './App';
+import forSingleOrganization from '../../organizations/forSingleOrganization';
 import { getAppState } from '../../../store/rootReducer';
 import { getRootQualifiers } from '../../../store/appState/duck';
 
@@ -27,4 +28,4 @@ const mapStateToProps = state => ({
   topQualifiers: getRootQualifiers(getAppState(state)).filter(q => q !== 'APP')
 });
 
-export default connect(mapStateToProps)(App);
+export default forSingleOrganization(connect(mapStateToProps)(App));
index 2c795d171b4bf70e0ab06f26e86fdafc43175bc1..17fa912dc8ec1f4d008f9b486a901065335e1f42 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 { RouterState, IndexRouteProps } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      Promise.all([
-        import('./components/AppContainer').then(i => i.default),
-        import('../organizations/forSingleOrganization').then(i => i.default)
-      ]).then(([AppContainer, forSingleOrganization]) =>
-        callback(null, { component: forSingleOrganization(AppContainer) })
-      );
-    }
+    indexRoute: { component: lazyLoad(() => import('./components/AppContainer')) }
   }
 ];
 
index d79c2bd51c87e94b8e123f41a47e8fe649855e03..e9b0a329a90d59f2d333b26728357c7f9c75ed7d 100644 (file)
@@ -25,13 +25,14 @@ import PageError from '../../shared/components/PageError';
 import Suggestions from '../../../../app/components/embed-docs-modal/Suggestions';
 import { translate } from '../../../../helpers/l10n';
 import { Organization } from '../../../../app/types';
+import forSingleOrganization from '../../../organizations/forSingleOrganization';
 import '../../styles.css';
 
 interface Props {
   organization?: Organization;
 }
 
-export default function App({ organization }: Props) {
+function App({ organization }: Props) {
   return (
     <div className="page page-limited">
       <Suggestions suggestions="global_permissions" />
@@ -42,3 +43,5 @@ export default function App({ organization }: Props) {
     </div>
   );
 }
+
+export default forSingleOrganization(App);
index 0f8a806aa6228b684a05f0363df64972cb3ca0ff..e7705b24ccee091f5b4910127caff34c18d00c24 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 { RouterState, IndexRouteProps } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 export const globalPermissionsRoutes = [
   {
-    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      Promise.all([
-        import('./global/components/App').then(i => i.default),
-        import('../organizations/forSingleOrganization').then(i => i.default)
-      ]).then(([App, forSingleOrganization]) =>
-        callback(null, { component: forSingleOrganization(App) })
-      );
-    }
+    indexRoute: { component: lazyLoad(() => import('./global/components/App')) }
   }
 ];
 
 export const projectPermissionsRoutes = [
   {
-    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      import('./project/components/AppContainer').then(i =>
-        callback(null, { component: i.default })
-      );
-    }
+    indexRoute: { component: lazyLoad(() => import('./project/components/AppContainer')) }
   }
 ];
index ad7784f990670d20aca71c24d4bc28f8c57111d2..75753a6139a3f5a989610a6df5a56257af1933ce 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 { RouterState, IndexRouteProps } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      import('./components/App').then(i => callback(null, { component: (i as any).default }));
-    }
+    indexRoute: { component: lazyLoad(() => import('./components/App')) }
   }
 ];
 
index 7c12b45e3ec311f9cfbcba10c6bc4d13e134a627..af7d4dac0d08535d5240cce09824e53ce3e4c4f8 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 { RouterState, IndexRouteProps } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      import('./components/ProjectActivityAppContainer').then(i =>
-        callback(null, { component: (i as any).default })
-      );
+    indexRoute: {
+      component: lazyLoad(() => import('./components/ProjectActivityAppContainer') as any)
     }
   }
 ];
index e1d1d87abde196709dab49481a6c9cb4a5fb689e..17fa912dc8ec1f4d008f9b486a901065335e1f42 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 { RouterState, IndexRouteProps } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      import('./components/AppContainer').then(i =>
-        callback(null, { component: (i as any).default })
-      );
-    }
+    indexRoute: { component: lazyLoad(() => import('./components/AppContainer')) }
   }
 ];
 
index 6df704df94c3325a2af10ed3660d2dbe29b830da..6f1319dbb921544e987e0a9cb6eaa68f303778f5 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 { RouterState, IndexRouteProps } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      import('./App').then(i => callback(null, { component: i.default }));
-    }
+    indexRoute: { component: lazyLoad(() => import('./App')) }
   }
 ];
 
index 6df704df94c3325a2af10ed3660d2dbe29b830da..6f1319dbb921544e987e0a9cb6eaa68f303778f5 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 { RouterState, IndexRouteProps } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      import('./App').then(i => callback(null, { component: i.default }));
-    }
+    indexRoute: { component: lazyLoad(() => import('./App')) }
   }
 ];
 
index 798b1b034f1bdd44ceeb549006137559143e6b65..1ad68ed7e8a4cbdcea77813c9ec298ee96cb091d 100644 (file)
 import * as React from 'react';
 import { connect } from 'react-redux';
 import App from './App';
-import { Organization } from '../../app/types';
+import forSingleOrganization from '../organizations/forSingleOrganization';
+import { Organization, LoggedInUser } from '../../app/types';
 import { onFail } from '../../store/rootActions';
 import { getAppState, getOrganizationByKey, getCurrentUser } from '../../store/rootReducer';
 import { receiveOrganizations } from '../../store/organizations/duck';
 import { changeProjectVisibility } from '../../api/organizations';
 import { fetchOrganization } from '../../apps/organizations/actions';
 
-interface Props {
-  appState: {
-    defaultOrganization: string;
-    qualifiers: string[];
-  };
-  currentUser: { login: string };
+interface StateProps {
+  appState: { defaultOrganization: string; qualifiers: string[] };
+  currentUser: LoggedInUser;
+  organization?: Organization;
+}
+
+interface DispatchProps {
   fetchOrganization: (organization: string) => void;
   onVisibilityChange: (organization: Organization, visibility: string) => void;
+}
+
+interface OwnProps {
   onRequestFail: (error: any) => void;
-  organization?: Organization;
 }
 
-class AppContainer extends React.PureComponent<Props> {
+class AppContainer extends React.PureComponent<OwnProps & StateProps & DispatchProps> {
   componentDidMount() {
     // if there is no organization, that means we are in the global scope
     // let's fetch defails for the default organization in this case
@@ -75,9 +79,9 @@ class AppContainer extends React.PureComponent<Props> {
   }
 }
 
-const mapStateToProps = (state: any, ownProps: Props) => ({
+const mapStateToProps = (state: any, ownProps: any) => ({
   appState: getAppState(state),
-  currentUser: getCurrentUser(state),
+  currentUser: getCurrentUser(state) as LoggedInUser,
   organization:
     ownProps.organization || getOrganizationByKey(state, getAppState(state).defaultOrganization)
 });
@@ -99,4 +103,6 @@ const mapDispatchToProps = (dispatch: Function) => ({
     dispatch(onVisibilityChange(organization, visibility))
 });
 
-export default connect<any, any, any>(mapStateToProps, mapDispatchToProps)(AppContainer);
+export default forSingleOrganization(
+  connect<StateProps, DispatchProps, OwnProps>(mapStateToProps, mapDispatchToProps)(AppContainer)
+);
index e2b34050856561f406c4e347bc1b7eefa5be1887..98bf9bf6c67c5980de4987b0eaf74adfff23d848 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 { RouterState, IndexRouteProps } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      Promise.all([
-        import('./AppContainer').then(i => i.default),
-        import('../organizations/forSingleOrganization').then(i => i.default)
-      ]).then(([AppContainer, forSingleOrganization]) =>
-        callback(null, { component: forSingleOrganization(AppContainer) })
-      );
-    }
+    indexRoute: { component: lazyLoad(() => import('./AppContainer')) }
   }
 ];
 
index 4d9769f5a709db88f8ea2f41bf7c99d44cd67904..83ba110b6e8b9eb2ada14355223a834260a0ca9b 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 { RouterState, IndexRouteProps, RouteComponent } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
-      import('./components/QualityGatesApp').then(i => callback(null, i.default));
-    },
+    component: lazyLoad(() => import('./components/QualityGatesApp')),
     childRoutes: [
       {
-        getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-          import('./components/Intro').then(i => callback(null, { component: i.default }));
-        }
+        indexRoute: { component: lazyLoad(() => import('./components/Intro')) }
       },
       {
         path: 'show/:id',
-        getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
-          import('./components/DetailsApp').then(i => callback(null, i.default));
-        }
+        component: lazyLoad(() => import('./components/DetailsApp'))
       }
     ]
   }
index 2fe1b621c9725ad26df4c59c3f596bcd41d31d2e..3a7a834957f299f353a62c94465536f3cb6b6583 100644 (file)
@@ -21,14 +21,15 @@ import * as React from 'react';
 import { searchQualityProfiles, getExporters, Actions } from '../../../api/quality-profiles';
 import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
 import { sortProfiles } from '../utils';
-import { translate } from '../../../helpers/l10n';
+import { Exporter, Profile } from '../types';
 import OrganizationHelmet from '../../../components/common/OrganizationHelmet';
+import { translate } from '../../../helpers/l10n';
+import { Languages } from '../../../store/languages/reducer';
 import '../styles.css';
-import { Exporter, Profile } from '../types';
 
 interface Props {
   children: React.ReactElement<any>;
-  languages: Array<{}>;
+  languages: Languages;
   onRequestFail: (reasong: any) => void;
   organization: { name: string; key: string } | null;
 }
index fae68d40027d80b7936550b8c315d23d2d29fe26..1f1d6d2f4a5381442c0c8b825c0fdcb12ce65a5a 100644 (file)
  */
 import { connect } from 'react-redux';
 import App from './App';
-import { getLanguages, getCurrentUser, getOrganizationByKey } from '../../../store/rootReducer';
+import forSingleOrganization from '../../organizations/forSingleOrganization';
+import { getLanguages, getOrganizationByKey } from '../../../store/rootReducer';
 import { onFail } from '../../../store/rootActions';
+import { Languages } from '../../../store/languages/reducer';
+
+interface StateProps {
+  languages: Languages;
+  organization: { name: string; key: string } | null;
+}
+
+interface DispatchProps {
+  onRequestFail: (reasong: any) => void;
+}
 
 const mapStateToProps = (state: any, ownProps: any) => ({
-  currentUser: getCurrentUser(state),
   languages: getLanguages(state),
   organization: ownProps.params.organizationKey
     ? getOrganizationByKey(state, ownProps.params.organizationKey)
@@ -34,4 +44,6 @@ const mapDispatchToProps = (dispatch: any) => ({
   onRequestFail: (error: any) => onFail(dispatch)(error)
 });
 
-export default connect(mapStateToProps, mapDispatchToProps)(App as any);
+export default forSingleOrganization(
+  connect<StateProps, DispatchProps>(mapStateToProps, mapDispatchToProps)(App)
+);
index 05f00838a0e5f4b253822c95717a56e527d6d28f..8935bac3555198a2309a09f94a2b4ce800559b3e 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 { RouterState, IndexRouteProps, RouteComponent, withRouter } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    getComponent(state: RouterState, callback: (err: any, component: RouteComponent) => any) {
-      import('./components/AppContainer')
-        .then(i => i.default)
-        .then(AppContainer => {
-          if (state.params.organizationKey) {
-            callback(null, AppContainer);
-          } else {
-            import('../organizations/forSingleOrganization')
-              .then(i => i.default)
-              .then(forSingleOrganization => callback(null, forSingleOrganization(AppContainer)));
-          }
-        });
-    },
-    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      import('./home/HomeContainer').then(i => callback(null, { component: i.default }));
-    },
+    component: lazyLoad(() => import('./components/AppContainer')),
+    indexRoute: { component: lazyLoad(() => import('./home/HomeContainer')) },
     childRoutes: [
       {
-        getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
-          import('./components/ProfileContainer').then(i => callback(null, withRouter(i.default)));
-        },
+        component: lazyLoad(() => import('./components/ProfileContainer')),
         childRoutes: [
           {
             path: 'show',
-            getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
-              import('./details/ProfileDetails').then(i => callback(null, i.default));
-            }
+            component: lazyLoad(() => import('./details/ProfileDetails'))
           },
           {
             path: 'changelog',
-            getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
-              import('./changelog/ChangelogContainer').then(i => callback(null, i.default));
-            }
+            component: lazyLoad(() => import('./changelog/ChangelogContainer'))
           },
           {
             path: 'compare',
-            getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
-              import('./compare/ComparisonContainer').then(i => callback(null, i.default));
-            }
+            component: lazyLoad(() => import('./compare/ComparisonContainer'))
           }
         ]
       }
index 31b74819be8bbe5aec45e078cfd43dade5560fde..28945131363349a066676cc3b6cd5555d8e5d12e 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 { RouterState, RouteComponent } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
     path: 'new',
-    getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
-      import('./components/LoginContainer').then(i => callback(null, i.default));
-    }
+    component: lazyLoad(() => import('./components/LoginContainer'))
   },
   {
     path: 'logout',
-    getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
-      import('./components/Logout').then(i => callback(null, i.default));
-    }
+    component: lazyLoad(() => import('./components/Logout'))
   },
   {
     path: 'unauthorized',
-    getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
-      import('./components/Unauthorized').then(i => callback(null, i.default));
-    }
+    component: lazyLoad(() => import('./components/Unauthorized'))
   },
   {
     path: 'email_already_exists',
-    getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
-      import('./components/EmailAlreadyExists').then(i => callback(null, i.default));
-    }
+    component: lazyLoad(() => import('./components/EmailAlreadyExists'))
   }
 ];
 
index c1c9cc95210daf9a615c46324f64db7c1d287c8f..7c54dfe0d720b5174875b1bf9e77c8056acd3bd9 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 { RouterState, RouteComponent, IndexRouteProps } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      import('./components/AppContainer').then(i => callback(null, { component: i.default }));
-    }
+    indexRoute: { component: lazyLoad(() => import('./components/AppContainer')) }
   },
   {
     path: 'encryption',
-    getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
-      import('./encryption/EncryptionAppContainer').then(i => callback(null, i.default));
-    }
+    component: lazyLoad(() => import('./encryption/EncryptionAppContainer'))
   }
 ];
 
index 9397beea9d1e16c7fd367940f3ad2952cfe7c2c6..75753a6139a3f5a989610a6df5a56257af1933ce 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 { RouterState, IndexRouteProps } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      import('./components/App').then(i => callback(null, { component: i.default }));
-    }
+    indexRoute: { component: lazyLoad(() => import('./components/App')) }
   }
 ];
 
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.js
deleted file mode 100644 (file)
index 68747dd..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info 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 Modal from '../../../components/controls/Modal';
-import { translate } from '../../../helpers/l10n';
-
-/*::
-type Props = {|
-  onFinish: () => void
-|};
-*/
-
-/*::
-type State = {
-  OnboardingContainer?: Object
-};
-*/
-
-export default class OnboardingModal extends React.PureComponent {
-  /*:: mounted: boolean; */
-  /*:: props: Props; */
-  state /*: State */ = {};
-
-  componentDidMount() {
-    this.mounted = true;
-    import('./OnboardingContainer').then(i => this.receiveComponent(i.default));
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  receiveComponent = (OnboardingContainer /*: Object */) => {
-    if (this.mounted) {
-      this.setState({ OnboardingContainer });
-    }
-  };
-
-  render() {
-    const { OnboardingContainer } = this.state;
-
-    return (
-      <Modal contentLabel={translate('tutorials.onboarding')} large={true}>
-        {OnboardingContainer != null && <OnboardingContainer onFinish={this.props.onFinish} />}
-      </Modal>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.tsx b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.tsx
new file mode 100644 (file)
index 0000000..e1013a3
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.
+ */
+import * as React from 'react';
+import Modal from '../../../components/controls/Modal';
+import { translate } from '../../../helpers/l10n';
+import { lazyLoad } from '../../../components/lazyLoad';
+
+interface Props {
+  onFinish: () => void;
+}
+
+const OnboardingContainer = lazyLoad(() => import('./OnboardingContainer'));
+
+export default function OnboardingModal({ onFinish }: Props) {
+  return (
+    <Modal contentLabel={translate('tutorials.onboarding')} large={true}>
+      <OnboardingContainer onFinish={onFinish} />
+    </Modal>
+  );
+}
index 691aab5f32f3b07e6c2f2ac0b143dca8f35e4bf9..d33b655e8e3279c72fcda6e62b41c80ec7d7cff0 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 { RouterState, IndexRouteProps } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      import('./UsersAppContainer').then(i => callback(null, { component: i.default }));
-    }
+    indexRoute: { component: lazyLoad(() => import('./UsersAppContainer')) }
   }
 ];
 
index 7fafd6740921c6d5b1e5caa7136bfec6969d8351..f3bb72447b8d59b2093d6192df331ff7c1ffd33d 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 { RouterState, RouteComponent, IndexRouteProps } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      import('./components/WebApiApp').then(i => callback(null, { component: (i as any).default }));
-    }
+    indexRoute: { component: lazyLoad(() => import('./components/WebApiApp')) }
   },
   {
     path: '**',
-    getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
-      import('./components/WebApiApp').then(i => callback(null, (i as any).default));
-    }
+    component: lazyLoad(() => import('./components/WebApiApp'))
   }
 ];
 
index fbd6fae2b97d72a07414516d448ff2674d3e0c62..75753a6139a3f5a989610a6df5a56257af1933ce 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 { RouterState, RouteComponent } from 'react-router';
+import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    indexRoute: {
-      getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
-        import('./components/App').then(i => callback(null, i.default));
-      }
-    }
+    indexRoute: { component: lazyLoad(() => import('./components/App')) }
   }
 ];
 
index 34f6de91d6be07b72cd40114f5431a86e624efd6..7043b40f4407771a7dbf4a63b19d10aa1b4173f0 100644 (file)
@@ -18,6 +18,8 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
+import { translate } from '../helpers/l10n';
+import { get, save } from '../helpers/storage';
 
 type ReactComponent<P> = React.ComponentClass<P> | React.StatelessComponent<P>;
 
@@ -25,9 +27,16 @@ interface Loader<P> {
   (): Promise<{ default: ReactComponent<P> }>;
 }
 
+export const LAST_FAILED_CHUNK_STORAGE_KEY = 'sonarqube.last_failed_chunk';
+
 export function lazyLoad<P>(loader: Loader<P>) {
+  interface ImportError {
+    request?: string;
+  }
+
   interface State {
     Component?: ReactComponent<P>;
+    error?: ImportError;
   }
 
   // use `React.Component`, not `React.PureComponent` to always re-render
@@ -38,7 +47,7 @@ export function lazyLoad<P>(loader: Loader<P>) {
 
     componentDidMount() {
       this.mounted = true;
-      loader().then(i => this.receiveComponent(i.default), () => {});
+      loader().then(i => this.receiveComponent(i.default), this.failToReceiveComponent);
     }
 
     componentWillUnmount() {
@@ -47,12 +56,35 @@ export function lazyLoad<P>(loader: Loader<P>) {
 
     receiveComponent = (Component: ReactComponent<P>) => {
       if (this.mounted) {
-        this.setState({ Component });
+        this.setState({ Component, error: undefined });
+      }
+    };
+
+    failToReceiveComponent = (error?: ImportError) => {
+      const lastFailedChunk = get(LAST_FAILED_CHUNK_STORAGE_KEY);
+      if (error && error.request === lastFailedChunk) {
+        // BOOM!
+        // this is the second time we try to load the same file
+        // usually that means the file does not exist on the server
+        // so we should not try to reload the page to not fall into infinite reloading
+        // just show the error message
+        if (this.mounted) {
+          this.setState({ Component: undefined, error });
+        }
+      } else {
+        if (error && error.request) {
+          save(LAST_FAILED_CHUNK_STORAGE_KEY, error.request);
+        }
+        window.location.reload();
       }
     };
 
     render() {
-      const { Component } = this.state;
+      const { Component, error } = this.state;
+
+      if (error && error.request) {
+        return <div className="alert alert-danger">{translate('default_error_message')}</div>;
+      }
 
       if (!Component) {
         return null;