]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR 22666 Adding test email configuration modal (#11664)
authorShane Findley <shane.findley@sonarsource.com>
Tue, 3 Sep 2024 13:59:37 +0000 (15:59 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 3 Sep 2024 20:02:44 +0000 (20:02 +0000)
server/sonar-web/src/main/js/apps/settings/components/email-notification/EmailForm.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/email-notification/EmailNotificationOverview.tsx
server/sonar-web/src/main/js/apps/settings/components/email-notification/EmailTestModal.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/email-notification/__tests__/EmailNotification-it.tsx
server/sonar-web/src/main/js/queries/emails.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

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 (file)
index 4f5c24d..0000000
+++ /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<string | undefined>();
-  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<HTMLTextAreaElement>) => {
-    setMessage(event.target.value);
-  };
-
-  const onRecipientChange = (event: ChangeEvent<HTMLInputElement>) => {
-    setRecipient(event.target.value);
-  };
-
-  const onSubjectChange = (event: ChangeEvent<HTMLInputElement>) => {
-    setSubject(event.target.value);
-  };
-
-  return (
-    <>
-      <BasicSeparator />
-      <div className="sw-p-6 sw-flex sw-gap-12">
-        <div className="sw-w-abs-300">
-          <SubHeading>{translate('email_configuration.test.title')}</SubHeading>
-          <div className="sw-mt-1">
-            <MandatoryFieldsExplanation />
-          </div>
-        </div>
-
-        <form className="sw-flex-1" onSubmit={handleFormSubmit}>
-          {isSuccess && (
-            <FlagMessage variant="success">
-              {translateWithParameters('email_configuration.test.email_was_sent_to_x', recipient)}
-            </FlagMessage>
-          )}
-
-          {error !== undefined && <FlagMessage variant="error">{error}</FlagMessage>}
-
-          <FormField label={translate('email_configuration.test.to_address')} required>
-            <InputField
-              disabled={isPending}
-              id="test-email-to"
-              onChange={onRecipientChange}
-              required
-              size="large"
-              type="email"
-              value={recipient}
-            />
-          </FormField>
-          <FormField label={translate('email_configuration.test.subject')}>
-            <InputField
-              disabled={isPending}
-              id="test-email-subject"
-              onChange={onSubjectChange}
-              size="large"
-              type="text"
-              value={subject}
-            />
-          </FormField>
-          <FormField label={translate('email_configuration.test.message')} required>
-            <InputTextArea
-              disabled={isPending}
-              id="test-email-message"
-              onChange={onMessageChange}
-              required
-              rows={5}
-              size="large"
-              value={message}
-            />
-          </FormField>
-
-          <ButtonPrimary disabled={isPending} type="submit" className="sw-mt-2">
-            {translate('email_configuration.test.send')}
-          </ButtonPrimary>
-          <Spinner isLoading={isPending} className="sw-ml-2" />
-        </form>
-      </div>
-    </>
-  );
-}
index 007520342e7bcd64342674b6b8dccd2c7838a0f9..e76898e482eeb58173489efb56a2ab7f58187455 100644 (file)
@@ -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<EmailTestModal
 
   return (
     <>
+      <BasicSeparator className="sw-my-6" />
+      <EmailTestModal />
       <BasicSeparator className="sw-my-6" />
       <div className="sw-flex sw-justify-between">
         <div className="sw-grid sw-gap-4">
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 (file)
index 0000000..781b06a
--- /dev/null
@@ -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 = (
+    <form className="sw-flex-1" id={FORM_ID} onSubmit={handleFormSubmit}>
+      <FormField
+        htmlFor="test-email-to"
+        label={formatMessage({ id: 'email_notification.test.to_address' })}
+        required
+      >
+        <InputField
+          disabled={isPending}
+          id="test-email-to"
+          onChange={(event) => setRecipient(event.target.value)}
+          required
+          size="full"
+          type="email"
+          value={recipient}
+        />
+      </FormField>
+      <FormField
+        htmlFor="test-email-subject"
+        label={formatMessage({ id: 'email_notification.test.subject' })}
+      >
+        <InputField
+          disabled={isPending}
+          id="test-email-subject"
+          onChange={(event) => setSubject(event.target.value)}
+          size="full"
+          type="text"
+          value={subject}
+        />
+      </FormField>
+      <FormField
+        htmlFor="test-email-message"
+        label={formatMessage({ id: 'email_notification.test.message' })}
+        required
+      >
+        <InputTextArea
+          disabled={isPending}
+          id="test-email-message"
+          onChange={(event) => setMessage(event.target.value)}
+          required
+          rows={5}
+          size="full"
+          value={message}
+        />
+      </FormField>
+    </form>
+  );
+
+  return (
+    <Modal
+      content={body}
+      isOpen={isOpen}
+      onOpenChange={setIsOpen}
+      primaryButton={
+        <Button
+          isDisabled={!isEmail(recipient) || isPending}
+          form={FORM_ID}
+          variety={ButtonVariety.Primary}
+          type="submit"
+        >
+          {formatMessage({ id: 'email_notification.test.submit' })}
+        </Button>
+      }
+      secondaryButton={
+        <Button onClick={() => setIsOpen(false)} variety={ButtonVariety.Default}>
+          {formatMessage({ id: 'cancel' })}
+        </Button>
+      }
+      title={formatMessage({ id: 'email_notification.test.modal_title' })}
+    >
+      <div className="sw-flex sw-justify-between sw-items-center">
+        <span className="sw-body-md-highlight">
+          {formatMessage({ id: 'email_notification.test.title' })}
+        </span>
+        <Button onClick={() => setIsOpen(true)} variety={ButtonVariety.Default}>
+          {formatMessage({ id: 'email_notification.test.create_test_email' })}
+        </Button>
+      </div>
+    </Modal>
+  );
+}
index b481742cbf879777dd246ab5ee191715b53bb6a9..997c6e7ef14a917a617474a6545ddf295d1e81cd 100644 (file)
@@ -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(<EmailNotification />);
+  return renderComponent(
+    <CurrentUserContext.Provider
+      value={{
+        currentUser: mockCurrentUser({
+          isLoggedIn: true,
+          permissions: { global: [Permissions.Admin] },
+        }),
+        updateCurrentUserHomepage: () => {},
+        updateDismissedNotices: () => {},
+      }}
+    >
+      <EmailNotification />
+    </CurrentUserContext.Provider>,
+  );
 }
index 091154eb50559483cb471c07768fe8957988583d..eefc943487654b7d2212a3b9bbe968d1536bdd65 100644 (file)
@@ -31,6 +31,6 @@ export function useSendTestEmailMutation() {
     },
     unknown
   >({
-    mutationFn: ({ message, recipient, subject }) => sendTestEmail(message, recipient, subject),
+    mutationFn: ({ message, recipient, subject }) => sendTestEmail(recipient, subject, message),
   });
 }
index 82ce90dac17aa721fce72cf71ebbc91f1d3aa3ef..b9b11af3068adab681e97665fccc4f9bb2166ed6 100644 (file)
@@ -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.
 
 #------------------------------------------------------------------------------
 #