jest.mocked(getSystemUpgrades).mockImplementation(this.handleGetSystemUpgrades);
jest.mocked(getEmailConfigurations).mockImplementation(this.handleGetEmailConfigurations);
jest.mocked(postEmailConfiguration).mockImplementation(this.handlePostEmailConfiguration);
+ jest.mocked(patchEmailConfiguration).mockImplementation(this.handlePatchEmailConfiguration);
}
handleGetSystemInfo = () => {
title={translate('email_notification.form.oauth_auth.title')}
>
<Note>{translate('email_notification.form.oauth_auth.description')}</Note>
+ <Note>{translate('email_notification.form.oauth_auth.supported')}</Note>
</SelectionCard>
</div>
<BasicSeparator />
onChange={(value) => onChange({ oauthAuthenticationHost: value })}
name={translate('email_notification.form.oauth_authentication_host')}
required
- value={
- configuration.authMethod === AuthMethod.OAuth
- ? configuration.oauthAuthenticationHost
- : ''
- }
+ value={configuration.oauthAuthenticationHost ?? ''}
/>
<BasicSeparator />
<EmailNotificationFormField
description={translate('email_notification.form.oauth_client_id.description')}
+ hasValue={configuration.isOauthClientIdSet}
id={OAUTH_CLIENT_ID}
onChange={(value) => onChange({ oauthClientId: value })}
name={translate('email_notification.form.oauth_client_id')}
required
type="password"
- value={configuration.authMethod === AuthMethod.OAuth ? configuration.oauthClientId : ''}
+ value={configuration.oauthClientId ?? ''}
/>
<BasicSeparator />
<EmailNotificationFormField
description={translate('email_notification.form.oauth_client_secret.description')}
+ hasValue={configuration.isOauthClientSecretSet}
id={OAUTH_CLIENT_SECRET}
onChange={(value) => onChange({ oauthClientSecret: value })}
name={translate('email_notification.form.oauth_client_secret')}
required
+ requiresRevaluation
type="password"
- value={
- configuration.authMethod === AuthMethod.OAuth ? configuration.oauthClientSecret : ''
- }
+ value={configuration.oauthClientSecret ?? ''}
/>
<BasicSeparator />
<EmailNotificationFormField
onChange={(value) => onChange({ oauthTenant: value })}
name={translate('email_notification.form.oauth_tenant')}
required
- value={configuration.authMethod === AuthMethod.OAuth ? configuration.oauthTenant : ''}
+ value={configuration.oauthTenant ?? ''}
/>
</>
) : (
<EmailNotificationFormField
description={translate('email_notification.form.basic_password.description')}
id={BASIC_PASSWORD}
+ hasValue={configuration.isBasicPasswordSet}
onChange={(value) => onChange({ basicPassword: value })}
name={translate('email_notification.form.basic_password')}
required
+ requiresRevaluation
type="password"
- value={configuration.authMethod === AuthMethod.Basic ? configuration.basicPassword : ''}
+ value={configuration.basicPassword ?? ''}
/>
)}
<BasicSeparator />
import { FormattedMessage } from 'react-intl';
import { useGetEmailConfiguration } from '../../../../queries/system';
import EmailNotificationConfiguration from './EmailNotificationConfiguration';
+import EmailNotificationOverview from './EmailNotificationOverview';
export default function EmailNotification() {
+ const [isEditing, setIsEditing] = React.useState(false);
const { data: configuration, isLoading } = useGetEmailConfiguration();
return (
</SubTitle>
<FormattedMessage id="email_notification.description" />
<Spinner isLoading={isLoading}>
- <EmailNotificationConfiguration emailConfiguration={configuration ?? null} />
+ {configuration == null || isEditing ? (
+ <EmailNotificationConfiguration
+ emailConfiguration={configuration ?? null}
+ onCancel={() => setIsEditing(false)}
+ onSubmitted={() => setIsEditing(false)}
+ />
+ ) : (
+ <EmailNotificationOverview
+ onEditClicked={() => setIsEditing(true)}
+ emailConfiguration={configuration}
+ />
+ )}
</Spinner>
</div>
);
*/
import { Button, ButtonVariety } from '@sonarsource/echoes-react';
import { NumberedList, NumberedListItem } from 'design-system/lib';
+import { noop } from 'lodash';
import React, { useCallback, useEffect } from 'react';
import { translate } from '../../../../helpers/l10n';
import {
import { AuthenticationSelector } from './AuthenticationSelector';
import { CommonSMTP } from './CommonSMTP';
import { SenderInformation } from './SenderInformation';
-import { checkEmailConfigurationValidity } from './utils';
+import { checkEmailConfigurationHasChanges } from './utils';
interface Props {
emailConfiguration: EmailConfiguration | null;
+ onCancel: () => void;
+ onSubmitted: () => void;
}
const FORM_ID = 'email-notifications';
};
export default function EmailNotificationConfiguration(props: Readonly<Props>) {
- const { emailConfiguration } = props;
+ const { emailConfiguration, onCancel, onSubmitted } = props;
const [canSave, setCanSave] = React.useState(false);
const [newConfiguration, setNewConfiguration] = React.useState<EmailConfiguration>(
const { mutateAsync: saveEmailConfiguration } = useSaveEmailConfigurationMutation();
const { mutateAsync: updateEmailConfiguration } = useUpdateEmailConfigurationMutation();
+ const hasConfiguration = emailConfiguration !== undefined;
+
const onChange = useCallback(
(newValue: Partial<EmailConfiguration>) => {
const newConfig = {
...newConfiguration,
...newValue,
};
- setCanSave(checkEmailConfigurationValidity(newConfig as EmailConfiguration));
+ setCanSave(
+ checkEmailConfigurationHasChanges(newConfig as EmailConfiguration, emailConfiguration),
+ );
setNewConfiguration(newConfig as EmailConfiguration);
},
- [newConfiguration, setNewConfiguration],
+ [emailConfiguration, newConfiguration],
);
const onSubmit = useCallback(
...authConfiguration,
};
- if (newConfiguration?.id === undefined) {
- saveEmailConfiguration(newEmailConfiguration);
+ if (emailConfiguration?.id === undefined) {
+ saveEmailConfiguration(newEmailConfiguration).then(() => onSubmitted(), noop);
} else {
- newEmailConfiguration.id = newConfiguration.id;
updateEmailConfiguration({
emailConfiguration: newEmailConfiguration,
- id: newEmailConfiguration.id,
- });
+ id: emailConfiguration.id,
+ }).then(() => onSubmitted(), noop);
}
}
},
- [canSave, newConfiguration, saveEmailConfiguration, updateEmailConfiguration],
+ [
+ canSave,
+ emailConfiguration,
+ onSubmitted,
+ newConfiguration,
+ saveEmailConfiguration,
+ updateEmailConfiguration,
+ ],
);
useEffect(() => {
>
{translate('email_notification.form.save_configuration')}
</Button>
+ {hasConfiguration && (
+ <Button className="sw-ml-2" onClick={onCancel} variety={ButtonVariety.Primary}>
+ Cancel
+ </Button>
+ )}
</form>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { InputSize, Select } from '@sonarsource/echoes-react';
+import {
+ ButtonIcon,
+ ButtonVariety,
+ IconDelete,
+ IconEdit,
+ InputSize,
+ Select,
+} from '@sonarsource/echoes-react';
import { FormField, InputField, TextError } from 'design-system/lib';
import { isEmpty, isUndefined } from 'lodash';
-import React from 'react';
+import React, { useEffect } from 'react';
import isEmail from 'validator/lib/isEmail';
import { translate, translateWithParameters } from '../../../../helpers/l10n';
interface Props {
children?: (props: { onChange: (value: string) => void }) => React.ReactNode;
- description: string;
+ description: React.ReactNode;
+ hasValue?: boolean;
id: string;
name: string;
- onChange: (value: string) => void;
+ onChange: (value: string | undefined) => void;
options?: string[];
required?: boolean;
+ requiresRevaluation?: boolean;
type?: InputType;
value: string | undefined;
}
export function EmailNotificationFormField(props: Readonly<Props>) {
- const { description, id, name, options, required, type = 'text', value } = props;
+ const {
+ description,
+ hasValue,
+ id,
+ name,
+ options,
+ required,
+ requiresRevaluation,
+ type = 'text',
+ value,
+ } = props;
const [validationMessage, setValidationMessage] = React.useState<string>();
- const handleCheck = (changedValue?: string) => {
- if (isEmpty(changedValue) && required) {
+ const handleCheck = (changedValue: string | undefined) => {
+ if (changedValue !== undefined && isEmpty(changedValue) && required) {
setValidationMessage(translate('settings.state.value_cant_be_empty_no_default'));
return false;
}
return true;
};
- const onChange = (newValue: string) => {
+ const onChange = (newValue: string | undefined) => {
handleCheck(newValue);
props.onChange(newValue);
};
requiredAriaLabel={translate('field_required')}
>
<div className="sw-row-span-2 sw-grid">
- {type === 'select' ? (
- <SelectInput
- id={id}
- name={name}
- options={options ?? []}
- onChange={onChange}
- required={required}
- value={value}
- />
- ) : (
- <BasicInput
- id={id}
- name={name}
- type={type}
- onChange={onChange}
- required={required}
- value={value}
- />
- )}
+ <EmailInput
+ hasValue={hasValue}
+ id={id}
+ name={name}
+ options={options ?? []}
+ onChange={onChange}
+ required={required}
+ requiresRevaluation={requiresRevaluation}
+ type={type}
+ value={value}
+ />
{hasValidationMessage && (
<TextError
);
}
+function EmailInput(
+ props: Readonly<{
+ hasValue: boolean | undefined;
+ id: string;
+ name: string;
+ onChange: (value: string | undefined) => void;
+ options: string[];
+ required?: boolean;
+ requiresRevaluation?: boolean;
+ type: InputType;
+ value: string | undefined;
+ }>,
+) {
+ const { type } = props;
+ switch (type) {
+ case 'password':
+ return <PasswordInput {...props} />;
+ case 'select':
+ return <SelectInput {...props} />;
+ default:
+ return <BasicInput {...props} />;
+ }
+}
+
function BasicInput(
props: Readonly<{
+ hasValue: boolean | undefined;
id: string;
name: string;
onChange: (value: string) => void;
value: string | undefined;
}>,
) {
- const { id, onChange, name, required, type, value } = props;
+ const { hasValue, id, onChange, name, required, type, value } = props;
return (
<InputField
min={type === 'number' ? 0 : undefined}
name={name}
onChange={(event) => onChange(event.target.value)}
+ placeholder={
+ type === 'password' && hasValue ? translate('email_notification.form.private') : undefined
+ }
required={required}
size="large"
step={type === 'number' ? 1 : undefined}
);
}
+function PasswordInput(
+ props: Readonly<{
+ hasValue: boolean | undefined;
+ id: string;
+ name: string;
+ onChange: (value: string | undefined) => void;
+ required?: boolean;
+ requiresRevaluation?: boolean;
+ value: string | undefined;
+ }>,
+) {
+ const { hasValue, id, onChange, name, required, requiresRevaluation, value } = props;
+ const [isEditing, setIsEditing] = React.useState<boolean>(requiresRevaluation === true);
+
+ useEffect(() => {
+ if (!requiresRevaluation) {
+ setIsEditing(!hasValue);
+ }
+ }, [hasValue, requiresRevaluation]);
+
+ return (
+ <div className="sw-flex">
+ <InputField
+ disabled={!isEditing && !requiresRevaluation}
+ id={id}
+ name={name}
+ onChange={(event) => onChange(event.target.value)}
+ required={isEditing && required}
+ size="large"
+ type="password"
+ value={
+ hasValue && !isEditing && !requiresRevaluation
+ ? translate('email_notification.form.private')
+ : value ?? ''
+ }
+ />
+ {!requiresRevaluation && (
+ <ButtonIcon
+ ariaLabel={isEditing ? translate('reset_verb') : translate('edit')}
+ data-testid={`${name}-${isEditing ? 'reset' : 'edit'}`}
+ className="sw-ml-2"
+ Icon={isEditing ? IconDelete : IconEdit}
+ onClick={() => {
+ if (isEditing) {
+ onChange(undefined);
+ }
+ setIsEditing(!isEditing);
+ }}
+ variety={ButtonVariety.Default}
+ />
+ )}
+ </div>
+ );
+}
+
function SelectInput(
props: Readonly<{
id: string;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { Button, ButtonVariety } from '@sonarsource/echoes-react';
+import { BasicSeparator, CodeSnippet } from 'design-system/lib';
+import React from 'react';
+import { FormattedMessage } from 'react-intl';
+import { translate } from '../../../../helpers/l10n';
+import { AuthMethod, EmailConfiguration } from '../../../../types/system';
+
+interface EmailTestModalProps {
+ emailConfiguration: EmailConfiguration;
+ onEditClicked: () => void;
+}
+
+export default function EmailNotificationOverview(props: Readonly<EmailTestModalProps>) {
+ const { emailConfiguration, onEditClicked } = props;
+
+ return (
+ <>
+ <BasicSeparator className="sw-my-6" />
+ <div className="sw-flex sw-justify-between">
+ <div className="sw-grid sw-gap-4">
+ <span className="sw-body-md-highlight sw-col-span-2">
+ {translate('email_notification.overview.heading')}
+ </span>
+
+ <PublicValue
+ messageKey="email_notification.overview.authentication_type"
+ value={emailConfiguration.authMethod}
+ />
+ <PublicValue
+ messageKey="email_notification.form.username"
+ value={emailConfiguration.username}
+ />
+
+ {emailConfiguration.authMethod === AuthMethod.Basic ? (
+ <PrivateValue messageKey="email_notification.form.basic_password" />
+ ) : (
+ <>
+ <PublicValue
+ messageKey="email_notification.form.oauth_authentication_host"
+ value={emailConfiguration.oauthAuthenticationHost}
+ />
+ <PrivateValue messageKey="email_notification.form.oauth_client_id" />
+ <PrivateValue messageKey="email_notification.form.oauth_client_secret" />
+ <PublicValue
+ messageKey="email_notification.form.oauth_tenant"
+ value={emailConfiguration.oauthTenant}
+ />
+ </>
+ )}
+
+ <PublicValue messageKey="email_notification.form.host" value={emailConfiguration.host} />
+ <PublicValue messageKey="email_notification.form.port" value={emailConfiguration.port} />
+ <PublicValue
+ messageKey="email_notification.form.security_protocol"
+ value={emailConfiguration.securityProtocol}
+ />
+ <PublicValue
+ messageKey="email_notification.form.from_address"
+ value={emailConfiguration.fromAddress}
+ />
+ <PublicValue
+ messageKey="email_notification.form.from_name"
+ value={emailConfiguration.fromName}
+ />
+ <PublicValue
+ messageKey="email_notification.form.subject_prefix"
+ value={emailConfiguration.subjectPrefix}
+ />
+ </div>
+ <Button onClick={onEditClicked} variety={ButtonVariety.DefaultGhost}>
+ {translate('edit')}
+ </Button>
+ </div>
+ </>
+ );
+}
+
+function PublicValue({ messageKey, value }: Readonly<{ messageKey: string; value: string }>) {
+ return (
+ <>
+ <label className="sw-body-sm-highlight">{translate(messageKey)}</label>
+ <div data-testid={`${messageKey}.value`}>
+ <CodeSnippet className="sw-px-1 sw-truncate" isOneLine noCopy snippet={value} />
+ </div>
+ </>
+ );
+}
+
+function PrivateValue({ messageKey }: Readonly<{ messageKey: string }>) {
+ return (
+ <>
+ <label className="sw-body-sm-highlight">{translate(messageKey)}</label>
+ <span data-testid={`${messageKey}.value`}>
+ <FormattedMessage id="email_notification.overview.private" />
+ </span>
+ </>
+ );
+}
import userEvent from '@testing-library/user-event';
import { addGlobalSuccessMessage } from 'design-system/lib';
import React from 'react';
-import { byLabelText, byRole, byText } from '~sonar-aligned/helpers/testSelector';
+import { byLabelText, byRole, byTestId, byText } from '~sonar-aligned/helpers/testSelector';
import SystemServiceMock from '../../../../../api/mocks/SystemServiceMock';
import * as api from '../../../../../api/system';
import { mockEmailConfiguration } from '../../../../../helpers/mocks/system';
name: 'email_notification.form.basic_auth.title email_notification.form.basic_auth.description',
}),
selectorOAuthAuth: byRole('radio', {
- name: 'email_notification.form.oauth_auth.title email_notification.form.oauth_auth.description recommended email_notification.form.oauth_auth.recommended_reason',
+ name: 'email_notification.form.oauth_auth.title email_notification.form.oauth_auth.description email_notification.form.oauth_auth.supported recommended email_notification.form.oauth_auth.recommended_reason',
}),
host: byRole('textbox', {
name: 'email_notification.form.host field_required',
name: 'email_notification.form.oauth_authentication_host field_required',
}),
oauth_client_id: byLabelText('email_notification.form.oauth_client_id*'),
+ oauth_client_id_edit: byTestId('email_notification.form.oauth_client_id-edit'),
+ oauth_client_id_reset: byTestId('email_notification.form.oauth_client_id-reset'),
oauth_client_secret: byLabelText('email_notification.form.oauth_client_secret*'),
oauth_tenant: byRole('textbox', { name: 'email_notification.form.oauth_tenant field_required' }),
save: byRole('button', {
name: 'email_notification.form.save_configuration',
}),
+
+ // overview values
+ overviewHeading: byText('email_notification.overview.heading'),
+ overview_auth_mode: byTestId('email_notification.overview.authentication_type.value'),
+ overview_username: byTestId('email_notification.form.username.value'),
+ overview_basic_password: byTestId('email_notification.form.basic_password.value'),
+ overview_oauth_auth_host: byTestId('email_notification.form.oauth_authentication_host.value'),
+ overview_oauth_client_id: byTestId('email_notification.form.oauth_client_id.value'),
+ overview_oauth_client_secret: byTestId('email_notification.form.oauth_client_secret.value'),
+ overview_oauth_tenant: byTestId('email_notification.form.oauth_tenant.value'),
+ overview_host: byTestId('email_notification.form.host.value'),
+ overview_port: byTestId('email_notification.form.port.value'),
+ overview_security_protocol: byTestId('email_notification.form.security_protocol.value'),
+ overview_from_address: byTestId('email_notification.form.from_address.value'),
+ overview_from_name: byTestId('email_notification.form.from_name.value'),
+ overview_subject_prefix: byTestId('email_notification.form.subject_prefix.value'),
+
+ edit: byRole('button', {
+ name: 'edit',
+ }),
};
describe('Email Basic Configuration', () => {
expect(addGlobalSuccessMessage).toHaveBeenCalledWith(
'email_notification.form.save_configuration.create_success',
);
+
+ expect(await ui.overviewHeading.find()).toBeInTheDocument();
+
+ expect(ui.overview_auth_mode.get()).toHaveTextContent(AuthMethod.Basic);
+ expect(ui.overview_username.get()).toHaveTextContent('username');
+ expect(ui.overview_basic_password.get()).toHaveTextContent(
+ 'email_notification.overview.private',
+ );
+ expect(ui.overview_host.get()).toHaveTextContent('host');
+ expect(ui.overview_port.get()).toHaveTextContent('1234');
+ expect(ui.overview_security_protocol.get()).toHaveTextContent('SSLTLS');
+ expect(ui.overview_from_address.get()).toHaveTextContent('admin@localhost.com');
+ expect(ui.overview_from_name.get()).toHaveTextContent('fromName');
+ expect(ui.overview_subject_prefix.get()).toHaveTextContent('prefix');
+ });
+
+ it('renders the overview after loading configuration', async () => {
+ systemHandler.addEmailConfiguration(
+ mockEmailConfiguration(AuthMethod.Basic, { id: 'email-1' }),
+ );
+
+ renderEmailNotifications();
+
+ expect(await ui.overviewHeading.find()).toBeInTheDocument();
+ expect(ui.overview_auth_mode.get()).toHaveTextContent(AuthMethod.Basic);
+ expect(ui.overview_username.get()).toHaveTextContent('username');
+ expect(ui.overview_basic_password.get()).toHaveTextContent(
+ 'email_notification.overview.private',
+ );
+ expect(ui.overview_host.get()).toHaveTextContent('host');
+ expect(ui.overview_port.get()).toHaveTextContent('port');
+ expect(ui.overview_security_protocol.get()).toHaveTextContent('SSLTLS');
+ expect(ui.overview_from_address.get()).toHaveTextContent('from_address');
+ expect(ui.overview_from_name.get()).toHaveTextContent('from_name');
+ expect(ui.overview_subject_prefix.get()).toHaveTextContent('subject_prefix');
});
it('can edit an existing configuration', async () => {
jest.spyOn(api, 'patchEmailConfiguration');
const user = userEvent.setup();
renderEmailNotifications();
+
+ expect(await ui.overviewHeading.find()).toBeInTheDocument();
+
+ await user.click(ui.edit.get());
+
expect(await ui.editSubheading1.find()).toBeInTheDocument();
expect(ui.save.get()).toBeDisabled();
expect(addGlobalSuccessMessage).toHaveBeenCalledWith(
'email_notification.form.save_configuration.update_success',
);
+
+ expect(await ui.overviewHeading.find()).toBeInTheDocument();
+
+ expect(ui.overview_auth_mode.get()).toHaveTextContent(AuthMethod.Basic);
+ expect(ui.overview_username.get()).toHaveTextContent('username-updated');
+ expect(ui.overview_basic_password.get()).toHaveTextContent(
+ 'email_notification.overview.private',
+ );
+ expect(ui.overview_host.get()).toHaveTextContent('host-updated');
+ expect(ui.overview_port.get()).toHaveTextContent('5678');
+ expect(ui.overview_security_protocol.get()).toHaveTextContent('STARTTLS');
+ expect(ui.overview_from_address.get()).toHaveTextContent('updated@email.com');
+ expect(ui.overview_from_name.get()).toHaveTextContent('from_name-updated');
+ expect(ui.overview_subject_prefix.get()).toHaveTextContent('subject_prefix-updated');
});
});
expect(addGlobalSuccessMessage).toHaveBeenCalledWith(
'email_notification.form.save_configuration.create_success',
);
+
+ expect(await ui.overviewHeading.find()).toBeInTheDocument();
+
+ expect(ui.overview_auth_mode.get()).toHaveTextContent(AuthMethod.OAuth);
+ expect(ui.overview_oauth_auth_host.get()).toHaveTextContent('oauth_auth_host');
+ expect(ui.overview_oauth_client_id.get()).toHaveTextContent(
+ 'email_notification.overview.private',
+ );
+ expect(ui.overview_oauth_client_secret.get()).toHaveTextContent(
+ 'email_notification.overview.private',
+ );
+ expect(ui.overview_oauth_tenant.get()).toHaveTextContent('oauth_tenant');
+ expect(ui.overview_host.get()).toHaveTextContent('host');
+ expect(ui.overview_port.get()).toHaveTextContent('1234');
+ expect(ui.overview_security_protocol.get()).toHaveTextContent('SSLTLS');
+ expect(ui.overview_from_address.get()).toHaveTextContent('admin@localhost.com');
+ expect(ui.overview_from_name.get()).toHaveTextContent('fromName');
+ expect(ui.overview_subject_prefix.get()).toHaveTextContent('prefix');
+ expect(ui.overview_username.get()).toHaveTextContent('username');
+ });
+
+ it('renders the overview after loading configuration', async () => {
+ systemHandler.addEmailConfiguration(
+ mockEmailConfiguration(AuthMethod.OAuth, { id: 'email-2' }),
+ );
+
+ renderEmailNotifications();
+
+ expect(await ui.overviewHeading.find()).toBeInTheDocument();
+ expect(ui.overview_auth_mode.get()).toHaveTextContent(AuthMethod.OAuth);
+ expect(ui.overview_oauth_auth_host.get()).toHaveTextContent('oauth_auth_host');
+ expect(ui.overview_oauth_client_id.get()).toHaveTextContent(
+ 'email_notification.overview.private',
+ );
+ expect(ui.overview_oauth_client_secret.get()).toHaveTextContent(
+ 'email_notification.overview.private',
+ );
+ expect(ui.overview_oauth_tenant.get()).toHaveTextContent('oauth_tenant');
+ expect(ui.overview_host.get()).toHaveTextContent('host');
+ expect(ui.overview_port.get()).toHaveTextContent('port');
+ expect(ui.overview_security_protocol.get()).toHaveTextContent('SSLTLS');
+ expect(ui.overview_from_address.get()).toHaveTextContent('from_address');
+ expect(ui.overview_from_name.get()).toHaveTextContent('from_name');
+ expect(ui.overview_subject_prefix.get()).toHaveTextContent('subject_prefix');
});
- it('can edit the oauth configuration', async () => {
+ it('can edit the configuration', async () => {
systemHandler.addEmailConfiguration(
mockEmailConfiguration(AuthMethod.OAuth, { id: 'email-1' }),
);
jest.spyOn(api, 'patchEmailConfiguration');
const user = userEvent.setup();
renderEmailNotifications();
+
+ expect(await ui.overviewHeading.find()).toBeInTheDocument();
+ await user.click(ui.edit.get());
+
expect(await ui.editSubheading1.find()).toBeInTheDocument();
await user.click(ui.selectorOAuthAuth.get());
expect(ui.save.get()).toBeDisabled();
+
await user.type(ui.oauth_auth_host.get(), '-updated');
+ await user.click(ui.oauth_client_id_edit.get());
await user.type(ui.oauth_client_id.get(), 'updated_id');
await user.type(ui.oauth_client_secret.get(), 'updated_secret');
await user.type(ui.oauth_tenant.get(), '-updated');
expect(addGlobalSuccessMessage).toHaveBeenCalledWith(
'email_notification.form.save_configuration.update_success',
);
+
+ expect(await ui.overviewHeading.find()).toBeInTheDocument();
+
+ expect(ui.overview_auth_mode.get()).toHaveTextContent(AuthMethod.OAuth);
+ expect(ui.overview_oauth_auth_host.get()).toHaveTextContent('oauth_auth_host');
+ expect(ui.overview_oauth_client_id.get()).toHaveTextContent(
+ 'email_notification.overview.private',
+ );
+ expect(ui.overview_oauth_client_secret.get()).toHaveTextContent(
+ 'email_notification.overview.private',
+ );
+ expect(ui.overview_oauth_tenant.get()).toHaveTextContent('oauth_tenant');
+ expect(ui.overview_host.get()).toHaveTextContent('host');
+ expect(ui.overview_port.get()).toHaveTextContent('5678');
+ expect(ui.overview_security_protocol.get()).toHaveTextContent('STARTTLS');
+ expect(ui.overview_from_address.get()).toHaveTextContent('updated@email.com');
+ expect(ui.overview_from_name.get()).toHaveTextContent('from_name-updated');
+ expect(ui.overview_subject_prefix.get()).toHaveTextContent('subject_prefix-updated');
+ expect(ui.overview_username.get()).toHaveTextContent('username-updated');
});
});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+import { mockEmailConfiguration } from '../../../../../helpers/mocks/system';
+import { AuthMethod, EmailConfiguration } from '../../../../../types/system';
+import { checkEmailConfigurationHasChanges } from '../utils';
+
+const validBasicValues = { basicPassword: 'password' };
+const validOAuthValues = { oauthClientId: 'oauthClientId', oauthClientSecret: 'oauthClientSecret' };
+
+describe('checkEmailConfigurationValidity empty configurations', () => {
+ it('should return false if configuration is undefined', () => {
+ expect(checkEmailConfigurationHasChanges(null, null)).toBe(false);
+ });
+
+ it('should return true if configuration is valid and no existing configuration', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, validBasicValues),
+ null,
+ ),
+ ).toBe(true);
+ });
+});
+
+describe('checkEmailConfigurationValidity common props', () => {
+ it('should return false if authMethod is not set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, {
+ ...validBasicValues,
+ authMethod: undefined,
+ }),
+ null,
+ ),
+ ).toBe(false);
+ });
+
+ it('should return true if authMethod is set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, {
+ ...validBasicValues,
+ authMethod: AuthMethod.Basic,
+ }),
+ null,
+ ),
+ ).toBe(true);
+ });
+
+ it('should return false if username is not set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, {
+ ...validBasicValues,
+ username: undefined,
+ }),
+ null,
+ ),
+ ).toBe(false);
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, { ...validBasicValues, username: '' }),
+ null,
+ ),
+ ).toBe(false);
+ });
+
+ it('should return true if username is set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, { ...validBasicValues, username: 'username' }),
+ null,
+ ),
+ ).toBe(true);
+ });
+
+ it('should return false if host is not set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, {
+ ...validBasicValues,
+ host: undefined,
+ }),
+ null,
+ ),
+ ).toBe(false);
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, { ...validBasicValues, host: '' }),
+ null,
+ ),
+ ).toBe(false);
+ });
+
+ it('should return true if host is set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, { ...validBasicValues, host: 'username' }),
+ null,
+ ),
+ ).toBe(true);
+ });
+
+ it('should return false if port is not set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, {
+ ...validBasicValues,
+ port: undefined,
+ }),
+ null,
+ ),
+ ).toBe(false);
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, { ...validBasicValues, port: '' }),
+ null,
+ ),
+ ).toBe(false);
+ });
+
+ it('should return true if port is set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, { ...validBasicValues, port: 'port' }),
+ null,
+ ),
+ ).toBe(true);
+ });
+
+ it('should return false if securityProtocol is not set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, {
+ ...validBasicValues,
+ securityProtocol: undefined,
+ }),
+ null,
+ ),
+ ).toBe(false);
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, {
+ ...validBasicValues,
+ securityProtocol: '',
+ }),
+ null,
+ ),
+ ).toBe(false);
+ });
+
+ it('should return true if securityProtocol is set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, {
+ ...validBasicValues,
+ securityProtocol: 'securityProtocol',
+ }),
+ null,
+ ),
+ ).toBe(true);
+ });
+
+ it('should return false if fromAddress is not set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, {
+ ...validBasicValues,
+ fromAddress: undefined,
+ }),
+ null,
+ ),
+ ).toBe(false);
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, {
+ ...validBasicValues,
+ fromAddress: '',
+ }),
+ null,
+ ),
+ ).toBe(false);
+ });
+
+ it('should return true if fromAddress is set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, {
+ ...validBasicValues,
+ fromAddress: 'fromAddress',
+ }),
+ null,
+ ),
+ ).toBe(true);
+ });
+
+ it('should return false if fromName is not set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, {
+ ...validBasicValues,
+ fromName: undefined,
+ }),
+ null,
+ ),
+ ).toBe(false);
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, {
+ ...validBasicValues,
+ fromName: '',
+ }) as EmailConfiguration,
+ null,
+ ),
+ ).toBe(false);
+ });
+
+ it('should return true if fromName is set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, { ...validBasicValues, fromName: 'fromName' }),
+ null,
+ ),
+ ).toBe(true);
+ });
+
+ it('should return false if subjectPrefix is not set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, {
+ ...validBasicValues,
+ subjectPrefix: undefined,
+ }),
+ null,
+ ),
+ ).toBe(false);
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, {
+ ...validBasicValues,
+ subjectPrefix: '',
+ }),
+ null,
+ ),
+ ).toBe(false);
+ });
+
+ it('should return true if subjectPrefix is set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, {
+ ...validBasicValues,
+ subjectPrefix: 'subjectPrefix',
+ }),
+ null,
+ ),
+ ).toBe(true);
+ });
+});
+
+describe('checkEmailConfigurationValidity basic-auth props', () => {
+ it('should return false if basicPassword is not set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, {
+ basicPassword: undefined,
+ isBasicPasswordSet: undefined,
+ }),
+ null,
+ ),
+ ).toBe(false);
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, {
+ basicPassword: '',
+ isBasicPasswordSet: undefined,
+ }),
+ null,
+ ),
+ ).toBe(false);
+ });
+
+ it('should return true if basicPassword is set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, {
+ basicPassword: 'basicPassword',
+ isBasicPasswordSet: undefined,
+ }),
+ null,
+ ),
+ ).toBe(true);
+ });
+});
+
+describe('checkEmailConfigurationValidity editing basic-auth props', () => {
+ it('should return false if basicPassword is not set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, { basicPassword: undefined }),
+ mockEmailConfiguration(AuthMethod.Basic, validBasicValues),
+ ),
+ ).toBe(false);
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, { basicPassword: '' }),
+ mockEmailConfiguration(AuthMethod.Basic, validBasicValues),
+ ),
+ ).toBe(false);
+ });
+
+ it('should return true if basicPassword is set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, { basicPassword: 'basicPassword' }),
+ mockEmailConfiguration(AuthMethod.Basic, validBasicValues),
+ ),
+ ).toBe(true);
+ });
+});
+
+describe('checkEmailConfigurationValidity oauth-auth props', () => {
+ it('should return false if oauthAuthenticationHost is not set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, {
+ ...validOAuthValues,
+ oauthAuthenticationHost: undefined,
+ }),
+ null,
+ ),
+ ).toBe(false);
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, {
+ ...validOAuthValues,
+ oauthAuthenticationHost: '',
+ }),
+ null,
+ ),
+ ).toBe(false);
+ });
+
+ it('should return true if oauthAuthenticationHost is set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, {
+ ...validOAuthValues,
+ oauthAuthenticationHost: 'oauthAuthenticationHost',
+ }),
+ null,
+ ),
+ ).toBe(true);
+ });
+
+ it('should return false if oauthClientId is not set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, {
+ ...validOAuthValues,
+ oauthClientId: undefined,
+ }),
+ null,
+ ),
+ ).toBe(false);
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, {
+ ...validOAuthValues,
+ oauthClientId: '',
+ }),
+ null,
+ ),
+ ).toBe(false);
+ });
+
+ it('should return true if oauthClientId is set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, {
+ ...validOAuthValues,
+ oauthClientId: 'oauthClientId',
+ }),
+ null,
+ ),
+ ).toBe(true);
+ });
+
+ it('should return false if oauthClientSecret is not set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, {
+ ...validOAuthValues,
+ oauthClientSecret: undefined,
+ }),
+ null,
+ ),
+ ).toBe(false);
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, {
+ ...validOAuthValues,
+ oauthClientSecret: '',
+ }),
+ null,
+ ),
+ ).toBe(false);
+ });
+
+ it('should return true if oauthClientSecret is set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, {
+ ...validOAuthValues,
+ oauthClientSecret: 'oauthClientSecret',
+ }),
+ null,
+ ),
+ ).toBe(true);
+ });
+
+ it('should return false if oauthTenant is not set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, {
+ ...validOAuthValues,
+ oauthTenant: undefined,
+ }),
+ null,
+ ),
+ ).toBe(false);
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, {
+ ...validOAuthValues,
+ oauthTenant: '',
+ }),
+ null,
+ ),
+ ).toBe(false);
+ });
+
+ it('should return true if oauthTenant is set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, {
+ ...validOAuthValues,
+ oauthTenant: 'oauthTenant',
+ }),
+ null,
+ ),
+ ).toBe(true);
+ });
+});
+
+describe('checkEmailConfigurationValidity editing oauth-auth props', () => {
+ it('should return false if oauthAuthenticationHost is not set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, {
+ ...validOAuthValues,
+ oauthAuthenticationHost: undefined,
+ }),
+ mockEmailConfiguration(AuthMethod.OAuth),
+ ),
+ ).toBe(false);
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, {
+ ...validOAuthValues,
+ oauthAuthenticationHost: '',
+ }),
+ mockEmailConfiguration(AuthMethod.OAuth),
+ ),
+ ).toBe(false);
+ });
+
+ it('should return true if oauthAuthenticationHost is set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, {
+ ...validOAuthValues,
+ oauthAuthenticationHost: 'oauthAuthenticationHost',
+ }),
+ mockEmailConfiguration(AuthMethod.OAuth),
+ ),
+ ).toBe(true);
+ });
+
+ it('should return true if oauthClientId is set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, {
+ ...validOAuthValues,
+ oauthClientId: 'oauthClientId',
+ }),
+ mockEmailConfiguration(AuthMethod.OAuth),
+ ),
+ ).toBe(true);
+ });
+
+ it('should return true if oauthClientSecret is set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, {
+ ...validOAuthValues,
+ oauthClientSecret: 'oauthClientSecret',
+ }),
+ mockEmailConfiguration(AuthMethod.OAuth),
+ ),
+ ).toBe(true);
+ });
+
+ it('should return false if oauthTenant is not set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, {
+ ...validOAuthValues,
+ oauthTenant: undefined,
+ }),
+ mockEmailConfiguration(AuthMethod.OAuth),
+ ),
+ ).toBe(false);
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, {
+ ...validOAuthValues,
+ oauthTenant: '',
+ }),
+ mockEmailConfiguration(AuthMethod.OAuth),
+ ),
+ ).toBe(false);
+ });
+
+ it('should return true if oauthTenant is set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, {
+ ...validOAuthValues,
+ oauthTenant: 'oauthTenant',
+ }),
+ mockEmailConfiguration(AuthMethod.OAuth),
+ ),
+ ).toBe(true);
+ });
+
+ it('should return false if oauthClientId is edited in isolation', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, {
+ oauthClientId: 'oauthClientId',
+ }),
+ mockEmailConfiguration(AuthMethod.OAuth),
+ ),
+ ).toBe(false);
+ });
+
+ it('should return true if oauthClientSecret is edited in isolation', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, {
+ oauthClientSecret: 'oauthClientSecret',
+ }),
+ mockEmailConfiguration(AuthMethod.OAuth),
+ ),
+ ).toBe(true);
+ });
+});
+
+describe('checkEmailConfigurationValidity editing', () => {
+ it('should return false if configuration has not changed', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic),
+ mockEmailConfiguration(AuthMethod.Basic),
+ ),
+ ).toBe(false);
+ });
+
+ it('should return true if configuration has changed', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, { ...validBasicValues, username: 'new-username' }),
+ mockEmailConfiguration(AuthMethod.Basic),
+ ),
+ ).toBe(true);
+ });
+
+ it('should return false if configuration type has changed and password not set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, {
+ isOauthClientIdSet: false,
+ isOauthClientSecretSet: false,
+ }),
+ mockEmailConfiguration(AuthMethod.Basic),
+ ),
+ ).toBe(false);
+ });
+
+ it('should return true if configuration type has changed and password is set', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, validOAuthValues),
+ mockEmailConfiguration(AuthMethod.Basic),
+ ),
+ ).toBe(true);
+ });
+
+ it('should return true if configuration type has changed and password already exists', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.Basic, validBasicValues),
+ mockEmailConfiguration(AuthMethod.OAuth, { isBasicPasswordSet: true }),
+ ),
+ ).toBe(true);
+ });
+
+ it('should return true if configuration type has changed and oauthClientId & oauthClientSecret already exist', () => {
+ expect(
+ checkEmailConfigurationHasChanges(
+ mockEmailConfiguration(AuthMethod.OAuth, { oauthClientSecret: 'oauthClientSecret' }),
+ mockEmailConfiguration(AuthMethod.Basic, {
+ isOauthClientIdSet: true,
+ isOauthClientSecretSet: true,
+ }),
+ ),
+ ).toBe(true);
+ });
+});
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { isUndefined } from 'lodash';
-import { AuthMethod, EmailConfiguration } from '../../../../types/system';
+import {
+ AuthMethod,
+ EmailConfiguration,
+ EmailConfigurationBasicAuth,
+ EmailConfigurationOAuth,
+} from '../../../../types/system';
export const AUTH_METHOD = 'auth-method';
onChange: (newValue: Partial<EmailConfiguration>) => void;
}
-export function checkEmailConfigurationValidity(configuration: EmailConfiguration): boolean {
+const COMMON_EMAIL_PROPS: (keyof EmailConfiguration)[] = [
+ 'authMethod',
+ 'username',
+ 'host',
+ 'port',
+ 'securityProtocol',
+ 'fromAddress',
+ 'fromName',
+ 'subjectPrefix',
+];
+
+export function checkEmailConfigurationHasChanges(
+ configuration: EmailConfiguration | null,
+ originalConfiguration: EmailConfiguration | null,
+): boolean {
+ if (!configuration) {
+ return false;
+ }
+
+ const isEditing = originalConfiguration !== null;
+ const isOAuth = configuration.authMethod === AuthMethod.OAuth;
+
let isValid = false;
- const commonProps: (keyof EmailConfiguration)[] = [
- 'authMethod',
- 'username',
- 'host',
- 'port',
- 'securityProtocol',
- 'fromAddress',
- 'fromName',
- 'subjectPrefix',
- ];
- if (configuration.authMethod === AuthMethod.Basic) {
- isValid = checkRequiredPropsAreValid(configuration, [...commonProps, 'basicPassword']);
- } else {
- isValid = checkRequiredPropsAreValid(configuration, [
- ...commonProps,
+ if (isOAuth) {
+ const oauthClientIdChanged =
+ configuration.isOauthClientIdSet === true &&
+ checkRequiredPropsAreValid(configuration as EmailConfigurationOAuth, ['oauthClientId']);
+
+ const privatePropsToCheck: (keyof EmailConfigurationOAuth)[] = ['oauthClientSecret'];
+ if (!isEditing || oauthClientIdChanged || !configuration.isOauthClientIdSet) {
+ privatePropsToCheck.push('oauthClientId');
+ }
+
+ isValid = checkRequiredPropsAreValid(configuration as EmailConfigurationOAuth, [
+ ...COMMON_EMAIL_PROPS,
'oauthAuthenticationHost',
- 'oauthClientId',
- 'oauthClientSecret',
'oauthTenant',
+ ...privatePropsToCheck,
+ ]);
+ } else {
+ isValid = checkRequiredPropsAreValid(configuration as EmailConfigurationBasicAuth, [
+ ...COMMON_EMAIL_PROPS,
+ 'basicPassword',
]);
}
return isValid;
}
+// Check if required props are present and contain a value that is not an empty string.
function checkRequiredPropsAreValid<T>(obj: T, props: (keyof T)[]): boolean {
- return props.every(
- (prop) => !isUndefined(obj[prop]) && typeof obj[prop] === 'string' && obj[prop].length > 0,
- );
+ return props.every((prop) => typeof obj[prop] === 'string' && obj[prop]);
}
OAuth = 'OAUTH',
}
-export type EmailConfiguration = (
- | {
- authMethod: AuthMethod.Basic;
- basicPassword: string;
- readonly isBasicPasswordSet?: boolean;
- }
- | {
- authMethod: AuthMethod.OAuth;
- readonly isOauthClientIdSet?: boolean;
- readonly isOauthClientSecretSet?: boolean;
- oauthAuthenticationHost: string;
- oauthClientId: string;
- oauthClientSecret: string;
- oauthTenant: string;
- }
-) &
- EmailConfigurationCommon;
+export type EmailConfiguration = EmailConfigurationAuth & EmailConfigurationCommon;
+export type EmailConfigurationAuth = EmailNotificationBasicAuth | EmailNotificationOAuth;
+export type EmailConfigurationBasicAuth = EmailNotificationBasicAuth & EmailConfigurationCommon;
+export type EmailConfigurationOAuth = EmailNotificationOAuth & EmailConfigurationCommon;
interface EmailConfigurationCommon {
fromAddress: string;
subjectPrefix: string;
username: string;
}
+
+interface EmailNotificationBasicAuth {
+ authMethod: AuthMethod.Basic;
+ basicPassword: string;
+ readonly isBasicPasswordSet?: boolean;
+}
+
+interface EmailNotificationOAuth {
+ authMethod: AuthMethod.OAuth;
+ readonly isOauthClientIdSet?: boolean;
+ readonly isOauthClientSecretSet?: boolean;
+ oauthAuthenticationHost: string;
+ oauthClientId: string;
+ oauthClientSecret: string;
+ oauthTenant: string;
+}
email_notification.form.basic_password=SMTP password
email_notification.form.basic_password.description=Password used to authenticate to the SMTP server.
email_notification.form.oauth_auth.title=Modern Authentication
-email_notification.form.oauth_auth.description=Authenticate with OAuth Microsoft
+email_notification.form.oauth_auth.description=Authenticate with OAuth
+email_notification.form.oauth_auth.supported=Supported: Microsoft
email_notification.form.oauth_auth.recommended_reason=for stronger security compliance
email_notification.form.oauth_authentication_host=Authentication host
email_notification.form.oauth_authentication_host.description=Host of the Identity Provider issuing access tokens.
email_notification.form.save_configuration.update_success=Email configuration updated successfully.
email_notification.form.delete_configuration=Delete configuration
email_notification.state.value_should_be_valid_email=A valid email address is required.
+email_notification.overview.heading=SMTP configuration settings
+email_notification.overview.authentication_type=Authentication type
+email_notification.overview.private=Hidden for security reasons
+email_notification.form.private=**************
+email_notification.overview.value={0} value
email_configuration.test.title=Test Configuration
email_configuration.test.to_address=To