Skip to content

Commit

Permalink
Merge branch 'develop' into fix/992-notify-data-file-search-selection…
Browse files Browse the repository at this point in the history
…-changes
  • Loading branch information
andrew-jameson authored Nov 3, 2022
2 parents c64d4bc + 5e0284b commit e091cb3
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 5 deletions.
4 changes: 1 addition & 3 deletions tdrs-backend/tdpservice/email/email_enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,5 @@ class EmailType(Enum):
DATA_SUBMISSION_FAILED = 'data-submitted-failed.html'
REQUEST_APPROVED = 'request-approved.html'
REQUEST_DENIED = 'request-denied.html'
INACTIVE_ACCOUNT_10_DAYS = 'inactive-account-10-days.html'
INACTIVE_ACCOUNT_3_DAYS = 'inactive-account-3-days.html'
INACTIVE_ACCOUNT_1_DAYS = 'inactive-account-1-days.html'
DEACTIVATION_WARNING = 'account-deactivation-warning.html'
ACCOUNT_DEACTIVATED = 'account-deactivated.html'
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Helper methods for sending emails to users about their account deactivation."""
from tdpservice.email.email_enums import EmailType
from tdpservice.email.email import automated_email
from datetime import datetime, timedelta, timezone


def send_deactivation_warning_email(users, days):
"""Send an email to users that are about to be deactivated."""
template_path = EmailType.DEACTIVATION_WARNING.value
text_message = f'Your account will be deactivated in {days} days.'
subject = f'Account Deactivation Warning: {days} days remaining'
deactivation_date = datetime.now(timezone.utc) + timedelta(days=days)

for user in users:
recipient_email = user.email
context = {
'first_name': user.first_name,
'days': days,
'deactivation_date': deactivation_date
}

logger_context = {
'user_id': user.id,
'object_id': user.id,
'object_repr': user.email
}

automated_email.delay(
email_path=template_path,
recipient_email=recipient_email,
subject=subject,
email_context=context,
text_message=text_message,
logger_context=logger_context
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Test functions for email helper."""
import pytest

import tdpservice
from tdpservice.email.helpers.account_deactivation_warning import send_deactivation_warning_email


@pytest.mark.django_db
def test_send_deactivation_warning_email(mocker, user):
"""Test that the send_deactivation_warning_email function runs."""
mocker.patch(
'tdpservice.email.helpers.account_deactivation_warning.automated_email.delay',
return_value=None
)
send_deactivation_warning_email([user], 10)

assert tdpservice.email.email.automated_email.delay.called_once_with(
recipient_email=user.email,
subject='Account Deactivation Warning: 10 days remaining',
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{% extends 'base.html' %}
{% block content %}
<!-- Body copy -->
<p style="color: #000000;">
<p>Dear {{ first_name }},</p>

<p>We noticed that you have not logged into the TANF Data Portal (TDP) for almost 180 days.</p>

<p>Since your account has been inactive, it’s scheduled to be deactivated in <b>{{ days }} day(s)</b> on <b>{{ deactivation_date }}</b> at <b>5PM EST</b>. However, if you are interested in keeping your account, it’s easy! </p>

<!-- This link uses descriptive text to inform the user what will happen with the link is tapped. -->
<!-- It also uses inline styles since some email clients won't render embedded styles from the head. -->
<!-- <a href="" style="color: #336a90; text-decoration: underline;">Reset your password now</a> -->
<p><a class="button" href="{{ url }}"
style="background-color:#336a90;border-radius:4px;color:#ffffff;display:inline-block;font-family:sans-serif;font-size:18px;font-weight:bold;line-height:60px;text-align:center;text-decoration:none;width: auto; padding-left: 24px; padding-right: 24px;-webkit-text-size-adjust:none;">Sign in to keep your account active</a></p>

<b>Need help?</b><br>
We're here for you! Check out the <a href="http://tdp-project-updates.app.cloud.gov/knowledge-center/" target="_blank">Getting Started</a> guide or reach out to the TDP Admin team at <a href="mailto:TANFData@acf.hhs.gov" target="_blank">TANFData@acf.hhs.gov</a>.

<p>Thank you,</p>
TDP Team
</p>
{% endblock %}
35 changes: 35 additions & 0 deletions tdrs-backend/tdpservice/scheduling/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Tasks that are triggered by the scheduler."""
from __future__ import absolute_import
from tdpservice.users.models import User, AccountApprovalStatusChoices

from django.utils import timezone
from celery import shared_task
from datetime import datetime, timedelta
import logging
from tdpservice.email.helpers.account_deactivation_warning import send_deactivation_warning_email


logger = logging.getLogger(__name__)

@shared_task
def check_for_accounts_needing_deactivation_warning():
"""Check for accounts that need deactivation warning emails."""
deactivate_in_10_days = users_to_deactivate(10)
deactivate_in_3_days = users_to_deactivate(3)
deactivate_in_1_day = users_to_deactivate(1)

if deactivate_in_10_days:
send_deactivation_warning_email(deactivate_in_10_days, 10)
if deactivate_in_3_days:
send_deactivation_warning_email(deactivate_in_3_days, 3)
if deactivate_in_1_day:
send_deactivation_warning_email(deactivate_in_1_day, 1)

def users_to_deactivate(days):
"""Return a list of users that have not logged in in the last {180 - days} days."""
days = 180 - days
return User.objects.filter(
last_login__lte=datetime.now(tz=timezone.utc) - timedelta(days=days),
last_login__gte=datetime.now(tz=timezone.utc) - timedelta(days=days+1),
account_approval_status=AccountApprovalStatusChoices.APPROVED,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""Test functions for deactivated account warnings."""
from django.utils import timezone

import pytest
import tdpservice
from datetime import datetime, timedelta
from tdpservice.scheduling.tasks import check_for_accounts_needing_deactivation_warning
from tdpservice.users.models import AccountApprovalStatusChoices

import logging
logger = logging.getLogger(__name__)


@pytest.mark.django_db
def test_deactivation_email_10_days(user, mocker):
"""Test that the check_for_accounts_needing_deactivation_warning task runs."""
mocker.patch(
'tdpservice.email.helpers.account_deactivation_warning.send_deactivation_warning_email',
return_value=None
)
mocker.patch(
'tdpservice.scheduling.tasks.users_to_deactivate',
return_value=[user]
)

user.last_login = datetime.now(tz=timezone.utc) - timedelta(days=170)
user.first_name = 'UniqueName'
user.save()
check_for_accounts_needing_deactivation_warning()
assert tdpservice.email.helpers.account_deactivation_warning.send_deactivation_warning_email.called_once_with(
users=[user], days=10)

@pytest.mark.django_db
def test_deactivation_email_3_days(user, mocker):
"""Test that the check_for_accounts_needing_deactivation_warning task runs."""
mocker.patch(
'tdpservice.email.helpers.account_deactivation_warning.send_deactivation_warning_email',
return_value=None
)
mocker.patch(
'tdpservice.scheduling.tasks.users_to_deactivate',
return_value=[user]
)

user.last_login = datetime.now(tz=timezone.utc) - timedelta(days=177)
user.first_name = 'UniqueName'
user.save()
check_for_accounts_needing_deactivation_warning()
assert tdpservice.email.helpers.account_deactivation_warning.send_deactivation_warning_email.called_once_with(
users=[user], days=3)

@pytest.mark.django_db
def test_deactivation_email_1_days(user, mocker):
"""Test that the check_for_accounts_needing_deactivation_warning task runs."""
mocker.patch(
'tdpservice.email.helpers.account_deactivation_warning.send_deactivation_warning_email',
return_value=None
)
mocker.patch(
'tdpservice.scheduling.tasks.users_to_deactivate',
return_value=[user]
)

user.last_login = datetime.now(tz=timezone.utc) - timedelta(days=179)
user.first_name = 'UniqueName'
user.save()
check_for_accounts_needing_deactivation_warning()
assert tdpservice.email.helpers.account_deactivation_warning.send_deactivation_warning_email.called_once_with(
users=[user], days=1)


@pytest.mark.django_db
def test_no_users_to_warn(user, mocker):
"""Test that the check_for_accounts_needing_deactivation_warning task runs."""
mocker.patch(
'tdpservice.email.helpers.account_deactivation_warning.send_deactivation_warning_email',
return_value=None
)
mocker.patch(
'tdpservice.scheduling.tasks.users_to_deactivate',
return_value=[user]
)

user.last_login = datetime.now() - timedelta(days=169)
user.first_name = 'UniqueName'
user.save()
check_for_accounts_needing_deactivation_warning()
tdpservice.email.helpers.account_deactivation_warning.send_deactivation_warning_email.assert_not_called()

@pytest.mark.django_db
def test_users_to_deactivate(user):
"""Test that the users_to_deactivate function returns the correct users."""
user.last_login = datetime.now() - timedelta(days=170)
user.first_name = 'UniqueName'
user.account_approval_status = AccountApprovalStatusChoices.APPROVED
user.save()
users = tdpservice.scheduling.tasks.users_to_deactivate(10)
assert users[0].first_name == user.first_name
11 changes: 11 additions & 0 deletions tdrs-backend/tdpservice/settings/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from typing import Any, Optional

from django.core.exceptions import ImproperlyConfigured
from celery.schedules import crontab

from configurations import Configuration

Expand Down Expand Up @@ -424,3 +425,13 @@ class Common(Configuration):
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'UTC'

CELERY_BEAT_SCHEDULE = {
'name': {
'task': 'tdpservice.scheduling.tasks.check_for_accounts_needing_deactivation_warning',
'schedule': crontab(day_of_week='*', hour='13', minute='*'), # Every day at 1pm UTC (9am EST)
'options': {
'expires': 15.0,
},
},
}
2 changes: 1 addition & 1 deletion tdrs-frontend/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: "3.4"
services:
zaproxy:
image: owasp/zap2docker-stable
image: owasp/zap2docker-stable:2.11.0
container_name: zap-scan
command: sleep 3600
ports:
Expand Down
1 change: 0 additions & 1 deletion tdrs-frontend/nginx/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,4 @@ server {
include locations.conf;
add_header Content-Security-Policy "default-src 'self'; *.acf.hhs.gov; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; *.acf.hhs.gov manifest-src 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'none';";
add_header Access-Control-Allow-Origin "https://tanfdata.acf.hhs.gov";

}
2 changes: 2 additions & 0 deletions tdrs-frontend/src/components/Reports/Reports.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ describe('Reports', () => {
const origDispatch = store.dispatch
store.dispatch = jest.fn(origDispatch)

window.HTMLElement.prototype.scrollIntoView = jest.fn(() => null)

const { getByText, getByLabelText, getByRole } = render(
<Provider store={store}>
<Reports />
Expand Down
8 changes: 8 additions & 0 deletions tdrs-frontend/src/components/UploadReport/UploadReport.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ function UploadReport({ handleCancel, header, stt, submitEnabled }) {
type: null,
message: null,
})
const alertRef = useRef(null)

// Ensure newly rendered header is focused,
// else it won't be read be screen readers.
Expand Down Expand Up @@ -84,6 +85,12 @@ function UploadReport({ handleCancel, header, stt, submitEnabled }) {
fileInput.init()
}, [])

useEffect(() => {
if (localAlert.active && alertRef && alertRef.current) {
alertRef.current.scrollIntoView({ behavior: 'smooth' })
}
}, [localAlert, alertRef])

return (
<>
<h2
Expand All @@ -95,6 +102,7 @@ function UploadReport({ handleCancel, header, stt, submitEnabled }) {
</h2>
{localAlert.active && (
<div
ref={alertRef}
className={classNames('usa-alert usa-alert--slim', {
[`usa-alert--${localAlert.type}`]: true,
})}
Expand Down

0 comments on commit e091cb3

Please sign in to comment.