]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10833 Reduce loading waterfall
authorStas Vilchik <stas.vilchik@sonarsource.com>
Wed, 30 May 2018 15:33:38 +0000 (17:33 +0200)
committerSonarTech <sonartech@sonarsource.com>
Mon, 4 Jun 2018 18:20:50 +0000 (20:20 +0200)
60 files changed:
server/sonar-server/src/main/java/org/sonar/server/ui/ws/GlobalAction.java
server/sonar-server/src/test/java/org/sonar/server/ui/ws/GlobalActionTest.java
server/sonar-web/config/webpack.config.js
server/sonar-web/public/index.html
server/sonar-web/src/main/js/app/components/App.tsx
server/sonar-web/src/main/js/app/components/AppContextContainer.tsx [deleted file]
server/sonar-web/src/main/js/app/components/GlobalFooter.tsx
server/sonar-web/src/main/js/app/components/GlobalLoading.tsx
server/sonar-web/src/main/js/app/components/Landing.tsx
server/sonar-web/src/main/js/app/components/MigrationContainer.js [deleted file]
server/sonar-web/src/main/js/app/components/MigrationContainer.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/__tests__/GlobalFooter-test.tsx
server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx
server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsProvider.tsx
server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/SuggestionsProvider-test.tsx
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx
server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx
server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavExplore.tsx
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.tsx
server/sonar-web/src/main/js/app/utils/startReactApp.js
server/sonar-web/src/main/js/apps/about/components/AboutApp.js
server/sonar-web/src/main/js/apps/documentation/components/App.tsx
server/sonar-web/src/main/js/apps/documentation/components/Menu.tsx
server/sonar-web/src/main/js/apps/issues/IssuesPageSelector.tsx
server/sonar-web/src/main/js/apps/issues/components/App.tsx
server/sonar-web/src/main/js/apps/issues/components/AppContainer.tsx
server/sonar-web/src/main/js/apps/issues/components/PageActions.tsx
server/sonar-web/src/main/js/apps/organizations/components/OrganizationDelete.tsx
server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationDelete-test.tsx
server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMeta.tsx
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMeta-test.tsx
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.tsx.snap
server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx
server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx
server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx
server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.tsx
server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx
server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelectorContainer.tsx
server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx
server/sonar-web/src/main/js/apps/projects/components/PageHeader.tsx
server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx
server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx
server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx
server/sonar-web/src/main/js/apps/projects/components/__tests__/NoFavoriteProjects-test.tsx
server/sonar-web/src/main/js/apps/projects/components/__tests__/PageHeader-test.tsx
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap
server/sonar-web/src/main/js/apps/sessions/components/LoginContainer.tsx
server/sonar-web/src/main/js/apps/tutorials/onboarding/AnalysisStep.js
server/sonar-web/src/main/js/apps/tutorials/onboarding/LanguageStep.js
server/sonar-web/src/main/js/apps/tutorials/onboarding/Onboarding.js
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/LanguageStep-test.js
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/Onboarding-test.js
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/Onboarding-test.js.snap
server/sonar-web/src/main/js/components/common/InstanceMessage.tsx
server/sonar-web/src/main/js/components/common/__tests__/InstanceMessage-test.tsx
server/sonar-web/src/main/js/components/docs/DocMarkdownBlock.tsx
server/sonar-web/src/main/js/components/docs/__tests__/DocMarkdownBlock-test.tsx
server/sonar-web/src/main/js/helpers/system.ts [new file with mode: 0644]

index 19baa4411087a9261439f307973e7fe681d425a7..b2b70701e6e68a90672051fecab752a951b7884f 100644 (file)
@@ -50,7 +50,6 @@ import static org.sonar.core.config.WebConstants.SONAR_LF_ENABLE_GRAVATAR;
 import static org.sonar.core.config.WebConstants.SONAR_LF_GRAVATAR_SERVER_URL;
 import static org.sonar.core.config.WebConstants.SONAR_LF_LOGO_URL;
 import static org.sonar.core.config.WebConstants.SONAR_LF_LOGO_WIDTH_PX;
-import static org.sonar.process.ProcessProperties.Property.SONARCLOUD_ENABLED;
 import static org.sonar.process.ProcessProperties.Property.SONAR_UPDATECENTER_ACTIVATE;
 
 public class GlobalAction implements NavigationWsAction, Startable {
@@ -94,7 +93,6 @@ public class GlobalAction implements NavigationWsAction, Startable {
 
   @Override
   public void start() {
-    this.systemSettingValuesByKey.put(SONARCLOUD_ENABLED.getKey(), config.get(SONARCLOUD_ENABLED.getKey()).orElse(null));
     this.systemSettingValuesByKey.put(SONAR_UPDATECENTER_ACTIVATE.getKey(), config.get(SONAR_UPDATECENTER_ACTIVATE.getKey()).orElse(null));
   }
 
index adc91a98de50638b3ea8ad3707a4bf809b22f59b..0daaf5c5a3a3b3079bf34ad479b020b21caf7a0a 100644 (file)
@@ -104,7 +104,6 @@ public class GlobalActionTest {
     settings.setProperty("sonar.lf.logoWidthPx", 135);
     settings.setProperty("sonar.lf.gravatarServerUrl", "https://secure.gravatar.com/avatar/{EMAIL_MD5}.jpg?s={SIZE}&d=identicon");
     settings.setProperty("sonar.lf.enableGravatar", true);
-    settings.setProperty("sonar.sonarcloud.enabled", true);
     settings.setProperty("sonar.updatecenter.activate", false);
     settings.setProperty("sonar.editions.jsonUrl", "https://foo.bar/editions.json");
     settings.setProperty("sonar.technicalDebt.ratingGrid", "0.05,0.1,0.2,0.5");
@@ -118,7 +117,6 @@ public class GlobalActionTest {
       "    \"sonar.lf.logoWidthPx\": \"135\"," +
       "    \"sonar.lf.gravatarServerUrl\": \"https://secure.gravatar.com/avatar/{EMAIL_MD5}.jpg?s={SIZE}&d=identicon\"," +
       "    \"sonar.lf.enableGravatar\": \"true\"," +
-      "    \"sonar.sonarcloud.enabled\": \"true\"," +
       "    \"sonar.editions.jsonUrl\": \"https://foo.bar/editions.json\"," +
       "    \"sonar.updatecenter.activate\": \"false\"," +
       "    \"sonar.technicalDebt.ratingGrid\": \"0.05,0.1,0.2,0.5\"" +
index 5e89c7f0e2f2374a0b4c7fe1144a793637d34696..7289ad9554418bd21e13e494e5be760127b824d7 100644 (file)
@@ -137,7 +137,12 @@ module.exports = ({ production = true }) => ({
     }),
 
     // keep `InterpolateHtmlPlugin` after `HtmlWebpackPlugin`
-    !production && new InterpolateHtmlPlugin({ WEB_CONTEXT: '' }),
+    !production &&
+      new InterpolateHtmlPlugin({
+        WEB_CONTEXT: process.env.WEB_CONTEXT || '',
+        SERVER_STATUS: process.env.SERVER_STATUS || 'UP',
+        INSTANCE: process.env.INSTANCE || 'SonarQube'
+      }),
 
     !production && new webpack.HotModuleReplacementPlugin()
   ].filter(Boolean),
index 4da3597a841e3166c1436f925820f7e589405635..812681b41f7ec9c83e776f9d0f534bba2148a1d3 100644 (file)
@@ -23,7 +23,7 @@
   <meta name="msapplication-TileColor" content="#FFFFFF" />
   <meta name="msapplication-TileImage" content="%WEB_CONTEXT%/mstile-512x512.png" />
 
-  <title>Loading...</title>
+  <title>%INSTANCE%</title>
   
   <% for (let css of htmlWebpackPlugin.files.css) { %>
     <style><%= compilation.assets[css].source() %></style>
       <span class="global-loading-text">Loading...</span>
     </div>
   </div>
-  <script>window.baseUrl = '%WEB_CONTEXT%';</script>
+  <script>
+    window.baseUrl = '%WEB_CONTEXT%';
+    window.serverStatus = '%SERVER_STATUS%';
+    window.instance = '%INSTANCE%';
+  </script>
   <% for (let chunk in htmlWebpackPlugin.files.chunks) { %>
     <script src="%WEB_CONTEXT%/<%= htmlWebpackPlugin.files.chunks[chunk].entry %>"></script>
   <% } %>
index 7390182e2fd6537b8259a1e62d15722edfc3b03f..3930b084f79784198bb85ec8a356e93ddf70c1d3 100644 (file)
@@ -26,6 +26,7 @@ import { CurrentUser } from '../types';
 import { fetchCurrentUser } from '../../store/users/actions';
 import { fetchLanguages, fetchAppState } from '../../store/rootActions';
 import { fetchMyOrganizations } from '../../apps/account/organizations/actions';
+import { getInstance } from '../../helpers/system';
 
 interface Props {
   children: JSX.Element;
@@ -39,7 +40,6 @@ interface State {
   branchesEnabled: boolean;
   canAdmin: boolean;
   loading: boolean;
-  onSonarCloud: boolean;
   organizationsEnabled: boolean;
 }
 
@@ -49,7 +49,6 @@ class App extends React.PureComponent<Props, State> {
   static childContextTypes = {
     branchesEnabled: PropTypes.bool.isRequired,
     canAdmin: PropTypes.bool.isRequired,
-    onSonarCloud: PropTypes.bool,
     organizationsEnabled: PropTypes.bool
   };
 
@@ -59,7 +58,6 @@ class App extends React.PureComponent<Props, State> {
       branchesEnabled: false,
       canAdmin: false,
       loading: true,
-      onSonarCloud: false,
       organizationsEnabled: false
     };
   }
@@ -68,7 +66,6 @@ class App extends React.PureComponent<Props, State> {
     return {
       branchesEnabled: this.state.branchesEnabled,
       canAdmin: this.state.canAdmin,
-      onSonarCloud: this.state.onSonarCloud,
       organizationsEnabled: this.state.organizationsEnabled
     };
   }
@@ -104,9 +101,6 @@ class App extends React.PureComponent<Props, State> {
         this.setState({
           branchesEnabled: appState.branchesEnabled,
           canAdmin: appState.canAdmin,
-          onSonarCloud: Boolean(
-            appState.settings && appState.settings['sonar.sonarcloud.enabled'] === 'true'
-          ),
           organizationsEnabled: appState.organizationsEnabled
         });
       }
@@ -120,7 +114,7 @@ class App extends React.PureComponent<Props, State> {
     }
     return (
       <>
-        <Helmet defaultTitle={this.state.onSonarCloud ? 'SonarCloud' : 'SonarQube'} />
+        <Helmet defaultTitle={getInstance()} />
         {this.props.children}
       </>
     );
diff --git a/server/sonar-web/src/main/js/app/components/AppContextContainer.tsx b/server/sonar-web/src/main/js/app/components/AppContextContainer.tsx
deleted file mode 100644 (file)
index 0ab3e78..0000000
+++ /dev/null
@@ -1,76 +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.
- */
-import * as React from 'react';
-import * as PropTypes from 'prop-types';
-import Helmet from 'react-helmet';
-import GlobalLoading from './GlobalLoading';
-import { tryGetGlobalNavigation } from '../../api/nav';
-
-interface Props {
-  children?: React.ReactNode;
-}
-
-interface State {
-  loading: boolean;
-  onSonarCloud: boolean;
-}
-
-export default class AppContextContainer extends React.PureComponent<Props, State> {
-  mounted = false;
-  static childContextTypes = { onSonarCloud: PropTypes.bool };
-  state: State = { loading: true, onSonarCloud: false };
-
-  getChildContext() {
-    return { onSonarCloud: this.state.onSonarCloud };
-  }
-
-  componentDidMount() {
-    this.mounted = true;
-    tryGetGlobalNavigation().then(
-      appState => {
-        if (this.mounted) {
-          this.setState({
-            loading: false,
-            onSonarCloud: Boolean(
-              appState.settings && appState.settings['sonar.sonarcloud.enabled'] === 'true'
-            )
-          });
-        }
-      },
-      () => {}
-    );
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  render() {
-    if (this.state.loading) {
-      return <GlobalLoading />;
-    }
-    return (
-      <>
-        <Helmet defaultTitle={this.state.onSonarCloud ? 'SonarCloud' : 'SonarQube'} />
-        {this.props.children}
-      </>
-    );
-  }
-}
index 5baa8f12be9e80616ae50527fb26ef57d104c995..63e5ee4eab523d45457eed628bde346bfab4a3e9 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import * as PropTypes from 'prop-types';
 import { Link } from 'react-router';
 import GlobalFooterSonarCloud from './GlobalFooterSonarCloud';
 import GlobalFooterBranding from './GlobalFooterBranding';
 import InstanceMessage from '../../components/common/InstanceMessage';
 import { translate, translateWithParameters } from '../../helpers/l10n';
+import { isSonarCloud } from '../../helpers/system';
 
 interface Props {
   hideLoggedInInfo?: boolean;
@@ -31,72 +31,69 @@ interface Props {
   sonarqubeVersion?: string;
 }
 
-export default class GlobalFooter extends React.PureComponent<Props> {
-  static contextTypes = {
-    onSonarCloud: PropTypes.bool
-  };
-
-  render() {
-    const { hideLoggedInInfo, productionDatabase, sonarqubeVersion } = this.props;
-    if (this.context.onSonarCloud) {
-      return <GlobalFooterSonarCloud />;
-    }
+export default function GlobalFooter({
+  hideLoggedInInfo,
+  productionDatabase,
+  sonarqubeVersion
+}: Props) {
+  if (isSonarCloud()) {
+    return <GlobalFooterSonarCloud />;
+  }
 
-    return (
-      <div id="footer" className="page-footer page-container">
-        {productionDatabase === false && (
-          <div className="alert alert-danger">
-            <p className="big" id="evaluation_warning">
-              {translate('footer.production_database_warning')}
-            </p>
-            <p>
-              <InstanceMessage message={translate('footer.production_database_explanation')} />
-            </p>
-          </div>
-        )}
+  return (
+    <div className="page-footer page-container" id="footer">
+      {productionDatabase === false && (
+        <div className="alert alert-danger">
+          <p className="big" id="evaluation_warning">
+            {translate('footer.production_database_warning')}
+          </p>
+          <p>
+            <InstanceMessage message={translate('footer.production_database_explanation')} />
+          </p>
+        </div>
+      )}
 
-        <GlobalFooterBranding />
+      <GlobalFooterBranding />
 
-        <ul className="page-footer-menu">
-          {!hideLoggedInInfo &&
-            sonarqubeVersion && (
-              <li className="page-footer-menu-item">
-                {translateWithParameters('footer.version_x', sonarqubeVersion)}
-              </li>
-            )}
-          <li className="page-footer-menu-item">
-            <a href="http://www.gnu.org/licenses/lgpl-3.0.txt">{translate('footer.license')}</a>
-          </li>
-          <li className="page-footer-menu-item">
-            <a href="http://www.sonarqube.org">{translate('footer.community')}</a>
-          </li>
-          <li className="page-footer-menu-item">
-            <a href="https://redirect.sonarsource.com/doc/home.html">
-              {translate('footer.documentation')}
-            </a>
-          </li>
+      <ul className="page-footer-menu">
+        {!hideLoggedInInfo &&
+          sonarqubeVersion && (
+            <li className="page-footer-menu-item">
+              {translateWithParameters('footer.version_x', sonarqubeVersion)}
+            </li>
+          )}
+        <li className="page-footer-menu-item">
+          <a href="http://www.gnu.org/licenses/lgpl-3.0.txt">{translate('footer.license')}</a>
+        </li>
+        <li className="page-footer-menu-item">
+          <a href="http://www.sonarqube.org">{translate('footer.community')}</a>
+        </li>
+        <li className="page-footer-menu-item">
+          <a href="https://redirect.sonarsource.com/doc/home.html">
+            {translate('footer.documentation')}
+          </a>
+        </li>
+        <li className="page-footer-menu-item">
+          <a href="https://redirect.sonarsource.com/doc/community.html">
+            {translate('footer.support')}
+          </a>
+        </li>
+        <li className="page-footer-menu-item">
+          <a href="https://redirect.sonarsource.com/doc/plugin-library.html">
+            {translate('footer.plugins')}
+          </a>
+        </li>
+        {!hideLoggedInInfo && (
           <li className="page-footer-menu-item">
-            <a href="https://redirect.sonarsource.com/doc/community.html">
-              {translate('footer.support')}
-            </a>
+            <Link to="/web_api">{translate('footer.web_api')}</Link>
           </li>
+        )}
+        {!hideLoggedInInfo && (
           <li className="page-footer-menu-item">
-            <a href="https://redirect.sonarsource.com/doc/plugin-library.html">
-              {translate('footer.plugins')}
-            </a>
+            <Link to="/about">{translate('footer.about')}</Link>
           </li>
-          {!hideLoggedInInfo && (
-            <li className="page-footer-menu-item">
-              <Link to="/web_api">{translate('footer.web_api')}</Link>
-            </li>
-          )}
-          {!hideLoggedInInfo && (
-            <li className="page-footer-menu-item">
-              <Link to="/about">{translate('footer.about')}</Link>
-            </li>
-          )}
-        </ul>
-      </div>
-    );
-  }
+        )}
+      </ul>
+    </div>
+  );
 }
index ec873fb3a2e45afd643089fca093e40da223d0bc..8ae812825260e6ab434f20c0bfc98300481a7bef 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import Helmet from 'react-helmet';
 
 export default function GlobalLoading() {
   return (
     <>
-      <Helmet defaultTitle={'Loading...'} />
       <div className="global-loading">
         <i className="spinner global-loading-spinner" />
         <span className="global-loading-text">Loading...</span>
index d9f522035faec48d52134b19f1f8f8e329de2b57..55f14b6056d6d4611d33843cdabf038e96fbff7e 100644 (file)
@@ -21,12 +21,12 @@ import * as React from 'react';
 import * as PropTypes from 'prop-types';
 import { connect } from 'react-redux';
 import { CurrentUser, isLoggedIn } from '../types';
-import { getCurrentUser, getGlobalSettingValue } from '../../store/rootReducer';
+import { getCurrentUser } from '../../store/rootReducer';
 import { getHomePageUrl } from '../../helpers/urls';
+import { isSonarCloud } from '../../helpers/system';
 
 interface Props {
   currentUser: CurrentUser | undefined;
-  onSonarCloud: boolean;
 }
 
 class Landing extends React.PureComponent<Props> {
@@ -35,7 +35,7 @@ class Landing extends React.PureComponent<Props> {
   };
 
   componentDidMount() {
-    const { currentUser, onSonarCloud } = this.props;
+    const { currentUser } = this.props;
     if (currentUser && isLoggedIn(currentUser)) {
       if (currentUser.homepage) {
         const homepage = getHomePageUrl(currentUser.homepage);
@@ -43,7 +43,7 @@ class Landing extends React.PureComponent<Props> {
       } else {
         this.context.router.replace('/projects');
       }
-    } else if (onSonarCloud) {
+    } else if (isSonarCloud()) {
       window.location.href = 'https://about.sonarcloud.io';
     } else {
       this.context.router.replace('/about');
@@ -55,12 +55,8 @@ class Landing extends React.PureComponent<Props> {
   }
 }
 
-const mapStateToProps = (state: any) => {
-  const onSonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled');
-  return {
-    currentUser: getCurrentUser(state),
-    onSonarCloud: Boolean(onSonarCloudSetting && onSonarCloudSetting.value === 'true')
-  };
-};
+const mapStateToProps = (state: any) => ({
+  currentUser: getCurrentUser(state)
+});
 
 export default connect<Props>(mapStateToProps)(Landing);
diff --git a/server/sonar-web/src/main/js/app/components/MigrationContainer.js b/server/sonar-web/src/main/js/app/components/MigrationContainer.js
deleted file mode 100644 (file)
index 72a4a20..0000000
+++ /dev/null
@@ -1,60 +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 { withRouter } from 'react-router';
-import GlobalLoading from './GlobalLoading';
-import { getSystemStatus } from '../../api/system';
-
-class MigrationContainer extends React.PureComponent {
-  /*::
-  props: {
-    children?: React.Element<*>,
-    router: { push: ({ pathname: string, query?: { return_to: string } }) => void }
-  };
-  */
-
-  state = { loading: true };
-
-  componentDidMount() {
-    getSystemStatus().then(r => {
-      if (r.status === 'UP') {
-        this.setState({ loading: false });
-      } else {
-        this.props.router.push({
-          pathname: '/maintenance',
-          query: {
-            return_to: window.location.pathname + window.location.search + window.location.hash
-          }
-        });
-      }
-    });
-  }
-
-  render() {
-    if (this.state.loading) {
-      return <GlobalLoading />;
-    }
-
-    return this.props.children;
-  }
-}
-
-export default withRouter(MigrationContainer);
diff --git a/server/sonar-web/src/main/js/app/components/MigrationContainer.tsx b/server/sonar-web/src/main/js/app/components/MigrationContainer.tsx
new file mode 100644 (file)
index 0000000..ab47d5f
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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 { WithRouterProps } from 'react-router';
+import { getSystemStatus } from '../../helpers/system';
+
+export default class MigrationContainer extends React.PureComponent<WithRouterProps> {
+  componentDidMount() {
+    if (getSystemStatus() !== 'UP') {
+      this.props.router.push({
+        pathname: '/maintenance',
+        query: {
+          // eslint-disable-next-line camelcase
+          return_to: window.location.pathname + window.location.search + window.location.hash
+        }
+      });
+    }
+  }
+
+  render() {
+    if (getSystemStatus() !== 'UP') {
+      return null;
+    }
+    return this.props.children;
+  }
+}
index 81bc6f95a7f86202914648f88638cd49af8ccf17..6eb9e440d3a7a8d70e943a5f16a1a259792f5c2a 100644 (file)
@@ -20,6 +20,9 @@
 import * as React from 'react';
 import { shallow } from 'enzyme';
 import GlobalFooter from '../GlobalFooter';
+import { isSonarCloud } from '../../../helpers/system';
+
+jest.mock('../../../helpers/system', () => ({ isSonarCloud: jest.fn() }));
 
 it('should render the only logged in information', () => {
   expect(getWrapper()).toMatchSnapshot();
@@ -44,7 +47,6 @@ it('should render SonarCloud footer', () => {
 });
 
 function getWrapper(props = {}, onSonarCloud = false) {
-  return shallow(<GlobalFooter productionDatabase={true} {...props} />, {
-    context: { onSonarCloud }
-  });
+  (isSonarCloud as jest.Mock).mockImplementation(() => onSonarCloud);
+  return shallow(<GlobalFooter productionDatabase={true} {...props} />);
 }
index 5280e553a75a43beb4edd556788696705e7f3077..9beafb064fb85d0c0e084a6b78dfea8a14e08222 100644 (file)
@@ -25,6 +25,7 @@ import { CurrentUser, isLoggedIn } from '../../types';
 import { translate } from '../../../helpers/l10n';
 import { getBaseUrl } from '../../../helpers/urls';
 import { DropdownOverlay } from '../../../components/controls/Dropdown';
+import { isSonarCloud } from '../../../helpers/system';
 
 interface Props {
   currentUser: CurrentUser;
@@ -34,7 +35,6 @@ interface Props {
 
 export default class EmbedDocsPopup extends React.PureComponent<Props> {
   static contextTypes = {
-    onSonarCloud: PropTypes.bool,
     openOnboardingTutorial: PropTypes.func
   };
 
@@ -159,8 +159,7 @@ export default class EmbedDocsPopup extends React.PureComponent<Props> {
               {translate('api_documentation.page')}
             </Link>
           </li>
-          {this.context.onSonarCloud && this.renderSonarCloudLinks()}
-          {!this.context.onSonarCloud && this.renderSonarQubeLinks()}
+          {isSonarCloud() ? this.renderSonarCloudLinks() : this.renderSonarQubeLinks()}
         </ul>
       </DropdownOverlay>
     );
index bf1efda4cbbf917ccc4e7d63f66f492961c6d933..909c6a5d1d43c20df77c082545c8050d0852e6d2 100644 (file)
@@ -22,6 +22,7 @@ import * as PropTypes from 'prop-types';
 // eslint-disable-next-line import/no-extraneous-dependencies
 import * as suggestionsJson from 'Docs/EmbedDocsSuggestions.json';
 import { SuggestionsContext } from './SuggestionsContext';
+import { isSonarCloud } from '../../../helpers/system';
 
 export interface SuggestionLink {
   link: string;
@@ -48,10 +49,6 @@ export default class SuggestionsProvider extends React.Component<Props, State> {
     suggestions: PropTypes.object
   };
 
-  static contextTypes = {
-    onSonarCloud: PropTypes.bool
-  };
-
   state: State = { suggestions: [] };
 
   getChildContext = (): { suggestions: SuggestionsContext } => {
@@ -85,7 +82,7 @@ export default class SuggestionsProvider extends React.Component<Props, State> {
   };
 
   render() {
-    const suggestions = this.context.onSonarCloud
+    const suggestions = isSonarCloud()
       ? this.state.suggestions
       : this.state.suggestions.filter(suggestion => suggestion.scope !== 'sonarcloud');
 
index 32a957e9117353f7d786134d02ff287218faeb39..2092c80a14db426a2b529f63bddfd25a23c752c8 100644 (file)
@@ -20,6 +20,7 @@
 import * as React from 'react';
 import { shallow } from 'enzyme';
 import SuggestionsProvider from '../SuggestionsProvider';
+import { isSonarCloud } from '../../../../helpers/system';
 
 jest.mock(
   'Docs/EmbedDocsSuggestions.json',
@@ -30,7 +31,10 @@ jest.mock(
   { virtual: true }
 );
 
+jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() }));
+
 it('should add & remove suggestions', () => {
+  (isSonarCloud as jest.Mock).mockImplementation(() => false);
   const children = jest.fn();
   const wrapper = shallow(<SuggestionsProvider>{children}</SuggestionsProvider>);
   const instance = wrapper.instance() as SuggestionsProvider;
@@ -49,10 +53,9 @@ it('should add & remove suggestions', () => {
 });
 
 it('should show sonarcloud pages', () => {
+  (isSonarCloud as jest.Mock).mockImplementation(() => true);
   const children = jest.fn();
-  const wrapper = shallow(<SuggestionsProvider>{children}</SuggestionsProvider>, {
-    context: { onSonarCloud: true }
-  });
+  const wrapper = shallow(<SuggestionsProvider>{children}</SuggestionsProvider>);
   const instance = wrapper.instance() as SuggestionsProvider;
   expect(children).lastCalledWith({ suggestions: [] });
 
index fa0fd612b5bfc1229a99308b83043758eb6ddf3b..6a390a20932de1432335e8aef7e743b2833c0b01 100644 (file)
@@ -37,6 +37,7 @@ import HelpTooltip from '../../../../components/controls/HelpTooltip';
 import Toggler from '../../../../components/controls/Toggler';
 import Tooltip from '../../../../components/controls/Tooltip';
 import DropdownIcon from '../../../../components/icons-components/DropdownIcon';
+import { isSonarCloud } from '../../../../helpers/system';
 
 interface Props {
   branchLikes: BranchLike[];
@@ -53,8 +54,7 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State
   mounted = false;
 
   static contextTypes = {
-    branchesEnabled: PropTypes.bool.isRequired,
-    onSonarCloud: PropTypes.bool
+    branchesEnabled: PropTypes.bool.isRequired
   };
 
   state: State = {
@@ -130,7 +130,7 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State
     const { branchLikes, currentBranchLike } = this.props;
     const { configuration } = this.props.component;
 
-    if (this.context.onSonarCloud && !this.context.branchesEnabled) {
+    if (isSonarCloud() && !this.context.branchesEnabled) {
       return null;
     }
 
index 83a894a3b38dd22dd3b5784d430d40fcfc6ff996..9a805b74c8212e94e094cdc5cfaa02282fdba6ed 100644 (file)
@@ -29,10 +29,17 @@ import {
   PullRequest
 } from '../../../../types';
 import { click } from '../../../../../helpers/testUtils';
+import { isSonarCloud } from '../../../../../helpers/system';
+
+jest.mock('../../../../../helpers/system', () => ({ isSonarCloud: jest.fn() }));
 
 const mainBranch: MainBranch = { isMain: true, name: 'master' };
 const fooBranch: LongLivingBranch = { isMain: false, name: 'foo', type: BranchType.LONG };
 
+beforeEach(() => {
+  (isSonarCloud as jest.Mock).mockImplementation(() => false);
+});
+
 it('renders main branch', () => {
   const component = {} as Component;
   expect(
@@ -131,6 +138,7 @@ it('renders no branch support popup', () => {
 });
 
 it('renders nothing on SonarCloud without branch support', () => {
+  (isSonarCloud as jest.Mock).mockImplementation(() => true);
   const component = {} as Component;
   const wrapper = shallow(
     <ComponentNavBranch
index d7525d24054392e9f132f5a9badf34c0929b40d4..dc3d5127587b1383e8119ee9d69a7f53bcc85c34 100644 (file)
@@ -32,9 +32,10 @@ import NavBar from '../../../../components/nav/NavBar';
 import Tooltip from '../../../../components/controls/Tooltip';
 import { lazyLoad } from '../../../../components/lazyLoad';
 import { translate } from '../../../../helpers/l10n';
-import { getCurrentUser, getAppState, getGlobalSettingValue } from '../../../../store/rootReducer';
+import { getCurrentUser, getAppState } from '../../../../store/rootReducer';
 import { skipOnboarding } from '../../../../store/users/actions';
 import { SuggestionLink } from '../../embed-docs-modal/SuggestionsProvider';
+import { isSonarCloud } from '../../../../helpers/system';
 import './GlobalNav.css';
 
 const GlobalNavPlus = lazyLoad(() => import('./GlobalNavPlus'));
@@ -42,7 +43,6 @@ const GlobalNavPlus = lazyLoad(() => import('./GlobalNavPlus'));
 interface StateProps {
   appState: AppState;
   currentUser: CurrentUser;
-  onSonarCloud: boolean;
 }
 
 interface DispatchProps {
@@ -100,16 +100,16 @@ class GlobalNav extends React.PureComponent<Props, State> {
         <GlobalNavMenu {...this.props} />
 
         <ul className="global-navbar-menu pull-right">
-          <GlobalNavExplore location={this.props.location} onSonarCloud={this.props.onSonarCloud} />
+          {isSonarCloud() && <GlobalNavExplore location={this.props.location} />}
           <EmbedDocsPopupHelper
             currentUser={this.props.currentUser}
             showTooltip={this.state.onboardingTutorialTooltip}
             suggestions={this.props.suggestions}
-            tooltip={!this.props.onSonarCloud}
+            tooltip={!isSonarCloud()}
           />
           <Search appState={this.props.appState} currentUser={this.props.currentUser} />
           {isLoggedIn(this.props.currentUser) &&
-            this.props.onSonarCloud && (
+            isSonarCloud() && (
               <Tooltip
                 overlay={translate('tutorials.follow_later')}
                 visible={this.state.onboardingTutorialTooltip}>
@@ -127,15 +127,10 @@ class GlobalNav extends React.PureComponent<Props, State> {
   }
 }
 
-const mapStateToProps = (state: any): StateProps => {
-  const sonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled');
-
-  return {
-    currentUser: getCurrentUser(state),
-    appState: getAppState(state),
-    onSonarCloud: Boolean(sonarCloudSetting && sonarCloudSetting.value === 'true')
-  };
-};
+const mapStateToProps = (state: any): StateProps => ({
+  currentUser: getCurrentUser(state),
+  appState: getAppState(state)
+});
 
 const mapDispatchToProps: DispatchProps = { skipOnboarding };
 
index c5028920cd6a8b57390f54d33708f3e5f42a6c38..aaf35d724e20e84738d703f8fbef4140f5621f5f 100644 (file)
@@ -23,19 +23,14 @@ import { translate } from '../../../../helpers/l10n';
 
 interface Props {
   location: { pathname: string };
-  onSonarCloud: boolean;
 }
 
-export default function GlobalNavExplore({ location, onSonarCloud }: Props) {
-  if (!onSonarCloud) {
-    return null;
-  }
-
+export default function GlobalNavExplore({ location }: Props) {
   const active = location.pathname.startsWith('explore');
 
   return (
     <li>
-      <Link to="/explore/projects" className={active ? 'active' : undefined}>
+      <Link className={active ? 'active' : undefined} to="/explore/projects">
         {translate('explore')}
       </Link>
     </li>
index 99e20aa58c566e6ab7344f6cd18d832e398c74b1..25c7daaa489ea80a45839cc5fba1933c883ca119 100644 (file)
@@ -26,12 +26,12 @@ import { getQualityGatesUrl, getBaseUrl } from '../../../../helpers/urls';
 import { isMySet } from '../../../../apps/issues/utils';
 import Dropdown from '../../../../components/controls/Dropdown';
 import DropdownIcon from '../../../../components/icons-components/DropdownIcon';
+import { isSonarCloud } from '../../../../helpers/system';
 
 interface Props {
   appState: AppState;
   currentUser: CurrentUser;
   location: { pathname: string };
-  onSonarCloud?: boolean;
 }
 
 export default class GlobalNavMenu extends React.PureComponent<Props> {
@@ -40,14 +40,14 @@ export default class GlobalNavMenu extends React.PureComponent<Props> {
   }
 
   renderProjects() {
-    if (this.props.onSonarCloud && !isLoggedIn(this.props.currentUser)) {
+    if (isSonarCloud() && !isLoggedIn(this.props.currentUser)) {
       return null;
     }
 
     return (
       <li>
-        <Link to="/projects" activeClassName="active">
-          {this.props.onSonarCloud ? translate('my_projects') : translate('projects.page')}
+        <Link activeClassName="active" to="/projects">
+          {isSonarCloud() ? translate('my_projects') : translate('projects.page')}
         </Link>
       </li>
     );
@@ -56,7 +56,7 @@ export default class GlobalNavMenu extends React.PureComponent<Props> {
   renderPortfolios() {
     return (
       <li>
-        <Link to="/portfolios" activeClassName="active">
+        <Link activeClassName="active" to="/portfolios">
           {translate('portfolios.page')}
         </Link>
       </li>
@@ -64,18 +64,18 @@ export default class GlobalNavMenu extends React.PureComponent<Props> {
   }
 
   renderIssuesLink() {
-    if (this.props.onSonarCloud && !isLoggedIn(this.props.currentUser)) {
+    if (isSonarCloud() && !isLoggedIn(this.props.currentUser)) {
       return null;
     }
 
     const active = this.props.location.pathname === 'issues';
 
-    if (this.props.onSonarCloud) {
+    if (isSonarCloud()) {
       return (
         <li>
           <Link
-            to={{ pathname: '/issues', query: { resolved: 'false' } }}
-            className={active ? 'active' : undefined}>
+            className={active ? 'active' : undefined}
+            to={{ pathname: '/issues', query: { resolved: 'false' } }}>
             {translate('my_issues')}
           </Link>
         </li>
@@ -88,7 +88,7 @@ export default class GlobalNavMenu extends React.PureComponent<Props> {
         : { resolved: 'false' };
     return (
       <li>
-        <Link to={{ pathname: '/issues', query }} className={active ? 'active' : undefined}>
+        <Link className={active ? 'active' : undefined} to={{ pathname: '/issues', query }}>
           {translate('issues.page')}
         </Link>
       </li>
@@ -98,7 +98,7 @@ export default class GlobalNavMenu extends React.PureComponent<Props> {
   renderRulesLink() {
     return (
       <li>
-        <Link to="/coding_rules" className={this.activeLink('/coding_rules')}>
+        <Link className={this.activeLink('/coding_rules')} to="/coding_rules">
           {translate('coding_rules.page')}
         </Link>
       </li>
@@ -108,7 +108,7 @@ export default class GlobalNavMenu extends React.PureComponent<Props> {
   renderProfilesLink() {
     return (
       <li>
-        <Link to="/profiles" activeClassName="active">
+        <Link activeClassName="active" to="/profiles">
           {translate('quality_profiles.page')}
         </Link>
       </li>
@@ -118,7 +118,7 @@ export default class GlobalNavMenu extends React.PureComponent<Props> {
   renderQualityGatesLink() {
     return (
       <li>
-        <Link to={getQualityGatesUrl()} activeClassName="active">
+        <Link activeClassName="active" to={getQualityGatesUrl()}>
           {translate('quality_gates.page')}
         </Link>
       </li>
@@ -132,7 +132,7 @@ export default class GlobalNavMenu extends React.PureComponent<Props> {
 
     return (
       <li>
-        <Link to="/admin" activeClassName="active">
+        <Link activeClassName="active" to="/admin">
           {translate('layout.settings')}
         </Link>
       </li>
index 6d143225d4a24c3de7f45e0c31b2c5615cc81d9a..611b8a238d1411309f1a5b8c81139f4b0f8b292c 100644 (file)
@@ -147,10 +147,8 @@ const startReactApp = () => {
           </Route>
 
           <Route component={MigrationContainer}>
-            <Route component={lazyLoad(() => import('../components/AppContextContainer'))}>
-              <Route component={lazyLoad(() => import('../components/SimpleSessionsContainer'))}>
-                <Route path="/sessions" childRoutes={sessionsRoutes} />
-              </Route>
+            <Route component={lazyLoad(() => import('../components/SimpleSessionsContainer'))}>
+              <Route path="/sessions" childRoutes={sessionsRoutes} />
             </Route>
 
             <Route path="/" component={App}>
index 3a82128c095c49241e60ed09ab35e078292beac2..9074488497c8cc9b26148715fedfc52239493758 100644 (file)
@@ -36,6 +36,7 @@ import { getFacet } from '../../../api/issues';
 import { getAppState, getCurrentUser, getGlobalSettingValue } from '../../../store/rootReducer';
 import { translate } from '../../../helpers/l10n';
 import { fetchAboutPageSettings } from '../actions';
+import { isSonarCloud } from '../../../helpers/system';
 import '../styles.css';
 
 /*::
@@ -60,8 +61,7 @@ class AboutApp extends React.PureComponent {
     },
     currentUser: { isLoggedIn: boolean },
     customText?: string,
-    fetchAboutPageSettings: () => Promise<*>,
-    onSonarCloud?: { value: string }
+    fetchAboutPageSettings: () => Promise<*>
   };
 */
 
@@ -72,7 +72,7 @@ class AboutApp extends React.PureComponent {
 
   componentDidMount() {
     this.mounted = true;
-    if (this.props.onSonarCloud && this.props.onSonarCloud.value === 'true') {
+    if (isSonarCloud()) {
       window.location = 'https://about.sonarcloud.io';
     } else {
       this.loadData();
@@ -104,24 +104,31 @@ class AboutApp extends React.PureComponent {
   }
 
   loadData() {
-    Promise.all([this.loadProjects(), this.loadIssues(), this.loadCustomText()]).then(responses => {
-      if (this.mounted) {
-        const [projectsCount, issues] = responses;
-        const issueTypes = keyBy(issues.facet, 'val');
-        this.setState({
-          projectsCount,
-          issueTypes,
-          loading: false
-        });
+    Promise.all([this.loadProjects(), this.loadIssues(), this.loadCustomText()]).then(
+      responses => {
+        if (this.mounted) {
+          const [projectsCount, issues] = responses;
+          const issueTypes = keyBy(issues.facet, 'val');
+          this.setState({
+            projectsCount,
+            issueTypes,
+            loading: false
+          });
+        }
+      },
+      () => {
+        if (this.mounted) {
+          this.setState({ loading: false });
+        }
       }
-    });
+    );
   }
 
   render() {
-    const { customText, onSonarCloud } = this.props;
+    const { customText } = this.props;
     const { loading, issueTypes, projectsCount } = this.state;
 
-    if (onSonarCloud && onSonarCloud.value === 'true') {
+    if (isSonarCloud()) {
       return null;
     }
 
@@ -135,18 +142,19 @@ class AboutApp extends React.PureComponent {
     }
 
     return (
-      <div id="about-page" className="page page-limited about-page">
+      <div className="page page-limited about-page" id="about-page">
         <div className="about-page-entry">
           <div className="about-page-intro">
             <h1 className="big-spacer-bottom">{translate('layout.sonar.slogan')}</h1>
             {!this.props.currentUser.isLoggedIn && (
-              <Link to="/sessions/new" className="button button-active big-spacer-right">
+              <Link className="button button-active big-spacer-right" to="/sessions/new">
                 {translate('layout.login')}
               </Link>
             )}
             <a
               className="button"
               href="https://redirect.sonarsource.com/doc/home.html"
+              rel="noopener noreferrer"
               target="_blank">
               {translate('about_page.read_documentation')}
             </a>
@@ -202,8 +210,7 @@ class AboutApp extends React.PureComponent {
 const mapStateToProps = state => ({
   appState: getAppState(state),
   currentUser: getCurrentUser(state),
-  customText: getGlobalSettingValue(state, 'sonar.lf.aboutText'),
-  onSonarCloud: getGlobalSettingValue(state, 'sonar.sonarcloud.enabled')
+  customText: getGlobalSettingValue(state, 'sonar.lf.aboutText')
 });
 
 const mapDispatchToProps = { fetchAboutPageSettings };
index cec2a0e8251c2985e6363436a8cad5dffa681597..84ed6c38d7c2d66f3c035fecd1a4a83c946f1852 100644 (file)
@@ -20,7 +20,6 @@
 import * as React from 'react';
 import Helmet from 'react-helmet';
 import { Link } from 'react-router';
-import * as PropTypes from 'prop-types';
 import Menu from './Menu';
 import NotFound from '../../../app/components/NotFound';
 import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
@@ -28,6 +27,7 @@ import DocMarkdownBlock from '../../../components/docs/DocMarkdownBlock';
 import DeferredSpinner from '../../../components/common/DeferredSpinner';
 import { translate } from '../../../helpers/l10n';
 import { getFrontMatter } from '../../../helpers/markdown';
+import { isSonarCloud } from '../../../helpers/system';
 import '../styles.css';
 
 interface Props {
@@ -43,10 +43,6 @@ interface State {
 export default class App extends React.PureComponent<Props, State> {
   mounted = false;
 
-  static contextTypes = {
-    onSonarCloud: PropTypes.bool
-  };
-
   state: State = { loading: false, notFound: false };
 
   componentDidMount() {
@@ -72,7 +68,7 @@ export default class App extends React.PureComponent<Props, State> {
       ({ default: content }) => {
         if (this.mounted) {
           const { scope } = getFrontMatter(content || '');
-          if (scope === 'sonarcloud' && !this.context.onSonarCloud) {
+          if (scope === 'sonarcloud' && !isSonarCloud()) {
             this.setState({ loading: false, notFound: true });
           } else {
             this.setState({ content, loading: false, notFound: false });
index dffe69579ba0d80c2e5da3337e69c04a2c7d73db..fbd4ee1a753c93961fd700b8b9180c75f8a839bf 100644 (file)
@@ -20,7 +20,6 @@
 import * as React from 'react';
 import { Link } from 'react-router';
 import * as classNames from 'classnames';
-import * as PropTypes from 'prop-types';
 import OpenCloseIcon from '../../../components/icons-components/OpenCloseIcon';
 import {
   activeOrChildrenActive,
@@ -29,6 +28,7 @@ import {
   getEntryRoot
 } from '../utils';
 import * as Docs from '../documentation.directory-loader';
+import { isSonarCloud } from '../../../helpers/system';
 
 const pages = (Docs as any) as DocumentationEntry[];
 
@@ -37,12 +37,8 @@ interface Props {
 }
 
 export default class Menu extends React.PureComponent<Props> {
-  static contextTypes = {
-    onSonarCloud: PropTypes.bool
-  };
-
   getMenuEntriesHierarchy = (root?: string): Array<DocumentationEntry> => {
-    const instancePages = this.context.onSonarCloud
+    const instancePages = isSonarCloud()
       ? pages
       : pages.filter(page => page.scope !== 'sonarcloud');
     const toplevelEntries = getEntryChildren(instancePages, root);
index 70ba38db7a4146b4f63a2c7849366ad963c8f64d..b5c65fd6ed5051883a89e2a9987f778021b31a9b 100644 (file)
@@ -22,28 +22,24 @@ import { connect } from 'react-redux';
 import AppContainer from './components/AppContainer';
 import { CurrentUser, isLoggedIn } from '../../app/types';
 import { RawQuery } from '../../helpers/query';
-import { getCurrentUser, getGlobalSettingValue } from '../../store/rootReducer';
+import { getCurrentUser } from '../../store/rootReducer';
+import { isSonarCloud } from '../../helpers/system';
 
 interface StateProps {
   currentUser: CurrentUser;
-  onSonarCloud: boolean;
 }
 
 interface Props extends StateProps {
   location: { pathname: string; query: RawQuery };
 }
 
-function IssuesPage({ currentUser, location, onSonarCloud }: Props) {
-  const myIssues = (isLoggedIn(currentUser) && onSonarCloud) || undefined;
+function IssuesPage({ currentUser, location }: Props) {
+  const myIssues = (isLoggedIn(currentUser) && isSonarCloud()) || undefined;
   return <AppContainer location={location} myIssues={myIssues} />;
 }
 
-const stateToProps = (state: any) => {
-  const onSonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled');
-  return {
-    currentUser: getCurrentUser(state),
-    onSonarCloud: Boolean(onSonarCloudSetting && onSonarCloudSetting.value === 'true')
-  };
-};
+const stateToProps = (state: any) => ({
+  currentUser: getCurrentUser(state)
+});
 
 export default connect<StateProps>(stateToProps)(IssuesPage);
index 708e786183edba26d9e611fcd6a8a93865d164a5..2ae38d862de5bd4c2edfa4fb1b5d5681b4b8d795 100644 (file)
@@ -70,6 +70,7 @@ import { scrollToElement } from '../../../helpers/scrolling';
 import EmptySearch from '../../../components/common/EmptySearch';
 import Checkbox from '../../../components/controls/Checkbox';
 import DropdownIcon from '../../../components/icons-components/DropdownIcon';
+import { isSonarCloud } from '../../../helpers/system';
 import '../../../components/search-navigator.css';
 import '../styles.css';
 
@@ -91,7 +92,6 @@ interface Props {
   location: { pathname: string; query: RawQuery };
   myIssues?: boolean;
   onBranchesChange: () => void;
-  onSonarCloud: boolean;
   organization?: { key: string };
 }
 
@@ -845,13 +845,13 @@ export default class App extends React.PureComponent<Props, State> {
   }
 
   renderFacets() {
-    const { component, currentUser, onSonarCloud } = this.props;
+    const { component, currentUser } = this.props;
     const { query } = this.state;
 
     return (
       <div className="layout-page-filters">
         {currentUser.isLoggedIn &&
-          !onSonarCloud && (
+          !isSonarCloud() && (
             <MyIssuesFilter
               myIssues={this.state.myIssues}
               onMyIssuesChange={this.handleMyIssuesChange}
@@ -1029,11 +1029,10 @@ export default class App extends React.PureComponent<Props, State> {
                     canSetHome={Boolean(
                       !this.props.organization &&
                         !this.props.component &&
-                        (!this.props.onSonarCloud || this.props.myIssues)
+                        (!isSonarCloud() || this.props.myIssues)
                     )}
                     loading={this.state.loading}
                     onReload={this.handleReload}
-                    onSonarCloud={this.props.onSonarCloud}
                     paging={paging}
                     selectedIndex={selectedIndex}
                   />
index 8ef800732185d045f47a668527856fe2999c7190..7bc1a48e240a3d9183e00c346d6cb0e35d18005d 100644 (file)
@@ -24,11 +24,7 @@ import { searchIssues } from '../../../api/issues';
 import { getOrganizations } from '../../../api/organizations';
 import { CurrentUser } from '../../../app/types';
 import throwGlobalError from '../../../app/utils/throwGlobalError';
-import {
-  getCurrentUser,
-  areThereCustomOrganizations,
-  getGlobalSettingValue
-} from '../../../store/rootReducer';
+import { getCurrentUser, areThereCustomOrganizations } from '../../../store/rootReducer';
 import { lazyLoad } from '../../../components/lazyLoad';
 import { parseIssueFromResponse } from '../../../helpers/issues';
 import { RawQuery } from '../../../helpers/query';
@@ -36,16 +32,11 @@ import { receiveOrganizations } from '../../../store/organizations/duck';
 
 interface StateProps {
   currentUser: CurrentUser;
-  onSonarCloud: boolean;
 }
 
-const mapStateToProps = (state: any): StateProps => {
-  const onSonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled');
-  return {
-    currentUser: getCurrentUser(state),
-    onSonarCloud: Boolean(onSonarCloudSetting && onSonarCloudSetting.value === 'true')
-  };
-};
+const mapStateToProps = (state: any): StateProps => ({
+  currentUser: getCurrentUser(state)
+});
 
 const fetchIssueOrganizations = (organizationKeys: string[]) => (dispatch: Dispatch<any>) => {
   if (!organizationKeys.length) {
index 30d4d3191aeb5616ef9cdcea0ad564b21f0fb446..38e0821b0e99a7a7717b02bd07280daab000f7d8 100644 (file)
@@ -24,12 +24,12 @@ import { HomePageType, Paging } from '../../../app/types';
 import DeferredSpinner from '../../../components/common/DeferredSpinner';
 import HomePageSelect from '../../../components/controls/HomePageSelect';
 import { translate } from '../../../helpers/l10n';
+import { isSonarCloud } from '../../../helpers/system';
 
 interface Props {
   canSetHome: boolean;
   loading: boolean;
   onReload: () => void;
-  onSonarCloud: boolean;
   paging: Paging | undefined;
   selectedIndex: number | undefined;
 }
@@ -73,9 +73,7 @@ export default class PageActions extends React.PureComponent<Props> {
           <HomePageSelect
             className="huge-spacer-left"
             currentPage={
-              this.props.onSonarCloud
-                ? { type: HomePageType.MyIssues }
-                : { type: HomePageType.Issues }
+              isSonarCloud() ? { type: HomePageType.MyIssues } : { type: HomePageType.Issues }
             }
           />
         )}
index bdc610e13d3b144add1e93827990ed0c12a52ce9..ce7ebfdaad9c3ab2e1b2be47399d4d20436271b3 100644 (file)
@@ -28,6 +28,7 @@ import { deleteOrganization } from '../actions';
 import { Organization } from '../../../app/types';
 import { Button } from '../../../components/ui/buttons';
 import { getOrganizationBilling } from '../../../api/organizations';
+import { isSonarCloud } from '../../../helpers/system';
 
 interface DispatchToProps {
   deleteOrganization: (key: string) => Promise<void>;
@@ -46,8 +47,7 @@ interface State {
 export class OrganizationDelete extends React.PureComponent<Props, State> {
   mounted = false;
   static contextTypes = {
-    router: PropTypes.object,
-    onSonarCloud: PropTypes.bool
+    router: PropTypes.object
   };
 
   state: State = {};
@@ -62,7 +62,7 @@ export class OrganizationDelete extends React.PureComponent<Props, State> {
   }
 
   fetchOrganizationPlanInfo = () => {
-    if (this.context.onSonarCloud) {
+    if (isSonarCloud()) {
       getOrganizationBilling(this.props.organization.key).then(
         billingInfo => {
           if (this.mounted) {
index fff071d494abb93fab44ef7cc068fba938ed7103..04cc36701683bd25e38c7d3f6ae59697fdfb1599 100644 (file)
@@ -22,6 +22,7 @@ import { shallow } from 'enzyme';
 import { OrganizationDelete } from '../OrganizationDelete';
 import { getOrganizationBilling } from '../../../../api/organizations';
 import { waitAndUpdate } from '../../../../helpers/testUtils';
+import { isSonarCloud } from '../../../../helpers/system';
 
 jest.mock('../../../../api/organizations', () => ({
   getOrganizationBilling: jest.fn(() =>
@@ -29,6 +30,8 @@ jest.mock('../../../../api/organizations', () => ({
   )
 }));
 
+jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() }));
+
 beforeEach(() => {
   (getOrganizationBilling as jest.Mock<any>).mockClear();
 });
@@ -38,6 +41,7 @@ it('smoke test', () => {
 });
 
 it('should redirect the page', async () => {
+  (isSonarCloud as jest.Mock).mockImplementation(() => false);
   const deleteOrganization = jest.fn(() => Promise.resolve());
   const replace = jest.fn();
   const wrapper = getWrapper({ deleteOrganization }, { router: { replace } });
@@ -48,6 +52,7 @@ it('should redirect the page', async () => {
 });
 
 it('should show a info message for paying organization', async () => {
+  (isSonarCloud as jest.Mock).mockImplementation(() => true);
   const wrapper = getWrapper({}, { onSonarCloud: true });
   await waitAndUpdate(wrapper);
   expect(getOrganizationBilling).toHaveBeenCalledWith('foo');
index ba47200eb0ef4278061d2d58d22cf3538fd84712..6815ae5dae9c872b30c2f6a1d1df9c39cb7ce8c5 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import { connect } from 'react-redux';
 import { Organization, HomePageType } from '../../../app/types';
 import HomePageSelect from '../../../components/controls/HomePageSelect';
 import { translate } from '../../../helpers/l10n';
-import { getGlobalSettingValue } from '../../../store/rootReducer';
+import { isSonarCloud } from '../../../helpers/system';
 
-interface StateProps {
-  onSonarCloud: boolean;
-}
-
-interface Props extends StateProps {
+interface Props {
   organization: Organization;
 }
 
-export function OrganizationNavigationMeta({ onSonarCloud, organization }: Props) {
+export default function OrganizationNavigationMeta({ organization }: Props) {
   return (
     <div className="navbar-context-meta">
       {organization.url != null && (
@@ -47,7 +42,7 @@ export function OrganizationNavigationMeta({ onSonarCloud, organization }: Props
       <div className="text-muted">
         <strong>{translate('organization.key')}:</strong> {organization.key}
       </div>
-      {onSonarCloud && (
+      {isSonarCloud() && (
         <div className="navbar-context-meta-secondary">
           <HomePageSelect
             currentPage={{ type: HomePageType.Organization, organization: organization.key }}
@@ -57,13 +52,3 @@ export function OrganizationNavigationMeta({ onSonarCloud, organization }: Props
     </div>
   );
 }
-
-const mapStateToProps = (state: any): StateProps => {
-  const sonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled');
-
-  return {
-    onSonarCloud: Boolean(sonarCloudSetting && sonarCloudSetting.value === 'true')
-  };
-};
-
-export default connect(mapStateToProps)(OrganizationNavigationMeta);
index b9fd225d4711aeba70e2dbd7b03c6bfd25e641da..96d9f772c63f03fc9f2a5bf8ae3e902139350bcb 100644 (file)
  */
 import * as React from 'react';
 import { shallow } from 'enzyme';
-import { OrganizationNavigationMeta } from '../OrganizationNavigationMeta';
+import OrganizationNavigationMeta from '../OrganizationNavigationMeta';
 import { Visibility } from '../../../../app/types';
 
+jest.mock('../../../../helpers/system', () => ({ isSonarCloud: () => true }));
+
 it('renders', () => {
   expect(
     shallow(
       <OrganizationNavigationMeta
-        onSonarCloud={true}
         organization={{
           key: 'foo',
           name: 'Foo',
index 58a0d30113b1e0071961b1d803b8859a74ba7a05..820e5dc86119a09bb66fd8d44592b2d9c9e1e165 100644 (file)
@@ -17,7 +17,7 @@ exports[`render 1`] = `
         }
       }
     />
-    <Connect(OrganizationNavigationMeta)
+    <OrganizationNavigationMeta
       organization={
         Object {
           "key": "foo",
index da458f1b01445ca8224b96119093acbdbb67eadf..9898a4f2b014898440b63d388fd6ef9d412a3ff7 100644 (file)
@@ -26,13 +26,13 @@ import CodeSnippet from '../../../components/common/CodeSnippet';
 import Modal from '../../../components/controls/Modal';
 import { getBranchLikeQuery } from '../../../helpers/branches';
 import { translate } from '../../../helpers/l10n';
-import './styles.css';
 import { Button, ResetButtonLink } from '../../../components/ui/buttons';
+import { isSonarCloud } from '../../../helpers/system';
+import './styles.css';
 
 interface Props {
   branchLike?: BranchLike;
   metrics: { [key: string]: Metric };
-  onSonarCloud: boolean;
   project: string;
   qualifier: string;
 }
@@ -71,7 +71,7 @@ export default class BadgesModal extends React.PureComponent<Props, State> {
     const { selectedType, badgeOptions } = this.state;
     const header = translate('overview.badges.title');
     const fullBadgeOptions = { project, ...badgeOptions, ...getBranchLikeQuery(branchLike) };
-    const badges = this.props.onSonarCloud
+    const badges = isSonarCloud()
       ? [BadgeType.measure, BadgeType.qualityGate, BadgeType.marketing]
       : [BadgeType.measure, BadgeType.qualityGate];
     return (
index 5cff7462feb34e44d20f14dc4aa4bce7d3ee77b6..23555a9a631887b7a58d2e4ec5be20c1a94d659f 100644 (file)
@@ -22,10 +22,10 @@ import { shallow } from 'enzyme';
 import BadgesModal from '../BadgesModal';
 import { click } from '../../../../helpers/testUtils';
 import { ShortLivingBranch, BranchType } from '../../../../app/types';
+import { isSonarCloud } from '../../../../helpers/system';
 
-jest.mock('../../../../helpers/urls', () => ({
-  getHostUrl: () => 'host'
-}));
+jest.mock('../../../../helpers/urls', () => ({ getHostUrl: () => 'host' }));
+jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() }));
 
 const shortBranch: ShortLivingBranch = {
   isMain: false,
@@ -35,14 +35,9 @@ const shortBranch: ShortLivingBranch = {
 };
 
 it('should display the modal after click on sonar cloud', () => {
+  (isSonarCloud as jest.Mock).mockImplementation(() => true);
   const wrapper = shallow(
-    <BadgesModal
-      branchLike={shortBranch}
-      metrics={{}}
-      onSonarCloud={true}
-      project="foo"
-      qualifier="TRK"
-    />
+    <BadgesModal branchLike={shortBranch} metrics={{}} project="foo" qualifier="TRK" />
   );
   expect(wrapper).toMatchSnapshot();
   click(wrapper.find('Button'));
@@ -50,14 +45,9 @@ it('should display the modal after click on sonar cloud', () => {
 });
 
 it('should display the modal after click on sonar qube', () => {
+  (isSonarCloud as jest.Mock).mockImplementation(() => false);
   const wrapper = shallow(
-    <BadgesModal
-      branchLike={shortBranch}
-      metrics={{}}
-      onSonarCloud={false}
-      project="foo"
-      qualifier="TRK"
-    />
+    <BadgesModal branchLike={shortBranch} metrics={{}} project="foo" qualifier="TRK" />
   );
   expect(wrapper).toMatchSnapshot();
   click(wrapper.find('Button'));
index 37d1618f37d50a36aa44545c3987f685862ec94f..c2145d14c3b0254e4168652bb1c282be720a7d50 100644 (file)
@@ -44,12 +44,11 @@ interface Props {
 
 export default class Meta extends React.PureComponent<Props> {
   static contextTypes = {
-    onSonarCloud: PropTypes.bool,
     organizationsEnabled: PropTypes.bool
   };
 
   render() {
-    const { onSonarCloud, organizationsEnabled } = this.context;
+    const { organizationsEnabled } = this.context;
     const { branchLike, component, metrics } = this.props;
     const { qualifier, description, qualityProfiles, qualityGate, visibility } = component;
 
@@ -110,7 +109,6 @@ export default class Meta extends React.PureComponent<Props> {
             <BadgesModal
               branchLike={branchLike}
               metrics={metrics}
-              onSonarCloud={onSonarCloud}
               project={component.key}
               qualifier={component.qualifier}
             />
index 2da30f32e174c5b974a0aa027e66402fdb1b8d58..c015b983357235d1514f69f50f1e37f720184b09 100644 (file)
@@ -36,6 +36,7 @@ import { RawQuery } from '../../../helpers/query';
 import { Project, Facets } from '../types';
 import { fetchProjects, parseSorting, SORTING_SWITCH } from '../utils';
 import { parseUrlQuery, Query } from '../query';
+import { isSonarCloud } from '../../../helpers/system';
 import '../../../components/search-navigator.css';
 import '../styles.css';
 
@@ -43,7 +44,6 @@ export interface Props {
   currentUser: CurrentUser;
   isFavorite: boolean;
   location: { pathname: string; query: RawQuery };
-  onSonarCloud: boolean;
   organization?: { key: string };
   organizationsEnabled: boolean;
   storageOptionsSuffix?: string;
@@ -245,7 +245,7 @@ export default class AllProjects extends React.PureComponent<Props, State> {
                 onQueryChange={this.updateLocationQuery}
                 organization={this.props.organization}
                 query={this.state.query}
-                showFavoriteFilter={!this.props.onSonarCloud}
+                showFavoriteFilter={!isSonarCloud()}
                 view={this.getView()}
                 visualization={this.getVisualization()}
               />
@@ -266,7 +266,6 @@ export default class AllProjects extends React.PureComponent<Props, State> {
             loading={this.state.loading}
             onPerspectiveChange={this.handlePerspectiveChange}
             onQueryChange={this.updateLocationQuery}
-            onSonarCloud={this.props.onSonarCloud}
             onSortChange={this.handleSortChange}
             organization={this.props.organization}
             projects={this.state.projects}
@@ -301,7 +300,6 @@ export default class AllProjects extends React.PureComponent<Props, State> {
             cardType={this.getView()}
             isFavorite={this.props.isFavorite}
             isFiltered={this.isFiltered()}
-            onSonarCloud={this.props.onSonarCloud}
             organization={this.props.organization}
             projects={this.state.projects}
             query={this.state.query}
index cd24cb1cb1eb8743db142a228a39fb5286646796..5de80726a26ce2d4b524b0064e085037e4b36350 100644 (file)
 import { connect } from 'react-redux';
 import { CurrentUser } from '../../../app/types';
 import { lazyLoad } from '../../../components/lazyLoad';
-import {
-  getCurrentUser,
-  areThereCustomOrganizations,
-  getGlobalSettingValue
-} from '../../../store/rootReducer';
+import { getCurrentUser, areThereCustomOrganizations } from '../../../store/rootReducer';
 import { RawQuery } from '../../../helpers/query';
 
 interface StateProps {
   currentUser: CurrentUser;
-  onSonarCloud: boolean;
   organizationsEnabled: boolean;
 }
 
@@ -40,14 +35,10 @@ interface OwnProps {
   storageOptionsSuffix?: string;
 }
 
-const stateToProps = (state: any) => {
-  const onSonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled');
-  return {
-    currentUser: getCurrentUser(state),
-    onSonarCloud: Boolean(onSonarCloudSetting && onSonarCloudSetting.value === 'true'),
-    organizationsEnabled: areThereCustomOrganizations(state)
-  };
-};
+const stateToProps = (state: any) => ({
+  currentUser: getCurrentUser(state),
+  organizationsEnabled: areThereCustomOrganizations(state)
+});
 
 export default connect<StateProps, {}, OwnProps>(stateToProps)(
   lazyLoad(() => import('./AllProjects'))
index 07119e134ede03df0fa0916b7699a2304b2f6492..1fffa7f7d3eeb86660c0142f9021d8a0c78eca35 100644 (file)
@@ -24,11 +24,11 @@ import { PROJECTS_DEFAULT_FILTER, PROJECTS_FAVORITE, PROJECTS_ALL } from '../uti
 import { get } from '../../../helpers/storage';
 import { searchProjects } from '../../../api/components';
 import { CurrentUser, isLoggedIn } from '../../../app/types';
+import { isSonarCloud } from '../../../helpers/system';
 
 interface Props {
   currentUser: CurrentUser;
   location: { pathname: string; query: { [x: string]: string } };
-  onSonarCloud: boolean;
 }
 
 interface State {
@@ -47,17 +47,17 @@ export default class DefaultPageSelector extends React.PureComponent<Props, Stat
   }
 
   componentDidMount() {
-    if (this.props.onSonarCloud && !isLoggedIn(this.props.currentUser)) {
+    if (isSonarCloud() && !isLoggedIn(this.props.currentUser)) {
       this.context.router.replace('/explore/projects');
     }
 
-    if (!this.props.onSonarCloud) {
+    if (!isSonarCloud()) {
       this.defineIfShouldBeRedirected();
     }
   }
 
   componentDidUpdate(prevProps: Props) {
-    if (!this.props.onSonarCloud) {
+    if (!isSonarCloud()) {
       if (prevProps.location !== this.props.location) {
         this.defineIfShouldBeRedirected();
       } else if (this.state.shouldBeRedirected === true) {
@@ -113,7 +113,7 @@ export default class DefaultPageSelector extends React.PureComponent<Props, Stat
   }
 
   render() {
-    if (this.props.onSonarCloud && isLoggedIn(this.props.currentUser)) {
+    if (isSonarCloud() && isLoggedIn(this.props.currentUser)) {
       return <AllProjectsContainer isFavorite={true} location={this.props.location} />;
     }
 
index 00f726ac4d13b909b7932411cf67a059525a0276..f9cacdeec5386d0b38e45749bf96d7fadfe2dd03 100644 (file)
 import { connect } from 'react-redux';
 import DefaultPageSelector from './DefaultPageSelector';
 import { CurrentUser } from '../../../app/types';
-import { getCurrentUser, getGlobalSettingValue } from '../../../store/rootReducer';
+import { getCurrentUser } from '../../../store/rootReducer';
 
 interface StateProps {
   currentUser: CurrentUser;
-  onSonarCloud: boolean;
 }
 
-const stateToProps = (state: any) => {
-  const onSonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled');
-  return {
-    currentUser: getCurrentUser(state),
-    onSonarCloud: Boolean(onSonarCloudSetting && onSonarCloudSetting.value === 'true')
-  };
-};
+const stateToProps = (state: any) => ({
+  currentUser: getCurrentUser(state)
+});
 
 export default connect<StateProps>(stateToProps)(DefaultPageSelector);
index 935ff76d795267c7b3d9c11e6127985adf5d5e10..6c52d002bb1a79fe854b5bedf896088d8b47e099 100644 (file)
@@ -28,16 +28,13 @@ import Dropdown from '../../../components/controls/Dropdown';
 import { getMyOrganizations } from '../../../store/rootReducer';
 import OrganizationListItem from '../../../components/ui/OrganizationListItem';
 import { translate } from '../../../helpers/l10n';
+import { isSonarCloud } from '../../../helpers/system';
 
 interface StateProps {
   organizations: Organization[];
 }
 
-interface Props extends StateProps {
-  onSonarCloud: boolean;
-}
-
-export class NoFavoriteProjects extends React.PureComponent<Props> {
+export class NoFavoriteProjects extends React.PureComponent<StateProps> {
   static contextTypes = {
     openOnboardingTutorial: PropTypes.func
   };
@@ -49,11 +46,11 @@ export class NoFavoriteProjects extends React.PureComponent<Props> {
   };
 
   render() {
-    const { onSonarCloud, organizations } = this.props;
+    const { organizations } = this.props;
     return (
       <div className="projects-empty-list">
         <h3>{translate('projects.no_favorite_projects')}</h3>
-        {onSonarCloud ? (
+        {isSonarCloud() ? (
           <div className="spacer-top">
             <p>{translate('projects.no_favorite_projects.how_to_add_projects')}</p>
             <div className="huge-spacer-top">
index c0aeb6f835b9893aad4085297a3eb7090e1052db..17c584fdc52d5d75cc03427bed1ad5fdf61f1963 100644 (file)
@@ -28,6 +28,7 @@ import HomePageSelect from '../../../components/controls/HomePageSelect';
 import { translate } from '../../../helpers/l10n';
 import { RawQuery } from '../../../helpers/query';
 import { Project } from '../types';
+import { isSonarCloud } from '../../../helpers/system';
 
 interface Props {
   currentUser: CurrentUser;
@@ -35,7 +36,6 @@ interface Props {
   loading: boolean;
   onPerspectiveChange: (x: { view: string; visualization?: string }) => void;
   onQueryChange: (change: RawQuery) => void;
-  onSonarCloud: boolean;
   onSortChange: (sort: string, desc: boolean) => void;
   organization?: { key: string };
   projects?: Project[];
@@ -50,7 +50,7 @@ export default function PageHeader(props: Props) {
   const { loading, total, projects, currentUser, view } = props;
   const limitReached = projects != null && total != null && projects.length < total;
   const defaultOption = isLoggedIn(currentUser) ? 'name' : 'analysis_date';
-  const showHomePageSelect = !props.onSonarCloud || props.isFavorite;
+  const showHomePageSelect = !isSonarCloud() || props.isFavorite;
 
   return (
     <header className="page-header projects-topbar-items">
@@ -106,7 +106,7 @@ export default function PageHeader(props: Props) {
         <HomePageSelect
           className="huge-spacer-left"
           currentPage={
-            props.onSonarCloud ? { type: HomePageType.MyProjects } : { type: HomePageType.Projects }
+            isSonarCloud() ? { type: HomePageType.MyProjects } : { type: HomePageType.Projects }
           }
         />
       )}
index 045298cc38d1ac2dbba07e07f9c0a3ca5313c2d5..491da7f539b24a19068b35073ad2f9dbfe572063 100644 (file)
@@ -30,7 +30,6 @@ interface Props {
   cardType?: string;
   isFavorite: boolean;
   isFiltered: boolean;
-  onSonarCloud: boolean;
   organization?: { key: string };
   projects: Project[];
   query: Query;
@@ -42,11 +41,7 @@ export default class ProjectsList extends React.PureComponent<Props> {
     if (isFiltered) {
       return isFavorite ? <EmptyFavoriteSearch query={query} /> : <EmptySearch />;
     }
-    return isFavorite ? (
-      <NoFavoriteProjects onSonarCloud={this.props.onSonarCloud} />
-    ) : (
-      <EmptyInstance />
-    );
+    return isFavorite ? <NoFavoriteProjects /> : <EmptyInstance />;
   }
 
   render() {
@@ -58,8 +53,8 @@ export default class ProjectsList extends React.PureComponent<Props> {
           ? projects.map(project => (
               <ProjectCard
                 key={project.key}
-                project={project}
                 organization={this.props.organization}
+                project={project}
                 type={this.props.cardType}
               />
             ))
index b6339e6c3fbabb65d98b90217ff8b5f7e0c12a0e..7497a286fa95ea1b57aaf39bffd5be66cedb95d9 100644 (file)
@@ -171,7 +171,6 @@ function shallowRender(
       currentUser={{ isLoggedIn: true }}
       isFavorite={false}
       location={{ pathname: '/projects', query: {} }}
-      onSonarCloud={false}
       organizationsEnabled={false}
       {...props}
     />,
index f840055a266597c05912f0af5c8ce70daa189096..f5beee0d39cae7d03b67959f3f3fb494808cae9d 100644 (file)
@@ -88,11 +88,7 @@ function mountRender(
   replace: any = jest.fn()
 ) {
   return mount(
-    <DefaultPageSelector
-      currentUser={currentUser}
-      location={{ pathname: '/projects', query }}
-      onSonarCloud={false}
-    />,
+    <DefaultPageSelector currentUser={currentUser} location={{ pathname: '/projects', query }} />,
     { context: { router: { replace } } }
   );
 }
index 611e3e650e0423b4780737d1fd07ccffb81ca0f2..d49eaa626107683438a5557c2b484fa0a8dd67ce 100644 (file)
@@ -21,17 +21,20 @@ import * as React from 'react';
 import { shallow } from 'enzyme';
 import { NoFavoriteProjects } from '../NoFavoriteProjects';
 import { Visibility } from '../../../../app/types';
+import { isSonarCloud } from '../../../../helpers/system';
+
+jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() }));
 
 it('renders', () => {
-  expect(shallow(<NoFavoriteProjects onSonarCloud={false} organizations={[]} />)).toMatchSnapshot();
+  (isSonarCloud as jest.Mock).mockImplementation(() => false);
+  expect(shallow(<NoFavoriteProjects organizations={[]} />)).toMatchSnapshot();
 });
 
 it('renders for SonarCloud', () => {
+  (isSonarCloud as jest.Mock).mockImplementation(() => true);
   const organizations = [
     { isAdmin: true, key: 'org1', name: 'org1', projectVisibility: Visibility.Public },
     { isAdmin: false, key: 'org2', name: 'org2', projectVisibility: Visibility.Public }
   ];
-  expect(
-    shallow(<NoFavoriteProjects onSonarCloud={true} organizations={organizations} />)
-  ).toMatchSnapshot();
+  expect(shallow(<NoFavoriteProjects organizations={organizations} />)).toMatchSnapshot();
 });
index 30c4d346c4fcd76dfa38723598d02645f06366ce..a8230cb27d5238927b0681a3a88064c50135c4a8 100644 (file)
@@ -75,7 +75,6 @@ function shallowRender(props?: {}) {
       loading={false}
       onPerspectiveChange={jest.fn()}
       onQueryChange={jest.fn()}
-      onSonarCloud={false}
       onSortChange={jest.fn()}
       projects={[]}
       query={{ search: 'test' }}
index ec7df22da87f7515240865337973655e3482ffc8..88824ab0f4d2024d04b546055cfee6b48cb77a14 100644 (file)
@@ -38,7 +38,6 @@ exports[`renders 1`] = `
             loading={false}
             onPerspectiveChange={[Function]}
             onQueryChange={[Function]}
-            onSonarCloud={false}
             onSortChange={[Function]}
             projects={
               Array [
@@ -87,7 +86,6 @@ exports[`renders 1`] = `
         cardType="overall"
         isFavorite={false}
         isFiltered={false}
-        onSonarCloud={false}
         projects={
           Array [
             Object {
@@ -170,7 +168,6 @@ exports[`renders 2`] = `
             loading={false}
             onPerspectiveChange={[Function]}
             onQueryChange={[Function]}
-            onSonarCloud={false}
             onSortChange={[Function]}
             projects={
               Array [
index 2750f8f083e85f170e1ab9c9a38fb17f987db669..31e0eb9bc60842cc2dc4f7a2c1d4900b4b15f49e 100644 (file)
@@ -18,7 +18,6 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import * as PropTypes from 'prop-types';
 import { connect } from 'react-redux';
 import Login from './Login';
 import LoginSonarCloud from './LoginSonarCloud';
@@ -26,6 +25,7 @@ import { doLogin } from '../../../store/rootActions';
 import { getIdentityProviders } from '../../../api/users';
 import { IdentityProvider } from '../../../app/types';
 import { getBaseUrl } from '../../../helpers/urls';
+import { isSonarCloud } from '../../../helpers/system';
 
 interface OwnProps {
   location: {
@@ -51,10 +51,6 @@ interface State {
 class LoginContainer extends React.PureComponent<Props, State> {
   mounted = false;
 
-  static contextTypes = {
-    onSonarCloud: PropTypes.bool
-  };
-
   state: State = {};
 
   componentDidMount() {
@@ -96,7 +92,7 @@ class LoginContainer extends React.PureComponent<Props, State> {
       return null;
     }
 
-    if (this.context.onSonarCloud) {
+    if (isSonarCloud()) {
       return (
         <LoginSonarCloud
           identityProviders={identityProviders}
index 05f71b3224b4694b0084194705146f63e57d3e18..afaa359bfb7cf05d7b7c57e7fe72e3cbdcd5adca 100644 (file)
@@ -37,7 +37,6 @@ type Props = {|
   onReset: () => void,
   open: boolean,
   organization?: string,
-  sonarCloud: boolean,
   stepNumber: number,
   token: string
 |};
@@ -73,7 +72,6 @@ export default class AnalysisStep extends React.PureComponent {
               onDone={this.handleLanguageSelect}
               onReset={this.handleLanguageReset}
               organization={this.props.organization}
-              sonarCloud={this.props.sonarCloud}
             />
           </div>
           <div className="flex-column flex-column-half">{this.renderCommand()}</div>
index a2c44562cf17d825bd155143b84adcfd44e745d9..c9bd1c15e5a92df101c4e1050ad5ca0c68e8ac30 100644 (file)
@@ -22,13 +22,13 @@ import React from 'react';
 import NewProjectForm from './NewProjectForm';
 import RadioToggle from '../../../components/controls/RadioToggle';
 import { translate } from '../../../helpers/l10n';
+import { isSonarCloud } from '../../../helpers/system';
 
 /*::
 type Props = {|
   onDone: (result: Result) => void,
   onReset: () => void,
   organization?: string,
-  sonarCloud: boolean
 |};
 */
 
@@ -48,8 +48,6 @@ export type Result = State; */
 export default class LanguageStep extends React.PureComponent {
   /*:: props: Props; */
 
-  static defaultProps = { sonarCloud: false };
-
   state /*: State */ = {};
 
   isConfigured = () => {
@@ -157,7 +155,7 @@ export default class LanguageStep extends React.PureComponent {
           (this.state.cFamilyCompiler === 'clang-gcc' && this.state.os != null))) ||
       (this.state.language === 'other' && this.state.os !== undefined);
 
-    const languages = this.props.sonarCloud
+    const languages = isSonarCloud()
       ? ['java', 'dotnet', 'c-family', 'other']
       : ['java', 'dotnet', 'other'];
 
index e7c31f647ccd0ad5efbb7d8f065d429f5ef10c05..cc04c85868c56ddca259a3d73d92b654a3d31e92 100644 (file)
@@ -31,6 +31,7 @@ import { skipOnboarding } from '../../../api/users';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { getProjectUrl } from '../../../helpers/urls';
 import './styles.css';
+import { isSonarCloud } from '../../../helpers/system';
 
 /*::
 type Props = {|
@@ -58,7 +59,6 @@ export default class Onboarding extends React.PureComponent {
   /*:: state: State; */
 
   static contextTypes = {
-    onSonarCloud: PropTypes.bool,
     router: PropTypes.object
   };
 
@@ -145,7 +145,6 @@ export default class Onboarding extends React.PureComponent {
       return null;
     }
 
-    const { onSonarCloud } = this.context;
     const { organizationsEnabled } = this.props;
     const { step, token } = this.state;
     let stepNumber = 1;
@@ -171,7 +170,9 @@ export default class Onboarding extends React.PureComponent {
               )}
               <p className="note">
                 {translate(
-                  onSonarCloud ? 'tutorials.find_it_back_in_plus' : 'tutorials.find_it_back_in_help'
+                  isSonarCloud()
+                    ? 'tutorials.find_it_back_in_plus'
+                    : 'tutorials.find_it_back_in_help'
                 )}
               </p>
             </div>
@@ -208,7 +209,6 @@ export default class Onboarding extends React.PureComponent {
             onReset={this.handleReset}
             open={step === 'analysis'}
             organization={this.state.organization}
-            sonarCloud={onSonarCloud}
             stepNumber={stepNumber}
             token={token}
           />
index 07b69ea16a49f001e484e6879bd024cd7f208f5b..2609b550dd5616bb052d3605bdfdf2e97ea21b73 100644 (file)
 import React from 'react';
 import { shallow } from 'enzyme';
 import LanguageStep from '../LanguageStep';
+import { isSonarCloud } from '../../../../helpers/system';
+
+jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() }));
+
+beforeEach(() => {
+  isSonarCloud.mockImplementation(() => false);
+});
 
 it('selects java', () => {
   const onDone = jest.fn();
@@ -60,8 +67,9 @@ it('selects c#', () => {
 });
 
 it('selects c-family', () => {
+  isSonarCloud.mockImplementation(() => true);
   const onDone = jest.fn();
-  const wrapper = shallow(<LanguageStep onDone={onDone} onReset={jest.fn()} sonarCloud={true} />);
+  const wrapper = shallow(<LanguageStep onDone={onDone} onReset={jest.fn()} />);
 
   wrapper.find('RadioToggle').prop('onCheck')('c-family');
   wrapper.update();
index 2d5019ae82d41e4b49e657a5a971d57a4d2d098f..e3acb2a83f672f6a0a7a4b06fb0f87eede70b3a4 100644 (file)
@@ -22,14 +22,22 @@ import React from 'react';
 import { shallow, mount } from 'enzyme';
 import Onboarding from '../Onboarding';
 import { click, doAsync } from '../../../../helpers/testUtils';
+import { getInstance, isSonarCloud } from '../../../../helpers/system';
 
 jest.mock('../../../../api/users', () => ({
   skipOnboarding: () => Promise.resolve()
 }));
 
+jest.mock('../../../../helpers/system', () => ({
+  getInstance: jest.fn(),
+  isSonarCloud: jest.fn()
+}));
+
 const currentUser = { login: 'admin', isLoggedIn: true };
 
 it('guides for on-premise', () => {
+  getInstance.mockImplementation(() => 'SonarQube');
+  isSonarCloud.mockImplementation(() => false);
   const wrapper = shallow(
     <Onboarding
       className="modal-container"
@@ -47,9 +55,10 @@ it('guides for on-premise', () => {
 });
 
 it('guides for sonarcloud', () => {
+  getInstance.mockImplementation(() => 'SonarCloud');
+  isSonarCloud.mockImplementation(() => true);
   const wrapper = shallow(
-    <Onboarding currentUser={currentUser} onFinish={jest.fn()} organizationsEnabled={true} />,
-    { context: { onSonarCloud: true } }
+    <Onboarding currentUser={currentUser} onFinish={jest.fn()} organizationsEnabled={true} />
   );
   expect(wrapper).toMatchSnapshot();
 
@@ -65,6 +74,8 @@ it('guides for sonarcloud', () => {
 });
 
 it('finishes', () => {
+  getInstance.mockImplementation(() => 'SonarQube');
+  isSonarCloud.mockImplementation(() => false);
   const onFinish = jest.fn();
   const wrapper = mount(
     <Onboarding currentUser={currentUser} onFinish={onFinish} organizationsEnabled={false} />
index 997502681fe5a697ef17c106ec0a5f7b7ad544b3..313284fb6a0e6e7cdec1cfa376e409bee145573a 100644 (file)
@@ -201,7 +201,6 @@ exports[`guides for sonarcloud 1`] = `
       onFinish={[Function]}
       onReset={[Function]}
       open={false}
-      sonarCloud={true}
       stepNumber={3}
     />
   </div>
@@ -279,7 +278,6 @@ exports[`guides for sonarcloud 2`] = `
       onReset={[Function]}
       open={false}
       organization="my-org"
-      sonarCloud={true}
       stepNumber={3}
     />
   </div>
@@ -357,7 +355,6 @@ exports[`guides for sonarcloud 3`] = `
       onReset={[Function]}
       open={true}
       organization="my-org"
-      sonarCloud={true}
       stepNumber={3}
       token="abcd1234"
     />
index 9c68882039deafc2e3d98333a0a958e5227b6b4b..451e7850dc5ef28d9364a825c3644ec07357e27d 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import * as PropTypes from 'prop-types';
+import { getInstance } from '../../helpers/system';
 
 interface Props {
-  children?: (transformedMessage: string) => React.ReactNode;
+  children?: (transformedMessage: string) => React.ReactChild;
   message: string;
 }
 
-const InstanceMessage: React.SFC<Props> = (
-  { children, message }: Props,
-  context: { onSonarCloud: boolean }
-) => {
-  const transformedMessage = message.replace(
-    /\{instance\}/gim,
-    context.onSonarCloud ? 'SonarCloud' : 'SonarQube'
-  );
+export default function InstanceMessage({ children, message }: Props): any {
+  const transformedMessage = message.replace(/\{instance\}/gim, getInstance());
 
   if (children) {
     return children(transformedMessage);
   }
 
-  return transformedMessage as any;
-};
-
-InstanceMessage.contextTypes = {
-  onSonarCloud: PropTypes.bool
-};
-
-export default InstanceMessage;
+  return transformedMessage;
+}
index 482d988b92e789c14673b8365eea4df524a66843..b9d77e3b74839d2a4e676211e7c5248fab34fa47 100644 (file)
@@ -20,6 +20,9 @@
 import * as React from 'react';
 import { shallow } from 'enzyme';
 import InstanceMessage from '../InstanceMessage';
+import { getInstance } from '../../../helpers/system';
+
+jest.mock('../../../helpers/system', () => ({ getInstance: jest.fn() }));
 
 it('should replace {instance} with "SonarQube"', () => {
   const childFunc = jest.fn();
@@ -44,7 +47,6 @@ function getWrapper(
   message: string,
   onSonarCloud = false
 ) {
-  return shallow(<InstanceMessage message={message}>{children}</InstanceMessage>, {
-    context: { onSonarCloud }
-  });
+  (getInstance as jest.Mock).mockImplementation(() => (onSonarCloud ? 'SonarCloud' : 'SonarQube'));
+  return shallow(<InstanceMessage message={message}>{children}</InstanceMessage>);
 }
index 384f0339d78b37a729ddd5e01b9a245e1834abf4..4ec569cb43221b1612a591f71bc48f543144f6d9 100644 (file)
@@ -21,11 +21,11 @@ import * as React from 'react';
 import * as classNames from 'classnames';
 import remark from 'remark';
 import reactRenderer from 'remark-react';
-import * as PropTypes from 'prop-types';
 import DocLink from './DocLink';
 import DocParagraph from './DocParagraph';
 import DocImg from './DocImg';
 import { separateFrontMatter } from '../../helpers/markdown';
+import { isSonarCloud } from '../../helpers/system';
 
 interface Props {
   className?: string;
@@ -33,43 +33,36 @@ interface Props {
   displayH1?: boolean;
 }
 
-export default class DocMarkdownBlock extends React.PureComponent<Props> {
-  static contextTypes = {
-    onSonarCloud: PropTypes.bool
-  };
-
-  render() {
-    const { className, content, displayH1 } = this.props;
-    const parsed = separateFrontMatter(content || '');
-    return (
-      <div className={classNames('markdown', className)}>
-        {displayH1 && <h1>{parsed.frontmatter.title}</h1>}
-        {
-          remark()
-            // .use(remarkInclude)
-            .use(reactRenderer, {
-              remarkReactComponents: {
-                // do not render outer <div />
-                div: React.Fragment,
-                // use custom link to render documentation anchors
-                a: DocLink,
-                // used to handle `@include`
-                p: DocParagraph,
-                // use custom img tag to render documentation images
-                img: DocImg
-              },
-              toHast: {}
-            })
-            .processSync(filterContent(parsed.content, this.context.onSonarCloud)).contents
-        }
-      </div>
-    );
-  }
+export default function DocMarkdownBlock({ className, content, displayH1 }: Props) {
+  const parsed = separateFrontMatter(content || '');
+  return (
+    <div className={classNames('markdown', className)}>
+      {displayH1 && <h1>{parsed.frontmatter.title}</h1>}
+      {
+        remark()
+          // .use(remarkInclude)
+          .use(reactRenderer, {
+            remarkReactComponents: {
+              // do not render outer <div />
+              div: React.Fragment,
+              // use custom link to render documentation anchors
+              a: DocLink,
+              // used to handle `@include`
+              p: DocParagraph,
+              // use custom img tag to render documentation images
+              img: DocImg
+            },
+            toHast: {}
+          })
+          .processSync(filterContent(parsed.content)).contents
+      }
+    </div>
+  );
 }
 
-function filterContent(content: string, onSonarCloud: boolean) {
-  const beginning = onSonarCloud ? '<!-- sonarqube -->' : '<!-- sonarcloud -->';
-  const ending = onSonarCloud ? '<!-- /sonarqube -->' : '<!-- /sonarcloud -->';
+function filterContent(content: string) {
+  const beginning = isSonarCloud() ? '<!-- sonarqube -->' : '<!-- sonarcloud -->';
+  const ending = isSonarCloud() ? '<!-- /sonarqube -->' : '<!-- /sonarcloud -->';
 
   let newContent = content;
   let start = newContent.indexOf(beginning);
index ebe70a272478f84c29dbd765c3a9c437c5c5cbe7..898ff22a5a375858be3d7169d1a386790e25ed1c 100644 (file)
@@ -20,6 +20,7 @@
 import * as React from 'react';
 import { shallow } from 'enzyme';
 import DocMarkdownBlock from '../DocMarkdownBlock';
+import { isSonarCloud } from '../../../helpers/system';
 
 // mock `remark` and `remark-react` to work around the issue with cjs imports
 jest.mock('remark', () => {
@@ -32,6 +33,8 @@ jest.mock('remark-react', () => {
   return { default: remarkReact };
 });
 
+jest.mock('../../../helpers/system', () => ({ isSonarCloud: jest.fn() }));
+
 it('should render simple markdown', () => {
   expect(shallow(<DocMarkdownBlock content="this is *bold* text" />)).toMatchSnapshot();
 });
@@ -42,7 +45,7 @@ it('should render use custom component for links', () => {
   ).toMatchSnapshot();
 });
 
-it.only('should cut sonarqube/sonarcloud content', () => {
+it('should cut sonarqube/sonarcloud content', () => {
   const content = `
 some
 
@@ -62,11 +65,9 @@ sonarcloud
 
 text`;
 
-  expect(
-    shallow(<DocMarkdownBlock content={content} />, { context: { onSonarCloud: false } })
-  ).toMatchSnapshot();
+  (isSonarCloud as jest.Mock).mockImplementation(() => false);
+  expect(shallow(<DocMarkdownBlock content={content} />)).toMatchSnapshot();
 
-  expect(
-    shallow(<DocMarkdownBlock content={content} />, { context: { onSonarCloud: true } })
-  ).toMatchSnapshot();
+  (isSonarCloud as jest.Mock).mockImplementation(() => true);
+  expect(shallow(<DocMarkdownBlock content={content} />)).toMatchSnapshot();
 });
diff --git a/server/sonar-web/src/main/js/helpers/system.ts b/server/sonar-web/src/main/js/helpers/system.ts
new file mode 100644 (file)
index 0000000..6bc79a7
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+export function getSystemStatus(): string {
+  return (window as any).serverStatus;
+}
+
+export function getInstance(): 'SonarQube' | 'SonarCloud' {
+  return (window as any).instance;
+}
+
+export function isSonarCloud() {
+  return getInstance() === 'SonarCloud';
+}