]> source.dussan.org Git - sonarqube.git/blob
8ba7492f05ffb237550b08fe8cfe23f4fe9cd590
[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 { FormattedMessage } from 'react-intl';
22 import { Link } from 'react-router';
23 import { Button, SubmitButton } from 'sonar-ui-common/components/controls/buttons';
24 import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip';
25 import Select from 'sonar-ui-common/components/controls/Select';
26 import AlertSuccessIcon from 'sonar-ui-common/components/icons/AlertSuccessIcon';
27 import { Alert } from 'sonar-ui-common/components/ui/Alert';
28 import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
29 import { translate } from 'sonar-ui-common/helpers/l10n';
30 import { AlmKeys, AlmSettingsInstance, ProjectAlmBinding } from '../../../../types/alm-settings';
31
32 export interface PRDecorationBindingRendererProps {
33   formData: ProjectAlmBinding;
34   instances: AlmSettingsInstance[];
35   isValid: boolean;
36   loading: boolean;
37   onFieldChange: (id: keyof ProjectAlmBinding, value: string) => void;
38   onReset: () => void;
39   onSubmit: () => void;
40   originalData?: ProjectAlmBinding;
41   saving: boolean;
42   success: boolean;
43 }
44
45 function optionRenderer(instance: AlmSettingsInstance) {
46   return instance.url ? (
47     <>
48       <span>{instance.key} — </span>
49       <span className="text-muted">{instance.url}</span>
50     </>
51   ) : (
52     <span>{instance.key}</span>
53   );
54 }
55
56 function renderField(props: {
57   help?: boolean;
58   helpParams?: { [key: string]: string | JSX.Element };
59   id: string;
60   onFieldChange: (id: keyof ProjectAlmBinding, value: string) => void;
61   optional?: boolean;
62   propKey: keyof ProjectAlmBinding;
63   value: string;
64 }) {
65   const { help, helpParams, id, propKey, optional, value, onFieldChange } = props;
66   return (
67     <div className="form-field">
68       <label className="display-flex-center" htmlFor={id}>
69         {translate('settings.pr_decoration.binding.form', id)}
70         {!optional && <em className="mandatory">*</em>}
71         {help && (
72           <HelpTooltip
73             className="spacer-left"
74             overlay={
75               <FormattedMessage
76                 defaultMessage={translate('settings.pr_decoration.binding.form', id, 'help')}
77                 id={`settings.pr_decoration.binding.form.${id}.help`}
78                 values={helpParams}
79               />
80             }
81             placement="right"
82           />
83         )}
84       </label>
85       <input
86         className="input-super-large"
87         id={id}
88         maxLength={256}
89         name={id}
90         onChange={e => onFieldChange(propKey, e.currentTarget.value)}
91         type="text"
92         value={value}
93       />
94     </div>
95   );
96 }
97
98 function isDataSame(
99   { key, repository = '', slug = '' }: ProjectAlmBinding,
100   { key: oKey = '', repository: oRepository = '', slug: oSlug = '' }: ProjectAlmBinding
101 ) {
102   return key === oKey && repository === oRepository && slug === oSlug;
103 }
104
105 export default function PRDecorationBindingRenderer(props: PRDecorationBindingRendererProps) {
106   const {
107     formData: { key, repository, slug },
108     instances,
109     isValid,
110     loading,
111     originalData,
112     saving,
113     success
114   } = props;
115
116   if (loading) {
117     return <DeferredSpinner />;
118   }
119
120   if (instances.length < 1) {
121     return (
122       <div>
123         <Alert className="spacer-top huge-spacer-bottom" variant="info">
124           <FormattedMessage
125             defaultMessage={translate('settings.pr_decoration.binding.no_bindings')}
126             id="settings.pr_decoration.binding.no_bindings"
127             values={{
128               link: (
129                 <Link to="/documentation/analysis/pull-request/#pr-decoration">
130                   {translate('learn_more')}
131                 </Link>
132               )
133             }}
134           />
135         </Alert>
136       </div>
137     );
138   }
139
140   const selected = key && instances.find(i => i.key === key);
141   const alm = selected && selected.alm;
142
143   const isChanged = !isDataSame({ key, repository, slug }, originalData || { key: '' });
144
145   return (
146     <div>
147       <header className="page-header">
148         <h1 className="page-title">{translate('settings.pr_decoration.binding.title')}</h1>
149       </header>
150
151       <div className="markdown small spacer-top big-spacer-bottom">
152         {translate('settings.pr_decoration.binding.description')}
153       </div>
154
155       <form
156         onSubmit={(event: React.SyntheticEvent<HTMLFormElement>) => {
157           event.preventDefault();
158           props.onSubmit();
159         }}>
160         <div className="form-field">
161           <label htmlFor="name">
162             {translate('settings.pr_decoration.binding.form.name')}
163             <em className="mandatory spacer-right">*</em>
164           </label>
165           <Select
166             autosize={true}
167             className="abs-width-400"
168             clearable={false}
169             id="name"
170             menuContainerStyle={{
171               maxWidth: '210%' /* Allow double the width of the select */,
172               width: 'auto'
173             }}
174             onChange={(instance: AlmSettingsInstance) => props.onFieldChange('key', instance.key)}
175             optionRenderer={optionRenderer}
176             options={instances}
177             searchable={false}
178             value={key}
179             valueKey="key"
180             valueRenderer={optionRenderer}
181           />
182         </div>
183
184         {alm === AlmKeys.Bitbucket && (
185           <>
186             {renderField({
187               help: true,
188               helpParams: {
189                 example: (
190                   <>
191                     {'.../projects/'}
192                     <strong>{'{KEY}'}</strong>
193                     {'/repos/{SLUG}/browse'}
194                   </>
195                 )
196               },
197               id: 'bitbucket.repository',
198               onFieldChange: props.onFieldChange,
199               propKey: 'repository',
200               value: repository || ''
201             })}
202             {renderField({
203               help: true,
204               helpParams: {
205                 example: (
206                   <>
207                     {'.../projects/{KEY}/repos/'}
208                     <strong>{'{SLUG}'}</strong>
209                     {'/browse'}
210                   </>
211                 )
212               },
213               id: 'bitbucket.slug',
214               onFieldChange: props.onFieldChange,
215               propKey: 'slug',
216               value: slug || ''
217             })}
218           </>
219         )}
220
221         {alm === AlmKeys.GitHub &&
222           renderField({
223             help: true,
224             helpParams: { example: 'SonarSource/sonarqube' },
225             id: 'github.repository',
226             onFieldChange: props.onFieldChange,
227             propKey: 'repository',
228             value: repository || ''
229           })}
230
231         {alm === AlmKeys.GitLab &&
232           renderField({
233             help: true,
234             id: 'gitlab.repository',
235             onFieldChange: props.onFieldChange,
236             optional: true,
237             propKey: 'repository',
238             value: repository || ''
239           })}
240
241         <div className="display-flex-center">
242           <DeferredSpinner className="spacer-right" loading={saving} />
243           {isChanged && (
244             <SubmitButton className="spacer-right button-success" disabled={saving || !isValid}>
245               <span data-test="project-settings__alm-save">{translate('save')}</span>
246             </SubmitButton>
247           )}
248           {originalData && (
249             <Button className="spacer-right" onClick={props.onReset}>
250               <span data-test="project-settings__alm-reset">{translate('reset_verb')}</span>
251             </Button>
252           )}
253           {!saving && success && (
254             <span className="text-success">
255               <AlertSuccessIcon className="spacer-right" />
256               {translate('settings.state.saved')}
257             </span>
258           )}
259         </div>
260       </form>
261     </div>
262   );
263 }