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