Skip to content

Commit

Permalink
chore: PR feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
brobro10000 committed Jan 25, 2024
1 parent c735a39 commit 03ff109
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 97 deletions.
22 changes: 19 additions & 3 deletions enterprise_access/apps/content_assignments/content_metadata_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
API file interacting with assignment metadata (created to avoid a circular
import error)
"""
from datetime import datetime
import datetime

from django.utils import timezone

from enterprise_access.apps.content_metadata.api import get_and_cache_catalog_content_metadata

Expand Down Expand Up @@ -60,7 +62,7 @@ def get_human_readable_date(datetime_string, output_pattern=DEFAULT_STRFTIME_PAT
return None


def parse_datetime_string(datetime_string):
def parse_datetime_string(datetime_string, **set_to_utc):
"""
Given a datetime string value from some content metadata record,
parse it into a datetime object.
Expand All @@ -71,7 +73,10 @@ def parse_datetime_string(datetime_string):
last_exception = None
for input_pattern in DATE_INPUT_PATTERNS:
try:
return datetime.strptime(datetime_string, input_pattern)
formatted_date = datetime.datetime.strptime(datetime_string, input_pattern)
if set_to_utc:
return formatted_date.replace(tzinfo=datetime.timezone.utc)
return formatted_date
except ValueError as exc:
last_exception = exc

Expand All @@ -97,3 +102,14 @@ def get_course_partners(course_metadata):
if len(names) == 2:
return ' and '.join(names)
return ', '.join(names[:-1]) + ', and ' + names[-1]


def is_date_n_days_from_now(target_datetime, num_days):
"""
Takes an integer number of days to offset from the date_to_offset to determine if
the target_date matches the date_to_offset + days_offset date
The target_date and date_to_offset arguments are UTC timezone objects
"""
future_datetime = timezone.now() + timezone.timedelta(days=num_days)
return target_datetime.date() == future_datetime.date()
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@

from django.core.management.base import BaseCommand
from django.core.paginator import Paginator
from django.utils import timezone

from enterprise_access.apps.content_assignments.api import send_reminder_email_for_pending_assignment
from enterprise_access.apps.content_assignments.constants import LearnerContentAssignmentStateChoices
from enterprise_access.apps.content_assignments.content_metadata_api import get_content_metadata_for_assignments
from enterprise_access.apps.content_assignments.content_metadata_api import (
get_content_metadata_for_assignments,
is_date_n_days_from_now,
parse_datetime_string
)
from enterprise_access.apps.content_assignments.models import AssignmentConfiguration
from enterprise_access.apps.content_assignments.utils import are_dates_matching_with_day_offset
from enterprise_access.apps.content_assignments.tasks import send_exec_ed_enrollment_warmer

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -102,17 +104,15 @@ def handle(self, *args, **options):
start_date = content_metadata.get('normalized_metadata', {}).get('start_date')
course_type = content_metadata.get('course_type')

is_executive_education_course_type = course_type in (
'executive-education-2u', 'executive-education')
is_executive_education_course_type = course_type == 'executive-education-2u'

# Determine if the date from today + days_before_course_state_date is
# equal to the date of the start date
# If they are equal, then send the nudge email, otherwise continue
datetime_start_date = self.to_datetime(start_date)
can_send_nudge_notification_in_advance = are_dates_matching_with_day_offset(
days_offset=days_before_course_start_date,
target_date=datetime_start_date,
date_to_offset=timezone.now(),
datetime_start_date = parse_datetime_string(start_date, set_to_utc=True)
can_send_nudge_notification_in_advance = is_date_n_days_from_now(
target_datetime=datetime_start_date,
num_days=days_before_course_start_date
)

if is_executive_education_course_type and can_send_nudge_notification_in_advance:
Expand All @@ -122,14 +122,15 @@ def handle(self, *args, **options):
'days_before_course_start_date: [%s], can_send_nudge_notification_in_advance: [%s], '
'course_type: [%s], dry_run [%s]'
)
logger.info(message,
assignment_configuration.uuid,
start_date,
datetime_start_date,
days_before_course_start_date,
can_send_nudge_notification_in_advance,
course_type,
dry_run,
)
logger.info(
message,
assignment_configuration.uuid,
start_date,
datetime_start_date,
days_before_course_start_date,
can_send_nudge_notification_in_advance,
course_type,
dry_run,
)
if not dry_run:
send_reminder_email_for_pending_assignment.delay(assignment.uuid)
send_exec_ed_enrollment_warmer.delay(assignment.uuid, days_before_course_start_date)
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def setUp(self):
)

@mock.patch(COMMAND_PATH + '.get_content_metadata_for_assignments')
@mock.patch('enterprise_access.apps.content_assignments.api.send_reminder_email_for_pending_assignment.delay')
@mock.patch('enterprise_access.apps.content_assignments.tasks.send_exec_ed_enrollment_warmer.delay')
@mock.patch('enterprise_access.apps.subsidy_access_policy.models.SubsidyAccessPolicy.subsidy_client')
def test_command_dry_run(
self,
Expand All @@ -113,14 +113,10 @@ def test_command_dry_run(
"""
Verify that management command work as expected in dry run mode.
"""
enrollment_end = timezone.now() - timezone.timedelta(days=5)
enrollment_end = enrollment_end.replace(microsecond=0)
subsidy_expiry = timezone.now() + timezone.timedelta(days=5)
subsidy_expiry = subsidy_expiry.replace(microsecond=0)
start_date = timezone.now() + timezone.timedelta(days=30)
start_date = start_date.replace(microsecond=0)
end_date = timezone.now() + timezone.timedelta(days=180)
end_date = end_date.replace(microsecond=0)
enrollment_end = timezone.now().replace(microsecond=0) - timezone.timedelta(days=5)
subsidy_expiry = timezone.now().replace(microsecond=0) + timezone.timedelta(days=5)
start_date = timezone.now().replace(microsecond=0) + timezone.timedelta(days=30)
end_date = timezone.now().replace(microsecond=0) + timezone.timedelta(days=180)

mock_subsidy_client.retrieve_subsidy.return_value = {
'enterprise_customer_uuid': str(self.enterprise_uuid),
Expand Down Expand Up @@ -194,25 +190,21 @@ def test_command_dry_run(
mock_send_reminder_email_for_pending_assignment_task.assert_not_called()

@mock.patch(COMMAND_PATH + '.get_content_metadata_for_assignments')
@mock.patch('enterprise_access.apps.content_assignments.api.send_reminder_email_for_pending_assignment.delay')
@mock.patch('enterprise_access.apps.content_assignments.tasks.send_exec_ed_enrollment_warmer.delay')
@mock.patch('enterprise_access.apps.subsidy_access_policy.models.SubsidyAccessPolicy.subsidy_client')
def test_command(
self,
mock_subsidy_client,
mock_send_reminder_email_for_pending_assignment_task,
mock_send_exec_ed_enrollment_warmer_task,
mock_content_metadata_for_assignments,
):
"""
Verify that management command work as expected.
"""
enrollment_end = timezone.now() + timezone.timedelta(days=5)
enrollment_end = enrollment_end.replace(microsecond=0)
subsidy_expiry = timezone.now() - timezone.timedelta(days=5)
subsidy_expiry = subsidy_expiry.replace(microsecond=0)
start_date = timezone.now() + timezone.timedelta(days=14)
start_date = start_date.replace(microsecond=0)
end_date = timezone.now() + timezone.timedelta(days=180)
end_date = end_date.replace(microsecond=0)
enrollment_end = timezone.now().replace(microsecond=0) + timezone.timedelta(days=5)
subsidy_expiry = timezone.now().replace(microsecond=0) - timezone.timedelta(days=5)
start_date = timezone.now().replace(microsecond=0) + timezone.timedelta(days=14)
end_date = timezone.now().replace(microsecond=0) + timezone.timedelta(days=180)

mock_subsidy_client.retrieve_subsidy.return_value = {
'enterprise_customer_uuid': str(self.enterprise_uuid),
Expand All @@ -237,7 +229,7 @@ def test_command(
'enroll_by_date': enrollment_end.strftime("%Y-%m-%d %H:%M"),
'content_price': 321,
},
'course_type': 'executive-education',
'course_type': 'executive-education-2u',
},
'edX+edXTesseract4D': {
'key': 'edX+edXTesseract4D',
Expand Down Expand Up @@ -283,31 +275,29 @@ def test_command(

call_command(self.command, days_before_course_start_date=14)

mock_send_reminder_email_for_pending_assignment_task.assert_has_calls([
call(self.alice_assignment.uuid),
call(self.bob_assignment.uuid),
mock_send_exec_ed_enrollment_warmer_task.assert_has_calls([
call(self.alice_assignment.uuid, 14),
call(self.bob_assignment.uuid, 14),
call(self.rob_assignment.uuid, 14),
call(self.richard_assignment.uuid, 14)
])

@mock.patch(COMMAND_PATH + '.get_content_metadata_for_assignments')
@mock.patch('enterprise_access.apps.content_assignments.api.send_reminder_email_for_pending_assignment.delay')
@mock.patch('enterprise_access.apps.content_assignments.tasks.send_exec_ed_enrollment_warmer.delay')
@mock.patch('enterprise_access.apps.subsidy_access_policy.models.SubsidyAccessPolicy.subsidy_client')
def test_command_multiple_assignment_dates(
self,
mock_subsidy_client,
mock_send_reminder_email_for_pending_assignment_task,
mock_send_exec_ed_enrollment_warmer_task,
mock_content_metadata_for_assignments,
):
"""
Verify that management command work as expected given multiple dates have been mocked.
"""
enrollment_end = timezone.now() + timezone.timedelta(days=5)
enrollment_end = enrollment_end.replace(microsecond=0)
subsidy_expiry = timezone.now() - timezone.timedelta(days=5)
subsidy_expiry = subsidy_expiry.replace(microsecond=0)
start_date = timezone.now() + timezone.timedelta(days=14)
start_date = start_date.replace(microsecond=0)
end_date = timezone.now() + timezone.timedelta(days=180)
end_date = end_date.replace(microsecond=0)
enrollment_end = timezone.now().replace(microsecond=0) + timezone.timedelta(days=5)
subsidy_expiry = timezone.now().replace(microsecond=0) - timezone.timedelta(days=5)
start_date = timezone.now().replace(microsecond=0) + timezone.timedelta(days=14)
end_date = timezone.now().replace(microsecond=0) + timezone.timedelta(days=180)

# Three nonpassing dates for assignments
start_date_beyond_30_days = timezone.now() + timezone.timedelta(days=90)
Expand Down Expand Up @@ -388,30 +378,26 @@ def test_command_multiple_assignment_dates(

call_command(self.command, days_before_course_start_date=14)

mock_send_reminder_email_for_pending_assignment_task.assert_has_calls([
call(self.alice_assignment.uuid),
mock_send_exec_ed_enrollment_warmer_task.assert_has_calls([
call(self.alice_assignment.uuid, 14),
])

@mock.patch(COMMAND_PATH + '.get_content_metadata_for_assignments')
@mock.patch('enterprise_access.apps.content_assignments.api.send_reminder_email_for_pending_assignment.delay')
@mock.patch('enterprise_access.apps.content_assignments.tasks.send_exec_ed_enrollment_warmer.delay')
@mock.patch('enterprise_access.apps.subsidy_access_policy.models.SubsidyAccessPolicy.subsidy_client')
def test_command_multiple_assignment_course_types(
self,
mock_subsidy_client,
mock_send_reminder_email_for_pending_assignment_task,
mock_send_exec_ed_enrollment_warmer_task,
mock_content_metadata_for_assignments,
):
"""
Verify that management command work as expected given a course_type is not executive-education.
"""
enrollment_end = timezone.now() + timezone.timedelta(days=5)
enrollment_end = enrollment_end.replace(microsecond=0)
subsidy_expiry = timezone.now() - timezone.timedelta(days=5)
subsidy_expiry = subsidy_expiry.replace(microsecond=0)
start_date = timezone.now() + timezone.timedelta(days=14)
start_date = start_date.replace(microsecond=0)
end_date = timezone.now() + timezone.timedelta(days=180)
end_date = end_date.replace(microsecond=0)
enrollment_end = timezone.now().replace(microsecond=0) + timezone.timedelta(days=5)
subsidy_expiry = timezone.now().replace(microsecond=0) - timezone.timedelta(days=5)
start_date = timezone.now().replace(microsecond=0) + timezone.timedelta(days=14)
end_date = timezone.now().replace(microsecond=0) + timezone.timedelta(days=180)

mock_subsidy_client.retrieve_subsidy.return_value = {
'enterprise_customer_uuid': str(self.enterprise_uuid),
Expand Down Expand Up @@ -484,31 +470,27 @@ def test_command_multiple_assignment_course_types(

call_command(self.command, days_before_course_start_date=14)

mock_send_reminder_email_for_pending_assignment_task.assert_has_calls([
call(self.alice_assignment.uuid),
mock_send_exec_ed_enrollment_warmer_task.assert_has_calls([
call(self.alice_assignment.uuid, 14),
])

@mock.patch(COMMAND_PATH + '.get_content_metadata_for_assignments')
@mock.patch('enterprise_access.apps.content_assignments.api.send_reminder_email_for_pending_assignment.delay')
@mock.patch('enterprise_access.apps.content_assignments.tasks.send_exec_ed_enrollment_warmer.delay')
@mock.patch('enterprise_access.apps.subsidy_access_policy.models.SubsidyAccessPolicy.subsidy_client')
def test_command_multiple_assignment_states(
self,
mock_subsidy_client,
mock_send_reminder_email_for_pending_assignment_task,
mock_send_exec_ed_enrollment_warmer_task,
mock_content_metadata_for_assignments,
):
"""
Verify that management command work as expected given the state of the course is allocated,
cancelled or errored state.
"""
enrollment_end = timezone.now() + timezone.timedelta(days=5)
enrollment_end = enrollment_end.replace(microsecond=0)
subsidy_expiry = timezone.now() - timezone.timedelta(days=5)
subsidy_expiry = subsidy_expiry.replace(microsecond=0)
start_date = timezone.now() + timezone.timedelta(days=14)
start_date = start_date.replace(microsecond=0)
end_date = timezone.now() + timezone.timedelta(days=180)
end_date = end_date.replace(microsecond=0)
enrollment_end = timezone.now().replace(microsecond=0) + timezone.timedelta(days=5)
subsidy_expiry = timezone.now().replace(microsecond=0) - timezone.timedelta(days=5)
start_date = timezone.now().replace(microsecond=0) + timezone.timedelta(days=14)
end_date = timezone.now().replace(microsecond=0) + timezone.timedelta(days=180)

# Update bobs assignment state
self.bob_assignment.state = LearnerContentAssignmentStateChoices.ERRORED
Expand Down Expand Up @@ -582,6 +564,6 @@ def test_command_multiple_assignment_states(

call_command(self.command, days_before_course_start_date=14)

mock_send_reminder_email_for_pending_assignment_task.assert_has_calls([
call(self.alice_assignment.uuid),
mock_send_exec_ed_enrollment_warmer_task.assert_has_calls([
call(self.alice_assignment.uuid, 14),
])
46 changes: 46 additions & 0 deletions enterprise_access/apps/content_assignments/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,52 @@ def send_cancel_email_for_pending_assignment(cancelled_assignment_uuid):
logger.info(f'Sent braze campaign cancelled uuid={campaign_uuid} message for assignment {assignment}')


# pylint: disable=abstract-method
class SendExecutiveEducationNudgeTask(BaseAssignmentRetryAndErrorActionTask):
"""
Base class for the ``send_exec_ed_enrollment_warmer`` task.
"""
def add_errored_action(self, assignment, exc):
assignment.add_errored_reminded_action(exc)


@shared_task(base=SendExecutiveEducationNudgeTask)
def send_exec_ed_enrollment_warmer(assignment_uuid, days_before_course_start_date):
"""
Send email via braze for nudging users of their pending accepted assignments
Args:
assignment_uuid: (string) the subsidy request uuid
"""
assignment = _get_assignment_or_raise(assignment_uuid)

campaign_sender = BrazeCampaignSender(assignment)
braze_trigger_properties = campaign_sender.get_properties(
'contact_admin_link',
'organization',
'course_title',
'enrollment_deadline',
'start_date',
'course_partner',
'course_card_image',
'learner_portal_link',
'action_required_by',
)

braze_trigger_properties['days_before_course_start_date'] = days_before_course_start_date

campaign_uuid = settings.BRAZE_ASSIGNMENT_NUDGE_EXEC_ED_ACCEPTED_ASSIGNMENT_CAMPAIGN

campaign_sender.send_campaign_message(
braze_trigger_properties,
campaign_uuid,
)
logger.info(
f'Sent braze campaign nudge reminder at '
f'days_before_course_start_date={days_before_course_start_date} '
f'uuid={campaign_uuid} message for assignment {assignment}'
)


# pylint: disable=abstract-method
class SendReminderEmailTask(BaseAssignmentRetryAndErrorActionTask):
"""
Expand Down
14 changes: 0 additions & 14 deletions enterprise_access/apps/content_assignments/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
"""
import traceback

from django.utils import timezone


def chunks(a_list, chunk_size):
"""
Expand All @@ -17,15 +15,3 @@ def chunks(a_list, chunk_size):
def format_traceback(exception):
trace = ''.join(traceback.format_tb(exception.__traceback__))
return f'{exception}\n{trace}'


def are_dates_matching_with_day_offset(days_offset, target_date, date_to_offset):
"""
Takes an integer number of days to offset from the date_to_offset to determine if
the target_date matches the date_to_offset + days_offset date
The target_date and date_to_offset arguments are UTC timezone objects
"""
offset_date = date_to_offset + timezone.timedelta(days=days_offset)
are_dates_matching = target_date.strftime('%Y-%m-%d') == offset_date.strftime('%Y-%m-%d')
return are_dates_matching
Loading

0 comments on commit 03ff109

Please sign in to comment.