From 403358e09f3357666870ccc2dba6a31eea000387 Mon Sep 17 00:00:00 2001 From: Carla Duarte Date: Thu, 11 Mar 2021 16:10:17 -0500 Subject: [PATCH] AA-213: create progress tab mfe waffle flag --- .../progress/v1/serializers.py | 14 +++++++++++++- .../progress/v1/tests/test_views.py | 19 ++++++++++++++++++- .../course_home_api/progress/v1/views.py | 7 ++++++- lms/djangoapps/course_home_api/toggles.py | 10 ++++++++++ lms/djangoapps/courseware/tabs.py | 12 +++++++++++- lms/djangoapps/courseware/views/views.py | 11 ++++++++++- lms/urls.py | 3 ++- 7 files changed, 70 insertions(+), 6 deletions(-) diff --git a/lms/djangoapps/course_home_api/progress/v1/serializers.py b/lms/djangoapps/course_home_api/progress/v1/serializers.py index 50c7931150..d08f3b7588 100644 --- a/lms/djangoapps/course_home_api/progress/v1/serializers.py +++ b/lms/djangoapps/course_home_api/progress/v1/serializers.py @@ -6,11 +6,17 @@ from rest_framework.reverse import reverse class CourseGradeSerializer(serializers.Serializer): + """ + Serializer for course grade + """ percent = serializers.FloatField() is_passing = serializers.BooleanField(source='passed') class SubsectionScoresSerializer(serializers.Serializer): + """ + Serializer for subsections in section_scores + """ assignment_type = serializers.CharField(source='format') display_name = serializers.CharField() has_graded_assignment = serializers.BooleanField(source='graded') @@ -32,13 +38,16 @@ class SubsectionScoresSerializer(serializers.Serializer): class SectionScoresSerializer(serializers.Serializer): """ - Serializer for chapters in courseware_summary + Serializer for sections in section_scores """ display_name = serializers.CharField() subsections = SubsectionScoresSerializer(source='sections', many=True) class GradingPolicySerializer(serializers.Serializer): + """ + Serializer for grading policy + """ assignment_policies = serializers.SerializerMethodField() grade_range = serializers.DictField(source='GRADE_CUTOFFS') @@ -50,6 +59,9 @@ class GradingPolicySerializer(serializers.Serializer): class CertificateDataSerializer(serializers.Serializer): + """ + Serializer for certificate data + """ cert_status = serializers.CharField() cert_web_view_url = serializers.CharField() download_url = serializers.CharField() diff --git a/lms/djangoapps/course_home_api/progress/v1/tests/test_views.py b/lms/djangoapps/course_home_api/progress/v1/tests/test_views.py index 889a247605..c4c5fc04ea 100644 --- a/lms/djangoapps/course_home_api/progress/v1/tests/test_views.py +++ b/lms/djangoapps/course_home_api/progress/v1/tests/test_views.py @@ -10,7 +10,8 @@ from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.tests.factories import UserFactory from lms.djangoapps.course_home_api.tests.utils import BaseCourseHomeTests -from lms.djangoapps.course_home_api.toggles import COURSE_HOME_MICROFRONTEND +from lms.djangoapps.course_home_api.toggles import COURSE_HOME_MICROFRONTEND, COURSE_HOME_MICROFRONTEND_PROGRESS_TAB +from lms.djangoapps.experiments.testutils import override_experiment_waffle_flag from lms.djangoapps.verify_student.models import ManualVerification from openedx.core.djangoapps.user_api.preferences.api import set_user_preference @@ -27,6 +28,8 @@ class ProgressTabTestViews(BaseCourseHomeTests): super().setUp() self.url = reverse('course-home-progress-tab', args=[self.course.id]) + @override_experiment_waffle_flag(COURSE_HOME_MICROFRONTEND, active=True) + @override_waffle_flag(COURSE_HOME_MICROFRONTEND_PROGRESS_TAB, active=True) @ddt.data(CourseMode.AUDIT, CourseMode.VERIFIED) def test_get_authenticated_enrolled_user(self, enrollment_mode): CourseEnrollment.enroll(self.user, self.course.id, enrollment_mode) @@ -47,21 +50,35 @@ class ProgressTabTestViews(BaseCourseHomeTests): elif enrollment_mode == CourseMode.AUDIT: assert response.data['certificate_data']['cert_status'] == 'audit_passing' + @override_experiment_waffle_flag(COURSE_HOME_MICROFRONTEND, active=True) + @override_waffle_flag(COURSE_HOME_MICROFRONTEND_PROGRESS_TAB, active=True) def test_get_authenticated_user_not_enrolled(self): response = self.client.get(self.url) # expecting a redirect assert response.status_code == 302 + @override_experiment_waffle_flag(COURSE_HOME_MICROFRONTEND, active=True) + @override_waffle_flag(COURSE_HOME_MICROFRONTEND_PROGRESS_TAB, active=True) def test_get_unauthenticated_user(self): self.client.logout() response = self.client.get(self.url) assert response.status_code == 401 + @override_experiment_waffle_flag(COURSE_HOME_MICROFRONTEND, active=True) + @override_waffle_flag(COURSE_HOME_MICROFRONTEND_PROGRESS_TAB, active=True) def test_get_unknown_course(self): url = reverse('course-home-progress-tab', args=['course-v1:unknown+course+2T2020']) response = self.client.get(url) assert response.status_code == 404 + @override_experiment_waffle_flag(COURSE_HOME_MICROFRONTEND, active=True) + @override_waffle_flag(COURSE_HOME_MICROFRONTEND_PROGRESS_TAB, active=False) + @ddt.data(CourseMode.AUDIT, CourseMode.VERIFIED) + def test_waffle_flag_disabled(self, enrollment_mode): + CourseEnrollment.enroll(self.user, self.course.id, enrollment_mode) + response = self.client.get(self.url) + assert response.status_code == 404 + # TODO: (AA-212) implement masquerade # def test_masquerade(self): # user = UserFactory() diff --git a/lms/djangoapps/course_home_api/progress/v1/views.py b/lms/djangoapps/course_home_api/progress/v1/views.py index 19df71bd9b..237dfcc254 100644 --- a/lms/djangoapps/course_home_api/progress/v1/views.py +++ b/lms/djangoapps/course_home_api/progress/v1/views.py @@ -2,6 +2,7 @@ Progress Tab Views """ +from django.http.response import Http404 from edx_django_utils import monitoring as monitoring_utils from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser @@ -13,6 +14,7 @@ from rest_framework.response import Response from xmodule.modulestore.django import modulestore from common.djangoapps.student.models import CourseEnrollment from lms.djangoapps.course_home_api.progress.v1.serializers import ProgressTabSerializer +from lms.djangoapps.course_home_api.toggles import course_home_mfe_progress_tab_is_active from lms.djangoapps.courseware.access import has_access from lms.djangoapps.courseware.courses import get_course_blocks_completion_summary, get_course_with_access, get_studio_url from lms.djangoapps.courseware.masquerade import setup_masquerade @@ -49,7 +51,7 @@ class ProgressTabView(RetrieveAPIView): course_grade: Object containing the following fields: percent: (float) the user's total graded percent in the course is_passing: (bool) whether the user's grade is above the passing grade cutoff - graded_course_blocks: List of serialized Chapters. Each Chapter has the following fields: + section_scores: List of serialized Chapters. Each Chapter has the following fields: display_name: (str) a str of what the name of the Chapter is for displaying on the site subsections: List of serialized Subsections, each has the following fields: assignment_type: (str) the format, if any, of the Subsection (Homework, Exam, etc) @@ -97,6 +99,9 @@ class ProgressTabView(RetrieveAPIView): course_key_string = kwargs.get('course_key_string') course_key = CourseKey.from_string(course_key_string) + if not course_home_mfe_progress_tab_is_active(course_key): + raise Http404 + # Enable NR tracing for this view based on course monitoring_utils.set_custom_attribute('course_id', course_key_string) monitoring_utils.set_custom_attribute('user_id', request.user.id) diff --git a/lms/djangoapps/course_home_api/toggles.py b/lms/djangoapps/course_home_api/toggles.py index cefe4909ca..9e0b9ac9cd 100644 --- a/lms/djangoapps/course_home_api/toggles.py +++ b/lms/djangoapps/course_home_api/toggles.py @@ -15,6 +15,9 @@ COURSE_HOME_MICROFRONTEND_DATES_TAB = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'c COURSE_HOME_MICROFRONTEND_OUTLINE_TAB = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'course_home_mfe_outline_tab', __name__) +COURSE_HOME_MICROFRONTEND_PROGRESS_TAB = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'course_home_mfe_progress_tab', + __name__) + def course_home_mfe_is_active(course_key): return ( @@ -35,3 +38,10 @@ def course_home_mfe_outline_tab_is_active(course_key): course_home_mfe_is_active(course_key) and COURSE_HOME_MICROFRONTEND_OUTLINE_TAB.is_enabled(course_key) ) + + +def course_home_mfe_progress_tab_is_active(course_key): + return ( + course_home_mfe_is_active(course_key) and + COURSE_HOME_MICROFRONTEND_PROGRESS_TAB.is_enabled(course_key) + ) diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index b0cc0cac6d..4d7de4a190 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -11,7 +11,7 @@ from django.utils.translation import ugettext_noop from lms.djangoapps.courseware.access import has_access from lms.djangoapps.courseware.entrance_exams import user_can_skip_entrance_exam -from lms.djangoapps.course_home_api.toggles import course_home_mfe_dates_tab_is_active, course_home_mfe_outline_tab_is_active # lint-amnesty, pylint: disable=line-too-long +from lms.djangoapps.course_home_api.toggles import course_home_mfe_dates_tab_is_active, course_home_mfe_outline_tab_is_active, course_home_mfe_progress_tab_is_active # lint-amnesty, pylint: disable=line-too-long from openedx.core.lib.course_tabs import CourseTabPluginManager from openedx.features.course_experience import RELATIVE_DATES_FLAG, DISABLE_UNIFIED_COURSE_TAB_FLAG, default_course_url_name # lint-amnesty, pylint: disable=line-too-long from openedx.features.course_experience.url_helpers import get_learning_mfe_home_url @@ -110,6 +110,16 @@ class ProgressTab(EnrolledTab): is_hideable = True is_default = False + def __init__(self, tab_dict): + def link_func(course, reverse_func): + if course_home_mfe_progress_tab_is_active(course.id): + return get_learning_mfe_home_url(course_key=course.id, view_name=self.view_name) + else: + return reverse_func(self.view_name, args=[six.text_type(course.id)]) + + tab_dict['link_func'] = link_func + super(ProgressTab, self).__init__(tab_dict) # pylint: disable=super-with-arguments + @classmethod def is_enabled(cls, course, user=None): if not super(ProgressTab, cls).is_enabled(course, user=user): diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index 9287598770..890e28ebc1 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -55,7 +55,10 @@ from lms.djangoapps.ccx.custom_exception import CCXLocatorValidationException from lms.djangoapps.certificates import api as certs_api from lms.djangoapps.certificates.models import CertificateStatuses from lms.djangoapps.commerce.utils import EcommerceService -from lms.djangoapps.course_home_api.toggles import course_home_mfe_dates_tab_is_active +from lms.djangoapps.course_home_api.toggles import ( + course_home_mfe_dates_tab_is_active, + course_home_mfe_progress_tab_is_active +) from openedx.features.course_experience.url_helpers import get_learning_mfe_home_url, is_request_from_learning_mfe from lms.djangoapps.courseware.access import has_access, has_ccx_coach_role from lms.djangoapps.courseware.access_utils import check_course_open_for_learner, check_public_access @@ -1088,8 +1091,14 @@ def dates(request, course_id): @data_sharing_consent_required def progress(request, course_id, student_id=None): """ Display the progress page. """ + from lms.urls import COURSE_PROGRESS_NAME + course_key = CourseKey.from_string(course_id) + if course_home_mfe_progress_tab_is_active(course_key) and not request.user.is_staff: + microfrontend_url = get_learning_mfe_home_url(course_key=course_key, view_name=COURSE_PROGRESS_NAME) + raise Redirect(microfrontend_url) + with modulestore().bulk_operations(course_key): return _progress(request, course_key, student_id) diff --git a/lms/urls.py b/lms/urls.py index cfcdf14a9b..f11650915a 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -58,6 +58,7 @@ from common.djangoapps.util import views as util_views RESET_COURSE_DEADLINES_NAME = 'reset_course_deadlines' RENDER_XBLOCK_NAME = 'render_xblock' COURSE_DATES_NAME = 'dates' +COURSE_PROGRESS_NAME = 'progress' if settings.DEBUG or settings.FEATURES.get('ENABLE_DJANGO_ADMIN_SITE'): django_autodiscover() @@ -498,7 +499,7 @@ urlpatterns += [ settings.COURSE_ID_PATTERN, ), courseware_views.progress, - name='progress', + name=COURSE_PROGRESS_NAME, ), # dates page