From 4a31f1ff8f39bdd5189d75f420ab052ec5a75cd6 Mon Sep 17 00:00:00 2001 From: Pascal Mugnier Date: Thu, 26 Apr 2018 15:31:42 +0200 Subject: [PATCH] SONAR-10609 Rework the global help modal to a dropdown (#177) --- server/sonar-web/.eslintrc | 6 +- server/sonar-web/package.json | 2 +- .../images/embed-doc/google-group-icon.svg | 1 + .../public/images/embed-doc/sc-icon.svg | 1 + .../public/images/embed-doc/so-icon.svg | 1 + .../public/images/embed-doc/sq-icon.svg | 1 + .../public/images/embed-doc/twitter-icon.svg | 1 + server/sonar-web/scripts/start.js | 3 +- .../js/app/components/GlobalContainer.tsx | 38 +++-- .../embed-docs-modal/EmbedDocsPopup.tsx | 159 ++++++++++++++++++ .../embed-docs-modal/EmbedDocsPopupHelper.tsx | 95 +++++++++++ .../EmbedDocsSuggestions.json | 11 ++ .../embed-docs-modal/Suggestions.tsx | 53 ++++++ .../SuggestionsContext.ts} | 13 +- .../embed-docs-modal/SuggestionsProvider.tsx | 84 +++++++++ .../__tests__/EmbedDocsPopup-test.tsx} | 33 ++-- .../EmbedDocsPopup-test.tsx.snap | 151 +++++++++++++++++ .../main/js/app/components/help/GlobalHelp.js | 118 ------------- .../main/js/app/components/help/LinksHelp.js | 69 -------- .../components/help/LinksHelpSonarCloud.js | 65 ------- .../js/app/components/help/ShortcutsHelp.js | 135 --------------- .../help/__tests__/GlobalHelp-test.js | 70 -------- .../__snapshots__/GlobalHelp-test.js.snap | 69 -------- .../app/components/nav/global/GlobalNav.tsx | 47 +----- server/sonar-web/src/main/js/app/theme.js | 1 + .../apps/projects/components/AllProjects.tsx | 2 + .../components/__tests__/AllProjects-test.tsx | 19 +-- .../__snapshots__/AllProjects-test.tsx.snap | 6 + .../main/js/components/common/BubblePopup.tsx | 2 +- .../components/common/BubblePopupHelper.tsx | 3 +- .../sonar-web/src/main/js/typings/json.d.ts | 23 +++ server/sonar-web/yarn.lock | 6 +- .../resources/org/sonar/l10n/core.properties | 12 ++ 33 files changed, 663 insertions(+), 637 deletions(-) create mode 100644 server/sonar-web/public/images/embed-doc/google-group-icon.svg create mode 100644 server/sonar-web/public/images/embed-doc/sc-icon.svg create mode 100644 server/sonar-web/public/images/embed-doc/so-icon.svg create mode 100644 server/sonar-web/public/images/embed-doc/sq-icon.svg create mode 100644 server/sonar-web/public/images/embed-doc/twitter-icon.svg create mode 100644 server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx create mode 100644 server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopupHelper.tsx create mode 100644 server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsSuggestions.json create mode 100644 server/sonar-web/src/main/js/app/components/embed-docs-modal/Suggestions.tsx rename server/sonar-web/src/main/js/app/components/{help/GlobalHelp.d.ts => embed-docs-modal/SuggestionsContext.ts} (74%) create mode 100644 server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsProvider.tsx rename server/sonar-web/src/main/js/app/components/{help/TutorialsHelp.js => embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx} (60%) create mode 100644 server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/app/components/help/GlobalHelp.js delete mode 100644 server/sonar-web/src/main/js/app/components/help/LinksHelp.js delete mode 100644 server/sonar-web/src/main/js/app/components/help/LinksHelpSonarCloud.js delete mode 100644 server/sonar-web/src/main/js/app/components/help/ShortcutsHelp.js delete mode 100644 server/sonar-web/src/main/js/app/components/help/__tests__/GlobalHelp-test.js delete mode 100644 server/sonar-web/src/main/js/app/components/help/__tests__/__snapshots__/GlobalHelp-test.js.snap create mode 100644 server/sonar-web/src/main/js/typings/json.d.ts diff --git a/server/sonar-web/.eslintrc b/server/sonar-web/.eslintrc index f3c47f9df45..801383c08c7 100644 --- a/server/sonar-web/.eslintrc +++ b/server/sonar-web/.eslintrc @@ -1,3 +1,7 @@ { - "extends": "sonarqube" + "extends": "sonarqube", + + "rules": { + "import/extensions": ["error", "never", { "json": "always" }] + } } diff --git a/server/sonar-web/package.json b/server/sonar-web/package.json index a8b3f522569..69537bc59a5 100644 --- a/server/sonar-web/package.json +++ b/server/sonar-web/package.json @@ -103,7 +103,7 @@ "style-loader": "0.20.3", "ts-jest": "22.0.1", "ts-loader": "4.1.0", - "typescript": "2.8.1", + "typescript": "2.8.3", "typescript-eslint-parser": "15.0.0", "webpack": "4.1.1", "webpack-bundle-analyzer": "2.11.1", diff --git a/server/sonar-web/public/images/embed-doc/google-group-icon.svg b/server/sonar-web/public/images/embed-doc/google-group-icon.svg new file mode 100644 index 00000000000..fd581b716c0 --- /dev/null +++ b/server/sonar-web/public/images/embed-doc/google-group-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/server/sonar-web/public/images/embed-doc/sc-icon.svg b/server/sonar-web/public/images/embed-doc/sc-icon.svg new file mode 100644 index 00000000000..bc3d84e95d0 --- /dev/null +++ b/server/sonar-web/public/images/embed-doc/sc-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/server/sonar-web/public/images/embed-doc/so-icon.svg b/server/sonar-web/public/images/embed-doc/so-icon.svg new file mode 100644 index 00000000000..12ba4acd52c --- /dev/null +++ b/server/sonar-web/public/images/embed-doc/so-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/server/sonar-web/public/images/embed-doc/sq-icon.svg b/server/sonar-web/public/images/embed-doc/sq-icon.svg new file mode 100644 index 00000000000..da6e96895ed --- /dev/null +++ b/server/sonar-web/public/images/embed-doc/sq-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/server/sonar-web/public/images/embed-doc/twitter-icon.svg b/server/sonar-web/public/images/embed-doc/twitter-icon.svg new file mode 100644 index 00000000000..55f5f705251 --- /dev/null +++ b/server/sonar-web/public/images/embed-doc/twitter-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/server/sonar-web/scripts/start.js b/server/sonar-web/scripts/start.js index dfb765d1b65..cef0a40eaab 100644 --- a/server/sonar-web/scripts/start.js +++ b/server/sonar-web/scripts/start.js @@ -26,9 +26,9 @@ const WebpackDevServer = require('webpack-dev-server'); const clearConsole = require('react-dev-utils/clearConsole'); const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); const errorOverlayMiddleware = require('react-error-overlay/middleware'); +const getMessages = require('./utils/getMessages'); const getConfig = require('../config/webpack.config'); const paths = require('../config/paths'); -const getMessages = require('./utils/getMessages'); const config = getConfig({ production: false }); @@ -107,7 +107,6 @@ function runDevServer(compiler, host, port, protocol) { proxy: { '/api': proxy, '/fonts': proxy, - '/images': proxy, '/static': proxy, '/integration': proxy } diff --git a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx index a89319b690a..3b81367b34f 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx @@ -22,6 +22,7 @@ import * as PropTypes from 'prop-types'; import GlobalNav from './nav/global/GlobalNav'; import GlobalFooterContainer from './GlobalFooterContainer'; import GlobalMessagesContainer from './GlobalMessagesContainer'; +import SuggestionsProvider from './embed-docs-modal/SuggestionsProvider'; import Workspace from '../../components/workspace/Workspace'; interface Props { @@ -59,23 +60,28 @@ export default class GlobalContainer extends React.PureComponent { // it is important to pass `location` down to `GlobalNav` to trigger render on url change return ( -
-
-
- - - - {this.props.children} - + + {({ suggestions }) => ( +
+
+
+ + + + {this.props.children} + +
+
+
-
- -
+ )} + ); } } diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx b/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx new file mode 100644 index 00000000000..4a75361d7e5 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx @@ -0,0 +1,159 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import * as PropTypes from 'prop-types'; +import { Link } from 'react-router'; +import { SuggestionLink } from './SuggestionsProvider'; +import BubblePopup, { BubblePopupPosition } from '../../../components/common/BubblePopup'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + onClose: () => void; + popupPosition?: BubblePopupPosition; + suggestions: Array; +} + +export default class EmbedDocsPopup extends React.PureComponent { + static contextTypes = { + onSonarCloud: PropTypes.bool, + openOnboardingTutorial: PropTypes.func + }; + + onAnalyzeProjectClick = (event: React.SyntheticEvent) => { + event.preventDefault(); + event.currentTarget.blur(); + this.context.openOnboardingTutorial(); + }; + + renderTitle(text: string) { + return
  • {text}
  • ; + } + + renderSuggestions() { + if (this.props.suggestions.length === 0) { + return null; + } + return ( + <> + {this.renderTitle(translate('embed_docs.suggestion'))} + {this.props.suggestions.map((suggestion, index) => ( +
  • + + {suggestion.text} + +
  • + ))} +
  • + + ); + } + + renderIconLink(link: string, icon: string, text: string) { + return ( + + {text} + {text} + + ); + } + + renderSonarCloudLinks() { + return ( + +
  • + {this.renderTitle(translate('embed_docs.get_support'))} +
  • + {this.renderIconLink( + 'https://about.sonarcloud.io/contact/', + 'sc-icon.svg', + translate('embed_docs.contact_form') + )} +
  • + {this.renderTitle(translate('embed_docs.stay_connected'))} +
  • + {this.renderIconLink('https://about.sonarcloud.io/news/', 'sc-icon.svg', 'Product News')} +
  • +
  • + {this.renderIconLink('https://twitter.com/sonarcloud', 'twitter-icon.svg', 'Twitter')} +
  • + + ); + } + + renderSonarQubeLinks() { + return ( + +
  • + + {translate('embed_docs.analyze_new_project')} + +
  • +
  • + {this.renderTitle(translate('embed_docs.get_support'))} +
  • + {this.renderIconLink( + 'https://groups.google.com/forum/#!forum/sonarqube', + 'google-group-icon.svg', + 'Google Groups' + )} +
  • +
  • + {this.renderIconLink( + 'http://stackoverflow.com/questions/tagged/sonarqube', + 'so-icon.svg', + 'Stack Overflow' + )} +
  • + {this.renderTitle(translate('embed_docs.stay_connected'))} +
  • + {this.renderIconLink('https://blog.sonarsource.com/', 'sq-icon.svg', 'Product News')} +
  • +
  • + {this.renderIconLink('https://twitter.com/SonarQube', 'twitter-icon.svg', 'Twitter')} +
  • +
    + ); + } + + render() { + return ( + +
      + {this.renderSuggestions()} +
    • + + {translate('embed_docs.documentation_index')} + +
    • + {this.context.onSonarCloud && this.renderSonarCloudLinks()} + {!this.context.onSonarCloud && this.renderSonarQubeLinks()} +
    +
    + ); + } +} diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopupHelper.tsx b/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopupHelper.tsx new file mode 100644 index 00000000000..aca948c6aa9 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopupHelper.tsx @@ -0,0 +1,95 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import EmbedDocsPopup from './EmbedDocsPopup'; +import { SuggestionLink } from './SuggestionsProvider'; +import BubblePopupHelper from '../../../components/common/BubblePopupHelper'; +import HelpIcon from '../../../components/icons-components/HelpIcon'; +import Tooltip from '../../../components/controls/Tooltip'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + showTooltip: boolean; + suggestions: Array; + tooltip: boolean; +} +interface State { + helpOpen: boolean; +} + +export default class EmbedDocsPopupHelper extends React.PureComponent { + state: State = { helpOpen: false }; + + componentDidMount() { + window.addEventListener('keypress', this.onKeyPress); + } + + componentWillUnmount() { + 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.toggleHelp(); + } + }; + + setHelpDisplay = (helpOpen: boolean) => { + this.setState({ helpOpen }); + }; + + handleClick = (event: React.MouseEvent) => { + event.preventDefault(); + this.toggleHelp(); + }; + + toggleHelp = () => { + this.setState(state => { + return { helpOpen: !state.helpOpen }; + }); + }; + + closeHelp = () => { + this.setState({ helpOpen: false }); + }; + + render() { + return ( + } + position="bottomleft" + togglePopup={this.setHelpDisplay}> + + + + + + + ); + } +} diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsSuggestions.json b/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsSuggestions.json new file mode 100644 index 00000000000..ae1ce94f6fd --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsSuggestions.json @@ -0,0 +1,11 @@ +{ + "projects": [ + { "link": "#", "text": "Foo Suggestion " }, + { "link": "#", "text": "Bar Suggestion " } + ], + "profiles": [ + { "link": "#", "text": "Foo Suggestion " }, + { "link": "#", "text": "Bar Suggestion " }, + { "link": "#", "text": "Baz Suggestion " } + ] +} diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/Suggestions.tsx b/server/sonar-web/src/main/js/app/components/embed-docs-modal/Suggestions.tsx new file mode 100644 index 00000000000..7ffd50b40cb --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/embed-docs-modal/Suggestions.tsx @@ -0,0 +1,53 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import * as PropTypes from 'prop-types'; +import { SuggestionsContext } from './SuggestionsContext'; + +interface Props { + suggestions: string; +} + +export default class Suggestions extends React.PureComponent { + context!: { suggestions: SuggestionsContext }; + + static contextTypes = { + suggestions: PropTypes.object.isRequired + }; + + componentDidMount() { + this.context.suggestions.addSuggestions(this.props.suggestions); + } + + componentDidUpdate(prevProps: Props) { + if (prevProps.suggestions !== this.props.suggestions) { + this.context.suggestions.removeSuggestions(this.props.suggestions); + this.context.suggestions.addSuggestions(prevProps.suggestions); + } + } + + componentWillUnmount() { + this.context.suggestions.removeSuggestions(this.props.suggestions); + } + + render() { + return null; + } +} diff --git a/server/sonar-web/src/main/js/app/components/help/GlobalHelp.d.ts b/server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsContext.ts similarity index 74% rename from server/sonar-web/src/main/js/app/components/help/GlobalHelp.d.ts rename to server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsContext.ts index 45dcbde901d..8292383c5db 100644 --- a/server/sonar-web/src/main/js/app/components/help/GlobalHelp.d.ts +++ b/server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsContext.ts @@ -17,14 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import * as React from 'react'; -import { CurrentUser, AppState } from '../../types'; - -export interface Props { - currentUser: CurrentUser; - onClose: () => void; - onSonarCloud?: boolean; - onTutorialSelect: () => void; +export interface SuggestionsContext { + addSuggestions: (key: string) => void; + removeSuggestions: (key: string) => void; } - -export default class GlobalHelp extends React.PureComponent {} diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsProvider.tsx b/server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsProvider.tsx new file mode 100644 index 00000000000..833c382e066 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsProvider.tsx @@ -0,0 +1,84 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import * as PropTypes from 'prop-types'; +import * as suggestionsJson from './EmbedDocsSuggestions.json'; +import { SuggestionsContext } from './SuggestionsContext'; + +export interface SuggestionLink { + text: string; + link: string; +} + +interface SuggestionsJson { + [key: string]: Array; +} + +interface Props { + children: ({ suggestions }: { suggestions: Array }) => React.ReactNode; +} + +interface State { + suggestions: Array; +} + +export default class SuggestionsProvider extends React.Component { + keys: string[] = []; + + static childContextTypes = { + suggestions: PropTypes.object + }; + + state = { suggestions: [] }; + + getChildContext = (): { suggestions: SuggestionsContext } => { + return { + suggestions: { + addSuggestions: this.addSuggestions, + removeSuggestions: this.removeSuggestions + } + }; + }; + + fetchSuggestions = () => { + const jsonList = suggestionsJson as SuggestionsJson; + let suggestions: Array = []; + this.keys.forEach(key => { + if (jsonList[key]) { + suggestions = [...jsonList[key], ...suggestions]; + } + }); + this.setState({ suggestions }); + }; + + addSuggestions = (newKey: string) => { + this.keys = [...this.keys, newKey]; + this.fetchSuggestions(); + }; + + removeSuggestions = (oldKey: string) => { + this.keys = this.keys.filter(key => key !== oldKey); + this.fetchSuggestions(); + }; + + render() { + return this.props.children({ suggestions: this.state.suggestions }); + } +} diff --git a/server/sonar-web/src/main/js/app/components/help/TutorialsHelp.js b/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx similarity index 60% rename from server/sonar-web/src/main/js/app/components/help/TutorialsHelp.js rename to server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx index 1bc62190778..647715c847a 100644 --- a/server/sonar-web/src/main/js/app/components/help/TutorialsHelp.js +++ b/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx @@ -17,26 +17,17 @@ * 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'; +import * as React from 'react'; +import { shallow } from 'enzyme'; +import EmbedDocsPopups from '../EmbedDocsPopup'; -/*:: -type Props = { onTutorialSelect: () => void }; -*/ +const suggestions = [{ link: '#', text: 'foo' }, { link: '#', text: 'bar' }]; -export default function TutorialsHelp({ onTutorialSelect } /*: Props */) { - const handleClick = (event /*: Event */) => { - event.preventDefault(); - onTutorialSelect(); - }; - - return ( -
    -

    {translate('help.section.tutorials')}

    - - {translate('tutorials.onboarding')} - -
    - ); -} +it('should display suggestion links', () => { + const context = {}; + const wrapper = shallow(, { + context + }); + wrapper.update(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap b/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap new file mode 100644 index 00000000000..e6405633094 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap @@ -0,0 +1,151 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should display suggestion links 1`] = ` + + + +`; diff --git a/server/sonar-web/src/main/js/app/components/help/GlobalHelp.js b/server/sonar-web/src/main/js/app/components/help/GlobalHelp.js deleted file mode 100644 index 0b9af453a7b..00000000000 --- a/server/sonar-web/src/main/js/app/components/help/GlobalHelp.js +++ /dev/null @@ -1,118 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -// @flow -import React from 'react'; -import classNames from 'classnames'; -import LinksHelp from './LinksHelp'; -import LinksHelpSonarCloud from './LinksHelpSonarCloud'; -import ShortcutsHelp from './ShortcutsHelp'; -import TutorialsHelp from './TutorialsHelp'; -import Modal from '../../../components/controls/Modal'; -import { translate } from '../../../helpers/l10n'; - -/*:: -type Props = { - currentUser: { isLoggedIn: boolean }, - onClose: () => void, - onTutorialSelect: () => void, - onSonarCloud?: boolean -}; -*/ - -/*:: -type State = { - section: string -}; -*/ - -export default class GlobalHelp extends React.PureComponent { - /*:: props: Props; */ - state /*: State */ = { section: 'shortcuts' }; - - handleCloseClick = (event /*: Event */) => { - event.preventDefault(); - this.props.onClose(); - }; - - handleSectionClick = (event /*: Event & { currentTarget: HTMLElement } */) => { - event.preventDefault(); - const { section } = event.currentTarget.dataset; - this.setState({ section }); - }; - - renderSection = () => { - switch (this.state.section) { - case 'shortcuts': - return ; - case 'links': - return this.props.onSonarCloud ? ( - - ) : ( - - ); - case 'tutorials': - return ; - default: - return null; - } - }; - - renderMenuItem = (section /*: string */) => ( -
  • - - {translate('help.section', section)} - -
  • - ); - - renderMenu = () => ( -
      - {(this.props.currentUser.isLoggedIn && !this.props.onSonarCloud - ? ['shortcuts', 'tutorials', 'links'] - : ['shortcuts', 'links'] - ).map(this.renderMenuItem)} -
    - ); - - render() { - return ( - -
    -

    {translate('help')}

    -
    - -
    -
    {this.renderMenu()}
    -
    {this.renderSection()}
    -
    - - -
    - ); - } -} diff --git a/server/sonar-web/src/main/js/app/components/help/LinksHelp.js b/server/sonar-web/src/main/js/app/components/help/LinksHelp.js deleted file mode 100644 index d363c8493d3..00000000000 --- a/server/sonar-web/src/main/js/app/components/help/LinksHelp.js +++ /dev/null @@ -1,69 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -// @flow -import React from 'react'; -import { Link } from 'react-router'; -import { translate } from '../../../helpers/l10n'; - -/*:: -type Props = { onClose: () => void }; -*/ - -export default function LinksHelp({ onClose } /*: Props */) { - return ( -
    -

    {translate('help.section.links')}

    - -

    - {translate('footer.community')} -

    - -

    - - {translate('footer.documentation')} - -

    - -

    - - {translate('footer.support')} - -

    - -

    - - {translate('footer.plugins')} - -

    - -

    - - {translate('footer.web_api')} - -

    - -

    - - {translate('footer.about')} - -

    -
    - ); -} diff --git a/server/sonar-web/src/main/js/app/components/help/LinksHelpSonarCloud.js b/server/sonar-web/src/main/js/app/components/help/LinksHelpSonarCloud.js deleted file mode 100644 index ce276efea8d..00000000000 --- a/server/sonar-web/src/main/js/app/components/help/LinksHelpSonarCloud.js +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -// @flow -import React from 'react'; -import { Link } from 'react-router'; -import { translate } from '../../../helpers/l10n'; - -/*:: -type Props = { onClose: () => void }; -*/ - -export default function LinksHelpSonarCloud({ onClose } /*: Props */) { - return ( -
    -

    {translate('help.section.links')}

    - -

    - {translate('footer.news')} -

    - -

    - {translate('footer.terms')} -

    - -

    - {translate('footer.twitter')} -

    - -

    - {translate('footer.get_started')} -

    - -

    - {translate('footer.help')} -

    - -

    - - {translate('footer.web_api')} - -

    - -

    - {translate('footer.about')} -

    -
    - ); -} diff --git a/server/sonar-web/src/main/js/app/components/help/ShortcutsHelp.js b/server/sonar-web/src/main/js/app/components/help/ShortcutsHelp.js deleted file mode 100644 index 40ce74f4c23..00000000000 --- a/server/sonar-web/src/main/js/app/components/help/ShortcutsHelp.js +++ /dev/null @@ -1,135 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -// @flow -import React from 'react'; -import { translate } from '../../../helpers/l10n'; - -export default function ShortcutsHelp() { - return ( -
    -

    {translate('help.section.shortcuts')}

    - -
    -
    -
    -

    {translate('shortcuts.section.global')}

    -
      -
    • - s - {translate('shortcuts.section.global.search')} -
    • -
    • - ? - {translate('shortcuts.section.global.shortcuts')} -
    • -
    -
    - -

    {translate('shortcuts.section.rules')}

    -
      -
    • - ↑ - ↓ - {translate('shortcuts.section.rules.navigate_between_rules')} -
    • -
    • - → - {translate('shortcuts.section.rules.open_details')} -
    • -
    • - ← - {translate('shortcuts.section.rules.return_to_list')} -
    • -
    • - a - {translate('shortcuts.section.rules.activate')} -
    • -
    • - d - {translate('shortcuts.section.rules.deactivate')} -
    • -
    -
    - -
    -

    {translate('shortcuts.section.issues')}

    -
      -
    • - ↑ - ↓ - {translate('shortcuts.section.issues.navigate_between_issues')} -
    • -
    • - → - {translate('shortcuts.section.issues.open_details')} -
    • -
    • - ← - {translate('shortcuts.section.issues.return_to_list')} -
    • -
    • - alt - + - ↑ - ↓ - {translate('issues.to_navigate_issue_locations')} -
    • -
    • - alt - + - ← - → - {translate('issues.to_switch_flows')} -
    • -
    • - f - {translate('shortcuts.section.issue.do_transition')} -
    • -
    • - a - {translate('shortcuts.section.issue.assign')} -
    • -
    • - m - {translate('shortcuts.section.issue.assign_to_me')} -
    • -
    • - i - {translate('shortcuts.section.issue.change_severity')} -
    • -
    • - c - {translate('shortcuts.section.issue.comment')} -
    • -
    • - ctrl - enter - {translate('shortcuts.section.issue.submit_comment')} -
    • -
    • - t - {translate('shortcuts.section.issue.change_tags')} -
    • -
    -
    -
    -
    - ); -} diff --git a/server/sonar-web/src/main/js/app/components/help/__tests__/GlobalHelp-test.js b/server/sonar-web/src/main/js/app/components/help/__tests__/GlobalHelp-test.js deleted file mode 100644 index bac69262c54..00000000000 --- a/server/sonar-web/src/main/js/app/components/help/__tests__/GlobalHelp-test.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -// @flow -import React from 'react'; -import { shallow } from 'enzyme'; -import GlobalHelp from '../GlobalHelp'; -import { click } from '../../../../helpers/testUtils'; - -it('switches between tabs', () => { - const wrapper = shallow( - - ); - expect(wrapper.find('ShortcutsHelp')).toHaveLength(1); - clickOnSection(wrapper, 'links'); - expect(wrapper.find('LinksHelp')).toHaveLength(1); - clickOnSection(wrapper, 'tutorials'); - expect(wrapper.find('TutorialsHelp')).toHaveLength(1); - clickOnSection(wrapper, 'shortcuts'); - expect(wrapper.find('ShortcutsHelp')).toHaveLength(1); -}); - -it('does not show tutorials for anonymous', () => { - expect( - shallow( - - ) - ).toMatchSnapshot(); -}); - -it('display special links page for SonarCloud', () => { - const wrapper = shallow( - - ); - clickOnSection(wrapper, 'links'); - expect(wrapper.find('LinksHelpSonarCloud')).toHaveLength(1); -}); - -function clickOnSection(wrapper /*: Object */, section /*: string */) { - click(wrapper.find(`[data-section="${section}"]`), { currentTarget: { dataset: { section } } }); -} diff --git a/server/sonar-web/src/main/js/app/components/help/__tests__/__snapshots__/GlobalHelp-test.js.snap b/server/sonar-web/src/main/js/app/components/help/__tests__/__snapshots__/GlobalHelp-test.js.snap deleted file mode 100644 index 972930cce48..00000000000 --- a/server/sonar-web/src/main/js/app/components/help/__tests__/__snapshots__/GlobalHelp-test.js.snap +++ /dev/null @@ -1,69 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`does not show tutorials for anonymous 1`] = ` - -
    -

    - help -

    -
    - - -
    -`; 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 index 84d8cde8305..b91c43abb47 100644 --- 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 @@ -25,16 +25,16 @@ import GlobalNavExplore from './GlobalNavExplore'; import GlobalNavUserContainer from './GlobalNavUserContainer'; import GlobalNavPlus from './GlobalNavPlus'; import Search from '../../search/Search'; -import GlobalHelp from '../../help/GlobalHelp'; +import EmbedDocsPopupHelper from '../../embed-docs-modal/EmbedDocsPopupHelper'; 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 { SuggestionLink } from '../../embed-docs-modal/SuggestionsProvider'; import './GlobalNav.css'; interface StateProps { @@ -52,6 +52,7 @@ interface Props extends StateProps, DispatchProps { isOnboardingTutorialOpen: boolean; location: { pathname: string }; openOnboardingTutorial: () => void; + suggestions: Array; } interface State { @@ -64,7 +65,6 @@ class GlobalNav extends React.PureComponent { state: State = { helpOpen: false, onboardingTutorialTooltip: false }; componentDidMount() { - window.addEventListener('keypress', this.onKeyPress); if (this.props.currentUser.showOnboardingTutorial) { this.openOnboardingTutorial(); } @@ -74,28 +74,8 @@ class GlobalNav extends React.PureComponent { 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(); @@ -120,13 +100,11 @@ class GlobalNav extends React.PureComponent {
    • - - - - - +
    • {isLoggedIn(this.props.currentUser) && @@ -140,15 +118,6 @@ class GlobalNav extends React.PureComponent {
    - {this.state.helpOpen && ( - - )} - {this.props.isOnboardingTutorialOpen && ( )} diff --git a/server/sonar-web/src/main/js/app/theme.js b/server/sonar-web/src/main/js/app/theme.js index 37ff59c71bd..59808a135ba 100644 --- a/server/sonar-web/src/main/js/app/theme.js +++ b/server/sonar-web/src/main/js/app/theme.js @@ -59,6 +59,7 @@ module.exports = { gridSize: `${grid}px`, baseFontSize: '13px', + verySmallFontSize: '10px', smallFontSize: '12px', mediumFontSize: '14px', bigFontSize: '16px', diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx index cd9547a11b4..2f164bcc170 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx @@ -24,6 +24,7 @@ import { omitBy } from 'lodash'; import PageHeader from './PageHeader'; import ProjectsList from './ProjectsList'; import PageSidebar from './PageSidebar'; +import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; import Visualizations from '../visualizations/Visualizations'; import { CurrentUser, isLoggedIn } from '../../../app/types'; import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication'; @@ -317,6 +318,7 @@ export default class AllProjects extends React.PureComponent { render() { return (
    + {this.renderSide()} diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx index e3fac302fb4..b6339e6c3fb 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx @@ -19,7 +19,7 @@ */ /* eslint-disable import/order */ import * as React from 'react'; -import { mount, shallow } from 'enzyme'; +import { shallow } from 'enzyme'; import AllProjects, { Props } from '../AllProjects'; import { get, save } from '../../../../helpers/storage'; @@ -71,7 +71,7 @@ it('renders', () => { }); it('fetches projects', () => { - mountRender(); + shallowRender(); expect(fetchProjects).lastCalledWith( { coverage: undefined, @@ -104,7 +104,7 @@ it('redirects to the saved search', () => { (key: string) => (key === 'sonarqube.projects.view' ? 'leak' : null) ); const replace = jest.fn(); - mountRender({}, jest.fn(), replace); + shallowRender({}, jest.fn(), replace); expect(replace).lastCalledWith({ pathname: '/projects', query: { view: 'leak' } }); }); @@ -161,19 +161,6 @@ it('changes perspective to risk visualization', () => { expect(save).toHaveBeenCalledWith('sonarqube.projects.visualization', 'risk', undefined); }); -function mountRender(props: any = {}, push: Function = jest.fn(), replace: Function = jest.fn()) { - return mount( - , - { context: { router: { push, replace } } } - ); -} - function shallowRender( props: Partial = {}, push: Function = jest.fn(), diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap index 3dd798e4094..ec7df22da87 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap @@ -5,6 +5,9 @@ exports[`renders 1`] = ` className="layout-page projects-page" id="projects-page" > + + { diff --git a/server/sonar-web/src/main/js/typings/json.d.ts b/server/sonar-web/src/main/js/typings/json.d.ts new file mode 100644 index 00000000000..0f858f5d256 --- /dev/null +++ b/server/sonar-web/src/main/js/typings/json.d.ts @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +declare module '*.json' { + const value: any; + export default value; +} diff --git a/server/sonar-web/yarn.lock b/server/sonar-web/yarn.lock index d9881ef214d..7bbd7883247 100644 --- a/server/sonar-web/yarn.lock +++ b/server/sonar-web/yarn.lock @@ -8290,9 +8290,9 @@ typescript-eslint-parser@15.0.0: lodash.unescape "4.0.1" semver "5.5.0" -typescript@2.8.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.8.1.tgz#6160e4f8f195d5ba81d4876f9c0cc1fbc0820624" +typescript@2.8.3: + version "2.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.8.3.tgz#5d817f9b6f31bb871835f4edf0089f21abe6c170" ua-parser-js@^0.7.9: version "0.7.17" 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 331c0b74f45..696cafb5d08 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -2545,6 +2545,18 @@ organization.change_visibility_form.header=Set Default Visibility of New Project organization.change_visibility_form.warning=This will not change the visibility of already existing projects. organization.change_visibility_form.submit=Change Default Visibility +#------------------------------------------------------------------------------ +# +# EMBEDED DOCS +# +#------------------------------------------------------------------------------ +embed_docs.suggestion=Suggestions For This Page +embed_docs.documentation_index=Documentation index +embed_docs.get_support=Get Support +embed_docs.stay_connected=Stay Connected +embed_docs.contact_form=Contact form +embed_docs.analyze_new_project=Analyze new project + #------------------------------------------------------------------------------ # # GLOBAL FOOTER -- 2.39.5