-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#2355: Custom rejection reason emails - [BACKUP] - MIGRATION #2869
Conversation
🥳 Successfully deployed to developer sandbox hotgov. |
/** An IIFE for admin in DjangoAdmin to listen to changes on the domain request | ||
* status select and to show/hide the rejection reason | ||
*/ | ||
(function (){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic is now consolidated inside of the CustomizableEmailBase
class.
Otherwise, if we add a third customizable field in the future, we'd have to remember to change this on top of the other changes.
🥳 Successfully deployed to developer sandbox hotgov. |
🥳 Successfully deployed to developer sandbox hotgov. |
@@ -500,114 +437,316 @@ function initializeWidgetOnList(list, parentId) { | |||
} | |||
})(); | |||
|
|||
class CustomizableEmailBase { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I was implementing this, I noticed a lot of required code repetition in many different places.
If you weren't very familiar with this part of the codebase it would be painful to work with.
Not only would this cause code drift between rejected and action needed, it would make adding a third custom status email (like for ineligible) difficult.
We already do something similar for the domain / domain request tables
🥳 Successfully deployed to developer sandbox hotgov. |
🥳 Successfully deployed to developer sandbox hotgov. |
# Send out an email if an action needed reason exists | ||
if self.action_needed_reason and self.action_needed_reason != self.ActionNeededReasons.OTHER: | ||
email_content = self.action_needed_reason_email | ||
self._send_action_needed_reason_email(send_email, email_content) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is now centralized in def save()
.
The reason why this is not being done exclusively in action_needed is because one of the ACs conflicts with how the FSM works. More specifically, we want to send out a second email when you directly change the action_needed_reason even if the state is already action_needed.
Rather than duplicating logic twice, of which it is already hard to track, we can centralize everything into save for now. The long term solution for this would probably require a refactor of how we're doing emails in general.
🥳 Successfully deployed to developer sandbox hotgov. |
Remove refactors + simplify logic
🥳 Successfully deployed to developer sandbox hotgov. |
🥳 Successfully deployed to developer sandbox hotgov. |
🥳 Successfully deployed to developer sandbox hotgov. |
1 similar comment
🥳 Successfully deployed to developer sandbox hotgov. |
🥳 Successfully deployed to developer sandbox hotgov. |
1 similar comment
🥳 Successfully deployed to developer sandbox hotgov. |
🥳 Successfully deployed to developer sandbox hotgov. |
1 similar comment
🥳 Successfully deployed to developer sandbox hotgov. |
🥳 Successfully deployed to developer sandbox hotgov. |
@@ -1939,6 +1927,15 @@ def save_model(self, request, obj, form, change): | |||
if should_save: | |||
return super().save_model(request, obj, form, change) | |||
|
|||
def _handle_custom_emails(self, obj): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Much cleaner
}); | ||
} | ||
|
||
initializeModalConfirm() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoa! Unlike you not to do something about this repetitive code!
* @property {string} sessionVariableName - The session variable name. Used for show/hide on textAreaFormGroup/dropdownFormGroup. | ||
* @property {string} apiErrorMessage - The error message that the ajax call returns. | ||
*/ | ||
constructor(config) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If one of the config props does not come through, JS execution will lock up. OPTIONAL: check that all expected config props are there otherwise return early? At the start of the DOM loaded listener.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, makes sense
|
||
# Only send out an email if the underlying reason itself changed or if no email was sent previously. | ||
if status_info.get("cached_reason") != status_info.get("reason") or status_info.get("cached_reason") is None: | ||
bcc_address = settings.DEFAULT_FROM_EMAIL if settings.IS_PRODUCTION else "" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like you missed an opportunity here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain more of your thought process?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nvm
bcc_address=bcc_address, | ||
custom_email_content=status_info.get("email"), | ||
wrap_email=status_information.get("wrap_email"), | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OPTIONAL: Do we want an else block here that logs that send_custom_status_update_email met unexpected conditions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like a try/catch, or just checking for the values of status_info / bcc_address? Not sure I understand
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function gets called. Inside the function, stuff happens only when certain conditions are right, otherwise it's just a ghost function silently running with no effect. I think we should log something to the effect of 'ran silently' which indicates an issue with our app or data
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah true. Yeah I can add more logging here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, added logging on some of the fails. Disagree on logging on the if status_info.get("cached_reason") != status_info.get("reason") or status_info.get("cached_reason") is None:
bit since we aren't supposed to be sending an email in this state anyway . Its not really a failure in this state, but intentional. It'll just create cluttered logs on every save for the model
<button | ||
aria-label="Edit email in textarea" | ||
type="button" | ||
class="usa-button usa-button--unstyled usa-button--dja-link-color usa-button__small-text margin-left-1 text-no-underline field-rejection_reason_email__edit flex-align-self-start" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since these buttons/ modals will never exist on the same page at the same time, I think there's an opportunity to significantly simplify the HTML and the JS by using generic handles (email-edit instead of field-rejection_reason_email__edit).
On the JS: You can now trim down config by moving a lot of the handles to the parent class
On the template: You can probably pull out the modals into an include that's a variant of modal.html, if not modal.html itself (that would be ideal)
It's not insignificant and your code has nothing wrong with it, so I won' block if you decide not to do it, but we should capture this plan for the next custom email ticket if it exists.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since these buttons/ modals will never exist on the same page at the same time
Ah, I think they do actually! Basically the blocks for detail_table_fieldset.html are nested inside in a for loop on each field present on the page. You can verify this via inspect element or by breaking the js somewhere to prevent hiding dom elements.
However, I do really like your train of thought here. We could achieve what your describing by assigning those generic names as classes and having the js infer what the right values are given their expected dom position
You can probably pull out the modals into an include that's a variant of modal.html, if not modal.html itself
You're totally right about that. I'll look at it and see if this is easily achievable (it might be)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay. I went through and simplified the javascript (though more can be done with the formgroups). Let me know what you think.
For the templates + further refinement we can definitely do that in a follow-on like you said
@@ -632,6 +637,7 @@ def assert_email_is_accurate( | |||
with less_console_noise(): | |||
# Access the arguments passed to send_email | |||
call_args = self.mock_client.EMAILS_SENT | |||
logger.info(f"what are the call args? {call_args}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BLOCKING: Remove
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great catch
"""When a rejection reason is set, an email is sent out and [email protected] | ||
is BCC'd in production""" | ||
# Create fake creator | ||
EMAIL = "[email protected]" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Q: Why caps? Good practice for const? I've rarely seen them in the project.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly its just because I copy + pasted another test over as a base, but I do think what you said is correct. What I do like about it here is that its visually distinct when skimming the test though
@@ -337,13 +337,6 @@ def test_withdraw_sends_email(self): | |||
domain_request, msg, "withdraw", 1, expected_content="withdrawn", expected_email=user.email | |||
) | |||
|
|||
@less_console_noise_decorator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Q: This test should still work. Is that not the case? If so why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looked at it again and figured it out. Just had to modify check_email_sent
for this function specifically (just moved the logic out for this one). Thanks for pointing it out.
Basically this test was failing because reject wasn't calling the send email function anymore, as we now handle that on save and with javascript. I thought it wasn't relevant initially, but thinking on it now the fact that we have some save logic means we should keep it anyway
domain_request, | ||
file_path="emails/status_change_rejected.txt", | ||
reason=rejection_reason, | ||
# excluded_reasons=[DomainRequest.RejectionReasons.OTHER] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No opinion on this
500 Err on backup when I try to access a domain request |
|
🥳 Successfully deployed to developer sandbox hotgov. |
Yeah good callout, basically to solve this we'd need to pass in the creator field since whats in the db doesn't match whats on the form in this state. This would complicate the code a bit, and I don't think its that harmful because most the time in prod the creator isn't being changed on email send + they can change it anyway if they need to |
🥳 Successfully deployed to developer sandbox hotgov. |
1 similar comment
🥳 Successfully deployed to developer sandbox hotgov. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Adding a note here that I noticed a small part of the email content in the email footer was different from our content google docs and sent the list to @Katherine-Osos to decide whether to update the docs or the emails.
@witha-c Awesome, thanks! I didn't modify their content at all - so that must've been inaccurate for some time! Good find |
🥳 Successfully deployed to developer sandbox hotgov. |
Ticket
Resolves #2355
Changes
Context for reviewers
This PR makes the rejection reason emails editable, like what we're doing with action needed.
The only (current) difference between the two is that for rejection you can send emails out in the state "other". The only reason for this is that we had content for it. However, this can be changed if it is deemed that it needs changing.
As it turns out, the old logic for action needed email wasn't as flexible as we would have hoped. The infra had to be changed to accommodate for both rejection reason and action needed. This is the main reason for the line count
Setup
Code Review Verification Steps
As the original developer, I have
Satisfied acceptance criteria and met development standards
Ensured code standards are met (Original Developer)
Validated user-facing changes (if applicable)
As a code reviewer, I have
Reviewed, tested, and left feedback about the changes
Ensured code standards are met (Code reviewer)
Validated user-facing changes as a developer
New pages have been added to .pa11yci file so that they will be tested with our automated accessibility testing
Checked keyboard navigability
Meets all designs and user flows provided by design/product
Tested general usability, landmarks, page header structure, and links with a screen reader (such as Voiceover or ANDI)
Tested with multiple browsers, the suggestion is to use ones that the developer didn't (check off which ones were used)
(Rarely needed) Tested as both an analyst and applicant user
Note: Multiple code reviewers can share the checklists above, a second reviewers should not make a duplicate checklist
As a designer reviewer, I have
Verified that the changes match the design intention
Validated user-facing changes as a designer
Checked keyboard navigability
Tested general usability, landmarks, page header structure, and links with a screen reader (such as Voiceover or ANDI)
Tested with multiple browsers (check off which ones were used)
(Rarely needed) Tested as both an analyst and applicant user
Screenshots