3 * Copyright (C) 2009-2022 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
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';
35 ProjectAlmBindingConfigurationErrors,
36 ProjectAlmBindingConfigurationErrorScope,
37 ProjectAlmBindingResponse,
38 } from '../../../../types/alm-settings';
39 import { ALM_INTEGRATION_CATEGORY } from '../../constants';
40 import AlmSpecificForm from './AlmSpecificForm';
42 export interface PRDecorationBindingRendererProps {
43 formData: Omit<ProjectAlmBindingResponse, 'alm'>;
44 instances: AlmSettingsInstance[];
46 isConfigured: boolean;
49 onFieldChange: (id: keyof ProjectAlmBindingResponse, value: string | boolean) => void;
53 successfullyUpdated: boolean;
54 onCheckConfiguration: () => void;
55 checkingConfiguration: boolean;
56 configurationErrors?: ProjectAlmBindingConfigurationErrors;
60 function optionRenderer(props: OptionProps<AlmSettingsInstance, false>) {
61 return <components.Option {...props}>{customOptions(props.data)}</components.Option>;
64 function singleValueRenderer(props: SingleValueProps<AlmSettingsInstance>) {
65 return <components.SingleValue {...props}>{customOptions(props.data)}</components.SingleValue>;
68 function customOptions(instance: AlmSettingsInstance) {
69 return instance.url ? (
71 <span>{instance.key} — </span>
72 <span className="text-muted">{instance.url}</span>
75 <span>{instance.key}</span>
79 export default function PRDecorationBindingRenderer(props: PRDecorationBindingRendererProps) {
89 checkingConfiguration,
95 return <DeferredSpinner />;
98 if (instances.length < 1) {
101 <Alert className="spacer-top huge-spacer-bottom" variant="info">
104 defaultMessage={translate('settings.pr_decoration.binding.no_bindings.admin')}
105 id="settings.pr_decoration.binding.no_bindings.admin"
108 <Link to={getGlobalSettingsUrl(ALM_INTEGRATION_CATEGORY)}>
109 {translate('settings.pr_decoration.binding.no_bindings.link')}
115 translate('settings.pr_decoration.binding.no_bindings')
122 const selected = formData.key && instances.find((i) => i.key === formData.key);
123 const alm = selected && selected.alm;
127 <header className="page-header">
128 <h1 className="page-title">{translate('settings.pr_decoration.binding.title')}</h1>
131 <div className="markdown small spacer-top big-spacer-bottom">
132 {translate('settings.pr_decoration.binding.description')}
136 onSubmit={(event: React.SyntheticEvent<HTMLFormElement>) => {
137 event.preventDefault();
141 <MandatoryFieldsExplanation className="form-field" />
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" />
149 <div className="markdown small spacer-top">
150 {translate('settings.pr_decoration.binding.form.name.help')}
153 <div className="settings-definition-right">
156 className="abs-width-400 big-spacer-top it__configuration-name-select"
160 onChange={(instance: AlmSettingsInstance) => props.onFieldChange('key', instance.key)}
162 Option: optionRenderer,
163 SingleValue: singleValueRenderer,
165 value={instances.filter((instance) => instance.key === formData.key)}
173 instances={instances}
175 onFieldChange={props.onFieldChange}
179 <div className="display-flex-center big-spacer-top action-section">
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} />
186 {!updating && successfullyUpdated && (
187 <span className="text-success spacer-right">
188 <AlertSuccessIcon className="spacer-right" />
189 {translate('settings.state.saved')}
194 <Button className="spacer-right" onClick={props.onReset}>
195 <span data-test="project-settings__alm-reset">{translate('reset_verb')}</span>
198 <Button onClick={props.onCheckConfiguration} disabled={checkingConfiguration}>
199 {translate('settings.pr_decoration.binding.check_configuration')}
200 <DeferredSpinner className="spacer-left" loading={checkingConfiguration} />
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')}
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>
217 {configurationErrors.scope === ProjectAlmBindingConfigurationErrorScope.Global && (
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'
227 <Link to={getGlobalSettingsUrl(ALM_INTEGRATION_CATEGORY, { alm })}>
229 'settings.pr_decoration.binding.check_configuration.failure.check_global_settings.link'
236 translate('settings.pr_decoration.binding.check_configuration.contact_admin')
242 {isConfigured && !isChanged && !checkingConfiguration && !configurationErrors && (
243 <Alert variant="success" display="inline" className="big-spacer-top">
244 {translate('settings.pr_decoration.binding.check_configuration.success')}