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