aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-web/src/main/js')
-rw-r--r--server/sonar-web/src/main/js/@types/remark-react.d.ts33
-rw-r--r--server/sonar-web/src/main/js/@types/remark.d.ts22
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css1
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx83
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/NoBranchSupportPopup.tsx30
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/SingleBranchHelperPopup.tsx34
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx10
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap81
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/NoBranchSupportPopup-test.tsx.snap43
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/SingleBranchHelperPopup-test.tsx.snap43
-rw-r--r--server/sonar-web/src/main/js/app/styles/components/badges.css1
-rw-r--r--server/sonar-web/src/main/js/app/styles/components/search-navigator.css1
-rw-r--r--server/sonar-web/src/main/js/app/styles/components/tooltips.css168
-rw-r--r--server/sonar-web/src/main/js/app/styles/init/links.css4
-rw-r--r--server/sonar-web/src/main/js/app/styles/init/tables.css10
-rw-r--r--server/sonar-web/src/main/js/app/styles/style.css8
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/Facet.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/ProfileFacet.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx18
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx20
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/TemplateFacet.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGate.js10
-rw-r--r--server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGate-test.js.snap17
-rw-r--r--server/sonar-web/src/main/js/apps/overview/styles.css5
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityGate/Header.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/__snapshots__/Header-test.tsx.snap14
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityProfiles/Header.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/__snapshots__/Header-test.tsx.snap14
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.js6
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.js6
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.tsx45
-rw-r--r--server/sonar-web/src/main/js/components/common/BubblePopup.tsx4
-rw-r--r--server/sonar-web/src/main/js/components/controls/Popup.tsx73
-rw-r--r--server/sonar-web/src/main/js/components/controls/Tooltip.css126
-rw-r--r--server/sonar-web/src/main/js/components/controls/Tooltip.tsx9
-rw-r--r--server/sonar-web/src/main/js/components/docs/DocLink.tsx45
-rw-r--r--server/sonar-web/src/main/js/components/docs/DocMarkdownBlock.tsx48
-rw-r--r--server/sonar-web/src/main/js/components/docs/DocTooltip.tsx134
-rw-r--r--server/sonar-web/src/main/js/components/docs/__tests__/DocLink-test.tsx30
-rw-r--r--server/sonar-web/src/main/js/components/docs/__tests__/DocMarkdownBlock-test.tsx43
-rw-r--r--server/sonar-web/src/main/js/components/docs/__tests__/DocTooltip-test.tsx48
-rw-r--r--server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocLink-test.tsx.snap7
-rw-r--r--server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocMarkdownBlock-test.tsx.snap32
-rw-r--r--server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocTooltip-test.tsx.snap61
-rw-r--r--server/sonar-web/src/main/js/components/facet/FacetHeader.tsx5
-rw-r--r--server/sonar-web/src/main/js/components/icons-components/PlusCircleIcon.tsx39
50 files changed, 1003 insertions, 497 deletions
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<Props, State> {
@@ -59,14 +57,9 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State
onSonarCloud: PropTypes.bool
};
- constructor(props: Props) {
- super(props);
- this.state = {
- dropdownOpen: false,
- noBranchSupportPopupOpen: false,
- singleBranchPopupOpen: false
- };
- }
+ state: State = {
+ dropdownOpen: false
+ };
componentDidMount() {
this.mounted = true;
@@ -78,7 +71,7 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State
!isSameBranchLike(nextProps.currentBranchLike, this.props.currentBranchLike) ||
nextProps.location !== this.props.location
) {
- this.setState({ dropdownOpen: false, singleBranchPopupOpen: false });
+ this.setState({ dropdownOpen: false });
}
}
@@ -99,34 +92,6 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State
}
};
- toggleSingleBranchPopup = (show?: boolean) => {
- if (show !== undefined) {
- this.setState({ singleBranchPopupOpen: show });
- } else {
- this.setState(state => ({ singleBranchPopupOpen: !state.singleBranchPopupOpen }));
- }
- };
-
- toggleNoBranchSupportPopup = (show?: boolean) => {
- if (show !== undefined) {
- this.setState({ noBranchSupportPopupOpen: show });
- } else {
- this.setState(state => ({ noBranchSupportPopupOpen: !state.noBranchSupportPopupOpen }));
- }
- };
-
- handleSingleBranchClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
- event.preventDefault();
- event.stopPropagation();
- this.toggleSingleBranchPopup();
- };
-
- handleNoBranchSupportClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
- 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<Props, State
};
renderSingleBranchPopup = () => (
- <div className="display-inline-block spacer-left">
- <a className="link-no-underline" href="#" onClick={this.handleSingleBranchClick}>
- <HelpIcon fill={theme.blue} />
- </a>
- <BubblePopupHelper
- isOpen={this.state.singleBranchPopupOpen}
- popup={<SingleBranchHelperPopup />}
- position="bottomleft"
- togglePopup={this.toggleSingleBranchPopup}
- />
- </div>
+ <Popup overlay={<SingleBranchHelperPopup />}>
+ {({ onClick }) => (
+ <a className="display-flex-center spacer-left link-no-underline" href="#" onClick={onClick}>
+ <PlusCircleIcon fill={theme.blue} size={12} />
+ </a>
+ )}
+ </Popup>
);
renderNoBranchSupportPopup = () => (
- <div className="display-inline-block spacer-left">
- <a className="link-no-underline" href="#" onClick={this.handleNoBranchSupportClick}>
- <HelpIcon fill={theme.gray80} />
- </a>
- <BubblePopupHelper
- isOpen={this.state.noBranchSupportPopupOpen}
- popup={<NoBranchSupportPopup />}
- position="bottomleft"
- togglePopup={this.toggleNoBranchSupportPopup}
- />
- </div>
+ <Popup overlay={<NoBranchSupportPopup />}>
+ {({ onClick }) => (
+ <a className="display-flex-center spacer-left link-no-underline" href="#" onClick={onClick}>
+ <PlusCircleIcon fill={theme.gray80} size={12} />
+ </a>
+ )}
+ </Popup>
);
render() {
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/NoBranchSupportPopup.tsx b/server/sonar-web/src/main/js/app/components/nav/component/NoBranchSupportPopup.tsx
index d6a15f81671..5493c37d39c 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/NoBranchSupportPopup.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/NoBranchSupportPopup.tsx
@@ -18,25 +18,21 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import BubblePopup from '../../../../components/common/BubblePopup';
import { translate } from '../../../../helpers/l10n';
-interface Props {
- popupPosition?: any;
-}
-
-export default function NoBranchSupportPopup(props: Props) {
+export default function NoBranchSupportPopup() {
return (
- <BubblePopup position={props.popupPosition} customClass="bubble-popup-bottom">
- <div className="abs-width-400">
- <h6 className="spacer-bottom">{translate('branches.no_support.header')}</h6>
- <p className="big-spacer-bottom markdown">{translate('branches.no_support.header.text')}</p>
- <p>
- <a href="https://redirect.sonarsource.com/editions/developer.html" target="_blank">
- {translate('learn_more')}
- </a>
- </p>
- </div>
- </BubblePopup>
+ <>
+ <h6 className="spacer-bottom">{translate('branches.no_support.header')}</h6>
+ <p className="big-spacer-bottom markdown">{translate('branches.no_support.header.text')}</p>
+ <p>
+ <a
+ href="https://redirect.sonarsource.com/editions/developer.html"
+ rel="noopener noreferrer"
+ target="_blank">
+ {translate('learn_more')}
+ </a>
+ </p>
+ </>
);
}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/SingleBranchHelperPopup.tsx b/server/sonar-web/src/main/js/app/components/nav/component/SingleBranchHelperPopup.tsx
index 399aff45650..b569b999505 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/SingleBranchHelperPopup.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/SingleBranchHelperPopup.tsx
@@ -18,28 +18,22 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import BubblePopup from '../../../../components/common/BubblePopup';
import { translate } from '../../../../helpers/l10n';
-interface Props {
- popupPosition?: any;
-}
-
-export default function SingleBranchHelperPopup(props: Props) {
+export default function SingleBranchHelperPopup() {
return (
- <BubblePopup position={props.popupPosition} customClass="bubble-popup-bottom">
- <div className="abs-width-400">
- <h6 className="spacer-bottom">{translate('branches.learn_how_to_analyze')}</h6>
- <p className="big-spacer-bottom markdown">
- {translate('branches.learn_how_to_analyze.text')}
- </p>
- <a
- className="button"
- href="https://redirect.sonarsource.com/doc/branches.html"
- target="_blank">
- {translate('about_page.read_documentation')}
- </a>
- </div>
- </BubblePopup>
+ <>
+ <h6 className="spacer-bottom">{translate('branches.learn_how_to_analyze')}</h6>
+ <p className="big-spacer-bottom markdown">
+ {translate('branches.learn_how_to_analyze.text')}
+ </p>
+ <a
+ className="button"
+ href="https://redirect.sonarsource.com/doc/branches.html"
+ rel="noopener noreferrer"
+ target="_blank">
+ {translate('about_page.read_documentation')}
+ </a>
+ </>
);
}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx
index 5258daf383f..2c2651988af 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx
@@ -114,10 +114,7 @@ it('renders single branch popup', () => {
/>,
{ context: { branchesEnabled: true } }
);
- expect(wrapper).toMatchSnapshot();
- expect(wrapper.find('BubblePopupHelper').prop('isOpen')).toBe(false);
- click(wrapper.find('a'));
- expect(wrapper.find('BubblePopupHelper').prop('isOpen')).toBe(true);
+ expect(wrapper.find('Popup')).toMatchSnapshot();
});
it('renders no branch support popup', () => {
@@ -130,10 +127,7 @@ it('renders no branch support popup', () => {
/>,
{ context: { branchesEnabled: false } }
);
- expect(wrapper).toMatchSnapshot();
- expect(wrapper.find('BubblePopupHelper').prop('isOpen')).toBe(false);
- click(wrapper.find('a'));
- expect(wrapper.find('BubblePopupHelper').prop('isOpen')).toBe(true);
+ expect(wrapper.find('Popup')).toMatchSnapshot();
});
it('renders nothing on SonarCloud without branch support', () => {
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap
index 42bc367a274..c3042574729 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap
@@ -36,44 +36,9 @@ exports[`renders main branch 1`] = `
`;
exports[`renders no branch support popup 1`] = `
-<div
- className="navbar-context-branches"
->
- <BranchIcon
- branchLike={
- Object {
- "isMain": true,
- "name": "master",
- }
- }
- className="little-spacer-right"
- fill="#cdcdcd"
- />
- <span
- className="note"
- >
- master
- </span>
- <div
- className="display-inline-block spacer-left"
- >
- <a
- className="link-no-underline"
- href="#"
- onClick={[Function]}
- >
- <HelpIcon
- fill="#cdcdcd"
- />
- </a>
- <BubblePopupHelper
- isOpen={false}
- popup={<NoBranchSupportPopup />}
- position="bottomleft"
- togglePopup={[Function]}
- />
- </div>
-</div>
+<Popup
+ overlay={<NoBranchSupportPopup />}
+/>
`;
exports[`renders pull request 1`] = `
@@ -185,41 +150,7 @@ exports[`renders short-living branch 1`] = `
`;
exports[`renders single branch popup 1`] = `
-<div
- className="navbar-context-branches"
->
- <BranchIcon
- branchLike={
- Object {
- "isMain": true,
- "name": "master",
- }
- }
- className="little-spacer-right"
- />
- <span
- className="note"
- >
- master
- </span>
- <div
- className="display-inline-block spacer-left"
- >
- <a
- className="link-no-underline"
- href="#"
- onClick={[Function]}
- >
- <HelpIcon
- fill="#4b9fd5"
- />
- </a>
- <BubblePopupHelper
- isOpen={false}
- popup={<SingleBranchHelperPopup />}
- position="bottomleft"
- togglePopup={[Function]}
- />
- </div>
-</div>
+<Popup
+ overlay={<SingleBranchHelperPopup />}
+/>
`;
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/NoBranchSupportPopup-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/NoBranchSupportPopup-test.tsx.snap
index cdc9d4cb965..964fc40a823 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/NoBranchSupportPopup-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/NoBranchSupportPopup-test.tsx.snap
@@ -1,30 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders 1`] = `
-<BubblePopup
- customClass="bubble-popup-bottom"
->
- <div
- className="abs-width-400"
+<React.Fragment>
+ <h6
+ className="spacer-bottom"
>
- <h6
- className="spacer-bottom"
- >
- branches.no_support.header
- </h6>
- <p
- className="big-spacer-bottom markdown"
+ branches.no_support.header
+ </h6>
+ <p
+ className="big-spacer-bottom markdown"
+ >
+ branches.no_support.header.text
+ </p>
+ <p>
+ <a
+ href="https://redirect.sonarsource.com/editions/developer.html"
+ rel="noopener noreferrer"
+ target="_blank"
>
- branches.no_support.header.text
- </p>
- <p>
- <a
- href="https://redirect.sonarsource.com/editions/developer.html"
- target="_blank"
- >
- learn_more
- </a>
- </p>
- </div>
-</BubblePopup>
+ learn_more
+ </a>
+ </p>
+</React.Fragment>
`;
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/SingleBranchHelperPopup-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/SingleBranchHelperPopup-test.tsx.snap
index 459630aab47..a27c56302eb 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/SingleBranchHelperPopup-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/SingleBranchHelperPopup-test.tsx.snap
@@ -1,29 +1,24 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders 1`] = `
-<BubblePopup
- customClass="bubble-popup-bottom"
->
- <div
- className="abs-width-400"
+<React.Fragment>
+ <h6
+ className="spacer-bottom"
>
- <h6
- className="spacer-bottom"
- >
- branches.learn_how_to_analyze
- </h6>
- <p
- className="big-spacer-bottom markdown"
- >
- branches.learn_how_to_analyze.text
- </p>
- <a
- className="button"
- href="https://redirect.sonarsource.com/doc/branches.html"
- target="_blank"
- >
- about_page.read_documentation
- </a>
- </div>
-</BubblePopup>
+ branches.learn_how_to_analyze
+ </h6>
+ <p
+ className="big-spacer-bottom markdown"
+ >
+ branches.learn_how_to_analyze.text
+ </p>
+ <a
+ className="button"
+ href="https://redirect.sonarsource.com/doc/branches.html"
+ rel="noopener noreferrer"
+ target="_blank"
+ >
+ about_page.read_documentation
+ </a>
+</React.Fragment>
`;
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<Props> {
className={classNames({ 'search-navigator-facet-box-forbidden': this.props.disabled })}
property={this.props.property}>
<FacetHeader
- helper={this.props.disabled ? this.props.disabledHelper : undefined}
- name={translate('coding_rules.facet', this.props.property)}
+ name={
+ <Tooltip overlay={this.props.disabled ? this.props.disabledHelper : undefined}>
+ <span>{translate('coding_rules.facet', this.props.property)}</span>
+ </Tooltip>
+ }
onClear={this.handleClear}
onClick={this.props.disabled ? undefined : this.handleHeaderClick}
open={this.props.open && !this.props.disabled}
- values={values}
- />
+ values={values}>
+ {this.props.children}
+ </FacetHeader>
{this.props.open &&
items !== undefined && <FacetItemsList>{items.map(this.renderItem)}</FacetItemsList>}
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<Props> {
onClear={this.handleClear}
onClick={this.handleHeaderClick}
open={this.props.open}
- values={this.getTextValue()}
- />
+ values={this.getTextValue()}>
+ <DocTooltip className="spacer-left" doc="rules/rules-quality-profiles" />
+ </FacetHeader>
{this.props.open && <FacetItemsList>{profiles.map(this.renderItem)}</FacetItemsList>}
</FacetBox>
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<Props, State> {
{params.length > 0 && <RuleDetailsParameters params={params} />}
{isEditable && (
- <div className="coding-rules-detail-description">
+ <div className="coding-rules-detail-description display-flex-center">
{/* `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 */}
<CustomRuleButton
@@ -202,12 +203,15 @@ export default class RuleDetails extends React.PureComponent<Props, State> {
modalHeader={translate('coding_rules.delete_rule')}
onConfirm={this.handleDelete}>
{({ onClick }) => (
- <Button
- className="button-red spacer-left js-delete"
- id="coding-rules-detail-rule-delete"
- onClick={onClick}>
- {translate('delete')}
- </Button>
+ <>
+ <Button
+ className="button-red spacer-left js-delete"
+ id="coding-rules-detail-rule-delete"
+ onClick={onClick}>
+ {translate('delete')}
+ </Button>
+ <DocTooltip className="spacer-left" doc="rules/custom-rule-removal" />
+ </>
)}
</ConfirmButton>
</div>
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<Props, State> {
return null;
}
return (
- <Tooltip overlay={translate('coding_rules.custom_rule.title')}>
- <li className="coding-rules-detail-property">
- {translate('coding_rules.custom_rule')}
- {' ('}
- <Link to={getRuleUrl(ruleDetails.templateKey, this.props.organization)}>
- {translate('coding_rules.show_template')}
- </Link>
- {')'}
- </li>
- </Tooltip>
+ <li className="coding-rules-detail-property">
+ {translate('coding_rules.custom_rule')}
+ {' ('}
+ <Link to={getRuleUrl(ruleDetails.templateKey, this.props.organization)}>
+ {translate('coding_rules.show_template')}
+ </Link>
+ {')'}
+ <DocTooltip className="little-spacer-left" doc="rules/custom-rules" />
+ </li>
);
};
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/TemplateFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/TemplateFacet.tsx
index 4a5c87d4774..1f9c3f803ab 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/TemplateFacet.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/TemplateFacet.tsx
@@ -20,6 +20,7 @@
import * as React from 'react';
import Facet, { BasicProps } from './Facet';
import { Omit } from '../../../app/types';
+import DocTooltip from '../../../components/docs/DocTooltip';
import { translate } from '../../../helpers/l10n';
interface Props extends Omit<BasicProps, 'onChange' | 'values'> {
@@ -55,8 +56,9 @@ export default class TemplateFacet extends React.PureComponent<Props> {
renderName={this.renderName}
renderTextName={this.renderName}
singleSelection={true}
- values={value !== undefined ? [String(value)] : []}
- />
+ values={value !== undefined ? [String(value)] : []}>
+ <DocTooltip className="spacer-left" doc="rules/rule-templates" />
+ </Facet>
);
}
}
diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGate.js b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGate.js
index 20ab7885146..8c647e71b56 100644
--- a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGate.js
+++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGate.js
@@ -25,6 +25,7 @@ import * as theme from '../../../app/theme';
import { translate } from '../../../helpers/l10n';
import Level from '../../../components/ui/Level';
import Tooltip from '../../../components/controls/Tooltip';
+import DocTooltip from '../../../components/docs/DocTooltip';
import HelpIcon from '../../../components/icons-components/HelpIcon';
/*:: import type { Component, MeasuresList } from '../types'; */
@@ -64,10 +65,11 @@ export default function QualityGate({ branchLike, component, measures } /*: Prop
return (
<div className="overview-quality-gate" id="overview-quality-gate">
- <h2 className="overview-title">
- {translate('overview.quality_gate')}
- <Level level={level} />
- </h2>
+ <div className="display-flex-center">
+ <h2 className="overview-title">{translate('overview.quality_gate')}</h2>
+ <DocTooltip className="spacer-left" doc="quality-gates/project-homepage-quality-gate" />
+ <Level className="big-spacer-left" level={level} />
+ </div>
{ignoredConditions && (
<div className="alert alert-info display-inline-block big-spacer-top">
diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGate-test.js.snap b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGate-test.js.snap
index 2b9f86b6062..2387806ada6 100644
--- a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGate-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGate-test.js.snap
@@ -5,14 +5,23 @@ exports[`renders message about ignored conditions 1`] = `
className="overview-quality-gate"
id="overview-quality-gate"
>
- <h2
- className="overview-title"
+ <div
+ className="display-flex-center"
>
- overview.quality_gate
+ <h2
+ className="overview-title"
+ >
+ overview.quality_gate
+ </h2>
+ <DocTooltip
+ className="spacer-left"
+ doc="quality-gates/project-homepage-quality-gate"
+ />
<Level
+ className="big-spacer-left"
level="OK"
/>
- </h2>
+ </div>
<div
className="alert alert-info display-inline-block big-spacer-top"
>
diff --git a/server/sonar-web/src/main/js/apps/overview/styles.css b/server/sonar-web/src/main/js/apps/overview/styles.css
index ac492720ff5..0d0dc6dbee9 100644
--- a/server/sonar-web/src/main/js/apps/overview/styles.css
+++ b/server/sonar-web/src/main/js/apps/overview/styles.css
@@ -45,11 +45,6 @@
font-weight: 400;
}
-.overview-title > .level {
- vertical-align: top;
- margin-left: 15px;
-}
-
/*
* Quality Gate
*/
diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/Header.tsx b/server/sonar-web/src/main/js/apps/projectQualityGate/Header.tsx
index 5adc7becf55..de42184f96d 100644
--- a/server/sonar-web/src/main/js/apps/projectQualityGate/Header.tsx
+++ b/server/sonar-web/src/main/js/apps/projectQualityGate/Header.tsx
@@ -18,12 +18,16 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import DocTooltip from '../../components/docs/DocTooltip';
import { translate } from '../../helpers/l10n';
export default function Header() {
return (
<header className="page-header">
- <h1 className="page-title">{translate('project_quality_gate.page')}</h1>
+ <div className="page-title display-flex-center">
+ <h1>{translate('project_quality_gate.page')}</h1>
+ <DocTooltip className="spacer-left" doc="quality-gates/quality-gate-projects" />
+ </div>
<div className="page-description">{translate('project_quality_gate.page.description')}</div>
</header>
);
diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/__snapshots__/Header-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/__snapshots__/Header-test.tsx.snap
index eaade8b5468..ccf8e090d57 100644
--- a/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/__snapshots__/Header-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/__snapshots__/Header-test.tsx.snap
@@ -4,11 +4,17 @@ exports[`renders 1`] = `
<header
className="page-header"
>
- <h1
- className="page-title"
+ <div
+ className="page-title display-flex-center"
>
- project_quality_gate.page
- </h1>
+ <h1>
+ project_quality_gate.page
+ </h1>
+ <DocTooltip
+ className="spacer-left"
+ doc="quality-gates/quality-gate-projects"
+ />
+ </div>
<div
className="page-description"
>
diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/Header.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/Header.tsx
index b1347b6ff2e..cf31adcb621 100644
--- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/Header.tsx
+++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/Header.tsx
@@ -18,12 +18,16 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import DocTooltip from '../../components/docs/DocTooltip';
import { translate } from '../../helpers/l10n';
export default function Header() {
return (
<header className="page-header">
- <h1 className="page-title">{translate('project_quality_profiles.page')}</h1>
+ <div className="page-title display-flex-center">
+ <h1>{translate('project_quality_profiles.page')}</h1>
+ <DocTooltip className="spacer-left" doc="quality-profiles/quality-profile-projects" />
+ </div>
<div className="page-description">
{translate('project_quality_profiles.page.description')}
</div>
diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/__snapshots__/Header-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/__snapshots__/Header-test.tsx.snap
index 4f0e35f4a2f..03ed0f32b01 100644
--- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/__snapshots__/Header-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/__snapshots__/Header-test.tsx.snap
@@ -4,11 +4,17 @@ exports[`renders 1`] = `
<header
className="page-header"
>
- <h1
- className="page-title"
+ <div
+ className="page-title display-flex-center"
>
- project_quality_profiles.page
- </h1>
+ <h1>
+ project_quality_profiles.page
+ </h1>
+ <DocTooltip
+ className="spacer-left"
+ doc="quality-profiles/quality-profile-projects"
+ />
+ </div>
<div
className="page-description"
>
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.js b/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.js
index f3fb35559e7..982d6d9f2ab 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.js
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.js
@@ -21,6 +21,7 @@ import React from 'react';
import { sortBy, uniqBy } from 'lodash';
import AddConditionForm from './AddConditionForm';
import Condition from './Condition';
+import DocTooltip from '../../../components/docs/DocTooltip';
import { translate, getLocalizedMetricName } from '../../../helpers/l10n';
function getKey(condition, index) {
@@ -89,7 +90,10 @@ export default class Conditions extends React.PureComponent {
}));
return (
<div className="quality-gate-section" id="quality-gate-conditions">
- <h3 className="spacer-bottom">{translate('quality_gates.conditions')}</h3>
+ <header className="display-flex-center spacer-bottom">
+ <h3>{translate('quality_gates.conditions')}</h3>
+ <DocTooltip className="spacer-left" doc="quality-gates/quality-gate-conditions" />
+ </header>
<div className="big-spacer-bottom">{translate('quality_gates.introduction')}</div>
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.js b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.js
index ebe73fe1e8a..35b37f12bae 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.js
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.js
@@ -20,6 +20,7 @@
import React from 'react';
import Conditions from './Conditions';
import Projects from './Projects';
+import DocTooltip from '../../../components/docs/DocTooltip';
import { translate } from '../../../helpers/l10n';
export default class DetailsContent extends React.PureComponent {
@@ -47,7 +48,10 @@ export default class DetailsContent extends React.PureComponent {
/>
<div id="quality-gate-projects" className="quality-gate-section">
- <h3 className="spacer-bottom">{translate('quality_gates.projects')}</h3>
+ <header className="display-flex-center spacer-bottom">
+ <h3>{translate('quality_gates.projects')}</h3>
+ <DocTooltip className="spacer-left" doc="quality-gates/quality-gate-projects" />
+ </header>
{gate.isDefault ? (
defaultMessage
) : (
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx
index a661becfdef..8e2b1aa14ba 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx
@@ -23,6 +23,7 @@ import RenameQualityGateForm from './RenameQualityGateForm';
import CopyQualityGateForm from './CopyQualityGateForm';
import DeleteQualityGateForm from './DeleteQualityGateForm';
import { fetchQualityGate, QualityGate, setQualityGateAsDefault } from '../../../api/quality-gates';
+import DocTooltip from '../../../components/docs/DocTooltip';
import { Button } from '../../../components/ui/buttons';
import { translate } from '../../../helpers/l10n';
@@ -73,10 +74,15 @@ export default class DetailsHeader extends React.PureComponent<Props, State> {
<div className="layout-page-header-panel layout-page-main-header issues-main-header">
<div className="layout-page-header-panel-inner layout-page-main-header-inner">
<div className="layout-page-main-inner">
- <h2 className="pull-left">
- {qualityGate.name}
- {qualityGate.isBuiltIn && <BuiltInQualityGateBadge className="spacer-left" />}
- </h2>
+ <div className="pull-left display-flex-center">
+ <h2>{qualityGate.name}</h2>
+ {qualityGate.isBuiltIn && (
+ <>
+ <BuiltInQualityGateBadge className="spacer-left" />
+ <DocTooltip className="spacer-left" doc="quality-gates/built-in-quality-gate" />
+ </>
+ )}
+ </div>
<div className="pull-right">
{actions.rename && (
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx
index 4a267e581fb..da61b1c5623 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx
@@ -21,6 +21,7 @@ import * as React from 'react';
import CreateQualityGateForm from '../components/CreateQualityGateForm';
import { QualityGate } from '../../../api/quality-gates';
import { Organization } from '../../../app/types';
+import DocTooltip from '../../../components/docs/DocTooltip';
import { Button } from '../../../components/ui/buttons';
import { translate } from '../../../helpers/l10n';
@@ -50,7 +51,6 @@ export default class ListHeader extends React.PureComponent<Props, State> {
return (
<header className="page-header">
- <h1 className="page-title">{translate('quality_gates.page')}</h1>
{this.props.canCreate && (
<div className="page-actions">
<Button id="quality-gate-add" onClick={this.openCreateQualityGateForm}>
@@ -58,6 +58,10 @@ export default class ListHeader extends React.PureComponent<Props, State> {
</Button>
</div>
)}
+ <div className="display-flex-center">
+ <h1 className="page-title">{translate('quality_gates.page')}</h1>
+ <DocTooltip className="spacer-left" doc="quality-gates/quality-gate" />
+ </div>
{this.state.createQualityGateOpen && (
<CreateQualityGateForm
onClose={this.closeCreateQualityGateForm}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.tsx
index c30eff07980..af274592121 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.tsx
@@ -32,8 +32,8 @@ interface Props {
export default function ProfileLink({ name, language, organization, children, ...other }: Props) {
return (
<Link
- to={getProfilePath(name, language, organization)}
activeClassName="link-no-underline"
+ to={getProfilePath(name, language, organization)}
{...other}>
{children}
</Link>
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.tsx
index 812d6110047..ae1e57854f9 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.tsx
@@ -21,6 +21,7 @@ import * as React from 'react';
import { groupBy, pick, sortBy } from 'lodash';
import ProfilesListRow from './ProfilesListRow';
import ProfilesListHeader from './ProfilesListHeader';
+import DocTooltip from '../../../components/docs/DocTooltip';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { Profile } from '../types';
@@ -61,7 +62,13 @@ export default class ProfilesList extends React.PureComponent<Props> {
{', '}
{translateWithParameters('quality_profiles.x_profiles', profilesCount)}
</th>
- <th className="text-right nowrap">{translate('quality_profiles.list.projects')}</th>
+ <th className="text-right nowrap">
+ {translate('quality_profiles.list.projects')}
+ <DocTooltip
+ className="table-cell-doc"
+ doc="quality-profiles/quality-profile-projects"
+ />
+ </th>
<th className="text-right nowrap">{translate('quality_profiles.list.rules')}</th>
<th className="text-right nowrap">{translate('quality_profiles.list.updated')}</th>
<th className="text-right nowrap">{translate('quality_profiles.list.used')}</th>
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.tsx
index 677ce226b99..a0b3302ff48 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.tsx
@@ -28,6 +28,7 @@ import { getRulesUrl } from '../../../helpers/urls';
import { isStagnant } from '../utils';
import { Profile } from '../types';
import Tooltip from '../../../components/controls/Tooltip';
+import DocTooltip from '../../../components/docs/DocTooltip';
interface Props {
onRequestFail: (reason: any) => void;
@@ -41,14 +42,21 @@ export default class ProfilesListRow extends React.PureComponent<Props> {
const { profile } = this.props;
const offset = 25 * (profile.depth - 1);
return (
- <div style={{ paddingLeft: offset }}>
- <ProfileLink
- language={profile.language}
- name={profile.name}
- organization={this.props.organization}>
- {profile.name}
- </ProfileLink>
- {profile.isBuiltIn && <BuiltInQualityProfileBadge className="spacer-left" />}
+ <div className="display-flex-center" style={{ paddingLeft: offset }}>
+ <div>
+ <ProfileLink
+ language={profile.language}
+ name={profile.name}
+ organization={this.props.organization}>
+ {profile.name}
+ </ProfileLink>
+ </div>
+ {profile.isBuiltIn && (
+ <>
+ <BuiltInQualityProfileBadge className="spacer-left" />
+ <DocTooltip className="spacer-left" doc="quality-profiles/built-in-quality-profile" />
+ </>
+ )}
</div>
);
}
@@ -57,7 +65,12 @@ export default class ProfilesListRow extends React.PureComponent<Props> {
const { profile } = this.props;
if (profile.isDefault) {
- return <span className="badge">{translate('default')}</span>;
+ return (
+ <>
+ <span className="badge">{translate('default')}</span>
+ <DocTooltip className="table-cell-doc" doc="quality-profiles/default-quality-profile" />
+ </>
+ );
}
return <span>{profile.projectCount}</span>;
@@ -122,23 +135,23 @@ export default class ProfilesListRow extends React.PureComponent<Props> {
render() {
return (
<tr
- className="quality-profiles-table-row"
+ className="quality-profiles-table-row text-middle"
data-key={this.props.profile.key}
data-name={this.props.profile.name}>
- <td className="quality-profiles-table-name">{this.renderName()}</td>
- <td className="quality-profiles-table-projects thin nowrap text-right">
+ <td className="quality-profiles-table-name text-middle">{this.renderName()}</td>
+ <td className="quality-profiles-table-projects thin nowrap text-middle text-right">
{this.renderProjects()}
</td>
- <td className="quality-profiles-table-rules thin nowrap text-right">
+ <td className="quality-profiles-table-rules thin nowrap text-middle text-right">
{this.renderRules()}
</td>
- <td className="quality-profiles-table-date thin nowrap text-right">
+ <td className="quality-profiles-table-date thin nowrap text-middle text-right">
{this.renderUpdateDate()}
</td>
- <td className="quality-profiles-table-date thin nowrap text-right">
+ <td className="quality-profiles-table-date thin nowrap text-middle text-right">
{this.renderUsageDate()}
</td>
- <td className="quality-profiles-table-actions thin nowrap text-right">
+ <td className="quality-profiles-table-actions thin nowrap text-middle text-right">
<ProfileActions
fromList={true}
onRequestFail={this.props.onRequestFail}
diff --git a/server/sonar-web/src/main/js/components/common/BubblePopup.tsx b/server/sonar-web/src/main/js/components/common/BubblePopup.tsx
index 38ebd834989..7020ee57490 100644
--- a/server/sonar-web/src/main/js/components/common/BubblePopup.tsx
+++ b/server/sonar-web/src/main/js/components/common/BubblePopup.tsx
@@ -32,6 +32,10 @@ interface Props {
position: BubblePopupPosition;
}
+/**
+ * Deprecated.
+ * Use <Popup /> 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<any>;
+ overlay: React.ReactNode;
+}
+
+interface State {
+ visible: boolean;
+}
+
+export default class Popup extends React.Component<Props, State> {
+ state: State = { visible: false };
+
+ componentWillReceiveProps(nextProps: Props) {
+ if (nextProps.overlay !== this.props.overlay) {
+ this.setState({ visible: false });
+ }
+ }
+
+ handleClick = (event?: React.MouseEvent<HTMLElement>) => {
+ 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 (
+ <OutsideClickHandler onClickOutside={this.handleClickOutside}>
+ {({ ref }) => <div ref={ref}>{this.props.overlay}</div>}
+ </OutsideClickHandler>
+ );
+ }
+
+ render() {
+ return (
+ <Tooltip classNameSpace="popup" overlay={this.renderOverlay()} visible={this.state.visible}>
+ {this.props.children({ onClick: this.handleClick })}
+ </Tooltip>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/components/controls/Tooltip.css b/server/sonar-web/src/main/js/components/controls/Tooltip.css
index c2529a06b75..bbb8ae5c942 100644
--- a/server/sonar-web/src/main/js/components/controls/Tooltip.css
+++ b/server/sonar-web/src/main/js/components/controls/Tooltip.css
@@ -17,72 +17,120 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-.tooltip {
+.tooltip,
+.popup {
position: absolute;
z-index: var(--tooltipZIndex);
display: block;
height: auto;
box-sizing: border-box;
+}
+
+.tooltip {
font-size: var(--smallFontSize);
font-weight: 300;
line-height: 1.5;
animation: fadeIn 0.3s forwards;
}
-.tooltip.top {
+.popup {
+ font-size: var(--baseFontSize);
+ font-weight: normal;
+}
+
+.tooltip.top,
+.popup.top {
padding: 5px 0;
margin-top: -3px;
}
-.tooltip.right {
+.tooltip.right,
+.popup.right {
padding: 0 5px;
margin-left: 3px;
}
-.tooltip.bottom {
+.tooltip.bottom,
+.popup.bottom {
padding: 5px 0;
margin-top: 3px;
}
-.tooltip.left {
+.tooltip.left,
+.popup.left {
padding: 0 5px;
margin-left: -3px;
}
-.tooltip-inner {
+.tooltip-inner,
+.popup-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 {
+ padding: 3px 8px;
+ color: #fff;
+ background-color: #475760;
+ letter-spacing: 0.04em;
+}
+
+.popup-inner {
+ padding: calc(2 * var(--gridSize));
+ border: 1px solid var(--barBorderColor);
+ color: var(--baseFontColor);
+ background-color: #fff;
+ box-shadow: var(--defaultShadow);
+}
+
.tooltip-inner .alert {
margin-bottom: 5px;
border-radius: 4px;
}
-.tooltip-arrow {
+.tooltip-inner a {
+ color: var(--lightBlue);
+}
+
+.tooltip-arrow,
+.popup-arrow,
+.popup-arrow::after {
position: absolute;
width: 0;
height: 0;
border: solid transparent;
}
-.tooltip.top .tooltip-arrow {
+.tooltip.top .tooltip-arrow,
+.popup.top .popup-arrow,
+.popup.top .popup-arrow::after {
bottom: 0;
left: 50%;
border-width: 5px 5px 0;
- border-top-color: #475760;
transform: translateX(-5px);
}
-.tooltip.right .tooltip-arrow {
+.tooltip.top .tooltip-arrow {
+ border-top-color: #475760;
+}
+
+.popup.top .popup-arrow {
+ border-top-color: var(--barBorderColor);
+}
+
+.popup.top .popup-arrow::after {
+ content: '';
+ border-top-color: #fff;
+ transform: translateX(-5px) translateY(-1px);
+}
+
+.tooltip.right .tooltip-arrow,
+.popup.right .popup-arrow,
+.popup.right .popup-arrow::after {
top: 50%;
left: 0;
transform: translateY(-5px);
@@ -90,7 +138,23 @@
border-right-color: #475760;
}
-.tooltip.left .tooltip-arrow {
+.tooltip.right .tooltip-arrow {
+ border-right-color: #475760;
+}
+
+.popup.right .popup-arrow {
+ border-right-color: var(--barBorderColor);
+}
+
+.popup.right .popup-arrow::after {
+ content: '';
+ border-right-color: #fff;
+ transform: translateY(-5px) translateX(1px);
+}
+
+.tooltip.left .tooltip-arrow,
+.popup.left .popup-arrow,
+.popup.left .popup-arrow::after {
top: 50%;
right: 0;
transform: translateY(-5px);
@@ -98,7 +162,23 @@
border-left-color: #475760;
}
-.tooltip.bottom .tooltip-arrow {
+.tooltip.left .tooltip-arrow {
+ border-left-color: #475760;
+}
+
+.popup.left .popup-arrow {
+ border-left-color: var(--barBorderColor);
+}
+
+.popup.left .popup-arrow::after {
+ content: '';
+ border-left-color: #fff;
+ transform: translateY(-5px) translateX(-1px);
+}
+
+.tooltip.bottom .tooltip-arrow,
+.popup.bottom .popup-arrow,
+.popup.bottom .popup-arrow::after {
top: 0;
left: 50%;
transform: translateX(-5px);
@@ -106,6 +186,20 @@
border-bottom-color: #475760;
}
+.tooltip.bottom .tooltip-arrow {
+ border-bottom-color: #475760;
+}
+
+.popup.bottom .popup-arrow {
+ border-bottom-color: var(--barBorderColor);
+}
+
+.popup.bottom .popup-arrow::after {
+ content: '';
+ border-bottom-color: #fff;
+ transform: translateX(-5px) translateY(1px);
+}
+
@keyframes fadeIn {
from {
opacity: 0;
diff --git a/server/sonar-web/src/main/js/components/controls/Tooltip.tsx b/server/sonar-web/src/main/js/components/controls/Tooltip.tsx
index a16c9c691db..adc29a6270e 100644
--- a/server/sonar-web/src/main/js/components/controls/Tooltip.tsx
+++ b/server/sonar-web/src/main/js/components/controls/Tooltip.tsx
@@ -25,6 +25,7 @@ import './Tooltip.css';
export type Placement = 'bottom' | 'right' | 'left' | 'top';
interface Props {
+ classNameSpace?: string;
children: React.ReactElement<{}>;
mouseEnterDelay?: number;
onShow?: () => void;
@@ -244,6 +245,8 @@ export class TooltipInner extends React.Component<Props, State> {
};
render() {
+ const { classNameSpace = 'tooltip' } = this.props;
+
return (
<>
{React.cloneElement(this.props.children, {
@@ -253,7 +256,7 @@ export class TooltipInner extends React.Component<Props, State> {
{this.isVisible() && (
<TooltipPortal>
<div
- className={`tooltip ${this.getPlacement()}`}
+ className={`${classNameSpace} ${this.getPlacement()}`}
ref={this.tooltipNodeRef}
style={
isMeasured(this.state)
@@ -265,9 +268,9 @@ export class TooltipInner extends React.Component<Props, State> {
}
: undefined
}>
- <div className="tooltip-inner">{this.props.overlay}</div>
+ <div className={`${classNameSpace}-inner`}>{this.props.overlay}</div>
<div
- className="tooltip-arrow"
+ className={`${classNameSpace}-arrow`}
style={
isMeasured(this.state)
? { marginLeft: -this.state.leftFix, marginTop: -this.state.topFix }
diff --git a/server/sonar-web/src/main/js/components/docs/DocLink.tsx b/server/sonar-web/src/main/js/components/docs/DocLink.tsx
new file mode 100644
index 00000000000..fd19f91cd94
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/docs/DocLink.tsx
@@ -0,0 +1,45 @@
+/*
+ * 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';
+
+export default function DocLink(props: React.AnchorHTMLAttributes<HTMLAnchorElement>) {
+ 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 */}
+ <Link to="" {...other}>
+ {children}
+ </Link>
+ <strong className="little-spacer-left text-danger">[TODO]</strong>
+ </>
+ );
+ }
+ }
+
+ return (
+ <a href={href} {...other}>
+ {children}
+ </a>
+ );
+}
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 (
+ <div className={classNames('markdown', className)}>
+ {
+ remark()
+ .use(reactRenderer, {
+ remarkReactComponents: {
+ // do not render outer <div />
+ div: React.Fragment,
+ // use custom link to render documentation anchors
+ a: DocLink
+ }
+ })
+ .processSync(content).contents
+ }
+ </div>
+ );
+}
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<Props, State> {
+ 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<HTMLAnchorElement>) => {
+ 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 (
+ <div className="abs-width-300">
+ <i className="spinner" />
+ </div>
+ );
+ }
+
+ return (
+ <OutsideClickHandler onClickOutside={this.close}>
+ {({ ref }) => (
+ <div ref={ref}>
+ <DocMarkdownBlock className="cut-margins abs-width-300" content={this.state.content} />
+ </div>
+ )}
+ </OutsideClickHandler>
+ );
+ }
+
+ render() {
+ return (
+ <div className={classNames('display-flex-center', this.props.className)}>
+ <Tooltip
+ classNameSpace="popup"
+ overlay={this.renderOverlay()}
+ visible={this.state.content !== undefined && this.state.open}>
+ <a
+ className="display-flex-center link-no-underline"
+ href="#"
+ onClick={this.handleHelpClick}>
+ <HelpIcon fill={theme.gray80} size={12} />
+ </a>
+ </Tooltip>
+ </div>
+ );
+ }
+}
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(<DocLink href="http://sample.com" />)).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__/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(<DocMarkdownBlock content="this is *bold* text" />)).toMatchSnapshot();
+});
+
+it('should render use custom component for links', () => {
+ expect(
+ shallow(<DocMarkdownBlock content="some [link](#quality-profiles)" />).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(<DocTooltip doc="foo/bar" />);
+ 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(<DocTooltip doc="foo/bar" />);
+ 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(<DocTooltip doc="foo/bar" />);
+ 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`] = `
+<a
+ href="http://sample.com"
+/>
+`;
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`] = `
+<div
+ className="markdown"
+>
+ <React.Fragment
+ key="h-1"
+ >
+ <p
+ key="h-2"
+ >
+ this is
+ <em
+ key="h-3"
+ >
+ bold
+ </em>
+ text
+ </p>
+ </React.Fragment>
+</div>
+`;
+
+exports[`should render use custom component for links 1`] = `
+<DocLink
+ href="#quality-profiles"
+ key="h-3"
+>
+ link
+</DocLink>
+`;
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`] = `
+<div
+ className="display-flex-center"
+>
+ <Tooltip
+ classNameSpace="popup"
+ overlay={
+ <div
+ className="abs-width-300"
+ >
+ <i
+ className="spinner"
+ />
+ </div>
+ }
+ visible={true}
+ >
+ <a
+ className="display-flex-center link-no-underline"
+ href="#"
+ onClick={[Function]}
+ >
+ <HelpIcon
+ fill="#cdcdcd"
+ size={12}
+ />
+ </a>
+ </Tooltip>
+</div>
+`;
+
+exports[`should render 2`] = `
+<div
+ className="display-flex-center"
+>
+ <Tooltip
+ classNameSpace="popup"
+ overlay={
+ <OutsideClickHandler
+ onClickOutside={[Function]}
+ >
+ [Function]
+ </OutsideClickHandler>
+ }
+ visible={true}
+ >
+ <a
+ className="display-flex-center link-no-underline"
+ href="#"
+ onClick={[Function]}
+ >
+ <HelpIcon
+ fill="#cdcdcd"
+ size={12}
+ />
+ </a>
+ </Tooltip>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/components/facet/FacetHeader.tsx b/server/sonar-web/src/main/js/components/facet/FacetHeader.tsx
index 00b6803c124..cc1ae72047a 100644
--- a/server/sonar-web/src/main/js/components/facet/FacetHeader.tsx
+++ b/server/sonar-web/src/main/js/components/facet/FacetHeader.tsx
@@ -25,8 +25,9 @@ import { Button } from '../ui/buttons';
import { translate, translateWithParameters } from '../../helpers/l10n';
interface Props {
+ children?: React.ReactNode;
helper?: string;
- name: string;
+ name: React.ReactNode;
onClear?: () => void;
onClick?: () => void;
open: boolean;
@@ -90,6 +91,8 @@ export default class FacetHeader extends React.PureComponent<Props> {
</span>
)}
+ {this.props.children}
+
<span className="search-navigator-facet-header-value spacer-left spacer-right ">
{this.renderValueIndicator()}
</span>
diff --git a/server/sonar-web/src/main/js/components/icons-components/PlusCircleIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/PlusCircleIcon.tsx
new file mode 100644
index 00000000000..eb529b71d77
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/icons-components/PlusCircleIcon.tsx
@@ -0,0 +1,39 @@
+/*
+ * 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 { IconProps } from './types';
+
+export default function PlusCircleIcon({ className, fill = 'currentColor', size = 16 }: IconProps) {
+ return (
+ <svg
+ className={className}
+ height={size}
+ version="1.1"
+ viewBox="0 0 16 16"
+ width={size}
+ xmlSpace="preserve"
+ xmlnsXlink="http://www.w3.org/1999/xlink">
+ <path
+ d="M8 1c3.863 0 7 3.137 7 7s-3.137 7-7 7-7-3.137-7-7 3.137-7 7-7zm3.726 7.985A.274.274 0 0 0 12 8.711V7.289a.274.274 0 0 0-.274-.274H8.985V4.274A.274.274 0 0 0 8.711 4H7.289a.274.274 0 0 0-.274.274v2.741H4.274A.274.274 0 0 0 4 7.289v1.422c0 .152.123.274.274.274h2.741v2.741c0 .151.122.274.274.274h1.422a.274.274 0 0 0 .274-.274V8.985h2.741z"
+ style={{ fill }}
+ />;
+ </svg>
+ );
+}