]> source.dussan.org Git - sonarqube.git/blob
5589c1e952718ee682c84aff430f207e848cf759
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2022 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 { components, OptionProps, SingleValueProps } from 'react-select';
23 import Link from '../../../../components/common/Link';
24 import { Button, SubmitButton } from '../../../../components/controls/buttons';
25 import Select from '../../../../components/controls/Select';
26 import AlertSuccessIcon from '../../../../components/icons/AlertSuccessIcon';
27 import { Alert } from '../../../../components/ui/Alert';
28 import DeferredSpinner from '../../../../components/ui/DeferredSpinner';
29 import MandatoryFieldMarker from '../../../../components/ui/MandatoryFieldMarker';
30 import MandatoryFieldsExplanation from '../../../../components/ui/MandatoryFieldsExplanation';
31 import { translate } from '../../../../helpers/l10n';
32 import { getGlobalSettingsUrl } from '../../../../helpers/urls';
33 import {
34   AlmSettingsInstance,
35   ProjectAlmBindingConfigurationErrors,
36   ProjectAlmBindingConfigurationErrorScope,
37   ProjectAlmBindingResponse,
38 } from '../../../../types/alm-settings';
39 import { ALM_INTEGRATION_CATEGORY } from '../../constants';
40 import AlmSpecificForm from './AlmSpecificForm';
41
42 export interface PRDecorationBindingRendererProps {
43   formData: Omit<ProjectAlmBindingResponse, 'alm'>;
44   instances: AlmSettingsInstance[];
45   isChanged: boolean;
46   isConfigured: boolean;
47   isValid: boolean;
48   loading: boolean;
49   onFieldChange: (id: keyof ProjectAlmBindingResponse, value: string | boolean) => void;
50   onReset: () => void;
51   onSubmit: () => void;
52   updating: boolean;
53   successfullyUpdated: boolean;
54   onCheckConfiguration: () => void;
55   checkingConfiguration: boolean;
56   configurationErrors?: ProjectAlmBindingConfigurationErrors;
57   isSysAdmin: boolean;
58 }
59
60 function optionRenderer(props: OptionProps<AlmSettingsInstance, false>) {
61   return <components.Option {...props}>{customOptions(props.data)}</components.Option>;
62 }
63
64 function singleValueRenderer(props: SingleValueProps<AlmSettingsInstance>) {
65   return <components.SingleValue {...props}>{customOptions(props.data)}</components.SingleValue>;
66 }
67
68 function customOptions(instance: AlmSettingsInstance) {
69   return instance.url ? (
70     <>
71       <span>{instance.key} — </span>
72       <span className="text-muted">{instance.url}</span>
73     </>
74   ) : (
75     <span>{instance.key}</span>
76   );
77 }
78
79 export default function PRDecorationBindingRenderer(props: PRDecorationBindingRendererProps) {
80   const {
81     formData,
82     instances,
83     isChanged,
84     isConfigured,
85     isValid,
86     loading,
87     updating,
88     successfullyUpdated,
89     checkingConfiguration,
90     configurationErrors,
91     isSysAdmin,
92   } = props;
93
94   if (loading) {
95     return <DeferredSpinner />;
96   }
97
98   if (instances.length < 1) {
99     return (
100       <div>
101         <Alert className="spacer-top huge-spacer-bottom" variant="info">
102           {isSysAdmin ? (
103             <FormattedMessage
104               defaultMessage={translate('settings.pr_decoration.binding.no_bindings.admin')}
105               id="settings.pr_decoration.binding.no_bindings.admin"
106               values={{
107                 link: (
108                   <Link to={getGlobalSettingsUrl(ALM_INTEGRATION_CATEGORY)}>
109                     {translate('settings.pr_decoration.binding.no_bindings.link')}
110                   </Link>
111                 ),
112               }}
113             />
114           ) : (
115             translate('settings.pr_decoration.binding.no_bindings')
116           )}
117         </Alert>
118       </div>
119     );
120   }
121
122   const selected = formData.key && instances.find((i) => i.key === formData.key);
123   const alm = selected && selected.alm;
124
125   return (
126     <div>
127       <header className="page-header">
128         <h1 className="page-title">{translate('settings.pr_decoration.binding.title')}</h1>
129       </header>
130
131       <div className="markdown small spacer-top big-spacer-bottom">
132         {translate('settings.pr_decoration.binding.description')}
133       </div>
134
135       <form
136         onSubmit={(event: React.SyntheticEvent<HTMLFormElement>) => {
137           event.preventDefault();
138           props.onSubmit();
139         }}
140       >
141         <MandatoryFieldsExplanation className="form-field" />
142
143         <div className="settings-definition big-spacer-bottom">
144           <div className="settings-definition-left">
145             <label className="h3" htmlFor="name">
146               {translate('settings.pr_decoration.binding.form.name')}
147               <MandatoryFieldMarker className="spacer-right" />
148             </label>
149             <div className="markdown small spacer-top">
150               {translate('settings.pr_decoration.binding.form.name.help')}
151             </div>
152           </div>
153           <div className="settings-definition-right">
154             <Select
155               inputId="name"
156               className="abs-width-400 big-spacer-top it__configuration-name-select"
157               isClearable={false}
158               isSearchable={false}
159               options={instances}
160               onChange={(instance: AlmSettingsInstance) => props.onFieldChange('key', instance.key)}
161               components={{
162                 Option: optionRenderer,
163                 SingleValue: singleValueRenderer,
164               }}
165               value={instances.filter((instance) => instance.key === formData.key)}
166             />
167           </div>
168         </div>
169
170         {alm && (
171           <AlmSpecificForm
172             alm={alm}
173             instances={instances}
174             formData={formData}
175             onFieldChange={props.onFieldChange}
176           />
177         )}
178
179         <div className="display-flex-center big-spacer-top action-section">
180           {isChanged && (
181             <SubmitButton className="spacer-right button-success" disabled={updating || !isValid}>
182               <span data-test="project-settings__alm-save">{translate('save')}</span>
183               <DeferredSpinner className="spacer-left" loading={updating} />
184             </SubmitButton>
185           )}
186           {!updating && successfullyUpdated && (
187             <span className="text-success spacer-right">
188               <AlertSuccessIcon className="spacer-right" />
189               {translate('settings.state.saved')}
190             </span>
191           )}
192           {isConfigured && (
193             <>
194               <Button className="spacer-right" onClick={props.onReset}>
195                 <span data-test="project-settings__alm-reset">{translate('reset_verb')}</span>
196               </Button>
197               {!isChanged && (
198                 <Button onClick={props.onCheckConfiguration} disabled={checkingConfiguration}>
199                   {translate('settings.pr_decoration.binding.check_configuration')}
200                   <DeferredSpinner className="spacer-left" loading={checkingConfiguration} />
201                 </Button>
202               )}
203             </>
204           )}
205         </div>
206         {!checkingConfiguration && configurationErrors?.errors && (
207           <Alert variant="error" display="inline" className="big-spacer-top">
208             <p className="spacer-bottom">
209               {translate('settings.pr_decoration.binding.check_configuration.failure')}
210             </p>
211             <ul className="list-styled">
212               {configurationErrors.errors.map((error, i) => (
213                 // eslint-disable-next-line react/no-array-index-key
214                 <li key={i}>{error.msg}</li>
215               ))}
216             </ul>
217             {configurationErrors.scope === ProjectAlmBindingConfigurationErrorScope.Global && (
218               <p>
219                 {isSysAdmin ? (
220                   <FormattedMessage
221                     id="settings.pr_decoration.binding.check_configuration.failure.check_global_settings"
222                     defaultMessage={translate(
223                       'settings.pr_decoration.binding.check_configuration.failure.check_global_settings'
224                     )}
225                     values={{
226                       link: (
227                         <Link to={getGlobalSettingsUrl(ALM_INTEGRATION_CATEGORY, { alm })}>
228                           {translate(
229                             'settings.pr_decoration.binding.check_configuration.failure.check_global_settings.link'
230                           )}
231                         </Link>
232                       ),
233                     }}
234                   />
235                 ) : (
236                   translate('settings.pr_decoration.binding.check_configuration.contact_admin')
237                 )}
238               </p>
239             )}
240           </Alert>
241         )}
242         {isConfigured && !isChanged && !checkingConfiguration && !configurationErrors && (
243           <Alert variant="success" display="inline" className="big-spacer-top">
244             {translate('settings.pr_decoration.binding.check_configuration.success')}
245           </Alert>
246         )}
247       </form>
248     </div>
249   );
250 }