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