From a3e8edb751045393fa65d530439721d150f7630c Mon Sep 17 00:00:00 2001 From: Mathieu Suen Date: Wed, 25 May 2022 11:22:26 +0200 Subject: [PATCH] SONAR-16383 Change secondary issues in file to allow focus on the current issue --- .../main/js/api/mocks/IssuesServiceMock.ts | 24 ++++ .../js/apps/issues/__tests__/IssueApp-it.tsx | 9 ++ .../js/apps/issues/components/IssuesApp.tsx | 1 + .../issues/components/IssuesSourceViewer.tsx | 2 + .../__tests__/IssuesSourceViewer-test.tsx | 1 + .../IssuesSourceViewer-test.tsx.snap | 4 + .../ComponentSourceSnippetGroupViewer.tsx | 85 ++++++----- .../CrossComponentSourceViewerWrapper.tsx | 3 + .../SnippetViewer.tsx | 38 ++--- ...ComponentSourceSnippetGroupViewer-test.tsx | 26 +--- ...CrossComponentSourceViewerWrapper-test.tsx | 1 + .../__tests__/SnippetViewer-test.tsx | 9 -- ...nentSourceSnippetGroupViewer-test.tsx.snap | 54 ------- ...ComponentSourceViewerWrapper-test.tsx.snap | 3 + .../__snapshots__/SnippetViewer-test.tsx.snap | 136 +++++++++--------- .../HotspotSnippetContainerRenderer.tsx | 4 - ...spotSnippetContainerRenderer-test.tsx.snap | 4 - .../js/components/issue/SecondaryIssue.tsx | 48 +++++++ 18 files changed, 220 insertions(+), 232 deletions(-) create mode 100644 server/sonar-web/src/main/js/components/issue/SecondaryIssue.tsx diff --git a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts index e961edf5b66..65128a0e82e 100644 --- a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts @@ -158,6 +158,30 @@ export default class IssuesServiceMock { ], 'component.key' ) + }, + { + issue: mockRawIssue(false, { + key: 'issue3', + component: 'project:file.bar', + message: 'Second issue', + rule: 'other', + textRange: { + startLine: 28, + endLine: 28, + startOffset: 0, + endOffset: 1 + } + }), + snippets: keyBy( + [ + mockSnippetsByComponent( + 'file.bar', + 'project', + times(40, i => i + 20) + ) + ], + 'component.key' + ) } ]; (searchIssues as jest.Mock).mockImplementation(this.handleSearchIssues); diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx index 8153bf39579..65066ba0845 100644 --- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx @@ -79,6 +79,15 @@ it('should open issue and navigate', async () => { ).toBeInTheDocument(); }); +it('should be able to navigate to other issue located in the same file', async () => { + const user = userEvent.setup(); + renderIssueApp(); + await user.click(await screen.findByRole('region', { name: 'Fix that' })); + expect(await screen.findByRole('region', { name: 'Second issue' })).toBeInTheDocument(); + await user.click(await screen.findByRole('region', { name: 'Second issue' })); + expect(screen.getByRole('heading', { level: 1, name: 'Second issue' })).toBeInTheDocument(); +}); + it('should support OWASP Top 10 version 2021', async () => { const user = userEvent.setup(); renderIssueApp(); diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx index 34864825c2e..8d148446839 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx @@ -1086,6 +1086,7 @@ export default class App extends React.PureComponent { issues={issues} locationsNavigator={this.state.locationsNavigator} onIssueChange={this.handleIssueChange} + onIssueSelect={this.openIssue} onLocationSelect={this.selectLocation} openIssue={openIssue} selectedFlowIndex={this.state.selectedFlowIndex} diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.tsx index 649f1693ab0..a91db3a73f0 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.tsx @@ -29,6 +29,7 @@ interface Props { issues: Issue[]; locationsNavigator: boolean; onIssueChange: (issue: Issue) => void; + onIssueSelect: (issueKey: string) => void; onLocationSelect: (index: number) => void; openIssue: Issue; selectedFlowIndex: number | undefined; @@ -96,6 +97,7 @@ export default class IssuesSourceViewer extends React.PureComponent { issues={this.props.issues} locations={locations} onIssueChange={this.props.onIssueChange} + onIssueSelect={this.props.onIssueSelect} onLoaded={this.handleLoaded} onLocationSelect={this.props.onLocationSelect} scroll={this.handleScroll} diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesSourceViewer-test.tsx b/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesSourceViewer-test.tsx index e1e2a957372..3f7941cf591 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesSourceViewer-test.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesSourceViewer-test.tsx @@ -71,6 +71,7 @@ function shallowRender(props: Partial = {}) { issues={[mockIssue()]} locationsNavigator={true} onIssueChange={jest.fn()} + onIssueSelect={jest.fn()} onLocationSelect={jest.fn()} openIssue={mockIssue()} selectedFlowIndex={undefined} diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesSourceViewer-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesSourceViewer-test.tsx.snap index b5cd58eeb0b..395562973cf 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesSourceViewer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesSourceViewer-test.tsx.snap @@ -211,6 +211,7 @@ exports[`should render CrossComponentSourceViewer correctly 1`] = ` ] } onIssueChange={[MockFunction]} + onIssueSelect={[MockFunction]} onLoaded={[Function]} onLocationSelect={[MockFunction]} scroll={[Function]} @@ -449,6 +450,7 @@ exports[`should render SourceViewer correctly: all secondary locations on same l ] } onIssueChange={[MockFunction]} + onIssueSelect={[MockFunction]} onLoaded={[Function]} onLocationSelect={[MockFunction]} scroll={[Function]} @@ -533,6 +535,7 @@ exports[`should render SourceViewer correctly: default 1`] = ` } locations={Array []} onIssueChange={[MockFunction]} + onIssueSelect={[MockFunction]} onLoaded={[Function]} onLocationSelect={[MockFunction]} scroll={[Function]} @@ -731,6 +734,7 @@ exports[`should render SourceViewer correctly: single secondary location 1`] = ` ] } onIssueChange={[MockFunction]} + onIssueSelect={[MockFunction]} onLoaded={[Function]} onLocationSelect={[MockFunction]} scroll={[Function]} diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx index d54d1f0ad95..8aaa4277267 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx @@ -21,7 +21,7 @@ import { noop } from 'lodash'; import * as React from 'react'; import { getSources } from '../../../api/components'; import Issue from '../../../components/issue/Issue'; -import LineIssuesList from '../../../components/SourceViewer/components/LineIssuesList'; +import SecondaryIssue from '../../../components/issue/SecondaryIssue'; import getCoverageStatus from '../../../components/SourceViewer/helpers/getCoverageStatus'; import { locationsByLine } from '../../../components/SourceViewer/helpers/indexing'; import SourceViewerHeaderSlim from '../../../components/SourceViewer/SourceViewerHeaderSlim'; @@ -62,6 +62,7 @@ interface Props { loadDuplications: (component: string, line: SourceLine) => void; locations: FlowLocation[]; onIssueChange: (issue: TypeIssue) => void; + onIssueSelect: (issueKey: string) => void; onIssuePopupToggle: (issue: string, popupName: string, open?: boolean) => void; onLocationSelect: (index: number) => void; renderDuplicationPopup: ( @@ -77,7 +78,6 @@ interface State { additionalLines: { [line: number]: SourceLine }; highlightedSymbols: string[]; loading: boolean; - openIssuesByLine: Dict; snippets: Snippet[]; } @@ -88,7 +88,6 @@ export default class ComponentSourceSnippetGroupViewer extends React.PureCompone additionalLines: {}, highlightedSymbols: [], loading: false, - openIssuesByLine: {}, snippets: [] }; @@ -287,18 +286,6 @@ export default class ComponentSourceSnippetGroupViewer extends React.PureCompone ); }; - handleOpenIssues = (line: SourceLine) => { - this.setState(state => ({ - openIssuesByLine: { ...state.openIssuesByLine, [line.line]: true } - })); - }; - - handleCloseIssues = (line: SourceLine) => { - this.setState(state => ({ - openIssuesByLine: { ...state.openIssuesByLine, [line.line]: false } - })); - }; - handleSymbolClick = (clickedSymbols: string[]) => { this.setState(({ highlightedSymbols }) => { const newHighlightedSymbols = clickedSymbols.filter( @@ -317,9 +304,13 @@ export default class ComponentSourceSnippetGroupViewer extends React.PureCompone }; renderIssuesList = (line: SourceLine) => { - const { openIssuesByLine } = this.state; - - const { isLastOccurenceOfPrimaryComponent, issue, issuesByLine, snippetGroup } = this.props; + const { + isLastOccurenceOfPrimaryComponent, + issue, + issuesByLine, + snippetGroup, + branchLike + } = this.props; const locations = issue.component === snippetGroup.component.key && issue.textRange !== undefined ? locationsByLine([issue]) @@ -327,38 +318,52 @@ export default class ComponentSourceSnippetGroupViewer extends React.PureCompone const isFlow = issue.secondaryLocations.length === 0; const includeIssueLocation = isFlow ? isLastOccurenceOfPrimaryComponent : true; - const issuesForLine = issuesByLine[line.line] || []; + const issueLocations = includeIssueLocation ? locations[line.line] : []; - const selectedIssue = issuesForLine.find(i => i.key === issue.key)?.key; - - const issueLocationsByLine = includeIssueLocation ? locations : {}; return ( - + issuesForLine.length > 0 && ( +
+ {issuesForLine.map(issueToDisplay => { + if (issueToDisplay.key === issue.key && issueLocations && issueLocations.length) { + return ( + + ); + } + return ( + + ); + })} +
+ ) ); }; renderSnippet({ index, - issuesByLine, lastSnippetOfLastGroup, locationsByLine, snippet }: { index: number; - issuesByLine: IssuesByLine; lastSnippetOfLastGroup: boolean; locationsByLine: { [line: number]: LinearIssueLocation[] }; snippet: SourceLine[]; @@ -370,20 +375,16 @@ export default class ComponentSourceSnippetGroupViewer extends React.PureCompone duplications={this.props.duplications} duplicationsByLine={this.props.duplicationsByLine} expandBlock={this.expandBlock} - handleCloseIssues={this.handleCloseIssues} - handleOpenIssues={this.handleOpenIssues} handleSymbolClick={this.handleSymbolClick} highlightedLocationMessage={this.props.highlightedLocationMessage} highlightedSymbols={this.state.highlightedSymbols} index={index} issue={this.props.issue} - issuesByLine={issuesByLine} lastSnippetOfLastGroup={lastSnippetOfLastGroup} loadDuplications={this.loadDuplications} locations={this.props.locations} locationsByLine={locationsByLine} onLocationSelect={this.props.onLocationSelect} - openIssuesByLine={this.state.openIssuesByLine} renderDuplicationPopup={this.renderDuplicationPopup} scroll={this.props.scroll} snippet={snippet} @@ -396,7 +397,6 @@ export default class ComponentSourceSnippetGroupViewer extends React.PureCompone branchLike, isLastOccurenceOfPrimaryComponent, issue, - issuesByLine, issuePopup, lastSnippetGroup, snippetGroup @@ -447,7 +447,6 @@ export default class ComponentSourceSnippetGroupViewer extends React.PureCompone {this.renderSnippet({ snippet, index: snippets[index].index, - issuesByLine, locationsByLine: includeIssueLocation ? locations : {}, lastSnippetOfLastGroup: lastSnippetGroup && index === snippets.length - 1 })} diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewerWrapper.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewerWrapper.tsx index 2d20f25c5ac..11dc8ddab0d 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewerWrapper.tsx +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewerWrapper.tsx @@ -60,6 +60,7 @@ interface Props { issues: Issue[]; locations: FlowLocation[]; onIssueChange: (issue: Issue) => void; + onIssueSelect: (issueKey: string) => void; onLoaded?: () => void; onLocationSelect: (index: number) => void; scroll?: (element: HTMLElement) => void; @@ -253,6 +254,7 @@ export default class CrossComponentSourceViewerWrapper extends React.PureCompone loadDuplications={this.fetchDuplications} locations={snippetGroup.locations || []} onIssueChange={this.props.onIssueChange} + onIssueSelect={this.props.onIssueSelect} onIssuePopupToggle={this.handleIssuePopupToggle} onLocationSelect={this.props.onLocationSelect} renderDuplicationPopup={this.renderDuplicationPopup} @@ -277,6 +279,7 @@ export default class CrossComponentSourceViewerWrapper extends React.PureCompone loadDuplications={this.fetchDuplications} locations={[]} onIssueChange={this.props.onIssueChange} + onIssueSelect={this.props.onIssueSelect} onIssuePopupToggle={this.handleIssuePopupToggle} onLocationSelect={this.props.onLocationSelect} renderDuplicationPopup={this.renderDuplicationPopup} 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 07a0685f534..9981db89700 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 @@ -30,18 +30,16 @@ import { import { translate } from '../../../helpers/l10n'; import { scrollHorizontally } from '../../../helpers/scrolling'; import { - Dict, Duplication, ExpandDirection, FlowLocation, Issue, - IssuesByLine, LinearIssueLocation, SourceLine, SourceViewerFile } from '../../../types/types'; import './SnippetViewer.css'; -import { inSnippet, LINES_BELOW_ISSUE } from './utils'; +import { LINES_BELOW_ISSUE } from './utils'; interface Props { component: SourceViewerFile; @@ -50,20 +48,16 @@ interface Props { duplications?: Duplication[]; duplicationsByLine?: { [line: number]: number[] }; expandBlock: (snippetIndex: number, direction: ExpandDirection) => Promise; - handleCloseIssues: (line: SourceLine) => void; - handleOpenIssues: (line: SourceLine) => void; handleSymbolClick: (symbols: string[]) => void; highlightedLocationMessage: { index: number; text: string | undefined } | undefined; highlightedSymbols: string[]; index: number; issue: Pick; - issuesByLine: IssuesByLine; lastSnippetOfLastGroup: boolean; loadDuplications?: (line: SourceLine) => void; locations: FlowLocation[]; locationsByLine: { [line: number]: LinearIssueLocation[] }; onLocationSelect: (index: number) => void; - openIssuesByLine: Dict; renderAdditionalChildInLine?: (line: SourceLine) => React.ReactNode | undefined; renderDuplicationPopup: (index: number, line: number) => React.ReactNode; scroll?: (element: HTMLElement, offset?: number) => void; @@ -118,7 +112,6 @@ export default class SnippetViewer extends React.PureComponent { displayDuplications, displaySCM, index, - issuesForLine, issueLocations, line, snippet, @@ -128,7 +121,6 @@ export default class SnippetViewer extends React.PureComponent { displayDuplications: boolean; displaySCM?: boolean; index: number; - issuesForLine: Issue[]; issueLocations: LinearIssueLocation[]; line: SourceLine; snippet: SourceLine[]; @@ -142,16 +134,14 @@ export default class SnippetViewer extends React.PureComponent { const lineDuplications = (duplicationsCount && duplicationsByLine && duplicationsByLine[line.line]) || []; - const isSinkLine = issuesForLine.some(i => i.key === this.props.issue.key); const firstLineNumber = snippet && snippet.length ? snippet[0].line : 0; const noop = () => {}; return ( 1} + displayIssues={false} displayLineNumberOptions={displayLineNumberOptions} displayLocationMarkers={true} displaySCM={displaySCM} @@ -165,18 +155,18 @@ export default class SnippetViewer extends React.PureComponent { )} highlightedSymbols={optimizeHighlightedSymbols(symbols, this.props.highlightedSymbols)} issueLocations={issueLocations} - issues={issuesForLine} + issues={[]} key={line.line} last={false} line={line} loadDuplications={this.props.loadDuplications || noop} onIssueSelect={noop} onIssueUnselect={noop} - onIssuesClose={this.props.handleCloseIssues} - onIssuesOpen={this.props.handleOpenIssues} + onIssuesClose={noop} + onIssuesOpen={noop} onLocationSelect={this.props.onLocationSelect} onSymbolClick={this.props.handleSymbolClick} - openIssues={this.props.openIssuesByLine[line.line]} + openIssues={false} previousLine={index > 0 ? snippet[index - 1] : undefined} renderDuplicationPopup={this.props.renderDuplicationPopup} scroll={this.doScroll} @@ -192,10 +182,8 @@ export default class SnippetViewer extends React.PureComponent { component, displaySCM, issue, - issuesByLine = {}, lastSnippetOfLastGroup, locationsByLine, - openIssuesByLine, snippet } = this.props; const lastLine = @@ -205,14 +193,11 @@ export default class SnippetViewer extends React.PureComponent { const bottomLine = snippet[snippet.length - 1].line; const issueLine = issue.textRange ? issue.textRange.endLine : issue.line; - const lowestVisibleIssue = Math.max( - ...Object.keys(issuesByLine) - .map(k => parseInt(k, 10)) - .filter(l => inSnippet(l, snippet) && (l === issueLine || openIssuesByLine[l])) - ); - const verticalBuffer = lastSnippetOfLastGroup - ? Math.max(0, LINES_BELOW_ISSUE - (bottomLine - lowestVisibleIssue)) - : 0; + + const verticalBuffer = + lastSnippetOfLastGroup && issueLine + ? Math.max(0, LINES_BELOW_ISSUE - (bottomLine - issueLine)) + : 0; const displayDuplications = Boolean(this.props.loadDuplications) && snippet.some(s => !!s.duplicated); @@ -241,7 +226,6 @@ export default class SnippetViewer extends React.PureComponent { displayDuplications, displaySCM, index, - issuesForLine: issuesByLine[line.line] || [], issueLocations: locationsByLine[line.line] || [], line, snippet, diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetGroupViewer-test.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetGroupViewer-test.tsx index cbbe8669da5..e39ceb01d82 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetGroupViewer-test.tsx +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetGroupViewer-test.tsx @@ -234,16 +234,6 @@ it('should get the right branch when expanding', async () => { expect(getSources).toHaveBeenCalledWith({ branch: 'asdf', from: 8, key: 'project:a', to: 67 }); }); -it('should handle correctly open/close issue', () => { - const wrapper = shallowRender(); - const sourceLine = mockSourceLine(); - expect(wrapper.state('openIssuesByLine')).toEqual({}); - wrapper.instance().handleOpenIssues(sourceLine); - expect(wrapper.state('openIssuesByLine')).toEqual({ [sourceLine.line]: true }); - wrapper.instance().handleCloseIssues(sourceLine); - expect(wrapper.state('openIssuesByLine')).toEqual({ [sourceLine.line]: false }); -}); - it('should handle symbol highlighting', () => { const wrapper = shallowRender(); expect(wrapper.state('highlightedSymbols')).toEqual([]); @@ -294,19 +284,6 @@ it('should correctly handle lines actions', () => { ); }); -it('should render correctly line with issue', () => { - const issue = mockIssue(false, { - textRange: { endLine: 1, startLine: 1, endOffset: 1, startOffset: 0 } - }); - const wrapper = shallowRender({ - issue, - issuesByLine: { '1': [issue] } - }); - wrapper.instance().setState({ openIssuesByLine: { '1': true } }); - const wrapperLine = shallow(wrapper.instance().renderIssuesList(mockSourceLine({ line: 1 }))); - expect(wrapperLine).toMatchSnapshot(); -}); - describe('getNodes', () => { const snippetGroup: SnippetGroup = { component: mockSourceViewerFile(), @@ -324,6 +301,7 @@ describe('getNodes', () => { loadDuplications={jest.fn()} locations={[]} onIssueChange={jest.fn()} + onIssueSelect={jest.fn()} onIssuePopupToggle={jest.fn()} onLocationSelect={jest.fn()} renderDuplicationPopup={jest.fn()} @@ -386,6 +364,7 @@ describe('getHeight', () => { loadDuplications={jest.fn()} locations={[]} onIssueChange={jest.fn()} + onIssueSelect={jest.fn()} onIssuePopupToggle={jest.fn()} onLocationSelect={jest.fn()} renderDuplicationPopup={jest.fn()} @@ -437,6 +416,7 @@ function shallowRender(props: Partial { wrapper.instance().renderLine({ displayDuplications: false, index: 1, - issuesForLine: [], issueLocations: [], line: sourceline, snippet: [sourceline], @@ -146,20 +145,16 @@ function shallowRender(props: Partial = {}) { duplications={undefined} duplicationsByLine={undefined} expandBlock={jest.fn()} - handleCloseIssues={jest.fn()} - handleOpenIssues={jest.fn()} handleSymbolClick={jest.fn()} highlightedLocationMessage={{ index: 0, text: '' }} highlightedSymbols={[]} index={0} issue={mockIssue()} - issuesByLine={{}} lastSnippetOfLastGroup={false} loadDuplications={jest.fn()} locations={[]} locationsByLine={{}} onLocationSelect={jest.fn()} - openIssuesByLine={{}} renderDuplicationPopup={jest.fn()} scroll={jest.fn()} snippet={[]} @@ -175,20 +170,16 @@ function mountRender(props: Partial = {}) { duplications={undefined} duplicationsByLine={undefined} expandBlock={jest.fn()} - handleCloseIssues={jest.fn()} - handleOpenIssues={jest.fn()} handleSymbolClick={jest.fn()} highlightedLocationMessage={{ index: 0, text: '' }} highlightedSymbols={[]} index={0} issue={mockIssue()} - issuesByLine={{}} lastSnippetOfLastGroup={false} loadDuplications={jest.fn()} locations={[]} locationsByLine={{}} onLocationSelect={jest.fn()} - openIssuesByLine={{}} renderDuplicationPopup={jest.fn()} scroll={jest.fn()} snippet={[mockSourceLine()]} diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/ComponentSourceSnippetGroupViewer-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/ComponentSourceSnippetGroupViewer-test.tsx.snap index c19827b9ef7..58f647c8012 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/ComponentSourceSnippetGroupViewer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/ComponentSourceSnippetGroupViewer-test.tsx.snap @@ -39,57 +39,3 @@ exports[`should render correctly 1`] = ` /> `; - -exports[`should render correctly line with issue 1`] = ` -
- -
-`; diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/CrossComponentSourceViewerWrapper-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/CrossComponentSourceViewerWrapper-test.tsx.snap index 7027b2e58a7..f587717fbbc 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/CrossComponentSourceViewerWrapper-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/CrossComponentSourceViewerWrapper-test.tsx.snap @@ -171,6 +171,7 @@ exports[`should render correctly 2`] = ` } onIssueChange={[MockFunction]} onIssuePopupToggle={[Function]} + onIssueSelect={[MockFunction]} onLocationSelect={[MockFunction]} renderDuplicationPopup={[Function]} scroll={[MockFunction]} @@ -346,6 +347,7 @@ exports[`should render correctly: no component found 1`] = ` locations={Array []} onIssueChange={[MockFunction]} onIssuePopupToggle={[Function]} + onIssueSelect={[MockFunction]} onLocationSelect={[MockFunction]} renderDuplicationPopup={[Function]} scroll={[MockFunction]} @@ -506,6 +508,7 @@ exports[`should render correctly: no component found 1`] = ` } onIssueChange={[MockFunction]} onIssuePopupToggle={[Function]} + onIssueSelect={[MockFunction]} onLocationSelect={[MockFunction]} renderDuplicationPopup={[Function]} scroll={[MockFunction]} diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/SnippetViewer-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/SnippetViewer-test.tsx.snap index 804854852ec..085138cd9cf 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/SnippetViewer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/SnippetViewer-test.tsx.snap @@ -21,10 +21,9 @@ exports[`should render correctly 1`] = ` > import java.util.ArrayList;", @@ -112,10 +112,9 @@ exports[`should render correctly 1`] = ` verticalBuffer={0} /> import java.util.ArrayList;", @@ -201,10 +201,9 @@ exports[`should render correctly when at the bottom of the file 1`] = ` > import java.util.ArrayList;", @@ -292,10 +292,9 @@ exports[`should render correctly when at the bottom of the file 1`] = ` verticalBuffer={0} /> import java.util.ArrayList;", @@ -344,10 +344,9 @@ exports[`should render correctly when at the bottom of the file 1`] = ` verticalBuffer={0} /> import java.util.ArrayList;", @@ -422,10 +422,9 @@ exports[`should render correctly when at the top of the file 1`] = ` > import java.util.ArrayList;", @@ -513,10 +513,9 @@ exports[`should render correctly when at the top of the file 1`] = ` verticalBuffer={0} /> import java.util.ArrayList;", @@ -565,10 +565,9 @@ exports[`should render correctly when at the top of the file 1`] = ` verticalBuffer={0} /> import java.util.ArrayList;", @@ -617,10 +617,9 @@ exports[`should render correctly when at the top of the file 1`] = ` verticalBuffer={0} /> import java.util.ArrayList;", @@ -669,10 +669,9 @@ exports[`should render correctly when at the top of the file 1`] = ` verticalBuffer={0} /> import java.util.ArrayList;", @@ -721,10 +721,9 @@ exports[`should render correctly when at the top of the file 1`] = ` verticalBuffer={0} /> import java.util.ArrayList;", @@ -810,10 +810,9 @@ exports[`should render correctly with no SCM 1`] = ` > import java.util.ArrayList;", @@ -903,10 +903,9 @@ exports[`should render correctly with no SCM 1`] = ` verticalBuffer={0} /> import java.util.ArrayList;", 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 8c6c1ba68fc..e7c24596abc 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 @@ -185,19 +185,15 @@ export default function HotspotSnippetContainerRenderer( expandBlock={(_i, direction) => animateExpansion(scrollableRef, props.onExpandBlock, direction) } - handleCloseIssues={noop} - handleOpenIssues={noop} handleSymbolClick={props.onSymbolClick} highlightedLocationMessage={highlightedLocation} highlightedSymbols={highlightedSymbols} index={0} issue={hotspot} - issuesByLine={{}} lastSnippetOfLastGroup={false} locations={secondaryLocations} locationsByLine={primaryLocations} onLocationSelect={props.onLocationSelect} - openIssuesByLine={{}} renderAdditionalChildInLine={renderHotspotBoxInLine} renderDuplicationPopup={noop} snippet={sourceLines} 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 a4ade78ebae..d65b34bfaa8 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 @@ -423,8 +423,6 @@ exports[`should render correctly: with sourcelines 1`] = ` displayLineNumberOptions={false} displaySCM={false} expandBlock={[Function]} - handleCloseIssues={[Function]} - handleOpenIssues={[Function]} handleSymbolClick={[MockFunction]} highlightedSymbols={Array []} index={0} @@ -514,12 +512,10 @@ exports[`should render correctly: with sourcelines 1`] = ` ], } } - issuesByLine={Object {}} lastSnippetOfLastGroup={false} locations={Array []} locationsByLine={Object {}} onLocationSelect={[MockFunction]} - openIssuesByLine={Object {}} renderAdditionalChildInLine={[Function]} renderDuplicationPopup={[Function]} scroll={[Function]} diff --git a/server/sonar-web/src/main/js/components/issue/SecondaryIssue.tsx b/server/sonar-web/src/main/js/components/issue/SecondaryIssue.tsx new file mode 100644 index 00000000000..f56266e5d43 --- /dev/null +++ b/server/sonar-web/src/main/js/components/issue/SecondaryIssue.tsx @@ -0,0 +1,48 @@ +/* + * 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 * as React from 'react'; +import { colors } from '../../app/theme'; +import { Issue as TypeIssue } from '../../types/types'; +import IssueTypeIcon from '../icons/IssueTypeIcon'; +import './Issue.css'; + +export interface SecondaryIssueProps { + issue: TypeIssue; + onClick: (issueKey: string) => void; +} + +export default function SecondaryIssue(props: SecondaryIssueProps) { + const { issue } = props; + return ( +
props.onClick(issue.key)} + role="region" + aria-label={issue.message}> + + {issue.message} +
+ ); +} -- 2.39.5