3 * Copyright (C) 2009-2022 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 import { findLastIndex } from 'lodash';
21 import * as React from 'react';
22 import { getDuplications } from '../../../api/components';
23 import { getIssueFlowSnippets } from '../../../api/issues';
24 import DuplicationPopup from '../../../components/SourceViewer/components/DuplicationPopup';
26 filterDuplicationBlocksByLine,
27 getDuplicationBlocksForIndex,
28 isDuplicationBlockInRemovedComponent
29 } from '../../../components/SourceViewer/helpers/duplications';
31 duplicationsByLine as getDuplicationsByLine,
32 issuesByComponentAndLine
33 } from '../../../components/SourceViewer/helpers/indexing';
34 import { SourceViewerContext } from '../../../components/SourceViewer/SourceViewerContext';
35 import { Alert } from '../../../components/ui/Alert';
36 import DeferredSpinner from '../../../components/ui/DeferredSpinner';
37 import { WorkspaceContext } from '../../../components/workspace/context';
38 import { getBranchLikeQuery } from '../../../helpers/branch-like';
39 import { throwGlobalError } from '../../../helpers/error';
40 import { translate } from '../../../helpers/l10n';
41 import { BranchLike } from '../../../types/branch-like';
50 } from '../../../types/types';
51 import ComponentSourceSnippetGroupViewer from './ComponentSourceSnippetGroupViewer';
52 import { groupLocationsByComponent } from './utils';
55 branchLike: BranchLike | undefined;
56 highlightedLocationMessage?: { index: number; text: string | undefined };
59 locations: FlowLocation[];
60 onIssueChange: (issue: Issue) => void;
61 onLoaded?: () => void;
62 onLocationSelect: (index: number) => void;
63 scroll?: (element: HTMLElement) => void;
64 selectedFlowIndex: number | undefined;
68 components: Dict<SnippetsByComponent>;
69 duplicatedFiles?: Dict<DuplicatedFile>;
70 duplications?: Duplication[];
71 duplicationsByLine: { [line: number]: number[] };
72 issuePopup?: { issue: string; name: string };
74 notAccessible: boolean;
77 export default class CrossComponentSourceViewerWrapper extends React.PureComponent<Props, State> {
81 duplicationsByLine: {},
88 this.fetchIssueFlowSnippets(this.props.issue.key);
91 componentDidUpdate(prevProps: Props) {
92 if (prevProps.issue.key !== this.props.issue.key) {
93 this.fetchIssueFlowSnippets(this.props.issue.key);
97 componentWillUnmount() {
101 fetchDuplications = (component: string) => {
104 ...getBranchLikeQuery(this.props.branchLike)
109 duplicatedFiles: r.files,
110 duplications: r.duplications,
111 duplicationsByLine: getDuplicationsByLine(r.duplications)
119 fetchIssueFlowSnippets(issueKey: string) {
120 this.setState({ loading: true });
121 getIssueFlowSnippets(issueKey).then(
126 issuePopup: undefined,
129 if (this.props.onLoaded) {
130 this.props.onLoaded();
134 (response: Response) => {
135 if (response.status !== 403) {
136 throwGlobalError(response);
139 this.setState({ loading: false, notAccessible: response.status === 403 });
145 handleIssuePopupToggle = (issue: string, popupName: string, open?: boolean) => {
146 this.setState((state: State) => {
148 state.issuePopup && state.issuePopup.name === popupName && state.issuePopup.issue === issue;
149 if (open !== false && !samePopup) {
150 return { issuePopup: { issue, name: popupName } };
151 } else if (open !== true && samePopup) {
152 return { issuePopup: undefined };
158 renderDuplicationPopup = (component: SourceViewerFile, index: number, line: number) => {
159 const { duplicatedFiles, duplications } = this.state;
161 if (!component || !duplicatedFiles) {
165 const blocks = getDuplicationBlocksForIndex(duplications, index);
168 <WorkspaceContext.Consumer>
169 {({ openComponent }) => (
171 blocks={filterDuplicationBlocksByLine(blocks, line)}
172 branchLike={this.props.branchLike}
173 duplicatedFiles={duplicatedFiles}
174 inRemovedComponent={isDuplicationBlockInRemovedComponent(blocks)}
175 openComponent={openComponent}
176 sourceViewerFile={component}
179 </WorkspaceContext.Consumer>
184 const { loading, notAccessible } = this.state;
196 <Alert className="spacer-top" variant="warning">
197 {translate('code_viewer.no_source_code_displayed_due_to_security')}
202 const { issue, locations } = this.props;
203 const { components, duplications, duplicationsByLine } = this.state;
204 const issuesByComponent = issuesByComponentAndLine(this.props.issues);
205 const locationsByComponent = groupLocationsByComponent(issue, locations, components);
207 const lastOccurenceOfPrimaryComponent = findLastIndex(
208 locationsByComponent,
209 ({ component }) => component.key === issue.component
214 {locationsByComponent.map((snippetGroup, i) => {
216 <SourceViewerContext.Provider
217 // eslint-disable-next-line react/no-array-index-key
218 key={`${issue.key}-${this.props.selectedFlowIndex || 0}-${i}`}
219 value={{ branchLike: this.props.branchLike, file: snippetGroup.component }}>
220 <ComponentSourceSnippetGroupViewer
221 branchLike={this.props.branchLike}
222 duplications={duplications}
223 duplicationsByLine={duplicationsByLine}
224 highlightedLocationMessage={this.props.highlightedLocationMessage}
226 issuePopup={this.state.issuePopup}
227 issuesByLine={issuesByComponent[snippetGroup.component.key] || {}}
228 isLastOccurenceOfPrimaryComponent={i === lastOccurenceOfPrimaryComponent}
229 lastSnippetGroup={i === locationsByComponent.length - 1}
230 loadDuplications={this.fetchDuplications}
231 locations={snippetGroup.locations || []}
232 onIssueChange={this.props.onIssueChange}
233 onIssuePopupToggle={this.handleIssuePopupToggle}
234 onLocationSelect={this.props.onLocationSelect}
235 renderDuplicationPopup={this.renderDuplicationPopup}
236 scroll={this.props.scroll}
237 snippetGroup={snippetGroup}
239 </SourceViewerContext.Provider>