import CrossComponentSourceViewer from '../crossComponentSourceViewer/CrossComponentSourceViewer';
import { getLocations, getSelectedLocation } from '../utils';
-interface Props {
+export interface IssuesSourceViewerProps {
branchLike: BranchLike | undefined;
issues: Issue[];
locationsNavigator: boolean;
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>
+ );
}
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');
).toMatchSnapshot();
});
-function shallowRender(props: Partial<IssuesSourceViewer['props']> = {}) {
+function shallowRender(props: Partial<IssuesSourceViewerProps> = {}) {
return shallow(
<IssuesSourceViewer
branchLike={mockMainBranch()}
]
}
onIssueSelect={[MockFunction]}
- onLoaded={[Function]}
onLocationSelect={[MockFunction]}
/>
</div>
]
}
onIssueSelect={[MockFunction]}
- onLoaded={[Function]}
onLocationSelect={[MockFunction]}
/>
</div>
}
locations={Array []}
onIssueSelect={[MockFunction]}
- onLoaded={[Function]}
onLocationSelect={[MockFunction]}
/>
</div>
]
}
onIssueSelect={[MockFunction]}
- onLoaded={[Function]}
onLocationSelect={[MockFunction]}
/>
</div>
line: number
) => React.ReactNode;
snippetGroup: SnippetGroup;
+ selectedLocationIndex: number | undefined;
}
interface State {
};
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])
key={issueToDisplay.key}
issue={issueToDisplay}
onClick={this.props.onIssueSelect}
+ selectedLocationIndex={selectedLocationIndex}
/>
))}
</div>
isLastOccurenceOfPrimaryComponent,
issue,
lastSnippetGroup,
- snippetGroup
+ snippetGroup,
+ selectedLocationIndex
} = this.props;
const { additionalLines, loading, snippets } = this.state;
const locations =
/>
{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
issues: Issue[];
locations: FlowLocation[];
onIssueSelect: (issueKey: string) => void;
- onLoaded?: () => void;
onLocationSelect: (index: number) => void;
selectedFlowIndex: number | undefined;
+ selectedLocationIndex: number | undefined;
}
interface State {
components,
loading: false
});
- if (this.props.onLoaded) {
- this.props.onLoaded();
- }
}
} catch (response) {
const rsp = response as Response;
render() {
const { loading, notAccessible } = this.state;
+ const { selectedLocationIndex } = this.props;
if (loading) {
return (
onLocationSelect={this.props.onLocationSelect}
renderDuplicationPopup={this.renderDuplicationPopup}
snippetGroup={snippetGroup}
+ selectedLocationIndex={selectedLocationIndex}
/>
</SourceViewerContext.Provider>
);
<ComponentSourceSnippetGroupViewer
branchLike={mockMainBranch()}
highlightedLocationMessage={{ index: 0, text: '' }}
+ selectedLocationIndex={0}
isLastOccurenceOfPrimaryComponent={true}
issue={mockIssue()}
issuesByLine={{}}
<CrossComponentSourceViewer
branchLike={undefined}
highlightedLocationMessage={undefined}
+ selectedLocationIndex={undefined}
issue={mockIssue(true, {
key: '1',
component: 'project:main.js',
})}
issues={[]}
locations={[mockFlowLocation({ component: 'project:main.js' })]}
- onLoaded={jest.fn()}
onIssueSelect={jest.fn()}
onLocationSelect={jest.fn()}
selectedFlowIndex={0}
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>
+ );
+ }
}
return (
<div
className={issueClass}
- data-issue={issue.key}
onClick={this.handleClick}
role="region"
aria-label={issue.message}>
<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"
>
<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"
>