From 8c745cabbfb281380d41a3e54b0db6e9c702a7a1 Mon Sep 17 00:00:00 2001 From: Dillon Dumesnil Date: Fri, 29 Jan 2021 16:43:06 -0500 Subject: [PATCH] AA-492: Adds research tracking event for reset deadlines This PR also removes the exemption for staff from seeing the reset deadlines banner (staff will now see the banner). Staff users would still be unable to submit problems and wouldn't have a way of resetting their deadlines while enrolled. --- .../api/v1/tests/test_views.py | 25 +++++++++++----- .../course_experience/api/v1/views.py | 29 +++++++++++++++---- .../tests/views/test_course_outline.py | 5 ++-- openedx/features/course_experience/utils.py | 7 ----- .../call_to_action.py | 4 +++ 5 files changed, 47 insertions(+), 23 deletions(-) diff --git a/openedx/features/course_experience/api/v1/tests/test_views.py b/openedx/features/course_experience/api/v1/tests/test_views.py index 7f18409aa3..6704c624fa 100644 --- a/openedx/features/course_experience/api/v1/tests/test_views.py +++ b/openedx/features/course_experience/api/v1/tests/test_views.py @@ -10,6 +10,7 @@ from mock import patch from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.student.models import CourseEnrollment +from common.djangoapps.util.testing import EventTestMixin from lms.djangoapps.courseware.tests.helpers import MasqueradeMixin from lms.djangoapps.course_home_api.tests.utils import BaseCourseHomeTests from openedx.core.djangoapps.schedules.models import Schedule @@ -18,28 +19,30 @@ from xmodule.modulestore.tests.factories import CourseFactory @ddt.ddt -class ResetCourseDeadlinesViewTests(BaseCourseHomeTests, MasqueradeMixin): +class ResetCourseDeadlinesViewTests(EventTestMixin, BaseCourseHomeTests, MasqueradeMixin): """ Tests for reset deadlines endpoint. """ + def setUp(self): + # Need to supply tracker name for the EventTestMixin. Also, EventTestMixin needs to come + # first in class inheritance so the setUp call here appropriately works + super().setUp('openedx.features.course_experience.api.v1.views.tracker') + def test_reset_deadlines(self): CourseEnrollment.enroll(self.user, self.course.id, CourseMode.VERIFIED) # Test correct post body response = self.client.post(reverse('course-experience-reset-course-deadlines'), {'course_key': self.course.id}) self.assertEqual(response.status_code, 200) - # Test body with incorrect body param + # Test body with incorrect body param (course_key is required) response = self.client.post(reverse('course-experience-reset-course-deadlines'), {'course': self.course.id}) self.assertEqual(response.status_code, 400) - # Test body with additional incorrect body param - response = self.client.post( - reverse('course-experience-reset-course-deadlines'), {'course_key': self.course.id, 'invalid': 'value'} - ) - self.assertEqual(response.status_code, 400) + self.assert_no_events_were_emitted() def test_reset_deadlines_with_masquerade(self): """ Staff users should be able to masquerade as a learner and reset the learner's schedule """ course = CourseFactory.create(self_paced=True) student_username = self.user.username + student_user_id = self.user.id student_enrollment = CourseEnrollment.enroll(self.user, course.id) student_schedule = ScheduleFactory.create( start_date=timezone.now() - datetime.timedelta(days=100), @@ -61,6 +64,14 @@ class ResetCourseDeadlinesViewTests(BaseCourseHomeTests, MasqueradeMixin): self.assertEqual(updated_schedule.start_date.date(), datetime.datetime.today().date()) updated_staff_schedule = Schedule.objects.get(id=staff_schedule.id) self.assertEqual(updated_staff_schedule.start_date, staff_schedule.start_date) + self.assert_event_emitted( + 'edx.ui.lms.reset_deadlines.clicked', + courserun_key=str(course.id), + is_masquerading=True, + is_staff=False, + org_key=course.org, + user_id=student_user_id, + ) def test_post_unauthenticated_user(self): self.client.logout() diff --git a/openedx/features/course_experience/api/v1/views.py b/openedx/features/course_experience/api/v1/views.py index 41ba680332..2e4a34b1b1 100644 --- a/openedx/features/course_experience/api/v1/views.py +++ b/openedx/features/course_experience/api/v1/views.py @@ -7,6 +7,7 @@ from django.conf import settings from django.urls import reverse from django.utils.html import format_html from django.utils.translation import ugettext as _ +from eventtracking import tracker from rest_framework.decorators import api_view, authentication_classes, permission_classes from rest_framework.exceptions import APIException, ParseError @@ -18,11 +19,12 @@ from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthenticat from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser from opaque_keys.edx.keys import CourseKey +from lms.djangoapps.course_api.api import course_detail from lms.djangoapps.course_home_api.toggles import course_home_mfe_dates_tab_is_active from lms.djangoapps.course_home_api.utils import get_microfrontend_url from lms.djangoapps.courseware.access import has_access from lms.djangoapps.courseware.courses import get_course_with_access -from lms.djangoapps.courseware.masquerade import setup_masquerade +from lms.djangoapps.courseware.masquerade import is_masquerading, setup_masquerade from openedx.core.djangoapps.schedules.utils import reset_self_paced_schedule from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser @@ -48,22 +50,24 @@ def reset_course_deadlines(request): Set the start_date of a schedule to today, which in turn will adjust due dates for sequentials belonging to a self paced course + Request Parameters: + course_key: course key + research_event_data: any data that should be included in the research tracking event + Example: sending the location of where the reset deadlines banner (i.e. outline-tab) + IMPORTANT NOTE: If updates are happening to the logic here, ALSO UPDATE the `reset_course_deadlines` function in common/djangoapps/util/views.py as well. """ course_key = request.data.get('course_key', None) + research_event_data = request.data.get('research_event_data', {}) # If body doesnt contain 'course_key', return 400 to client. if not course_key: raise ParseError(_("'course_key' is required.")) - # If body contains params other than 'course_key', return 400 to client. - if len(request.data) > 1: - raise ParseError(_("Only 'course_key' is expected.")) - try: course_key = CourseKey.from_string(course_key) - _course_masquerade, user = setup_masquerade( + course_masquerade, user = setup_masquerade( request, course_key, has_access(request.user, 'staff', course_key) @@ -73,6 +77,19 @@ def reset_course_deadlines(request): if missed_deadlines and not missed_gated_content: reset_self_paced_schedule(user, course_key) + course_overview = course_detail(request, user.username, course_key) + # For context here, research_event_data should already contain `location` indicating + # the page/location dates were reset from and could also contain `block_id` if reset + # within courseware. + research_event_data.update({ + 'courserun_key': str(course_key), + 'is_masquerading': is_masquerading(user, course_key, course_masquerade), + 'is_staff': has_access(user, 'staff', course_key).has_access, + 'org_key': course_overview.display_org_with_default, + 'user_id': user.id, + }) + tracker.emit('edx.ui.lms.reset_deadlines.clicked', research_event_data) + if course_home_mfe_dates_tab_is_active(course_key): body_link = get_microfrontend_url(course_key=str(course_key), view_name='dates') else: diff --git a/openedx/features/course_experience/tests/views/test_course_outline.py b/openedx/features/course_experience/tests/views/test_course_outline.py index 28064fa484..212f0eabe9 100644 --- a/openedx/features/course_experience/tests/views/test_course_outline.py +++ b/openedx/features/course_experience/tests/views/test_course_outline.py @@ -200,9 +200,8 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase, MasqueradeMixin): @ddt.data( ([CourseMode.AUDIT, CourseMode.VERIFIED], CourseMode.AUDIT, False, True), ([CourseMode.AUDIT, CourseMode.VERIFIED], CourseMode.VERIFIED, False, True), - ([CourseMode.AUDIT, CourseMode.VERIFIED, CourseMode.MASTERS], CourseMode.MASTERS, False, True), - ([CourseMode.PROFESSIONAL], CourseMode.PROFESSIONAL, False, True), - ([CourseMode.AUDIT, CourseMode.VERIFIED], CourseMode.VERIFIED, True, False), + ([CourseMode.MASTERS], CourseMode.MASTERS, False, True), + ([CourseMode.PROFESSIONAL], CourseMode.PROFESSIONAL, True, True), # staff accounts should also see the banner ) @ddt.unpack def test_reset_course_deadlines_banner_shows_for_self_paced_course( diff --git a/openedx/features/course_experience/utils.py b/openedx/features/course_experience/utils.py index f5af09d5e4..44afae085f 100644 --- a/openedx/features/course_experience/utils.py +++ b/openedx/features/course_experience/utils.py @@ -281,13 +281,6 @@ def dates_banner_should_display(course_key, user): if not CourseEnrollment.is_enrolled(user, course_key): return False, False - # Don't display the banner for course staff - is_course_staff = bool( - user and course_overview and has_access(user, 'staff', course_overview, course_overview.id) - ) - if is_course_staff: - return False, False - # Don't display the banner if the course has ended if course_end_date and course_end_date < timezone.now(): return False, False diff --git a/openedx/features/personalized_learner_schedules/call_to_action.py b/openedx/features/personalized_learner_schedules/call_to_action.py index 34c56bdc3b..bf6570a9a9 100644 --- a/openedx/features/personalized_learner_schedules/call_to_action.py +++ b/openedx/features/personalized_learner_schedules/call_to_action.py @@ -151,6 +151,10 @@ class PersonalizedLearnerScheduleCallToAction: }, 'url': '{}{}'.format(settings.LMS_ROOT_URL, reverse('course-experience-reset-course-deadlines')), }, + 'research_event_data': { + 'block_id': str(xblock.location), + 'location': '{category}-view'.format(category=xblock.category), + }, } return cta_data