diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js index 140421db5..94fdd8b07 100644 --- a/src/registrar/assets/js/get-gov-admin.js +++ b/src/registrar/assets/js/get-gov-admin.js @@ -426,6 +426,10 @@ function initializeWidgetOnList(list, parentId) { let statusSelect = document.getElementById('id_status'); function moveStatusChangelog(actionNeededReasonFormGroup, statusSelect) { + if (!actionNeededReasonFormGroup || !statusSelect) { + return; + } + let flexContainer = actionNeededReasonFormGroup.querySelector('.flex-container'); let statusChangelog = document.getElementById('dja-status-changelog'); diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index 7f64e9341..d26bb3284 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -7,6 +7,7 @@ from django.db import models from django_fsm import FSMField, transition # type: ignore from django.utils import timezone +from waffle import flag_is_active from registrar.models.domain import Domain from registrar.models.federal_agency import FederalAgency from registrar.models.utility.generic_helper import CreateOrUpdateOrganizationTypeHelper @@ -17,8 +18,6 @@ from ..utility.email import send_templated_email, EmailSendingError from itertools import chain -from waffle.decorators import flag_is_active - logger = logging.getLogger(__name__) @@ -675,34 +674,50 @@ def delete_and_clean_up_domain(self, called_from): def _send_status_update_email( self, new_status, email_template, email_template_subject, send_email=True, bcc_address="", wrap_email=False ): - """Send a status update email to the submitter. + """Send a status update email to the creator. - The email goes to the email address that the submitter gave as their - contact information. If there is not submitter information, then do + The email goes to the email address that the creator gave as their + contact information. If there is not creator information, then do nothing. + If the waffle flag "profile_feature" is active, then this email will be sent to the + domain request creator rather than the submitter + send_email: bool -> Used to bypass the send_templated_email function, in the event we just want to log that an email would have been sent, rather than actually sending one. + + wrap_email: bool -> Wraps emails using `wrap_text_and_preserve_paragraphs` if any given + paragraph exceeds our desired max length (for prettier display). """ - if self.submitter is None or self.submitter.email is None: - logger.warning(f"Cannot send {new_status} email, no submitter email address.") + recipient = self.creator if flag_is_active(None, "profile_feature") else self.submitter + if recipient is None or recipient.email is None: + logger.warning( + f"Cannot send {new_status} email, no creator email address for domain request with pk: {self.pk}." + f" Name: {self.requested_domain.name}" + if self.requested_domain + else "" + ) return None if not send_email: - logger.info(f"Email was not sent. Would send {new_status} email: {self.submitter.email}") + logger.info(f"Email was not sent. Would send {new_status} email to: {recipient.email}") return None try: send_templated_email( email_template, email_template_subject, - self.submitter.email, - context={"domain_request": self}, + recipient.email, + context={ + "domain_request": self, + # This is the user that we refer to in the email + "recipient": recipient, + }, bcc_address=bcc_address, wrap_email=wrap_email, ) - logger.info(f"The {new_status} email sent to: {self.submitter.email}") + logger.info(f"The {new_status} email sent to: {recipient.email}") except EmailSendingError: logger.warning("Failed to send confirmation email", exc_info=True) diff --git a/src/registrar/templates/emails/action_needed_reasons/already_has_domains.txt b/src/registrar/templates/emails/action_needed_reasons/already_has_domains.txt index 842b4ac4f..264fe265b 100644 --- a/src/registrar/templates/emails/action_needed_reasons/already_has_domains.txt +++ b/src/registrar/templates/emails/action_needed_reasons/already_has_domains.txt @@ -1,5 +1,5 @@ {% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #} -Hi, {{ domain_request.submitter.first_name }}. +Hi, {{ recipient.first_name }}. We've identified an action that you’ll need to complete before we continue reviewing your .gov domain request. diff --git a/src/registrar/templates/emails/action_needed_reasons/bad_name.txt b/src/registrar/templates/emails/action_needed_reasons/bad_name.txt index beba3521d..95967639c 100644 --- a/src/registrar/templates/emails/action_needed_reasons/bad_name.txt +++ b/src/registrar/templates/emails/action_needed_reasons/bad_name.txt @@ -1,5 +1,5 @@ {% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #} -Hi, {{ domain_request.submitter.first_name }}. +Hi, {{ recipient.first_name }}. We've identified an action that you’ll need to complete before we continue reviewing your .gov domain request. diff --git a/src/registrar/templates/emails/action_needed_reasons/eligibility_unclear.txt b/src/registrar/templates/emails/action_needed_reasons/eligibility_unclear.txt index df8db313d..280321045 100644 --- a/src/registrar/templates/emails/action_needed_reasons/eligibility_unclear.txt +++ b/src/registrar/templates/emails/action_needed_reasons/eligibility_unclear.txt @@ -1,5 +1,5 @@ {% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #} -Hi, {{ domain_request.submitter.first_name }}. +Hi, {{ recipient.first_name }}. We've identified an action that you’ll need to complete before we continue reviewing your .gov domain request. diff --git a/src/registrar/templates/emails/action_needed_reasons/questionable_authorizing_official.txt b/src/registrar/templates/emails/action_needed_reasons/questionable_authorizing_official.txt index 1c2c5d855..259968204 100644 --- a/src/registrar/templates/emails/action_needed_reasons/questionable_authorizing_official.txt +++ b/src/registrar/templates/emails/action_needed_reasons/questionable_authorizing_official.txt @@ -1,5 +1,5 @@ {% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #} -Hi, {{ domain_request.submitter.first_name }}. +Hi, {{ recipient.first_name }}. We've identified an action that you’ll need to complete before we continue reviewing your .gov domain request. diff --git a/src/registrar/templates/emails/domain_request_withdrawn.txt b/src/registrar/templates/emails/domain_request_withdrawn.txt index 5ef429541..0c061c53c 100644 --- a/src/registrar/templates/emails/domain_request_withdrawn.txt +++ b/src/registrar/templates/emails/domain_request_withdrawn.txt @@ -1,5 +1,5 @@ {% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #} -Hi, {{ domain_request.submitter.first_name }}. +Hi, {{ recipient.first_name }}. Your .gov domain request has been withdrawn and will not be reviewed by our team. diff --git a/src/registrar/templates/emails/includes/domain_request_summary.txt b/src/registrar/templates/emails/includes/domain_request_summary.txt index 10ec9ac2c..b3f6c0d95 100644 --- a/src/registrar/templates/emails/includes/domain_request_summary.txt +++ b/src/registrar/templates/emails/includes/domain_request_summary.txt @@ -43,7 +43,7 @@ Purpose of your domain: {{ domain_request.purpose }} Your contact information: -{% spaceless %}{% include "emails/includes/contact.txt" with contact=domain_request.submitter %}{% endspaceless %} +{% spaceless %}{% include "emails/includes/contact.txt" with contact=recipient %}{% endspaceless %} Other employees from your organization:{% for other in domain_request.other_contacts.all %} {% spaceless %}{% include "emails/includes/contact.txt" with contact=other %}{% endspaceless %} diff --git a/src/registrar/templates/emails/status_change_approved.txt b/src/registrar/templates/emails/status_change_approved.txt index cab88d973..bbef8c81a 100644 --- a/src/registrar/templates/emails/status_change_approved.txt +++ b/src/registrar/templates/emails/status_change_approved.txt @@ -1,5 +1,5 @@ {% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #} -Hi, {{ domain_request.submitter.first_name }}. +Hi, {{ recipient.first_name }}. Congratulations! Your .gov domain request has been approved. diff --git a/src/registrar/templates/emails/status_change_rejected.txt b/src/registrar/templates/emails/status_change_rejected.txt index 4febb4c64..e377b12ff 100644 --- a/src/registrar/templates/emails/status_change_rejected.txt +++ b/src/registrar/templates/emails/status_change_rejected.txt @@ -1,5 +1,5 @@ {% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #} -Hi, {{ domain_request.submitter.first_name }}. +Hi, {{ recipient.first_name }}. Your .gov domain request has been rejected. diff --git a/src/registrar/templates/emails/submission_confirmation.txt b/src/registrar/templates/emails/submission_confirmation.txt index c0ae3bb8f..2dd4387a4 100644 --- a/src/registrar/templates/emails/submission_confirmation.txt +++ b/src/registrar/templates/emails/submission_confirmation.txt @@ -1,5 +1,5 @@ {% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #} -Hi, {{ domain_request.submitter.first_name }}. +Hi, {{ recipient.first_name }}. We received your .gov domain request. diff --git a/src/registrar/tests/test_models.py b/src/registrar/tests/test_models.py index 272996147..37fdc5354 100644 --- a/src/registrar/tests/test_models.py +++ b/src/registrar/tests/test_models.py @@ -25,6 +25,7 @@ from .common import MockSESClient, less_console_noise, completed_domain_request, set_domain_request_investigators from django_fsm import TransitionNotAllowed +from waffle.testutils import override_flag # Test comment for push -- will remove @@ -33,29 +34,44 @@ @boto3_mocking.patching class TestDomainRequest(TestCase): def setUp(self): + + self.dummy_user, _ = Contact.objects.get_or_create( + email="mayor@igorville.com", first_name="Hello", last_name="World" + ) + self.dummy_user_2, _ = User.objects.get_or_create( + username="intern@igorville.com", email="intern@igorville.com", first_name="Lava", last_name="World" + ) self.started_domain_request = completed_domain_request( - status=DomainRequest.DomainRequestStatus.STARTED, name="started.gov" + status=DomainRequest.DomainRequestStatus.STARTED, + name="started.gov", ) self.submitted_domain_request = completed_domain_request( - status=DomainRequest.DomainRequestStatus.SUBMITTED, name="submitted.gov" + status=DomainRequest.DomainRequestStatus.SUBMITTED, + name="submitted.gov", ) self.in_review_domain_request = completed_domain_request( - status=DomainRequest.DomainRequestStatus.IN_REVIEW, name="in-review.gov" + status=DomainRequest.DomainRequestStatus.IN_REVIEW, + name="in-review.gov", ) self.action_needed_domain_request = completed_domain_request( - status=DomainRequest.DomainRequestStatus.ACTION_NEEDED, name="action-needed.gov" + status=DomainRequest.DomainRequestStatus.ACTION_NEEDED, + name="action-needed.gov", ) self.approved_domain_request = completed_domain_request( - status=DomainRequest.DomainRequestStatus.APPROVED, name="approved.gov" + status=DomainRequest.DomainRequestStatus.APPROVED, + name="approved.gov", ) self.withdrawn_domain_request = completed_domain_request( - status=DomainRequest.DomainRequestStatus.WITHDRAWN, name="withdrawn.gov" + status=DomainRequest.DomainRequestStatus.WITHDRAWN, + name="withdrawn.gov", ) self.rejected_domain_request = completed_domain_request( - status=DomainRequest.DomainRequestStatus.REJECTED, name="rejected.gov" + status=DomainRequest.DomainRequestStatus.REJECTED, + name="rejected.gov", ) self.ineligible_domain_request = completed_domain_request( - status=DomainRequest.DomainRequestStatus.INELIGIBLE, name="ineligible.gov" + status=DomainRequest.DomainRequestStatus.INELIGIBLE, + name="ineligible.gov", ) # Store all domain request statuses in a variable for ease of use @@ -199,7 +215,9 @@ def test_status_fsm_submit_succeed(self): domain_request.submit() self.assertEqual(domain_request.status, domain_request.DomainRequestStatus.SUBMITTED) - def check_email_sent(self, domain_request, msg, action, expected_count): + def check_email_sent( + self, domain_request, msg, action, expected_count, expected_content=None, expected_email="mayor@igorville.com" + ): """Check if an email was sent after performing an action.""" with self.subTest(msg=msg, action=action): @@ -213,19 +231,35 @@ def check_email_sent(self, domain_request, msg, action, expected_count): sent_emails = [ email for email in MockSESClient.EMAILS_SENT - if "mayor@igorville.gov" in email["kwargs"]["Destination"]["ToAddresses"] + if expected_email in email["kwargs"]["Destination"]["ToAddresses"] ] self.assertEqual(len(sent_emails), expected_count) + if expected_content: + email_content = sent_emails[0]["kwargs"]["Content"]["Simple"]["Body"]["Text"]["Data"] + self.assertIn(expected_content, email_content) + + @override_flag("profile_feature", active=False) def test_submit_from_started_sends_email(self): msg = "Create a domain request and submit it and see if email was sent." - domain_request = completed_domain_request() - self.check_email_sent(domain_request, msg, "submit", 1) + domain_request = completed_domain_request(submitter=self.dummy_user, user=self.dummy_user_2) + self.check_email_sent(domain_request, msg, "submit", 1, expected_content="Hello") + + @override_flag("profile_feature", active=True) + def test_submit_from_started_sends_email_to_creator(self): + """Tests if, when the profile feature flag is on, we send an email to the creator""" + msg = "Create a domain request and submit it and see if email was sent when the feature flag is on." + domain_request = completed_domain_request(submitter=self.dummy_user, user=self.dummy_user_2) + self.check_email_sent( + domain_request, msg, "submit", 1, expected_content="Lava", expected_email="intern@igorville.com" + ) def test_submit_from_withdrawn_sends_email(self): msg = "Create a withdrawn domain request and submit it and see if email was sent." - domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.WITHDRAWN) - self.check_email_sent(domain_request, msg, "submit", 1) + domain_request = completed_domain_request( + status=DomainRequest.DomainRequestStatus.WITHDRAWN, submitter=self.dummy_user + ) + self.check_email_sent(domain_request, msg, "submit", 1, expected_content="Hello") def test_submit_from_action_needed_does_not_send_email(self): msg = "Create a domain request with ACTION_NEEDED status and submit it, check if email was not sent." @@ -239,18 +273,24 @@ def test_submit_from_in_review_does_not_send_email(self): def test_approve_sends_email(self): msg = "Create a domain request and approve it and see if email was sent." - domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW) - self.check_email_sent(domain_request, msg, "approve", 1) + domain_request = completed_domain_request( + status=DomainRequest.DomainRequestStatus.IN_REVIEW, submitter=self.dummy_user + ) + self.check_email_sent(domain_request, msg, "approve", 1, expected_content="Hello") def test_withdraw_sends_email(self): msg = "Create a domain request and withdraw it and see if email was sent." - domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW) - self.check_email_sent(domain_request, msg, "withdraw", 1) + domain_request = completed_domain_request( + status=DomainRequest.DomainRequestStatus.IN_REVIEW, submitter=self.dummy_user + ) + self.check_email_sent(domain_request, msg, "withdraw", 1, expected_content="Hello") def test_reject_sends_email(self): msg = "Create a domain request and reject it and see if email was sent." - domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.APPROVED) - self.check_email_sent(domain_request, msg, "reject", 1) + domain_request = completed_domain_request( + status=DomainRequest.DomainRequestStatus.APPROVED, submitter=self.dummy_user + ) + self.check_email_sent(domain_request, msg, "reject", 1, expected_content="Hello") def test_reject_with_prejudice_does_not_send_email(self): msg = "Create a domain request and reject it with prejudice and see if email was sent."