Browse Source

SONAR-10833 Reduce loading waterfall

tags/7.5
Stas Vilchik 6 years ago
parent
commit
fa85337a62
59 changed files with 363 additions and 515 deletions
  1. 0
    2
      server/sonar-server/src/main/java/org/sonar/server/ui/ws/GlobalAction.java
  2. 0
    2
      server/sonar-server/src/test/java/org/sonar/server/ui/ws/GlobalActionTest.java
  3. 6
    1
      server/sonar-web/config/webpack.config.js
  4. 6
    2
      server/sonar-web/public/index.html
  5. 2
    8
      server/sonar-web/src/main/js/app/components/App.tsx
  6. 0
    76
      server/sonar-web/src/main/js/app/components/AppContextContainer.tsx
  7. 59
    62
      server/sonar-web/src/main/js/app/components/GlobalFooter.tsx
  8. 0
    2
      server/sonar-web/src/main/js/app/components/GlobalLoading.tsx
  9. 7
    11
      server/sonar-web/src/main/js/app/components/Landing.tsx
  10. 15
    32
      server/sonar-web/src/main/js/app/components/MigrationContainer.tsx
  11. 5
    3
      server/sonar-web/src/main/js/app/components/__tests__/GlobalFooter-test.tsx
  12. 2
    3
      server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx
  13. 2
    5
      server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsProvider.tsx
  14. 6
    3
      server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/SuggestionsProvider-test.tsx
  15. 3
    3
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx
  16. 8
    0
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx
  17. 9
    14
      server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx
  18. 2
    7
      server/sonar-web/src/main/js/app/components/nav/global/GlobalNavExplore.tsx
  19. 14
    14
      server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.tsx
  20. 2
    4
      server/sonar-web/src/main/js/app/utils/startReactApp.js
  21. 26
    19
      server/sonar-web/src/main/js/apps/about/components/AboutApp.js
  22. 2
    6
      server/sonar-web/src/main/js/apps/documentation/components/App.tsx
  23. 2
    6
      server/sonar-web/src/main/js/apps/documentation/components/Menu.tsx
  24. 7
    11
      server/sonar-web/src/main/js/apps/issues/IssuesPageSelector.tsx
  25. 4
    5
      server/sonar-web/src/main/js/apps/issues/components/App.tsx
  26. 4
    13
      server/sonar-web/src/main/js/apps/issues/components/AppContainer.tsx
  27. 2
    4
      server/sonar-web/src/main/js/apps/issues/components/PageActions.tsx
  28. 3
    3
      server/sonar-web/src/main/js/apps/organizations/components/OrganizationDelete.tsx
  29. 5
    0
      server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationDelete-test.tsx
  30. 4
    19
      server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMeta.tsx
  31. 3
    2
      server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMeta-test.tsx
  32. 1
    1
      server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.tsx.snap
  33. 3
    3
      server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx
  34. 7
    17
      server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx
  35. 1
    3
      server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx
  36. 2
    4
      server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
  37. 5
    14
      server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.tsx
  38. 5
    5
      server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx
  39. 4
    9
      server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelectorContainer.tsx
  40. 4
    7
      server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx
  41. 3
    3
      server/sonar-web/src/main/js/apps/projects/components/PageHeader.tsx
  42. 2
    7
      server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx
  43. 0
    1
      server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx
  44. 1
    5
      server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx
  45. 7
    4
      server/sonar-web/src/main/js/apps/projects/components/__tests__/NoFavoriteProjects-test.tsx
  46. 0
    1
      server/sonar-web/src/main/js/apps/projects/components/__tests__/PageHeader-test.tsx
  47. 0
    3
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap
  48. 2
    6
      server/sonar-web/src/main/js/apps/sessions/components/LoginContainer.tsx
  49. 0
    2
      server/sonar-web/src/main/js/apps/tutorials/onboarding/AnalysisStep.js
  50. 2
    4
      server/sonar-web/src/main/js/apps/tutorials/onboarding/LanguageStep.js
  51. 4
    4
      server/sonar-web/src/main/js/apps/tutorials/onboarding/Onboarding.js
  52. 9
    1
      server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/LanguageStep-test.js
  53. 13
    2
      server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/Onboarding-test.js
  54. 0
    3
      server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/Onboarding-test.js.snap
  55. 6
    18
      server/sonar-web/src/main/js/components/common/InstanceMessage.tsx
  56. 5
    3
      server/sonar-web/src/main/js/components/common/__tests__/InstanceMessage-test.tsx
  57. 29
    36
      server/sonar-web/src/main/js/components/docs/DocMarkdownBlock.tsx
  58. 8
    7
      server/sonar-web/src/main/js/components/docs/__tests__/DocMarkdownBlock-test.tsx
  59. 30
    0
      server/sonar-web/src/main/js/helpers/system.ts

+ 0
- 2
server/sonar-server/src/main/java/org/sonar/server/ui/ws/GlobalAction.java View 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));
}


+ 0
- 2
server/sonar-server/src/test/java/org/sonar/server/ui/ws/GlobalActionTest.java View 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\"" +

+ 6
- 1
server/sonar-web/config/webpack.config.js View 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),

+ 6
- 2
server/sonar-web/public/index.html View 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>
@@ -37,7 +37,11 @@
<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>
<% } %>

+ 2
- 8
server/sonar-web/src/main/js/app/components/App.tsx View 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}
</>
);

+ 0
- 76
server/sonar-web/src/main/js/app/components/AppContextContainer.tsx View File

@@ -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}
</>
);
}
}

+ 59
- 62
server/sonar-web/src/main/js/app/components/GlobalFooter.tsx View File

@@ -18,12 +18,12 @@
* 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>
);
}

+ 0
- 2
server/sonar-web/src/main/js/app/components/GlobalLoading.tsx View File

@@ -18,12 +18,10 @@
* 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>

+ 7
- 11
server/sonar-web/src/main/js/app/components/Landing.tsx View 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);

server/sonar-web/src/main/js/app/components/MigrationContainer.js → server/sonar-web/src/main/js/app/components/MigrationContainer.tsx View File

@@ -17,44 +17,27 @@
* 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 };
import * as React from 'react';
import { WithRouterProps } from 'react-router';
import { getSystemStatus } from '../../helpers/system';

export default class MigrationContainer extends React.PureComponent<WithRouterProps> {
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
}
});
}
});
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 (this.state.loading) {
return <GlobalLoading />;
if (getSystemStatus() !== 'UP') {
return null;
}

return this.props.children;
}
}

export default withRouter(MigrationContainer);

+ 5
- 3
server/sonar-web/src/main/js/app/components/__tests__/GlobalFooter-test.tsx View 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} />);
}

+ 2
- 3
server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx View 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>
);

+ 2
- 5
server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsProvider.tsx View 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');


+ 6
- 3
server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/SuggestionsProvider-test.tsx View 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: [] });


+ 3
- 3
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx View 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;
}


+ 8
- 0
server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx View 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

+ 9
- 14
server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx View 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 };


+ 2
- 7
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavExplore.tsx View 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>

+ 14
- 14
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.tsx View 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>

+ 2
- 4
server/sonar-web/src/main/js/app/utils/startReactApp.js View 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}>

+ 26
- 19
server/sonar-web/src/main/js/apps/about/components/AboutApp.js View 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 };

+ 2
- 6
server/sonar-web/src/main/js/apps/documentation/components/App.tsx View 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 });

+ 2
- 6
server/sonar-web/src/main/js/apps/documentation/components/Menu.tsx View 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);

+ 7
- 11
server/sonar-web/src/main/js/apps/issues/IssuesPageSelector.tsx View 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);

+ 4
- 5
server/sonar-web/src/main/js/apps/issues/components/App.tsx View 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}
/>

+ 4
- 13
server/sonar-web/src/main/js/apps/issues/components/AppContainer.tsx View 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) {

+ 2
- 4
server/sonar-web/src/main/js/apps/issues/components/PageActions.tsx View 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 }
}
/>
)}

+ 3
- 3
server/sonar-web/src/main/js/apps/organizations/components/OrganizationDelete.tsx View 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) {

+ 5
- 0
server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationDelete-test.tsx View 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');

+ 4
- 19
server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMeta.tsx View File

@@ -18,21 +18,16 @@
* 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);

+ 3
- 2
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMeta-test.tsx View File

@@ -19,14 +19,15 @@
*/
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',

+ 1
- 1
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.tsx.snap View File

@@ -17,7 +17,7 @@ exports[`render 1`] = `
}
}
/>
<Connect(OrganizationNavigationMeta)
<OrganizationNavigationMeta
organization={
Object {
"key": "foo",

+ 3
- 3
server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx View 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 (

+ 7
- 17
server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx View 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'));

+ 1
- 3
server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx View 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}
/>

+ 2
- 4
server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx View 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}

+ 5
- 14
server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.tsx View File

@@ -20,16 +20,11 @@
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'))

+ 5
- 5
server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx View 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} />;
}


+ 4
- 9
server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelectorContainer.tsx View File

@@ -20,19 +20,14 @@
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);

+ 4
- 7
server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx View 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">

+ 3
- 3
server/sonar-web/src/main/js/apps/projects/components/PageHeader.tsx View 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 }
}
/>
)}

+ 2
- 7
server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx View 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}
/>
))

+ 0
- 1
server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx View File

@@ -171,7 +171,6 @@ function shallowRender(
currentUser={{ isLoggedIn: true }}
isFavorite={false}
location={{ pathname: '/projects', query: {} }}
onSonarCloud={false}
organizationsEnabled={false}
{...props}
/>,

+ 1
- 5
server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx View 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 } } }
);
}

+ 7
- 4
server/sonar-web/src/main/js/apps/projects/components/__tests__/NoFavoriteProjects-test.tsx View 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();
});

+ 0
- 1
server/sonar-web/src/main/js/apps/projects/components/__tests__/PageHeader-test.tsx View 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' }}

+ 0
- 3
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap View 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 [

+ 2
- 6
server/sonar-web/src/main/js/apps/sessions/components/LoginContainer.tsx View 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}

+ 0
- 2
server/sonar-web/src/main/js/apps/tutorials/onboarding/AnalysisStep.js View 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>

+ 2
- 4
server/sonar-web/src/main/js/apps/tutorials/onboarding/LanguageStep.js View 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'];


+ 4
- 4
server/sonar-web/src/main/js/apps/tutorials/onboarding/Onboarding.js View 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}
/>

+ 9
- 1
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/LanguageStep-test.js View File

@@ -21,6 +21,13 @@
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();

+ 13
- 2
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/Onboarding-test.js View 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} />

+ 0
- 3
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/Onboarding-test.js.snap View 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"
/>

+ 6
- 18
server/sonar-web/src/main/js/components/common/InstanceMessage.tsx View File

@@ -18,31 +18,19 @@
* 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;
}

+ 5
- 3
server/sonar-web/src/main/js/components/common/__tests__/InstanceMessage-test.tsx View 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>);
}

+ 29
- 36
server/sonar-web/src/main/js/components/docs/DocMarkdownBlock.tsx View 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);

+ 8
- 7
server/sonar-web/src/main/js/components/docs/__tests__/DocMarkdownBlock-test.tsx View 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();
});

+ 30
- 0
server/sonar-web/src/main/js/helpers/system.ts View File

@@ -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';
}

Loading…
Cancel
Save