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