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