diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 11a41a22d..837fb9811 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -21,6 +21,7 @@ from epplibwrapper.errors import ErrorCode, RegistryError from registrar.models.user_domain_role import UserDomainRole from waffle.admin import FlagAdmin from waffle.models import Sample, Switch +from registrar.utility.admin_helpers import get_all_action_needed_reason_emails, get_action_needed_reason_default_email from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website, SeniorOfficial from registrar.utility.constants import BranchChoices from registrar.utility.errors import FSMDomainRequestError, FSMErrorCodes @@ -1956,9 +1957,9 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): # Set the action_needed_reason_email to the default if nothing exists. # Since this check occurs after save, if the user enters a value then we won't update. - default_email = self._get_action_needed_reason_default_email(obj, obj.action_needed_reason) + default_email = get_action_needed_reason_default_email(request, obj, obj.action_needed_reason) if obj.action_needed_reason_email: - emails = self.get_all_action_needed_reason_emails(obj) + emails = get_all_action_needed_reason_emails(request, obj) is_custom_email = obj.action_needed_reason_email not in emails.values() if not is_custom_email: obj.action_needed_reason_email = default_email @@ -2170,8 +2171,6 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): # Initialize extra_context and add filtered entries extra_context = extra_context or {} extra_context["filtered_audit_log_entries"] = filtered_audit_log_entries - emails = self.get_all_action_needed_reason_emails(obj) - extra_context["action_needed_reason_emails"] = json.dumps(emails) extra_context["has_profile_feature_flag"] = flag_is_active(request, "profile_feature") # Denote if an action needed email was sent or not @@ -2183,42 +2182,6 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): # Call the superclass method with updated extra_context return super().change_view(request, object_id, form_url, extra_context) - def get_all_action_needed_reason_emails(self, domain_request): - """Returns a json dictionary of every action needed reason and its associated email - for this particular domain request.""" - - emails = {} - for action_needed_reason in domain_request.ActionNeededReasons: - # Map the action_needed_reason to its default email - emails[action_needed_reason.value] = self._get_action_needed_reason_default_email( - domain_request, action_needed_reason.value - ) - - return emails - - def _get_action_needed_reason_default_email(self, domain_request, action_needed_reason): - """Returns the default email associated with the given action needed reason""" - if not action_needed_reason or action_needed_reason == DomainRequest.ActionNeededReasons.OTHER: - return None - - if flag_is_active(None, "profile_feature"): # type: ignore - recipient = domain_request.creator - else: - recipient = domain_request.submitter - - # Return the context of the rendered views - context = {"domain_request": domain_request, "recipient": recipient} - - # Get the email body - template_path = f"emails/action_needed_reasons/{action_needed_reason}.txt" - - email_body_text = get_template(template_path).render(context=context) - email_body_text_cleaned = None - if email_body_text: - email_body_text_cleaned = email_body_text.strip().lstrip("\n") - - return email_body_text_cleaned - def process_log_entry(self, log_entry): """Process a log entry and return filtered entry dictionary if applicable.""" changes = log_entry.changes diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js index ef9262668..da51b1ae5 100644 --- a/src/registrar/assets/js/get-gov-admin.js +++ b/src/registrar/assets/js/get-gov-admin.js @@ -510,7 +510,7 @@ document.addEventListener('DOMContentLoaded', function() { const dropdown = document.getElementById("id_action_needed_reason"); const textarea = document.getElementById("id_action_needed_reason_email") const domainRequestId = dropdown ? document.getElementById("domain_request_id").value : null - const texareaPlaceholder = document.querySelector(".field-action_needed_reason_email__placeholder"); + const textareaPlaceholder = document.querySelector(".field-action_needed_reason_email__placeholder"); const directEditButton = document.querySelector('.field-action_needed_reason_email__edit'); const modalTrigger = document.querySelector('.field-action_needed_reason_email__modal-trigger'); const modalConfirm = document.getElementById('confirm-edit-email'); @@ -520,11 +520,9 @@ document.addEventListener('DOMContentLoaded', function() { `; let lastSentEmailContent = document.getElementById("last-sent-email-content"); const helpText = document.querySelector('.field-action_needed_reason_email .help'); - const emailData = document.getElementById('action-needed-emails-data'); - const actionNeededEmailData = emailData.textContent; - const actionNeededEmailsJson = JSON.parse(actionNeededEmailData); const initialDropdownValue = dropdown ? dropdown.value : null; - const initialEmailValue = actionNeededEmailData ? actionNeededEmailData.value : null; + const initialEmailValue = textarea.value; + // We will use the const to control the modal let isEmailAlreadySentConst = lastSentEmailContent.value.replace(/\s+/g, '') === textarea.value.replace(/\s+/g, ''); // We will use the function to control the label and help @@ -532,34 +530,36 @@ document.addEventListener('DOMContentLoaded', function() { return lastSentEmailContent.value.replace(/\s+/g, '') === textarea.value.replace(/\s+/g, ''); } - if (!dropdown || !textarea || !domainRequestId || !formLabel || !modalConfirm || !emailData) return; + if (!dropdown || !textarea || !domainRequestId || !formLabel || !modalConfirm) return; + const apiUrl = document.getElementById("get-action-needed-email-for-user-json").value; function updateUserInterface(reason) { if (!reason) { // No reason selected, we will set the label to "Email", show the "Make a selection" placeholder, hide the trigger, textarea, hide the help text formLabel.innerHTML = "Email:"; - showElement(texareaPlaceholder); - texareaPlaceholder.innerHTML = "Select an action needed reason to see email"; + textareaPlaceholder.innerHTML = "Select an action needed reason to see email"; + showElement(textareaPlaceholder); hideElement(directEditButton); hideElement(modalTrigger); hideElement(textarea); hideElement(helpText); - } else if (reason == 'other') { + } else if (reason === 'other') { // 'Other' selected, we will set the label to "Email", show the "No email will be sent" placeholder, hide the trigger, textarea, hide the help text formLabel.innerHTML = "Email:"; + textareaPlaceholder.innerHTML = "No email will be sent"; + showElement(textareaPlaceholder); showElement(helpText); - showElement(texareaPlaceholder); - texareaPlaceholder.innerHTML = "No email will be sent"; hideElement(directEditButton); hideElement(modalTrigger); hideElement(textarea); hideElement(helpText); } else { // A triggering selection is selected, all hands on board: - hideElement(texareaPlaceholder); - showElement(textarea); textarea.setAttribute('readonly', true); + showElement(textarea); showElement(helpText); + hideElement(textareaPlaceholder); + if (isEmailAlreadySentConst) { hideElement(directEditButton); showElement(modalTrigger); @@ -582,13 +582,24 @@ document.addEventListener('DOMContentLoaded', function() { dropdown.addEventListener("change", function() { const reason = dropdown.value; - const emailBody = reason in actionNeededEmailsJson ? actionNeededEmailsJson[reason] : null; - - if (reason && emailBody) { + if (reason && reason !== "other") { // If it's not the initial value if (initialDropdownValue !== dropdown.value || initialEmailValue !== textarea.value) { // Replace the email content - textarea.value = emailBody; + fetch(`${apiUrl}?reason=${reason}&domain_request_id=${domainRequestId}`) + .then(response => { + return response.json().then(data => data); + }) + .then(data => { + if (data.error) { + console.error("Error in AJAX call: " + data.error); + }else { + textarea.value = data.action_needed_email; + } + }) + .catch(error => { + console.error("Error action needed email: ", error) + }); } } diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py index 19fa99809..50f0f99db 100644 --- a/src/registrar/config/urls.py +++ b/src/registrar/config/urls.py @@ -27,6 +27,7 @@ from registrar.views.domain_requests_json import get_domain_requests_json from registrar.views.utility.api_views import ( get_senior_official_from_federal_agency_json, get_federal_and_portfolio_types_from_federal_agency_json, + get_action_needed_email_for_user_json, ) from registrar.views.domains_json import get_domains_json from registrar.views.utility import always_404 @@ -147,6 +148,11 @@ urlpatterns = [ get_federal_and_portfolio_types_from_federal_agency_json, name="get-federal-and-portfolio-types-from-federal-agency-json", ), + path( + "admin/api/get-action-needed-email-for-user-json/", + get_action_needed_email_for_user_json, + name="get-action-needed-email-for-user-json", + ), path("admin/", admin.site.urls), path( "reports/export_data_type_user/", diff --git a/src/registrar/templates/django/admin/domain_request_change_form.html b/src/registrar/templates/django/admin/domain_request_change_form.html index a7d59d22c..e76eefc3a 100644 --- a/src/registrar/templates/django/admin/domain_request_change_form.html +++ b/src/registrar/templates/django/admin/domain_request_change_form.html @@ -8,6 +8,8 @@ {# Store the current object id so we can access it easier #} + {% url 'get-action-needed-email-for-user-json' as url %} + {% for fieldset in adminform %} {% comment %} TODO: this will eventually need to be changed to something like this diff --git a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html index ebf34929f..e9fe93e7b 100644 --- a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html +++ b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html @@ -218,22 +218,8 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html) {% block after_help_text %} {% if field.field.name == "action_needed_reason_email" %} -
- - {% comment %} - Store the action needed reason emails in a json-based dictionary. - This allows us to change the action_needed_reason_email field dynamically, depending on value. - The alternative to this is an API endpoint. - - Given that we have a limited number of emails, doing it this way makes sense. - {% endcomment %} - {% if action_needed_reason_emails %} - - {% endif %} {% elif field.field.name == "creator" %}
diff --git a/src/registrar/utility/admin_helpers.py b/src/registrar/utility/admin_helpers.py new file mode 100644 index 000000000..dff3751bc --- /dev/null +++ b/src/registrar/utility/admin_helpers.py @@ -0,0 +1,42 @@ +from django.http import HttpRequest +from registrar.models.domain_request import DomainRequest +from waffle.decorators import flag_is_active +from django.template.loader import get_template + + +def get_all_action_needed_reason_emails(request, domain_request): + """Returns a dictionary of every action needed reason and its associated email + for this particular domain request.""" + + emails = {} + for action_needed_reason in domain_request.ActionNeededReasons: + # Map the action_needed_reason to its default email + emails[action_needed_reason.value] = get_action_needed_reason_default_email( + request, domain_request, action_needed_reason.value + ) + + return emails + + +def get_action_needed_reason_default_email(request, domain_request, action_needed_reason): + """Returns the default email associated with the given action needed reason""" + if not action_needed_reason or action_needed_reason == DomainRequest.ActionNeededReasons.OTHER: + return None + + if flag_is_active(request, "profile_feature"): # type: ignore + recipient = domain_request.creator + else: + recipient = domain_request.submitter + + # Return the context of the rendered views + context = {"domain_request": domain_request, "recipient": recipient} + + # Get the email body + template_path = f"emails/action_needed_reasons/{action_needed_reason}.txt" + + email_body_text = get_template(template_path).render(context=context) + email_body_text_cleaned = None + if email_body_text: + email_body_text_cleaned = email_body_text.strip().lstrip("\n") + + return email_body_text_cleaned \ No newline at end of file diff --git a/src/registrar/views/utility/api_views.py b/src/registrar/views/utility/api_views.py index 97eb7e86c..6a6269baa 100644 --- a/src/registrar/views/utility/api_views.py +++ b/src/registrar/views/utility/api_views.py @@ -1,10 +1,10 @@ import logging from django.http import JsonResponse from django.forms.models import model_to_dict -from registrar.models import FederalAgency, SeniorOfficial +from registrar.models import FederalAgency, SeniorOfficial, DomainRequest from django.contrib.admin.views.decorators import staff_member_required from django.contrib.auth.decorators import login_required - +from registrar.utility.admin_helpers import get_all_action_needed_reason_emails from registrar.models.portfolio import Portfolio from registrar.utility.constants import BranchChoices @@ -68,3 +68,27 @@ def get_federal_and_portfolio_types_from_federal_agency_json(request): } return JsonResponse(response_data) + + +@login_required +@staff_member_required +def get_action_needed_email_for_user_json(request): + """Returns a default action needed email for a given user""" + + # This API is only accessible to admins and analysts + superuser_perm = request.user.has_perm("registrar.full_access_permission") + analyst_perm = request.user.has_perm("registrar.analyst_access_permission") + if not request.user.is_authenticated or not any([analyst_perm, superuser_perm]): + return JsonResponse({"error": "You do not have access to this resource"}, status=403) + + reason = request.GET.get("reason") + domain_request_id = request.GET.get("domain_request_id") + if not reason: + return JsonResponse({"error": "No reason specified"}, status=404) + + if not domain_request_id: + return JsonResponse({"error": "No domain_request_id specified"}, status=404) + + domain_request = DomainRequest.objects.filter(id=domain_request_id).first() + emails = get_all_action_needed_reason_emails(request, domain_request) + return JsonResponse({"action_needed_email": emails.get(reason)}, status=200)