@@ -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)); | |||
} | |||
@@ -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\"" + |
@@ -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), |
@@ -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> | |||
<% } %> |
@@ -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} | |||
</> | |||
); |
@@ -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} | |||
</> | |||
); | |||
} | |||
} |
@@ -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> | |||
); | |||
} |
@@ -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> |
@@ -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); |
@@ -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); |
@@ -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} />); | |||
} |
@@ -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> | |||
); |
@@ -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'); | |||
@@ -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: [] }); | |||
@@ -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; | |||
} | |||
@@ -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 |
@@ -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 }; | |||
@@ -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> |
@@ -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> |
@@ -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}> |
@@ -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 }; |
@@ -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 }); |
@@ -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); |
@@ -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); |
@@ -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} | |||
/> |
@@ -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) { |
@@ -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 } | |||
} | |||
/> | |||
)} |
@@ -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) { |
@@ -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'); |
@@ -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); |
@@ -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', |
@@ -17,7 +17,7 @@ exports[`render 1`] = ` | |||
} | |||
} | |||
/> | |||
<Connect(OrganizationNavigationMeta) | |||
<OrganizationNavigationMeta | |||
organization={ | |||
Object { | |||
"key": "foo", |
@@ -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 ( |
@@ -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')); |
@@ -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} | |||
/> |
@@ -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} |
@@ -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')) |
@@ -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} />; | |||
} | |||
@@ -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); |
@@ -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"> |
@@ -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 } | |||
} | |||
/> | |||
)} |
@@ -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} | |||
/> | |||
)) |
@@ -171,7 +171,6 @@ function shallowRender( | |||
currentUser={{ isLoggedIn: true }} | |||
isFavorite={false} | |||
location={{ pathname: '/projects', query: {} }} | |||
onSonarCloud={false} | |||
organizationsEnabled={false} | |||
{...props} | |||
/>, |
@@ -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 } } } | |||
); | |||
} |
@@ -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(); | |||
}); |
@@ -75,7 +75,6 @@ function shallowRender(props?: {}) { | |||
loading={false} | |||
onPerspectiveChange={jest.fn()} | |||
onQueryChange={jest.fn()} | |||
onSonarCloud={false} | |||
onSortChange={jest.fn()} | |||
projects={[]} | |||
query={{ search: 'test' }} |
@@ -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 [ |
@@ -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} |
@@ -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> |
@@ -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']; | |||
@@ -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} | |||
/> |
@@ -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(); |
@@ -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} /> |
@@ -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" | |||
/> |
@@ -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; | |||
} |
@@ -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>); | |||
} |
@@ -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); |
@@ -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(); | |||
}); |
@@ -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'; | |||
} |