]> source.dussan.org Git - sonarqube.git/blob
8cdcbbdfd628fac64774ebf66d019ddda95867ea
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2019 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
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.
19  */
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';
27 import {
28   filterDuplicationBlocksByLine,
29   isDuplicationBlockInRemovedComponent,
30   getDuplicationBlocksForIndex
31 } from '../../../components/SourceViewer/helpers/duplications';
32 import {
33   duplicationsByLine,
34   issuesByComponentAndLine
35 } from '../../../components/SourceViewer/helpers/indexing';
36 import { getDuplications } from '../../../api/components';
37 import { getBranchLikeQuery } from '../../../helpers/branches';
38
39 interface Props {
40   branchLike: T.Branch | T.PullRequest | undefined;
41   highlightedLocationMessage?: { index: number; text: string | undefined };
42   issue: T.Issue;
43   issues: T.Issue[];
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;
50 }
51
52 interface State {
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 };
59   loading: boolean;
60 }
61
62 export default class CrossComponentSourceViewerWrapper extends React.PureComponent<Props, State> {
63   mounted = false;
64   state: State = {
65     components: {},
66     duplicationsByLine: {},
67     loading: true
68   };
69
70   componentDidMount() {
71     this.mounted = true;
72     this.fetchIssueFlowSnippets(this.props.issue.key);
73   }
74
75   componentWillReceiveProps(newProps: Props) {
76     if (newProps.issue.key !== this.props.issue.key) {
77       this.fetchIssueFlowSnippets(newProps.issue.key);
78     }
79   }
80
81   componentWillUnmount() {
82     this.mounted = false;
83   }
84
85   fetchDuplications = (component: string, line: T.SourceLine) => {
86     getDuplications({
87       key: component,
88       ...getBranchLikeQuery(this.props.branchLike)
89     }).then(
90       r => {
91         if (this.mounted) {
92           this.setState(state => ({
93             duplicatedFiles: r.files,
94             duplications: r.duplications,
95             duplicationsByLine: duplicationsByLine(r.duplications),
96             linePopup:
97               r.duplications.length === 1
98                 ? { component, index: 0, line: line.line, name: 'duplications' }
99                 : state.linePopup
100           }));
101         }
102       },
103       () => {}
104     );
105   };
106
107   fetchIssueFlowSnippets(issueKey: string) {
108     this.setState({ loading: true });
109     getIssueFlowSnippets(issueKey).then(
110       components => {
111         if (this.mounted) {
112           this.setState({
113             components,
114             issuePopup: undefined,
115             linePopup: undefined,
116             loading: false
117           });
118           if (this.props.onLoaded) {
119             this.props.onLoaded();
120           }
121         }
122       },
123       () => {
124         if (this.mounted) {
125           this.setState({ loading: false });
126         }
127       }
128     );
129   }
130
131   handleIssuePopupToggle = (issue: string, popupName: string, open?: boolean) => {
132     this.setState((state: State) => {
133       const samePopup =
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 };
139       }
140       return null;
141     });
142   };
143
144   handleLinePopupToggle = ({
145     component,
146     index,
147     line,
148     name,
149     open
150   }: T.LinePopup & { component: string }) => {
151     this.setState((state: State) => {
152       const samePopup =
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 };
162       }
163       return null;
164     });
165   };
166
167   handleCloseLinePopup = () => {
168     this.setState({ linePopup: undefined });
169   };
170
171   renderDuplicationPopup = (component: T.SourceViewerFile, index: number, line: number) => {
172     const { duplicatedFiles, duplications } = this.state;
173
174     if (!component || !duplicatedFiles) {
175       return null;
176     }
177
178     const blocks = getDuplicationBlocksForIndex(duplications, index);
179
180     return (
181       <WorkspaceContext.Consumer>
182         {({ openComponent }) => (
183           <DuplicationPopup
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}
191           />
192         )}
193       </WorkspaceContext.Consumer>
194     );
195   };
196
197   render() {
198     const { loading } = this.state;
199
200     if (loading) {
201       return (
202         <div>
203           <DeferredSpinner />
204         </div>
205       );
206     }
207
208     const { components, duplications, duplicationsByLine, linePopup } = this.state;
209     const issuesByComponent = issuesByComponentAndLine(this.props.issues);
210     const locationsByComponent = groupLocationsByComponent(this.props.locations, components);
211
212     return (
213       <div>
214         {locationsByComponent.map((snippetGroup, i) => {
215           let componentProps = {};
216           if (linePopup && snippetGroup.component.key === linePopup.component) {
217             componentProps = {
218               duplications,
219               duplicationsByLine,
220               linePopup: { index: linePopup.index, line: linePopup.line, name: linePopup.name }
221             };
222           }
223           return (
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}
241               {...componentProps}
242             />
243           );
244         })}
245       </div>
246     );
247   }
248 }