From 4c2edb7abdb283c6bed56ce6be32304f67529045 Mon Sep 17 00:00:00 2001
From: Stas Vilchik
Date: Wed, 25 Apr 2018 13:54:16 +0200
Subject: [PATCH] SONAR-10611 Display inline documentation tooltips (#180)
---
.../quality-gates/built-in-quality-gate.md | 1 +
.../quality-gates/default-quality-gate.md | 1 +
.../project-homepage-quality-gate.md | 1 +
.../quality-gates/quality-gate-conditions.md | 5 +
.../quality-gates/quality-gate-projects.md | 1 +
.../tooltips/quality-gates/quality-gate.md | 1 +
.../built-in-quality-profile.md | 1 +
.../default-quality-profile.md | 1 +
.../quality-profile-projects.md | 1 +
.../src/tooltips/rules/custom-rule-removal.md | 1 +
.../src/tooltips/rules/custom-rules.md | 1 +
.../src/tooltips/rules/rule-templates.md | 1 +
.../tooltips/rules/rules-quality-profiles.md | 5 +
server/sonar-web/config/webpack.config.js | 10 +-
server/sonar-web/package.json | 3 +
.../src/main/js/@types/remark-react.d.ts | 33 +
.../sonar-web/src/main/js/@types/remark.d.ts | 22 +
.../components/nav/component/ComponentNav.css | 1 +
.../nav/component/ComponentNavBranch.tsx | 83 +-
.../nav/component/NoBranchSupportPopup.tsx | 30 +-
.../nav/component/SingleBranchHelperPopup.tsx | 34 +-
.../__tests__/ComponentNavBranch-test.tsx | 10 +-
.../ComponentNavBranch-test.tsx.snap | 81 +-
.../NoBranchSupportPopup-test.tsx.snap | 43 +-
.../SingleBranchHelperPopup-test.tsx.snap | 43 +-
.../main/js/app/styles/components/badges.css | 1 +
.../styles/components/search-navigator.css | 1 -
.../js/app/styles/components/tooltips.css | 168 ----
.../src/main/js/app/styles/init/links.css | 4 -
.../src/main/js/app/styles/init/tables.css | 10 +
.../src/main/js/app/styles/style.css | 8 +
.../js/apps/coding-rules/components/Facet.tsx | 14 +-
.../coding-rules/components/ProfileFacet.tsx | 6 +-
.../coding-rules/components/RuleDetails.tsx | 18 +-
.../components/RuleDetailsMeta.tsx | 20 +-
.../coding-rules/components/TemplateFacet.tsx | 6 +-
.../apps/overview/qualityGate/QualityGate.js | 10 +-
.../__snapshots__/QualityGate-test.js.snap | 17 +-
.../src/main/js/apps/overview/styles.css | 5 -
.../js/apps/projectQualityGate/Header.tsx | 6 +-
.../__snapshots__/Header-test.tsx.snap | 14 +-
.../js/apps/projectQualityProfiles/Header.tsx | 6 +-
.../__snapshots__/Header-test.tsx.snap | 14 +-
.../quality-gates/components/Conditions.js | 6 +-
.../components/DetailsContent.js | 6 +-
.../components/DetailsHeader.tsx | 14 +-
.../quality-gates/components/ListHeader.tsx | 6 +-
.../components/ProfileLink.tsx | 2 +-
.../quality-profiles/home/ProfilesList.tsx | 9 +-
.../quality-profiles/home/ProfilesListRow.tsx | 45 +-
.../main/js/components/common/BubblePopup.tsx | 4 +
.../src/main/js/components/controls/Popup.tsx | 73 ++
.../main/js/components/controls/Tooltip.css | 126 ++-
.../main/js/components/controls/Tooltip.tsx | 9 +-
.../src/main/js/components/docs/DocLink.tsx | 45 ++
.../js/components/docs/DocMarkdownBlock.tsx | 48 ++
.../main/js/components/docs/DocTooltip.tsx | 134 ++++
.../docs/__tests__/DocLink-test.tsx | 30 +
.../docs/__tests__/DocMarkdownBlock-test.tsx | 43 +
.../docs/__tests__/DocTooltip-test.tsx | 48 ++
.../__snapshots__/DocLink-test.tsx.snap | 7 +
.../DocMarkdownBlock-test.tsx.snap | 32 +
.../__snapshots__/DocTooltip-test.tsx.snap | 61 ++
.../main/js/components/facet/FacetHeader.tsx | 5 +-
.../icons-components/PlusCircleIcon.tsx | 39 +
server/sonar-web/yarn.lock | 743 +++++++++++++++++-
.../resources/org/sonar/l10n/core.properties | 3 +-
67 files changed, 1763 insertions(+), 517 deletions(-)
create mode 100644 server/sonar-docs/src/tooltips/quality-gates/built-in-quality-gate.md
create mode 100644 server/sonar-docs/src/tooltips/quality-gates/default-quality-gate.md
create mode 100644 server/sonar-docs/src/tooltips/quality-gates/project-homepage-quality-gate.md
create mode 100644 server/sonar-docs/src/tooltips/quality-gates/quality-gate-conditions.md
create mode 100644 server/sonar-docs/src/tooltips/quality-gates/quality-gate-projects.md
create mode 100644 server/sonar-docs/src/tooltips/quality-gates/quality-gate.md
create mode 100644 server/sonar-docs/src/tooltips/quality-profiles/built-in-quality-profile.md
create mode 100644 server/sonar-docs/src/tooltips/quality-profiles/default-quality-profile.md
create mode 100644 server/sonar-docs/src/tooltips/quality-profiles/quality-profile-projects.md
create mode 100644 server/sonar-docs/src/tooltips/rules/custom-rule-removal.md
create mode 100644 server/sonar-docs/src/tooltips/rules/custom-rules.md
create mode 100644 server/sonar-docs/src/tooltips/rules/rule-templates.md
create mode 100644 server/sonar-docs/src/tooltips/rules/rules-quality-profiles.md
create mode 100644 server/sonar-web/src/main/js/@types/remark-react.d.ts
create mode 100644 server/sonar-web/src/main/js/@types/remark.d.ts
delete mode 100644 server/sonar-web/src/main/js/app/styles/components/tooltips.css
create mode 100644 server/sonar-web/src/main/js/components/controls/Popup.tsx
create mode 100644 server/sonar-web/src/main/js/components/docs/DocLink.tsx
create mode 100644 server/sonar-web/src/main/js/components/docs/DocMarkdownBlock.tsx
create mode 100644 server/sonar-web/src/main/js/components/docs/DocTooltip.tsx
create mode 100644 server/sonar-web/src/main/js/components/docs/__tests__/DocLink-test.tsx
create mode 100644 server/sonar-web/src/main/js/components/docs/__tests__/DocMarkdownBlock-test.tsx
create mode 100644 server/sonar-web/src/main/js/components/docs/__tests__/DocTooltip-test.tsx
create mode 100644 server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocLink-test.tsx.snap
create mode 100644 server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocMarkdownBlock-test.tsx.snap
create mode 100644 server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocTooltip-test.tsx.snap
create mode 100644 server/sonar-web/src/main/js/components/icons-components/PlusCircleIcon.tsx
diff --git a/server/sonar-docs/src/tooltips/quality-gates/built-in-quality-gate.md b/server/sonar-docs/src/tooltips/quality-gates/built-in-quality-gate.md
new file mode 100644
index 00000000000..7d5470bd4da
--- /dev/null
+++ b/server/sonar-docs/src/tooltips/quality-gates/built-in-quality-gate.md
@@ -0,0 +1 @@
+Built-in, immutable Quality Gate reflecting best practices.
diff --git a/server/sonar-docs/src/tooltips/quality-gates/default-quality-gate.md b/server/sonar-docs/src/tooltips/quality-gates/default-quality-gate.md
new file mode 100644
index 00000000000..2cf1296dc53
--- /dev/null
+++ b/server/sonar-docs/src/tooltips/quality-gates/default-quality-gate.md
@@ -0,0 +1 @@
+The Default gate is applied to all projects not explicitly assigned to a gate. Quality Profile and Gate administrators can assign projects to a gate from the Quality Profile page. Project administrators can also choose a non-default gate.
diff --git a/server/sonar-docs/src/tooltips/quality-gates/project-homepage-quality-gate.md b/server/sonar-docs/src/tooltips/quality-gates/project-homepage-quality-gate.md
new file mode 100644
index 00000000000..115b34f419a
--- /dev/null
+++ b/server/sonar-docs/src/tooltips/quality-gates/project-homepage-quality-gate.md
@@ -0,0 +1 @@
+A Quality Gate is a set of measure-based Boolean conditions. It helps you know immediately whether your project is production-ready. If your current status is not Passed, you'll see which values caused the problem and the value required to pass.
diff --git a/server/sonar-docs/src/tooltips/quality-gates/quality-gate-conditions.md b/server/sonar-docs/src/tooltips/quality-gates/quality-gate-conditions.md
new file mode 100644
index 00000000000..e961eeb17f6
--- /dev/null
+++ b/server/sonar-docs/src/tooltips/quality-gates/quality-gate-conditions.md
@@ -0,0 +1,5 @@
+For each Quality Gate condition you must choose the metric to be tested, the threshold at which to raise a warning or error, and whether or not to apply the condition to all code or only to Leak Period code (irrelevant for conditions "on New Code").
+
+---
+
+See also: [Fixing the Water Leak](/fixing-the-water-leak)
diff --git a/server/sonar-docs/src/tooltips/quality-gates/quality-gate-projects.md b/server/sonar-docs/src/tooltips/quality-gates/quality-gate-projects.md
new file mode 100644
index 00000000000..2cf1296dc53
--- /dev/null
+++ b/server/sonar-docs/src/tooltips/quality-gates/quality-gate-projects.md
@@ -0,0 +1 @@
+The Default gate is applied to all projects not explicitly assigned to a gate. Quality Profile and Gate administrators can assign projects to a gate from the Quality Profile page. Project administrators can also choose a non-default gate.
diff --git a/server/sonar-docs/src/tooltips/quality-gates/quality-gate.md b/server/sonar-docs/src/tooltips/quality-gates/quality-gate.md
new file mode 100644
index 00000000000..0762cbcbcaa
--- /dev/null
+++ b/server/sonar-docs/src/tooltips/quality-gates/quality-gate.md
@@ -0,0 +1 @@
+A Quality Gate is a set of measure-based, Boolean conditions. It helps you know immediately whether your projects are production-ready. Ideally, all projects will use the same quality gate. Each project's Quality Gate status is displayed prominently on its homepage.
diff --git a/server/sonar-docs/src/tooltips/quality-profiles/built-in-quality-profile.md b/server/sonar-docs/src/tooltips/quality-profiles/built-in-quality-profile.md
new file mode 100644
index 00000000000..5c0ddbf1f2c
--- /dev/null
+++ b/server/sonar-docs/src/tooltips/quality-profiles/built-in-quality-profile.md
@@ -0,0 +1 @@
+Built-in profiles are provided directly by the language analyzers and may be updated with each new analyzer version. They represent best-practice, minimum rule sets.
diff --git a/server/sonar-docs/src/tooltips/quality-profiles/default-quality-profile.md b/server/sonar-docs/src/tooltips/quality-profiles/default-quality-profile.md
new file mode 100644
index 00000000000..310164c8be5
--- /dev/null
+++ b/server/sonar-docs/src/tooltips/quality-profiles/default-quality-profile.md
@@ -0,0 +1 @@
+For each language there is a default profile. All projects not explicitly assigned to some other profile will be analyzed with the default.
diff --git a/server/sonar-docs/src/tooltips/quality-profiles/quality-profile-projects.md b/server/sonar-docs/src/tooltips/quality-profiles/quality-profile-projects.md
new file mode 100644
index 00000000000..0c96fe0fb64
--- /dev/null
+++ b/server/sonar-docs/src/tooltips/quality-profiles/quality-profile-projects.md
@@ -0,0 +1 @@
+Projects assigned to a profile will always be analyzed with it for that language, regardless of which profile is the default. Quality Profile administrators may assign projects to a profile. Project administrators may also choose a non-default profile for each language.
diff --git a/server/sonar-docs/src/tooltips/rules/custom-rule-removal.md b/server/sonar-docs/src/tooltips/rules/custom-rule-removal.md
new file mode 100644
index 00000000000..c8d9be85acd
--- /dev/null
+++ b/server/sonar-docs/src/tooltips/rules/custom-rule-removal.md
@@ -0,0 +1 @@
+Only custom rules may be deleted. When a custom rule is deleted, it is not removed from the SonarQube instance. Instead its status is set to "REMOVED", allowing relevant issues to continue to be displayed properly.
diff --git a/server/sonar-docs/src/tooltips/rules/custom-rules.md b/server/sonar-docs/src/tooltips/rules/custom-rules.md
new file mode 100644
index 00000000000..37981a7dffb
--- /dev/null
+++ b/server/sonar-docs/src/tooltips/rules/custom-rules.md
@@ -0,0 +1 @@
+Custom rules are created by administrators from templates, and are the only fully-editable rules.
diff --git a/server/sonar-docs/src/tooltips/rules/rule-templates.md b/server/sonar-docs/src/tooltips/rules/rule-templates.md
new file mode 100644
index 00000000000..582e4cf24be
--- /dev/null
+++ b/server/sonar-docs/src/tooltips/rules/rule-templates.md
@@ -0,0 +1 @@
+Rule Templates allow users to easily define their own rules. They are like cookie cutters from which you can stamp out new, "custom rules". The rules created from a template are listed on its rule detail page.
diff --git a/server/sonar-docs/src/tooltips/rules/rules-quality-profiles.md b/server/sonar-docs/src/tooltips/rules/rules-quality-profiles.md
new file mode 100644
index 00000000000..809df1df132
--- /dev/null
+++ b/server/sonar-docs/src/tooltips/rules/rules-quality-profiles.md
@@ -0,0 +1,5 @@
+Quality Profiles are collections of Rules to apply during an analysis.
+
+---
+
+See also: [Quality Profiles](/quality-profiles)
diff --git a/server/sonar-web/config/webpack.config.js b/server/sonar-web/config/webpack.config.js
index cbad122a5b5..cc2bfe7e970 100644
--- a/server/sonar-web/config/webpack.config.js
+++ b/server/sonar-web/config/webpack.config.js
@@ -33,7 +33,11 @@ module.exports = ({ production = true }) => ({
devtool: production ? 'source-map' : 'cheap-module-source-map',
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.
- extensions: ['.ts', '.tsx', '.js', '.json']
+ extensions: ['.ts', '.tsx', '.js', '.json'],
+ // import from 'Docs/foo.md' is rewritten to import from 'sonar-docs/src/foo.md'
+ alias: {
+ Docs: path.resolve(__dirname, '../../sonar-docs/src/tooltips')
+ }
},
entry: [
!production && require.resolve('react-dev-utils/webpackHotDevClient'),
@@ -69,6 +73,10 @@ module.exports = ({ production = true }) => ({
utils.postcssLoader()
].filter(Boolean)
},
+ {
+ test: /\.md$/,
+ use: 'raw-loader'
+ },
{ test: require.resolve('lodash'), loader: 'expose-loader?_' },
{ test: require.resolve('react'), loader: 'expose-loader?React' },
{ test: require.resolve('react-dom'), loader: 'expose-loader?ReactDOM' }
diff --git a/server/sonar-web/package.json b/server/sonar-web/package.json
index 11e65a186d7..a8b3f522569 100644
--- a/server/sonar-web/package.json
+++ b/server/sonar-web/package.json
@@ -94,9 +94,12 @@
"postcss-custom-properties": "6.2.0",
"postcss-loader": "2.1.1",
"prettier": "1.11.1",
+ "raw-loader": "0.5.1",
"react-dev-utils": "5.0.0",
"react-error-overlay": "1.0.7",
"react-test-renderer": "16.2.0",
+ "remark": "9.0.0",
+ "remark-react": "4.0.1",
"style-loader": "0.20.3",
"ts-jest": "22.0.1",
"ts-loader": "4.1.0",
diff --git a/server/sonar-web/src/main/js/@types/remark-react.d.ts b/server/sonar-web/src/main/js/@types/remark-react.d.ts
new file mode 100644
index 00000000000..0e944118732
--- /dev/null
+++ b/server/sonar-web/src/main/js/@types/remark-react.d.ts
@@ -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.
+ */
+declare module 'remark-react' {
+ interface Options {
+ /** `h()` */
+ createElement?: (...args: any[]) => JSX.Element;
+ /** Key prefix. */
+ prefix?: string;
+ /** Components. */
+ remarkReactComponents?: any;
+ /** Sanitation schema. */
+ sanitize?: any;
+ }
+
+ export default function remarkReact(options?: Options): JSX.Element;
+}
diff --git a/server/sonar-web/src/main/js/@types/remark.d.ts b/server/sonar-web/src/main/js/@types/remark.d.ts
new file mode 100644
index 00000000000..60cc5c56b85
--- /dev/null
+++ b/server/sonar-web/src/main/js/@types/remark.d.ts
@@ -0,0 +1,22 @@
+/*
+ * 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 'remark' {
+ export default function remark(): any;
+}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css
index 2362ba94a4e..8be68d6447c 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css
@@ -21,6 +21,7 @@
.navbar-context-branches {
display: inline-flex;
justify-content: center;
+ align-items: center;
flex-shrink: 1 !important;
min-width: 0;
line-height: calc(2 * var(--gridSize));
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx
index a369fd1412a..5569d2a7844 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx
@@ -34,8 +34,8 @@ import {
isPullRequest
} from '../../../../helpers/branches';
import { translate } from '../../../../helpers/l10n';
-import HelpIcon from '../../../../components/icons-components/HelpIcon';
-import BubblePopupHelper from '../../../../components/common/BubblePopupHelper';
+import PlusCircleIcon from '../../../../components/icons-components/PlusCircleIcon';
+import Popup from '../../../../components/controls/Popup';
import Tooltip from '../../../../components/controls/Tooltip';
interface Props {
@@ -47,8 +47,6 @@ interface Props {
interface State {
dropdownOpen: boolean;
- noBranchSupportPopupOpen: boolean;
- singleBranchPopupOpen: boolean;
}
export default class ComponentNavBranch extends React.PureComponent {
@@ -59,14 +57,9 @@ export default class ComponentNavBranch extends React.PureComponent {
- if (show !== undefined) {
- this.setState({ singleBranchPopupOpen: show });
- } else {
- this.setState(state => ({ singleBranchPopupOpen: !state.singleBranchPopupOpen }));
- }
- };
-
- toggleNoBranchSupportPopup = (show?: boolean) => {
- if (show !== undefined) {
- this.setState({ noBranchSupportPopupOpen: show });
- } else {
- this.setState(state => ({ noBranchSupportPopupOpen: !state.noBranchSupportPopupOpen }));
- }
- };
-
- handleSingleBranchClick = (event: React.SyntheticEvent) => {
- event.preventDefault();
- event.stopPropagation();
- this.toggleSingleBranchPopup();
- };
-
- handleNoBranchSupportClick = (event: React.SyntheticEvent) => {
- event.preventDefault();
- event.stopPropagation();
- this.toggleNoBranchSupportPopup();
- };
-
renderDropdown = () => {
const { configuration } = this.props.component;
return this.state.dropdownOpen ? (
@@ -174,31 +139,23 @@ export default class ComponentNavBranch extends React.PureComponent (
-
+
+ about_page.read_documentation
+
+
`;
diff --git a/server/sonar-web/src/main/js/app/styles/components/badges.css b/server/sonar-web/src/main/js/app/styles/components/badges.css
index c7c42cc6d59..9d0e5a5a2d9 100644
--- a/server/sonar-web/src/main/js/app/styles/components/badges.css
+++ b/server/sonar-web/src/main/js/app/styles/components/badges.css
@@ -143,6 +143,7 @@ a.badge-focus:active {
color: var(--secondFontColor);
font-size: var(--smallFontSize);
font-weight: 400;
+ white-space: nowrap;
}
.outline-badge.active {
diff --git a/server/sonar-web/src/main/js/app/styles/components/search-navigator.css b/server/sonar-web/src/main/js/app/styles/components/search-navigator.css
index 6c9d545ce78..b661a707b43 100644
--- a/server/sonar-web/src/main/js/app/styles/components/search-navigator.css
+++ b/server/sonar-web/src/main/js/app/styles/components/search-navigator.css
@@ -464,7 +464,6 @@ a.search-navigator-facet:focus .facet-stat {
.search-navigator-facet-header-value {
display: block;
- padding: 8px 0;
overflow: hidden;
}
diff --git a/server/sonar-web/src/main/js/app/styles/components/tooltips.css b/server/sonar-web/src/main/js/app/styles/components/tooltips.css
deleted file mode 100644
index ca2109a4cd4..00000000000
--- a/server/sonar-web/src/main/js/app/styles/components/tooltips.css
+++ /dev/null
@@ -1,168 +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.
- */
-.tooltip,
-.rc-tooltip {
- position: absolute;
- z-index: var(--tooltipZIndex);
- display: block;
- height: auto;
- font-size: var(--smallFontSize);
- font-weight: 300;
- line-height: 1.5;
- animation: fadeIn 0.3s forwards;
-}
-
-.rc-tooltip-hidden {
- display: none;
-}
-
-.tooltip.top,
-.rc-tooltip-placement-top {
- padding: 5px 0;
- margin-top: -3px;
-}
-
-.tooltip.right,
-.rc-tooltip-placement-right {
- padding: 0 5px;
- margin-left: 3px;
-}
-
-.tooltip.bottom,
-.rc-tooltip-placement-bottom {
- padding: 5px 0;
- margin-top: 3px;
-}
-
-.tooltip.left,
-.rc-tooltip-placement-left {
- padding: 0 5px;
- margin-left: -3px;
-}
-
-.tooltip-inner,
-.rc-tooltip-inner {
- max-width: 300px;
- padding: 3px 8px;
- color: #fff;
- text-align: left;
- text-decoration: none;
- background-color: #475760;
- border-radius: 4px;
- letter-spacing: 0.04em;
- overflow: hidden;
- word-break: break-word;
-}
-
-.tooltip-inner .alert,
-.rc-tooltip-inner .alert {
- margin-bottom: 5px;
- border-radius: 4px;
-}
-
-.tooltip-arrow,
-.rc-tooltip-arrow {
- position: absolute;
- width: 0;
- height: 0;
- border: solid transparent;
-}
-
-.tooltip.top .tooltip-arrow,
-.rc-tooltip-placement-top .rc-tooltip-arrow {
- bottom: 0;
- left: 50%;
- margin-left: -5px;
- border-width: 5px 5px 0;
- border-top-color: #475760;
-}
-
-.tooltip.top-left .tooltip-arrow,
-.rc-tooltip-placement-topLeft .rc-tooltip-arrow {
- right: 5px;
- bottom: 0;
- margin-bottom: -5px;
- border-width: 5px 5px 0;
- border-top-color: #475760;
-}
-
-.tooltip.top-right .tooltip-arrow,
-.rc-tooltip-placement-topRight .rc-tooltip-arrow {
- bottom: 0;
- left: 5px;
- margin-bottom: -5px;
- border-width: 5px 5px 0;
- border-top-color: #475760;
-}
-
-.tooltip.right .tooltip-arrow,
-.rc-tooltip-placement-right .rc-tooltip-arrow {
- top: 50%;
- left: 0;
- margin-top: -5px;
- border-width: 5px 5px 5px 0;
- border-right-color: #475760;
-}
-
-.tooltip.left .tooltip-arrow,
-.rc-tooltip-placement-left .rc-tooltip-arrow {
- top: 50%;
- right: 0;
- margin-top: -5px;
- border-width: 5px 0 5px 5px;
- border-left-color: #475760;
-}
-
-.tooltip.bottom .tooltip-arrow,
-.rc-tooltip-placement-bottom .rc-tooltip-arrow {
- top: 0;
- left: 50%;
- margin-left: -5px;
- border-width: 0 5px 5px;
- border-bottom-color: #475760;
-}
-
-.tooltip.bottom-left .tooltip-arrow,
-.rc-tooltip-placement-bottomLeft .rc-tooltip-arrow {
- top: 0;
- right: 5px;
- margin-top: -5px;
- border-width: 0 5px 5px;
- border-bottom-color: #475760;
-}
-
-.tooltip.bottom-right .tooltip-arrow,
-.rc-tooltip-placement-bottomRight .rc-tooltip-arrow {
- top: 0;
- left: 5px;
- margin-top: -5px;
- border-width: 0 5px 5px;
- border-bottom-color: #475760;
-}
-
-@keyframes fadeIn {
- from {
- opacity: 0;
- }
-
- to {
- opacity: 1;
- }
-}
diff --git a/server/sonar-web/src/main/js/app/styles/init/links.css b/server/sonar-web/src/main/js/app/styles/init/links.css
index c370f7bb0e8..bcf268567d2 100644
--- a/server/sonar-web/src/main/js/app/styles/init/links.css
+++ b/server/sonar-web/src/main/js/app/styles/init/links.css
@@ -52,10 +52,6 @@ a:focus {
border-bottom-color: var(--lightBlue);
}
-.tooltip a {
- color: var(--lightBlue);
-}
-
.link-no-underline {
border-bottom: none;
}
diff --git a/server/sonar-web/src/main/js/app/styles/init/tables.css b/server/sonar-web/src/main/js/app/styles/init/tables.css
index bdc3568bf8d..2a2a29366bb 100644
--- a/server/sonar-web/src/main/js/app/styles/init/tables.css
+++ b/server/sonar-web/src/main/js/app/styles/init/tables.css
@@ -76,6 +76,7 @@ table.data > thead:after {
}
table.data > thead > tr > th {
+ position: relative;
vertical-align: top;
line-height: 18px;
padding: 8px 10px;
@@ -96,6 +97,7 @@ table.data > tfoot > tr > td {
}
table.data > tbody > tr > td {
+ position: relative;
padding: 8px 10px;
vertical-align: text-top;
line-height: 16px;
@@ -263,3 +265,11 @@ table.form td img {
table#project-history tr > td {
vertical-align: top;
}
+
+.table-cell-doc {
+ position: absolute;
+ z-index: var(--aboveNormalZIndex);
+ right: -8px;
+ top: 50%;
+ margin-top: -6px;
+}
diff --git a/server/sonar-web/src/main/js/app/styles/style.css b/server/sonar-web/src/main/js/app/styles/style.css
index f5bcff13ca5..4fc6c561948 100644
--- a/server/sonar-web/src/main/js/app/styles/style.css
+++ b/server/sonar-web/src/main/js/app/styles/style.css
@@ -65,6 +65,14 @@
line-height: 1.5;
}
+.markdown.cut-margins > *:first-child {
+ margin-top: 0 !important;
+}
+
+.markdown.cut-margins > *:last-child {
+ margin-bottom: 0 !important;
+}
+
.rule-desc p,
.markdown p,
.rule-desc ul,
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/Facet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/Facet.tsx
index dd0a053202e..7e844278970 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/Facet.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/Facet.tsx
@@ -21,6 +21,7 @@ import * as React from 'react';
import { orderBy, without, sortBy } from 'lodash';
import * as classNames from 'classnames';
import { FacetKey } from '../query';
+import Tooltip from '../../../components/controls/Tooltip';
import FacetBox from '../../../components/facet/FacetBox';
import FacetHeader from '../../../components/facet/FacetHeader';
import FacetItem from '../../../components/facet/FacetItem';
@@ -37,6 +38,7 @@ export interface BasicProps {
}
interface Props extends BasicProps {
+ children?: React.ReactNode;
disabled?: boolean;
disabledHelper?: string;
halfWidth?: boolean;
@@ -101,13 +103,17 @@ export default class Facet extends React.PureComponent {
className={classNames({ 'search-navigator-facet-box-forbidden': this.props.disabled })}
property={this.props.property}>
+ {translate('coding_rules.facet', this.props.property)}
+
+ }
onClear={this.handleClear}
onClick={this.props.disabled ? undefined : this.handleHeaderClick}
open={this.props.open && !this.props.disabled}
- values={values}
- />
+ values={values}>
+ {this.props.children}
+
{this.props.open &&
items !== undefined && {items.map(this.renderItem)}}
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/ProfileFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/ProfileFacet.tsx
index 402b971dcaa..68b6f9ae1ca 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/ProfileFacet.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/ProfileFacet.tsx
@@ -22,6 +22,7 @@ import { sortBy } from 'lodash';
import * as classNames from 'classnames';
import { Query, FacetKey } from '../query';
import { Profile } from '../../../api/quality-profiles';
+import DocTooltip from '../../../components/docs/DocTooltip';
import FacetBox from '../../../components/facet/FacetBox';
import FacetHeader from '../../../components/facet/FacetHeader';
import FacetItem from '../../../components/facet/FacetItem';
@@ -161,8 +162,9 @@ export default class ProfileFacet extends React.PureComponent {
onClear={this.handleClear}
onClick={this.handleHeaderClick}
open={this.props.open}
- values={this.getTextValue()}
- />
+ values={this.getTextValue()}>
+
+
{this.props.open && {profiles.map(this.renderItem)}}
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx
index e951e3b9d44..21481a1f240 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx
@@ -31,6 +31,7 @@ import { getRuleDetails, deleteRule, updateRule } from '../../../api/rules';
import { RuleActivation, RuleDetails as IRuleDetails } from '../../../app/types';
import DeferredSpinner from '../../../components/common/DeferredSpinner';
import ConfirmButton from '../../../components/controls/ConfirmButton';
+import DocTooltip from '../../../components/docs/DocTooltip';
import { Button } from '../../../components/ui/buttons';
import { translate, translateWithParameters } from '../../../helpers/l10n';
@@ -175,7 +176,7 @@ export default class RuleDetails extends React.PureComponent {
{params.length > 0 && }
{isEditable && (
-
+
{/* `templateRule` is used to get rule meta data, `customRule` is used to get parameter values */}
{/* it's expected to pass the same rule to both parameters */}
{
modalHeader={translate('coding_rules.delete_rule')}
onConfirm={this.handleDelete}>
{({ onClick }) => (
-
+ <>
+
+
+ >
)}
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx
index 5d5bc25cdae..16e9f0d6f65 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx
@@ -27,6 +27,7 @@ import { getRuleUrl } from '../../../helpers/urls';
import LinkIcon from '../../../components/icons-components/LinkIcon';
import RuleScopeIcon from '../../../components/icons-components/RuleScopeIcon';
import Tooltip from '../../../components/controls/Tooltip';
+import DocTooltip from '../../../components/docs/DocTooltip';
import { translate } from '../../../helpers/l10n';
import IssueTypeIcon from '../../../components/ui/IssueTypeIcon';
import SeverityHelper from '../../../components/shared/SeverityHelper';
@@ -174,16 +175,15 @@ export default class RuleDetailsMeta extends React.PureComponent {
return null;
}
return (
-
-
instead.
+ */
export default function BubblePopup(props: Props) {
const popupClass = classNames('bubble-popup', props.customClass);
const popupStyle = { ...props.position };
diff --git a/server/sonar-web/src/main/js/components/controls/Popup.tsx b/server/sonar-web/src/main/js/components/controls/Popup.tsx
new file mode 100644
index 00000000000..68f8415f4e5
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/controls/Popup.tsx
@@ -0,0 +1,73 @@
+/*
+ * 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 OutsideClickHandler from './OutsideClickHandler';
+import Tooltip from './Tooltip';
+
+interface Props {
+ children: (props: { onClick: () => void }) => React.ReactElement;
+ overlay: React.ReactNode;
+}
+
+interface State {
+ visible: boolean;
+}
+
+export default class Popup extends React.Component {
+ state: State = { visible: false };
+
+ componentWillReceiveProps(nextProps: Props) {
+ if (nextProps.overlay !== this.props.overlay) {
+ this.setState({ visible: false });
+ }
+ }
+
+ handleClick = (event?: React.MouseEvent) => {
+ if (event) {
+ event.preventDefault();
+ event.currentTarget.blur();
+ }
+
+ // defer opening to not trigger OutsideClickHandler.onClickOutside callback
+ setTimeout(() => {
+ this.setState({ visible: true });
+ }, 0);
+ };
+
+ handleClickOutside = () => {
+ this.setState({ visible: false });
+ };
+
+ renderOverlay() {
+ return (
+
+ {({ ref }) =>
) {
+ const { children, href, ...other } = props;
+
+ if (process.env.NODE_ENV === 'development') {
+ if (href && href.startsWith('#')) {
+ return (
+ <>
+ {/* TODO implement after SONAR-10612 Create documentation space in the web app */}
+
+ {children}
+
+ [TODO]
+ >
+ );
+ }
+ }
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/server/sonar-web/src/main/js/components/docs/DocMarkdownBlock.tsx b/server/sonar-web/src/main/js/components/docs/DocMarkdownBlock.tsx
new file mode 100644
index 00000000000..7a74a9f4b10
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/docs/DocMarkdownBlock.tsx
@@ -0,0 +1,48 @@
+/*
+ * 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 classNames from 'classnames';
+import remark from 'remark';
+import reactRenderer from 'remark-react';
+import DocLink from './DocLink';
+
+interface Props {
+ className?: string;
+ content: string | undefined;
+}
+
+export default function DocMarkdownBlock({ className, content }: Props) {
+ return (
+
+ {
+ remark()
+ .use(reactRenderer, {
+ remarkReactComponents: {
+ // do not render outer
+ div: React.Fragment,
+ // use custom link to render documentation anchors
+ a: DocLink
+ }
+ })
+ .processSync(content).contents
+ }
+
+ );
+}
diff --git a/server/sonar-web/src/main/js/components/docs/DocTooltip.tsx b/server/sonar-web/src/main/js/components/docs/DocTooltip.tsx
new file mode 100644
index 00000000000..0dc27dd9954
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/docs/DocTooltip.tsx
@@ -0,0 +1,134 @@
+/*
+ * 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 classNames from 'classnames';
+import DocMarkdownBlock from './DocMarkdownBlock';
+import HelpIcon from '../icons-components/HelpIcon';
+import Tooltip from '../controls/Tooltip';
+import OutsideClickHandler from '../controls/OutsideClickHandler';
+import * as theme from '../../app/theme';
+
+interface Props {
+ className?: string;
+ /** Key of the documentation chunk */
+ doc: string;
+}
+
+interface State {
+ content?: string;
+ loading: boolean;
+ open: boolean;
+}
+
+export default class DocTooltip extends React.PureComponent {
+ mounted = false;
+ state: State = { loading: false, open: false };
+
+ componentDidMount() {
+ this.mounted = true;
+ document.addEventListener('scroll', this.close, true);
+ }
+
+ componentWillReceiveProps(nextProps: Props) {
+ if (nextProps.doc !== this.props.doc) {
+ this.setState({ content: undefined, loading: false, open: false });
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ document.removeEventListener('scroll', this.close, true);
+ }
+
+ fetchContent = () => {
+ this.setState({ loading: true });
+ import(`Docs/${this.props.doc}.md`).then(
+ ({ default: content }) => {
+ if (this.mounted) {
+ this.setState({ content, loading: false });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ };
+
+ close = () => {
+ this.setState({ open: false });
+ };
+
+ handleHelpClick = (event: React.MouseEvent) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ if (!this.state.open && !this.state.loading && this.state.content === undefined) {
+ this.fetchContent();
+ }
+
+ if (this.state.open) {
+ this.setState({ open: false });
+ } else {
+ // defer opening to not trigger OutsideClickHandler.onClickOutside callback
+ setTimeout(() => {
+ this.setState({ open: true });
+ }, 0);
+ }
+ };
+
+ renderOverlay() {
+ if (this.state.loading) {
+ return (
+
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/DocLink-test.tsx b/server/sonar-web/src/main/js/components/docs/__tests__/DocLink-test.tsx
new file mode 100644
index 00000000000..ad740c2d833
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/docs/__tests__/DocLink-test.tsx
@@ -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 DocLink from '../DocLink';
+
+it('should render simple link', () => {
+ expect(shallow()).toMatchSnapshot();
+});
+
+it.skip('should render documentation anchor', () => {
+ expect(shallow()).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/DocMarkdownBlock-test.tsx b/server/sonar-web/src/main/js/components/docs/__tests__/DocMarkdownBlock-test.tsx
new file mode 100644
index 00000000000..587343d7c0b
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/docs/__tests__/DocMarkdownBlock-test.tsx
@@ -0,0 +1,43 @@
+/*
+ * 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 DocMarkdownBlock from '../DocMarkdownBlock';
+
+// mock `remark` and `remark-react` to work around the issue with cjs imports
+jest.mock('remark', () => {
+ const remark = require.requireActual('remark');
+ return { default: remark };
+});
+
+jest.mock('remark-react', () => {
+ const remarkReact = require.requireActual('remark-react');
+ return { default: remarkReact };
+});
+
+it('should render simple markdown', () => {
+ expect(shallow()).toMatchSnapshot();
+});
+
+it('should render use custom component for links', () => {
+ expect(
+ shallow().find('DocLink')
+ ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/DocTooltip-test.tsx b/server/sonar-web/src/main/js/components/docs/__tests__/DocTooltip-test.tsx
new file mode 100644
index 00000000000..bc5b4998cc7
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/docs/__tests__/DocTooltip-test.tsx
@@ -0,0 +1,48 @@
+/*
+ * 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 DocTooltip from '../DocTooltip';
+import { click } from '../../../helpers/testUtils';
+
+jest.useFakeTimers();
+
+it('should render', () => {
+ const wrapper = shallow();
+ wrapper.setState({ content: 'this is *bold* text', open: true, loading: true });
+ expect(wrapper).toMatchSnapshot();
+ wrapper.setState({ loading: false });
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should reset state when receiving new doc', () => {
+ const wrapper = shallow();
+ wrapper.setState({ content: 'this is *bold* text', open: true });
+ wrapper.setProps({ doc: 'baz' });
+ expect(wrapper.state()).toEqual({ content: undefined, loading: false, open: false });
+});
+
+it('should toggle', () => {
+ const wrapper = shallow();
+ expect(wrapper.state('open')).toBe(false);
+ click(wrapper.find('a'));
+ jest.runAllTimers();
+ expect(wrapper.state('open')).toBe(true);
+});
diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocLink-test.tsx.snap b/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocLink-test.tsx.snap
new file mode 100644
index 00000000000..97a6d1d24bc
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocLink-test.tsx.snap
@@ -0,0 +1,7 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render simple link 1`] = `
+
+`;
diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocMarkdownBlock-test.tsx.snap b/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocMarkdownBlock-test.tsx.snap
new file mode 100644
index 00000000000..28a2441e530
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocMarkdownBlock-test.tsx.snap
@@ -0,0 +1,32 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render simple markdown 1`] = `
+
+
+
+ this is
+
+ bold
+
+ text
+
+
+
+`;
+
+exports[`should render use custom component for links 1`] = `
+
+ link
+
+`;
diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocTooltip-test.tsx.snap b/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocTooltip-test.tsx.snap
new file mode 100644
index 00000000000..38e7789c044
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocTooltip-test.tsx.snap
@@ -0,0 +1,61 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render 1`] = `
+