]> source.dussan.org Git - sonarqube.git/blob
9a833ae0e0102b464023953421f4adc03c63dcfc
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2023 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 withAvailableFeatures, {
23   WithAvailableFeaturesProps,
24 } from '../../../../app/components/available-features/withAvailableFeatures';
25 import DocLink from '../../../../components/common/DocLink';
26 import Toggle from '../../../../components/controls/Toggle';
27 import { Alert } from '../../../../components/ui/Alert';
28 import MandatoryFieldMarker from '../../../../components/ui/MandatoryFieldMarker';
29 import { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants';
30 import { translate } from '../../../../helpers/l10n';
31 import { convertGithubApiUrlToLink, stripTrailingSlash } from '../../../../helpers/urls';
32 import {
33   AlmKeys,
34   AlmSettingsInstance,
35   ProjectAlmBindingResponse,
36 } from '../../../../types/alm-settings';
37 import { Feature } from '../../../../types/features';
38 import { Dict } from '../../../../types/types';
39
40 export interface AlmSpecificFormProps extends WithAvailableFeaturesProps {
41   alm: AlmKeys;
42   instances: AlmSettingsInstance[];
43   formData: Omit<ProjectAlmBindingResponse, 'alm'>;
44   onFieldChange: (id: keyof ProjectAlmBindingResponse, value: string | boolean) => void;
45 }
46
47 interface LabelProps {
48   id: string;
49   optional?: boolean;
50 }
51
52 interface CommonFieldProps extends LabelProps {
53   help?: boolean;
54   helpParams?: Dict<string | JSX.Element>;
55   helpExample?: JSX.Element;
56   onFieldChange: (id: keyof ProjectAlmBindingResponse, value: string | boolean) => void;
57   propKey: keyof ProjectAlmBindingResponse;
58 }
59
60 function renderFieldWrapper(
61   label: React.ReactNode,
62   input: React.ReactNode,
63   help?: React.ReactNode
64 ) {
65   return (
66     <div className="settings-definition">
67       <div className="settings-definition-left">
68         {label}
69         {help && <div className="markdown small spacer-top">{help}</div>}
70       </div>
71       <div className="settings-definition-right padded-top">{input}</div>
72     </div>
73   );
74 }
75
76 function renderHelp({ help, helpExample, helpParams, id }: CommonFieldProps) {
77   return (
78     help && (
79       <>
80         <FormattedMessage
81           defaultMessage={translate('settings.pr_decoration.binding.form', id, 'help')}
82           id={`settings.pr_decoration.binding.form.${id}.help`}
83           values={helpParams}
84         />
85         {helpExample && (
86           <div className="spacer-top nowrap">
87             {translate('example')}: <em>{helpExample}</em>
88           </div>
89         )}
90       </>
91     )
92   );
93 }
94
95 function renderLabel(props: LabelProps) {
96   const { optional, id } = props;
97   return (
98     <label className="h3" htmlFor={id}>
99       {translate('settings.pr_decoration.binding.form', id)}
100       {!optional && <MandatoryFieldMarker />}
101     </label>
102   );
103 }
104
105 function renderBooleanField(
106   props: Omit<CommonFieldProps, 'optional'> & {
107     value: boolean;
108     inputExtra?: React.ReactNode;
109   }
110 ) {
111   const { id, value, onFieldChange, propKey, inputExtra } = props;
112   return renderFieldWrapper(
113     renderLabel({ ...props, optional: true }),
114     <div className="display-flex-center big-spacer-top">
115       <div className="display-inline-block text-top">
116         <Toggle name={id} onChange={(v) => onFieldChange(propKey, v)} value={value} />
117         {value == null && <span className="spacer-left note">{translate('settings.not_set')}</span>}
118       </div>
119       {inputExtra}
120     </div>,
121     renderHelp(props)
122   );
123 }
124
125 function renderField(
126   props: CommonFieldProps & {
127     value: string;
128   }
129 ) {
130   const { id, propKey, value, onFieldChange } = props;
131   return renderFieldWrapper(
132     renderLabel(props),
133     <input
134       className="input-super-large big-spacer-top"
135       id={id}
136       maxLength={256}
137       name={id}
138       onChange={(e) => onFieldChange(propKey, e.currentTarget.value)}
139       type="text"
140       value={value}
141     />,
142     renderHelp(props)
143   );
144 }
145
146 export function AlmSpecificForm(props: AlmSpecificFormProps) {
147   const {
148     alm,
149     instances,
150     formData: { repository, slug, summaryCommentEnabled, monorepo },
151   } = props;
152
153   let formFields: JSX.Element;
154   const instance = instances.find((i) => i.alm === alm);
155
156   switch (alm) {
157     case AlmKeys.Azure:
158       formFields = (
159         <>
160           {renderField({
161             help: true,
162             helpExample: <strong>My Project</strong>,
163             id: 'azure.project',
164             onFieldChange: props.onFieldChange,
165             propKey: 'slug',
166             value: slug || '',
167           })}
168           {renderField({
169             help: true,
170             helpExample: <strong>My Repository</strong>,
171             id: 'azure.repository',
172             onFieldChange: props.onFieldChange,
173             propKey: 'repository',
174             value: repository || '',
175           })}
176         </>
177       );
178       break;
179     case AlmKeys.BitbucketServer:
180       formFields = (
181         <>
182           {renderField({
183             help: true,
184             helpExample: (
185               <>
186                 {instance?.url
187                   ? `${stripTrailingSlash(instance.url)}/projects/`
188                   : 'https://bb.company.com/projects/'}
189                 <strong>{'MY_PROJECT_KEY'}</strong>
190                 {'/repos/my-repository-slug/browse'}
191               </>
192             ),
193             id: 'bitbucket.repository',
194             onFieldChange: props.onFieldChange,
195             propKey: 'repository',
196             value: repository || '',
197           })}
198           {renderField({
199             help: true,
200             helpExample: (
201               <>
202                 {instance?.url
203                   ? `${stripTrailingSlash(instance.url)}/projects/MY_PROJECT_KEY/repos/`
204                   : 'https://bb.company.com/projects/MY_PROJECT_KEY/repos/'}
205                 <strong>{'my-repository-slug'}</strong>
206                 {'/browse'}
207               </>
208             ),
209             id: 'bitbucket.slug',
210             onFieldChange: props.onFieldChange,
211             propKey: 'slug',
212             value: slug || '',
213           })}
214         </>
215       );
216       break;
217     case AlmKeys.BitbucketCloud:
218       formFields = (
219         <>
220           {renderField({
221             help: true,
222             helpExample: (
223               <>
224                 {'https://bitbucket.org/my-workspace/'}
225                 <strong>{'my-repository-slug'}</strong>
226               </>
227             ),
228             id: 'bitbucketcloud.repository',
229             onFieldChange: props.onFieldChange,
230             propKey: 'repository',
231             value: repository || '',
232           })}
233         </>
234       );
235       break;
236     case AlmKeys.GitHub:
237       formFields = (
238         <>
239           {renderField({
240             help: true,
241             helpExample: (
242               <>
243                 {instance?.url
244                   ? `${stripTrailingSlash(convertGithubApiUrlToLink(instance.url))}/`
245                   : 'https://github.com/'}
246                 <strong>{'sonarsource/sonarqube'}</strong>
247               </>
248             ),
249             id: 'github.repository',
250             onFieldChange: props.onFieldChange,
251             propKey: 'repository',
252             value: repository || '',
253           })}
254           {renderBooleanField({
255             help: true,
256             id: 'github.summary_comment_setting',
257             onFieldChange: props.onFieldChange,
258             propKey: 'summaryCommentEnabled',
259             value: summaryCommentEnabled === undefined ? true : summaryCommentEnabled,
260           })}
261         </>
262       );
263       break;
264     case AlmKeys.GitLab:
265       formFields = (
266         <>
267           {renderField({
268             help: true,
269             helpExample: <strong>123456</strong>,
270             id: 'gitlab.repository',
271             onFieldChange: props.onFieldChange,
272             propKey: 'repository',
273             value: repository || '',
274           })}
275         </>
276       );
277       break;
278   }
279
280   const monorepoEnabled = props.hasFeature(Feature.MonoRepositoryPullRequestDecoration);
281
282   return (
283     <>
284       {formFields}
285       {monorepoEnabled &&
286         renderBooleanField({
287           help: true,
288           helpParams: {
289             doc_link: (
290               <DocLink to={ALM_DOCUMENTATION_PATHS[alm]}>{translate('learn_more')}</DocLink>
291             ),
292           },
293           id: 'monorepo',
294           onFieldChange: props.onFieldChange,
295           propKey: 'monorepo',
296           value: monorepo,
297           inputExtra: monorepo && (
298             <Alert className="no-margin-bottom spacer-left" variant="warning" display="inline">
299               {translate('settings.pr_decoration.binding.form.monorepo.warning')}
300             </Alert>
301           ),
302         })}
303     </>
304   );
305 }
306
307 export default withAvailableFeatures(AlmSpecificForm);