3 * Copyright (C) 2009-2019 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 * as React from 'react';
21 import ComponentSourceSnippetViewer from './ComponentSourceSnippetViewer';
22 import { groupLocationsByComponent } from './utils';
23 import DeferredSpinner from '../../../components/common/DeferredSpinner';
24 import DuplicationPopup from '../../../components/SourceViewer/components/DuplicationPopup';
25 import { WorkspaceContext } from '../../../components/workspace/context';
26 import { getIssueFlowSnippets } from '../../../api/issues';
28 filterDuplicationBlocksByLine,
29 isDuplicationBlockInRemovedComponent,
30 getDuplicationBlocksForIndex
31 } from '../../../components/SourceViewer/helpers/duplications';
34 issuesByComponentAndLine
35 } from '../../../components/SourceViewer/helpers/indexing';
36 import { getDuplications } from '../../../api/components';
37 import { getBranchLikeQuery } from '../../../helpers/branches';
40 branchLike: T.Branch | T.PullRequest | undefined;
41 highlightedLocationMessage?: { index: number; text: string | undefined };
44 locations: T.FlowLocation[];
45 onIssueChange: (issue: T.Issue) => void;
46 onLoaded?: () => void;
47 onLocationSelect: (index: number) => void;
48 scroll?: (element: HTMLElement) => void;
49 selectedFlowIndex: number | undefined;
53 components: T.Dict<T.SnippetsByComponent>;
54 duplicatedFiles?: T.Dict<T.DuplicatedFile>;
55 duplications?: T.Duplication[];
56 duplicationsByLine: { [line: number]: number[] };
57 issuePopup?: { issue: string; name: string };
58 linePopup?: T.LinePopup & { component: string };
62 export default class CrossComponentSourceViewerWrapper extends React.PureComponent<Props, State> {
66 duplicationsByLine: {},
72 this.fetchIssueFlowSnippets(this.props.issue.key);
75 componentWillReceiveProps(newProps: Props) {
76 if (newProps.issue.key !== this.props.issue.key) {
77 this.fetchIssueFlowSnippets(newProps.issue.key);
81 componentWillUnmount() {
85 fetchDuplications = (component: string, line: T.SourceLine) => {
88 ...getBranchLikeQuery(this.props.branchLike)
92 this.setState(state => ({
93 duplicatedFiles: r.files,
94 duplications: r.duplications,
95 duplicationsByLine: duplicationsByLine(r.duplications),
97 r.duplications.length === 1
98 ? { component, index: 0, line: line.line, name: 'duplications' }
107 fetchIssueFlowSnippets(issueKey: string) {
108 this.setState({ loading: true });
109 getIssueFlowSnippets(issueKey).then(
114 issuePopup: undefined,
115 linePopup: undefined,
118 if (this.props.onLoaded) {
119 this.props.onLoaded();
125 this.setState({ loading: false });
131 handleIssuePopupToggle = (issue: string, popupName: string, open?: boolean) => {
132 this.setState((state: State) => {
134 state.issuePopup && state.issuePopup.name === popupName && state.issuePopup.issue === issue;
135 if (open !== false && !samePopup) {
136 return { issuePopup: { issue, name: popupName } };
137 } else if (open !== true && samePopup) {
138 return { issuePopup: undefined };
144 handleLinePopupToggle = ({
150 }: T.LinePopup & { component: string }) => {
151 this.setState((state: State) => {
153 state.linePopup !== undefined &&
154 state.linePopup.line === line &&
155 state.linePopup.name === name &&
156 state.linePopup.component === component &&
157 state.linePopup.index === index;
158 if (open !== false && !samePopup) {
159 return { linePopup: { component, index, line, name } };
160 } else if (open !== true && samePopup) {
161 return { linePopup: undefined };
167 handleCloseLinePopup = () => {
168 this.setState({ linePopup: undefined });
171 renderDuplicationPopup = (component: T.SourceViewerFile, index: number, line: number) => {
172 const { duplicatedFiles, duplications } = this.state;
174 if (!component || !duplicatedFiles) {
178 const blocks = getDuplicationBlocksForIndex(duplications, index);
181 <WorkspaceContext.Consumer>
182 {({ openComponent }) => (
184 blocks={filterDuplicationBlocksByLine(blocks, line)}
185 branchLike={this.props.branchLike}
186 duplicatedFiles={duplicatedFiles}
187 inRemovedComponent={isDuplicationBlockInRemovedComponent(blocks)}
188 onClose={this.handleCloseLinePopup}
189 openComponent={openComponent}
190 sourceViewerFile={component}
193 </WorkspaceContext.Consumer>
198 const { loading } = this.state;
208 const { components, duplications, duplicationsByLine, linePopup } = this.state;
209 const issuesByComponent = issuesByComponentAndLine(this.props.issues);
210 const locationsByComponent = groupLocationsByComponent(this.props.locations, components);
214 {locationsByComponent.map((snippetGroup, i) => {
215 let componentProps = {};
216 if (linePopup && snippetGroup.component.key === linePopup.component) {
220 linePopup: { index: linePopup.index, line: linePopup.line, name: linePopup.name }
224 <ComponentSourceSnippetViewer
225 branchLike={this.props.branchLike}
226 highlightedLocationMessage={this.props.highlightedLocationMessage}
227 issue={this.props.issue}
228 issuePopup={this.state.issuePopup}
229 issuesByLine={issuesByComponent[snippetGroup.component.key] || {}}
230 key={`${this.props.issue.key}-${this.props.selectedFlowIndex}-${i}`}
231 last={i === locationsByComponent.length - 1}
232 loadDuplications={this.fetchDuplications}
233 locations={snippetGroup.locations || []}
234 onIssueChange={this.props.onIssueChange}
235 onIssuePopupToggle={this.handleIssuePopupToggle}
236 onLinePopupToggle={this.handleLinePopupToggle}
237 onLocationSelect={this.props.onLocationSelect}
238 renderDuplicationPopup={this.renderDuplicationPopup}
239 scroll={this.props.scroll}
240 snippetGroup={snippetGroup}