diff --git a/src/registrar/admin.py b/src/registrar/admin.py index c17af86a8..b6706fad4 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1885,7 +1885,7 @@ def save_model(self, request, obj, form, change): if requested_user is not None: portfolio_invitation.retrieve() portfolio_invitation.save() - messages.success(request, f"{requested_email} has been invited to the organization: {domain_org}") + messages.success(request, f"{requested_email} has been invited to become a member of {domain_org}") if not send_domain_invitation_email( email=requested_email, @@ -1894,7 +1894,7 @@ def save_model(self, request, obj, form, change): is_member_of_different_org=member_of_a_different_org, requested_user=requested_user, ): - messages.warning(request, "Could not send email confirmation to existing domain managers.") + messages.warning(request, "Could not send email notification to existing domain managers.") if requested_user is not None: # Domain Invitation creation for an existing User obj.retrieve() diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 4538de71e..ddacdf5e4 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -399,7 +399,7 @@ def test_add_domain_invitation_success_when_user_not_portfolio_member( # Assert success message mock_messages_success.assert_has_calls( [ - call(request, "test@example.com has been invited to the organization: new portfolio"), + call(request, "test@example.com has been invited to become a member of new portfolio"), call(request, "test@example.com has been invited to the domain: example.com"), ] ) @@ -656,7 +656,7 @@ def test_add_domain_invitation_when_user_not_portfolio_member_raises_exception_s # Assert success message mock_messages_success.assert_called_once_with( - request, "test@example.com has been invited to the organization: new portfolio" + request, "test@example.com has been invited to become a member of new portfolio" ) # Assert error message @@ -784,7 +784,7 @@ def test_add_domain_invitation_success_when_email_not_portfolio_member( # Assert success message mock_messages_success.assert_has_calls( [ - call(request, "nonexistent@example.com has been invited to the organization: new portfolio"), + call(request, "nonexistent@example.com has been invited to become a member of new portfolio"), call(request, "nonexistent@example.com has been invited to the domain: example.com"), ] ) @@ -1018,7 +1018,7 @@ def test_add_domain_invitation_when_user_not_portfolio_email_raises_exception_se # Assert success message mock_messages_success.assert_called_once_with( - request, "nonexistent@example.com has been invited to the organization: new portfolio" + request, "nonexistent@example.com has been invited to become a member of new portfolio" ) # Assert error message @@ -1503,7 +1503,7 @@ def test_save_exception_email_sending_error(self, mock_messages_error, mock_send self.client.force_login(self.superuser) # Mock the email sending function to raise EmailSendingError - mock_send_email.side_effect = EmailSendingError("Email service unavailable") + mock_send_email.side_effect = EmailSendingError("Email service unavailable.") # Create an instance of the admin class admin_instance = PortfolioInvitationAdmin(PortfolioInvitation, admin_site=None) @@ -1521,9 +1521,16 @@ def test_save_exception_email_sending_error(self, mock_messages_error, mock_send # Call the save_model method admin_instance.save_model(request, portfolio_invitation, None, None) + msg = ( + "Email service unavailable. Try again, and if the problem persists, " + 'contact us.' + ) # Assert that messages.error was called with the correct message - mock_messages_error.assert_called_once_with(request, "Email service unavailable") + mock_messages_error.assert_called_once_with( + request, + msg, + ) @less_console_noise_decorator @patch("registrar.admin.send_portfolio_invitation_email") @@ -1585,8 +1592,17 @@ def test_save_exception_generic_error(self, mock_messages_error, mock_send_email # Call the save_model method admin_instance.save_model(request, portfolio_invitation, None, None) + msg = ( + "An unexpected error occurred: james.gordon@gotham.gov could not be added to this domain. " + 'Try again, and if the problem persists, contact us.' + ) + # Assert that messages.error was called with the correct message - mock_messages_error.assert_called_once_with(request, "Could not send email invitation.") + mock_messages_error.assert_called_once_with( + request, + msg, + ) @less_console_noise_decorator @patch("registrar.admin.send_portfolio_admin_addition_emails") diff --git a/src/registrar/tests/test_email_invitations.py b/src/registrar/tests/test_email_invitations.py index 99515648a..bdbad0de3 100644 --- a/src/registrar/tests/test_email_invitations.py +++ b/src/registrar/tests/test_email_invitations.py @@ -525,7 +525,10 @@ def test_send_portfolio_invitation_email_failure(self, mock_send_templated_email with self.assertRaises(EmailSendingError) as context: send_portfolio_invitation_email(self.email, self.requestor, self.portfolio, is_admin_invitation) - self.assertIn("Could not sent email invitation to", str(context.exception)) + self.assertIn( + "An unexpected error occurred: invitee@example.com could not be added to this domain.", + str(context.exception), + ) @less_console_noise_decorator @patch( diff --git a/src/registrar/tests/test_views_domain.py b/src/registrar/tests/test_views_domain.py index 0e9c70d1a..44fb65023 100644 --- a/src/registrar/tests/test_views_domain.py +++ b/src/registrar/tests/test_views_domain.py @@ -981,7 +981,7 @@ def test_domain_user_add_form_fails_to_send_to_some_managers( self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) success_page = success_result.follow() - self.assertContains(success_page, "Could not send email confirmation to existing domain managers.") + self.assertContains(success_page, "Could not send email notification to existing domain managers.") @GenericTestHelper.switch_to_enterprise_mode_wrapper @boto3_mocking.patching diff --git a/src/registrar/utility/email_invitations.py b/src/registrar/utility/email_invitations.py index b6b775c10..a140703cc 100644 --- a/src/registrar/utility/email_invitations.py +++ b/src/registrar/utility/email_invitations.py @@ -103,7 +103,7 @@ def _send_domain_invitation_email(email, requestor_email, domains, requested_use f" Error: {err}", exc_info=True, ) - raise EmailSendingError(f"Could not send email invitation to {email} for domains: {domain_names}") from err + raise EmailSendingError(f"An unexpected error occurred: {email} could not be added to this domain.") from err def send_domain_invitation_email( @@ -335,9 +335,7 @@ def send_portfolio_invitation_email(email: str, requestor, portfolio, is_admin_i f" Error: {err}", exc_info=True, ) - raise EmailSendingError( - f"Could not sent email invitation to {email} for portfolio {portfolio}. Portfolio invitation not saved." - ) from err + raise EmailSendingError(f"An unexpected error occurred: {email} could not be added to this domain.") from err all_admin_emails_sent = True # send emails to portfolio admins diff --git a/src/registrar/utility/errors.py b/src/registrar/utility/errors.py index 9446d84f7..d31719961 100644 --- a/src/registrar/utility/errors.py +++ b/src/registrar/utility/errors.py @@ -54,12 +54,6 @@ def __init__(self, email=None, domain=None, portfolio=None): # Default message if no additional info is provided message = "Can't send invitation email. No email is associated with your user account." - # Customize message based on provided arguments - if email and domain: - message = f"Can't send email to '{email}' on domain '{domain}'. No email exists for the requestor." - elif email and portfolio: - message = f"Can't send email to '{email}' for portfolio '{portfolio}'. No email exists for the requestor." - super().__init__(message) @@ -71,9 +65,9 @@ class OutsideOrgMemberError(InvitationError): def __init__(self, email=None): # Default message if no additional info is provided - message = "Can not invite member of a .gov organization to a different organization." + message = "Can not invite member to this organization." if email: - message = f"{email} is already a member of another .gov organization." + message = f"{email} is not a member of this organization." super().__init__(message) diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 71e9ae89c..70be1beca 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -1324,7 +1324,7 @@ def form_valid(self, form): if requested_user is not None: portfolio_invitation.retrieve() portfolio_invitation.save() - messages.success(self.request, f"{requested_email} has been invited to the organization: {domain_org}") + messages.success(self.request, f"{requested_email} has been invited to become a member of {domain_org}") if requested_user is None: self._handle_new_user_invitation(requested_email, requestor, member_of_a_different_org) @@ -1343,9 +1343,9 @@ def _handle_new_user_invitation(self, email, requestor, member_of_different_org) domains=self.object, is_member_of_different_org=member_of_different_org, ): - messages.warning(self.request, "Could not send email confirmation to existing domain managers.") + messages.warning(self.request, "Could not send email notification to existing domain managers.") DomainInvitation.objects.get_or_create(email=email, domain=self.object) - messages.success(self.request, f"{email} has been invited to the domain: {self.object}") + messages.success(self.request, f"{email} has been invited to this domain.") def _handle_existing_user(self, email, requestor, requested_user, member_of_different_org): """Handle adding an existing user to the domain.""" @@ -1356,7 +1356,7 @@ def _handle_existing_user(self, email, requestor, requested_user, member_of_diff is_member_of_different_org=member_of_different_org, requested_user=requested_user, ): - messages.warning(self.request, "Could not send email confirmation to existing domain managers.") + messages.warning(self.request, "Could not send email notification to existing domain managers.") UserDomainRole.objects.create( user=requested_user, domain=self.object, diff --git a/src/registrar/views/portfolios.py b/src/registrar/views/portfolios.py index abc2c2194..15eab4bd9 100644 --- a/src/registrar/views/portfolios.py +++ b/src/registrar/views/portfolios.py @@ -446,7 +446,7 @@ def _process_added_domains(self, added_domain_ids, member, requestor, portfolio) is_member_of_different_org=member_of_a_different_org, requested_user=member, ): - messages.warning(self.request, "Could not send email confirmation to existing domain managers.") + messages.warning(self.request, "Could not send email notification to existing domain managers.") # Bulk create UserDomainRole instances for added domains UserDomainRole.objects.bulk_create( [ @@ -777,7 +777,7 @@ def _process_added_domains(self, added_domain_ids, email, requestor, portfolio): domains=added_domains, is_member_of_different_org=member_of_a_different_org, ): - messages.warning(self.request, "Could not send email confirmation to existing domain managers.") + messages.warning(self.request, "Could not send email notification to existing domain managers.") # Update existing invitations from CANCELED to INVITED existing_invitations = DomainInvitation.objects.filter(domain__in=added_domains, email=email) @@ -1179,7 +1179,7 @@ def _handle_exceptions(self, exception, portfolio, email): elif isinstance(exception, MissingEmailError): messages.error(self.request, str(exception)) logger.error( - f"Can't send email to '{email}' for portfolio '{portfolio}'. No email exists for the requestor.", + "Can't send invitation email. No email is associated with your account.", exc_info=True, ) else: diff --git a/src/registrar/views/utility/invitation_helper.py b/src/registrar/views/utility/invitation_helper.py index 18c427940..390467a8c 100644 --- a/src/registrar/views/utility/invitation_helper.py +++ b/src/registrar/views/utility/invitation_helper.py @@ -9,6 +9,7 @@ MissingEmailError, OutsideOrgMemberError, ) +from django.utils.html import format_html logger = logging.getLogger(__name__) @@ -61,7 +62,7 @@ def handle_invitation_exceptions(request, exception, email): """Handle exceptions raised during the process.""" if isinstance(exception, EmailSendingError): logger.warning(exception, exc_info=True) - messages.error(request, str(exception)) + messages.error(request, with_contact_link(str(exception))) elif isinstance(exception, MissingEmailError): messages.error(request, str(exception)) logger.error(exception, exc_info=True) @@ -72,7 +73,17 @@ def handle_invitation_exceptions(request, exception, email): elif isinstance(exception, AlreadyDomainInvitedError): messages.error(request, str(exception)) elif isinstance(exception, IntegrityError): - messages.error(request, f"{email} is already a manager for this domain") + messages.error(request, f"An unexpected error occurred: {email} could not be added to this domain.") else: logger.warning("Could not send email invitation (Other Exception)", exc_info=True) - messages.error(request, "Could not send email invitation.") + messages.error( + request, with_contact_link(f"An unexpected error occurred: {email} could not be added to this domain.") + ) + + +def with_contact_link(error_message: str, contact_url: str = "https://get.gov/contact") -> str: + return format_html( + '{} Try again, and if the problem persists, contact us.', + error_message, + contact_url, + )