aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src
diff options
context:
space:
mode:
authorRevanshu Paliwal <revanshu.paliwal@sonarsource.com>2022-08-09 15:04:16 +0200
committersonartech <sonartech@sonarsource.com>2022-08-10 10:04:06 +0000
commit1ef2bbc5841cd248c44058681d548c146ad7bffa (patch)
treeff8ccbb3c00746bf78bbeabd57027aa73d7a2f63 /server/sonar-web/src
parent51f2a5f82dab35a96ccdcee1d7e891af7f3c1394 (diff)
downloadsonarqube-1ef2bbc5841cd248c44058681d548c146ad7bffa.tar.gz
sonarqube-1ef2bbc5841cd248c44058681d548c146ad7bffa.zip
SONAR-16538 On selecting issue, scrolling to the issue message box in code panel
Diffstat (limited to 'server/sonar-web/src')
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.tsx97
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesSourceViewer-test.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesSourceViewer-test.tsx.snap4
-rw-r--r--server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx20
-rw-r--r--server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewer.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetGroupViewer-test.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/CrossComponentSourceViewer-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/issue/IssueMessageBox.tsx70
-rw-r--r--server/sonar-web/src/main/js/components/issue/IssueView.tsx1
-rw-r--r--server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/IssueView-test.tsx.snap2
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<Props> {
- 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 (
- <div ref={node => (this.node = node)}>
- <CrossComponentSourceViewer
- branchLike={this.props.branchLike}
- highlightedLocationMessage={highlightedLocationMessage}
- issue={openIssue}
- issues={this.props.issues}
- locations={locations}
- onIssueSelect={this.props.onIssueSelect}
- onLoaded={this.handleLoaded}
- onLocationSelect={this.props.onLocationSelect}
- selectedFlowIndex={selectedFlowIndex}
- />
- </div>
- );
- }
+ const highlightedLocationMessage =
+ locationsNavigator && selectedLocationIndex !== undefined
+ ? selectedLocation && { index: selectedLocationIndex, text: selectedLocation.msg }
+ : undefined;
+ return (
+ <div>
+ <CrossComponentSourceViewer
+ branchLike={branchLike}
+ highlightedLocationMessage={highlightedLocationMessage}
+ issue={openIssue}
+ issues={issues}
+ locations={locations}
+ onIssueSelect={props.onIssueSelect}
+ onLocationSelect={props.onLocationSelect}
+ selectedFlowIndex={selectedFlowIndex}
+ selectedLocationIndex={selectedLocationIndex}
+ />
+ </div>
+ );
}
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<IssuesSourceViewer['props']> = {}) {
+function shallowRender(props: Partial<IssuesSourceViewerProps> = {}) {
return shallow(
<IssuesSourceViewer
branchLike={mockMainBranch()}
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 44f2305d623..17f3c6d7887 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
@@ -209,7 +209,6 @@ exports[`should render CrossComponentSourceViewer correctly 1`] = `
]
}
onIssueSelect={[MockFunction]}
- onLoaded={[Function]}
onLocationSelect={[MockFunction]}
/>
</div>
@@ -444,7 +443,6 @@ exports[`should render SourceViewer correctly: all secondary locations on same l
]
}
onIssueSelect={[MockFunction]}
- onLoaded={[Function]}
onLocationSelect={[MockFunction]}
/>
</div>
@@ -525,7 +523,6 @@ exports[`should render SourceViewer correctly: default 1`] = `
}
locations={Array []}
onIssueSelect={[MockFunction]}
- onLoaded={[Function]}
onLocationSelect={[MockFunction]}
/>
</div>
@@ -720,7 +717,6 @@ exports[`should render SourceViewer correctly: single secondary location 1`] = `
]
}
onIssueSelect={[MockFunction]}
- onLoaded={[Function]}
onLocationSelect={[MockFunction]}
/>
</div>
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}
/>
))}
</div>
@@ -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 && (
- <IssueMessageBox selected={true} issue={issue} onClick={this.props.onIssueSelect} />
+ <IssueMessageBox
+ selected={true}
+ issue={issue}
+ onClick={this.props.onIssueSelect}
+ selectedLocationIndex={selectedLocationIndex}
+ />
)}
{snippetLines.map((snippet, index) => (
<SnippetViewer
diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewer.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewer.tsx
index b1ef51e80a0..77588f5fe94 100644
--- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewer.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewer.tsx
@@ -60,9 +60,9 @@ interface Props {
issues: Issue[];
locations: FlowLocation[];
onIssueSelect: (issueKey: string) => 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<Prop
components,
loading: false
});
- if (this.props.onLoaded) {
- this.props.onLoaded();
- }
}
} catch (response) {
const rsp = response as Response;
@@ -187,6 +184,7 @@ export default class CrossComponentSourceViewer extends React.PureComponent<Prop
render() {
const { loading, notAccessible } = this.state;
+ const { selectedLocationIndex } = this.props;
if (loading) {
return (
@@ -240,6 +238,7 @@ export default class CrossComponentSourceViewer extends React.PureComponent<Prop
onLocationSelect={this.props.onLocationSelect}
renderDuplicationPopup={this.renderDuplicationPopup}
snippetGroup={snippetGroup}
+ selectedLocationIndex={selectedLocationIndex}
/>
</SourceViewerContext.Provider>
);
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<ComponentSourceSnippetGroupViewer['props']
<ComponentSourceSnippetGroupViewer
branchLike={mockMainBranch()}
highlightedLocationMessage={{ index: 0, text: '' }}
+ selectedLocationIndex={0}
isLastOccurenceOfPrimaryComponent={true}
issue={mockIssue()}
issuesByLine={{}}
diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/CrossComponentSourceViewer-test.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/CrossComponentSourceViewer-test.tsx
index 11d711d6422..22ba8be364b 100644
--- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/CrossComponentSourceViewer-test.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/CrossComponentSourceViewer-test.tsx
@@ -116,6 +116,7 @@ function shallowRender(props: Partial<CrossComponentSourceViewer['props']> = {})
<CrossComponentSourceViewer
branchLike={undefined}
highlightedLocationMessage={undefined}
+ selectedLocationIndex={undefined}
issue={mockIssue(true, {
key: '1',
component: 'project:main.js',
@@ -123,7 +124,6 @@ function shallowRender(props: Partial<CrossComponentSourceViewer['props']> = {})
})}
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 (
- <div
- className={classNames('issue-message-box display-flex-row display-flex-center padded-right', {
- 'selected big-padded-top big-padded-bottom': selected,
- 'secondary-issue padded-top padded-bottom': !selected
- })}
- key={issue.key}
- onClick={() => props.onClick(issue.key)}
- role="region"
- aria-label={issue.message}>
- <IssueTypeIcon
- className="big-spacer-right spacer-left"
- fill={colors.baseFontColor}
- query={issue.type}
- />
- {issue.message}
- </div>
- );
+export default class IssueMessageBox extends React.Component<IssueMessageBoxProps> {
+ messageBoxRef: React.RefObject<HTMLDivElement> = 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 (
+ <div
+ className={classNames(
+ 'issue-message-box display-flex-row display-flex-center padded-right',
+ {
+ 'selected big-padded-top big-padded-bottom': selected,
+ 'secondary-issue padded-top padded-bottom': !selected
+ }
+ )}
+ key={issue.key}
+ onClick={() => this.props.onClick(issue.key)}
+ role="region"
+ ref={this.messageBoxRef}
+ aria-label={issue.message}>
+ <IssueTypeIcon
+ className="big-spacer-right spacer-left"
+ fill={colors.baseFontColor}
+ query={issue.type}
+ />
+ {issue.message}
+ </div>
+ );
+ }
}
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<Props> {
return (
<div
className={issueClass}
- data-issue={issue.key}
onClick={this.handleClick}
role="region"
aria-label={issue.message}>
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`] = `
<div
aria-label="Reduce the number of conditional operators (4) used in the expression"
className="issue hotspot selected"
- data-issue="AVsae-CQS-9G3txfbFN2"
onClick={[Function]}
role="region"
>
@@ -84,7 +83,6 @@ exports[`should render issues correctly 1`] = `
<div
aria-label="Reduce the number of conditional operators (4) used in the expression"
className="issue selected"
- data-issue="AVsae-CQS-9G3txfbFN2"
onClick={[Function]}
role="region"
>