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/apps/issues/crossComponentSourceViewer/SnippetViewer.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/SnippetViewer-test.tsx22
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotPrimaryLocationBox.css38
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotPrimaryLocationBox.tsx47
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainer.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotPrimaryLocationBox-test.tsx57
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainer-test.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainerRenderer-test.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotPrimaryLocationBox-test.tsx.snap19
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainer-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainerRenderer-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerRenderer-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx3
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx3
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.tsx3
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap123
18 files changed, 356 insertions, 0 deletions
diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.tsx
index 7cf29c0fdba..afee226ce59 100644
--- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.tsx
@@ -70,6 +70,7 @@ interface Props {
onIssuePopupToggle: (issue: string, popupName: string, open?: boolean) => void;
onLocationSelect: (index: number) => void;
openIssuesByLine: Dict<boolean>;
+ renderAdditionalChildInLine?: (lineNumber: number) => React.ReactNode | undefined;
renderDuplicationPopup: (index: number, line: number) => React.ReactNode;
scroll?: (element: HTMLElement, offset?: number) => void;
snippet: SourceLine[];
@@ -153,6 +154,10 @@ export default class SnippetViewer extends React.PureComponent<Props> {
return (
<Line
+ additionalChild={
+ this.props.renderAdditionalChildInLine &&
+ this.props.renderAdditionalChildInLine(line.line)
+ }
branchLike={this.props.branchLike}
displayAllIssues={false}
displayCoverage={true}
diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/SnippetViewer-test.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/SnippetViewer-test.tsx
index d9dd7b58d76..10600b2a642 100644
--- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/SnippetViewer-test.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/SnippetViewer-test.tsx
@@ -52,6 +52,28 @@ it('should render correctly with no SCM', () => {
expect(wrapper).toMatchSnapshot();
});
+it('should render additional child in line', () => {
+ const sourceline = mockSourceLine({ line: 42 });
+
+ const child = <div>child</div>;
+ const renderAdditionalChildInLine = jest.fn().mockReturnValue(child);
+ const wrapper = shallowRender({ renderAdditionalChildInLine, snippet: [sourceline] });
+
+ const renderedLine = wrapper.instance().renderLine({
+ displayDuplications: false,
+ index: 1,
+ issuesForLine: [],
+ issueLocations: [],
+ line: sourceline,
+ snippet: [sourceline],
+ symbols: [],
+ verticalBuffer: 5
+ });
+
+ expect(renderAdditionalChildInLine).toBeCalledWith(42);
+ expect(renderedLine.props.additionalChild).toBe(child);
+});
+
it('should render correctly when at the top of the file', () => {
const snippet = range(1, 8).map(line => mockSourceLine({ line }));
const wrapper = shallowRender({
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotPrimaryLocationBox.css b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotPrimaryLocationBox.css
new file mode 100644
index 00000000000..435e1830be4
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotPrimaryLocationBox.css
@@ -0,0 +1,38 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.
+ */
+
+.hotspot-primary-location {
+ border: 1px solid var(--blue);
+ background-color: var(--issueBgColor);
+ border-left: 4px solid;
+ margin: 10px -10px;
+}
+
+.hotspot-primary-location.hotspot-risk-exposure-HIGH {
+ border-left-color: var(--red);
+}
+
+.hotspot-primary-location.hotspot-risk-exposure-MEDIUM {
+ border-left-color: var(--orange);
+}
+
+.hotspot-primary-location.hotspot-risk-exposure-LOW {
+ border-left-color: var(--yellow);
+}
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotPrimaryLocationBox.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotPrimaryLocationBox.tsx
new file mode 100644
index 00000000000..be0102264c9
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotPrimaryLocationBox.tsx
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 classNames from 'classnames';
+import * as React from 'react';
+import { ButtonLink } from '../../../components/controls/buttons';
+import { translate } from '../../../helpers/l10n';
+import { Hotspot } from '../../../types/security-hotspots';
+import './HotspotPrimaryLocationBox.css';
+
+export interface HotspotPrimaryLocationBoxProps {
+ hotspot: Hotspot;
+ onCommentClick: () => void;
+}
+
+export default function HotspotPrimaryLocationBox(props: HotspotPrimaryLocationBoxProps) {
+ const { hotspot } = props;
+ return (
+ <div
+ className={classNames(
+ 'hotspot-primary-location',
+ 'display-flex-space-between display-flex-center padded-top padded-bottom big-padded-left big-padded-right',
+ `hotspot-risk-exposure-${hotspot.rule.vulnerabilityProbability}`
+ )}>
+ <div className="text-bold">{hotspot.message}</div>
+ <ButtonLink className="nowrap big-spacer-left" onClick={props.onCommentClick}>
+ {translate('hotspots.comment.open')}
+ </ButtonLink>
+ </div>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainer.tsx
index 0bd19aa2657..9b8928dc4aa 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainer.tsx
@@ -32,6 +32,7 @@ interface Props {
branchLike?: BranchLike;
component: Component;
hotspot: Hotspot;
+ onCommentButtonClick: () => void;
}
interface State {
@@ -175,6 +176,7 @@ export default class HotspotSnippetContainer extends React.Component<Props, Stat
hotspot={hotspot}
loading={loading}
locations={locations}
+ onCommentButtonClick={this.props.onCommentButtonClick}
onExpandBlock={this.handleExpansion}
onSymbolClick={this.handleSymbolClick}
sourceLines={sourceLines}
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx
index 835ac640ce7..15c8ac2a848 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx
@@ -31,6 +31,7 @@ import {
SourceViewerFile
} from '../../../types/types';
import SnippetViewer from '../../issues/crossComponentSourceViewer/SnippetViewer';
+import HotspotPrimaryLocationBox from './HotspotPrimaryLocationBox';
export interface HotspotSnippetContainerRendererProps {
branchLike?: BranchLike;
@@ -39,6 +40,7 @@ export interface HotspotSnippetContainerRendererProps {
hotspot: Hotspot;
loading: boolean;
locations: { [line: number]: LinearIssueLocation[] };
+ onCommentButtonClick: () => void;
onExpandBlock: (direction: ExpandDirection) => Promise<void>;
onSymbolClick: (symbols: string[]) => void;
sourceLines: SourceLine[];
@@ -61,6 +63,13 @@ export default function HotspotSnippetContainerRenderer(
sourceViewerFile
} = props;
+ const renderHotspotBoxInLine = (lineNumber: number) =>
+ lineNumber === hotspot.line ? (
+ <HotspotPrimaryLocationBox hotspot={hotspot} onCommentClick={props.onCommentButtonClick} />
+ ) : (
+ undefined
+ );
+
return (
<>
{!loading && sourceLines.length === 0 && (
@@ -101,6 +110,7 @@ export default function HotspotSnippetContainerRenderer(
onIssuePopupToggle={noop}
onLocationSelect={noop}
openIssuesByLine={{}}
+ renderAdditionalChildInLine={renderHotspotBoxInLine}
renderDuplicationPopup={noop}
snippet={sourceLines}
/>
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx
index 8bbc129f9b7..c8256ead7a5 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx
@@ -169,6 +169,7 @@ export function HotspotViewerRenderer(props: HotspotViewerRendererProps) {
branchLike={fillBranchLike(hotspot.project.branch, hotspot.project.pullRequest)}
component={component}
hotspot={hotspot}
+ onCommentButtonClick={props.onShowCommentForm}
/>
<HotspotViewerTabs hotspot={hotspot} />
<HotspotReviewHistoryAndComments
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotPrimaryLocationBox-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotPrimaryLocationBox-test.tsx
new file mode 100644
index 00000000000..2c9cec0485c
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotPrimaryLocationBox-test.tsx
@@ -0,0 +1,57 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { ButtonLink } from '../../../../components/controls/buttons';
+import { mockHotspot, mockHotspotRule } from '../../../../helpers/mocks/security-hotspots';
+import { RiskExposure } from '../../../../types/security-hotspots';
+import HotspotPrimaryLocationBox, {
+ HotspotPrimaryLocationBoxProps
+} from '../HotspotPrimaryLocationBox';
+
+it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot();
+});
+
+it.each([[RiskExposure.HIGH], [RiskExposure.MEDIUM], [RiskExposure.LOW]])(
+ 'should indicate risk exposure: %s',
+ vulnerabilityProbability => {
+ const wrapper = shallowRender({
+ hotspot: mockHotspot({ rule: mockHotspotRule({ vulnerabilityProbability }) })
+ });
+
+ expect(wrapper.hasClass(`hotspot-risk-exposure-${vulnerabilityProbability}`)).toBe(true);
+ }
+);
+
+it('should handle click', () => {
+ const onCommentClick = jest.fn();
+ const wrapper = shallowRender({ onCommentClick });
+
+ wrapper.find(ButtonLink).simulate('click');
+
+ expect(onCommentClick).toBeCalled();
+});
+
+function shallowRender(props: Partial<HotspotPrimaryLocationBoxProps> = {}) {
+ return shallow(
+ <HotspotPrimaryLocationBox hotspot={mockHotspot()} onCommentClick={jest.fn()} {...props} />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainer-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainer-test.tsx
index d9037ba7136..acc09b37d7c 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainer-test.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainer-test.tsx
@@ -205,6 +205,7 @@ function shallowRender(props?: Partial<HotspotSnippetContainer['props']>) {
branchLike={branch}
component={mockComponent()}
hotspot={mockHotspot()}
+ onCommentButtonClick={jest.fn()}
{...props}
/>
);
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainerRenderer-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainerRenderer-test.tsx
index 2ca6b7e4f41..07407eff2d0 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainerRenderer-test.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainerRenderer-test.tsx
@@ -22,6 +22,7 @@ import * as React from 'react';
import { mockMainBranch } from '../../../../helpers/mocks/branch-like';
import { mockHotspot } from '../../../../helpers/mocks/security-hotspots';
import { mockSourceLine, mockSourceViewerFile } from '../../../../helpers/testMocks';
+import SnippetViewer from '../../../issues/crossComponentSourceViewer/SnippetViewer';
import HotspotSnippetContainerRenderer, {
HotspotSnippetContainerRendererProps
} from '../HotspotSnippetContainerRenderer';
@@ -31,6 +32,18 @@ it('should render correctly', () => {
expect(shallowRender({ sourceLines: [mockSourceLine()] })).toMatchSnapshot('with sourcelines');
});
+it('should render a HotspotPrimaryLocationBox', () => {
+ const wrapper = shallowRender({
+ hotspot: mockHotspot({ line: 42 }),
+ sourceLines: [mockSourceLine()]
+ });
+
+ const { renderAdditionalChildInLine } = wrapper.find(SnippetViewer).props();
+
+ expect(renderAdditionalChildInLine!(10)).toBeUndefined();
+ expect(renderAdditionalChildInLine!(42)).not.toBeUndefined();
+});
+
function shallowRender(props?: Partial<HotspotSnippetContainerRendererProps>) {
return shallow(
<HotspotSnippetContainerRenderer
@@ -40,6 +53,7 @@ function shallowRender(props?: Partial<HotspotSnippetContainerRendererProps>) {
hotspot={mockHotspot()}
loading={false}
locations={{}}
+ onCommentButtonClick={jest.fn()}
onExpandBlock={jest.fn()}
onSymbolClick={jest.fn()}
sourceLines={[]}
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotPrimaryLocationBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotPrimaryLocationBox-test.tsx.snap
new file mode 100644
index 00000000000..ed9097dff9f
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotPrimaryLocationBox-test.tsx.snap
@@ -0,0 +1,19 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ className="hotspot-primary-location display-flex-space-between display-flex-center padded-top padded-bottom big-padded-left big-padded-right hotspot-risk-exposure-HIGH"
+>
+ <div
+ className="text-bold"
+ >
+ '3' is a magic number.
+ </div>
+ <ButtonLink
+ className="nowrap big-spacer-left"
+ onClick={[MockFunction]}
+ >
+ hotspots.comment.open
+ </ButtonLink>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainer-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainer-test.tsx.snap
index 2dcb778d83e..622c59b6b2d 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainer-test.tsx.snap
@@ -95,6 +95,7 @@ exports[`should render correctly 1`] = `
],
}
}
+ onCommentButtonClick={[MockFunction]}
onExpandBlock={[Function]}
onSymbolClick={[Function]}
sourceLines={Array []}
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainerRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainerRenderer-test.tsx.snap
index 2807a804133..beda736b7f9 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainerRenderer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainerRenderer-test.tsx.snap
@@ -219,6 +219,7 @@ exports[`should render correctly: with sourcelines 1`] = `
onIssuePopupToggle={[Function]}
onLocationSelect={[Function]}
openIssuesByLine={Object {}}
+ renderAdditionalChildInLine={[Function]}
renderDuplicationPopup={[Function]}
snippet={
Array [
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerRenderer-test.tsx.snap
index ea3ef767b32..f24651eb644 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerRenderer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerRenderer-test.tsx.snap
@@ -371,6 +371,7 @@ exports[`should render correctly: anonymous user 1`] = `
],
}
}
+ onCommentButtonClick={[MockFunction]}
/>
<HotspotViewerTabs
hotspot={
@@ -904,6 +905,7 @@ exports[`should render correctly: assignee without name 1`] = `
],
}
}
+ onCommentButtonClick={[MockFunction]}
/>
<HotspotViewerTabs
hotspot={
@@ -1437,6 +1439,7 @@ exports[`should render correctly: default 1`] = `
],
}
}
+ onCommentButtonClick={[MockFunction]}
/>
<HotspotViewerTabs
hotspot={
@@ -1970,6 +1973,7 @@ exports[`should render correctly: deleted assignee 1`] = `
],
}
}
+ onCommentButtonClick={[MockFunction]}
/>
<HotspotViewerTabs
hotspot={
@@ -2516,6 +2520,7 @@ exports[`should render correctly: show success modal 1`] = `
],
}
}
+ onCommentButtonClick={[MockFunction]}
/>
<HotspotViewerTabs
hotspot={
@@ -3049,6 +3054,7 @@ exports[`should render correctly: unassigned 1`] = `
],
}
}
+ onCommentButtonClick={[MockFunction]}
/>
<HotspotViewerTabs
hotspot={
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx
index 9f42aee16d3..6a8cbac14eb 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx
@@ -31,6 +31,7 @@ import LineNumber from './LineNumber';
import LineSCM from './LineSCM';
interface Props {
+ additionalChild?: React.ReactNode;
branchLike: BranchLike | undefined;
displayAllIssues?: boolean;
displayCoverage: boolean;
@@ -90,6 +91,7 @@ export default class Line extends React.PureComponent<Props> {
render() {
const {
+ additionalChild,
branchLike,
displayAllIssues,
displayCoverage,
@@ -182,6 +184,7 @@ export default class Line extends React.PureComponent<Props> {
)}
<LineCode
+ additionalChild={additionalChild}
branchLike={branchLike}
displayIssueLocationsCount={displayIssueLocationsCount}
displayIssueLocationsLink={displayIssueLocationsLink}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx
index 6c00a0e02a9..8904fc41747 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx
@@ -32,6 +32,7 @@ import {
import LineIssuesList from './LineIssuesList';
interface Props {
+ additionalChild?: React.ReactNode;
branchLike: BranchLike | undefined;
displayIssueLocationsCount?: boolean;
displayIssueLocationsLink?: boolean;
@@ -154,6 +155,7 @@ export default class LineCode extends React.PureComponent<Props, State> {
render() {
const {
+ additionalChild,
highlightedLocationMessage,
highlightedSymbols,
issues,
@@ -253,6 +255,7 @@ export default class LineCode extends React.PureComponent<Props, State> {
selectedIssue={selectedIssue}
/>
)}
+ {additionalChild}
</td>
);
}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.tsx
index b4a66e3fdc8..ebe3cc56430 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.tsx
@@ -25,6 +25,9 @@ import LineCode from '../LineCode';
it('render code', () => {
expect(shallowRender()).toMatchSnapshot();
+ expect(shallowRender({ additionalChild: <div>additional child</div> })).toMatchSnapshot(
+ 'with additional child'
+ );
});
function shallowRender(props: Partial<LineCode['props']> = {}) {
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap
index 13141eafff8..77bebd4c983 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap
@@ -119,3 +119,126 @@ exports[`render code 1`] = `
/>
</td>
`;
+
+exports[`render code: with additional child 1`] = `
+<td
+ className="source-line-code code has-issues"
+ data-line-number={16}
+>
+ <div
+ className="source-line-code-inner"
+ >
+ <pre>
+ <span
+ className="k source-line-code-issue"
+ key="0"
+ >
+ impor
+ </span>
+ <span
+ className="k"
+ key="1"
+ >
+ t
+ </span>
+ <span
+ className=""
+ key="2"
+ >
+ java.util.
+ </span>
+ <span
+ className="sym-9 sym highlighted"
+ key="3"
+ >
+ ArrayList
+ </span>
+ <span
+ className=""
+ key="4"
+ >
+ ;
+ </span>
+ </pre>
+ </div>
+ <LineIssuesList
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": false,
+ "name": "branch-6.7",
+ }
+ }
+ issues={
+ Array [
+ Object {
+ "actions": Array [],
+ "component": "main.js",
+ "componentLongName": "main.js",
+ "componentQualifier": "FIL",
+ "componentUuid": "foo1234",
+ "creationDate": "2017-03-01T09:36:01+0100",
+ "flows": Array [],
+ "fromHotspot": false,
+ "key": "issue-1",
+ "line": 25,
+ "message": "Reduce the number of conditional operators (4) used in the expression",
+ "project": "myproject",
+ "projectKey": "foo",
+ "projectName": "Foo",
+ "rule": "javascript:S1067",
+ "ruleName": "foo",
+ "secondaryLocations": Array [],
+ "severity": "MAJOR",
+ "status": "OPEN",
+ "textRange": Object {
+ "endLine": 26,
+ "endOffset": 15,
+ "startLine": 25,
+ "startOffset": 0,
+ },
+ "transitions": Array [],
+ "type": "BUG",
+ },
+ Object {
+ "actions": Array [],
+ "component": "main.js",
+ "componentLongName": "main.js",
+ "componentQualifier": "FIL",
+ "componentUuid": "foo1234",
+ "creationDate": "2017-03-01T09:36:01+0100",
+ "flows": Array [],
+ "fromHotspot": false,
+ "key": "issue-2",
+ "line": 25,
+ "message": "Reduce the number of conditional operators (4) used in the expression",
+ "project": "myproject",
+ "projectKey": "foo",
+ "projectName": "Foo",
+ "rule": "javascript:S1067",
+ "ruleName": "foo",
+ "secondaryLocations": Array [],
+ "severity": "MAJOR",
+ "status": "OPEN",
+ "textRange": Object {
+ "endLine": 26,
+ "endOffset": 15,
+ "startLine": 25,
+ "startOffset": 0,
+ },
+ "transitions": Array [],
+ "type": "BUG",
+ },
+ ]
+ }
+ onIssueChange={[MockFunction]}
+ onIssueClick={[MockFunction]}
+ onIssuePopupToggle={[MockFunction]}
+ selectedIssue="issue-1"
+ />
+ <div>
+ additional child
+ </div>
+</td>
+`;