@@ -0,0 +1,78 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 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 GlobalNav from './nav/global/GlobalNav'; | |||
import GlobalFooterContainer from './GlobalFooterContainer'; | |||
import GlobalMessagesContainer from './GlobalMessagesContainer'; | |||
interface Props { | |||
children: React.ReactNode; | |||
location: { pathname: string }; | |||
} | |||
interface State { | |||
isOnboardingTutorialOpen: boolean; | |||
} | |||
export default class GlobalContainer extends React.PureComponent<Props, State> { | |||
static childContextTypes = { | |||
closeOnboardingTutorial: PropTypes.func, | |||
openOnboardingTutorial: PropTypes.func | |||
}; | |||
constructor(props: Props) { | |||
super(props); | |||
this.state = { isOnboardingTutorialOpen: false }; | |||
} | |||
getChildContext() { | |||
return { | |||
closeOnboardingTutorial: this.closeOnboardingTutorial, | |||
openOnboardingTutorial: this.openOnboardingTutorial | |||
}; | |||
} | |||
openOnboardingTutorial = () => this.setState({ isOnboardingTutorialOpen: true }); | |||
closeOnboardingTutorial = () => this.setState({ isOnboardingTutorialOpen: false }); | |||
render() { | |||
// it is important to pass `location` down to `GlobalNav` to trigger render on url change | |||
return ( | |||
<div className="global-container"> | |||
<div className="page-wrapper" id="container"> | |||
<div className="page-container"> | |||
<GlobalNav | |||
closeOnboardingTutorial={this.closeOnboardingTutorial} | |||
isOnboardingTutorialOpen={this.state.isOnboardingTutorialOpen} | |||
location={this.props.location} | |||
openOnboardingTutorial={this.openOnboardingTutorial} | |||
/> | |||
<GlobalMessagesContainer /> | |||
{this.props.children} | |||
</div> | |||
</div> | |||
<GlobalFooterContainer /> | |||
</div> | |||
); | |||
} | |||
} |
@@ -1,83 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import { Link } from 'react-router'; | |||
import GlobalFooterSonarCloud from './GlobalFooterSonarCloud'; | |||
import GlobalFooterBranding from './GlobalFooterBranding'; | |||
import { translate, translateWithParameters } from '../../helpers/l10n'; | |||
/*:: | |||
type Props = { | |||
hideLoggedInInfo?: boolean, | |||
productionDatabase: boolean, | |||
onSonarCloud?: { value: string }, | |||
sonarqubeVersion?: string | |||
}; | |||
*/ | |||
export default function GlobalFooter( | |||
{ hideLoggedInInfo, productionDatabase, onSonarCloud, sonarqubeVersion } /*: Props */ | |||
) { | |||
if (onSonarCloud && onSonarCloud.value === 'true') { | |||
return <GlobalFooterSonarCloud hideLoggedInInfo={hideLoggedInInfo} />; | |||
} | |||
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>{translate('footer.production_database_explanation')}</p> | |||
</div> | |||
)} | |||
<GlobalFooterBranding /> | |||
<div> | |||
{!hideLoggedInInfo && | |||
sonarqubeVersion && | |||
translateWithParameters('footer.version_x', sonarqubeVersion)} | |||
{!hideLoggedInInfo && sonarqubeVersion && ' - '} | |||
<a href="http://www.gnu.org/licenses/lgpl-3.0.txt">{translate('footer.licence')}</a> | |||
{' - '} | |||
<a href="http://www.sonarqube.org">{translate('footer.community')}</a> | |||
{' - '} | |||
<a href="https://redirect.sonarsource.com/doc/home.html"> | |||
{translate('footer.documentation')} | |||
</a> | |||
{' - '} | |||
<a href="https://redirect.sonarsource.com/doc/community.html"> | |||
{translate('footer.support')} | |||
</a> | |||
{' - '} | |||
<a href="https://redirect.sonarsource.com/doc/plugin-library.html"> | |||
{translate('footer.plugins')} | |||
</a> | |||
{!hideLoggedInInfo && ' - '} | |||
{!hideLoggedInInfo && <Link to="/web_api">{translate('footer.web_api')}</Link>} | |||
{!hideLoggedInInfo && ' - '} | |||
{!hideLoggedInInfo && <Link to="/about">{translate('footer.about')}</Link>} | |||
</div> | |||
</div> | |||
); | |||
} |
@@ -0,0 +1,97 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 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 { Link } from 'react-router'; | |||
import GlobalFooterSonarCloud from './GlobalFooterSonarCloud'; | |||
import GlobalFooterBranding from './GlobalFooterBranding'; | |||
import { translate, translateWithParameters } from '../../helpers/l10n'; | |||
interface Props { | |||
hideLoggedInInfo?: boolean; | |||
productionDatabase: boolean; | |||
onSonarCloud?: { value: string }; | |||
sonarqubeVersion?: string; | |||
} | |||
export default function GlobalFooter({ | |||
hideLoggedInInfo, | |||
productionDatabase, | |||
onSonarCloud, | |||
sonarqubeVersion | |||
}: Props) { | |||
if (onSonarCloud && onSonarCloud.value === 'true') { | |||
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>{translate('footer.production_database_explanation')}</p> | |||
</div> | |||
)} | |||
<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> | |||
<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"> | |||
<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> | |||
); | |||
} |
@@ -21,7 +21,13 @@ import { connect } from 'react-redux'; | |||
import { getAppState, getGlobalSettingValue } from '../../store/rootReducer'; | |||
import GlobalFooter from './GlobalFooter'; | |||
const mapStateToProps = (state: any) => ({ | |||
interface StateProps { | |||
onSonarCloud?: { value: string }; | |||
productionDatabase: boolean; | |||
sonarqubeVersion?: string; | |||
} | |||
const mapStateToProps = (state: any): StateProps => ({ | |||
sonarqubeVersion: getAppState(state).version, | |||
productionDatabase: getAppState(state).productionDatabase, | |||
onSonarCloud: getGlobalSettingValue(state, 'sonar.sonarcloud.enabled') |
@@ -17,8 +17,7 @@ | |||
* 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 * as React from 'react'; | |||
import { translate } from '../../helpers/l10n'; | |||
export default function GlobalFooterSonarCloud() { | |||
@@ -32,19 +31,26 @@ export default function GlobalFooterSonarCloud() { | |||
. All rights reserved. | |||
</div> | |||
<div> | |||
<a href="https://about.sonarcloud.io/news/">{translate('footer.news')}</a> | |||
{' - '} | |||
<a href="https://about.sonarcloud.io/terms.pdf">{translate('footer.terms')}</a> | |||
{' - '} | |||
<a href="https://twitter.com/sonarqube">{translate('footer.twitter')}</a> | |||
{' - '} | |||
<a href="https://about.sonarcloud.io/get-started/">{translate('footer.get_started')}</a> | |||
{' - '} | |||
<a href="https://about.sonarcloud.io/contact/">{translate('footer.help')}</a> | |||
{' - '} | |||
<a href="https://about.sonarcloud.io/">{translate('footer.about')}</a> | |||
</div> | |||
<ul className="page-footer-menu"> | |||
<li className="page-footer-menu-item"> | |||
<a href="https://about.sonarcloud.io/news/">{translate('footer.news')}</a> | |||
</li> | |||
<li className="page-footer-menu-item"> | |||
<a href="https://about.sonarcloud.io/terms.pdf">{translate('footer.terms')}</a> | |||
</li> | |||
<li className="page-footer-menu-item"> | |||
<a href="https://twitter.com/sonarqube">{translate('footer.twitter')}</a> | |||
</li> | |||
<li className="page-footer-menu-item"> | |||
<a href="https://about.sonarcloud.io/get-started/">{translate('footer.get_started')}</a> | |||
</li> | |||
<li className="page-footer-menu-item"> | |||
<a href="https://about.sonarcloud.io/contact/">{translate('footer.help')}</a> | |||
</li> | |||
<li className="page-footer-menu-item"> | |||
<a href="https://about.sonarcloud.io/">{translate('footer.about')}</a> | |||
</li> | |||
</ul> | |||
</div> | |||
); | |||
} |
@@ -17,8 +17,8 @@ | |||
* 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 { shallow } from 'enzyme'; | |||
import React from 'react'; | |||
import GlobalFooter from '../GlobalFooter'; | |||
it('should render the only logged in information', () => { |
@@ -17,8 +17,8 @@ | |||
* 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 { shallow } from 'enzyme'; | |||
import React from 'react'; | |||
import GlobalFooterSonarCloud from '../GlobalFooterSonarCloud'; | |||
it('should render correctly', () => { |
@@ -1,173 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should display the sq version 1`] = ` | |||
<div | |||
className="page-footer page-container" | |||
id="footer" | |||
> | |||
<GlobalFooterBranding /> | |||
<div> | |||
footer.version_x.6.4-SNAPSHOT | |||
- | |||
<a | |||
href="http://www.gnu.org/licenses/lgpl-3.0.txt" | |||
> | |||
footer.licence | |||
</a> | |||
- | |||
<a | |||
href="http://www.sonarqube.org" | |||
> | |||
footer.community | |||
</a> | |||
- | |||
<a | |||
href="https://redirect.sonarsource.com/doc/home.html" | |||
> | |||
footer.documentation | |||
</a> | |||
- | |||
<a | |||
href="https://redirect.sonarsource.com/doc/community.html" | |||
> | |||
footer.support | |||
</a> | |||
- | |||
<a | |||
href="https://redirect.sonarsource.com/doc/plugin-library.html" | |||
> | |||
footer.plugins | |||
</a> | |||
- | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/web_api" | |||
> | |||
footer.web_api | |||
</Link> | |||
- | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/about" | |||
> | |||
footer.about | |||
</Link> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should not render the only logged in information 1`] = ` | |||
<div | |||
className="page-footer page-container" | |||
id="footer" | |||
> | |||
<GlobalFooterBranding /> | |||
<div> | |||
<a | |||
href="http://www.gnu.org/licenses/lgpl-3.0.txt" | |||
> | |||
footer.licence | |||
</a> | |||
- | |||
<a | |||
href="http://www.sonarqube.org" | |||
> | |||
footer.community | |||
</a> | |||
- | |||
<a | |||
href="https://redirect.sonarsource.com/doc/home.html" | |||
> | |||
footer.documentation | |||
</a> | |||
- | |||
<a | |||
href="https://redirect.sonarsource.com/doc/community.html" | |||
> | |||
footer.support | |||
</a> | |||
- | |||
<a | |||
href="https://redirect.sonarsource.com/doc/plugin-library.html" | |||
> | |||
footer.plugins | |||
</a> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render SonarCloud footer 1`] = `<GlobalFooterSonarCloud />`; | |||
exports[`should render the only logged in information 1`] = ` | |||
<div | |||
className="page-footer page-container" | |||
id="footer" | |||
> | |||
<GlobalFooterBranding /> | |||
<div> | |||
<a | |||
href="http://www.gnu.org/licenses/lgpl-3.0.txt" | |||
> | |||
footer.licence | |||
</a> | |||
- | |||
<a | |||
href="http://www.sonarqube.org" | |||
> | |||
footer.community | |||
</a> | |||
- | |||
<a | |||
href="https://redirect.sonarsource.com/doc/home.html" | |||
> | |||
footer.documentation | |||
</a> | |||
- | |||
<a | |||
href="https://redirect.sonarsource.com/doc/community.html" | |||
> | |||
footer.support | |||
</a> | |||
- | |||
<a | |||
href="https://redirect.sonarsource.com/doc/plugin-library.html" | |||
> | |||
footer.plugins | |||
</a> | |||
- | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/web_api" | |||
> | |||
footer.web_api | |||
</Link> | |||
- | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/about" | |||
> | |||
footer.about | |||
</Link> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should show the db warning message 1`] = ` | |||
<div | |||
className="alert alert-danger" | |||
> | |||
<p | |||
className="big" | |||
id="evaluation_warning" | |||
> | |||
footer.production_database_warning | |||
</p> | |||
<p> | |||
footer.production_database_explanation | |||
</p> | |||
</div> | |||
`; |
@@ -0,0 +1,242 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should display the sq version 1`] = ` | |||
<div | |||
className="page-footer page-container" | |||
id="footer" | |||
> | |||
<GlobalFooterBranding /> | |||
<ul | |||
className="page-footer-menu" | |||
> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
footer.version_x.6.4-SNAPSHOT | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<a | |||
href="http://www.gnu.org/licenses/lgpl-3.0.txt" | |||
> | |||
footer.license | |||
</a> | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<a | |||
href="http://www.sonarqube.org" | |||
> | |||
footer.community | |||
</a> | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<a | |||
href="https://redirect.sonarsource.com/doc/home.html" | |||
> | |||
footer.documentation | |||
</a> | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<a | |||
href="https://redirect.sonarsource.com/doc/community.html" | |||
> | |||
footer.support | |||
</a> | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<a | |||
href="https://redirect.sonarsource.com/doc/plugin-library.html" | |||
> | |||
footer.plugins | |||
</a> | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/web_api" | |||
> | |||
footer.web_api | |||
</Link> | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/about" | |||
> | |||
footer.about | |||
</Link> | |||
</li> | |||
</ul> | |||
</div> | |||
`; | |||
exports[`should not render the only logged in information 1`] = ` | |||
<div | |||
className="page-footer page-container" | |||
id="footer" | |||
> | |||
<GlobalFooterBranding /> | |||
<ul | |||
className="page-footer-menu" | |||
> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<a | |||
href="http://www.gnu.org/licenses/lgpl-3.0.txt" | |||
> | |||
footer.license | |||
</a> | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<a | |||
href="http://www.sonarqube.org" | |||
> | |||
footer.community | |||
</a> | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<a | |||
href="https://redirect.sonarsource.com/doc/home.html" | |||
> | |||
footer.documentation | |||
</a> | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<a | |||
href="https://redirect.sonarsource.com/doc/community.html" | |||
> | |||
footer.support | |||
</a> | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<a | |||
href="https://redirect.sonarsource.com/doc/plugin-library.html" | |||
> | |||
footer.plugins | |||
</a> | |||
</li> | |||
</ul> | |||
</div> | |||
`; | |||
exports[`should render SonarCloud footer 1`] = `<GlobalFooterSonarCloud />`; | |||
exports[`should render the only logged in information 1`] = ` | |||
<div | |||
className="page-footer page-container" | |||
id="footer" | |||
> | |||
<GlobalFooterBranding /> | |||
<ul | |||
className="page-footer-menu" | |||
> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<a | |||
href="http://www.gnu.org/licenses/lgpl-3.0.txt" | |||
> | |||
footer.license | |||
</a> | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<a | |||
href="http://www.sonarqube.org" | |||
> | |||
footer.community | |||
</a> | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<a | |||
href="https://redirect.sonarsource.com/doc/home.html" | |||
> | |||
footer.documentation | |||
</a> | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<a | |||
href="https://redirect.sonarsource.com/doc/community.html" | |||
> | |||
footer.support | |||
</a> | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<a | |||
href="https://redirect.sonarsource.com/doc/plugin-library.html" | |||
> | |||
footer.plugins | |||
</a> | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/web_api" | |||
> | |||
footer.web_api | |||
</Link> | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/about" | |||
> | |||
footer.about | |||
</Link> | |||
</li> | |||
</ul> | |||
</div> | |||
`; | |||
exports[`should show the db warning message 1`] = ` | |||
<div | |||
className="alert alert-danger" | |||
> | |||
<p | |||
className="big" | |||
id="evaluation_warning" | |||
> | |||
footer.production_database_warning | |||
</p> | |||
<p> | |||
footer.production_database_explanation | |||
</p> | |||
</div> | |||
`; |
@@ -1,57 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<div | |||
className="page-footer page-container" | |||
id="footer" | |||
> | |||
<div> | |||
© 2008-2017, SonarCloud.io by | |||
<a | |||
href="http://www.sonarsource.com" | |||
title="SonarSource SA" | |||
> | |||
SonarSource SA | |||
</a> | |||
. All rights reserved. | |||
</div> | |||
<div> | |||
<a | |||
href="https://about.sonarcloud.io/news/" | |||
> | |||
footer.news | |||
</a> | |||
- | |||
<a | |||
href="https://about.sonarcloud.io/terms.pdf" | |||
> | |||
footer.terms | |||
</a> | |||
- | |||
<a | |||
href="https://twitter.com/sonarqube" | |||
> | |||
footer.twitter | |||
</a> | |||
- | |||
<a | |||
href="https://about.sonarcloud.io/get-started/" | |||
> | |||
footer.get_started | |||
</a> | |||
- | |||
<a | |||
href="https://about.sonarcloud.io/contact/" | |||
> | |||
footer.help | |||
</a> | |||
- | |||
<a | |||
href="https://about.sonarcloud.io/" | |||
> | |||
footer.about | |||
</a> | |||
</div> | |||
</div> | |||
`; |
@@ -0,0 +1,78 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<div | |||
className="page-footer page-container" | |||
id="footer" | |||
> | |||
<div> | |||
© 2008-2017, SonarCloud.io by | |||
<a | |||
href="http://www.sonarsource.com" | |||
title="SonarSource SA" | |||
> | |||
SonarSource SA | |||
</a> | |||
. All rights reserved. | |||
</div> | |||
<ul | |||
className="page-footer-menu" | |||
> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<a | |||
href="https://about.sonarcloud.io/news/" | |||
> | |||
footer.news | |||
</a> | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<a | |||
href="https://about.sonarcloud.io/terms.pdf" | |||
> | |||
footer.terms | |||
</a> | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<a | |||
href="https://twitter.com/sonarqube" | |||
> | |||
footer.twitter | |||
</a> | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<a | |||
href="https://about.sonarcloud.io/get-started/" | |||
> | |||
footer.get_started | |||
</a> | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<a | |||
href="https://about.sonarcloud.io/contact/" | |||
> | |||
footer.help | |||
</a> | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<a | |||
href="https://about.sonarcloud.io/" | |||
> | |||
footer.about | |||
</a> | |||
</li> | |||
</ul> | |||
</div> | |||
`; |
@@ -0,0 +1,30 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 SonarSource SA | |||
* mailto:contact 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 { CurrentUser, AppState } from '../../types'; | |||
export interface Props { | |||
currentUser: CurrentUser; | |||
onClose: () => void; | |||
onSonarCloud?: boolean; | |||
onTutorialSelect: () => void; | |||
} | |||
export default class GlobalHelp extends React.PureComponent<Props> {} |
@@ -17,8 +17,7 @@ | |||
* 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 * as React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import GlobalNavBranding from './GlobalNavBranding'; | |||
import GlobalNavMenu from './GlobalNavMenu'; | |||
@@ -28,42 +27,41 @@ import GlobalNavPlus from './GlobalNavPlus'; | |||
import Search from '../../search/Search'; | |||
import GlobalHelp from '../../help/GlobalHelp'; | |||
import * as theme from '../../../theme'; | |||
import { isLoggedIn } from '../../../types'; | |||
import { isLoggedIn, CurrentUser, AppState } from '../../../types'; | |||
import OnboardingModal from '../../../../apps/tutorials/onboarding/OnboardingModal'; | |||
import NavBar from '../../../../components/nav/NavBar'; | |||
import Tooltip from '../../../../components/controls/Tooltip'; | |||
import HelpIcon from '../../../../components/icons-components/HelpIcon'; | |||
import OnboardingModal from '../../../../apps/tutorials/onboarding/OnboardingModal'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
import { getCurrentUser, getAppState, getGlobalSettingValue } from '../../../../store/rootReducer'; | |||
import { skipOnboarding } from '../../../../store/users/actions'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
import './GlobalNav.css'; | |||
/*:: | |||
type Props = { | |||
appState: { organizationsEnabled: boolean }, | |||
currentUser: { isLoggedIn: boolean, showOnboardingTutorial: boolean }, | |||
location: { pathname: string }, | |||
skipOnboarding: () => void, | |||
onSonarCloud: boolean | |||
}; | |||
*/ | |||
interface StateProps { | |||
appState: AppState; | |||
currentUser: CurrentUser; | |||
onSonarCloud: boolean; | |||
} | |||
/*:: | |||
type State = { | |||
helpOpen: boolean, | |||
onboardingTutorialOpen: boolean, | |||
onboardingTutorialTooltip: boolean | |||
}; | |||
*/ | |||
class GlobalNav extends React.PureComponent { | |||
/*:: interval: ?number; */ | |||
/*:: props: Props; */ | |||
state /*: State */ = { | |||
helpOpen: false, | |||
onboardingTutorialOpen: false, | |||
onboardingTutorialTooltip: false | |||
}; | |||
interface DispatchProps { | |||
skipOnboarding: () => void; | |||
} | |||
interface Props extends StateProps, DispatchProps { | |||
closeOnboardingTutorial: () => void; | |||
isOnboardingTutorialOpen: boolean; | |||
location: { pathname: string }; | |||
openOnboardingTutorial: () => void; | |||
} | |||
interface State { | |||
helpOpen: boolean; | |||
onboardingTutorialTooltip: boolean; | |||
} | |||
class GlobalNav extends React.PureComponent<Props, State> { | |||
interval?: number; | |||
state: State = { helpOpen: false, onboardingTutorialTooltip: false }; | |||
componentDidMount() { | |||
window.addEventListener('keypress', this.onKeyPress); | |||
@@ -79,9 +77,9 @@ class GlobalNav extends React.PureComponent { | |||
window.removeEventListener('keypress', this.onKeyPress); | |||
} | |||
onKeyPress = e => { | |||
const tagName = e.target.tagName; | |||
const code = e.keyCode || e.which; | |||
onKeyPress = (event: KeyboardEvent) => { | |||
const { tagName } = event.target as HTMLElement; | |||
const code = event.keyCode || event.which; | |||
const isInput = tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA'; | |||
const isTriggerKey = code === 63; | |||
if (!isInput && isTriggerKey) { | |||
@@ -89,7 +87,7 @@ class GlobalNav extends React.PureComponent { | |||
} | |||
}; | |||
handleHelpClick = event => { | |||
handleHelpClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { | |||
event.preventDefault(); | |||
this.openHelp(); | |||
}; | |||
@@ -98,12 +96,16 @@ class GlobalNav extends React.PureComponent { | |||
closeHelp = () => this.setState({ helpOpen: false }); | |||
openOnboardingTutorial = () => this.setState({ helpOpen: false, onboardingTutorialOpen: true }); | |||
openOnboardingTutorial = () => { | |||
this.setState({ helpOpen: false }); | |||
this.props.openOnboardingTutorial(); | |||
}; | |||
closeOnboardingTutorial = () => { | |||
this.setState({ onboardingTutorialOpen: false, onboardingTutorialTooltip: true }); | |||
this.setState({ onboardingTutorialTooltip: true }); | |||
this.props.skipOnboarding(); | |||
this.interval = setInterval(() => { | |||
this.props.closeOnboardingTutorial(); | |||
this.interval = window.setInterval(() => { | |||
this.setState({ onboardingTutorialTooltip: false }); | |||
}, 3000); | |||
}; | |||
@@ -148,7 +150,7 @@ class GlobalNav extends React.PureComponent { | |||
/> | |||
)} | |||
{this.state.onboardingTutorialOpen && ( | |||
{this.props.isOnboardingTutorialOpen && ( | |||
<OnboardingModal onFinish={this.closeOnboardingTutorial} /> | |||
)} | |||
</NavBar> | |||
@@ -156,7 +158,7 @@ class GlobalNav extends React.PureComponent { | |||
} | |||
} | |||
const mapStateToProps = state => { | |||
const mapStateToProps = (state: any): StateProps => { | |||
const sonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled'); | |||
return { | |||
@@ -166,6 +168,6 @@ const mapStateToProps = state => { | |||
}; | |||
}; | |||
const mapDispatchToProps = { skipOnboarding }; | |||
const mapDispatchToProps: DispatchProps = { skipOnboarding }; | |||
export default connect(mapStateToProps, mapDispatchToProps)(GlobalNav); |
@@ -17,31 +17,23 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import PropTypes from 'prop-types'; | |||
import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import { isLoggedIn } from '../../../../app/types'; | |||
import { isLoggedIn, CurrentUser, AppState, Extension } from '../../../../app/types'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
import { getQualityGatesUrl } from '../../../../helpers/urls'; | |||
import { getQualityGatesUrl, getBaseUrl } from '../../../../helpers/urls'; | |||
import { isMySet } from '../../../../apps/issues/utils'; | |||
export default class GlobalNavMenu extends React.PureComponent { | |||
static propTypes = { | |||
appState: PropTypes.object.isRequired, | |||
currentUser: PropTypes.object.isRequired, | |||
location: PropTypes.shape({ | |||
pathname: PropTypes.string.isRequired | |||
}).isRequired, | |||
onSonarCloud: PropTypes.bool | |||
}; | |||
static defaultProps = { | |||
globalDashboards: [], | |||
globalPages: [] | |||
}; | |||
interface Props { | |||
appState: AppState; | |||
currentUser: CurrentUser; | |||
location: { pathname: string }; | |||
onSonarCloud: boolean; | |||
} | |||
activeLink(url) { | |||
return window.location.pathname.indexOf(window.baseUrl + url) === 0 ? 'active' : null; | |||
export default class GlobalNavMenu extends React.PureComponent<Props> { | |||
activeLink(url: string) { | |||
return window.location.pathname.indexOf(getBaseUrl() + url) === 0 ? 'active' : undefined; | |||
} | |||
renderProjects() { | |||
@@ -144,7 +136,7 @@ export default class GlobalNavMenu extends React.PureComponent { | |||
); | |||
} | |||
renderGlobalPageLink = ({ key, name }) => { | |||
renderGlobalPageLink = ({ key, name }: Extension) => { | |||
return ( | |||
<li key={key}> | |||
<Link to={`/extension/${key}`}>{name}</Link> | |||
@@ -153,7 +145,7 @@ export default class GlobalNavMenu extends React.PureComponent { | |||
}; | |||
renderMore() { | |||
const { globalPages } = this.props.appState; | |||
const { globalPages = [] } = this.props.appState; | |||
const withoutPortfolios = globalPages.filter(page => page.key !== 'governance/portfolios'); | |||
if (withoutPortfolios.length === 0) { | |||
return null; |
@@ -25,10 +25,10 @@ import { Link } from 'react-router'; | |||
import * as theme from '../../../theme'; | |||
import { CurrentUser, LoggedInUser, isLoggedIn, Organization } from '../../../types'; | |||
import Avatar from '../../../../components/ui/Avatar'; | |||
import OrganizationLink from '../../../../components/ui/OrganizationLink'; | |||
import OrganizationListItem from '../../../../components/ui/OrganizationListItem'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
import { getBaseUrl } from '../../../../helpers/urls'; | |||
import OrganizationAvatar from '../../../../components/common/OrganizationAvatar'; | |||
import Dropdown from '../../../../components/controls/Dropdown'; | |||
interface Props { | |||
appState: { organizationsEnabled: boolean }; | |||
@@ -36,32 +36,11 @@ interface Props { | |||
organizations: Organization[]; | |||
} | |||
interface State { | |||
open: boolean; | |||
} | |||
export default class GlobalNavUser extends React.PureComponent<Props, State> { | |||
node?: HTMLElement | null; | |||
export default class GlobalNavUser extends React.PureComponent<Props> { | |||
static contextTypes = { | |||
router: PropTypes.object | |||
}; | |||
constructor(props: Props) { | |||
super(props); | |||
this.state = { open: false }; | |||
} | |||
componentWillUnmount() { | |||
window.removeEventListener('click', this.handleClickOutside); | |||
} | |||
handleClickOutside = (event: MouseEvent) => { | |||
if (!this.node || !this.node.contains(event.target as Node)) { | |||
this.closeDropdown(); | |||
} | |||
}; | |||
handleLogin = (event: React.SyntheticEvent<HTMLAnchorElement>) => { | |||
event.preventDefault(); | |||
const shouldReturnToCurrentPage = window.location.pathname !== `${getBaseUrl()}/about`; | |||
@@ -76,98 +55,61 @@ export default class GlobalNavUser extends React.PureComponent<Props, State> { | |||
handleLogout = (event: React.SyntheticEvent<HTMLAnchorElement>) => { | |||
event.preventDefault(); | |||
this.closeDropdown(); | |||
this.context.router.push('/sessions/logout'); | |||
}; | |||
toggleDropdown = (event: React.SyntheticEvent<HTMLAnchorElement>) => { | |||
event.preventDefault(); | |||
if (this.state.open) { | |||
this.closeDropdown(); | |||
} else { | |||
this.openDropdown(); | |||
} | |||
}; | |||
openDropdown = () => { | |||
window.addEventListener('click', this.handleClickOutside, true); | |||
this.setState({ open: true }); | |||
}; | |||
closeDropdown = () => { | |||
window.removeEventListener('click', this.handleClickOutside); | |||
this.setState({ open: false }); | |||
}; | |||
renderAuthenticated() { | |||
const { organizations } = this.props; | |||
const currentUser = this.props.currentUser as LoggedInUser; | |||
const hasOrganizations = this.props.appState.organizationsEnabled && organizations.length > 0; | |||
return ( | |||
<li | |||
className={classNames('dropdown js-user-authenticated', { open: this.state.open })} | |||
ref={node => (this.node = node)}> | |||
<a className="dropdown-toggle navbar-avatar" href="#" onClick={this.toggleDropdown}> | |||
<Avatar | |||
hash={currentUser.avatar} | |||
name={currentUser.name} | |||
size={theme.globalNavContentHeightRaw} | |||
/> | |||
</a> | |||
{this.state.open && ( | |||
<ul className="dropdown-menu dropdown-menu-right"> | |||
<li className="dropdown-item"> | |||
<div className="text-ellipsis text-muted" title={currentUser.name}> | |||
<strong>{currentUser.name}</strong> | |||
</div> | |||
{currentUser.email != null && ( | |||
<div | |||
className="little-spacer-top text-ellipsis text-muted" | |||
title={currentUser.email}> | |||
{currentUser.email} | |||
<Dropdown> | |||
{({ onToggleClick, open }) => ( | |||
<li className={classNames('dropdown', 'js-user-authenticated', { open })}> | |||
<a className="dropdown-toggle navbar-avatar" href="#" onClick={onToggleClick}> | |||
<Avatar | |||
hash={currentUser.avatar} | |||
name={currentUser.name} | |||
size={theme.globalNavContentHeightRaw} | |||
/> | |||
</a> | |||
<ul className="dropdown-menu dropdown-menu-right"> | |||
<li className="dropdown-item"> | |||
<div className="text-ellipsis text-muted" title={currentUser.name}> | |||
<strong>{currentUser.name}</strong> | |||
</div> | |||
)} | |||
</li> | |||
<li className="divider" /> | |||
<li> | |||
<Link to="/account" onClick={this.closeDropdown}> | |||
{translate('my_account.page')} | |||
</Link> | |||
</li> | |||
{hasOrganizations && <li role="separator" className="divider" />} | |||
{hasOrganizations && ( | |||
{currentUser.email != null && ( | |||
<div | |||
className="little-spacer-top text-ellipsis text-muted" | |||
title={currentUser.email}> | |||
{currentUser.email} | |||
</div> | |||
)} | |||
</li> | |||
<li className="divider" /> | |||
<li> | |||
<Link to="/account/organizations" onClick={this.closeDropdown}> | |||
{translate('my_organizations')} | |||
</Link> | |||
<Link to="/account">{translate('my_account.page')}</Link> | |||
</li> | |||
)} | |||
{hasOrganizations && | |||
sortBy(organizations, org => org.name.toLowerCase()).map(organization => ( | |||
<li key={organization.key}> | |||
<OrganizationLink | |||
className="dropdown-item-flex" | |||
organization={organization} | |||
onClick={this.closeDropdown}> | |||
<div> | |||
<OrganizationAvatar organization={organization} small={true} /> | |||
<span className="spacer-left">{organization.name}</span> | |||
</div> | |||
{organization.isAdmin && ( | |||
<span className="outline-badge spacer-left">{translate('admin')}</span> | |||
)} | |||
</OrganizationLink> | |||
{hasOrganizations && <li role="separator" className="divider" />} | |||
{hasOrganizations && ( | |||
<li> | |||
<Link to="/account/organizations">{translate('my_organizations')}</Link> | |||
</li> | |||
))} | |||
{hasOrganizations && <li role="separator" className="divider" />} | |||
<li> | |||
<a onClick={this.handleLogout} href="#"> | |||
{translate('layout.logout')} | |||
</a> | |||
</li> | |||
</ul> | |||
)} | |||
{hasOrganizations && | |||
sortBy(organizations, org => org.name.toLowerCase()).map(organization => ( | |||
<OrganizationListItem key={organization.key} organization={organization} /> | |||
))} | |||
{hasOrganizations && <li role="separator" className="divider" />} | |||
<li> | |||
<a onClick={this.handleLogout} href="#"> | |||
{translate('layout.logout')} | |||
</a> | |||
</li> | |||
</ul> | |||
</li> | |||
)} | |||
</li> | |||
</Dropdown> | |||
); | |||
} | |||
@@ -43,7 +43,7 @@ it('should render the right interface for logged in user', () => { | |||
<GlobalNavUser appState={appState} currentUser={currentUser} organizations={[]} /> | |||
); | |||
wrapper.setState({ open: true }); | |||
expect(wrapper).toMatchSnapshot(); | |||
expect(wrapper.find('Dropdown').dive()).toMatchSnapshot(); | |||
}); | |||
it('should render user organizations', () => { | |||
@@ -51,7 +51,7 @@ it('should render user organizations', () => { | |||
<GlobalNavUser appState={appState} currentUser={currentUser} organizations={organizations} /> | |||
); | |||
wrapper.setState({ open: true }); | |||
expect(wrapper).toMatchSnapshot(); | |||
expect(wrapper.find('Dropdown').dive()).toMatchSnapshot(); | |||
}); | |||
it('should not render user organizations when they are not activated', () => { | |||
@@ -63,5 +63,5 @@ it('should not render user organizations when they are not activated', () => { | |||
/> | |||
); | |||
wrapper.setState({ open: true }); | |||
expect(wrapper).toMatchSnapshot(); | |||
expect(wrapper.find('Dropdown').dive()).toMatchSnapshot(); | |||
}); |
@@ -32,7 +32,6 @@ exports[`should show administration menu if the user has the rights 1`] = ` | |||
</li> | |||
<li> | |||
<Link | |||
className={null} | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/coding_rules" | |||
@@ -109,7 +108,6 @@ exports[`should work with extensions 1`] = ` | |||
</li> | |||
<li> | |||
<Link | |||
className={null} | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/coding_rules" |
@@ -2,7 +2,7 @@ | |||
exports[`should not render user organizations when they are not activated 1`] = ` | |||
<li | |||
className="dropdown js-user-authenticated open" | |||
className="dropdown js-user-authenticated" | |||
> | |||
<a | |||
className="dropdown-toggle navbar-avatar" | |||
@@ -41,7 +41,6 @@ exports[`should not render user organizations when they are not activated 1`] = | |||
/> | |||
<li> | |||
<Link | |||
onClick={[Function]} | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/account" | |||
@@ -75,7 +74,7 @@ exports[`should render the right interface for anonymous user 1`] = ` | |||
exports[`should render the right interface for logged in user 1`] = ` | |||
<li | |||
className="dropdown js-user-authenticated open" | |||
className="dropdown js-user-authenticated" | |||
> | |||
<a | |||
className="dropdown-toggle navbar-avatar" | |||
@@ -114,7 +113,6 @@ exports[`should render the right interface for logged in user 1`] = ` | |||
/> | |||
<li> | |||
<Link | |||
onClick={[Function]} | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/account" | |||
@@ -136,7 +134,7 @@ exports[`should render the right interface for logged in user 1`] = ` | |||
exports[`should render user organizations 1`] = ` | |||
<li | |||
className="dropdown js-user-authenticated open" | |||
className="dropdown js-user-authenticated" | |||
> | |||
<a | |||
className="dropdown-toggle navbar-avatar" | |||
@@ -175,7 +173,6 @@ exports[`should render user organizations 1`] = ` | |||
/> | |||
<li> | |||
<Link | |||
onClick={[Function]} | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/account" | |||
@@ -189,7 +186,6 @@ exports[`should render user organizations 1`] = ` | |||
/> | |||
<li> | |||
<Link | |||
onClick={[Function]} | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/account/organizations" | |||
@@ -197,105 +193,36 @@ exports[`should render user organizations 1`] = ` | |||
my_organizations | |||
</Link> | |||
</li> | |||
<li | |||
<OrganizationListItem | |||
key="bar" | |||
> | |||
<OrganizationLink | |||
className="dropdown-item-flex" | |||
onClick={[Function]} | |||
organization={ | |||
Object { | |||
"key": "bar", | |||
"name": "bar", | |||
"projectVisibility": "public", | |||
} | |||
organization={ | |||
Object { | |||
"key": "bar", | |||
"name": "bar", | |||
"projectVisibility": "public", | |||
} | |||
> | |||
<div> | |||
<OrganizationAvatar | |||
organization={ | |||
Object { | |||
"key": "bar", | |||
"name": "bar", | |||
"projectVisibility": "public", | |||
} | |||
} | |||
small={true} | |||
/> | |||
<span | |||
className="spacer-left" | |||
> | |||
bar | |||
</span> | |||
</div> | |||
</OrganizationLink> | |||
</li> | |||
<li | |||
} | |||
/> | |||
<OrganizationListItem | |||
key="foo" | |||
> | |||
<OrganizationLink | |||
className="dropdown-item-flex" | |||
onClick={[Function]} | |||
organization={ | |||
Object { | |||
"key": "foo", | |||
"name": "Foo", | |||
"projectVisibility": "public", | |||
} | |||
organization={ | |||
Object { | |||
"key": "foo", | |||
"name": "Foo", | |||
"projectVisibility": "public", | |||
} | |||
> | |||
<div> | |||
<OrganizationAvatar | |||
organization={ | |||
Object { | |||
"key": "foo", | |||
"name": "Foo", | |||
"projectVisibility": "public", | |||
} | |||
} | |||
small={true} | |||
/> | |||
<span | |||
className="spacer-left" | |||
> | |||
Foo | |||
</span> | |||
</div> | |||
</OrganizationLink> | |||
</li> | |||
<li | |||
} | |||
/> | |||
<OrganizationListItem | |||
key="myorg" | |||
> | |||
<OrganizationLink | |||
className="dropdown-item-flex" | |||
onClick={[Function]} | |||
organization={ | |||
Object { | |||
"key": "myorg", | |||
"name": "MyOrg", | |||
"projectVisibility": "public", | |||
} | |||
organization={ | |||
Object { | |||
"key": "myorg", | |||
"name": "MyOrg", | |||
"projectVisibility": "public", | |||
} | |||
> | |||
<div> | |||
<OrganizationAvatar | |||
organization={ | |||
Object { | |||
"key": "myorg", | |||
"name": "MyOrg", | |||
"projectVisibility": "public", | |||
} | |||
} | |||
small={true} | |||
/> | |||
<span | |||
className="spacer-left" | |||
> | |||
MyOrg | |||
</span> | |||
</div> | |||
</OrganizationLink> | |||
</li> | |||
} | |||
/> | |||
<li | |||
className="divider" | |||
role="separator" |
@@ -0,0 +1,28 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 SonarSource SA | |||
* mailto:contact 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 { CurrentUser, AppState } from '../../types'; | |||
export interface Props { | |||
appState: AppState; | |||
currentUser: CurrentUser; | |||
} | |||
export default class Search extends React.PureComponent<Props> {} |
@@ -140,18 +140,10 @@ | |||
} | |||
.page-footer a:hover, | |||
.page-footer a:active, | |||
.page-footer a:focus { | |||
color: var(--blue); | |||
} | |||
.page-footer a:hover { | |||
border-bottom-color: var(--lightBlue); | |||
} | |||
.page-footer a:active, | |||
.page-footer a:focus { | |||
border-bottom-color: var(--lightBlue); | |||
color: var(--blue); | |||
} | |||
.page-footer-with-sidebar { | |||
@@ -162,6 +154,16 @@ | |||
max-width: 980px; | |||
} | |||
.page-footer-menu-item { | |||
display: inline-block; | |||
} | |||
.page-footer-menu-item + .page-footer-menu-item::before { | |||
content: '-'; | |||
padding: 0 calc(0.5 * var(--gridSize)); | |||
user-select: none; | |||
} | |||
.page-with-sidebar { | |||
display: flex; | |||
} |
@@ -118,7 +118,7 @@ input[type='button'] { | |||
display: inline-block; | |||
vertical-align: baseline; | |||
height: var(--controlHeight); | |||
line-height: 22px; | |||
line-height: calc(var(--controlHeight) - 2px); | |||
padding: 0 12px; | |||
border: 1px solid var(--darkBlue); | |||
border-radius: 2px; | |||
@@ -184,6 +184,10 @@ input[type='button']:disabled:focus { | |||
box-shadow: none; | |||
} | |||
.button svg { | |||
padding-top: calc((var(--controlHeight) - 16px - 2px) / 2); | |||
} | |||
.button-red, | |||
input[type='submit'].button-red { | |||
border-color: var(--red); |
@@ -150,3 +150,13 @@ export interface LoggedInUser extends CurrentUser { | |||
export function isLoggedIn(user: CurrentUser): user is LoggedInUser { | |||
return user.isLoggedIn; | |||
} | |||
export interface AppState { | |||
adminPages?: Extension[]; | |||
authenticationError: boolean; | |||
authorizationError: boolean; | |||
canAdmin?: boolean; | |||
globalPages?: Extension[]; | |||
organizationsEnabled: boolean; | |||
qualifiers: string[]; | |||
} |
@@ -17,61 +17,44 @@ | |||
* 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 * as React from 'react'; | |||
import OrganizationAvatar from '../../../components/common/OrganizationAvatar'; | |||
import OrganizationLink from '../../../components/ui/OrganizationLink'; | |||
/*:: import type { Organization } from '../../../store/organizations/duck'; */ | |||
import { translate } from '../../../helpers/l10n'; | |||
import { Organization } from '../../../app/types'; | |||
/*:: | |||
type Props = { | |||
organization: Organization | |||
}; | |||
*/ | |||
export default function OrganizationCard(props /*: Props */) { | |||
const { organization } = props; | |||
interface Props { | |||
organization: Organization; | |||
} | |||
export default function OrganizationCard({ organization }: Props) { | |||
return ( | |||
<div className="account-project-card clearfix"> | |||
<aside className="account-project-side"> | |||
{!!organization.avatar && ( | |||
<div className="spacer-bottom"> | |||
<img src={organization.avatar} height={30} alt={organization.name} /> | |||
</div> | |||
)} | |||
{!!organization.url && ( | |||
<div className="text-limited text-top spacer-bottom"> | |||
<a className="small" href={organization.url} title={organization.url} rel="nofollow"> | |||
{organization.url} | |||
</a> | |||
</div> | |||
)} | |||
<aside className="account-project-side note"> | |||
<strong>{translate('organization.key')}:</strong> {organization.key} | |||
</aside> | |||
<h3 className="account-project-name"> | |||
<OrganizationLink organization={organization}>{organization.name}</OrganizationLink> | |||
<OrganizationAvatar organization={organization} /> | |||
<OrganizationLink className="spacer-left text-middle" organization={organization}> | |||
{organization.name} | |||
</OrganizationLink> | |||
{organization.isAdmin && ( | |||
<span className="outline-badge spacer-left">{translate('admin')}</span> | |||
)} | |||
</h3> | |||
{!!organization.description && ( | |||
<div className="account-project-description">{organization.description}</div> | |||
<div className="markdown spacer-top">{organization.description}</div> | |||
)} | |||
<div className="account-project-key"> | |||
<span className="little-spacer-right"> | |||
{translate('key')} | |||
{':'} | |||
</span> | |||
<input | |||
onClick={event => event.currentTarget.select()} | |||
readOnly={true} | |||
type="text" | |||
value={organization.key} | |||
/> | |||
</div> | |||
{!!organization.url && ( | |||
<div className="markdown spacer-top"> | |||
<a href={organization.url} title={organization.url} rel="nofollow"> | |||
{organization.url} | |||
</a> | |||
</div> | |||
)} | |||
</div> | |||
); | |||
} |
@@ -17,22 +17,19 @@ | |||
* 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 * as React from 'react'; | |||
import { sortBy } from 'lodash'; | |||
import OrganizationCard from './OrganizationCard'; | |||
/*:: import type { Organization } from '../../../store/organizations/duck'; */ | |||
import { Organization } from '../../../app/types'; | |||
/*:: | |||
type Props = { | |||
organizations: Array<Organization> | |||
}; | |||
*/ | |||
interface Props { | |||
organizations: Organization[]; | |||
} | |||
export default function OrganizationsList(props /*: Props */) { | |||
export default function OrganizationsList({ organizations }: Props) { | |||
return ( | |||
<ul className="account-projects-list"> | |||
{sortBy(props.organizations, organization => organization.name.toLocaleLowerCase()).map( | |||
{sortBy(organizations, organization => organization.name.toLocaleLowerCase()).map( | |||
organization => ( | |||
<li key={organization.key}> | |||
<OrganizationCard organization={organization} /> |
@@ -56,7 +56,7 @@ export default function OrganizationNavigationAdministration({ location, organiz | |||
href="#" | |||
onClick={onToggleClick}> | |||
{translate('layout.settings')} | |||
<DropdownIcon /> | |||
<DropdownIcon className="little-spacer-left" /> | |||
</a> | |||
<ul className="dropdown-menu"> | |||
{extensions.map(extension => ( |
@@ -24,8 +24,7 @@ import { Organization } from '../../../app/types'; | |||
import OrganizationAvatar from '../../../components/common/OrganizationAvatar'; | |||
import Dropdown from '../../../components/controls/Dropdown'; | |||
import DropdownIcon from '../../../components/icons-components/DropdownIcon'; | |||
import OrganizationLink from '../../../components/ui/OrganizationLink'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import OrganizationListItem from '../../../components/ui/OrganizationListItem'; | |||
interface Props { | |||
organization: Organization; | |||
@@ -49,17 +48,7 @@ export default function OrganizationNavigationHeader({ organization, organizatio | |||
</a> | |||
<ul className="dropdown-menu"> | |||
{sortBy(other, org => org.name.toLowerCase()).map(organization => ( | |||
<li key={organization.key}> | |||
<OrganizationLink className="dropdown-item-flex" organization={organization}> | |||
<div> | |||
<OrganizationAvatar organization={organization} small={true} /> | |||
<span className="spacer-left">{organization.name}</span> | |||
</div> | |||
{organization.isAdmin && ( | |||
<span className="outline-badge spacer-left">{translate('admin')}</span> | |||
)} | |||
</OrganizationLink> | |||
</li> | |||
<OrganizationListItem key={organization.key} organization={organization} /> | |||
))} | |||
</ul> | |||
</div> |
@@ -11,7 +11,9 @@ exports[`renders 1`] = ` | |||
onClick={[Function]} | |||
> | |||
layout.settings | |||
<DropdownIcon /> | |||
<DropdownIcon | |||
className="little-spacer-left" | |||
/> | |||
</a> | |||
<ul | |||
className="dropdown-menu" |
@@ -42,79 +42,28 @@ exports[`renders dropdown 1`] = ` | |||
<ul | |||
className="dropdown-menu" | |||
> | |||
<li | |||
<OrganizationListItem | |||
key="org1" | |||
> | |||
<OrganizationLink | |||
className="dropdown-item-flex" | |||
organization={ | |||
Object { | |||
"isAdmin": true, | |||
"key": "org1", | |||
"name": "org1", | |||
"projectVisibility": "public", | |||
} | |||
organization={ | |||
Object { | |||
"isAdmin": true, | |||
"key": "org1", | |||
"name": "org1", | |||
"projectVisibility": "public", | |||
} | |||
> | |||
<div> | |||
<OrganizationAvatar | |||
organization={ | |||
Object { | |||
"isAdmin": true, | |||
"key": "org1", | |||
"name": "org1", | |||
"projectVisibility": "public", | |||
} | |||
} | |||
small={true} | |||
/> | |||
<span | |||
className="spacer-left" | |||
> | |||
org1 | |||
</span> | |||
</div> | |||
<span | |||
className="outline-badge spacer-left" | |||
> | |||
admin | |||
</span> | |||
</OrganizationLink> | |||
</li> | |||
<li | |||
} | |||
/> | |||
<OrganizationListItem | |||
key="org2" | |||
> | |||
<OrganizationLink | |||
className="dropdown-item-flex" | |||
organization={ | |||
Object { | |||
"isAdmin": false, | |||
"key": "org2", | |||
"name": "org2", | |||
"projectVisibility": "public", | |||
} | |||
organization={ | |||
Object { | |||
"isAdmin": false, | |||
"key": "org2", | |||
"name": "org2", | |||
"projectVisibility": "public", | |||
} | |||
> | |||
<div> | |||
<OrganizationAvatar | |||
organization={ | |||
Object { | |||
"isAdmin": false, | |||
"key": "org2", | |||
"name": "org2", | |||
"projectVisibility": "public", | |||
} | |||
} | |||
small={true} | |||
/> | |||
<span | |||
className="spacer-left" | |||
> | |||
org2 | |||
</span> | |||
</div> | |||
</OrganizationLink> | |||
</li> | |||
} | |||
/> | |||
</ul> | |||
</div> | |||
`; |
@@ -19,22 +19,90 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import * as classNames from 'classnames'; | |||
import { connect } from 'react-redux'; | |||
import * as PropTypes from 'prop-types'; | |||
import { sortBy } from 'lodash'; | |||
import { Organization } from '../../../app/types'; | |||
import DropdownIcon from '../../../components/icons-components/DropdownIcon'; | |||
import Dropdown from '../../../components/controls/Dropdown'; | |||
import { getMyOrganizations } from '../../../store/rootReducer'; | |||
import OrganizationListItem from '../../../components/ui/OrganizationListItem'; | |||
import { translate } from '../../../helpers/l10n'; | |||
interface Props { | |||
interface StateProps { | |||
organizations: Organization[]; | |||
} | |||
interface Props extends StateProps { | |||
onSonarCloud: boolean; | |||
} | |||
export default function NoFavoriteProjects({ onSonarCloud }: Props) { | |||
return ( | |||
<div className="projects-empty-list"> | |||
<h3>{translate('projects.no_favorite_projects')}</h3> | |||
<p className="big-spacer-top">{translate('projects.no_favorite_projects.engagement')}</p> | |||
<p className="big-spacer-top"> | |||
<Link to={onSonarCloud ? '/explore/projects' : '/projects/all'} className="button"> | |||
{translate('projects.explore_projects')} | |||
</Link> | |||
</p> | |||
</div> | |||
); | |||
export class NoFavoriteProjects extends React.PureComponent<Props> { | |||
static contextTypes = { | |||
openOnboardingTutorial: PropTypes.func | |||
}; | |||
onAnalyzeProjectClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { | |||
event.preventDefault(); | |||
event.currentTarget.blur(); | |||
this.context.openOnboardingTutorial(); | |||
}; | |||
render() { | |||
const { onSonarCloud, organizations } = this.props; | |||
return ( | |||
<div className="projects-empty-list"> | |||
<h3>{translate('projects.no_favorite_projects')}</h3> | |||
{onSonarCloud ? ( | |||
<div className="spacer-top"> | |||
<p>{translate('projects.no_favorite_projects.how_to_add_projects')}</p> | |||
<div className="huge-spacer-top"> | |||
<a className="button" href="#" onClick={this.onAnalyzeProjectClick}> | |||
{translate('my_account.analyze_new_project')} | |||
</a> | |||
<Dropdown> | |||
{({ onToggleClick, open }) => ( | |||
<div | |||
className={classNames('display-inline-block', 'big-spacer-left', 'dropdown', { | |||
open | |||
})}> | |||
<a className="button" href="#" onClick={onToggleClick}> | |||
{translate('projects.no_favorite_projects.favorite_projects_from_orgs')} | |||
<DropdownIcon className="little-spacer-left" /> | |||
</a> | |||
<ul className="dropdown-menu"> | |||
{sortBy(organizations, org => org.name.toLowerCase()).map(organization => ( | |||
<OrganizationListItem key={organization.key} organization={organization} /> | |||
))} | |||
</ul> | |||
</div> | |||
)} | |||
</Dropdown> | |||
<Link className="button big-spacer-left" to="/explore/projects"> | |||
{translate('projects.no_favorite_projects.favorite_public_projects')} | |||
</Link> | |||
</div> | |||
</div> | |||
) : ( | |||
<div> | |||
<p className="big-spacer-top"> | |||
{translate('projects.no_favorite_projects.engagement')} | |||
</p> | |||
<p className="big-spacer-top"> | |||
<Link to="/projects/all" className="button"> | |||
{translate('projects.explore_projects')} | |||
</Link> | |||
</p> | |||
</div> | |||
)} | |||
</div> | |||
); | |||
} | |||
} | |||
const mapStateToProps = (state: any): StateProps => ({ | |||
organizations: getMyOrganizations(state) | |||
}); | |||
export default connect(mapStateToProps)(NoFavoriteProjects); |
@@ -19,8 +19,19 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import NoFavoriteProjects from '../NoFavoriteProjects'; | |||
import { NoFavoriteProjects } from '../NoFavoriteProjects'; | |||
import { Visibility } from '../../../../app/types'; | |||
it('renders', () => { | |||
expect(shallow(<NoFavoriteProjects onSonarCloud={false} />)).toMatchSnapshot(); | |||
expect(shallow(<NoFavoriteProjects onSonarCloud={false} organizations={[]} />)).toMatchSnapshot(); | |||
}); | |||
it('renders for SonarCloud', () => { | |||
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(); | |||
}); |
@@ -7,22 +7,61 @@ exports[`renders 1`] = ` | |||
<h3> | |||
projects.no_favorite_projects | |||
</h3> | |||
<p | |||
className="big-spacer-top" | |||
> | |||
projects.no_favorite_projects.engagement | |||
</p> | |||
<p | |||
className="big-spacer-top" | |||
<div> | |||
<p | |||
className="big-spacer-top" | |||
> | |||
projects.no_favorite_projects.engagement | |||
</p> | |||
<p | |||
className="big-spacer-top" | |||
> | |||
<Link | |||
className="button" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/projects/all" | |||
> | |||
projects.explore_projects | |||
</Link> | |||
</p> | |||
</div> | |||
</div> | |||
`; | |||
exports[`renders for SonarCloud 1`] = ` | |||
<div | |||
className="projects-empty-list" | |||
> | |||
<h3> | |||
projects.no_favorite_projects | |||
</h3> | |||
<div | |||
className="spacer-top" | |||
> | |||
<Link | |||
className="button" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/projects/all" | |||
<p> | |||
projects.no_favorite_projects.how_to_add_projects | |||
</p> | |||
<div | |||
className="huge-spacer-top" | |||
> | |||
projects.explore_projects | |||
</Link> | |||
</p> | |||
<a | |||
className="button" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
my_account.analyze_new_project | |||
</a> | |||
<Dropdown /> | |||
<Link | |||
className="button big-spacer-left" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/explore/projects" | |||
> | |||
projects.no_favorite_projects.favorite_public_projects | |||
</Link> | |||
</div> | |||
</div> | |||
</div> | |||
`; |
@@ -47,6 +47,6 @@ exports[`renders different types of "no projects" 3`] = ` | |||
<div | |||
className="projects-list" | |||
> | |||
<NoFavoriteProjects /> | |||
<Connect(NoFavoriteProjects) /> | |||
</div> | |||
`; |
@@ -267,3 +267,8 @@ | |||
margin-left: -250px; | |||
text-align: center; | |||
} | |||
.projects-empty-list { | |||
padding: calc(4 * var(--gridSize)) 0; | |||
text-align: center; | |||
} |
@@ -0,0 +1,26 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 SonarSource SA | |||
* mailto:contact 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'; | |||
export interface Props { | |||
onFinish: () => void; | |||
} | |||
export default class OnboardingModal extends React.PureComponent<Props> {} |
@@ -24,13 +24,13 @@ export default function DropdownIcon({ className, fill = 'currentColor', size = | |||
return ( | |||
<svg | |||
className={className} | |||
width={size} | |||
width={size / 16 * 7} | |||
height={size} | |||
viewBox="0 0 16 16" | |||
viewBox="0 0 7 16" | |||
version="1.1" | |||
xmlnsXlink="http://www.w3.org/1999/xlink" | |||
xmlSpace="preserve"> | |||
<g transform="matrix(0.0273438,0,0,0.0273438,4.5,2.65625)"> | |||
<g transform="matrix(0.0273438,0,0,0.0273438,-6.4e-06,2.65625)"> | |||
<path | |||
style={{ fill }} | |||
d="M256,176C256,180.333 254.417,184.083 251.25,187.25L139.25,299.25C136.083,302.417 132.333,304 128,304C123.667,304 119.917,302.417 116.75,299.25L4.75,187.25C1.583,184.083 0,180.333 0,176C0,171.667 1.583,167.917 4.75,164.75C7.917,161.583 11.667,160 16,160L240,160C244.333,160 248.083,161.583 251.25,164.75C254.417,167.917 256,171.667 256,176Z" |
@@ -26,6 +26,7 @@ interface Props { | |||
className?: string; | |||
height: number; | |||
notif?: React.ReactNode; | |||
[prop: string]: any; | |||
} | |||
export default function NavBar({ children, className, height, notif, ...other }: Props) { |
@@ -17,25 +17,28 @@ | |||
* 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 GlobalNav from './nav/global/GlobalNav'; | |||
import GlobalFooterContainer from './GlobalFooterContainer'; | |||
import GlobalMessagesContainer from './GlobalMessagesContainer'; | |||
import * as React from 'react'; | |||
import { Organization } from '../../app/types'; | |||
import OrganizationLink from './OrganizationLink'; | |||
import OrganizationAvatar from '../common/OrganizationAvatar'; | |||
import { translate } from '../../helpers/l10n'; | |||
export default function GlobalContainer(props /*: Object */) { | |||
// it is important to pass `location` down to `GlobalNav` to trigger render on url change | |||
interface Props { | |||
organization: Organization; | |||
} | |||
export default function OrganizationListItem({ organization }: Props) { | |||
return ( | |||
<div className="global-container"> | |||
<div className="page-wrapper" id="container"> | |||
<div className="page-container"> | |||
<GlobalNav location={props.location} /> | |||
<GlobalMessagesContainer /> | |||
{props.children} | |||
<li> | |||
<OrganizationLink className="dropdown-item-flex" organization={organization}> | |||
<div> | |||
<OrganizationAvatar organization={organization} small={true} /> | |||
<span className="spacer-left">{organization.name}</span> | |||
</div> | |||
</div> | |||
<GlobalFooterContainer /> | |||
</div> | |||
{organization.isAdmin && ( | |||
<span className="outline-badge spacer-left">{translate('admin')}</span> | |||
)} | |||
</OrganizationLink> | |||
</li> | |||
); | |||
} |
@@ -0,0 +1,38 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 SonarSource SA | |||
* mailto:contact 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 { shallow } from 'enzyme'; | |||
import OrganizationListItem from '../OrganizationListItem'; | |||
import { Visibility } from '../../../app/types'; | |||
it('renders', () => { | |||
expect( | |||
shallow( | |||
<OrganizationListItem | |||
organization={{ | |||
isAdmin: true, | |||
key: 'org', | |||
name: 'org', | |||
projectVisibility: Visibility.Public | |||
}} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); |
@@ -0,0 +1,41 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders 1`] = ` | |||
<li> | |||
<OrganizationLink | |||
className="dropdown-item-flex" | |||
organization={ | |||
Object { | |||
"isAdmin": true, | |||
"key": "org", | |||
"name": "org", | |||
"projectVisibility": "public", | |||
} | |||
} | |||
> | |||
<div> | |||
<OrganizationAvatar | |||
organization={ | |||
Object { | |||
"isAdmin": true, | |||
"key": "org", | |||
"name": "org", | |||
"projectVisibility": "public", | |||
} | |||
} | |||
small={true} | |||
/> | |||
<span | |||
className="spacer-left" | |||
> | |||
org | |||
</span> | |||
</div> | |||
<span | |||
className="outline-badge spacer-left" | |||
> | |||
admin | |||
</span> | |||
</OrganizationLink> | |||
</li> | |||
`; |
@@ -18,15 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { Extension } from '../../app/types'; | |||
interface AppState { | |||
adminPages?: Extension[]; | |||
authenticationError: boolean; | |||
authorizationError: boolean; | |||
organizationsEnabled: boolean; | |||
qualifiers?: string[]; | |||
} | |||
import { Extension, AppState } from '../../app/types'; | |||
interface SetAppStateAction { | |||
type: 'SET_APP_STATE'; | |||
@@ -62,7 +54,8 @@ export function requireAuthorization(): RequireAuthorizationAction { | |||
const defaultValue: AppState = { | |||
authenticationError: false, | |||
authorizationError: false, | |||
organizationsEnabled: false | |||
organizationsEnabled: false, | |||
qualifiers: [] | |||
}; | |||
export default function(state: AppState = defaultValue, action: Action): AppState { |
@@ -199,7 +199,7 @@ function byKey(state /*: ByKeyState */ = {}, action /*: Action */) { | |||
case 'RECEIVE_MY_ORGANIZATIONS': | |||
return onReceiveOrganizations(state, action); | |||
case 'CREATE_ORGANIZATION': | |||
return { ...state, [action.organization.key]: action.organization }; | |||
return { ...state, [action.organization.key]: { ...action.organization, isAdmin: true } }; | |||
case 'UPDATE_ORGANIZATION': | |||
return { | |||
...state, |
@@ -685,6 +685,9 @@ projects._projects=projects | |||
projects.no_projects.empty_instance=Once you analyze some projects, they will show up here. | |||
projects.no_favorite_projects=You don't have any favorite projects yet. | |||
projects.no_favorite_projects.engagement=Discover and mark as favorites projects you are interested in to have a quick access to them. | |||
projects.no_favorite_projects.how_to_add_projects=Here is how to add projects to this page | |||
projects.no_favorite_projects.favorite_projects_from_orgs=Favorite projects from your orgs | |||
projects.no_favorite_projects.favorite_public_projects=Favorite public projects | |||
projects.explore_projects=Explore Projects | |||
projects.not_analyzed=Project is not analyzed yet. | |||
projects.no_leak_period=Project has no leak data yet. | |||
@@ -2472,7 +2475,7 @@ footer.community=Community | |||
footer.documentation=Documentation | |||
footer.get_started=Get Started | |||
footer.help=Help | |||
footer.licence=LGPL v3 | |||
footer.license=LGPL v3 | |||
footer.news=News | |||
footer.plugins=Plugins | |||
footer.production_database_explanation=The embedded database will not scale, it will not support upgrading to newer versions of SonarQube, and there is no support for migrating your data out of it into a different database engine. |