fix: fix issue with incorrect bulk email schedules

[MICROBA-1835]
* The DateTime string received from the Comms MFE was already in UTC so there is no need to convert the schedule to UTC on the backend.
This commit is contained in:
Justin Hynes
2022-06-01 08:40:36 -04:00
parent 05f54e8074
commit d7ae3181b6
5 changed files with 37 additions and 228 deletions

View File

@@ -11,7 +11,6 @@ import hashlib
import logging
from collections import Counter
import dateutil
import pytz
from celery.states import READY_STATES
@@ -51,7 +50,6 @@ from lms.djangoapps.instructor_task.tasks import (
send_bulk_course_email,
generate_anonymous_ids_for_course
)
from openedx.core.djangoapps.user_api.preferences.api import get_user_preference
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
log = logging.getLogger(__name__)
@@ -589,37 +587,3 @@ def process_scheduled_instructor_tasks():
submit_scheduled_task(schedule)
except QueueConnectionError as exc:
log.error(f"Error processing scheduled task with task id '{schedule.task.id}': {exc}")
def convert_schedule_to_utc_from_local(schedule, browser_timezone, user):
"""
Utility function to help convert the schedule of an instructor task from the requesters local time and timezone
(taken from the request) to a UTC datetime.
Args:
schedule (String): The desired time to execute a scheduled task, in local time, in the form of an ISO8601
string.
timezone (String): The time zone, as captured by the user's web browser, in the form of a string.
user (User): The user requesting the action, captured from the originating web request. Used to lookup the
the time zone preference as set in the user's account settings.
Returns:
DateTime: A datetime instance describing when to execute this schedule task converted to the UTC timezone.
"""
# look up the requesting user's timezone from their account settings
preferred_timezone = get_user_preference(user, 'time_zone', username=user.username)
# use the user's preferred timezone (if available), otherwise use the browser timezone.
timezone = preferred_timezone if preferred_timezone else browser_timezone
# convert the schedule to UTC
log.info(f"Converting requested schedule from local time '{schedule}' with the timezone '{timezone}' to UTC")
local_tz = dateutil.tz.gettz(timezone)
if local_tz is None:
raise ValueError(
"Unable to determine the time zone to use to convert the schedule to UTC"
)
local_dt = dateutil.parser.parse(schedule).replace(tzinfo=local_tz)
schedule_utc = local_dt.astimezone(pytz.utc)
return schedule_utc

View File

@@ -6,6 +6,7 @@ import json
import logging
import pytz
import dateutil
from celery.states import REVOKED
from django.db import transaction
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
@@ -14,7 +15,6 @@ from rest_framework.response import Response
from rest_framework import generics, status
from lms.djangoapps.bulk_email.api import update_course_email
from lms.djangoapps.instructor_task.api import convert_schedule_to_utc_from_local
from lms.djangoapps.instructor_task.data import InstructorTaskTypes
from lms.djangoapps.instructor_task.models import InstructorTaskSchedule, SCHEDULED
from lms.djangoapps.instructor_task.rest_api.v1.exceptions import TaskUpdateException
@@ -126,10 +126,9 @@ class ModifyScheduledBulkEmailInstructorTask(generics.DestroyAPIView, generics.U
with transaction.atomic():
if schedule:
browser_timezone = request.data.get("browser_timezone", None)
schedule_utc = convert_schedule_to_utc_from_local(schedule, browser_timezone, request.user)
self._verify_valid_schedule(schedule_id, schedule_utc)
task_schedule.task_due = schedule_utc
schedule_dt = dateutil.parser.parse(schedule).replace(tzinfo=pytz.utc)
self._verify_valid_schedule(schedule_id, schedule_dt)
task_schedule.task_due = schedule_dt
task_schedule.save()
if email_data:
email_id = email_data.get("id")

View File

@@ -7,7 +7,6 @@ import json
from unittest.mock import MagicMock, Mock, patch
from uuid import uuid4
import dateutil
import pytest
import pytz
import ddt
@@ -22,7 +21,6 @@ from lms.djangoapps.bulk_email.data import BulkEmailTargetChoices
from lms.djangoapps.certificates.data import CertificateStatuses
from lms.djangoapps.certificates.models import CertificateGenerationHistory
from lms.djangoapps.instructor_task.api import (
convert_schedule_to_utc_from_local,
SpecificStudentIdMissingError,
generate_anonymous_ids,
generate_certificates_for_students,
@@ -63,7 +61,6 @@ from lms.djangoapps.instructor_task.tests.test_base import (
InstructorTaskTestCase,
TestReportMixin
)
from openedx.core.djangoapps.user_api.preferences.api import set_user_preference, delete_user_preference
LOG_PATH = 'lms.djangoapps.instructor_task.api'
@@ -531,77 +528,3 @@ class InstructorTaskCourseSubmitTest(TestReportMixin, InstructorTaskCourseTestCa
process_scheduled_instructor_tasks()
log.check_present((LOG_PATH, "ERROR", expected_messages[0]),)
@patch('lms.djangoapps.bulk_email.models.html_to_text', Mock(return_value='Mocking CourseEmail.text_message', autospec=True)) # lint-amnesty, pylint: disable=line-too-long
class ScheduledInstructorTaskTests(TestReportMixin, InstructorTaskCourseTestCase):
"""
Tests API methods that support scheduled instructor tasks
"""
def setUp(self):
super().setUp()
self.instructor = UserFactory.create(username="instructor", email="instructor@edx.org")
def tearDown(self):
super().tearDown()
delete_user_preference(self.instructor, 'time_zone', self.instructor.username)
def _get_expected_schedule(self, schedule, timezone):
local_tz = dateutil.tz.gettz(timezone)
local_dt = dateutil.parser.parse(schedule).replace(tzinfo=local_tz)
return local_dt.astimezone(pytz.utc)
def test_convert_schedule_to_utc_from_local_no_user_preference(self):
"""
A test that verifies the behavior of the `convert_schedule_to_utc_from_local` function. Verifies that we use
the browser provided timezone data if there is no preferred timezone set by the user in their account settings.
"""
schedule = "2099-05-02T14:00:00.000Z"
browser_timezone = "America/New_York"
expected_schedule = self._get_expected_schedule(schedule, browser_timezone)
schedule_utc = convert_schedule_to_utc_from_local(schedule, browser_timezone, self.instructor)
assert schedule_utc == expected_schedule
def test_convert_schedule_to_utc_from_local_with_preferred_timezone(self):
"""
A test that verifies the behavior of the `convert_schedule_to_utc_from_local` function. Verifies that we use the
preferred timezone if a user has set one in their account settings.
"""
schedule = "2099-05-02T14:00:00.000Z"
preferred_timezone = "America/Anchorage"
set_user_preference(self.instructor, 'time_zone', preferred_timezone)
expected_schedule = self._get_expected_schedule(schedule, preferred_timezone)
schedule_utc = convert_schedule_to_utc_from_local(schedule, "", self.instructor)
assert schedule_utc == expected_schedule
def test_convert_schedule_to_utc_from_local_with_preferred_and_browser_timezone(self):
"""
A test that verifies the behavior of the `convert_schedule_to_utc_from_local` function. Verifies that we use
the preferred timezone over the browser timezone when provided both values.
"""
schedule = "2099-05-02T14:00:00.000Z"
browser_timezone = "America/New_York"
preferred_timezone = "America/Anchorage"
set_user_preference(self.instructor, 'time_zone', preferred_timezone)
expected_schedule = self._get_expected_schedule(schedule, preferred_timezone)
schedule_utc = convert_schedule_to_utc_from_local(schedule, browser_timezone, self.instructor)
assert schedule_utc == expected_schedule
def test_convert_schedule_to_utc_from_local_with_invalid_timezone(self):
"""
A test that verifies the behavior of the `convert_schedule_to_utc_from_local` function. Verifies an error
condition if the application cannot determine a timezone to use for conversion.
"""
schedule = "2099-05-02T14:00:00.000Z"
expected_error_message = "Unable to determine the time zone to use to convert the schedule to UTC"
with self.assertRaises(ValueError) as value_err:
convert_schedule_to_utc_from_local(schedule, "Flim/Flam", self.instructor)
assert str(value_err.exception) == expected_error_message