Use API instead of json

This commit is contained in:
zandercymatics 2024-09-04 15:08:45 -06:00
parent ec9f085371
commit 487c2e5d62
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
7 changed files with 107 additions and 73 deletions

View file

@ -21,6 +21,7 @@ from epplibwrapper.errors import ErrorCode, RegistryError
from registrar.models.user_domain_role import UserDomainRole from registrar.models.user_domain_role import UserDomainRole
from waffle.admin import FlagAdmin from waffle.admin import FlagAdmin
from waffle.models import Sample, Switch 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.models import Contact, Domain, DomainRequest, DraftDomain, User, Website, SeniorOfficial
from registrar.utility.constants import BranchChoices from registrar.utility.constants import BranchChoices
from registrar.utility.errors import FSMDomainRequestError, FSMErrorCodes 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. # 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. # 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: 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() is_custom_email = obj.action_needed_reason_email not in emails.values()
if not is_custom_email: if not is_custom_email:
obj.action_needed_reason_email = default_email obj.action_needed_reason_email = default_email
@ -2170,8 +2171,6 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
# Initialize extra_context and add filtered entries # Initialize extra_context and add filtered entries
extra_context = extra_context or {} extra_context = extra_context or {}
extra_context["filtered_audit_log_entries"] = filtered_audit_log_entries 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") extra_context["has_profile_feature_flag"] = flag_is_active(request, "profile_feature")
# Denote if an action needed email was sent or not # 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 # Call the superclass method with updated extra_context
return super().change_view(request, object_id, form_url, 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): def process_log_entry(self, log_entry):
"""Process a log entry and return filtered entry dictionary if applicable.""" """Process a log entry and return filtered entry dictionary if applicable."""
changes = log_entry.changes changes = log_entry.changes

View file

@ -510,7 +510,7 @@ document.addEventListener('DOMContentLoaded', function() {
const dropdown = document.getElementById("id_action_needed_reason"); const dropdown = document.getElementById("id_action_needed_reason");
const textarea = document.getElementById("id_action_needed_reason_email") const textarea = document.getElementById("id_action_needed_reason_email")
const domainRequestId = dropdown ? document.getElementById("domain_request_id").value : null 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 directEditButton = document.querySelector('.field-action_needed_reason_email__edit');
const modalTrigger = document.querySelector('.field-action_needed_reason_email__modal-trigger'); const modalTrigger = document.querySelector('.field-action_needed_reason_email__modal-trigger');
const modalConfirm = document.getElementById('confirm-edit-email'); const modalConfirm = document.getElementById('confirm-edit-email');
@ -520,11 +520,9 @@ document.addEventListener('DOMContentLoaded', function() {
</svg>`; </svg>`;
let lastSentEmailContent = document.getElementById("last-sent-email-content"); let lastSentEmailContent = document.getElementById("last-sent-email-content");
const helpText = document.querySelector('.field-action_needed_reason_email .help'); 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 initialDropdownValue = dropdown ? dropdown.value : null;
const initialEmailValue = actionNeededEmailData ? actionNeededEmailData.value : null; const initialEmailValue = textarea.value;
// We will use the const to control the modal // We will use the const to control the modal
let isEmailAlreadySentConst = lastSentEmailContent.value.replace(/\s+/g, '') === textarea.value.replace(/\s+/g, ''); let isEmailAlreadySentConst = lastSentEmailContent.value.replace(/\s+/g, '') === textarea.value.replace(/\s+/g, '');
// We will use the function to control the label and help // 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, ''); 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) { function updateUserInterface(reason) {
if (!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 // 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:"; formLabel.innerHTML = "Email:";
showElement(texareaPlaceholder); textareaPlaceholder.innerHTML = "Select an action needed reason to see email";
texareaPlaceholder.innerHTML = "Select an action needed reason to see email"; showElement(textareaPlaceholder);
hideElement(directEditButton); hideElement(directEditButton);
hideElement(modalTrigger); hideElement(modalTrigger);
hideElement(textarea); hideElement(textarea);
hideElement(helpText); 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 // '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:"; formLabel.innerHTML = "Email:";
textareaPlaceholder.innerHTML = "No email will be sent";
showElement(textareaPlaceholder);
showElement(helpText); showElement(helpText);
showElement(texareaPlaceholder);
texareaPlaceholder.innerHTML = "No email will be sent";
hideElement(directEditButton); hideElement(directEditButton);
hideElement(modalTrigger); hideElement(modalTrigger);
hideElement(textarea); hideElement(textarea);
hideElement(helpText); hideElement(helpText);
} else { } else {
// A triggering selection is selected, all hands on board: // A triggering selection is selected, all hands on board:
hideElement(texareaPlaceholder);
showElement(textarea);
textarea.setAttribute('readonly', true); textarea.setAttribute('readonly', true);
showElement(textarea);
showElement(helpText); showElement(helpText);
hideElement(textareaPlaceholder);
if (isEmailAlreadySentConst) { if (isEmailAlreadySentConst) {
hideElement(directEditButton); hideElement(directEditButton);
showElement(modalTrigger); showElement(modalTrigger);
@ -582,13 +582,24 @@ document.addEventListener('DOMContentLoaded', function() {
dropdown.addEventListener("change", function() { dropdown.addEventListener("change", function() {
const reason = dropdown.value; const reason = dropdown.value;
const emailBody = reason in actionNeededEmailsJson ? actionNeededEmailsJson[reason] : null; if (reason && reason !== "other") {
if (reason && emailBody) {
// If it's not the initial value // If it's not the initial value
if (initialDropdownValue !== dropdown.value || initialEmailValue !== textarea.value) { if (initialDropdownValue !== dropdown.value || initialEmailValue !== textarea.value) {
// Replace the email content // 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)
});
} }
} }

View file

@ -27,6 +27,7 @@ from registrar.views.domain_requests_json import get_domain_requests_json
from registrar.views.utility.api_views import ( from registrar.views.utility.api_views import (
get_senior_official_from_federal_agency_json, get_senior_official_from_federal_agency_json,
get_federal_and_portfolio_types_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.domains_json import get_domains_json
from registrar.views.utility import always_404 from registrar.views.utility import always_404
@ -147,6 +148,11 @@ urlpatterns = [
get_federal_and_portfolio_types_from_federal_agency_json, get_federal_and_portfolio_types_from_federal_agency_json,
name="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("admin/", admin.site.urls),
path( path(
"reports/export_data_type_user/", "reports/export_data_type_user/",

View file

@ -8,6 +8,8 @@
{# Store the current object id so we can access it easier #} {# Store the current object id so we can access it easier #}
<input id="domain_request_id" class="display-none" value="{{original.id}}" /> <input id="domain_request_id" class="display-none" value="{{original.id}}" />
<input id="has_audit_logs" class="display-none" value="{%if filtered_audit_log_entries %}true{% else %}false{% endif %}"/> <input id="has_audit_logs" class="display-none" value="{%if filtered_audit_log_entries %}true{% else %}false{% endif %}"/>
{% url 'get-action-needed-email-for-user-json' as url %}
<input id="get-action-needed-email-for-user-json" class="display-none" value="{{ url }}" />
{% for fieldset in adminform %} {% for fieldset in adminform %}
{% comment %} {% comment %}
TODO: this will eventually need to be changed to something like this TODO: this will eventually need to be changed to something like this

View file

@ -218,22 +218,8 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html)
{% block after_help_text %} {% block after_help_text %}
{% if field.field.name == "action_needed_reason_email" %} {% if field.field.name == "action_needed_reason_email" %}
<div class="help"> <div class="help">
</div> </div>
{% 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 %}
<script id="action-needed-emails-data" type="application/json">
{{ action_needed_reason_emails|safe }}
</script>
{% endif %}
{% elif field.field.name == "creator" %} {% elif field.field.name == "creator" %}
<div class="flex-container tablet:margin-top-2"> <div class="flex-container tablet:margin-top-2">
<label aria-label="Creator contact details"></label> <label aria-label="Creator contact details"></label>

View file

@ -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

View file

@ -1,10 +1,10 @@
import logging import logging
from django.http import JsonResponse from django.http import JsonResponse
from django.forms.models import model_to_dict 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.admin.views.decorators import staff_member_required
from django.contrib.auth.decorators import login_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.models.portfolio import Portfolio
from registrar.utility.constants import BranchChoices 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) 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)