]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10609 Rework the global help modal to a dropdown (#177)
authorPascal Mugnier <pascal.mugnier@sonarsource.com>
Thu, 26 Apr 2018 13:31:42 +0000 (15:31 +0200)
committerSonarTech <sonartech@sonarsource.com>
Thu, 3 May 2018 18:20:50 +0000 (20:20 +0200)
35 files changed:
server/sonar-web/.eslintrc
server/sonar-web/package.json
server/sonar-web/public/images/embed-doc/google-group-icon.svg [new file with mode: 0644]
server/sonar-web/public/images/embed-doc/sc-icon.svg [new file with mode: 0644]
server/sonar-web/public/images/embed-doc/so-icon.svg [new file with mode: 0644]
server/sonar-web/public/images/embed-doc/sq-icon.svg [new file with mode: 0644]
server/sonar-web/public/images/embed-doc/twitter-icon.svg [new file with mode: 0644]
server/sonar-web/scripts/start.js
server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopupHelper.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsSuggestions.json [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/embed-docs-modal/Suggestions.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsContext.ts [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsProvider.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/help/GlobalHelp.d.ts [deleted file]
server/sonar-web/src/main/js/app/components/help/GlobalHelp.js [deleted file]
server/sonar-web/src/main/js/app/components/help/LinksHelp.js [deleted file]
server/sonar-web/src/main/js/app/components/help/LinksHelpSonarCloud.js [deleted file]
server/sonar-web/src/main/js/app/components/help/ShortcutsHelp.js [deleted file]
server/sonar-web/src/main/js/app/components/help/TutorialsHelp.js [deleted file]
server/sonar-web/src/main/js/app/components/help/__tests__/GlobalHelp-test.js [deleted file]
server/sonar-web/src/main/js/app/components/help/__tests__/__snapshots__/GlobalHelp-test.js.snap [deleted file]
server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx
server/sonar-web/src/main/js/app/theme.js
server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap
server/sonar-web/src/main/js/components/common/BubblePopup.tsx
server/sonar-web/src/main/js/components/common/BubblePopupHelper.tsx
server/sonar-web/src/main/js/typings/json.d.ts [new file with mode: 0644]
server/sonar-web/yarn.lock
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index f3c47f9df45d3666a97e9e9c37dbb452947aa547..801383c08c731b4696df4b798a66ccf454977ac9 100644 (file)
@@ -1,3 +1,7 @@
 {
-  "extends": "sonarqube"
+  "extends": "sonarqube",
+
+  "rules": {
+    "import/extensions": ["error", "never", { "json": "always" }]
+  }
 }
index a8b3f5225695db44986273f382fc2867f241c15c..69537bc59a506742a1396c3495fc79b4feda79f2 100644 (file)
     "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 (file)
index 0000000..fd581b7
--- /dev/null
@@ -0,0 +1 @@
+<svg viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M.563 0L.562 10.125H9l3.937 3.375V0H.562zm6.188 2.25a1.13 1.13 0 0 1 1.125 1.125A1.13 1.13 0 0 1 6.751 4.5a1.13 1.13 0 0 1-1.125-1.125A1.13 1.13 0 0 1 6.751 2.25zM3.938 6.917v.396H2.251v-.247c0-.208.395-.808 1.406-.808.18 0 .337.024.477.057-.127.22-.196.433-.196.602zm-.281-1.292a.848.848 0 0 1-.844-.844c0-.463.381-.844.844-.844.463 0 .844.381.844.844a.848.848 0 0 1-.844.844zm5.344 1.687h-4.5v-.395c0-.325.633-1.292 2.25-1.292s2.25.958 2.25 1.292v.395zm0-2.531c0-.463.381-.844.844-.844.463 0 .843.381.843.844 0 .463-.38.844-.843.844a.848.848 0 0 1-.844-.844zm2.25 2.531H9.563v-.395c0-.169-.068-.382-.195-.602.14-.033.297-.057.477-.057 1.011 0 1.406.598 1.406.809v.245z" fill="#4169e1" fill-rule="nonzero"/></svg>
\ 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 (file)
index 0000000..bc3d84e
--- /dev/null
@@ -0,0 +1 @@
+<svg viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M12.625 6.154a3.991 3.991 0 0 0-1.902-1.279v-.046C10.723 2.65 8.93.857 6.75.857c-2.179 0-3.972 1.793-3.972 3.972v.053A3.982 3.982 0 0 0 0 8.671c0 2.179 1.793 3.972 3.972 3.972a3.978 3.978 0 0 0 2.791-1.144 3.97 3.97 0 0 0 2.766 1.122c2.178 0 3.971-1.793 3.971-3.972 0-.905-.31-1.784-.877-2.489l.002-.006zm-3.073 5.489a2.99 2.99 0 0 1-2.973-2.971c0-.275-.225-.5-.5-.5a.501.501 0 0 0-.499.5 3.952 3.952 0 0 0 .56 2.032 2.971 2.971 0 0 1-2.164.936 2.985 2.985 0 0 1-2.97-2.97 2.985 2.985 0 0 1 2.97-2.971c.35 0 .697.062 1.026.183h.012c.114.038.223.09.324.155a.5.5 0 1 0 .65-.759 2.224 2.224 0 0 0-.646-.341 3.974 3.974 0 0 0-1.369-.243h-.192A2.985 2.985 0 0 1 6.75 1.85a2.986 2.986 0 0 1 2.972 2.972c0 .96-.466 1.863-1.249 2.42a.5.5 0 1 0 .58.814 3.983 3.983 0 0 0 1.526-2.184 2.979 2.979 0 0 1 1.941 2.789 2.986 2.986 0 0 1-2.969 2.972l.001.01z" fill="#f3702a" fill-rule="nonzero"/></svg>
\ 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 (file)
index 0000000..12ba4ac
--- /dev/null
@@ -0,0 +1 @@
+<svg viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><g fill-rule="nonzero"><path d="M10.686 12.3V8.683h1.201V13.5H1.052V8.683h1.201V12.3h8.433z" fill="#bcbbbb"/><path d="M3.578 8.34L9.47 9.572l.25-1.185-5.893-1.232-.249 1.185zm.779-2.806l5.456 2.541.499-1.091-5.456-2.557-.499 1.107zm1.512-2.681l4.63 3.85.764-.92-4.63-3.85-.764.92zM8.862 0l-.966.717 3.585 4.833.967-.717L8.862 0zM3.453 11.084H9.47V9.883H3.453v1.201z" fill="#f48023"/></g></svg>
\ 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 (file)
index 0000000..da6e968
--- /dev/null
@@ -0,0 +1 @@
+<svg viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M11.939 13.282h-.756C11.183 7.203 6.167 2.256 0 2.256v-.755c6.585 0 11.939 5.286 11.939 11.781zm.522-4.053c-.906-3.816-3.997-7-7.872-8.111l.173-.603c4.09 1.174 7.353 4.538 8.311 8.57l-.612.144zm.581-3.575A11.572 11.572 0 0 0 8.562.648l.261-.43a12.081 12.081 0 0 1 4.677 5.22l-.458.216z" fill="#4e9bcd" fill-rule="nonzero"/></svg>
\ 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 (file)
index 0000000..55f5f70
--- /dev/null
@@ -0,0 +1 @@
+<svg viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M13.5 2.57a5.52 5.52 0 0 1-1.593.428 2.685 2.685 0 0 0 1.216-1.524 5.431 5.431 0 0 1-1.756.668 2.666 2.666 0 0 0-2.022-.874c-.765 0-1.417.27-1.957.809a2.67 2.67 0 0 0-.809 1.958c0 .205.023.417.068.634a7.724 7.724 0 0 1-3.182-.853A7.84 7.84 0 0 1 .942 1.773a2.713 2.713 0 0 0-.377 1.396c0 .474.112.914.334 1.32.223.405.523.733.9.985a2.746 2.746 0 0 1-1.251-.352v.035c0 .668.21 1.254.63 1.76.42.506.949.824 1.589.955a2.846 2.846 0 0 1-.728.094c-.16 0-.334-.014-.523-.042a2.71 2.71 0 0 0 .977 1.366c.474.357 1.01.541 1.61.552a5.417 5.417 0 0 1-3.435 1.182c-.245 0-.468-.011-.668-.034a7.684 7.684 0 0 0 4.249 1.242c.982 0 1.904-.155 2.766-.467.863-.311 1.599-.728 2.21-1.25a8.407 8.407 0 0 0 1.581-1.803c.442-.68.772-1.39.989-2.129A7.86 7.86 0 0 0 12.112 4 5.805 5.805 0 0 0 13.5 2.57z" fill="#4d9bce" fill-rule="nonzero"/></svg>
\ No newline at end of file
index dfb765d1b6556fc8b96959e296d715c72aa47e0f..cef0a40eaabf59ff1216205f3b4582986b329e51 100644 (file)
@@ -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
     }
index a89319b690af2b8e0767cea5a91627f40c761132..3b81367b34febfd755c89420aa14ac2ee4ccedaf 100644 (file)
@@ -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<Props, State> {
     // it is important to pass `location` down to `GlobalNav` to trigger render on url change
 
     return (
-      <div className="global-container">
-        <div className="page-wrapper" id="container">
-          <div className="page-container">
-            <Workspace>
-              <GlobalNav
-                closeOnboardingTutorial={this.closeOnboardingTutorial}
-                isOnboardingTutorialOpen={this.state.isOnboardingTutorialOpen}
-                location={this.props.location}
-                openOnboardingTutorial={this.openOnboardingTutorial}
-              />
-              <GlobalMessagesContainer />
-              {this.props.children}
-            </Workspace>
+      <SuggestionsProvider>
+        {({ suggestions }) => (
+          <div className="global-container">
+            <div className="page-wrapper" id="container">
+              <div className="page-container">
+                <Workspace>
+                  <GlobalNav
+                    closeOnboardingTutorial={this.closeOnboardingTutorial}
+                    isOnboardingTutorialOpen={this.state.isOnboardingTutorialOpen}
+                    location={this.props.location}
+                    openOnboardingTutorial={this.openOnboardingTutorial}
+                    suggestions={suggestions}
+                  />
+                  <GlobalMessagesContainer />
+                  {this.props.children}
+                </Workspace>
+              </div>
+            </div>
+            <GlobalFooterContainer />
           </div>
-        </div>
-        <GlobalFooterContainer />
-      </div>
+        )}
+      </SuggestionsProvider>
     );
   }
 }
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 (file)
index 0000000..4a75361
--- /dev/null
@@ -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<SuggestionLink>;
+}
+
+export default class EmbedDocsPopup extends React.PureComponent<Props> {
+  static contextTypes = {
+    onSonarCloud: PropTypes.bool,
+    openOnboardingTutorial: PropTypes.func
+  };
+
+  onAnalyzeProjectClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+    event.preventDefault();
+    event.currentTarget.blur();
+    this.context.openOnboardingTutorial();
+  };
+
+  renderTitle(text: string) {
+    return <li className="dropdown-header">{text}</li>;
+  }
+
+  renderSuggestions() {
+    if (this.props.suggestions.length === 0) {
+      return null;
+    }
+    return (
+      <>
+        {this.renderTitle(translate('embed_docs.suggestion'))}
+        {this.props.suggestions.map((suggestion, index) => (
+          <li key={index}>
+            <a href={suggestion.link} target="_blank">
+              {suggestion.text} <i className="icon-detach" />
+            </a>
+          </li>
+        ))}
+        <li className="divider" />
+      </>
+    );
+  }
+
+  renderIconLink(link: string, icon: string, text: string) {
+    return (
+      <a href={link} rel="noopener noreferrer" target="_blank">
+        <img
+          alt={text}
+          className="spacer-right"
+          height="18"
+          src={'/images/embed-doc/' + icon}
+          width="18"
+        />
+        {text}
+      </a>
+    );
+  }
+
+  renderSonarCloudLinks() {
+    return (
+      <React.Fragment>
+        <li className="divider" />
+        {this.renderTitle(translate('embed_docs.get_support'))}
+        <li>
+          {this.renderIconLink(
+            'https://about.sonarcloud.io/contact/',
+            'sc-icon.svg',
+            translate('embed_docs.contact_form')
+          )}
+        </li>
+        {this.renderTitle(translate('embed_docs.stay_connected'))}
+        <li>
+          {this.renderIconLink('https://about.sonarcloud.io/news/', 'sc-icon.svg', 'Product News')}
+        </li>
+        <li>
+          {this.renderIconLink('https://twitter.com/sonarcloud', 'twitter-icon.svg', 'Twitter')}
+        </li>
+      </React.Fragment>
+    );
+  }
+
+  renderSonarQubeLinks() {
+    return (
+      <React.Fragment>
+        <li>
+          <a href="#" onClick={this.onAnalyzeProjectClick}>
+            {translate('embed_docs.analyze_new_project')}
+          </a>
+        </li>
+        <li className="divider" />
+        {this.renderTitle(translate('embed_docs.get_support'))}
+        <li>
+          {this.renderIconLink(
+            'https://groups.google.com/forum/#!forum/sonarqube',
+            'google-group-icon.svg',
+            'Google Groups'
+          )}
+        </li>
+        <li>
+          {this.renderIconLink(
+            'http://stackoverflow.com/questions/tagged/sonarqube',
+            'so-icon.svg',
+            'Stack Overflow'
+          )}
+        </li>
+        {this.renderTitle(translate('embed_docs.stay_connected'))}
+        <li>
+          {this.renderIconLink('https://blog.sonarsource.com/', 'sq-icon.svg', 'Product News')}
+        </li>
+        <li>
+          {this.renderIconLink('https://twitter.com/SonarQube', 'twitter-icon.svg', 'Twitter')}
+        </li>
+      </React.Fragment>
+    );
+  }
+
+  render() {
+    return (
+      <BubblePopup
+        customClass="bubble-popup-bottom bubble-popup-menu abs-width-240 embed-docs-popup"
+        position={this.props.popupPosition}>
+        <ul className="menu">
+          {this.renderSuggestions()}
+          <li>
+            <Link target="_blank" to="/documentation">
+              {translate('embed_docs.documentation_index')} <i className="icon-detach" />
+            </Link>
+          </li>
+          {this.context.onSonarCloud && this.renderSonarCloudLinks()}
+          {!this.context.onSonarCloud && this.renderSonarQubeLinks()}
+        </ul>
+      </BubblePopup>
+    );
+  }
+}
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 (file)
index 0000000..aca948c
--- /dev/null
@@ -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<SuggestionLink>;
+  tooltip: boolean;
+}
+interface State {
+  helpOpen: boolean;
+}
+
+export default class EmbedDocsPopupHelper extends React.PureComponent<Props, State> {
+  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<HTMLAnchorElement>) => {
+    event.preventDefault();
+    this.toggleHelp();
+  };
+
+  toggleHelp = () => {
+    this.setState(state => {
+      return { helpOpen: !state.helpOpen };
+    });
+  };
+
+  closeHelp = () => {
+    this.setState({ helpOpen: false });
+  };
+
+  render() {
+    return (
+      <BubblePopupHelper
+        isOpen={this.state.helpOpen}
+        offset={{ horizontal: 12, vertical: -10 }}
+        popup={<EmbedDocsPopup onClose={this.closeHelp} suggestions={this.props.suggestions} />}
+        position="bottomleft"
+        togglePopup={this.setHelpDisplay}>
+        <Tooltip
+          overlay={this.props.tooltip ? translate('tutorials.follow_later') : undefined}
+          visible={this.props.showTooltip}>
+          <a className="navbar-help" href="#" onClick={this.handleClick}>
+            <HelpIcon />
+          </a>
+        </Tooltip>
+      </BubblePopupHelper>
+    );
+  }
+}
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 (file)
index 0000000..ae1ce94
--- /dev/null
@@ -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 (file)
index 0000000..7ffd50b
--- /dev/null
@@ -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<Props> {
+  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/embed-docs-modal/SuggestionsContext.ts b/server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsContext.ts
new file mode 100644 (file)
index 0000000..8292383
--- /dev/null
@@ -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.
+ */
+export interface SuggestionsContext {
+  addSuggestions: (key: string) => void;
+  removeSuggestions: (key: string) => void;
+}
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 (file)
index 0000000..833c382
--- /dev/null
@@ -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<SuggestionLink>;
+}
+
+interface Props {
+  children: ({ suggestions }: { suggestions: Array<SuggestionLink> }) => React.ReactNode;
+}
+
+interface State {
+  suggestions: Array<SuggestionLink>;
+}
+
+export default class SuggestionsProvider extends React.Component<Props, State> {
+  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<SuggestionLink> = [];
+    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/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx b/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx
new file mode 100644 (file)
index 0000000..647715c
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import EmbedDocsPopups from '../EmbedDocsPopup';
+
+const suggestions = [{ link: '#', text: 'foo' }, { link: '#', text: 'bar' }];
+
+it('should display suggestion links', () => {
+  const context = {};
+  const wrapper = shallow(<EmbedDocsPopups onClose={jest.fn()} suggestions={suggestions} />, {
+    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 (file)
index 0000000..e640563
--- /dev/null
@@ -0,0 +1,151 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display suggestion links 1`] = `
+<BubblePopup
+  customClass="bubble-popup-bottom bubble-popup-menu abs-width-240 embed-docs-popup"
+>
+  <ul
+    className="menu"
+  >
+    <React.Fragment>
+      <li
+        className="dropdown-header"
+      >
+        embed_docs.suggestion
+      </li>
+      <li
+        key="0"
+      >
+        <a
+          href="#"
+          target="_blank"
+        >
+          foo
+           
+          <i
+            className="icon-detach"
+          />
+        </a>
+      </li>
+      <li
+        key="1"
+      >
+        <a
+          href="#"
+          target="_blank"
+        >
+          bar
+           
+          <i
+            className="icon-detach"
+          />
+        </a>
+      </li>
+      <li
+        className="divider"
+      />
+    </React.Fragment>
+    <li>
+      <Link
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        target="_blank"
+        to="/documentation"
+      >
+        embed_docs.documentation_index
+         
+        <i
+          className="icon-detach"
+        />
+      </Link>
+    </li>
+    <React.Fragment>
+      <li>
+        <a
+          href="#"
+          onClick={[Function]}
+        >
+          embed_docs.analyze_new_project
+        </a>
+      </li>
+      <li
+        className="divider"
+      />
+      <li
+        className="dropdown-header"
+      >
+        embed_docs.get_support
+      </li>
+      <li>
+        <a
+          href="https://groups.google.com/forum/#!forum/sonarqube"
+          rel="noopener noreferrer"
+          target="_blank"
+        >
+          <img
+            alt="Google Groups"
+            className="spacer-right"
+            height="18"
+            src="/images/embed-doc/google-group-icon.svg"
+            width="18"
+          />
+          Google Groups
+        </a>
+      </li>
+      <li>
+        <a
+          href="http://stackoverflow.com/questions/tagged/sonarqube"
+          rel="noopener noreferrer"
+          target="_blank"
+        >
+          <img
+            alt="Stack Overflow"
+            className="spacer-right"
+            height="18"
+            src="/images/embed-doc/so-icon.svg"
+            width="18"
+          />
+          Stack Overflow
+        </a>
+      </li>
+      <li
+        className="dropdown-header"
+      >
+        embed_docs.stay_connected
+      </li>
+      <li>
+        <a
+          href="https://blog.sonarsource.com/"
+          rel="noopener noreferrer"
+          target="_blank"
+        >
+          <img
+            alt="Product News"
+            className="spacer-right"
+            height="18"
+            src="/images/embed-doc/sq-icon.svg"
+            width="18"
+          />
+          Product News
+        </a>
+      </li>
+      <li>
+        <a
+          href="https://twitter.com/SonarQube"
+          rel="noopener noreferrer"
+          target="_blank"
+        >
+          <img
+            alt="Twitter"
+            className="spacer-right"
+            height="18"
+            src="/images/embed-doc/twitter-icon.svg"
+            width="18"
+          />
+          Twitter
+        </a>
+      </li>
+    </React.Fragment>
+  </ul>
+</BubblePopup>
+`;
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
deleted file mode 100644 (file)
index 45dcbde..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import * as React from 'react';
-import { CurrentUser, AppState } from '../../types';
-
-export interface Props {
-  currentUser: CurrentUser;
-  onClose: () => void;
-  onSonarCloud?: boolean;
-  onTutorialSelect: () => void;
-}
-
-export default class GlobalHelp extends React.PureComponent<Props> {}
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 (file)
index 0b9af45..0000000
+++ /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 <ShortcutsHelp />;
-      case 'links':
-        return this.props.onSonarCloud ? (
-          <LinksHelpSonarCloud onClose={this.props.onClose} />
-        ) : (
-          <LinksHelp onClose={this.props.onClose} />
-        );
-      case 'tutorials':
-        return <TutorialsHelp onTutorialSelect={this.props.onTutorialSelect} />;
-      default:
-        return null;
-    }
-  };
-
-  renderMenuItem = (section /*: string */) => (
-    <li key={section}>
-      <a
-        className={classNames({ active: section === this.state.section })}
-        data-section={section}
-        href="#"
-        onClick={this.handleSectionClick}>
-        {translate('help.section', section)}
-      </a>
-    </li>
-  );
-
-  renderMenu = () => (
-    <ul className="side-tabs-menu">
-      {(this.props.currentUser.isLoggedIn && !this.props.onSonarCloud
-        ? ['shortcuts', 'tutorials', 'links']
-        : ['shortcuts', 'links']
-      ).map(this.renderMenuItem)}
-    </ul>
-  );
-
-  render() {
-    return (
-      <Modal contentLabel={translate('help')} medium={true} onRequestClose={this.props.onClose}>
-        <div className="modal-head">
-          <h2>{translate('help')}</h2>
-        </div>
-
-        <div className="side-tabs-layout">
-          <div className="side-tabs-side">{this.renderMenu()}</div>
-          <div className="side-tabs-main">{this.renderSection()}</div>
-        </div>
-
-        <div className="modal-foot">
-          <a className="js-modal-close" href="#" onClick={this.handleCloseClick}>
-            {translate('close')}
-          </a>
-        </div>
-      </Modal>
-    );
-  }
-}
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 (file)
index d363c84..0000000
+++ /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 (
-    <div>
-      <h2 className="spacer-top spacer-bottom">{translate('help.section.links')}</h2>
-
-      <p className="spacer-bottom">
-        <a href="http://www.sonarqube.org">{translate('footer.community')}</a>
-      </p>
-
-      <p className="spacer-bottom">
-        <a href="https://redirect.sonarsource.com/doc/home.html">
-          {translate('footer.documentation')}
-        </a>
-      </p>
-
-      <p className="spacer-bottom">
-        <a href="https://redirect.sonarsource.com/doc/community.html">
-          {translate('footer.support')}
-        </a>
-      </p>
-
-      <p className="spacer-bottom">
-        <a href="https://redirect.sonarsource.com/doc/plugin-library.html">
-          {translate('footer.plugins')}
-        </a>
-      </p>
-
-      <p className="spacer-bottom">
-        <Link to="/web_api" onClick={onClose}>
-          {translate('footer.web_api')}
-        </Link>
-      </p>
-
-      <p>
-        <Link to="/about" onClick={onClose}>
-          {translate('footer.about')}
-        </Link>
-      </p>
-    </div>
-  );
-}
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 (file)
index ce276ef..0000000
+++ /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 (
-    <div>
-      <h2 className="spacer-top spacer-bottom">{translate('help.section.links')}</h2>
-
-      <p className="spacer-bottom">
-        <a href="https://about.sonarcloud.io/news/">{translate('footer.news')}</a>
-      </p>
-
-      <p className="spacer-bottom">
-        <a href="https://about.sonarcloud.io/terms.pdf">{translate('footer.terms')}</a>
-      </p>
-
-      <p className="spacer-bottom">
-        <a href="https://twitter.com/sonarqube">{translate('footer.twitter')}</a>
-      </p>
-
-      <p className="spacer-bottom">
-        <a href="https://about.sonarcloud.io/get-started/">{translate('footer.get_started')}</a>
-      </p>
-
-      <p className="spacer-bottom">
-        <a href="https://about.sonarcloud.io/contact/">{translate('footer.help')}</a>
-      </p>
-
-      <p className="spacer-bottom">
-        <Link to="/web_api" onClick={onClose}>
-          {translate('footer.web_api')}
-        </Link>
-      </p>
-
-      <p>
-        <a href="https://about.sonarcloud.io/">{translate('footer.about')}</a>
-      </p>
-    </div>
-  );
-}
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 (file)
index 40ce74f..0000000
+++ /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 (
-    <div>
-      <h2 className="spacer-top spacer-bottom">{translate('help.section.shortcuts')}</h2>
-
-      <div className="columns">
-        <div className="column-half">
-          <div className="spacer-bottom">
-            <h3 className="shortcuts-section-title">{translate('shortcuts.section.global')}</h3>
-            <ul className="shortcuts-list">
-              <li>
-                <span className="shortcut-button spacer-right">s</span>
-                {translate('shortcuts.section.global.search')}
-              </li>
-              <li>
-                <span className="shortcut-button spacer-right">?</span>
-                {translate('shortcuts.section.global.shortcuts')}
-              </li>
-            </ul>
-          </div>
-
-          <h3 className="shortcuts-section-title">{translate('shortcuts.section.rules')}</h3>
-          <ul className="shortcuts-list">
-            <li>
-              <span className="shortcut-button little-spacer-right">↑</span>
-              <span className="shortcut-button spacer-right">↓</span>
-              {translate('shortcuts.section.rules.navigate_between_rules')}
-            </li>
-            <li>
-              <span className="shortcut-button spacer-right">→</span>
-              {translate('shortcuts.section.rules.open_details')}
-            </li>
-            <li>
-              <span className="shortcut-button spacer-right">←</span>
-              {translate('shortcuts.section.rules.return_to_list')}
-            </li>
-            <li>
-              <span className="shortcut-button spacer-right">a</span>
-              {translate('shortcuts.section.rules.activate')}
-            </li>
-            <li>
-              <span className="shortcut-button spacer-right">d</span>
-              {translate('shortcuts.section.rules.deactivate')}
-            </li>
-          </ul>
-        </div>
-
-        <div className="column-half">
-          <h3 className="shortcuts-section-title">{translate('shortcuts.section.issues')}</h3>
-          <ul className="shortcuts-list">
-            <li>
-              <span className="shortcut-button little-spacer-right">↑</span>
-              <span className="shortcut-button spacer-right">↓</span>
-              {translate('shortcuts.section.issues.navigate_between_issues')}
-            </li>
-            <li>
-              <span className="shortcut-button spacer-right">→</span>
-              {translate('shortcuts.section.issues.open_details')}
-            </li>
-            <li>
-              <span className="shortcut-button spacer-right">←</span>
-              {translate('shortcuts.section.issues.return_to_list')}
-            </li>
-            <li>
-              <span className="shortcut-button little-spacer-right">alt</span>
-              <span className="little-spacer-right">+</span>
-              <span className="shortcut-button little-spacer-right">↑</span>
-              <span className="shortcut-button spacer-right">↓</span>
-              {translate('issues.to_navigate_issue_locations')}
-            </li>
-            <li>
-              <span className="shortcut-button little-spacer-right">alt</span>
-              <span className="little-spacer-right">+</span>
-              <span className="shortcut-button little-spacer-right">←</span>
-              <span className="shortcut-button spacer-right">→</span>
-              {translate('issues.to_switch_flows')}
-            </li>
-            <li>
-              <span className="shortcut-button spacer-right">f</span>
-              {translate('shortcuts.section.issue.do_transition')}
-            </li>
-            <li>
-              <span className="shortcut-button spacer-right">a</span>
-              {translate('shortcuts.section.issue.assign')}
-            </li>
-            <li>
-              <span className="shortcut-button spacer-right">m</span>
-              {translate('shortcuts.section.issue.assign_to_me')}
-            </li>
-            <li>
-              <span className="shortcut-button spacer-right">i</span>
-              {translate('shortcuts.section.issue.change_severity')}
-            </li>
-            <li>
-              <span className="shortcut-button spacer-right">c</span>
-              {translate('shortcuts.section.issue.comment')}
-            </li>
-            <li>
-              <span className="shortcut-button little-spacer-right">ctrl</span>
-              <span className="shortcut-button spacer-right">enter</span>
-              {translate('shortcuts.section.issue.submit_comment')}
-            </li>
-            <li>
-              <span className="shortcut-button spacer-right">t</span>
-              {translate('shortcuts.section.issue.change_tags')}
-            </li>
-          </ul>
-        </div>
-      </div>
-    </div>
-  );
-}
diff --git a/server/sonar-web/src/main/js/app/components/help/TutorialsHelp.js b/server/sonar-web/src/main/js/app/components/help/TutorialsHelp.js
deleted file mode 100644 (file)
index 1bc6219..0000000
+++ /dev/null
@@ -1,42 +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';
-
-/*::
-type Props = { onTutorialSelect: () => void };
-*/
-
-export default function TutorialsHelp({ onTutorialSelect } /*: Props */) {
-  const handleClick = (event /*: Event */) => {
-    event.preventDefault();
-    onTutorialSelect();
-  };
-
-  return (
-    <div>
-      <h2 className="spacer-top spacer-bottom">{translate('help.section.tutorials')}</h2>
-      <a href="#" onClick={handleClick}>
-        {translate('tutorials.onboarding')}
-      </a>
-    </div>
-  );
-}
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 (file)
index bac6926..0000000
+++ /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(
-    <GlobalHelp
-      currentUser={{ isLoggedIn: true }}
-      onClose={jest.fn()}
-      onTutorialSelect={jest.fn()}
-    />
-  );
-  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(
-      <GlobalHelp
-        currentUser={{ isLoggedIn: false }}
-        onClose={jest.fn()}
-        onTutorialSelect={jest.fn()}
-      />
-    )
-  ).toMatchSnapshot();
-});
-
-it('display special links page for SonarCloud', () => {
-  const wrapper = shallow(
-    <GlobalHelp
-      currentUser={{ isLoggedIn: false }}
-      onClose={jest.fn()}
-      onTutorialSelect={jest.fn()}
-      onSonarCloud={true}
-    />
-  );
-  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 (file)
index 972930c..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`does not show tutorials for anonymous 1`] = `
-<Modal
-  contentLabel="help"
-  medium={true}
-  onRequestClose={[MockFunction]}
->
-  <div
-    className="modal-head"
-  >
-    <h2>
-      help
-    </h2>
-  </div>
-  <div
-    className="side-tabs-layout"
-  >
-    <div
-      className="side-tabs-side"
-    >
-      <ul
-        className="side-tabs-menu"
-      >
-        <li
-          key="shortcuts"
-        >
-          <a
-            className="active"
-            data-section="shortcuts"
-            href="#"
-            onClick={[Function]}
-          >
-            help.section.shortcuts
-          </a>
-        </li>
-        <li
-          key="links"
-        >
-          <a
-            className=""
-            data-section="links"
-            href="#"
-            onClick={[Function]}
-          >
-            help.section.links
-          </a>
-        </li>
-      </ul>
-    </div>
-    <div
-      className="side-tabs-main"
-    >
-      <ShortcutsHelp />
-    </div>
-  </div>
-  <div
-    className="modal-foot"
-  >
-    <a
-      className="js-modal-close"
-      href="#"
-      onClick={[Function]}
-    >
-      close
-    </a>
-  </div>
-</Modal>
-`;
index 84d8cde83054f7997a2bc715bed447ad4f194d13..b91c43abb47a4a6f5c42b4e62008a8e990357d9f 100644 (file)
@@ -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<SuggestionLink>;
 }
 
 interface State {
@@ -64,7 +65,6 @@ class GlobalNav extends React.PureComponent<Props, State> {
   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<Props, State> {
     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<HTMLAnchorElement>) => {
-    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<Props, State> {
         <ul className="global-navbar-menu pull-right">
           <GlobalNavExplore location={this.props.location} onSonarCloud={this.props.onSonarCloud} />
           <li>
-            <Tooltip
-              overlay={this.props.onSonarCloud ? undefined : translate('tutorials.follow_later')}
-              visible={this.state.onboardingTutorialTooltip}>
-              <a className="navbar-help" href="#" onClick={this.handleHelpClick}>
-                <HelpIcon />
-              </a>
-            </Tooltip>
+            <EmbedDocsPopupHelper
+              showTooltip={this.state.onboardingTutorialTooltip}
+              suggestions={this.props.suggestions}
+              tooltip={!this.props.onSonarCloud}
+            />
           </li>
           <Search appState={this.props.appState} currentUser={this.props.currentUser} />
           {isLoggedIn(this.props.currentUser) &&
@@ -140,15 +118,6 @@ class GlobalNav extends React.PureComponent<Props, State> {
           <GlobalNavUserContainer {...this.props} />
         </ul>
 
-        {this.state.helpOpen && (
-          <GlobalHelp
-            currentUser={this.props.currentUser}
-            onClose={this.closeHelp}
-            onSonarCloud={this.props.onSonarCloud}
-            onTutorialSelect={this.openOnboardingTutorial}
-          />
-        )}
-
         {this.props.isOnboardingTutorialOpen && (
           <OnboardingModal onFinish={this.closeOnboardingTutorial} />
         )}
index 37ff59c71bdbe90997fcf2698101ee09e68705dc..59808a135ba4ed0723cb1f68139f5460f205500e 100644 (file)
@@ -59,6 +59,7 @@ module.exports = {
   gridSize: `${grid}px`,
 
   baseFontSize: '13px',
+  verySmallFontSize: '10px',
   smallFontSize: '12px',
   mediumFontSize: '14px',
   bigFontSize: '16px',
index cd9547a11b494b0e5f66f844fb4e37a24e285e3f..2f164bcc17094b1fe88709f2ff0fc573fa8b3743 100644 (file)
@@ -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<Props, State> {
   render() {
     return (
       <div className="layout-page projects-page" id="projects-page">
+        <Suggestions suggestions="projects" />
         <Helmet title={translate('projects.page')} />
 
         {this.renderSide()}
index e3fac302fb46ef54ba55158df9c9dda7ee181d03..b6339e6c3fbabb65d98b90217ff8b5f7e0c12a0e 100644 (file)
@@ -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(
-    <AllProjects
-      currentUser={{ isLoggedIn: true }}
-      fetchProjects={jest.fn()}
-      isFavorite={false}
-      location={{ pathname: '/projects', query: {} }}
-      {...props}
-    />,
-    { context: { router: { push, replace } } }
-  );
-}
-
 function shallowRender(
   props: Partial<Props> = {},
   push: Function = jest.fn(),
index 3dd798e4094c993436d1cdb861451f7ca2f58901..ec7df22da87f7515240865337973655e3482ffc8 100644 (file)
@@ -5,6 +5,9 @@ exports[`renders 1`] = `
   className="layout-page projects-page"
   id="projects-page"
 >
+  <Suggestions
+    suggestions="projects"
+  />
   <HelmetWrapper
     defer={true}
     encodeSpecialCharacters={true}
@@ -134,6 +137,9 @@ exports[`renders 2`] = `
   className="layout-page projects-page"
   id="projects-page"
 >
+  <Suggestions
+    suggestions="projects"
+  />
   <HelmetWrapper
     defer={true}
     encodeSpecialCharacters={true}
index 7020ee5749056225d83572cd2596f2d877d6c483..e23a99b7e2514f898c74158a3a1b3a3414a5a7ee 100644 (file)
@@ -29,7 +29,7 @@ export interface BubblePopupPosition {
 interface Props {
   customClass?: string;
   children: React.ReactNode;
-  position: BubblePopupPosition;
+  position?: BubblePopupPosition;
 }
 
 /**
index 627dad9cde527f25df90c99c18f14528b4c8beaa..9f66d022cc9a292a650a02fce0860f079d6202a5 100644 (file)
@@ -19,6 +19,7 @@
  */
 import * as React from 'react';
 import * as classNames from 'classnames';
+import { BubblePopupPosition } from './BubblePopup';
 
 interface Props {
   className?: string;
@@ -31,7 +32,7 @@ interface Props {
 }
 
 interface State {
-  position: { top: number; left?: number; right?: number };
+  position: BubblePopupPosition;
 }
 
 export default class BubblePopupHelper extends React.PureComponent<Props, State> {
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 (file)
index 0000000..0f858f5
--- /dev/null
@@ -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;
+}
index d9881ef214d274107e40477a67f6e3c9430dc37e..7bbd78832473d4f8ef0f2127d48ed08c09c72e42 100644 (file)
@@ -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"
index 331c0b74f453684d9aa824b2778d71564a4b1f5b..696cafb5d083b84f52c072af5eef45733878b086 100644 (file)
@@ -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