Skip to content

Commit

Permalink
MST-734 Add due_date to the learning_sequence accessible_sequences (#826
Browse files Browse the repository at this point in the history
)

Before this change, the date we use to test the sequence visibility was
max datetime. This means the onboarding exams with a due date will not
allow the onboarding panel to show. With this commit, the onboarding
status panel will show to learners before or after the due date of the
exam.
  • Loading branch information
schenedx authored Apr 5, 2021
1 parent 71b3aca commit 3879a42
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 9 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ Change Log
Unreleased
~~~~~~~~~~
[3.8.3] - 2021-04-05
~~~~~~~~~~~~~~~~~~~~~
* Use exam due_date or course end date to evaluate the visibility of the onboarding status panel

[3.8.2] - 2021-04-02
~~~~~~~~~~~~~~~~~~~~~
Expand Down
2 changes: 1 addition & 1 deletion edx_proctoring/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"""

# Be sure to update the version number in edx_proctoring/package.json
__version__ = '3.8.2'
__version__ = '3.8.3'

default_app_config = 'edx_proctoring.apps.EdxProctoringConfig' # pylint: disable=invalid-name
12 changes: 9 additions & 3 deletions edx_proctoring/tests/test_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,14 +350,16 @@ def __init__(self, accessible_sequences):

class MockScheduleData:
"""Mock Outline Schedule"""
def __init__(self, schedule_items):
def __init__(self, schedule_items, course_end=None):
self.sequences = schedule_items
self.course_end = course_end


class MockScheduleItemData:
"""Mock Schedule Item"""
def __init__(self, effective_start):
def __init__(self, effective_start, due_date=None):
self.effective_start = effective_start
self.due = due_date


class MockLearningSequencesService:
Expand All @@ -367,8 +369,12 @@ def __init__(self, accessible_sequences, schedule_items):
self.schedule_items = schedule_items

def get_user_course_outline_details(self, course_key, user, at_time):
""" Return mock CourseOutlineDetailsData """
""" Return mock UserCourseOutlineDetailsData """
return MockUserCourseOutlineDetailsData(
MockUserCourseOutlineData(self.accessible_sequences),
MockScheduleData(self.schedule_items),
)

def get_user_course_outline(self, course_key, user, at_time):
""" Return mock UserCourseOutlineData """
return MockUserCourseOutlineData(self.accessible_sequences)
31 changes: 30 additions & 1 deletion edx_proctoring/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@
"""

import unittest
from datetime import datetime
from datetime import datetime, timedelta
from itertools import product

import ddt
import pytz
from freezegun import freeze_time

from edx_proctoring.statuses import ProctoredExamStudentAttemptStatus
from edx_proctoring.tests.test_services import MockScheduleData, MockScheduleItemData
from edx_proctoring.utils import (
_emit_event,
get_time_remaining_for_attempt,
get_visibility_check_date,
humanized_time,
is_reattempting_exam,
obscured_user_id
Expand Down Expand Up @@ -155,3 +157,30 @@ def test_obscured_user_id(self):
user_id = 32432455
expected_obscured_user_id = '9b82efd5d28f1a170b23b8f648c3093e75a0a0ca'
self.assertEqual(expected_obscured_user_id, obscured_user_id(user_id))

@ddt.data(
(1, 3, 1),
(None, 4, 4),
(2, None, 2),
(-1, -3, -1),
(None, -2, -2),
(-3, None, -3)
)
@ddt.unpack
def test_get_visibility_check_date(self, mock_due_offset, mock_end_offset, expected_offset):
cur_time = pytz.utc.localize(datetime.now())
expected_datetime = cur_time + timedelta(days=expected_offset)
mock_due = None
if mock_due_offset:
mock_due = cur_time + timedelta(days=mock_due_offset)
mock_course_end = None
if mock_end_offset:
mock_course_end = cur_time + timedelta(days=mock_end_offset)
mock_usage_key = '32fjkle2jf3'
mock_sequences = {
mock_usage_key: MockScheduleItemData(cur_time, mock_due)
}
mock_schedule = MockScheduleData(mock_sequences, mock_course_end)
with freeze_time(cur_time):
due_date = get_visibility_check_date(mock_schedule, mock_usage_key)
self.assertEqual(due_date, expected_datetime)
6 changes: 4 additions & 2 deletions edx_proctoring/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,7 @@ def test_get_exam_insufficient_args(self):
self.assertEqual(response_data['time_limit_mins'], proctored_exam.time_limit_mins)


@ddt.ddt
class TestStudentOnboardingStatusView(ProctoredExamTestCase):
"""
Tests for StudentOnboardingStatusView
Expand Down Expand Up @@ -823,14 +824,15 @@ def test_no_accessible_onboarding(self):
message = 'There is no onboarding exam accessible to this user.'
self.assertEqual(response_data['detail'], message)

def test_onboarding_not_yet_released(self):
@ddt.data(None, timezone.now() + timezone.timedelta(days=3))
def test_onboarding_not_yet_released(self, due_date):
"""
If the onboarding section has not been released the release date is returned
"""
tomorrow = timezone.now() + timezone.timedelta(days=1)
self.course_scheduled_sections[
BlockUsageLocator.from_string(self.onboarding_exam.content_id)
] = MockScheduleItemData(tomorrow)
] = MockScheduleItemData(tomorrow, due_date=due_date)

set_runtime_service('learning_sequences', MockLearningSequencesService(
list(self.course_scheduled_sections.keys()),
Expand Down
18 changes: 18 additions & 0 deletions edx_proctoring/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,3 +282,21 @@ def is_reattempting_exam(from_status, to_status):
ProctoredExamStudentAttemptStatus.is_in_progress_status(from_status) and
ProctoredExamStudentAttemptStatus.is_pre_started_status(to_status)
)


def get_visibility_check_date(course_schedule, usage_key):
"""
Utility function to return the date, of which
we should use to test the learner's visibility to the exam
Returns one of the following:
* The due date of the course structure usage_key
* The course end date
* The max datetime if no course_end date specified
"""
visibility_check_date = course_schedule.course_end or pytz.utc.localize(datetime.max)
exam_schedule_item = course_schedule.sequences.get(usage_key)
if exam_schedule_item and exam_schedule_item.due:
visibility_check_date = exam_schedule_item.due

return visibility_check_date
8 changes: 7 additions & 1 deletion edx_proctoring/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
from edx_proctoring.utils import (
AuthenticatedAPIView,
get_time_remaining_for_attempt,
get_visibility_check_date,
humanized_time,
locate_attempt_by_attempt_code,
obscured_user_id
Expand Down Expand Up @@ -369,7 +370,12 @@ def get(self, request):

for onboarding_exam in onboarding_exams:
usage_key = BlockUsageLocator.from_string(onboarding_exam.content_id)
if usage_key not in details.outline.accessible_sequences:
visibility_check_date = get_visibility_check_date(details.schedule, usage_key)
user_outline = learning_sequences_service.get_user_course_outline(
course_key, user, visibility_check_date
)

if usage_key not in user_outline.accessible_sequences:
onboarding_exams.remove(onboarding_exam)

if not onboarding_exams:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@edx/edx-proctoring",
"//": "Be sure to update the version number in edx_proctoring/__init__.py",
"//": "Note that the version format is slightly different than that of the Python version when using prereleases.",
"version": "3.8.2",
"version": "3.8.3",
"main": "edx_proctoring/static/index.js",
"scripts":{
"test":"gulp test"
Expand Down

0 comments on commit 3879a42

Please sign in to comment.