]> source.dussan.org Git - sonarqube.git/blob
9d5e2fa471f366dd172302c4a458abeb9dff8c6f
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2020 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 {
22   deleteProjectAlmBinding,
23   getAlmSettings,
24   getProjectAlmBinding,
25   setProjectAzureBinding,
26   setProjectBitbucketBinding,
27   setProjectGithubBinding,
28   setProjectGitlabBinding
29 } from '../../../../api/alm-settings';
30 import throwGlobalError from '../../../../app/utils/throwGlobalError';
31 import {
32   AlmKeys,
33   AlmSettingsInstance,
34   ProjectAlmBindingResponse
35 } from '../../../../types/alm-settings';
36 import PRDecorationBindingRenderer from './PRDecorationBindingRenderer';
37
38 type FormData = T.Omit<ProjectAlmBindingResponse, 'alm'>;
39
40 interface Props {
41   component: T.Component;
42 }
43
44 interface State {
45   formData: FormData;
46   instances: AlmSettingsInstance[];
47   isChanged: boolean;
48   isConfigured: boolean;
49   isValid: boolean;
50   loading: boolean;
51   orignalData?: FormData;
52   saving: boolean;
53   success: boolean;
54 }
55
56 const REQUIRED_FIELDS_BY_ALM: {
57   [almKey in AlmKeys]: Array<keyof T.Omit<FormData, 'key'>>;
58 } = {
59   [AlmKeys.Azure]: [],
60   [AlmKeys.Bitbucket]: ['repository', 'slug'],
61   [AlmKeys.GitHub]: ['repository'],
62   [AlmKeys.GitLab]: []
63 };
64
65 export default class PRDecorationBinding extends React.PureComponent<Props, State> {
66   mounted = false;
67   state: State = {
68     formData: { key: '' },
69     instances: [],
70     isChanged: false,
71     isConfigured: false,
72     isValid: false,
73     loading: true,
74     saving: false,
75     success: false
76   };
77
78   componentDidMount() {
79     this.mounted = true;
80     this.fetchDefinitions();
81   }
82
83   componentWillUnmount() {
84     this.mounted = false;
85   }
86
87   fetchDefinitions = () => {
88     const project = this.props.component.key;
89     return Promise.all([getAlmSettings(project), this.getProjectBinding(project)])
90       .then(([instances, originalData]) => {
91         if (this.mounted) {
92           this.setState(({ formData }) => {
93             const newFormData = originalData || formData;
94             return {
95               formData: newFormData,
96               instances: instances || [],
97               isChanged: false,
98               isConfigured: !!originalData,
99               isValid: this.validateForm(newFormData),
100               loading: false,
101               orignalData: newFormData
102             };
103           });
104         }
105       })
106       .catch(() => {
107         if (this.mounted) {
108           this.setState({ loading: false });
109         }
110       });
111   };
112
113   getProjectBinding(project: string): Promise<ProjectAlmBindingResponse | undefined> {
114     return getProjectAlmBinding(project).catch((response: Response) => {
115       if (response && response.status === 404) {
116         return undefined;
117       }
118       return throwGlobalError(response);
119     });
120   }
121
122   catchError = () => {
123     if (this.mounted) {
124       this.setState({ saving: false });
125     }
126   };
127
128   handleReset = () => {
129     const { component } = this.props;
130     this.setState({ saving: true });
131     deleteProjectAlmBinding(component.key)
132       .then(() => {
133         if (this.mounted) {
134           this.setState({
135             formData: {
136               key: '',
137               repository: '',
138               slug: ''
139             },
140             isChanged: false,
141             isConfigured: false,
142             saving: false,
143             success: true
144           });
145         }
146       })
147       .catch(this.catchError);
148   };
149
150   submitProjectAlmBinding(
151     alm: AlmKeys,
152     key: string,
153     almSpecificFields?: T.Omit<FormData, 'key'>
154   ): Promise<void> {
155     const almSetting = key;
156     const project = this.props.component.key;
157
158     switch (alm) {
159       case AlmKeys.Azure:
160         return setProjectAzureBinding({
161           almSetting,
162           project
163         });
164       case AlmKeys.Bitbucket: {
165         if (!almSpecificFields) {
166           return Promise.reject();
167         }
168         const { repository = '', slug = '' } = almSpecificFields;
169         return setProjectBitbucketBinding({
170           almSetting,
171           project,
172           repository,
173           slug
174         });
175       }
176       case AlmKeys.GitHub: {
177         const repository = almSpecificFields?.repository;
178         // By default it must remain true.
179         const summaryCommentEnabled =
180           almSpecificFields?.summaryCommentEnabled === undefined
181             ? true
182             : almSpecificFields?.summaryCommentEnabled;
183         if (!repository) {
184           return Promise.reject();
185         }
186         return setProjectGithubBinding({
187           almSetting,
188           project,
189           repository,
190           summaryCommentEnabled
191         });
192       }
193
194       case AlmKeys.GitLab: {
195         const repository = almSpecificFields && almSpecificFields.repository;
196         return setProjectGitlabBinding({
197           almSetting,
198           project,
199           repository
200         });
201       }
202
203       default:
204         return Promise.reject();
205     }
206   }
207
208   handleSubmit = () => {
209     this.setState({ saving: true });
210     const {
211       formData: { key, ...additionalFields },
212       instances
213     } = this.state;
214
215     const selected = instances.find(i => i.key === key);
216     if (!key || !selected) {
217       return;
218     }
219
220     this.submitProjectAlmBinding(selected.alm, key, additionalFields)
221       .then(() => {
222         if (this.mounted) {
223           this.setState({
224             saving: false,
225             success: true
226           });
227         }
228       })
229       .then(this.fetchDefinitions)
230       .catch(this.catchError);
231   };
232
233   isDataSame(
234     { key, repository = '', slug = '', summaryCommentEnabled = false }: FormData,
235     {
236       key: oKey = '',
237       repository: oRepository = '',
238       slug: oSlug = '',
239       summaryCommentEnabled: osummaryCommentEnabled = false
240     }: FormData
241   ) {
242     return (
243       key === oKey &&
244       repository === oRepository &&
245       slug === oSlug &&
246       summaryCommentEnabled === osummaryCommentEnabled
247     );
248   }
249
250   handleFieldChange = (id: keyof ProjectAlmBindingResponse, value: string | boolean) => {
251     this.setState(({ formData, orignalData }) => {
252       const newFormData = {
253         ...formData,
254         [id]: value
255       };
256       return {
257         formData: newFormData,
258         isValid: this.validateForm(newFormData),
259         isChanged: !this.isDataSame(newFormData, orignalData || { key: '' }),
260         success: false
261       };
262     });
263   };
264
265   validateForm = ({ key, ...additionalFields }: State['formData']) => {
266     const { instances } = this.state;
267     const selected = instances.find(i => i.key === key);
268     if (!key || !selected) {
269       return false;
270     }
271     return REQUIRED_FIELDS_BY_ALM[selected.alm].reduce(
272       (result: boolean, field) => result && Boolean(additionalFields[field]),
273       true
274     );
275   };
276
277   render() {
278     return (
279       <PRDecorationBindingRenderer
280         onFieldChange={this.handleFieldChange}
281         onReset={this.handleReset}
282         onSubmit={this.handleSubmit}
283         {...this.state}
284       />
285     );
286   }
287 }