From 030370cf6255d1185ecb3deeaab7de1fe76e058b Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Mon, 28 Aug 2017 11:41:51 +0200 Subject: add branches help popups (#2420) --- server/sonar-web/src/main/js/app/components/App.js | 73 ----------- .../sonar-web/src/main/js/app/components/App.tsx | 86 ++++++++++++ .../src/main/js/app/components/GlobalLoading.js | 30 ----- .../src/main/js/app/components/GlobalLoading.tsx | 29 +++++ .../nav/component/ComponentNavBranch.tsx | 144 ++++++++++++++++++--- .../nav/component/NoBranchSupportPopup.tsx | 52 ++++++++ .../nav/component/SingleBranchHelperPopup.tsx | 47 +++++++ .../__tests__/ComponentNavBranch-test.tsx | 65 +++++++++- .../__tests__/NoBranchSupportPopup-test.tsx | 26 ++++ .../__tests__/SingleBranchHelperPopup-test.tsx | 26 ++++ .../__snapshots__/ComponentNavBranch-test.tsx.snap | 84 ++++++++++++ .../NoBranchSupportPopup-test.tsx.snap | 37 ++++++ .../SingleBranchHelperPopup-test.tsx.snap | 29 +++++ .../src/main/js/components/common/BubblePopup.js | 46 ------- .../src/main/js/components/common/BubblePopup.tsx | 39 ++++++ .../main/js/components/common/BubblePopupHelper.js | 113 ---------------- .../js/components/common/BubblePopupHelper.tsx | 105 +++++++++++++++ .../__snapshots__/BubblePopupHelper-test.js.snap | 4 - .../js/components/icons-components/HelpIcon.js | 6 +- .../sonar-web/src/main/js/store/appState/duck.js | 93 ------------- .../sonar-web/src/main/js/store/appState/duck.ts | 83 ++++++++++++ server/sonar-web/src/main/js/store/rootActions.js | 5 +- 22 files changed, 836 insertions(+), 386 deletions(-) delete mode 100644 server/sonar-web/src/main/js/app/components/App.js create mode 100644 server/sonar-web/src/main/js/app/components/App.tsx delete mode 100644 server/sonar-web/src/main/js/app/components/GlobalLoading.js create mode 100644 server/sonar-web/src/main/js/app/components/GlobalLoading.tsx create mode 100644 server/sonar-web/src/main/js/app/components/nav/component/NoBranchSupportPopup.tsx create mode 100644 server/sonar-web/src/main/js/app/components/nav/component/SingleBranchHelperPopup.tsx create mode 100644 server/sonar-web/src/main/js/app/components/nav/component/__tests__/NoBranchSupportPopup-test.tsx create mode 100644 server/sonar-web/src/main/js/app/components/nav/component/__tests__/SingleBranchHelperPopup-test.tsx create mode 100644 server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/NoBranchSupportPopup-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/SingleBranchHelperPopup-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/components/common/BubblePopup.js create mode 100644 server/sonar-web/src/main/js/components/common/BubblePopup.tsx delete mode 100644 server/sonar-web/src/main/js/components/common/BubblePopupHelper.js create mode 100644 server/sonar-web/src/main/js/components/common/BubblePopupHelper.tsx delete mode 100644 server/sonar-web/src/main/js/store/appState/duck.js create mode 100644 server/sonar-web/src/main/js/store/appState/duck.ts (limited to 'server/sonar-web/src/main/js') diff --git a/server/sonar-web/src/main/js/app/components/App.js b/server/sonar-web/src/main/js/app/components/App.js deleted file mode 100644 index a99a24f1d17..00000000000 --- a/server/sonar-web/src/main/js/app/components/App.js +++ /dev/null @@ -1,73 +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 PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import GlobalLoading from './GlobalLoading'; -import { fetchCurrentUser } from '../../store/users/actions'; -import { fetchLanguages, fetchAppState } from '../../store/rootActions'; - -class App extends React.PureComponent { - /*:: mounted: boolean; */ - - static propTypes = { - fetchAppState: PropTypes.func.isRequired, - fetchCurrentUser: PropTypes.func.isRequired, - fetchLanguages: PropTypes.func.isRequired, - children: PropTypes.element.isRequired - }; - - state = { - loading: true - }; - - componentDidMount() { - this.mounted = true; - - this.props - .fetchCurrentUser() - .then(() => Promise.all([this.props.fetchAppState(), this.props.fetchLanguages()])) - .then(this.finishLoading, this.finishLoading); - } - - componentWillUnmount() { - this.mounted = false; - } - - finishLoading = () => { - if (this.mounted) { - this.setState({ loading: false }); - } - }; - - render() { - if (this.state.loading) { - return ; - } - return this.props.children; - } -} - -export default connect(null, { - fetchAppState, - fetchCurrentUser, - fetchLanguages -})(App); diff --git a/server/sonar-web/src/main/js/app/components/App.tsx b/server/sonar-web/src/main/js/app/components/App.tsx new file mode 100644 index 00000000000..400ab16b2ae --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/App.tsx @@ -0,0 +1,86 @@ +/* + * 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 * as PropTypes from 'prop-types'; +import GlobalLoading from './GlobalLoading'; +import { fetchCurrentUser } from '../../store/users/actions'; +import { fetchLanguages, fetchAppState } from '../../store/rootActions'; + +interface Props { + children: JSX.Element; + fetchAppState: () => Promise; + fetchCurrentUser: () => Promise; + fetchLanguages: () => Promise; +} + +interface State { + branchesEnabled: boolean; + loading: boolean; +} + +class App extends React.PureComponent { + mounted: boolean; + state: State = { branchesEnabled: false, loading: true }; + + static childContextTypes = { + branchesEnabled: PropTypes.bool.isRequired + }; + + getChildContext() { + return { branchesEnabled: this.state.branchesEnabled }; + } + + componentDidMount() { + this.mounted = true; + + this.props + .fetchCurrentUser() + .then(() => Promise.all([this.fetchAppState(), this.props.fetchLanguages()])) + .then(this.finishLoading, this.finishLoading); + } + + componentWillUnmount() { + this.mounted = false; + } + + fetchAppState = () => { + return this.props.fetchAppState().then(appState => { + if (this.mounted) { + this.setState({ branchesEnabled: appState.branchesEnabled }); + } + }); + }; + + finishLoading = () => { + if (this.mounted) { + this.setState({ loading: false }); + } + }; + + render() { + if (this.state.loading) { + return ; + } + return this.props.children; + } +} + +export default connect(null, { fetchAppState, fetchCurrentUser, fetchLanguages })(App as any); diff --git a/server/sonar-web/src/main/js/app/components/GlobalLoading.js b/server/sonar-web/src/main/js/app/components/GlobalLoading.js deleted file mode 100644 index 060596c99b4..00000000000 --- a/server/sonar-web/src/main/js/app/components/GlobalLoading.js +++ /dev/null @@ -1,30 +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'; - -export default function GlobalLoading() { - return ( -
- - Loading... -
- ); -} diff --git a/server/sonar-web/src/main/js/app/components/GlobalLoading.tsx b/server/sonar-web/src/main/js/app/components/GlobalLoading.tsx new file mode 100644 index 00000000000..4e6a6206c26 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/GlobalLoading.tsx @@ -0,0 +1,29 @@ +/* + * 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'; + +export default function GlobalLoading() { + return ( +
+ + Loading... +
+ ); +} diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx index fbcbd1db06c..1a99483710f 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx @@ -19,11 +19,16 @@ */ import * as React from 'react'; import * as classNames from 'classnames'; +import * as PropTypes from 'prop-types'; import ComponentNavBranchesMenu from './ComponentNavBranchesMenu'; +import SingleBranchHelperPopup from './SingleBranchHelperPopup'; +import NoBranchSupportPopup from './NoBranchSupportPopup'; import { Branch, Component } from '../../../types'; import BranchIcon from '../../../../components/icons-components/BranchIcon'; import { isShortLivingBranch } from '../../../../helpers/branches'; import { translate } from '../../../../helpers/l10n'; +import HelpIcon from '../../../../components/icons-components/HelpIcon'; +import BubblePopupHelper from '../../../../components/common/BubblePopupHelper'; interface Props { branches: Branch[]; @@ -32,12 +37,22 @@ interface Props { } interface State { - open: boolean; + dropdownOpen: boolean; + noBranchSupportPopupOpen: boolean; + singleBranchPopupOpen: boolean; } export default class ComponentNavBranch extends React.PureComponent { mounted: boolean; - state: State = { open: false }; + state: State = { + dropdownOpen: false, + noBranchSupportPopupOpen: false, + singleBranchPopupOpen: false + }; + + static contextTypes = { + branchesEnabled: PropTypes.bool.isRequired + }; componentDidMount() { this.mounted = true; @@ -48,7 +63,7 @@ export default class ComponentNavBranch extends React.PureComponent { if (this.mounted) { - this.setState({ open: false }); + this.setState({ dropdownOpen: false }); } }; - render() { + toggleSingleBranchPopup = (show?: boolean) => { + if (show != undefined) { + this.setState({ singleBranchPopupOpen: show }); + } else { + this.setState(state => ({ singleBranchPopupOpen: !state.singleBranchPopupOpen })); + } + }; + + toggleNoBranchSupportPopup = (show?: boolean) => { + if (show != undefined) { + this.setState({ noBranchSupportPopupOpen: show }); + } else { + this.setState(state => ({ noBranchSupportPopupOpen: !state.noBranchSupportPopupOpen })); + } + }; + + handleSingleBranchClick = (event: React.SyntheticEvent) => { + event.preventDefault(); + event.stopPropagation(); + this.toggleSingleBranchPopup(); + }; + + handleNoBranchSupportClick = (event: React.SyntheticEvent) => { + event.preventDefault(); + event.stopPropagation(); + this.toggleNoBranchSupportPopup(); + }; + + renderDropdown = () => { + return this.state.dropdownOpen + ? + : null; + }; + + renderMergeBranch = () => { const { currentBranch } = this.props; + return isShortLivingBranch(currentBranch) && !currentBranch.isOrphan + ? + {translate('from')} {currentBranch.mergeBranch} + + : null; + }; + + renderSingleBranchPopup = () => +
+ + + + } + togglePopup={this.toggleSingleBranchPopup} + /> +
; + + renderNoBranchSupportPopup = () => +
+ + + + } + togglePopup={this.toggleNoBranchSupportPopup} + /> +
; + + render() { + const { branches, currentBranch } = this.props; + + if (!this.context.branchesEnabled) { + return ( +
+ + + {currentBranch.name} + + {this.renderNoBranchSupportPopup()} +
+ ); + } + + if (branches.length < 2) { + return ( +
+ + + {currentBranch.name} + + {this.renderSingleBranchPopup()} +
+ ); + } return ( -
+
{currentBranch.name} - {this.state.open && - } - {isShortLivingBranch(currentBranch) && - !currentBranch.isOrphan && - - {translate('from')} {currentBranch.mergeBranch} - } + {this.renderDropdown()} + {this.renderMergeBranch()}
); } diff --git a/server/sonar-web/src/main/js/app/components/nav/component/NoBranchSupportPopup.tsx b/server/sonar-web/src/main/js/app/components/nav/component/NoBranchSupportPopup.tsx new file mode 100644 index 00000000000..fe2f3235dbc --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/nav/component/NoBranchSupportPopup.tsx @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 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 BubblePopup from '../../../../components/common/BubblePopup'; +import { translate } from '../../../../helpers/l10n'; + +interface Props { + popupPosition?: any; +} + +export default function NoBranchSupportPopup(props: Props) { + return ( + +
+
+ {translate('branches.no_support.header')} +
+

+ {translate('branches.no_support.header.text')} +

+

+ + {translate('learn_more')} + + + {translate('buy_developer_pack')} + +

+
+
+ ); +} diff --git a/server/sonar-web/src/main/js/app/components/nav/component/SingleBranchHelperPopup.tsx b/server/sonar-web/src/main/js/app/components/nav/component/SingleBranchHelperPopup.tsx new file mode 100644 index 00000000000..abb4adc8c3d --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/nav/component/SingleBranchHelperPopup.tsx @@ -0,0 +1,47 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 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 BubblePopup from '../../../../components/common/BubblePopup'; +import { translate } from '../../../../helpers/l10n'; + +interface Props { + popupPosition?: any; +} + +export default function SingleBranchHelperPopup(props: Props) { + return ( + +
+
+ {translate('branches.learn_how_to_analyze')} +
+

+ {translate('branches.learn_how_to_analyze.text')} +

+ + {translate('about_page.read_documentation')} + +
+
+ ); +} diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx index 5d2d86f9d24..9f3a4e429c8 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx @@ -20,14 +20,29 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import ComponentNavBranch from '../ComponentNavBranch'; -import { BranchType, ShortLivingBranch, MainBranch, Component } from '../../../../types'; +import { + BranchType, + ShortLivingBranch, + MainBranch, + Component, + LongLivingBranch +} from '../../../../types'; import { click } from '../../../../../helpers/testUtils'; +const fooBranch: LongLivingBranch = { isMain: false, name: 'foo', type: BranchType.LONG }; + it('renders main branch', () => { const branch: MainBranch = { isMain: true, name: 'master' }; const component = {} as Component; expect( - shallow() + shallow( + , + { context: { branchesEnabled: true } } + ) ).toMatchSnapshot(); }); @@ -41,7 +56,14 @@ it('renders short-living branch', () => { }; const component = {} as Component; expect( - shallow() + shallow( + , + { context: { branchesEnabled: true } } + ) ).toMatchSnapshot(); }); @@ -49,9 +71,44 @@ it('opens menu', () => { const branch: MainBranch = { isMain: true, name: 'master' }; const component = {} as Component; const wrapper = shallow( - + , + { context: { branchesEnabled: true } } ); expect(wrapper.find('ComponentNavBranchesMenu')).toHaveLength(0); click(wrapper.find('a')); expect(wrapper.find('ComponentNavBranchesMenu')).toHaveLength(1); }); + +it('renders single branch popup', () => { + const branch: MainBranch = { isMain: true, name: 'master' }; + const component = {} as Component; + const wrapper = shallow( + , + { context: { branchesEnabled: true } } + ); + expect(wrapper).toMatchSnapshot(); + expect(wrapper.find('BubblePopupHelper').prop('isOpen')).toBe(false); + click(wrapper.find('a')); + expect(wrapper.find('BubblePopupHelper').prop('isOpen')).toBe(true); +}); + +it('renders no branch support popup', () => { + const branch: MainBranch = { isMain: true, name: 'master' }; + const component = {} as Component; + const wrapper = shallow( + , + { context: { branchesEnabled: false } } + ); + expect(wrapper).toMatchSnapshot(); + expect(wrapper.find('BubblePopupHelper').prop('isOpen')).toBe(false); + click(wrapper.find('a')); + expect(wrapper.find('BubblePopupHelper').prop('isOpen')).toBe(true); +}); diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/NoBranchSupportPopup-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/NoBranchSupportPopup-test.tsx new file mode 100644 index 00000000000..4f5925156fc --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/NoBranchSupportPopup-test.tsx @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 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 NoBranchSupportPopup from '../NoBranchSupportPopup'; + +it('renders', () => { + expect(shallow()).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/SingleBranchHelperPopup-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/SingleBranchHelperPopup-test.tsx new file mode 100644 index 00000000000..ca1ddc17ac4 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/SingleBranchHelperPopup-test.tsx @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 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 SingleBranchHelperPopup from '../SingleBranchHelperPopup'; + +it('renders', () => { + expect(shallow()).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap index 977dd6433c3..85f4f20a64d 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap @@ -26,6 +26,48 @@ exports[`renders main branch 1`] = `
`; +exports[`renders no branch support popup 1`] = ` +
+ + + master + +
+ + + + } + position="bottomleft" + togglePopup={[Function]} + /> +
+
+`; + exports[`renders short-living branch 1`] = `
`; + +exports[`renders single branch popup 1`] = ` +
+ + + master + +
+ + + + } + position="bottomleft" + togglePopup={[Function]} + /> +
+
+`; diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/NoBranchSupportPopup-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/NoBranchSupportPopup-test.tsx.snap new file mode 100644 index 00000000000..bcc5eadaede --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/NoBranchSupportPopup-test.tsx.snap @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` + +
+
+ branches.no_support.header +
+

+ branches.no_support.header.text +

+

+ + learn_more + + + buy_developer_pack + +

+
+
+`; diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/SingleBranchHelperPopup-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/SingleBranchHelperPopup-test.tsx.snap new file mode 100644 index 00000000000..459630aab47 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/SingleBranchHelperPopup-test.tsx.snap @@ -0,0 +1,29 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` + +
+
+ branches.learn_how_to_analyze +
+

+ branches.learn_how_to_analyze.text +

+ + about_page.read_documentation + +
+
+`; diff --git a/server/sonar-web/src/main/js/components/common/BubblePopup.js b/server/sonar-web/src/main/js/components/common/BubblePopup.js deleted file mode 100644 index c77167ee53b..00000000000 --- a/server/sonar-web/src/main/js/components/common/BubblePopup.js +++ /dev/null @@ -1,46 +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 classNames from 'classnames'; - -export default class BubblePopup extends React.PureComponent { - static propsType = { - children: PropTypes.object.isRequired, - position: PropTypes.object.isRequired, - customClass: PropTypes.string - }; - - static defaultProps = { - customClass: '' - }; - - render() { - const popupClass = classNames('bubble-popup', this.props.customClass); - const popupStyle = { ...this.props.position }; - - return ( -
- {this.props.children} -
-
- ); - } -} diff --git a/server/sonar-web/src/main/js/components/common/BubblePopup.tsx b/server/sonar-web/src/main/js/components/common/BubblePopup.tsx new file mode 100644 index 00000000000..ff2c3b12ee4 --- /dev/null +++ b/server/sonar-web/src/main/js/components/common/BubblePopup.tsx @@ -0,0 +1,39 @@ +/* + * 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 classNames from 'classnames'; + +interface Props { + customClass?: string; + children: React.ReactNode; + position: { top: number; right: number }; +} + +export default function BubblePopup(props: Props) { + const popupClass = classNames('bubble-popup', props.customClass); + const popupStyle = { ...props.position }; + + return ( +
+ {props.children} +
+
+ ); +} diff --git a/server/sonar-web/src/main/js/components/common/BubblePopupHelper.js b/server/sonar-web/src/main/js/components/common/BubblePopupHelper.js deleted file mode 100644 index 48fa517be01..00000000000 --- a/server/sonar-web/src/main/js/components/common/BubblePopupHelper.js +++ /dev/null @@ -1,113 +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 classNames from 'classnames'; - -/*:: -type Props = { - className?: string, - children?: React.Element<*>, - isOpen: boolean, - offset?: { - vertical: number, - horizontal: number - }, - popup: Object, - position: 'bottomleft' | 'bottomright', - togglePopup: (?boolean) => void -}; -*/ - -/*:: -type State = { - position: { top: number, right: number } -}; -*/ - -export default class BubblePopupHelper extends React.PureComponent { - /*:: props: Props; */ - state /*: State */ = { - position: { - top: 0, - right: 0 - } - }; - - componentDidMount() { - this.setState({ position: this.getPosition(this.props) }); - } - - componentWillReceiveProps(nextProps /*: Props */) { - if (!this.props.isOpen && nextProps.isOpen) { - window.addEventListener('keydown', this.handleKey, false); - window.addEventListener('click', this.handleOutsideClick, false); - } else if (this.props.isOpen && !nextProps.isOpen) { - window.removeEventListener('keydown', this.handleKey); - window.removeEventListener('click', this.handleOutsideClick); - } - } - - handleKey = (evt /*: KeyboardEvent */) => { - // Escape key - if (evt.keyCode === 27) { - this.props.togglePopup(false); - } - }; - - handleOutsideClick = (evt /*: SyntheticInputEvent */) => { - if (!this.popupContainer || !this.popupContainer.contains(evt.target)) { - this.props.togglePopup(false); - } - }; - - handleClick(evt /*: SyntheticInputEvent */) { - evt.stopPropagation(); - } - - getPosition(props /*: Props */) { - const containerPos = this.container.getBoundingClientRect(); - const { position } = props; - const offset = props.offset || { vertical: 0, horizontal: 0 }; - if (position === 'bottomleft') { - return { top: containerPos.height + offset.vertical, left: offset.horizontal }; - } else if (position === 'bottomright') { - return { top: containerPos.height + offset.vertical, right: offset.horizontal }; - } - } - - render() { - return ( -
(this.container = container)} - onClick={this.handleClick} - tabIndex={0} - role="tooltip"> - {this.props.children} - {this.props.isOpen && -
(this.popupContainer = popupContainer)}> - {React.cloneElement(this.props.popup, { - popupPosition: this.state.position - })} -
} -
- ); - } -} diff --git a/server/sonar-web/src/main/js/components/common/BubblePopupHelper.tsx b/server/sonar-web/src/main/js/components/common/BubblePopupHelper.tsx new file mode 100644 index 00000000000..f24365adf57 --- /dev/null +++ b/server/sonar-web/src/main/js/components/common/BubblePopupHelper.tsx @@ -0,0 +1,105 @@ +/* + * 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 classNames from 'classnames'; + +interface Props { + className?: string; + children?: React.ReactNode; + isOpen: boolean; + offset?: { vertical: number; horizontal: number }; + popup: React.ReactElement; + position: 'bottomleft' | 'bottomright'; + togglePopup: (show: boolean) => void; +} + +interface State { + position: { top: number; left?: number; right?: number }; +} + +export default class BubblePopupHelper extends React.PureComponent { + container: HTMLElement; + popupContainer: HTMLElement | null; + state: State = { + position: { top: 0, right: 0 } + }; + + componentDidMount() { + this.setState({ position: this.getPosition(this.props) }); + } + + componentWillReceiveProps(nextProps: Props) { + if (!this.props.isOpen && nextProps.isOpen) { + window.addEventListener('keydown', this.handleKey, false); + window.addEventListener('click', this.handleOutsideClick, false); + } else if (this.props.isOpen && !nextProps.isOpen) { + window.removeEventListener('keydown', this.handleKey); + window.removeEventListener('click', this.handleOutsideClick); + } + } + + handleKey = (event: KeyboardEvent) => { + // Escape key + if (event.keyCode === 27) { + this.props.togglePopup(false); + } + }; + + handleOutsideClick = (event: MouseEvent) => { + if (!this.popupContainer || !this.popupContainer.contains(event.target as Node)) { + this.props.togglePopup(false); + } + }; + + handleClick(event: React.SyntheticEvent) { + event.stopPropagation(); + } + + getPosition(props: Props) { + const containerPos = this.container.getBoundingClientRect(); + const { position } = props; + const offset = props.offset || { vertical: 0, horizontal: 0 }; + if (position === 'bottomleft') { + return { top: containerPos.height + offset.vertical, left: offset.horizontal }; + } else { + // if (position === 'bottomright') + return { top: containerPos.height + offset.vertical, right: offset.horizontal }; + } + } + + render() { + return ( +
(this.container = container as HTMLElement)} + onClick={this.handleClick} + tabIndex={0} + role="tooltip"> + {this.props.children} + {this.props.isOpen && +
(this.popupContainer = popupContainer)}> + {React.cloneElement(this.props.popup, { + popupPosition: this.state.position + })} +
} +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BubblePopupHelper-test.js.snap b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BubblePopupHelper-test.js.snap index 3c3b06134cb..9234d2e1550 100644 --- a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BubblePopupHelper-test.js.snap +++ b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BubblePopupHelper-test.js.snap @@ -29,7 +29,6 @@ exports[`should correctly handle clicks on the button 2`] = `
diff --git a/server/sonar-web/src/main/js/store/appState/duck.js b/server/sonar-web/src/main/js/store/appState/duck.js deleted file mode 100644 index 2358bb011fc..00000000000 --- a/server/sonar-web/src/main/js/store/appState/duck.js +++ /dev/null @@ -1,93 +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 -/*:: -type AppState = { - adminPages?: Array<*>, - authenticationError: boolean, - authorizationError: boolean, - organizationsEnabled: boolean, - qualifiers: ?Array -}; -*/ - -/*:: -type SetAppStateAction = { - type: 'SET_APP_STATE', - appState: AppState -}; -*/ - -/*:: -type SetAdminPagesAction = { - type: 'SET_ADMIN_PAGES', - adminPages: Array<*> -}; -*/ - -/*:: -export type Action = SetAppStateAction | SetAdminPagesAction; */ - -export function setAppState(appState /*: AppState */) /*: SetAppStateAction */ { - return { - type: 'SET_APP_STATE', - appState - }; -} - -export function setAdminPages(adminPages /*: Array<*> */) /*: SetAdminPagesAction */ { - return { - type: 'SET_ADMIN_PAGES', - adminPages - }; -} - -export function requireAuthorization() { - return { - type: 'REQUIRE_AUTHORIZATION' - }; -} - -const defaultValue = { - authenticationError: false, - authorizationError: false, - organizationsEnabled: false, - qualifiers: null -}; - -export default function(state /*: AppState */ = defaultValue, action /*: Action */) { - if (action.type === 'SET_APP_STATE') { - return { ...state, ...action.appState }; - } - - if (action.type === 'SET_ADMIN_PAGES') { - return { ...state, adminPages: action.adminPages }; - } - - if (action.type === 'REQUIRE_AUTHORIZATION') { - return { ...state, authorizationError: true }; - } - - return state; -} - -export function getRootQualifiers(state /*: AppState */) { - return state.qualifiers; -} diff --git a/server/sonar-web/src/main/js/store/appState/duck.ts b/server/sonar-web/src/main/js/store/appState/duck.ts new file mode 100644 index 00000000000..ed005f2888f --- /dev/null +++ b/server/sonar-web/src/main/js/store/appState/duck.ts @@ -0,0 +1,83 @@ +/* + * 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. + */ +interface AppState { + adminPages?: any[]; + authenticationError: boolean; + authorizationError: boolean; + organizationsEnabled: boolean; + qualifiers?: string[]; +} + +interface SetAppStateAction { + type: 'SET_APP_STATE'; + appState: AppState; +} + +interface SetAdminPagesAction { + type: 'SET_ADMIN_PAGES'; + adminPages: any[]; +} + +interface RequireAuthorizationAction { + type: 'REQUIRE_AUTHORIZATION'; +} + +export type Action = SetAppStateAction | SetAdminPagesAction | RequireAuthorizationAction; + +export function setAppState(appState: AppState): SetAppStateAction { + return { + type: 'SET_APP_STATE', + appState + }; +} + +export function setAdminPages(adminPages: any[]): SetAdminPagesAction { + return { type: 'SET_ADMIN_PAGES', adminPages }; +} + +export function requireAuthorization(): RequireAuthorizationAction { + return { type: 'REQUIRE_AUTHORIZATION' }; +} + +const defaultValue: AppState = { + authenticationError: false, + authorizationError: false, + organizationsEnabled: false +}; + +export default function(state: AppState = defaultValue, action: Action): AppState { + if (action.type === 'SET_APP_STATE') { + return { ...state, ...action.appState }; + } + + if (action.type === 'SET_ADMIN_PAGES') { + return { ...state, adminPages: action.adminPages }; + } + + if (action.type === 'REQUIRE_AUTHORIZATION') { + return { ...state, authorizationError: true }; + } + + return state; +} + +export function getRootQualifiers(state: AppState): string[] | undefined { + return state.qualifiers; +} diff --git a/server/sonar-web/src/main/js/store/rootActions.js b/server/sonar-web/src/main/js/store/rootActions.js index a698193de32..84ecaba4dd1 100644 --- a/server/sonar-web/src/main/js/store/rootActions.js +++ b/server/sonar-web/src/main/js/store/rootActions.js @@ -33,7 +33,10 @@ export const onFail = dispatch => error => parseError(error).then(message => dispatch(addGlobalErrorMessage(message))); export const fetchAppState = () => dispatch => - getGlobalNavigation().then(appState => dispatch(setAppState(appState)), onFail(dispatch)); + getGlobalNavigation().then(appState => { + dispatch(setAppState(appState)); + return appState; + }, onFail(dispatch)); export const fetchLanguages = () => dispatch => getLanguages().then(languages => dispatch(receiveLanguages(languages)), onFail(dispatch)); -- cgit v1.2.3