From 483f1347e1066f05aef7d20bf5ebd6d03aea78b0 Mon Sep 17 00:00:00 2001 From: Shane Findley Date: Tue, 3 Sep 2024 15:59:37 +0200 Subject: [PATCH] SONAR 22666 Adding test email configuration modal (#11664) --- .../email-notification/EmailForm.tsx | 136 ---------------- .../EmailNotificationOverview.tsx | 3 + .../email-notification/EmailTestModal.tsx | 145 ++++++++++++++++++ .../__tests__/EmailNotification-it.tsx | 81 +++++++++- .../sonar-web/src/main/js/queries/emails.ts | 2 +- .../resources/org/sonar/l10n/core.properties | 22 +-- 6 files changed, 241 insertions(+), 148 deletions(-) delete mode 100644 server/sonar-web/src/main/js/apps/settings/components/email-notification/EmailForm.tsx create mode 100644 server/sonar-web/src/main/js/apps/settings/components/email-notification/EmailTestModal.tsx diff --git a/server/sonar-web/src/main/js/apps/settings/components/email-notification/EmailForm.tsx b/server/sonar-web/src/main/js/apps/settings/components/email-notification/EmailForm.tsx deleted file mode 100644 index 4f5c24df21b..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/email-notification/EmailForm.tsx +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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 { Spinner } from '@sonarsource/echoes-react'; -import { - BasicSeparator, - ButtonPrimary, - FlagMessage, - FormField, - InputField, - InputTextArea, - SubHeading, -} from 'design-system'; -import React, { ChangeEvent, FormEvent, useState } from 'react'; -import { useIntl } from 'react-intl'; -import { useCurrentLoginUser } from '../../../../app/components/current-user/CurrentUserContext'; -import MandatoryFieldsExplanation from '../../../../components/ui/MandatoryFieldsExplanation'; -import { translate, translateWithParameters } from '../../../../helpers/l10n'; -import { parseError } from '../../../../helpers/request'; -import { useSendTestEmailMutation } from '../../../../queries/emails'; - -export default function EmailForm() { - const currentUser = useCurrentLoginUser(); - const { formatMessage } = useIntl(); - const { isPending, isSuccess, mutate: sendTestEmail } = useSendTestEmailMutation(); - - const [error, setError] = useState(); - const [message, setMessage] = useState( - formatMessage({ id: 'email_configuration.test.message_text' }), - ); - const [recipient, setRecipient] = useState(currentUser.email ?? ''); - const [subject, setSubject] = useState(formatMessage({ id: 'email_configuration.test.subject' })); - - const handleFormSubmit = (event: FormEvent) => { - event.preventDefault(); - sendTestEmail( - { message, recipient, subject }, - { - onError: async (error) => { - const errorMessage = await parseError(error); - setError(errorMessage); - }, - }, - ); - }; - - const onMessageChange = (event: ChangeEvent) => { - setMessage(event.target.value); - }; - - const onRecipientChange = (event: ChangeEvent) => { - setRecipient(event.target.value); - }; - - const onSubjectChange = (event: ChangeEvent) => { - setSubject(event.target.value); - }; - - return ( - <> - -
-
- {translate('email_configuration.test.title')} -
- -
-
- -
- {isSuccess && ( - - {translateWithParameters('email_configuration.test.email_was_sent_to_x', recipient)} - - )} - - {error !== undefined && {error}} - - - - - - - - - - - - - {translate('email_configuration.test.send')} - - - -
- - ); -} diff --git a/server/sonar-web/src/main/js/apps/settings/components/email-notification/EmailNotificationOverview.tsx b/server/sonar-web/src/main/js/apps/settings/components/email-notification/EmailNotificationOverview.tsx index 007520342e7..e76898e482e 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/email-notification/EmailNotificationOverview.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/email-notification/EmailNotificationOverview.tsx @@ -23,6 +23,7 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import { translate } from '../../../../helpers/l10n'; import { AuthMethod, EmailConfiguration } from '../../../../types/system'; +import EmailTestModal from './EmailTestModal'; interface EmailTestModalProps { emailConfiguration: EmailConfiguration; @@ -34,6 +35,8 @@ export default function EmailNotificationOverview(props: Readonly + +
diff --git a/server/sonar-web/src/main/js/apps/settings/components/email-notification/EmailTestModal.tsx b/server/sonar-web/src/main/js/apps/settings/components/email-notification/EmailTestModal.tsx new file mode 100644 index 00000000000..781b06a166b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/email-notification/EmailTestModal.tsx @@ -0,0 +1,145 @@ +/* + * 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, Modal } from '@sonarsource/echoes-react'; +import { + addGlobalErrorMessage, + addGlobalSuccessMessage, + FormField, + InputField, + InputTextArea, +} from 'design-system'; +import React, { FormEvent, useState } from 'react'; +import { useIntl } from 'react-intl'; +import { isEmail } from 'validator'; +import { useCurrentLoginUser } from '../../../../app/components/current-user/CurrentUserContext'; +import { translate } from '../../../../helpers/l10n'; +import { useSendTestEmailMutation } from '../../../../queries/emails'; + +const FORM_ID = 'test-email-form'; + +export default function EmailTestModal() { + const [isOpen, setIsOpen] = useState(false); + const currentUser = useCurrentLoginUser(); + const { formatMessage } = useIntl(); + const { isPending, mutate: sendTestEmail } = useSendTestEmailMutation(); + + const [message, setMessage] = useState( + formatMessage({ id: 'email_notification.test.message_text' }), + ); + const [recipient, setRecipient] = useState(currentUser.email ?? ''); + const [subject, setSubject] = useState(formatMessage({ id: 'email_notification.test.subject' })); + + const handleFormSubmit = (event: FormEvent) => { + event.preventDefault(); + sendTestEmail( + { message, recipient, subject }, + { + onError: () => { + addGlobalErrorMessage(translate('email_notification.test.failure')); + }, + onSuccess: () => { + addGlobalSuccessMessage(translate('email_notification.test.success')); + }, + onSettled: () => setIsOpen(false), + }, + ); + }; + + const body = ( +
+ + setRecipient(event.target.value)} + required + size="full" + type="email" + value={recipient} + /> + + + setSubject(event.target.value)} + size="full" + type="text" + value={subject} + /> + + + setMessage(event.target.value)} + required + rows={5} + size="full" + value={message} + /> + +
+ ); + + return ( + + {formatMessage({ id: 'email_notification.test.submit' })} + + } + secondaryButton={ + + } + title={formatMessage({ id: 'email_notification.test.modal_title' })} + > +
+ + {formatMessage({ id: 'email_notification.test.title' })} + + +
+
+ ); +} diff --git a/server/sonar-web/src/main/js/apps/settings/components/email-notification/__tests__/EmailNotification-it.tsx b/server/sonar-web/src/main/js/apps/settings/components/email-notification/__tests__/EmailNotification-it.tsx index b481742cbf8..997c6e7ef14 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/email-notification/__tests__/EmailNotification-it.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/email-notification/__tests__/EmailNotification-it.tsx @@ -23,9 +23,13 @@ import { addGlobalSuccessMessage } from 'design-system/lib'; import React from 'react'; import { byLabelText, byRole, byTestId, byText } from '~sonar-aligned/helpers/testSelector'; import SystemServiceMock from '../../../../../api/mocks/SystemServiceMock'; +import * as settingsApi from '../../../../../api/settings'; import * as api from '../../../../../api/system'; +import { CurrentUserContext } from '../../../../../app/components/current-user/CurrentUserContext'; import { mockEmailConfiguration } from '../../../../../helpers/mocks/system'; +import { mockCurrentUser } from '../../../../../helpers/testMocks'; import { renderComponent } from '../../../../../helpers/testReactTestingUtils'; +import { Permissions } from '../../../../../types/permissions'; import { AuthMethod } from '../../../../../types/system'; import EmailNotification from '../EmailNotification'; @@ -112,6 +116,16 @@ const ui = { edit: byRole('button', { name: 'edit', }), + + // test modal + test_email: byRole('button', { name: 'email_notification.test.create_test_email' }), + test_email_title: byRole('heading', { name: 'email_notification.test.modal_title' }), + test_email_to_address: byRole('textbox', { + name: 'email_notification.test.to_address required', + }), + test_email_subject: byRole('textbox', { name: 'email_notification.test.subject' }), + test_email_message: byRole('textbox', { name: 'email_notification.test.message required' }), + test_email_submit: byRole('button', { name: 'email_notification.test.submit' }), }; describe('Email Basic Configuration', () => { @@ -474,6 +488,71 @@ describe('Email Oauth Configuration', () => { }); }); +describe('EmailNotification send test email', () => { + it('should render the EmailTestModal', async () => { + const user = userEvent.setup(); + systemHandler.addEmailConfiguration( + mockEmailConfiguration(AuthMethod.Basic, { id: 'email-1' }), + ); + + renderEmailNotifications(); + expect(await ui.overviewHeading.find()).toBeInTheDocument(); + + await user.click(ui.test_email.get()); + + expect(ui.test_email_title.get()).toBeVisible(); + expect(ui.test_email_to_address.get()).toBeVisible(); + expect(ui.test_email_to_address.get()).toHaveValue(''); + expect(ui.test_email_subject.get()).toBeVisible(); + expect(ui.test_email_subject.get()).toHaveValue('email_notification.test.subject'); + expect(ui.test_email_message.get()).toBeVisible(); + expect(ui.test_email_message.get()).toHaveValue('email_notification.test.message_text'); + }); + + it('should be possible to send a test email', async () => { + jest.spyOn(settingsApi, 'sendTestEmail'); + const user = userEvent.setup(); + systemHandler.addEmailConfiguration( + mockEmailConfiguration(AuthMethod.Basic, { id: 'email-1' }), + ); + + renderEmailNotifications(); + expect(await ui.overviewHeading.find()).toBeInTheDocument(); + + await user.click(ui.test_email.get()); + + expect(ui.test_email_submit.get()).toBeDisabled(); + await user.type(ui.test_email_to_address.get(), 'test@test.com'); + expect(ui.test_email_submit.get()).toBeEnabled(); + + await user.clear(ui.test_email_subject.get()); + await user.type(ui.test_email_subject.get(), 'Test subject'); + await user.clear(ui.test_email_message.get()); + await user.type(ui.test_email_message.get(), 'This is a test message'); + + await user.click(ui.test_email_submit.get()); + expect(settingsApi.sendTestEmail).toHaveBeenCalledTimes(1); + expect(settingsApi.sendTestEmail).toHaveBeenCalledWith( + 'test@test.com', + 'Test subject', + 'This is a test message', + ); + }); +}); + function renderEmailNotifications() { - return renderComponent(); + return renderComponent( + {}, + updateDismissedNotices: () => {}, + }} + > + + , + ); } diff --git a/server/sonar-web/src/main/js/queries/emails.ts b/server/sonar-web/src/main/js/queries/emails.ts index 091154eb505..eefc9434876 100644 --- a/server/sonar-web/src/main/js/queries/emails.ts +++ b/server/sonar-web/src/main/js/queries/emails.ts @@ -31,6 +31,6 @@ export function useSendTestEmailMutation() { }, unknown >({ - mutationFn: ({ message, recipient, subject }) => sendTestEmail(message, recipient, subject), + mutationFn: ({ message, recipient, subject }) => sendTestEmail(recipient, subject, message), }); } diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 82ce90dac17..b9b11af3068 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -2697,7 +2697,7 @@ 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 -email_notification.form.oauth_auth.supported=Supported: Microsoft +email_notification.form.oauth_auth.supported=Supported providers: 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. @@ -2729,15 +2729,17 @@ 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 -email_configuration.test.subject=Subject -email_configuration.test.message=Message -email_configuration.test.message_text=This is a test message from SonarQube. -email_configuration.test.send=Send Test Email -email_configuration.test.email_was_sent_to_x=Email was sent to {0} - +email_notification.test.title=Test configuration +email_notification.test.modal_title=Test email +email_notification.test.to_address=To +email_notification.test.subject=Subject +email_notification.test.message=Message +email_notification.test.message_text=This is a test message from SonarQube. +email_notification.test.create_test_email=Create test email +email_notification.test.submit=Send test email +email_notification.test.success=Your email was sent successfully +email_notification.test.failure=Your email could not be sent. Ensure your authentication configuration settings and email recipient are valid. +email_notification.state.value_should_be_valid_email=A valid email address is required. #------------------------------------------------------------------------------ # -- 2.39.5