From: Stas Vilchik Date: Tue, 12 Dec 2017 13:37:19 +0000 (+0100) Subject: SONAR-10187 Provide more options to populate empty "My Projects" page X-Git-Tag: 7.0-RC1~81 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=82d363f3bc37245ea92c77b5c1b0ea418f91dbb5;p=sonarqube.git SONAR-10187 Provide more options to populate empty "My Projects" page --- diff --git a/server/sonar-web/src/main/js/app/components/GlobalContainer.js b/server/sonar-web/src/main/js/app/components/GlobalContainer.js deleted file mode 100644 index 43f8b058ec3..00000000000 --- a/server/sonar-web/src/main/js/app/components/GlobalContainer.js +++ /dev/null @@ -1,41 +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 GlobalNav from './nav/global/GlobalNav'; -import GlobalFooterContainer from './GlobalFooterContainer'; -import GlobalMessagesContainer from './GlobalMessagesContainer'; - -export default function GlobalContainer(props /*: Object */) { - // it is important to pass `location` down to `GlobalNav` to trigger render on url change - - return ( -
-
-
- - - {props.children} -
-
- -
- ); -} diff --git a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx new file mode 100644 index 00000000000..cbab4678fd5 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx @@ -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 { + 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 ( +
+
+
+ + + {this.props.children} +
+
+ +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/app/components/GlobalFooter.js b/server/sonar-web/src/main/js/app/components/GlobalFooter.js deleted file mode 100644 index 6cd008827fa..00000000000 --- a/server/sonar-web/src/main/js/app/components/GlobalFooter.js +++ /dev/null @@ -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 ; - } - - return ( - - ); -} diff --git a/server/sonar-web/src/main/js/app/components/GlobalFooter.tsx b/server/sonar-web/src/main/js/app/components/GlobalFooter.tsx new file mode 100644 index 00000000000..39422c1e761 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/GlobalFooter.tsx @@ -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 ; + } + + return ( + + ); +} diff --git a/server/sonar-web/src/main/js/app/components/GlobalFooterContainer.tsx b/server/sonar-web/src/main/js/app/components/GlobalFooterContainer.tsx index e9b31253cdd..1e85753db06 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalFooterContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/GlobalFooterContainer.tsx @@ -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') diff --git a/server/sonar-web/src/main/js/app/components/GlobalFooterSonarCloud.js b/server/sonar-web/src/main/js/app/components/GlobalFooterSonarCloud.js deleted file mode 100644 index 9c601855952..00000000000 --- a/server/sonar-web/src/main/js/app/components/GlobalFooterSonarCloud.js +++ /dev/null @@ -1,50 +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 { translate } from '../../helpers/l10n'; - -export default function GlobalFooterSonarCloud() { - return ( - - ); -} diff --git a/server/sonar-web/src/main/js/app/components/GlobalFooterSonarCloud.tsx b/server/sonar-web/src/main/js/app/components/GlobalFooterSonarCloud.tsx new file mode 100644 index 00000000000..1ccd6d5c9ff --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/GlobalFooterSonarCloud.tsx @@ -0,0 +1,56 @@ +/* + * 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 { translate } from '../../helpers/l10n'; + +export default function GlobalFooterSonarCloud() { + return ( + + ); +} diff --git a/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooter-test.js b/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooter-test.js deleted file mode 100644 index 74ef097213f..00000000000 --- a/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooter-test.js +++ /dev/null @@ -1,55 +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. - */ -import { shallow } from 'enzyme'; -import React from 'react'; -import GlobalFooter from '../GlobalFooter'; - -it('should render the only logged in information', () => { - expect(shallow()).toMatchSnapshot(); -}); - -it('should not render the only logged in information', () => { - expect( - shallow( - - ) - ).toMatchSnapshot(); -}); - -it('should show the db warning message', () => { - expect(shallow().find('.alert')).toMatchSnapshot(); -}); - -it('should display the sq version', () => { - expect( - shallow() - ).toMatchSnapshot(); -}); - -it('should render SonarCloud footer', () => { - expect( - shallow() - ).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooter-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooter-test.tsx new file mode 100644 index 00000000000..f8325090a85 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooter-test.tsx @@ -0,0 +1,55 @@ +/* + * 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 { shallow } from 'enzyme'; +import GlobalFooter from '../GlobalFooter'; + +it('should render the only logged in information', () => { + expect(shallow()).toMatchSnapshot(); +}); + +it('should not render the only logged in information', () => { + expect( + shallow( + + ) + ).toMatchSnapshot(); +}); + +it('should show the db warning message', () => { + expect(shallow().find('.alert')).toMatchSnapshot(); +}); + +it('should display the sq version', () => { + expect( + shallow() + ).toMatchSnapshot(); +}); + +it('should render SonarCloud footer', () => { + expect( + shallow() + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooterSonarCloud-test.js b/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooterSonarCloud-test.js deleted file mode 100644 index 2f41d408c61..00000000000 --- a/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooterSonarCloud-test.js +++ /dev/null @@ -1,26 +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. - */ -import { shallow } from 'enzyme'; -import React from 'react'; -import GlobalFooterSonarCloud from '../GlobalFooterSonarCloud'; - -it('should render correctly', () => { - expect(shallow()).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooterSonarCloud-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooterSonarCloud-test.tsx new file mode 100644 index 00000000000..98644558082 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooterSonarCloud-test.tsx @@ -0,0 +1,26 @@ +/* + * 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 { shallow } from 'enzyme'; +import GlobalFooterSonarCloud from '../GlobalFooterSonarCloud'; + +it('should render correctly', () => { + expect(shallow()).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.js.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.js.snap deleted file mode 100644 index 438127ce0bc..00000000000 --- a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.js.snap +++ /dev/null @@ -1,173 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should display the sq version 1`] = ` - -`; - -exports[`should not render the only logged in information 1`] = ` - -`; - -exports[`should render SonarCloud footer 1`] = ``; - -exports[`should render the only logged in information 1`] = ` - -`; - -exports[`should show the db warning message 1`] = ` -
-

- footer.production_database_warning -

-

- footer.production_database_explanation -

-
-`; diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.tsx.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.tsx.snap new file mode 100644 index 00000000000..b7069f5b1cb --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.tsx.snap @@ -0,0 +1,242 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should display the sq version 1`] = ` + +`; + +exports[`should not render the only logged in information 1`] = ` + +`; + +exports[`should render SonarCloud footer 1`] = ``; + +exports[`should render the only logged in information 1`] = ` + +`; + +exports[`should show the db warning message 1`] = ` +
+

+ footer.production_database_warning +

+

+ footer.production_database_explanation +

+
+`; diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooterSonarCloud-test.js.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooterSonarCloud-test.js.snap deleted file mode 100644 index 0a3aa1953b1..00000000000 --- a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooterSonarCloud-test.js.snap +++ /dev/null @@ -1,57 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` - -`; diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooterSonarCloud-test.tsx.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooterSonarCloud-test.tsx.snap new file mode 100644 index 00000000000..f7a6f4b0228 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooterSonarCloud-test.tsx.snap @@ -0,0 +1,78 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` + +`; diff --git a/server/sonar-web/src/main/js/app/components/help/GlobalHelp.d.ts b/server/sonar-web/src/main/js/app/components/help/GlobalHelp.d.ts new file mode 100644 index 00000000000..ea8fb503279 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/help/GlobalHelp.d.ts @@ -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 {} diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.js b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.js deleted file mode 100644 index 564775c0179..00000000000 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.js +++ /dev/null @@ -1,171 +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 { connect } from 'react-redux'; -import GlobalNavBranding from './GlobalNavBranding'; -import GlobalNavMenu from './GlobalNavMenu'; -import GlobalNavExplore from './GlobalNavExplore'; -import GlobalNavUserContainer from './GlobalNavUserContainer'; -import GlobalNavPlus from './GlobalNavPlus'; -import Search from '../../search/Search'; -import GlobalHelp from '../../help/GlobalHelp'; -import * as theme from '../../../theme'; -import { isLoggedIn } from '../../../types'; -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 { 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 -}; -*/ - -/*:: -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 - }; - - componentDidMount() { - window.addEventListener('keypress', this.onKeyPress); - if (this.props.currentUser.showOnboardingTutorial) { - this.openOnboardingTutorial(); - } - } - - componentWillUnmount() { - if (this.interval) { - clearInterval(this.interval); - } - window.removeEventListener('keypress', this.onKeyPress); - } - - onKeyPress = e => { - const tagName = e.target.tagName; - const code = e.keyCode || e.which; - const isInput = tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA'; - const isTriggerKey = code === 63; - if (!isInput && isTriggerKey) { - this.openHelp(); - } - }; - - handleHelpClick = event => { - event.preventDefault(); - this.openHelp(); - }; - - openHelp = () => this.setState({ helpOpen: true }); - - closeHelp = () => this.setState({ helpOpen: false }); - - openOnboardingTutorial = () => this.setState({ helpOpen: false, onboardingTutorialOpen: true }); - - closeOnboardingTutorial = () => { - this.setState({ onboardingTutorialOpen: false, onboardingTutorialTooltip: true }); - this.props.skipOnboarding(); - this.interval = setInterval(() => { - this.setState({ onboardingTutorialTooltip: false }); - }, 3000); - }; - - render() { - return ( - - - - - - - - {this.state.helpOpen && ( - - )} - - {this.state.onboardingTutorialOpen && ( - - )} - - ); - } -} - -const mapStateToProps = state => { - const sonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled'); - - return { - currentUser: getCurrentUser(state), - appState: getAppState(state), - onSonarCloud: Boolean(sonarCloudSetting && sonarCloudSetting.value === 'true') - }; -}; - -const mapDispatchToProps = { skipOnboarding }; - -export default connect(mapStateToProps, mapDispatchToProps)(GlobalNav); diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx new file mode 100644 index 00000000000..04404a1fe1e --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx @@ -0,0 +1,173 @@ +/* + * 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 { connect } from 'react-redux'; +import GlobalNavBranding from './GlobalNavBranding'; +import GlobalNavMenu from './GlobalNavMenu'; +import GlobalNavExplore from './GlobalNavExplore'; +import GlobalNavUserContainer from './GlobalNavUserContainer'; +import GlobalNavPlus from './GlobalNavPlus'; +import Search from '../../search/Search'; +import GlobalHelp from '../../help/GlobalHelp'; +import * as theme from '../../../theme'; +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 { translate } from '../../../../helpers/l10n'; +import { getCurrentUser, getAppState, getGlobalSettingValue } from '../../../../store/rootReducer'; +import { skipOnboarding } from '../../../../store/users/actions'; +import './GlobalNav.css'; + +interface StateProps { + appState: AppState; + currentUser: CurrentUser; + onSonarCloud: boolean; +} + +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 { + interval?: number; + state: State = { helpOpen: false, onboardingTutorialTooltip: false }; + + componentDidMount() { + window.addEventListener('keypress', this.onKeyPress); + if (this.props.currentUser.showOnboardingTutorial) { + this.openOnboardingTutorial(); + } + } + + componentWillUnmount() { + if (this.interval) { + clearInterval(this.interval); + } + window.removeEventListener('keypress', this.onKeyPress); + } + + 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) { + this.openHelp(); + } + }; + + handleHelpClick = (event: React.SyntheticEvent) => { + event.preventDefault(); + this.openHelp(); + }; + + openHelp = () => this.setState({ helpOpen: true }); + + closeHelp = () => this.setState({ helpOpen: false }); + + openOnboardingTutorial = () => { + this.setState({ helpOpen: false }); + this.props.openOnboardingTutorial(); + }; + + closeOnboardingTutorial = () => { + this.setState({ onboardingTutorialTooltip: true }); + this.props.skipOnboarding(); + this.props.closeOnboardingTutorial(); + this.interval = window.setInterval(() => { + this.setState({ onboardingTutorialTooltip: false }); + }, 3000); + }; + + render() { + return ( + + + + + + + + {this.state.helpOpen && ( + + )} + + {this.props.isOnboardingTutorialOpen && ( + + )} + + ); + } +} + +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 mapDispatchToProps: DispatchProps = { skipOnboarding }; + +export default connect(mapStateToProps, mapDispatchToProps)(GlobalNav); diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js deleted file mode 100644 index 7cea4c635b2..00000000000 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js +++ /dev/null @@ -1,189 +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. - */ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Link } from 'react-router'; -import { isLoggedIn } from '../../../../app/types'; -import { translate } from '../../../../helpers/l10n'; -import { getQualityGatesUrl } 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: [] - }; - - activeLink(url) { - return window.location.pathname.indexOf(window.baseUrl + url) === 0 ? 'active' : null; - } - - renderProjects() { - if (this.props.onSonarCloud && !isLoggedIn(this.props.currentUser)) { - return null; - } - - return ( -
  • - - {this.props.onSonarCloud ? translate('my_projects') : translate('projects.page')} - -
  • - ); - } - - renderPortfolios() { - return ( -
  • - - {translate('portfolios.page')} - -
  • - ); - } - - renderIssuesLink() { - if (this.props.onSonarCloud && !isLoggedIn(this.props.currentUser)) { - return null; - } - - const active = this.props.location.pathname === 'issues'; - - if (this.props.onSonarCloud) { - return ( -
  • - - {translate('my_issues')} - -
  • - ); - } - - const query = - this.props.currentUser.isLoggedIn && isMySet() - ? { resolved: 'false', myIssues: 'true' } - : { resolved: 'false' }; - return ( -
  • - - {translate('issues.page')} - -
  • - ); - } - - renderRulesLink() { - return ( -
  • - - {translate('coding_rules.page')} - -
  • - ); - } - - renderProfilesLink() { - return ( -
  • - - {translate('quality_profiles.page')} - -
  • - ); - } - - renderQualityGatesLink() { - return ( -
  • - - {translate('quality_gates.page')} - -
  • - ); - } - - renderAdministrationLink() { - if (!this.props.appState.canAdmin) { - return null; - } - - return ( -
  • - - {translate('layout.settings')} - -
  • - ); - } - - renderGlobalPageLink = ({ key, name }) => { - return ( -
  • - {name} -
  • - ); - }; - - renderMore() { - const { globalPages } = this.props.appState; - const withoutPortfolios = globalPages.filter(page => page.key !== 'governance/portfolios'); - if (withoutPortfolios.length === 0) { - return null; - } - return ( -
  • - - {translate('more')}  - - -
      {withoutPortfolios.map(this.renderGlobalPageLink)}
    -
  • - ); - } - - render() { - const governanceInstalled = this.props.appState.qualifiers.includes('VW'); - const { organizationsEnabled } = this.props.appState; - - return ( -
      - {this.renderProjects()} - {governanceInstalled && this.renderPortfolios()} - {this.renderIssuesLink()} - {!organizationsEnabled && this.renderRulesLink()} - {!organizationsEnabled && this.renderProfilesLink()} - {!organizationsEnabled && this.renderQualityGatesLink()} - {this.renderAdministrationLink()} - {this.renderMore()} -
    - ); - } -} diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.tsx b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.tsx new file mode 100644 index 00000000000..50277e52187 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.tsx @@ -0,0 +1,181 @@ +/* + * 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 { isLoggedIn, CurrentUser, AppState, Extension } from '../../../../app/types'; +import { translate } from '../../../../helpers/l10n'; +import { getQualityGatesUrl, getBaseUrl } from '../../../../helpers/urls'; +import { isMySet } from '../../../../apps/issues/utils'; + +interface Props { + appState: AppState; + currentUser: CurrentUser; + location: { pathname: string }; + onSonarCloud: boolean; +} + +export default class GlobalNavMenu extends React.PureComponent { + activeLink(url: string) { + return window.location.pathname.indexOf(getBaseUrl() + url) === 0 ? 'active' : undefined; + } + + renderProjects() { + if (this.props.onSonarCloud && !isLoggedIn(this.props.currentUser)) { + return null; + } + + return ( +
  • + + {this.props.onSonarCloud ? translate('my_projects') : translate('projects.page')} + +
  • + ); + } + + renderPortfolios() { + return ( +
  • + + {translate('portfolios.page')} + +
  • + ); + } + + renderIssuesLink() { + if (this.props.onSonarCloud && !isLoggedIn(this.props.currentUser)) { + return null; + } + + const active = this.props.location.pathname === 'issues'; + + if (this.props.onSonarCloud) { + return ( +
  • + + {translate('my_issues')} + +
  • + ); + } + + const query = + this.props.currentUser.isLoggedIn && isMySet() + ? { resolved: 'false', myIssues: 'true' } + : { resolved: 'false' }; + return ( +
  • + + {translate('issues.page')} + +
  • + ); + } + + renderRulesLink() { + return ( +
  • + + {translate('coding_rules.page')} + +
  • + ); + } + + renderProfilesLink() { + return ( +
  • + + {translate('quality_profiles.page')} + +
  • + ); + } + + renderQualityGatesLink() { + return ( +
  • + + {translate('quality_gates.page')} + +
  • + ); + } + + renderAdministrationLink() { + if (!this.props.appState.canAdmin) { + return null; + } + + return ( +
  • + + {translate('layout.settings')} + +
  • + ); + } + + renderGlobalPageLink = ({ key, name }: Extension) => { + return ( +
  • + {name} +
  • + ); + }; + + renderMore() { + const { globalPages = [] } = this.props.appState; + const withoutPortfolios = globalPages.filter(page => page.key !== 'governance/portfolios'); + if (withoutPortfolios.length === 0) { + return null; + } + return ( +
  • + + {translate('more')}  + + +
      {withoutPortfolios.map(this.renderGlobalPageLink)}
    +
  • + ); + } + + render() { + const governanceInstalled = this.props.appState.qualifiers.includes('VW'); + const { organizationsEnabled } = this.props.appState; + + return ( +
      + {this.renderProjects()} + {governanceInstalled && this.renderPortfolios()} + {this.renderIssuesLink()} + {!organizationsEnabled && this.renderRulesLink()} + {!organizationsEnabled && this.renderProfilesLink()} + {!organizationsEnabled && this.renderQualityGatesLink()} + {this.renderAdministrationLink()} + {this.renderMore()} +
    + ); + } +} diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.tsx b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.tsx index 1649a5e370e..fb19ba06d40 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.tsx @@ -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 { - node?: HTMLElement | null; - +export default class GlobalNavUser extends React.PureComponent { 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) => { event.preventDefault(); const shouldReturnToCurrentPage = window.location.pathname !== `${getBaseUrl()}/about`; @@ -76,98 +55,61 @@ export default class GlobalNavUser extends React.PureComponent { handleLogout = (event: React.SyntheticEvent) => { event.preventDefault(); - this.closeDropdown(); this.context.router.push('/sessions/logout'); }; - toggleDropdown = (event: React.SyntheticEvent) => { - 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 ( -
  • (this.node = node)}> - - - - {this.state.open && ( -
      -
    • -
      - {currentUser.name} -
      - {currentUser.email != null && ( -
      - {currentUser.email} + + {({ onToggleClick, open }) => ( +
    • + + + +
        +
      • +
        + {currentUser.name}
        - )} -
      • -
      • -
      • - - {translate('my_account.page')} - -
      • - {hasOrganizations &&
      • } - {hasOrganizations && ( + {currentUser.email != null && ( +
        + {currentUser.email} +
        + )} +
      • +
      • - - {translate('my_organizations')} - + {translate('my_account.page')}
      • - )} - {hasOrganizations && - sortBy(organizations, org => org.name.toLowerCase()).map(organization => ( -
      • - -
        - - {organization.name} -
        - {organization.isAdmin && ( - {translate('admin')} - )} -
        + {hasOrganizations &&
      • } + {hasOrganizations && ( +
      • + {translate('my_organizations')}
      • - ))} - {hasOrganizations &&
      • } -
      • - - {translate('layout.logout')} - -
      • -
      + )} + {hasOrganizations && + sortBy(organizations, org => org.name.toLowerCase()).map(organization => ( + + ))} + {hasOrganizations &&
    • } +
    • + + {translate('layout.logout')} + +
    • +
    +
  • )} - + ); } diff --git a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.tsx b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.tsx index 659fe6def16..dbec4b97992 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.tsx @@ -43,7 +43,7 @@ it('should render the right interface for logged in user', () => { ); 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', () => { ); 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(); }); diff --git a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavMenu-test.js.snap b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavMenu-test.js.snap index 673cc003e3c..34dd8f91396 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavMenu-test.js.snap +++ b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavMenu-test.js.snap @@ -32,7 +32,6 @@ exports[`should show administration menu if the user has the rights 1`] = `
  • -
  • - -
    - - - bar - -
    -
    -
  • -
  • + - -
    - - - Foo - -
    -
    -
  • -
  • + - -
    - - - MyOrg - -
    -
    -
  • + } + />
  • {} diff --git a/server/sonar-web/src/main/js/app/styles/components/page.css b/server/sonar-web/src/main/js/app/styles/components/page.css index a1a7d2b8610..53855c6747f 100644 --- a/server/sonar-web/src/main/js/app/styles/components/page.css +++ b/server/sonar-web/src/main/js/app/styles/components/page.css @@ -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; } diff --git a/server/sonar-web/src/main/js/app/styles/init/forms.css b/server/sonar-web/src/main/js/app/styles/init/forms.css index a9a610f6632..97287e21ac0 100644 --- a/server/sonar-web/src/main/js/app/styles/init/forms.css +++ b/server/sonar-web/src/main/js/app/styles/init/forms.css @@ -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); diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index af3c21a26db..2b2bc7a64a8 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/server/sonar-web/src/main/js/app/types.ts @@ -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[]; +} diff --git a/server/sonar-web/src/main/js/apps/account/organizations/OrganizationCard.js b/server/sonar-web/src/main/js/apps/account/organizations/OrganizationCard.js deleted file mode 100644 index 1bded7bb20f..00000000000 --- a/server/sonar-web/src/main/js/apps/account/organizations/OrganizationCard.js +++ /dev/null @@ -1,77 +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 OrganizationLink from '../../../components/ui/OrganizationLink'; -/*:: import type { Organization } from '../../../store/organizations/duck'; */ -import { translate } from '../../../helpers/l10n'; - -/*:: -type Props = { - organization: Organization -}; -*/ - -export default function OrganizationCard(props /*: Props */) { - const { organization } = props; - - return ( -
    - - -

    - {organization.name} - {organization.isAdmin && ( - {translate('admin')} - )} -

    - - {!!organization.description && ( -
    {organization.description}
    - )} - -
    - - {translate('key')} - {':'} - - event.currentTarget.select()} - readOnly={true} - type="text" - value={organization.key} - /> -
    -
    - ); -} diff --git a/server/sonar-web/src/main/js/apps/account/organizations/OrganizationCard.tsx b/server/sonar-web/src/main/js/apps/account/organizations/OrganizationCard.tsx new file mode 100644 index 00000000000..0f8b57127a7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/organizations/OrganizationCard.tsx @@ -0,0 +1,60 @@ +/* + * 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 OrganizationAvatar from '../../../components/common/OrganizationAvatar'; +import OrganizationLink from '../../../components/ui/OrganizationLink'; +import { translate } from '../../../helpers/l10n'; +import { Organization } from '../../../app/types'; + +interface Props { + organization: Organization; +} + +export default function OrganizationCard({ organization }: Props) { + return ( +
    + + +

    + + + {organization.name} + + {organization.isAdmin && ( + {translate('admin')} + )} +

    + + {!!organization.description && ( +
    {organization.description}
    + )} + + {!!organization.url && ( + + )} +
    + ); +} diff --git a/server/sonar-web/src/main/js/apps/account/organizations/OrganizationsList.js b/server/sonar-web/src/main/js/apps/account/organizations/OrganizationsList.js deleted file mode 100644 index 921b636dd3a..00000000000 --- a/server/sonar-web/src/main/js/apps/account/organizations/OrganizationsList.js +++ /dev/null @@ -1,44 +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 { sortBy } from 'lodash'; -import OrganizationCard from './OrganizationCard'; -/*:: import type { Organization } from '../../../store/organizations/duck'; */ - -/*:: -type Props = { - organizations: Array -}; -*/ - -export default function OrganizationsList(props /*: Props */) { - return ( -
      - {sortBy(props.organizations, organization => organization.name.toLocaleLowerCase()).map( - organization => ( -
    • - -
    • - ) - )} -
    - ); -} diff --git a/server/sonar-web/src/main/js/apps/account/organizations/OrganizationsList.tsx b/server/sonar-web/src/main/js/apps/account/organizations/OrganizationsList.tsx new file mode 100644 index 00000000000..5b0b8ccc367 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/organizations/OrganizationsList.tsx @@ -0,0 +1,41 @@ +/* + * 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 { sortBy } from 'lodash'; +import OrganizationCard from './OrganizationCard'; +import { Organization } from '../../../app/types'; + +interface Props { + organizations: Organization[]; +} + +export default function OrganizationsList({ organizations }: Props) { + return ( +
      + {sortBy(organizations, organization => organization.name.toLocaleLowerCase()).map( + organization => ( +
    • + +
    • + ) + )} +
    + ); +} diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationAdministration.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationAdministration.tsx index 98d50b19d61..accf47cbd70 100644 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationAdministration.tsx +++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationAdministration.tsx @@ -56,7 +56,7 @@ export default function OrganizationNavigationAdministration({ location, organiz href="#" onClick={onToggleClick}> {translate('layout.settings')} - +
      {extensions.map(extension => ( diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeader.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeader.tsx index afa4332544c..84f0256e579 100644 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeader.tsx +++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeader.tsx @@ -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
        {sortBy(other, org => org.name.toLowerCase()).map(organization => ( -
      • - -
        - - {organization.name} -
        - {organization.isAdmin && ( - {translate('admin')} - )} -
        -
      • + ))}
      diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationAdministration-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationAdministration-test.tsx.snap index 61f766d7cf7..713261b2290 100644 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationAdministration-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationAdministration-test.tsx.snap @@ -11,7 +11,9 @@ exports[`renders 1`] = ` onClick={[Function]} > layout.settings - +
        -
      • - -
        - - - org1 - -
        - - admin - -
        -
      • -
      • + - -
        - - - org2 - -
        -
        -
      • + } + />
      `; diff --git a/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx b/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx index b562b7d46bf..d1bb1d60a34 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx @@ -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 ( -
      -

      {translate('projects.no_favorite_projects')}

      -

      {translate('projects.no_favorite_projects.engagement')}

      -

      - - {translate('projects.explore_projects')} - -

      -
      - ); +export class NoFavoriteProjects extends React.PureComponent { + static contextTypes = { + openOnboardingTutorial: PropTypes.func + }; + + onAnalyzeProjectClick = (event: React.SyntheticEvent) => { + event.preventDefault(); + event.currentTarget.blur(); + this.context.openOnboardingTutorial(); + }; + + render() { + const { onSonarCloud, organizations } = this.props; + return ( +
      +

      {translate('projects.no_favorite_projects')}

      + {onSonarCloud ? ( +
      +

      {translate('projects.no_favorite_projects.how_to_add_projects')}

      +
      + + {translate('my_account.analyze_new_project')} + + + {({ onToggleClick, open }) => ( +
      + + {translate('projects.no_favorite_projects.favorite_projects_from_orgs')} + + +
        + {sortBy(organizations, org => org.name.toLowerCase()).map(organization => ( + + ))} +
      +
      + )} +
      + + {translate('projects.no_favorite_projects.favorite_public_projects')} + +
      +
      + ) : ( +
      +

      + {translate('projects.no_favorite_projects.engagement')} +

      +

      + + {translate('projects.explore_projects')} + +

      +
      + )} +
      + ); + } } + +const mapStateToProps = (state: any): StateProps => ({ + organizations: getMyOrganizations(state) +}); + +export default connect(mapStateToProps)(NoFavoriteProjects); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/NoFavoriteProjects-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/NoFavoriteProjects-test.tsx index 3635c476f77..8f767812c60 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/NoFavoriteProjects-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/NoFavoriteProjects-test.tsx @@ -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()).toMatchSnapshot(); + expect(shallow()).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() + ).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/NoFavoriteProjects-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/NoFavoriteProjects-test.tsx.snap index 3b6652f10f8..a7dede57348 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/NoFavoriteProjects-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/NoFavoriteProjects-test.tsx.snap @@ -7,22 +7,61 @@ exports[`renders 1`] = `

      projects.no_favorite_projects

      -

      - projects.no_favorite_projects.engagement -

      -

      +

      + projects.no_favorite_projects.engagement +

      +

      + + projects.explore_projects + +

      + + +`; + +exports[`renders for SonarCloud 1`] = ` +
      +

      + projects.no_favorite_projects +

      +
      - + projects.no_favorite_projects.how_to_add_projects +

      +
      - projects.explore_projects - -

      + + my_account.analyze_new_project + + + + projects.no_favorite_projects.favorite_public_projects + +
      +
      `; diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap index 93fb447b814..b2552033bef 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap @@ -47,6 +47,6 @@ exports[`renders different types of "no projects" 3`] = `
      - +
      `; diff --git a/server/sonar-web/src/main/js/apps/projects/styles.css b/server/sonar-web/src/main/js/apps/projects/styles.css index 707f0243994..e1084c7f01f 100644 --- a/server/sonar-web/src/main/js/apps/projects/styles.css +++ b/server/sonar-web/src/main/js/apps/projects/styles.css @@ -267,3 +267,8 @@ margin-left: -250px; text-align: center; } + +.projects-empty-list { + padding: calc(4 * var(--gridSize)) 0; + text-align: center; +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.d.ts b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.d.ts new file mode 100644 index 00000000000..d5d3100cbfa --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.d.ts @@ -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 {} diff --git a/server/sonar-web/src/main/js/components/icons-components/DropdownIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/DropdownIcon.tsx index 20bc9629f31..7cd3226777a 100644 --- a/server/sonar-web/src/main/js/components/icons-components/DropdownIcon.tsx +++ b/server/sonar-web/src/main/js/components/icons-components/DropdownIcon.tsx @@ -24,13 +24,13 @@ export default function DropdownIcon({ className, fill = 'currentColor', size = return ( - + + +
      + + {organization.name} +
      + {organization.isAdmin && ( + {translate('admin')} + )} + + + ); +} diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/OrganizationListItem-test.tsx b/server/sonar-web/src/main/js/components/ui/__tests__/OrganizationListItem-test.tsx new file mode 100644 index 00000000000..dd340f58686 --- /dev/null +++ b/server/sonar-web/src/main/js/components/ui/__tests__/OrganizationListItem-test.tsx @@ -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( + + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/OrganizationListItem-test.tsx.snap b/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/OrganizationListItem-test.tsx.snap new file mode 100644 index 00000000000..bd3a76ad927 --- /dev/null +++ b/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/OrganizationListItem-test.tsx.snap @@ -0,0 +1,41 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +
    • + +
      + + + org + +
      + + admin + +
      +
    • +`; diff --git a/server/sonar-web/src/main/js/store/appState/duck.ts b/server/sonar-web/src/main/js/store/appState/duck.ts index abb05d0b309..dd0f2a8c962 100644 --- a/server/sonar-web/src/main/js/store/appState/duck.ts +++ b/server/sonar-web/src/main/js/store/appState/duck.ts @@ -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 { diff --git a/server/sonar-web/src/main/js/store/organizations/duck.js b/server/sonar-web/src/main/js/store/organizations/duck.js index 4b4023f97ce..35578963545 100644 --- a/server/sonar-web/src/main/js/store/organizations/duck.js +++ b/server/sonar-web/src/main/js/store/organizations/duck.js @@ -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, diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 030f1b305bf..1fa402c992e 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -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.