]> source.dussan.org Git - sonarqube.git/commitdiff
MMF-1319 Document SC specific features (#318)
authorlaurawacrenier <laurawacrenier@gmail.com>
Tue, 12 Jun 2018 08:33:12 +0000 (10:33 +0200)
committerSonarTech <sonartech@sonarsource.com>
Tue, 12 Jun 2018 18:20:58 +0000 (20:20 +0200)
* Adding pages to SonarCloud doc
* Add page for paid plan
* Adding documentation about integrations in SonarCloud
* Add page suggestions
* Add inline documentation
* Allow to reference SonarCloud links in the documentation
* Update the way to install the Bitbucket Cloud App since it is now available in the marketplace

23 files changed:
server/sonar-docs/src/EmbedDocsSuggestions.json
server/sonar-docs/src/pages/analyze-a-project.md [new file with mode: 0644]
server/sonar-docs/src/pages/integrations/bitbucketcloud.md [new file with mode: 0644]
server/sonar-docs/src/pages/integrations/github.md [new file with mode: 0644]
server/sonar-docs/src/pages/integrations/index.md [new file with mode: 0644]
server/sonar-docs/src/pages/integrations/vsts.md [new file with mode: 0644]
server/sonar-docs/src/pages/sonarcloud-pricing.md [new file with mode: 0644]
server/sonar-docs/src/tooltips/organizations/organization.md [new file with mode: 0644]
server/sonar-web/src/main/js/apps/account/organizations/CreateOrganizationForm.tsx
server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.js
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationPage-test.js.snap
server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationStep.js
server/sonar-web/src/main/js/apps/tutorials/onboarding/Step.js
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OrganizationStep-test.js.snap
server/sonar-web/src/main/js/components/docs/DocLink.tsx
server/sonar-web/src/main/js/components/docs/DocMarkdownBlock.tsx
server/sonar-web/src/main/js/components/docs/DocTooltip.tsx
server/sonar-web/src/main/js/components/docs/DocTooltipLink.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/docs/__tests__/DocLink-test.tsx
server/sonar-web/src/main/js/components/docs/__tests__/DocTooltipLink-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocLink-test.tsx.snap
server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocTooltip-test.tsx.snap
server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocTooltipLink-test.tsx.snap [new file with mode: 0644]

index fce00fdf5497574feef6a5fc6db860549dd6a7fd..8408b1cde585392cd30d9c20468a77e3e5560be6 100644 (file)
   ],
   "custom_measures": [],
   "custom_metrics": [],
-  "global_permissions": [],
+  "global_permissions": [
+    {
+      "link": "/documentation/organizations/manage-team",
+      "text": "Manage a Team",
+      "scope": "sonarcloud"
+    }
+  ],
   "issues": [
     {
       "link": "/documentation/keyboard-shortcuts",
     }
   ],
   "organization_projects": [
+    {
+      "link": "/documentation/organizations/manage-team",
+      "text": "Manage a Team",
+      "scope": "sonarcloud"
+    }
+  ],
+  "organization_space": [
     {
       "link": "/documentation/organizations/index",
       "text": "Organizations",
   "project_activity": [],
   "project_quality_gate": [],
   "project_quality_profiles": [],
-  "projects_management": [],
-  "projects": [],
+  "projects_management": [
+    {
+      "link": "/documentation/analyze-a-project",
+      "text": "Analyze a Project",
+      "scope": "sonarcloud"
+    }
+  ],
+  "projects": [
+    {
+      "link": "/documentation/analyze-a-project",
+      "text": "Analyze a Project",
+      "scope": "sonarcloud"
+    }
+  ],
   "quality_gates": [
     {
       "link": "/documentation/fixing-the-water-leak",
   ],
   "settings": [],
   "system_info": [],
-  "user_groups": [],
+  "user_groups": [
+    {
+      "link": "/documentation/organizations/manage-team",
+      "text": "Manage a Team",
+      "scope": "sonarcloud"
+    }
+  ],
   "users": [],
   "webhooks": []
 }
diff --git a/server/sonar-docs/src/pages/analyze-a-project.md b/server/sonar-docs/src/pages/analyze-a-project.md
new file mode 100644 (file)
index 0000000..d0c4ddc
--- /dev/null
@@ -0,0 +1,25 @@
+---
+title: Analyze a Project
+scope: sonarcloud
+---
+
+## Prepare your organization
+
+A project must belong to an [organization](/organizations/index). Create one if you intend to collaborate with your team mates, or use your personal organization for test purposes.
+
+** /!\ Important note for private code:** Newly created organizations and personal organizations are under a free plan by default. This means projects analyzed on these organizations are public by default: the code will be browsable by anyone. If you want private projects, you should [upgrade your organization to a paid plan](/sonarcloud-pricing) in the "Administration > Billing" page of your organization.
+
+Find the key of your organization, you will need it at later stages. It can be found on the top right corner of your organization's header.
+
+## Run analysis
+
+SonarCloud currently does not trigger analyses automatically - this feature will come in a near future. Currently, it's up to you to launch them inside your
+existing CI scripts.
+
+Depending on which cloud solution you are using for your developments, you can rely on dedicated integrations to help you:
+
+* VSTS: [read our dedicated documentation](/integrations/vsts)
+* Bitbucket Cloud: [read our dedicated documentation](/integrations/bitbucketcloud)
+* GitHub: [read our dedicated documentation](/integrations/github)
+
+If you are not using those solutions, you will have to find out what command to execute to run the analysis. Our [tutorial](/#sonarcloud#/onboarding) will help you on this.
diff --git a/server/sonar-docs/src/pages/integrations/bitbucketcloud.md b/server/sonar-docs/src/pages/integrations/bitbucketcloud.md
new file mode 100644 (file)
index 0000000..4fd2e77
--- /dev/null
@@ -0,0 +1,79 @@
+---
+title: Integration with Bitbucket Cloud
+scope: sonarcloud
+---
+
+## Authentication
+
+You can connect to SonarCloud using your Bitbucket Cloud account. On the [login page](/#sonarcloud#/sessions/new), just click on the "Log in with Bitbucket" button.
+
+## Install SonarCloud add-on for Bitbucket Cloud
+
+Our Bitbucket Cloud application allows users to automate the SonarCloud analysis with Pipelines. It also allows users to view their SonarCloud metrics directly on Bitbucket Cloud via our Code Quality widget and the decoration of pull-requests.
+
+In Bitbucket Cloud, go to your team's "Settings > Find integrations" page, search for "SonarCloud" in the "Code Quality" category and click on "Add" to install the application.
+
+## Analyzing with Pipelines
+
+SonarCloud integrates with Bitbucket Pipelines to make it easier to trigger analyses. Follow the steps:
+
+1.  On SonarCloud, open and follow the "New Project" tutorial available from the `+` icon available at the top right part of the screen. You can copy-paste the command line displayed at the end.
+
+2.  On Bitbucket Cloud, go to the "Settings > Pipelines > Environment variables" page of your team, and add a new SONAR_TOKEN variable that contains the value of the SonarCloud token (something like `9ad01c85336b265406fa6554a9a681a4b281135f`) which you created during the [tutorial](/#sonarcloud#/onboarding) (and which is available inside the command line that you copy-pasted). **Make sure that you click on the "Lock" icon to encrypt and hide this token.**
+
+3.  Inside the `bitbucket-pipelines.yml` file of your repository, copy the command line provided by the tutorial and replace the actual token by its variable name. For example, for a Java Maven-based project, you should have something like:
+
+```
+script:
+  -mvn sonar:sonar -Dsonar.host.url=https://sonarcloud.io -Dsonar.organization=my-team-org -Dsonar.login=$SONAR_TOKEN
+```
+
+When this change on `bitbucket-pipelines.yml` is committed and pushed, Pipelines should automatically run a new build and therefore trigger the analysis of the repository. Shortly after, your project will appear on SonarCloud in your organization.
+
+4.  Once you see your project in SonarCloud, go to the Bitbucket Cloud "Settings > SonarCloud" page of your repository and find it in the select box to link it.
+
+From now on, everytime Pipelines triggers a build, SonarCloud will:
+
+* Analyze every new branch that contains the change on the `bitbucket-pipelines.yml` file.
+* Analyze and decorate every pull request based on such a branch.
+
+## Quality widget
+
+SonarCloud provides a widget that shows the current quality metrics of your project directly on the repository's Overview page on Bitbucket Cloud.
+
+If you have configured the analysis with Pipelines as described above, you will see this widget on the "Overview" page of your repository.
+
+If you haven't configured the analysis with Pipelines (maybe because you are using another CI tool), follow these few steps:
+
+1.  Go to the repository where you want to display the widget. On the "Overview" page, you should see an empty SonarCloud widget. Click on the link inside the empty widget or just go directly to your repository's "Settings > SonarCloud Settings".
+
+2.  If you're not already logged in on SonarCloud, do it by using the options provided there. You can log in with Bitbucket Cloud by clicking on the blue button, or click on "More options" to log in with GitHub or VSTS.
+
+3.  Once you're logged in on SonarCloud, you should see a dropdown allowing you to choose one of the projects you administer. Just choose the one you want to link to this Bitbucket repository and click "Save".
+
+4.  You can now go back to your repository's Overview page on Bitbucket and see the widget with all current SonarCloud metrics displayed.
+
+If you want to hide this widget (e.g. because your repository is not analyzed on SonarCloud), you can go to the "Settings > SonarCloud" page of your repository and check "Hide repository overview widget".
+
+## FAQ
+
+**Do you have a sample project on Bitbucket Cloud?**
+For the time being, you can take a look at this very simple JS project: [Sample project analysed on SonarCloud](https://bitbucket.org/bellingard/fab)
+
+**Pipelines can't find sonar-scanner**
+If you want to analyze a non-Java project (JS, TS, PHP, Python, Go, ...), you will need to download and install the [Scanner CLI](https://redirect.sonarsource.com/doc/install-configure-scanner.html) during the execution of your build prior to the actual code scan. You have two options:
+
+* You can download it (with curl for instance) from the links available on the documentation page and unpack it (preferably in a cached folder for later reuse).
+* On Node environments, you can rely on a [community NPM module](https://www.npmjs.com/package/sonarqube-scanner) to install it globally and therefore make it available in the PATH.
+
+**I don't see the any quality information whereas I configured everything**
+Make sure that your browser is not using some extensions like AdBlocks. They tend to break the integration of third-party applications in BitBucket Cloud.
+
+## Upcoming features and improvements
+
+There are various areas in which you can expect new features and improvements:
+
+* Tighter integration with Pipelines (less parameters to pass on the CLI, availability of the scanner, ...)
+* Pull request decoration with inline comments to show the issues within the PR
+* Better and easier team onboarding
+* Automatic analysis (i.e. no need to configure anything from Pipelines)
diff --git a/server/sonar-docs/src/pages/integrations/github.md b/server/sonar-docs/src/pages/integrations/github.md
new file mode 100644 (file)
index 0000000..ec29779
--- /dev/null
@@ -0,0 +1,31 @@
+---
+title: Integration with GitHub
+scope: sonarcloud
+---
+
+## Authentication
+
+You can connect to SonarCloud using your GitHub account. On the [login page](/#sonarcloud#/sessions/new), just click on the "Log in with GitHub" button.
+
+## Trigger analyses
+
+SonarCloud currently does not trigger analyses automatically. It's up to you to launch them inside your
+existing CI scripts. Please follow the [tutorial](/#sonarcloud#/onboarding) to get started.
+
+If you are using Travis CI, the SonarCloud Travis Add-on will make it easier to activate analyses. Simply follow
+the [guide to integrating with Travis CI](https://docs.travis-ci.com/user/sonarcloud/).
+
+## Activating pull request decoration
+
+To have your pull requests decorated by SonarCloud in GitHub, you need to [install the SonarCloud application](https://github.com/apps/sonarcloud) on your GitHub organization(s).
+
+Once installed, there is nothing more to do if you are using the Travis Add-on. In any other case, you will need
+to pass the following properties in your script during the analysis:
+
+```
+sonar.pullrequest.base=master
+sonar.pullrequest.branch=feature/my-new-feature
+sonar.pullrequest.key=5
+sonar.pullrequest.provider=GitHub
+sonar.pullrequest.github.repository=my-company/my-repo
+```
diff --git a/server/sonar-docs/src/pages/integrations/index.md b/server/sonar-docs/src/pages/integrations/index.md
new file mode 100644 (file)
index 0000000..01fcb49
--- /dev/null
@@ -0,0 +1,10 @@
+---
+title: Integrations
+scope: sonarcloud
+---
+
+SonarCloud integrates with the following cloud services to help developers get the most out of their code:
+
+* [Integration with GitHub](/integrations/github)
+* [Integration with Bitbucket Cloud](/integrations/bitbucketcloud)
+* [Integration with VSTS](/integrations/vsts)
diff --git a/server/sonar-docs/src/pages/integrations/vsts.md b/server/sonar-docs/src/pages/integrations/vsts.md
new file mode 100644 (file)
index 0000000..4166445
--- /dev/null
@@ -0,0 +1,37 @@
+---
+title: Integration with VSTS
+scope: sonarcloud
+---
+
+
+## Authentication
+
+You can connect to SonarCloud using your VSTS account. On the [login page](/#sonarcloud#/sessions/new), just click on the "Log in with VSTS" button.
+
+** /!\ Warning:** Only work and school VSTS accounts are authorized to login on SonarCloud.
+
+## Install the SonarCloud VSTS extension
+
+The SonarCloud VSTS extension brings everything you need to have your projects analyzed on SonarCloud 
+very quickly:
+* Integration with the Build definitions to easily trigger the analysis
+* Pull request decoration to get quick feedback on the code changes
+* Widget to have the overview quality of your projects inside VSTS dashboards
+
+Install [SonarCloud extension for VSTS](https://marketplace.visualstudio.com/items?itemName=SonarSource.sonarcloud)by clicking on the "Get it free" button.
+
+Then follow the comprehensive [Microsoft lab on how to integrate VSTS with SonarCloud](https://aka.ms/sonarcloudlab).
+
+## Quality Gate Status widget 
+
+You can monitor the Quality Gate status of your projects directly in your VSTS dashboard. Follow these simple steps to configure your widget:
+
+1. Once the VSTS extension is installed and your project has been successfully analyzed, go to one of your VSTS dashboards (or create one). Click on the pen icon in the bottom right corner of the screen, and then on the "+" icon to add a widget. 
+
+2. In the list of widgets, select the "Code Quality" one and then click on the "Add" button. An empty widget is added to your dashboard. 
+
+3. You can then click on the widget's cogwheel icon to configure it.
+
+    * **For public projects:** you can simply select your project from the dropdown. A searchbar inside the dropdown will help you find it easily. Just select it and click on the "Save" button.
+
+    * **For private projects:** you'll have to log in using the links provided under the dropdown. Once logged in, your private projects will appear in the dropdown. Select the one you are interested in, and click on "Save".
\ No newline at end of file
diff --git a/server/sonar-docs/src/pages/sonarcloud-pricing.md b/server/sonar-docs/src/pages/sonarcloud-pricing.md
new file mode 100644 (file)
index 0000000..c4ce5d2
--- /dev/null
@@ -0,0 +1,72 @@
+---
+title: Pricing
+scope: sonarcloud
+---
+
+Subscribing to a paid plan on SonarCloud allows you to analyze unlimited private projects. You can make your code visible by members of your organization only.
+
+You can activate the paid plan on the "Administration > Billing" page of your organization.
+
+## How is SonarCloud priced?
+
+SonarCloud is priced on a monthly basis per lines of code. You pay up front for a maximum number of lines of code to be analyzed in your organization.
+
+Find your max LOC below to see what it will cost you per month:
+
+| Up to lines of code  | Price per month in € |
+| ------------- |--------------:|
+| 100k          | 10            |
+| 250k          | 75            | 
+| 500k          | 150           |
+| 1M            | 250           |
+| 2M            | 500           |
+| 5M            | 1'500         |
+| 10M           | 3'000         |
+| 20M           | 4'000         |
+
+
+## What's the difference between the free and paid plans?
+
+2 options are available to start using SonarCloud: free and paid plans. Both plans let you benefit from all the features available on SonarCloud.
+
+*Free plan:*
+
+* For open source projects
+* Anyone can see your projects
+* You choose who can edit your projects
+* Unlimited lines of code (LOCs)
+
+*Paid plan:*
+
+* If you need (some or all) private projects
+* You choose who can see your private projects
+* You choose who can edit your projects
+* Priced by lines of private code
+
+
+## Can I try a private project on SonarCloud for free?
+
+Your first 14 days are on us. You just have to upgrade your organization to a paid plan, and fill your credit card information to get started. After your trial, if you love it you can continue using SonarCloud and you will be charged for the plan you selected when you first started your free trial. You can cancel anytime.
+
+## What is a Line of Code (LOC) on SonarCloud?
+
+LOCs are computed by summing up the LOCs of each project analyzed in SonarCloud. The LOCs used for a project are the LOCs found during the most recent analysis of this project.
+
+
+## How are Lines of Code (LOCs) counted towards billing?
+
+Only LOC from your private projects are counted toward your maximum number of LOCs. If you are getting close to the threshold you will be notified to either upgrade your plan or reduce the number of LOCs in your projects.
+
+## When will I be invoiced?
+
+You will be invoiced once a month, the day of the month after your trial ends. For example if you start your free trial on January 1st, it will last till January 14th and you will be first billed on January 15th for your upcoming month, aka January 15th to February 15th.
+
+## Can I stop using the service?
+
+Yes, you can stop using SonarCloud anytime you want.
+
+## Still have more questions?
+
+Contact us [here](https://about.sonarcloud.io/contact).
+
+
diff --git a/server/sonar-docs/src/tooltips/organizations/organization.md b/server/sonar-docs/src/tooltips/organizations/organization.md
new file mode 100644 (file)
index 0000000..0670083
--- /dev/null
@@ -0,0 +1,5 @@
+An organization is a space where a team or a whole company can collaborate across many projects. A new organization is on a free plan by default, which means its projects will be public. Subscribe to paid plan to analyze projects privately
+
+---
+
+See also: [Organizations](/organizations/index) and [Pricing](/paid-plan)
index 04254a29e1aa981e1062c11e108358667224722b..acc43e6de4c0a2697a293442002738bad656b8e4 100644 (file)
@@ -24,6 +24,7 @@ import * as PropTypes from 'prop-types';
 import { createOrganization } from '../../organizations/actions';
 import { Organization } from '../../../app/types';
 import Modal from '../../../components/controls/Modal';
+import DocTooltip from '../../../components/docs/DocTooltip';
 import { translate } from '../../../helpers/l10n';
 import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons';
 
@@ -126,7 +127,10 @@ class CreateOrganizationForm extends React.PureComponent<Props, State> {
     return (
       <Modal contentLabel="modal form" onRequestClose={this.props.onClose}>
         <header className="modal-head">
-          <h2>{translate('my_account.create_organization')}</h2>
+          <h2>
+            {translate('my_account.create_organization')}
+            <DocTooltip className="spacer-left" doc="organizations/organization" />
+          </h2>
         </header>
 
         <form onSubmit={this.handleSubmit}>
index 0b36388f0ab9103fa3c9df3e138466771d74a407..67077233600e93f583ec3cba9c67a5c01a63551a 100644 (file)
@@ -23,6 +23,7 @@ import Helmet from 'react-helmet';
 import { connect } from 'react-redux';
 import OrganizationNavigation from '../navigation/OrganizationNavigation';
 import NotFound from '../../../app/components/NotFound';
+import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
 import { fetchOrganization } from '../actions';
 import { getOrganizationByKey } from '../../../store/rootReducer';
 /*:: import type { Organization } from '../../../store/organizations/duck'; */
@@ -69,15 +70,15 @@ export class OrganizationPage extends React.PureComponent {
     this.mounted = false;
   }
 
-  updateOrganization = (organizationKey /*: string */) => {
+  stopLoading = () => {
     if (this.mounted) {
-      this.setState({ loading: true });
+      this.setState({ loading: false });
     }
-    this.props.fetchOrganization(organizationKey).then(() => {
-      if (this.mounted) {
-        this.setState({ loading: false });
-      }
-    });
+  };
+
+  updateOrganization = (organizationKey /*: string */) => {
+    this.setState({ loading: true });
+    this.props.fetchOrganization(organizationKey).then(this.stopLoading, this.stopLoading);
   };
 
   render() {
@@ -94,7 +95,8 @@ export class OrganizationPage extends React.PureComponent {
     return (
       <div>
         <Helmet defaultTitle={organization.name} titleTemplate={'%s - ' + organization.name} />
-        <OrganizationNavigation organization={organization} location={this.props.location} />
+        <Suggestions suggestions="organization_space" />
+        <OrganizationNavigation location={this.props.location} organization={organization} />
         {this.props.children}
       </div>
     );
index 46bff66681889d22875e06c1a5531312c48eb2a3..260e3f3ffb61fa9ee25da5a3b2ca72e6175913ab 100644 (file)
@@ -21,6 +21,9 @@ exports[`smoke test 1`] = `
     encodeSpecialCharacters={true}
     titleTemplate="%s - Foo"
   />
+  <Suggestions
+    suggestions="organization_space"
+  />
   <OrganizationNavigation
     organization={
       Object {
index af4bc9d654789260c2f9fa1c8b8254650e5c6a09..e55c34a341a8d7f60288458dd61e3e899e0f1498 100644 (file)
@@ -23,6 +23,7 @@ import classNames from 'classnames';
 import { sortBy } from 'lodash';
 import Step from './Step';
 import NewOrganizationForm from './NewOrganizationForm';
+import DocTooltip from '../../../components/docs/DocTooltip';
 import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessIcon';
 import { getOrganizations } from '../../../api/organizations';
 import Select from '../../../components/controls/Select';
@@ -263,7 +264,12 @@ export default class OrganizationStep extends React.PureComponent {
         renderForm={this.renderForm}
         renderResult={this.renderResult}
         stepNumber={this.props.stepNumber}
-        stepTitle={translate('onboarding.organization.header')}
+        stepTitle={
+          <span>
+            {translate('onboarding.organization.header')}
+            <DocTooltip className="little-spacer-left" doc="organizations/organization" />
+          </span>
+        }
       />
     );
   }
index 572eb59b49bbbff946afbfcb76d1e89685b37656..d9f6394a75fbb1283b24e6bb391250ae30f82113 100644 (file)
@@ -29,7 +29,7 @@ type Props = {|
   renderForm: () => React.Element<*>,
   renderResult: () => ?React.Element<*>,
   stepNumber: number,
-  stepTitle: string
+  stepTitle: React.Element<*> | string
 |};
 */
 
index 4ff55018414b23a46b76702aa177be34ccebc02a..7e30dedc4a95a2255ff3f2a590ae1db7e0ccd9a3 100644 (file)
@@ -21,7 +21,15 @@ exports[`works with existing organization 1`] = `
     renderForm={[Function]}
     renderResult={[Function]}
     stepNumber={1}
-    stepTitle="onboarding.organization.header"
+    stepTitle={
+      <span>
+        onboarding.organization.header
+        <DocTooltip
+          className="little-spacer-left"
+          doc="organizations/organization"
+        />
+      </span>
+    }
   >
     <div
       className="boxed-group onboarding-step is-open"
@@ -35,7 +43,108 @@ exports[`works with existing organization 1`] = `
         className="boxed-group-header"
       >
         <h2>
-          onboarding.organization.header
+          <span>
+            onboarding.organization.header
+            <DocTooltip
+              className="little-spacer-left"
+              doc="organizations/organization"
+            >
+              <HelpTooltip
+                className="little-spacer-left"
+                onShow={[Function]}
+                overlay={
+                  <div
+                    className="abs-width-300"
+                  >
+                    <LazyLoader
+                      className="cut-margins"
+                      isTooltip={true}
+                    />
+                  </div>
+                }
+              >
+                <div
+                  className="help-tooltip little-spacer-left"
+                >
+                  <Tooltip
+                    mouseLeaveDelay={0.25}
+                    onShow={[Function]}
+                    overlay={
+                      <div
+                        className="abs-width-300"
+                      >
+                        <LazyLoader
+                          className="cut-margins"
+                          isTooltip={true}
+                        />
+                      </div>
+                    }
+                  >
+                    <TooltipInner
+                      mouseEnterDelay={0.1}
+                      mouseLeaveDelay={0.25}
+                      onShow={[Function]}
+                      overlay={
+                        <div
+                          className="abs-width-300"
+                        >
+                          <LazyLoader
+                            className="cut-margins"
+                            isTooltip={true}
+                          />
+                        </div>
+                      }
+                    >
+                      <span
+                        className="display-inline-flex-center"
+                        onMouseEnter={[Function]}
+                        onMouseLeave={[Function]}
+                      >
+                        <HelpIcon
+                          fill="#b4b4b4"
+                          size={12}
+                        >
+                          <Icon
+                            size={12}
+                          >
+                            <svg
+                              height={12}
+                              style={
+                                Object {
+                                  "clipRule": "evenodd",
+                                  "fillRule": "evenodd",
+                                  "strokeLinejoin": "round",
+                                  "strokeMiterlimit": "1.41421",
+                                }
+                              }
+                              version="1.1"
+                              viewBox="0 0 16 16"
+                              width={12}
+                              xmlSpace="preserve"
+                              xmlnsXlink="http://www.w3.org/1999/xlink"
+                            >
+                              <g
+                                transform="matrix(0.0364583,0,0,0.0364583,1,-0.166667)"
+                              >
+                                <path
+                                  d="M224,344L224,296C224,293.667 223.25,291.75 221.75,290.25C220.25,288.75 218.333,288 216,288L168,288C165.667,288 163.75,288.75 162.25,290.25C160.75,291.75 160,293.667 160,296L160,344C160,346.333 160.75,348.25 162.25,349.75C163.75,351.25 165.667,352 168,352L216,352C218.333,352 220.25,351.25 221.75,349.75C223.25,348.25 224,346.333 224,344ZM288,176C288,161.333 283.375,147.75 274.125,135.25C264.875,122.75 253.333,113.083 239.5,106.25C225.667,99.417 211.5,96 197,96C156.5,96 125.583,113.75 104.25,149.25C101.75,153.25 102.417,156.75 106.25,159.75L139.25,184.75C140.417,185.75 142,186.25 144,186.25C146.667,186.25 148.75,185.25 150.25,183.25C159.083,171.917 166.25,164.25 171.75,160.25C177.417,156.25 184.583,154.25 193.25,154.25C201.25,154.25 208.375,156.417 214.625,160.75C220.875,165.083 224,170 224,175.5C224,181.833 222.333,186.917 219,190.75C215.667,194.583 210,198.333 202,202C191.5,206.667 181.875,213.875 173.125,223.625C164.375,233.375 160,243.833 160,255L160,264C160,266.333 160.75,268.25 162.25,269.75C163.75,271.25 165.667,272 168,272L216,272C218.333,272 220.25,271.25 221.75,269.75C223.25,268.25 224,266.333 224,264C224,260.833 225.792,256.708 229.375,251.625C232.958,246.542 237.5,242.417 243,239.25C248.333,236.25 252.417,233.875 255.25,232.125C258.083,230.375 261.917,227.458 266.75,223.375C271.583,219.292 275.292,215.292 277.875,211.375C280.458,207.458 282.792,202.417 284.875,196.25C286.958,190.083 288,183.333 288,176ZM384,224C384,258.833 375.417,290.958 358.25,320.375C341.083,349.792 317.792,373.083 288.375,390.25C258.958,407.417 226.833,416 192,416C157.167,416 125.042,407.417 95.625,390.25C66.208,373.083 42.917,349.792 25.75,320.375C8.583,290.958 0,258.833 0,224C0,189.167 8.583,157.042 25.75,127.625C42.917,98.208 66.208,74.917 95.625,57.75C125.042,40.583 157.167,32 192,32C226.833,32 258.958,40.583 288.375,57.75C317.792,74.917 341.083,98.208 358.25,127.625C375.417,157.042 384,189.167 384,224Z"
+                                  style={
+                                    Object {
+                                      "fill": "#b4b4b4",
+                                    }
+                                  }
+                                />
+                              </g>
+                            </svg>
+                          </Icon>
+                        </HelpIcon>
+                      </span>
+                    </TooltipInner>
+                  </Tooltip>
+                </div>
+              </HelpTooltip>
+            </DocTooltip>
+          </span>
         </h2>
       </div>
       <div
index 5e741918f9509c7cd5f5148f1013cd83930b910c..647256b3743d807ecc89478e0fe5614672e7f1f5 100644 (file)
@@ -21,26 +21,28 @@ import * as React from 'react';
 import { Link } from 'react-router';
 import DetachIcon from '../../components/icons-components/DetachIcon';
 
+const SONARCLOUD_LINK = '/#sonarcloud#/';
+
 export default function DocLink(props: React.AnchorHTMLAttributes<HTMLAnchorElement>) {
   const { children, href, ...other } = props;
-
   if (href && href.startsWith('/')) {
+    let url = `/documentation/${href.substr(1)}`;
+    if (href.startsWith(SONARCLOUD_LINK)) {
+      url = `/${href.substr(SONARCLOUD_LINK.length)}`;
+    }
     return (
-      <Link to={`/documentation/${href.substr(1)}`} {...other}>
+      <Link to={url} {...other}>
         {children}
       </Link>
     );
   }
 
   return (
-    <>
-      <a className="text-middle" href={href} rel="noopener noreferrer" target="_blank" {...other}>
+    <span className="display-inline-flex-center">
+      <a href={href} rel="noopener noreferrer" target="_blank" {...other}>
         {children}
       </a>
-      <DetachIcon
-        className="text-middle text-muted little-spacer-left little-spacer-right"
-        size={12}
-      />
-    </>
+      <DetachIcon className="text-muted little-spacer-left little-spacer-right" size={12} />
+    </span>
   );
 }
index 4ec569cb43221b1612a591f71bc48f543144f6d9..2d1f1c6579f9218487f62fa5af3f9021dc20635d 100644 (file)
@@ -24,6 +24,7 @@ import reactRenderer from 'remark-react';
 import DocLink from './DocLink';
 import DocParagraph from './DocParagraph';
 import DocImg from './DocImg';
+import DocTooltipLink from './DocTooltipLink';
 import { separateFrontMatter } from '../../helpers/markdown';
 import { isSonarCloud } from '../../helpers/system';
 
@@ -31,9 +32,10 @@ interface Props {
   className?: string;
   content: string | undefined;
   displayH1?: boolean;
+  isTooltip?: boolean;
 }
 
-export default function DocMarkdownBlock({ className, content, displayH1 }: Props) {
+export default function DocMarkdownBlock({ className, content, displayH1, isTooltip }: Props) {
   const parsed = separateFrontMatter(content || '');
   return (
     <div className={classNames('markdown', className)}>
@@ -46,7 +48,7 @@ export default function DocMarkdownBlock({ className, content, displayH1 }: Prop
               // do not render outer <div />
               div: React.Fragment,
               // use custom link to render documentation anchors
-              a: DocLink,
+              a: isTooltip ? DocTooltipLink : DocLink,
               // used to handle `@include`
               p: DocParagraph,
               // use custom img tag to render documentation images
index c456c9ecd210afe4f6a69f58ac4c48105e7d2ecf..754008144a4eb25c1d9d0993b9bad55e53bac45f 100644 (file)
@@ -82,7 +82,7 @@ export default class DocTooltip extends React.PureComponent<Props, State> {
         {this.state.loading ? (
           <i className="spinner" />
         ) : (
-          <DocMarkdownBlock className="cut-margins" content={this.state.content} />
+          <DocMarkdownBlock className="cut-margins" content={this.state.content} isTooltip={true} />
         )}
       </div>
     );
diff --git a/server/sonar-web/src/main/js/components/docs/DocTooltipLink.tsx b/server/sonar-web/src/main/js/components/docs/DocTooltipLink.tsx
new file mode 100644 (file)
index 0000000..e539615
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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 { Link } from 'react-router';
+import DetachIcon from '../../components/icons-components/DetachIcon';
+
+export default function DocTooltipLink(props: React.AnchorHTMLAttributes<HTMLAnchorElement>) {
+  const { children, href, ...other } = props;
+  return (
+    <span className="display-inline-flex-center">
+      {href && href.startsWith('/') ? (
+        <Link
+          rel="noopener noreferrer"
+          target="_blank"
+          to={`/documentation/${href.substr(1)}`}
+          {...other}>
+          {children}
+        </Link>
+      ) : (
+        <a href={href} rel="noopener noreferrer" target="_blank" {...other}>
+          {children}
+        </a>
+      )}
+      <DetachIcon className="little-spacer-left little-spacer-right" size={12} />
+    </span>
+  );
+}
index ad740c2d833a46e5269a77925ed08b681698fb51..4b394343c351812773e6e9750811a55691aed947 100644 (file)
@@ -25,6 +25,14 @@ it('should render simple link', () => {
   expect(shallow(<DocLink href="http://sample.com" />)).toMatchSnapshot();
 });
 
+it('should render documentation link', () => {
+  expect(shallow(<DocLink href="/foo/bar" />)).toMatchSnapshot();
+});
+
+it('should render sonarcloud link', () => {
+  expect(shallow(<DocLink href="/#sonarcloud#/foo/bar" />)).toMatchSnapshot();
+});
+
 it.skip('should render documentation anchor', () => {
   expect(shallow(<DocLink href="#quality-profiles" />)).toMatchSnapshot();
 });
diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/DocTooltipLink-test.tsx b/server/sonar-web/src/main/js/components/docs/__tests__/DocTooltipLink-test.tsx
new file mode 100644 (file)
index 0000000..21a4467
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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 DocTooltipLink from '../DocTooltipLink';
+
+it('should render simple link', () => {
+  expect(shallow(<DocTooltipLink href="http://sample.com" />)).toMatchSnapshot();
+});
+
+it('should render internal link', () => {
+  expect(shallow(<DocTooltipLink href="/foo/bar" />)).toMatchSnapshot();
+});
index d5678b25137eee7b870997803459da415b60ae7c..264acc8a5b4ad82d192979ef241dca51568c3891 100644 (file)
@@ -1,16 +1,33 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`should render documentation link 1`] = `
+<Link
+  onlyActiveOnIndex={false}
+  style={Object {}}
+  to="/documentation/foo/bar"
+/>
+`;
+
 exports[`should render simple link 1`] = `
-<React.Fragment>
+<span
+  className="display-inline-flex-center"
+>
   <a
-    className="text-middle"
     href="http://sample.com"
     rel="noopener noreferrer"
     target="_blank"
   />
   <DetachIcon
-    className="text-middle text-muted little-spacer-left little-spacer-right"
+    className="text-muted little-spacer-left little-spacer-right"
     size={12}
   />
-</React.Fragment>
+</span>
+`;
+
+exports[`should render sonarcloud link 1`] = `
+<Link
+  onlyActiveOnIndex={false}
+  style={Object {}}
+  to="/foo/bar"
+/>
 `;
index cc1d24b0783d91d5f3b88d008fe2f7280abd55c7..f19fea4d06569a06bbbb1fc52817a12cc55d5162 100644 (file)
@@ -25,6 +25,7 @@ exports[`should render 2`] = `
       <LazyLoader
         className="cut-margins"
         content="this is *bold* text"
+        isTooltip={true}
       />
     </div>
   }
diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocTooltipLink-test.tsx.snap b/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocTooltipLink-test.tsx.snap
new file mode 100644 (file)
index 0000000..6d71839
--- /dev/null
@@ -0,0 +1,35 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render internal link 1`] = `
+<span
+  className="display-inline-flex-center"
+>
+  <Link
+    onlyActiveOnIndex={false}
+    rel="noopener noreferrer"
+    style={Object {}}
+    target="_blank"
+    to="/documentation/foo/bar"
+  />
+  <DetachIcon
+    className="little-spacer-left little-spacer-right"
+    size={12}
+  />
+</span>
+`;
+
+exports[`should render simple link 1`] = `
+<span
+  className="display-inline-flex-center"
+>
+  <a
+    href="http://sample.com"
+    rel="noopener noreferrer"
+    target="_blank"
+  />
+  <DetachIcon
+    className="little-spacer-left little-spacer-right"
+    size={12}
+  />
+</span>
+`;