diff options
author | Philippe Perrin <philippe.perrin@sonarsource.com> | 2022-02-14 17:50:45 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-02-25 20:02:54 +0000 |
commit | 0635374eb836f7994bf759fa4473f750c7b5a63b (patch) | |
tree | bd6c19d1549a10fbfcaf0cb3f37e6472bb265bb5 /server/sonar-web | |
parent | e5474111c8f3e985cfa751d8cd86f1950aaf8e4d (diff) | |
download | sonarqube-0635374eb836f7994bf759fa4473f750c7b5a63b.tar.gz sonarqube-0635374eb836f7994bf759fa4473f750c7b5a63b.zip |
SONAR-16007 Display secondary locations within hotspot code snippet
Diffstat (limited to 'server/sonar-web')
14 files changed, 939 insertions, 189 deletions
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 9b8928dc4aa..6318465460f 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 @@ -24,8 +24,8 @@ import { getBranchLikeQuery } from '../../../helpers/branch-like'; import { BranchLike } from '../../../types/branch-like'; import { ComponentQualifier } from '../../../types/component'; import { Hotspot } from '../../../types/security-hotspots'; -import { Component, ExpandDirection, SourceLine } from '../../../types/types'; -import { constructSourceViewerFile } from '../utils'; +import { Component, ExpandDirection, FlowLocation, SourceLine } from '../../../types/types'; +import { constructSourceViewerFile, getLocations } from '../utils'; import HotspotSnippetContainerRenderer from './HotspotSnippetContainerRenderer'; interface Props { @@ -40,6 +40,7 @@ interface State { lastLine?: number; loading: boolean; sourceLines: SourceLine[]; + secondaryLocations: FlowLocation[]; } const BUFFER_LINES = 5; @@ -50,16 +51,19 @@ export default class HotspotSnippetContainer extends React.Component<Props, Stat state: State = { highlightedSymbols: [], loading: true, - sourceLines: [] + sourceLines: [], + secondaryLocations: [] }; - componentWillMount() { + async componentWillMount() { this.mounted = true; + await this.initializeSecondaryLocations(); this.fetchSources(); } - componentDidUpdate(prevProps: Props) { + async componentDidUpdate(prevProps: Props) { if (prevProps.hotspot.key !== this.props.hotspot.key) { + await this.initializeSecondaryLocations(); this.fetchSources(); } } @@ -85,15 +89,30 @@ export default class HotspotSnippetContainer extends React.Component<Props, Stat hotspot: { component, textRange } } = this.props; + const { secondaryLocations } = this.state; + if (!textRange) { // Hotspot not associated to any loc this.setState({ loading: false, lastLine: undefined, sourceLines: [] }); return; } - const from = Math.max(1, textRange.startLine - BUFFER_LINES); - // Add 1 to check for end-of-file: - const to = textRange.endLine + BUFFER_LINES + 1; + // Search for the min startLine within primary and secondary locations + const from = Math.max( + 1, + Math.min( + ...[textRange, ...secondaryLocations.map(l => l.textRange)].map( + t => t.startLine - BUFFER_LINES + ) + ) + ); + // Search for the max endLine within primary and secondary locations + const to = Math.max( + ...[textRange, ...secondaryLocations.map(l => l.textRange)].map( + // Add 1 to check for end-of-file + t => t.endLine + BUFFER_LINES + 1 + ) + ); this.setState({ loading: true }); @@ -113,6 +132,23 @@ export default class HotspotSnippetContainer extends React.Component<Props, Stat } } + initializeSecondaryLocations() { + const { hotspot } = this.props; + + return new Promise(resolve => { + this.setState( + { + secondaryLocations: getLocations(hotspot.flows, undefined).map((location, index) => ({ + ...location, + index, + text: location.msg + })) + }, + () => resolve(undefined) + ); + }); + } + handleExpansion = (direction: ExpandDirection) => { const { branchLike, hotspot } = this.props; const { sourceLines } = this.state; @@ -162,7 +198,7 @@ export default class HotspotSnippetContainer extends React.Component<Props, Stat render() { const { branchLike, component, hotspot } = this.props; - const { highlightedSymbols, lastLine, loading, sourceLines } = this.state; + const { highlightedSymbols, lastLine, loading, sourceLines, secondaryLocations } = this.state; const locations = locationsByLine([hotspot]); @@ -181,6 +217,7 @@ export default class HotspotSnippetContainer extends React.Component<Props, Stat onSymbolClick={this.handleSymbolClick} sourceLines={sourceLines} sourceViewerFile={sourceViewerFile} + secondaryLocations={secondaryLocations} /> ); } 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 15c8ac2a848..dfe5c696eb4 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 @@ -18,7 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { SourceViewerContext } from '../../../components/SourceViewer/SourceViewerContext'; import SourceViewerHeaderSlim from '../../../components/SourceViewer/SourceViewerHeaderSlim'; import DeferredSpinner from '../../../components/ui/DeferredSpinner'; import { translate } from '../../../helpers/l10n'; @@ -26,6 +25,7 @@ import { BranchLike } from '../../../types/branch-like'; import { Hotspot } from '../../../types/security-hotspots'; import { ExpandDirection, + FlowLocation, LinearIssueLocation, SourceLine, SourceViewerFile @@ -45,6 +45,7 @@ export interface HotspotSnippetContainerRendererProps { onSymbolClick: (symbols: string[]) => void; sourceLines: SourceLine[]; sourceViewerFile: SourceViewerFile; + secondaryLocations: FlowLocation[]; } const noop = () => undefined; @@ -58,9 +59,10 @@ export default function HotspotSnippetContainerRenderer( highlightedSymbols, hotspot, loading, - locations, + locations: primaryLocations, sourceLines, - sourceViewerFile + sourceViewerFile, + secondaryLocations } = props; const renderHotspotBoxInLine = (lineNumber: number) => @@ -87,34 +89,31 @@ export default function HotspotSnippetContainerRenderer( /> <DeferredSpinner className="big-spacer" loading={loading}> {sourceLines.length > 0 && ( - <SourceViewerContext.Provider /* Used by LineOptionsPopup */ - value={{ branchLike, file: sourceViewerFile }}> - <SnippetViewer - branchLike={undefined} - component={sourceViewerFile} - displayLineNumberOptions={false} - displaySCM={false} - expandBlock={(_i, direction) => props.onExpandBlock(direction)} - handleCloseIssues={noop} - handleOpenIssues={noop} - handleSymbolClick={props.onSymbolClick} - highlightedLocationMessage={undefined} - highlightedSymbols={highlightedSymbols} - index={0} - issue={hotspot} - issuesByLine={{}} - lastSnippetOfLastGroup={false} - locations={[]} - locationsByLine={locations} - onIssueChange={noop} - onIssuePopupToggle={noop} - onLocationSelect={noop} - openIssuesByLine={{}} - renderAdditionalChildInLine={renderHotspotBoxInLine} - renderDuplicationPopup={noop} - snippet={sourceLines} - /> - </SourceViewerContext.Provider> + <SnippetViewer + branchLike={undefined} + component={sourceViewerFile} + displayLineNumberOptions={false} + displaySCM={false} + expandBlock={(_i, direction) => props.onExpandBlock(direction)} + handleCloseIssues={noop} + handleOpenIssues={noop} + handleSymbolClick={props.onSymbolClick} + highlightedLocationMessage={undefined} + highlightedSymbols={highlightedSymbols} + index={0} + issue={hotspot} + issuesByLine={{}} + lastSnippetOfLastGroup={false} + locations={secondaryLocations} + locationsByLine={primaryLocations} + onIssueChange={noop} + onIssuePopupToggle={noop} + onLocationSelect={noop} + openIssuesByLine={{}} + renderAdditionalChildInLine={renderHotspotBoxInLine} + renderDuplicationPopup={noop} + snippet={sourceLines} + /> )} </DeferredSpinner> </div> 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 acc09b37d7c..98edc5eddf2 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 @@ -24,7 +24,7 @@ import { getSources } from '../../../../api/components'; import { mockBranch } from '../../../../helpers/mocks/branch-like'; import { mockComponent } from '../../../../helpers/mocks/component'; import { mockHotspot, mockHotspotComponent } from '../../../../helpers/mocks/security-hotspots'; -import { mockSourceLine } from '../../../../helpers/testMocks'; +import { mockFlowLocation, mockSourceLine } from '../../../../helpers/testMocks'; import { waitAndUpdate } from '../../../../helpers/testUtils'; import { ComponentQualifier } from '../../../../types/component'; import HotspotSnippetContainer from '../HotspotSnippetContainer'; @@ -44,12 +44,24 @@ it('should render correctly', () => { it('should load sources on mount', async () => { (getSources as jest.Mock).mockResolvedValueOnce( - range(5, 18).map(line => mockSourceLine({ line })) + range(1, 20).map(line => mockSourceLine({ line })) ); const hotspot = mockHotspot({ project: mockHotspotComponent({ branch: branch.name, qualifier: ComponentQualifier.Project }), - textRange: { startLine: 10, endLine: 11, startOffset: 0, endOffset: 12 } + textRange: { startLine: 10, endLine: 11, startOffset: 0, endOffset: 12 }, + flows: [ + { + locations: [ + mockFlowLocation({ + textRange: { startLine: 8, endLine: 8, startOffset: 0, endOffset: 1 } + }), + mockFlowLocation({ + textRange: { startLine: 13, endLine: 13, startOffset: 0, endOffset: 1 } + }) + ] + } + ] }); const wrapper = shallowRender({ hotspot }); @@ -58,11 +70,14 @@ it('should load sources on mount', async () => { expect(getSources).toBeCalledWith( expect.objectContaining({ - branch: branch.name + key: hotspot.component.key, + branch: branch.name, + from: 3, + to: 19 }) ); expect(wrapper.state().lastLine).toBeUndefined(); - expect(wrapper.state().sourceLines).toHaveLength(12); + expect(wrapper.state().sourceLines).toHaveLength(18); }); it('should handle load sources failure', async () => { 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 07407eff2d0..7f1151397b0 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 @@ -56,6 +56,7 @@ function shallowRender(props?: Partial<HotspotSnippetContainerRendererProps>) { onCommentButtonClick={jest.fn()} onExpandBlock={jest.fn()} onSymbolClick={jest.fn()} + secondaryLocations={[]} sourceLines={[]} sourceViewerFile={mockSourceViewerFile()} {...props} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistoryAndComments-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistoryAndComments-test.tsx.snap index ee97c8ccccc..89e707613bc 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistoryAndComments-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistoryAndComments-test.tsx.snap @@ -68,6 +68,21 @@ exports[`should render correctly 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -157,6 +172,21 @@ exports[`should render correctly without user 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", 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 622c59b6b2d..b2254d3d659 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 @@ -39,6 +39,21 @@ exports[`should render correctly 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -98,6 +113,21 @@ exports[`should render correctly 1`] = ` onCommentButtonClick={[MockFunction]} onExpandBlock={[Function]} onSymbolClick={[Function]} + secondaryLocations={ + Array [ + Object { + "component": "main.js", + "index": 0, + "text": undefined, + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ] + } sourceLines={Array []} sourceViewerFile={ Object { 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 beda736b7f9..615601d690d 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 @@ -89,155 +89,143 @@ exports[`should render correctly: with sourcelines 1`] = ` className="big-spacer" loading={false} > - <ContextProvider - value={ + <SnippetViewer + component={ Object { - "branchLike": Object { - "analysisDate": "2018-01-01", - "excludedFromPurge": true, - "isMain": true, - "name": "master", - }, - "file": Object { - "key": "foo", - "measures": Object { - "coverage": "85.2", - "duplicationDensity": "1.0", - "issues": "12", - "lines": "56", - }, - "path": "foo/bar.ts", - "project": "my-project", - "projectName": "MyProject", - "q": "FIL", - "uuid": "foo-bar", + "key": "foo", + "measures": Object { + "coverage": "85.2", + "duplicationDensity": "1.0", + "issues": "12", + "lines": "56", }, + "path": "foo/bar.ts", + "project": "my-project", + "projectName": "MyProject", + "q": "FIL", + "uuid": "foo-bar", } } - > - <SnippetViewer - component={ - Object { - "key": "foo", - "measures": Object { - "coverage": "85.2", - "duplicationDensity": "1.0", - "issues": "12", - "lines": "56", + displayLineNumberOptions={false} + displaySCM={false} + expandBlock={[Function]} + handleCloseIssues={[Function]} + handleOpenIssues={[Function]} + handleSymbolClick={[MockFunction]} + highlightedSymbols={Array []} + index={0} + issue={ + Object { + "assignee": "assignee", + "assigneeUser": Object { + "active": true, + "local": true, + "login": "assignee", + "name": "John Doe", + }, + "author": "author", + "authorUser": Object { + "active": true, + "local": true, + "login": "author", + "name": "John Doe", + }, + "canChangeStatus": true, + "changelog": Array [], + "comment": Array [], + "component": Object { + "key": "hotspot-component", + "longName": "Hotspot component long name", + "name": "Hotspot Component", + "path": "path/to/component", + "qualifier": "FIL", + }, + "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], }, - "path": "foo/bar.ts", - "project": "my-project", - "projectName": "MyProject", - "q": "FIL", - "uuid": "foo-bar", - } - } - displayLineNumberOptions={false} - displaySCM={false} - expandBlock={[Function]} - handleCloseIssues={[Function]} - handleOpenIssues={[Function]} - handleSymbolClick={[MockFunction]} - highlightedSymbols={Array []} - index={0} - issue={ - Object { - "assignee": "assignee", - "assigneeUser": Object { + ], + "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", + "line": 142, + "message": "'3' is a magic number.", + "project": Object { + "key": "hotspot-component", + "longName": "Hotspot component long name", + "name": "Hotspot Component", + "path": "path/to/component", + "qualifier": "TRK", + }, + "resolution": "FIXED", + "rule": Object { + "fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>", + "key": "squid:S2077", + "name": "That rule", + "riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>", + "securityCategory": "sql-injection", + "vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>", + "vulnerabilityProbability": "HIGH", + }, + "status": "REVIEWED", + "textRange": Object { + "endLine": 142, + "endOffset": 83, + "startLine": 142, + "startOffset": 26, + }, + "updateDate": "2013-05-13T17:55:42+0200", + "users": Array [ + Object { "active": true, "local": true, "login": "assignee", "name": "John Doe", }, - "author": "author", - "authorUser": Object { + Object { "active": true, "local": true, "login": "author", "name": "John Doe", }, - "canChangeStatus": true, - "changelog": Array [], - "comment": Array [], - "component": Object { - "key": "hotspot-component", - "longName": "Hotspot component long name", - "name": "Hotspot Component", - "path": "path/to/component", - "qualifier": "FIL", - }, - "creationDate": "2013-05-13T17:55:41+0200", - "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", - "line": 142, - "message": "'3' is a magic number.", - "project": Object { - "key": "hotspot-component", - "longName": "Hotspot component long name", - "name": "Hotspot Component", - "path": "path/to/component", - "qualifier": "TRK", - }, - "resolution": "FIXED", - "rule": Object { - "fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>", - "key": "squid:S2077", - "name": "That rule", - "riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>", - "securityCategory": "sql-injection", - "vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>", - "vulnerabilityProbability": "HIGH", - }, - "status": "REVIEWED", - "textRange": Object { - "endLine": 142, - "endOffset": 83, - "startLine": 142, - "startOffset": 26, - }, - "updateDate": "2013-05-13T17:55:42+0200", - "users": Array [ - Object { - "active": true, - "local": true, - "login": "assignee", - "name": "John Doe", - }, - Object { - "active": true, - "local": true, - "login": "author", - "name": "John Doe", - }, - ], - } + ], } - issuesByLine={Object {}} - lastSnippetOfLastGroup={false} - locations={Array []} - locationsByLine={Object {}} - onIssueChange={[Function]} - onIssuePopupToggle={[Function]} - onLocationSelect={[Function]} - openIssuesByLine={Object {}} - renderAdditionalChildInLine={[Function]} - renderDuplicationPopup={[Function]} - snippet={ - Array [ - Object { - "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;", - "coverageStatus": "covered", - "coveredConditions": 2, - "duplicated": false, - "isNew": true, - "line": 16, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - }, - ] - } - /> - </ContextProvider> + } + issuesByLine={Object {}} + lastSnippetOfLastGroup={false} + locations={Array []} + locationsByLine={Object {}} + onIssueChange={[Function]} + onIssuePopupToggle={[Function]} + onLocationSelect={[Function]} + openIssuesByLine={Object {}} + renderAdditionalChildInLine={[Function]} + renderDuplicationPopup={[Function]} + snippet={ + Array [ + Object { + "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;", + "coverageStatus": "covered", + "coveredConditions": 2, + "duplicated": false, + "isNew": true, + "line": 16, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + }, + ] + } + /> </DeferredSpinner> </div> </Fragment> 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 f24651eb644..780f88703c2 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 @@ -149,6 +149,21 @@ exports[`should render correctly: anonymous user 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -229,6 +244,21 @@ exports[`should render correctly: anonymous user 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -327,6 +357,21 @@ exports[`should render correctly: anonymous user 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -401,6 +446,21 @@ exports[`should render correctly: anonymous user 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -484,6 +544,21 @@ exports[`should render correctly: anonymous user 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -683,6 +758,21 @@ exports[`should render correctly: assignee without name 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -763,6 +853,21 @@ exports[`should render correctly: assignee without name 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -861,6 +966,21 @@ exports[`should render correctly: assignee without name 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -935,6 +1055,21 @@ exports[`should render correctly: assignee without name 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -1018,6 +1153,21 @@ exports[`should render correctly: assignee without name 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -1217,6 +1367,21 @@ exports[`should render correctly: default 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -1297,6 +1462,21 @@ exports[`should render correctly: default 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -1395,6 +1575,21 @@ exports[`should render correctly: default 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -1469,6 +1664,21 @@ exports[`should render correctly: default 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -1552,6 +1762,21 @@ exports[`should render correctly: default 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -1751,6 +1976,21 @@ exports[`should render correctly: deleted assignee 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -1831,6 +2071,21 @@ exports[`should render correctly: deleted assignee 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -1929,6 +2184,21 @@ exports[`should render correctly: deleted assignee 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -2003,6 +2273,21 @@ exports[`should render correctly: deleted assignee 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -2086,6 +2371,21 @@ exports[`should render correctly: deleted assignee 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -2298,6 +2598,21 @@ exports[`should render correctly: show success modal 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -2378,6 +2693,21 @@ exports[`should render correctly: show success modal 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -2476,6 +2806,21 @@ exports[`should render correctly: show success modal 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -2550,6 +2895,21 @@ exports[`should render correctly: show success modal 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -2633,6 +2993,21 @@ exports[`should render correctly: show success modal 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -2832,6 +3207,21 @@ exports[`should render correctly: unassigned 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -2912,6 +3302,21 @@ exports[`should render correctly: unassigned 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -3010,6 +3415,21 @@ exports[`should render correctly: unassigned 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -3084,6 +3504,21 @@ exports[`should render correctly: unassigned 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -3167,6 +3602,21 @@ exports[`should render correctly: unassigned 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/status/__tests__/__snapshots__/Status-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/status/__tests__/__snapshots__/Status-test.tsx.snap index 610a011c7cd..3dc6f22b2ce 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/status/__tests__/__snapshots__/Status-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/status/__tests__/__snapshots__/Status-test.tsx.snap @@ -54,6 +54,21 @@ exports[`should render correctly: closed 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -177,6 +192,21 @@ exports[`should render correctly: open 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", @@ -300,6 +330,21 @@ exports[`should render correctly: readonly 1`] = ` "qualifier": "FIL", }, "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", "line": 142, "message": "'3' is a magic number.", 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 8904fc41747..8a65af22ba2 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 @@ -22,7 +22,7 @@ import * as React from 'react'; import { BranchLike } from '../../../types/branch-like'; import { Issue, LinearIssueLocation, SourceLine } from '../../../types/types'; import LocationIndex from '../../common/LocationIndex'; -import LocationMessage from '../../common/LocationMessage'; +import Tooltip from '../../controls/Tooltip'; import { highlightIssueLocations, highlightSymbol, @@ -141,15 +141,18 @@ export default class LineCode extends React.PureComponent<Props, State> { const { onLocationSelect } = this.props; const onClick = onLocationSelect ? () => onLocationSelect(index) : undefined; const ref = selected ? (node: HTMLElement | null) => (this.activeMarkerNode = node) : undefined; + return ( - <LocationIndex - key={`marker-${index}`} - leading={leading} - onClick={onClick} - selected={selected}> - <span ref={ref}>{index + 1}</span> - {message && <LocationMessage selected={true}>{message}</LocationMessage>} - </LocationIndex> + <Tooltip overlay={message} placement="top"> + <LocationIndex + key={`marker-${index}`} + leading={leading} + onClick={onClick} + selected={selected} + aria-label={message ? `${index + 1}-${message}` : index + 1}> + <span ref={ref}>{index + 1}</span> + </LocationIndex> + </Tooltip> ); } 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 ebe3cc56430..92ee72265e2 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 @@ -28,6 +28,13 @@ it('render code', () => { expect(shallowRender({ additionalChild: <div>additional child</div> })).toMatchSnapshot( 'with additional child' ); + expect( + shallowRender({ + secondaryIssueLocations: [ + { index: 1, from: 5, to: 6, line: 16, startLine: 16, text: 'secondary-location-msg' } + ] + }) + ).toMatchSnapshot('with secondary location'); }); 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 77bebd4c983..37718ed25a8 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 @@ -242,3 +242,139 @@ exports[`render code: with additional child 1`] = ` </div> </td> `; + +exports[`render code: with secondary location 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> + <Tooltip + overlay="secondary-location-msg" + placement="top" + > + <LocationIndex + aria-label="2-secondary-location-msg" + key="marker-1" + leading={false} + onClick={[Function]} + selected={false} + > + <span> + 2 + </span> + </LocationIndex> + </Tooltip> + <span + className="k issue-location" + 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" + /> +</td> +`; diff --git a/server/sonar-web/src/main/js/helpers/mocks/security-hotspots.ts b/server/sonar-web/src/main/js/helpers/mocks/security-hotspots.ts index 391dd38edec..d07d65d739b 100644 --- a/server/sonar-web/src/main/js/helpers/mocks/security-hotspots.ts +++ b/server/sonar-web/src/main/js/helpers/mocks/security-hotspots.ts @@ -31,7 +31,7 @@ import { ReviewHistoryType, RiskExposure } from '../../types/security-hotspots'; -import { mockUser } from '../testMocks'; +import { mockFlowLocation, mockUser } from '../testMocks'; export function mockRawHotspot(overrides: Partial<RawHotspot> = {}): RawHotspot { return { @@ -65,6 +65,7 @@ export function mockHotspot(overrides?: Partial<Hotspot>): Hotspot { comment: [], component: mockHotspotComponent({ qualifier: ComponentQualifier.File }), creationDate: '2013-05-13T17:55:41+0200', + flows: [{ locations: [mockFlowLocation()] }], key: '01fc972e-2a3c-433e-bcae-0bd7f88f5123', line: 142, message: "'3' is a magic number.", diff --git a/server/sonar-web/src/main/js/types/security-hotspots.ts b/server/sonar-web/src/main/js/types/security-hotspots.ts index ecd85abcda1..8d0a15fcde0 100644 --- a/server/sonar-web/src/main/js/types/security-hotspots.ts +++ b/server/sonar-web/src/main/js/types/security-hotspots.ts @@ -18,7 +18,14 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { ComponentQualifier } from './component'; -import { IssueChangelog, IssueChangelogDiff, Paging, TextRange, UserBase } from './types'; +import { + FlowLocation, + IssueChangelog, + IssueChangelogDiff, + Paging, + TextRange, + UserBase +} from './types'; export enum RiskExposure { LOW = 'LOW', @@ -85,6 +92,7 @@ export interface Hotspot { comment: HotspotComment[]; component: HotspotComponent; creationDate: string; + flows: { locations: FlowLocation[] }[]; key: string; line?: number; message: string; |