]> source.dussan.org Git - sonarqube.git/commitdiff
[NO-JIRA] Simplify application initialization
authorPhilippe Perrin <philippe.perrin@sonarsource.com>
Tue, 19 Jul 2022 08:46:34 +0000 (10:46 +0200)
committersonartech <sonartech@sonarsource.com>
Thu, 21 Jul 2022 20:03:05 +0000 (20:03 +0000)
15 files changed:
server/sonar-web/src/main/js/api/nav.ts [deleted file]
server/sonar-web/src/main/js/api/navigation.ts [new file with mode: 0644]
server/sonar-web/src/main/js/api/users.ts
server/sonar-web/src/main/js/app/components/AdminContainer.tsx
server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
server/sonar-web/src/main/js/app/components/app-state/AppStateContext.tsx
server/sonar-web/src/main/js/app/index.ts
server/sonar-web/src/main/js/app/utils/startReactApp.tsx
server/sonar-web/src/main/js/apps/marketplace/EditionBoxes.tsx
server/sonar-web/src/main/js/apps/marketplace/__tests__/EditionBoxes-test.tsx
server/sonar-web/src/main/js/apps/projects/components/ApplicationCreation.tsx
server/sonar-web/src/main/js/apps/projects/components/__tests__/ApplicationCreation-test.tsx
server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRowActions-test.tsx

diff --git a/server/sonar-web/src/main/js/api/nav.ts b/server/sonar-web/src/main/js/api/nav.ts
deleted file mode 100644 (file)
index bca04ca..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { throwGlobalError } from '../helpers/error';
-import { getJSON } from '../helpers/request';
-import { BranchParameters } from '../types/branch-like';
-import { Component, Extension } from '../types/types';
-
-type NavComponent = Omit<Component, 'alm' | 'qualifier' | 'leakPeriodDate' | 'path' | 'tags'>;
-
-export function getComponentNavigation(
-  data: { component: string } & BranchParameters
-): Promise<NavComponent> {
-  return getJSON('/api/navigation/component', data).catch(throwGlobalError);
-}
-
-export function getMarketplaceNavigation(): Promise<{ serverId: string; ncloc: number }> {
-  return getJSON('/api/navigation/marketplace').catch(throwGlobalError);
-}
-
-export function getSettingsNavigation(): Promise<{
-  extensions: Extension[];
-  showUpdateCenter: boolean;
-}> {
-  return getJSON('/api/navigation/settings').catch(throwGlobalError);
-}
diff --git a/server/sonar-web/src/main/js/api/navigation.ts b/server/sonar-web/src/main/js/api/navigation.ts
new file mode 100644 (file)
index 0000000..6911120
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { throwGlobalError } from '../helpers/error';
+import { getJSON } from '../helpers/request';
+import { AppState } from '../types/appstate';
+import { BranchParameters } from '../types/branch-like';
+import { Component, Extension } from '../types/types';
+
+type NavComponent = Omit<Component, 'alm' | 'qualifier' | 'leakPeriodDate' | 'path' | 'tags'>;
+
+export function getComponentNavigation(
+  data: { component: string } & BranchParameters
+): Promise<NavComponent> {
+  return getJSON('/api/navigation/component', data).catch(throwGlobalError);
+}
+
+export function getMarketplaceNavigation(): Promise<{ serverId: string; ncloc: number }> {
+  return getJSON('/api/navigation/marketplace').catch(throwGlobalError);
+}
+
+export function getSettingsNavigation(): Promise<{
+  extensions: Extension[];
+  showUpdateCenter: boolean;
+}> {
+  return getJSON('/api/navigation/settings').catch(throwGlobalError);
+}
+
+export function getGlobalNavigation(): Promise<AppState> {
+  return getJSON('/api/navigation/global', undefined, true);
+}
index e512718770e1fce061af9b559c612730f038541b..dae13297e64c986f2752d259bf536754d6c47c5c 100644 (file)
@@ -23,7 +23,7 @@ import { IdentityProvider, Paging } from '../types/types';
 import { CurrentUser, HomePage, NoticeType, User } from '../types/users';
 
 export function getCurrentUser(): Promise<CurrentUser> {
-  return getJSON('/api/users/current');
+  return getJSON('/api/users/current', undefined, true);
 }
 
 export function dismissNotification(notice: NoticeType) {
index de4a84124e4bcb9416f10da26a07fc96277a94da..0b5bcc72ab9bfbd01b476dffdfcb8a2e70363c4f 100644 (file)
@@ -20,7 +20,7 @@
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { Outlet } from 'react-router-dom';
-import { getSettingsNavigation } from '../../api/nav';
+import { getSettingsNavigation } from '../../api/navigation';
 import { getPendingPlugins } from '../../api/plugins';
 import { getSystemStatus, waitSystemUPStatus } from '../../api/system';
 import handleRequiredAuthorization from '../../app/utils/handleRequiredAuthorization';
index 4d6d2f3fac0c52830ceefa2581db2a4885245565..2220ec14d6cf40a279b541defb07c1207b22fcff 100644 (file)
@@ -24,7 +24,7 @@ import { getProjectAlmBinding, validateProjectAlmBinding } from '../../api/alm-s
 import { getBranches, getPullRequests } from '../../api/branches';
 import { getAnalysisStatus, getTasksForComponent } from '../../api/ce';
 import { getComponentData } from '../../api/components';
-import { getComponentNavigation } from '../../api/nav';
+import { getComponentNavigation } from '../../api/navigation';
 import { Location, Router, withRouter } from '../../components/hoc/withRouter';
 import {
   getBranchLikeQuery,
index 88f82cbab52494b611162e15b8430907a812f20e..e7822a0a90cb6dbebea6642319605433a89951a7 100644 (file)
@@ -23,7 +23,7 @@ import { getProjectAlmBinding, validateProjectAlmBinding } from '../../../api/al
 import { getBranches, getPullRequests } from '../../../api/branches';
 import { getAnalysisStatus, getTasksForComponent } from '../../../api/ce';
 import { getComponentData } from '../../../api/components';
-import { getComponentNavigation } from '../../../api/nav';
+import { getComponentNavigation } from '../../../api/navigation';
 import { mockProjectAlmBindingConfigurationErrors } from '../../../helpers/mocks/alm-settings';
 import { mockBranch, mockMainBranch, mockPullRequest } from '../../../helpers/mocks/branch-like';
 import { mockComponent } from '../../../helpers/mocks/component';
@@ -65,7 +65,7 @@ jest.mock('../../../api/components', () => ({
   getComponentData: jest.fn().mockResolvedValue({ component: { analysisDate: '2018-07-30' } })
 }));
 
-jest.mock('../../../api/nav', () => ({
+jest.mock('../../../api/navigation', () => ({
   getComponentNavigation: jest.fn().mockResolvedValue({
     breadcrumbs: [{ key: 'portfolioKey', name: 'portfolio', qualifier: 'VW' }],
     key: 'portfolioKey'
index a8e130c84d2dd94cea0aa9c6f59968de85ba5218..d327bfb3aa180f49150b8fe399ecd58df101674e 100644 (file)
@@ -21,7 +21,7 @@
 import * as React from 'react';
 import { AppState } from '../../../types/appstate';
 
-const defaultAppState = {
+export const DEFAULT_APP_STATE = {
   authenticationError: false,
   authorizationError: false,
   edition: undefined,
@@ -30,4 +30,4 @@ const defaultAppState = {
   settings: {},
   version: ''
 };
-export const AppStateContext = React.createContext<AppState>(defaultAppState);
+export const AppStateContext = React.createContext<AppState>(DEFAULT_APP_STATE);
index a665c02f376360dbb23bf2d21aac4707d94c6776..86c7224278302c063e82f49d31df2a4a273a880c 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 { getGlobalNavigation } from '../api/navigation';
+import { getCurrentUser } from '../api/users';
 import { installExtensionsHandler, installWebAnalyticsHandler } from '../helpers/extensionsHandler';
 import { loadL10nBundle } from '../helpers/l10nBundle';
-import { HttpStatus, parseJSON, request } from '../helpers/request';
 import { getBaseUrl, getSystemStatus } from '../helpers/system';
-import { AppState } from '../types/appstate';
-import { L10nBundle } from '../types/l10nBundle';
-import { CurrentUser } from '../types/users';
 import './styles/sonar.ts';
 
 installWebAnalyticsHandler();
+installExtensionsHandler();
+initApplication();
 
-if (isMainApp()) {
-  installExtensionsHandler();
-
-  loadAll(loadAppState, loadUser).then(
-    ([l10nBundle, user, appState, startReactApp]) => {
-      startReactApp(l10nBundle.locale, appState, user);
-    },
-    error => {
-      if (isResponse(error) && error.status === HttpStatus.Unauthorized) {
-        redirectToLogin();
-      } else {
-        logError(error);
-      }
-    }
-  );
-} else {
-  // login, maintenance or setup pages
-
-  const appStateLoader = () =>
-    loadAppState().catch(() => {
-      return {
-        edition: undefined,
-        productionDatabase: true,
-        qualifiers: [],
-        settings: {},
-        version: ''
-      };
-    });
-
-  loadAll(appStateLoader).then(
-    ([l10nBundle, _user, appState, startReactApp]) => {
-      startReactApp(l10nBundle.locale, appState);
-    },
-    error => {
-      logError(error);
-    }
-  );
-}
-
-async function loadAll(
-  appStateLoader: () => Promise<AppState>,
-  userLoader?: () => Promise<CurrentUser | undefined>
-): Promise<
-  [
-    Required<L10nBundle>,
-    CurrentUser | undefined,
-    AppState,
-    (lang: string, appState: AppState, currentUser?: CurrentUser) => void
-  ]
-> {
-  const [l10nBundle, user, appState] = await Promise.all([
+async function initApplication() {
+  const [l10nBundle, currentUser, appState] = await Promise.all([
     loadL10nBundle(),
-    userLoader ? userLoader() : undefined,
-    appStateLoader()
-  ]);
-
-  const startReactApp = await loadApp();
-
-  return [l10nBundle, user, appState, startReactApp];
-}
-
-function loadUser() {
-  return request('/api/users/current')
-    .submit()
-    .then(checkStatus)
-    .then(parseJSON);
-}
-
-function loadAppState() {
-  return request('/api/navigation/global')
-    .submit()
-    .then(checkStatus)
-    .then(parseJSON);
-}
-
-function loadApp() {
-  return import(/* webpackChunkName: 'app' */ './utils/startReactApp').then(i => i.default);
-}
-
-function checkStatus(response: Response) {
-  return new Promise((resolve, reject) => {
-    if (response.status >= 200 && response.status < 300) {
-      resolve(response);
-    } else {
-      reject(response);
-    }
+    isMainApp() ? getCurrentUser() : undefined,
+    isMainApp() ? getGlobalNavigation() : undefined
+  ]).catch(error => {
+    // eslint-disable-next-line no-console
+    console.error('Application failed to start', error);
+    throw error;
   });
-}
-
-function isResponse(error: any): error is Response {
-  return typeof error.status === 'number';
-}
-
-function redirectToLogin() {
-  const returnTo = window.location.pathname + window.location.search + window.location.hash;
-  window.location.href = `${getBaseUrl()}/sessions/new?return_to=${encodeURIComponent(returnTo)}`;
-}
 
-function logError(error: any) {
-  // eslint-disable-next-line no-console
-  console.error('Application failed to start!', error);
+  const startReactApp = await import('./utils/startReactApp').then(i => i.default);
+  startReactApp(l10nBundle.locale, currentUser, appState);
 }
 
 function isMainApp() {
index b5387cfa9ec3a30321f83d4f05ba75baf113eb49..3bd41cee3be8a2d0af0e75591268be9353f75c53 100644 (file)
@@ -64,6 +64,7 @@ import { AppState } from '../../types/appstate';
 import { CurrentUser } from '../../types/users';
 import AdminContainer from '../components/AdminContainer';
 import App from '../components/App';
+import { DEFAULT_APP_STATE } from '../components/app-state/AppStateContext';
 import AppStateContextProvider from '../components/app-state/AppStateContextProvider';
 import ComponentContainer from '../components/ComponentContainer';
 import CurrentUserContextProvider from '../components/current-user/CurrentUserContextProvider';
@@ -223,14 +224,18 @@ function renderAdminRoutes() {
   );
 }
 
-export default function startReactApp(lang: string, appState: AppState, currentUser?: CurrentUser) {
+export default function startReactApp(
+  lang: string,
+  currentUser?: CurrentUser,
+  appState?: AppState
+) {
   exportModulesAsGlobals();
 
   const el = document.getElementById('content');
 
   render(
     <HelmetProvider>
-      <AppStateContextProvider appState={appState}>
+      <AppStateContextProvider appState={appState ?? DEFAULT_APP_STATE}>
         <CurrentUserContextProvider currentUser={currentUser}>
           <IntlProvider defaultLocale={lang} locale={lang}>
             <GlobalMessagesContainer />
index 6270c9faecb6c40ded3e2ff54cabac425c44a40e..f4c18553a1e0a207284aa8d2b70ae9969370ce28 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import { getMarketplaceNavigation } from '../../api/nav';
+import { getMarketplaceNavigation } from '../../api/navigation';
 import { getAllEditionsAbove } from '../../helpers/editions';
 import { EditionKey } from '../../types/editions';
 import EditionBox from './components/EditionBox';
index 7bc48dbcaa84ddb957227273c803a11e0ef8af0d..b1568cb8b7326cd5cfe19c0617e1df6526bcdedf 100644 (file)
@@ -22,7 +22,7 @@ import * as React from 'react';
 import { EditionKey } from '../../../types/editions';
 import EditionBoxes from '../EditionBoxes';
 
-jest.mock('../../../api/nav', () => ({
+jest.mock('../../../api/navigation', () => ({
   getMarketplaceNavigation: jest.fn().mockResolvedValue({})
 }));
 
index f58be34596ec26efbf17ebfb86f48bcbdd8e0bac..11bf1c334346c1c9ccd1088e15a9e23f9108036e 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import { getComponentNavigation } from '../../../api/nav';
+import { getComponentNavigation } from '../../../api/navigation';
 import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
 import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
 import CreateApplicationForm from '../../../app/components/extensions/CreateApplicationForm';
index ecdfa1b29fed45d246e6750d1965eb76030a783f..163991f03577d8d2a60a9d5ca4c2a7e3db97402d 100644 (file)
@@ -19,7 +19,7 @@
  */
 import { shallow, ShallowWrapper } from 'enzyme';
 import * as React from 'react';
-import { getComponentNavigation } from '../../../../api/nav';
+import { getComponentNavigation } from '../../../../api/navigation';
 import CreateApplicationForm from '../../../../app/components/extensions/CreateApplicationForm';
 import { Button } from '../../../../components/controls/buttons';
 import { mockAppState, mockLoggedInUser, mockRouter } from '../../../../helpers/testMocks';
@@ -27,7 +27,7 @@ import { queryToSearch } from '../../../../helpers/urls';
 import { ComponentQualifier } from '../../../../types/component';
 import { ApplicationCreation, ApplicationCreationProps } from '../ApplicationCreation';
 
-jest.mock('../../../../api/nav', () => ({
+jest.mock('../../../../api/navigation', () => ({
   getComponentNavigation: jest.fn().mockResolvedValue({})
 }));
 
index 6a592edf6d931cc0fdca58f4f112df1e752cb27f..64f0274f234d11754a63137973fdbed5b4f0c28b 100644 (file)
@@ -19,7 +19,7 @@
  */
 import * as React from 'react';
 import { Project } from '../../api/components';
-import { getComponentNavigation } from '../../api/nav';
+import { getComponentNavigation } from '../../api/navigation';
 import ActionsDropdown, { ActionsDropdownItem } from '../../components/controls/ActionsDropdown';
 import DeferredSpinner from '../../components/ui/DeferredSpinner';
 import { translate } from '../../helpers/l10n';
index 95182dc9f18ea2a1532cefe63d7b56c41bfab00b..8a8b3f2e706b252a5a45890ffa2402c575079cae 100644 (file)
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
-import { getComponentNavigation } from '../../../api/nav';
+import { getComponentNavigation } from '../../../api/navigation';
 import { mockLoggedInUser } from '../../../helpers/testMocks';
 import { click, waitAndUpdate } from '../../../helpers/testUtils';
 import ProjectRowActions, { Props } from '../ProjectRowActions';
 
-jest.mock('../../../api/nav', () => ({
+jest.mock('../../../api/navigation', () => ({
   getComponentNavigation: jest.fn().mockResolvedValue({})
 }));