From 1ef2bbc5841cd248c44058681d548c146ad7bffa Mon Sep 17 00:00:00 2001 From: Revanshu Paliwal Date: Tue, 9 Aug 2022 15:04:16 +0200 Subject: [PATCH] SONAR-16538 On selecting issue, scrolling to the issue message box in code panel --- .../issues/components/IssuesSourceViewer.tsx | 97 +++++++------------ .../__tests__/IssuesSourceViewer-test.tsx | 4 +- .../IssuesSourceViewer-test.tsx.snap | 4 - .../ComponentSourceSnippetGroupViewer.tsx | 20 +++- .../CrossComponentSourceViewer.tsx | 7 +- ...ComponentSourceSnippetGroupViewer-test.tsx | 1 + .../CrossComponentSourceViewer-test.tsx | 2 +- .../js/components/issue/IssueMessageBox.tsx | 70 +++++++++---- .../main/js/components/issue/IssueView.tsx | 1 - .../__snapshots__/IssueView-test.tsx.snap | 2 - 10 files changed, 108 insertions(+), 100 deletions(-) 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 d6038b1bea3..ee79ab27059 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 @@ -23,7 +23,7 @@ import { Issue } from '../../../types/types'; import CrossComponentSourceViewer from '../crossComponentSourceViewer/CrossComponentSourceViewer'; import { getLocations, getSelectedLocation } from '../utils'; -interface Props { +export interface IssuesSourceViewerProps { branchLike: BranchLike | undefined; issues: Issue[]; locationsNavigator: boolean; @@ -34,68 +34,39 @@ interface Props { selectedLocationIndex: number | undefined; } -export default class IssuesSourceViewer extends React.PureComponent { - node?: HTMLElement | null; +export default function IssuesSourceViewer(props: IssuesSourceViewerProps) { + const { + openIssue, + selectedFlowIndex, + selectedLocationIndex, + locationsNavigator, + branchLike, + issues + } = props; - componentDidUpdate(prevProps: Props) { - const { openIssue, selectedLocationIndex } = this.props; + const locations = getLocations(openIssue, selectedFlowIndex).map((loc, index) => { + loc.index = index; + return loc; + }); + const selectedLocation = getSelectedLocation(openIssue, selectedFlowIndex, selectedLocationIndex); - // Scroll back to the issue when the selected location is set to -1 - const shouldScrollBackToIssue = - selectedLocationIndex === -1 && selectedLocationIndex !== prevProps.selectedLocationIndex; - if ( - prevProps.openIssue.component === openIssue.component && - (prevProps.openIssue !== openIssue || shouldScrollBackToIssue) - ) { - this.scrollToIssue(); - } - } - - scrollToIssue = () => { - if (this.node) { - const element = this.node.querySelector(`[data-issue="${this.props.openIssue.key}"]`); - if (element) { - element.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' }); - } - } - }; - - handleLoaded = () => { - this.scrollToIssue(); - }; - - render() { - const { openIssue, selectedFlowIndex, selectedLocationIndex } = this.props; - - const locations = getLocations(openIssue, selectedFlowIndex).map((loc, index) => { - loc.index = index; - return loc; - }); - const selectedLocation = getSelectedLocation( - openIssue, - selectedFlowIndex, - selectedLocationIndex - ); - - const highlightedLocationMessage = - this.props.locationsNavigator && selectedLocationIndex !== undefined - ? selectedLocation && { index: selectedLocationIndex, text: selectedLocation.msg } - : undefined; - - return ( -
(this.node = node)}> - -
- ); - } + const highlightedLocationMessage = + locationsNavigator && selectedLocationIndex !== undefined + ? selectedLocation && { index: selectedLocationIndex, text: selectedLocation.msg } + : undefined; + return ( +
+ +
+ ); } 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 e43a9a96465..2a29aa7ea46 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 @@ -21,7 +21,7 @@ import { shallow } from 'enzyme'; import * as React from 'react'; import { mockMainBranch } from '../../../../helpers/mocks/branch-like'; import { mockFlowLocation, mockIssue } from '../../../../helpers/testMocks'; -import IssuesSourceViewer from '../IssuesSourceViewer'; +import IssuesSourceViewer, { IssuesSourceViewerProps } from '../IssuesSourceViewer'; it('should render SourceViewer correctly', () => { expect(shallowRender()).toMatchSnapshot('default'); @@ -64,7 +64,7 @@ it('should render CrossComponentSourceViewer correctly', () => { ).toMatchSnapshot(); }); -function shallowRender(props: Partial = {}) { +function shallowRender(props: Partial = {}) { return shallow( @@ -444,7 +443,6 @@ exports[`should render SourceViewer correctly: all secondary locations on same l ] } onIssueSelect={[MockFunction]} - onLoaded={[Function]} onLocationSelect={[MockFunction]} /> @@ -525,7 +523,6 @@ exports[`should render SourceViewer correctly: default 1`] = ` } locations={Array []} onIssueSelect={[MockFunction]} - onLoaded={[Function]} onLocationSelect={[MockFunction]} /> @@ -720,7 +717,6 @@ exports[`should render SourceViewer correctly: single secondary location 1`] = ` ] } onIssueSelect={[MockFunction]} - onLoaded={[Function]} onLocationSelect={[MockFunction]} /> 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 6f106817316..892849613c1 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 @@ -67,6 +67,7 @@ interface Props { line: number ) => React.ReactNode; snippetGroup: SnippetGroup; + selectedLocationIndex: number | undefined; } interface State { @@ -205,7 +206,13 @@ export default class ComponentSourceSnippetGroupViewer extends React.PureCompone }; renderIssuesList = (line: SourceLine) => { - const { isLastOccurenceOfPrimaryComponent, issue, issuesByLine, snippetGroup } = this.props; + const { + isLastOccurenceOfPrimaryComponent, + issue, + issuesByLine, + snippetGroup, + selectedLocationIndex + } = this.props; const locations = issue.component === snippetGroup.component.key && issue.textRange !== undefined ? locationsByLine([issue]) @@ -225,6 +232,7 @@ export default class ComponentSourceSnippetGroupViewer extends React.PureCompone key={issueToDisplay.key} issue={issueToDisplay} onClick={this.props.onIssueSelect} + selectedLocationIndex={selectedLocationIndex} /> ))} @@ -238,7 +246,8 @@ export default class ComponentSourceSnippetGroupViewer extends React.PureCompone isLastOccurenceOfPrimaryComponent, issue, lastSnippetGroup, - snippetGroup + snippetGroup, + selectedLocationIndex } = this.props; const { additionalLines, loading, snippets } = this.state; const locations = @@ -271,7 +280,12 @@ export default class ComponentSourceSnippetGroupViewer extends React.PureCompone /> {issue.component === snippetGroup.component.key && issue.textRange === undefined && ( - + )} {snippetLines.map((snippet, index) => ( void; - onLoaded?: () => void; onLocationSelect: (index: number) => void; selectedFlowIndex: number | undefined; + selectedLocationIndex: number | undefined; } interface State { @@ -145,9 +145,6 @@ export default class CrossComponentSourceViewer extends React.PureComponent ); 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 30ca9f59af4..57e292a25d2 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 @@ -294,6 +294,7 @@ function shallowRender(props: Partial = {}) = {}) })} issues={[]} locations={[mockFlowLocation({ component: 'project:main.js' })]} - onLoaded={jest.fn()} onIssueSelect={jest.fn()} onLocationSelect={jest.fn()} selectedFlowIndex={0} diff --git a/server/sonar-web/src/main/js/components/issue/IssueMessageBox.tsx b/server/sonar-web/src/main/js/components/issue/IssueMessageBox.tsx index a24c0bfedac..cceb592fb35 100644 --- a/server/sonar-web/src/main/js/components/issue/IssueMessageBox.tsx +++ b/server/sonar-web/src/main/js/components/issue/IssueMessageBox.tsx @@ -28,26 +28,56 @@ export interface IssueMessageBoxProps { selected: boolean; issue: Issue; onClick: (issueKey: string) => void; + selectedLocationIndex?: number; } -export default function IssueMessageBox(props: IssueMessageBoxProps) { - const { issue, selected } = props; - return ( -
props.onClick(issue.key)} - role="region" - aria-label={issue.message}> - - {issue.message} -
- ); +export default class IssueMessageBox extends React.Component { + messageBoxRef: React.RefObject = React.createRef(); + + componentDidMount() { + if (this.props.selected && this.messageBoxRef.current) { + this.messageBoxRef.current.scrollIntoView({ + block: 'center' + }); + } + } + + componentDidUpdate(prevProps: IssueMessageBoxProps) { + if ( + this.messageBoxRef.current && + ((prevProps.selected !== this.props.selected && this.props.selected) || + (prevProps.selectedLocationIndex !== this.props.selectedLocationIndex && + this.props.selectedLocationIndex === -1)) + ) { + this.messageBoxRef.current.scrollIntoView({ + block: 'center' + }); + } + } + + render() { + const { issue, selected } = this.props; + return ( +
this.props.onClick(issue.key)} + role="region" + ref={this.messageBoxRef} + aria-label={issue.message}> + + {issue.message} +
+ ); + } } diff --git a/server/sonar-web/src/main/js/components/issue/IssueView.tsx b/server/sonar-web/src/main/js/components/issue/IssueView.tsx index 71224db95a3..c5b9aa303b3 100644 --- a/server/sonar-web/src/main/js/components/issue/IssueView.tsx +++ b/server/sonar-web/src/main/js/components/issue/IssueView.tsx @@ -91,7 +91,6 @@ export default class IssueView extends React.PureComponent { return (
diff --git a/server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/IssueView-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/IssueView-test.tsx.snap index b6774a7e6d4..c0843b958a6 100644 --- a/server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/IssueView-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/IssueView-test.tsx.snap @@ -4,7 +4,6 @@ exports[`should render hotspots correctly 1`] = `
@@ -84,7 +83,6 @@ exports[`should render issues correctly 1`] = `
-- 2.39.5