From 9c8ebf6ac82b67f0a83def9cb3a16a9bb351db55 Mon Sep 17 00:00:00 2001 From: javier ontiveros Date: Mon, 20 Apr 2026 15:30:40 -0600 Subject: [PATCH 01/10] feat: added redirect to new instructor dash --- lms/djangoapps/instructor/toggles.py | 17 +++++++++++++++++ .../instructor/views/instructor_dashboard.py | 9 ++++++++- lms/djangoapps/utils.py | 9 +++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/instructor/toggles.py b/lms/djangoapps/instructor/toggles.py index 340079868722..6ea8ebe8552d 100644 --- a/lms/djangoapps/instructor/toggles.py +++ b/lms/djangoapps/instructor/toggles.py @@ -28,6 +28,16 @@ # .. toggle_tickets: PROD-1740 OPTIMISED_IS_SMALL_COURSE = WaffleFlag(f'{WAFFLE_FLAG_NAMESPACE}.optimised_is_small_course', __name__) +# .. toggle_name: instructor.disable_new_instructor_dashboard_mfe +# .. toggle_implementation: WaffleFlag +# .. toggle_default: False +# .. toggle_description: Waffle flag to disable the new instructor dashboard microfrontend and revert back +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2026-04-20 +DISABLE_NEW_INSTRUCTOR_DASHBOARD_MFE = WaffleFlag( + f'{WAFFLE_FLAG_NAMESPACE}.disable_new_instructor_dashboard_mfe', __name__ +) + def data_download_v2_is_enabled(): """ @@ -38,3 +48,10 @@ def data_download_v2_is_enabled(): def use_optimised_is_small_course(): return OPTIMISED_IS_SMALL_COURSE.is_enabled() + + +def disable_new_instructor_dashboard_mfe(): + """ + check if new instructor dashboard microfrontend is disabled. + """ + return DISABLE_NEW_INSTRUCTOR_DASHBOARD_MFE.is_enabled() diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index 70f9b8ef00ed..39ba739d7490 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -17,6 +17,7 @@ from django.views.decorators.cache import cache_control from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.http import require_POST +from django.shortcuts import redirect from edx_django_utils.plugins import get_plugins_view_context from edx_proctoring.api import does_backend_support_onboarding from edx_when.api import is_enabled_for_course @@ -46,6 +47,7 @@ from lms.djangoapps.discussion.django_comment_client.utils import has_forum_access from lms.djangoapps.grades.api import is_writable_gradebook_enabled from lms.djangoapps.instructor.constants import INSTRUCTOR_DASHBOARD_PLUGIN_VIEW_NAME +from lms.djangoapps.utils import get_instructor_dashboard_url from openedx.core.djangoapps.course_groups.cohorts import DEFAULT_COHORT_NAME, get_course_cohorts, is_course_cohorted from openedx.core.djangoapps.discussions.config.waffle_utils import legacy_discussion_experience_enabled from openedx.core.djangoapps.discussions.utils import available_division_schemes @@ -58,7 +60,7 @@ from xmodule.tabs import CourseTab # lint-amnesty, pylint: disable=wrong-import-order from .. import permissions -from ..toggles import data_download_v2_is_enabled +from ..toggles import data_download_v2_is_enabled, disable_new_instructor_dashboard_mfe from .tools import get_units_with_due_date, title_or_url log = logging.getLogger(__name__) @@ -144,6 +146,11 @@ def instructor_dashboard_2(request, course_id): # lint-amnesty, pylint: disable if not request.user.has_perm(permissions.VIEW_DASHBOARD, course_key): raise Http404() + # With new instructor dashboard we need to redirect them to it instead of rendering the old one, + # but we still want to check if they have access to view the dashboard before redirecting. + if not disable_new_instructor_dashboard_mfe(): + return redirect(get_instructor_dashboard_url(course_key)) + sections = [] if access['staff']: sections_content = [ diff --git a/lms/djangoapps/utils.py b/lms/djangoapps/utils.py index d892c8f633f5..956cf19855eb 100644 --- a/lms/djangoapps/utils.py +++ b/lms/djangoapps/utils.py @@ -66,3 +66,12 @@ def get_optimizely_client(cls): cls.optimizely_client = optimizely.Optimizely(config_manager=config_manager) return cls.optimizely_client + + +def get_instructor_dashboard_url(course_locator) -> str: + """ + Gets instructor microfrontend URL for the current course locator. + """ + mfe_base_url = settings.INSTRUCTOR_MICROFRONTEND_URL + print(f'mfe_base_url: {mfe_base_url}') + return f'{mfe_base_url}/{course_locator}/course_info' From ecfc0b478da07eaef4339fb9af2c0a4a52299798 Mon Sep 17 00:00:00 2001 From: javier ontiveros Date: Tue, 21 Apr 2026 11:17:37 -0600 Subject: [PATCH 02/10] chore: fixed tests and comments --- .../tests/test_view_authentication.py | 28 +++++++++++++------ lms/djangoapps/instructor/toggles.py | 14 +++++----- .../instructor/views/instructor_dashboard.py | 15 +++++++--- lms/djangoapps/utils.py | 8 ------ 4 files changed, 38 insertions(+), 27 deletions(-) diff --git a/lms/djangoapps/courseware/tests/test_view_authentication.py b/lms/djangoapps/courseware/tests/test_view_authentication.py index 8e3df22d56a1..f627e0d2ac67 100644 --- a/lms/djangoapps/courseware/tests/test_view_authentication.py +++ b/lms/djangoapps/courseware/tests/test_view_authentication.py @@ -92,7 +92,12 @@ def _check_staff(self, course): for index in range(len(course.textbooks)) ]) for url in urls: - self.assert_request_status_code(200, url) + # Instructor dashboard can return either 200 (traditional) or 302 (MFE redirect) + if 'instructor' in url: + response = self.client.get(url) + assert(response.status_code in [200, 302]) + else: + self.assert_request_status_code(200, url) # The student progress tab is not accessible to a student # before launch, so the instructor view-as-student feature @@ -186,7 +191,8 @@ def test_staff_course_access(self): # Now should be able to get to self.course, but not self.test_course url = reverse('instructor_dashboard', kwargs={'course_id': str(self.course.id)}) - self.assert_request_status_code(200, url) + response = self.client.get(url) + assert(response.status_code in [200, 302]) url = reverse('instructor_dashboard', kwargs={'course_id': str(self.test_course.id)}) self.assert_request_status_code(404, url) @@ -200,7 +206,8 @@ def test_instructor_course_access(self): # Now should be able to get to self.course, but not self.test_course url = reverse('instructor_dashboard', kwargs={'course_id': str(self.course.id)}) - self.assert_request_status_code(200, url) + response = self.client.get(url) + assert(response.status_code in [200, 302]) url = reverse('instructor_dashboard', kwargs={'course_id': str(self.test_course.id)}) self.assert_request_status_code(404, url) @@ -212,10 +219,12 @@ def test_org_staff_access(self): """ self.login(self.org_staff_user) url = reverse('instructor_dashboard', kwargs={'course_id': str(self.course.id)}) - self.assert_request_status_code(200, url) + response = self.client.get(url) + assert(response.status_code in [200, 302]) url = reverse('instructor_dashboard', kwargs={'course_id': str(self.test_course.id)}) - self.assert_request_status_code(200, url) + response = self.client.get(url) + assert(response.status_code in [200, 302]) url = reverse('instructor_dashboard', kwargs={'course_id': str(self.other_org_course.id)}) self.assert_request_status_code(404, url) @@ -227,10 +236,12 @@ def test_org_instructor_access(self): """ self.login(self.org_instructor_user) url = reverse('instructor_dashboard', kwargs={'course_id': str(self.course.id)}) - self.assert_request_status_code(200, url) + response = self.client.get(url) + assert(response.status_code in [200, 302]) url = reverse('instructor_dashboard', kwargs={'course_id': str(self.test_course.id)}) - self.assert_request_status_code(200, url) + response = self.client.get(url) + assert(response.status_code in [200, 302]) url = reverse('instructor_dashboard', kwargs={'course_id': str(self.other_org_course.id)}) self.assert_request_status_code(404, url) @@ -246,7 +257,8 @@ def test_global_staff_access(self): reverse('instructor_dashboard', kwargs={'course_id': str(self.test_course.id)})] for url in urls: - self.assert_request_status_code(200, url) + response = self.client.get(url) + assert(response.status_code in [200, 302]) @patch.dict('lms.djangoapps.courseware.access.settings.FEATURES', {'DISABLE_START_DATES': False}) def test_dark_launch_enrolled_student(self): diff --git a/lms/djangoapps/instructor/toggles.py b/lms/djangoapps/instructor/toggles.py index 6ea8ebe8552d..f6baf33609ad 100644 --- a/lms/djangoapps/instructor/toggles.py +++ b/lms/djangoapps/instructor/toggles.py @@ -28,14 +28,14 @@ # .. toggle_tickets: PROD-1740 OPTIMISED_IS_SMALL_COURSE = WaffleFlag(f'{WAFFLE_FLAG_NAMESPACE}.optimised_is_small_course', __name__) -# .. toggle_name: instructor.disable_new_instructor_dashboard_mfe +# .. toggle_name: instructor.legacy_instructor_dashboard_mfe # .. toggle_implementation: WaffleFlag # .. toggle_default: False -# .. toggle_description: Waffle flag to disable the new instructor dashboard microfrontend and revert back -# .. toggle_use_cases: open_edx +# .. toggle_description: Waffle flag to enable the legacy instructor experience +# .. toggle_use_cases: opt_out # .. toggle_creation_date: 2026-04-20 -DISABLE_NEW_INSTRUCTOR_DASHBOARD_MFE = WaffleFlag( - f'{WAFFLE_FLAG_NAMESPACE}.disable_new_instructor_dashboard_mfe', __name__ +LEGACY_INSTRUCTOR_DASHBOARD_MFE = WaffleFlag( + f'{WAFFLE_FLAG_NAMESPACE}.legacy_instructor_dashboard_mfe', __name__ ) @@ -50,8 +50,8 @@ def use_optimised_is_small_course(): return OPTIMISED_IS_SMALL_COURSE.is_enabled() -def disable_new_instructor_dashboard_mfe(): +def legacy_instructor_dashboard_mfe(): """ check if new instructor dashboard microfrontend is disabled. """ - return DISABLE_NEW_INSTRUCTOR_DASHBOARD_MFE.is_enabled() + return LEGACY_INSTRUCTOR_DASHBOARD_MFE.is_enabled() diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index 39ba739d7490..c53e8ac26ad2 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -10,6 +10,7 @@ from django.conf import settings from django.contrib.auth.decorators import login_required from django.http import Http404, HttpResponseRedirect, HttpResponseServerError +from django.shortcuts import redirect from django.urls import reverse from django.utils.html import escape from django.utils.translation import gettext as _ @@ -17,7 +18,6 @@ from django.views.decorators.cache import cache_control from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.http import require_POST -from django.shortcuts import redirect from edx_django_utils.plugins import get_plugins_view_context from edx_proctoring.api import does_backend_support_onboarding from edx_when.api import is_enabled_for_course @@ -47,7 +47,6 @@ from lms.djangoapps.discussion.django_comment_client.utils import has_forum_access from lms.djangoapps.grades.api import is_writable_gradebook_enabled from lms.djangoapps.instructor.constants import INSTRUCTOR_DASHBOARD_PLUGIN_VIEW_NAME -from lms.djangoapps.utils import get_instructor_dashboard_url from openedx.core.djangoapps.course_groups.cohorts import DEFAULT_COHORT_NAME, get_course_cohorts, is_course_cohorted from openedx.core.djangoapps.discussions.config.waffle_utils import legacy_discussion_experience_enabled from openedx.core.djangoapps.discussions.utils import available_division_schemes @@ -60,7 +59,7 @@ from xmodule.tabs import CourseTab # lint-amnesty, pylint: disable=wrong-import-order from .. import permissions -from ..toggles import data_download_v2_is_enabled, disable_new_instructor_dashboard_mfe +from ..toggles import data_download_v2_is_enabled, legacy_instructor_dashboard_mfe from .tools import get_units_with_due_date, title_or_url log = logging.getLogger(__name__) @@ -148,7 +147,7 @@ def instructor_dashboard_2(request, course_id): # lint-amnesty, pylint: disable # With new instructor dashboard we need to redirect them to it instead of rendering the old one, # but we still want to check if they have access to view the dashboard before redirecting. - if not disable_new_instructor_dashboard_mfe(): + if not legacy_instructor_dashboard_mfe(): return redirect(get_instructor_dashboard_url(course_key)) sections = [] @@ -824,3 +823,11 @@ def is_ecommerce_course(course_key): """ sku_count = len([mode.sku for mode in CourseMode.modes_for_course(course_key) if mode.sku]) return sku_count > 0 + + +def get_instructor_dashboard_url(course_locator: CourseKey) -> str: + """ + Gets instructor microfrontend URL for the current course locator. + """ + mfe_base_url = settings.INSTRUCTOR_MICROFRONTEND_URL + return f'{mfe_base_url}/{course_locator}/course_info' diff --git a/lms/djangoapps/utils.py b/lms/djangoapps/utils.py index 956cf19855eb..ad649f5d181b 100644 --- a/lms/djangoapps/utils.py +++ b/lms/djangoapps/utils.py @@ -67,11 +67,3 @@ def get_optimizely_client(cls): return cls.optimizely_client - -def get_instructor_dashboard_url(course_locator) -> str: - """ - Gets instructor microfrontend URL for the current course locator. - """ - mfe_base_url = settings.INSTRUCTOR_MICROFRONTEND_URL - print(f'mfe_base_url: {mfe_base_url}') - return f'{mfe_base_url}/{course_locator}/course_info' From 90b5d4cdeec0c3990b3ed02d96df0a85284cdd61 Mon Sep 17 00:00:00 2001 From: javier ontiveros Date: Tue, 21 Apr 2026 11:25:01 -0600 Subject: [PATCH 03/10] chore: remove trailing new line --- lms/djangoapps/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lms/djangoapps/utils.py b/lms/djangoapps/utils.py index ad649f5d181b..d892c8f633f5 100644 --- a/lms/djangoapps/utils.py +++ b/lms/djangoapps/utils.py @@ -66,4 +66,3 @@ def get_optimizely_client(cls): cls.optimizely_client = optimizely.Optimizely(config_manager=config_manager) return cls.optimizely_client - From 97989a665ce1a8f6af62d92db3cd83be1c9ad59b Mon Sep 17 00:00:00 2001 From: javier ontiveros Date: Tue, 21 Apr 2026 13:34:00 -0600 Subject: [PATCH 04/10] chore: improved tests --- .../tests/test_view_authentication.py | 208 ++++++++++++++++-- lms/djangoapps/instructor/toggles.py | 12 +- .../instructor/views/instructor_dashboard.py | 8 +- 3 files changed, 201 insertions(+), 27 deletions(-) diff --git a/lms/djangoapps/courseware/tests/test_view_authentication.py b/lms/djangoapps/courseware/tests/test_view_authentication.py index f627e0d2ac67..eb53d5fc4529 100644 --- a/lms/djangoapps/courseware/tests/test_view_authentication.py +++ b/lms/djangoapps/courseware/tests/test_view_authentication.py @@ -8,6 +8,7 @@ import pytz from django.urls import reverse +from edx_toggles.toggles.testutils import override_waffle_flag from common.djangoapps.student.tests.factories import ( BetaTesterFactory, @@ -19,6 +20,7 @@ StaffFactory, UserFactory, ) +from lms.djangoapps.instructor.toggles import LEGACY_INSTRUCTOR_DASHBOARD from lms.djangoapps.courseware.access import has_access from lms.djangoapps.courseware.tests.helpers import CourseAccessTestMixin, LoginEnrollmentTestCase from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseTestConsentRequired @@ -92,10 +94,9 @@ def _check_staff(self, course): for index in range(len(course.textbooks)) ]) for url in urls: - # Instructor dashboard can return either 200 (traditional) or 302 (MFE redirect) + # Instructor dashboard returns 302 (MFE redirect) by default if 'instructor' in url: - response = self.client.get(url) - assert(response.status_code in [200, 302]) + self.assert_request_status_code(302, url) else: self.assert_request_status_code(200, url) @@ -114,6 +115,58 @@ def _check_staff(self, course): ) self.assert_request_status_code(302, url) + def _check_staff_legacy(self, course): + """ + Check that access is right for staff in course with legacy instructor dashboard enabled. + """ + names = ['about_course', 'instructor_dashboard', 'progress'] + urls = self._reverse_urls(names, course) + urls.extend([ + reverse('book', kwargs={'course_id': str(course.id), + 'book_index': index}) + for index in range(len(course.textbooks)) + ]) + for url in urls: + # With legacy flag enabled, all URLs return 200 (instructor dashboard skips MFE redirect) + self.assert_request_status_code(200, url) + + # The student progress tab behavior is affected by legacy flag in normal scenarios + url = reverse( + 'student_progress', + kwargs={ + 'course_id': str(course.id), + 'student_id': self.enrolled_user.id, + } + ) + self.assert_request_status_code(200, url) + + def _check_staff_legacy_dark_launch(self, course): + """ + Check staff access during dark launch with legacy instructor dashboard enabled. + In dark launch scenarios, student progress URL still returns 302 even with legacy flag. + """ + names = ['about_course', 'instructor_dashboard', 'progress'] + urls = self._reverse_urls(names, course) + urls.extend([ + reverse('book', kwargs={'course_id': str(course.id), + 'book_index': index}) + for index in range(len(course.textbooks)) + ]) + for url in urls: + # With legacy flag enabled, all URLs return 200 (instructor dashboard skips MFE redirect) + self.assert_request_status_code(200, url) + + # In dark launch scenarios, student progress URL still returns 302 even with legacy flag + # because course access restrictions take precedence + url = reverse( + 'student_progress', + kwargs={ + 'course_id': str(course.id), + 'student_id': self.enrolled_user.id, + } + ) + self.assert_request_status_code(302, url) + def login(self, user): # lint-amnesty, pylint: disable=arguments-differ return super().login(user.email, self.TEST_PASSWORD) @@ -191,8 +244,7 @@ def test_staff_course_access(self): # Now should be able to get to self.course, but not self.test_course url = reverse('instructor_dashboard', kwargs={'course_id': str(self.course.id)}) - response = self.client.get(url) - assert(response.status_code in [200, 302]) + self.assert_request_status_code(302, url) url = reverse('instructor_dashboard', kwargs={'course_id': str(self.test_course.id)}) self.assert_request_status_code(404, url) @@ -206,8 +258,7 @@ def test_instructor_course_access(self): # Now should be able to get to self.course, but not self.test_course url = reverse('instructor_dashboard', kwargs={'course_id': str(self.course.id)}) - response = self.client.get(url) - assert(response.status_code in [200, 302]) + self.assert_request_status_code(302, url) url = reverse('instructor_dashboard', kwargs={'course_id': str(self.test_course.id)}) self.assert_request_status_code(404, url) @@ -219,12 +270,10 @@ def test_org_staff_access(self): """ self.login(self.org_staff_user) url = reverse('instructor_dashboard', kwargs={'course_id': str(self.course.id)}) - response = self.client.get(url) - assert(response.status_code in [200, 302]) + self.assert_request_status_code(302, url) url = reverse('instructor_dashboard', kwargs={'course_id': str(self.test_course.id)}) - response = self.client.get(url) - assert(response.status_code in [200, 302]) + self.assert_request_status_code(302, url) url = reverse('instructor_dashboard', kwargs={'course_id': str(self.other_org_course.id)}) self.assert_request_status_code(404, url) @@ -236,12 +285,10 @@ def test_org_instructor_access(self): """ self.login(self.org_instructor_user) url = reverse('instructor_dashboard', kwargs={'course_id': str(self.course.id)}) - response = self.client.get(url) - assert(response.status_code in [200, 302]) + self.assert_request_status_code(302, url) url = reverse('instructor_dashboard', kwargs={'course_id': str(self.test_course.id)}) - response = self.client.get(url) - assert(response.status_code in [200, 302]) + self.assert_request_status_code(302, url) url = reverse('instructor_dashboard', kwargs={'course_id': str(self.other_org_course.id)}) self.assert_request_status_code(404, url) @@ -257,8 +304,89 @@ def test_global_staff_access(self): reverse('instructor_dashboard', kwargs={'course_id': str(self.test_course.id)})] for url in urls: - response = self.client.get(url) - assert(response.status_code in [200, 302]) + self.assert_request_status_code(302, url) + + # Legacy instructor dashboard tests (with waffle flag enabled, expect 200 responses) + + @override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) + def test_staff_course_access_legacy(self): + """ + Verify staff can load the legacy instructor dashboard (expects 200 response). + """ + self.login(self.staff_user) + + url = reverse('instructor_dashboard', kwargs={'course_id': str(self.course.id)}) + self.assert_request_status_code(200, url) + + url = reverse('instructor_dashboard', kwargs={'course_id': str(self.test_course.id)}) + self.assert_request_status_code(404, url) + + @override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) + def test_instructor_course_access_legacy(self): + """ + Verify instructor can load the legacy instructor dashboard (expects 200 response). + """ + self.login(self.instructor_user) + + url = reverse('instructor_dashboard', kwargs={'course_id': str(self.course.id)}) + self.assert_request_status_code(200, url) + + url = reverse('instructor_dashboard', kwargs={'course_id': str(self.test_course.id)}) + self.assert_request_status_code(404, url) + + @override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) + def test_org_staff_access_legacy(self): + """ + Verify org staff can load the legacy instructor dashboard (expects 200 response). + """ + self.login(self.org_staff_user) + url = reverse('instructor_dashboard', kwargs={'course_id': str(self.course.id)}) + self.assert_request_status_code(200, url) + + url = reverse('instructor_dashboard', kwargs={'course_id': str(self.test_course.id)}) + self.assert_request_status_code(200, url) + + url = reverse('instructor_dashboard', kwargs={'course_id': str(self.other_org_course.id)}) + self.assert_request_status_code(404, url) + + @override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) + def test_org_instructor_access_legacy(self): + """ + Verify org instructor can load the legacy instructor dashboard (expects 200 response). + """ + self.login(self.org_instructor_user) + url = reverse('instructor_dashboard', kwargs={'course_id': str(self.course.id)}) + self.assert_request_status_code(200, url) + + url = reverse('instructor_dashboard', kwargs={'course_id': str(self.test_course.id)}) + self.assert_request_status_code(200, url) + + url = reverse('instructor_dashboard', kwargs={'course_id': str(self.other_org_course.id)}) + self.assert_request_status_code(404, url) + + @override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) + def test_global_staff_access_legacy(self): + """ + Verify the global staff user can access the legacy instructor dashboard (expects 200 response). + """ + self.login(self.global_staff_user) + + urls = [reverse('instructor_dashboard', kwargs={'course_id': str(self.course.id)}), + reverse('instructor_dashboard', kwargs={'course_id': str(self.test_course.id)})] + + for url in urls: + self.assert_request_status_code(200, url) + + @override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) + def test_staff_method_legacy(self): + """ + Test the _check_staff_legacy helper method with legacy flag enabled (expects 200 response). + """ + self.login(self.staff_user) + self.enroll(self.course, True) + + # Test the _check_staff_legacy method which includes instructor dashboard checks + self._check_staff_legacy(self.course) @patch.dict('lms.djangoapps.courseware.access.settings.FEATURES', {'DISABLE_START_DATES': False}) def test_dark_launch_enrolled_student(self): @@ -367,6 +495,52 @@ def test_enrollment_period(self): self.login(self.global_staff_user) assert self.enroll(self.course) + @override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) + @patch.dict('lms.djangoapps.courseware.access.settings.FEATURES', {'DISABLE_START_DATES': False}) + def test_dark_launch_instructor_legacy(self): + """ + Make sure that before course start instructors can access the + page for their course with legacy instructor dashboard enabled. + """ + now = datetime.datetime.now(pytz.UTC) + tomorrow = now + datetime.timedelta(days=1) + self.course.start = tomorrow + self.test_course.start = tomorrow + self.course = self.update_course(self.course, self.user.id) + self.test_course = self.update_course(self.test_course, self.user.id) + + self.login(self.instructor_user) + # Enroll in the classes---can't see courseware otherwise. + self.enroll(self.course, True) + self.enroll(self.test_course, True) + + # should now be able to get to everything for self.course + self._check_staff_legacy_dark_launch(self.course) + self._check_non_staff_dark(self.test_course) + + @override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) + @patch.dict('lms.djangoapps.courseware.access.settings.FEATURES', {'DISABLE_START_DATES': False}) + def test_dark_launch_global_staff_legacy(self): + """ + Make sure that before course start staff can access + course pages with legacy instructor dashboard enabled. + """ + now = datetime.datetime.now(pytz.UTC) + tomorrow = now + datetime.timedelta(days=1) + + self.course.start = tomorrow + self.test_course.start = tomorrow + self.course = self.update_course(self.course, self.user.id) + self.test_course = self.update_course(self.test_course, self.user.id) + + self.login(self.global_staff_user) + self.enroll(self.course, True) + self.enroll(self.test_course, True) + + # and now should be able to load both + self._check_staff_legacy_dark_launch(self.course) + self._check_staff_legacy_dark_launch(self.test_course) + class TestBetatesterAccess(ModuleStoreTestCase, CourseAccessTestMixin): """ diff --git a/lms/djangoapps/instructor/toggles.py b/lms/djangoapps/instructor/toggles.py index f6baf33609ad..577e5493df8f 100644 --- a/lms/djangoapps/instructor/toggles.py +++ b/lms/djangoapps/instructor/toggles.py @@ -28,14 +28,14 @@ # .. toggle_tickets: PROD-1740 OPTIMISED_IS_SMALL_COURSE = WaffleFlag(f'{WAFFLE_FLAG_NAMESPACE}.optimised_is_small_course', __name__) -# .. toggle_name: instructor.legacy_instructor_dashboard_mfe +# .. toggle_name: instructor.legacy_instructor_dashboard # .. toggle_implementation: WaffleFlag # .. toggle_default: False # .. toggle_description: Waffle flag to enable the legacy instructor experience # .. toggle_use_cases: opt_out # .. toggle_creation_date: 2026-04-20 -LEGACY_INSTRUCTOR_DASHBOARD_MFE = WaffleFlag( - f'{WAFFLE_FLAG_NAMESPACE}.legacy_instructor_dashboard_mfe', __name__ +LEGACY_INSTRUCTOR_DASHBOARD = WaffleFlag( + f'{WAFFLE_FLAG_NAMESPACE}.legacy_instructor_dashboard', __name__ ) @@ -50,8 +50,8 @@ def use_optimised_is_small_course(): return OPTIMISED_IS_SMALL_COURSE.is_enabled() -def legacy_instructor_dashboard_mfe(): +def legacy_instructor_dashboard(): """ - check if new instructor dashboard microfrontend is disabled. + Check if legacy instructor dashboard experience is enabled. """ - return LEGACY_INSTRUCTOR_DASHBOARD_MFE.is_enabled() + return LEGACY_INSTRUCTOR_DASHBOARD.is_enabled() diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index c53e8ac26ad2..4975fb164995 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -59,7 +59,7 @@ from xmodule.tabs import CourseTab # lint-amnesty, pylint: disable=wrong-import-order from .. import permissions -from ..toggles import data_download_v2_is_enabled, legacy_instructor_dashboard_mfe +from ..toggles import data_download_v2_is_enabled, legacy_instructor_dashboard from .tools import get_units_with_due_date, title_or_url log = logging.getLogger(__name__) @@ -147,7 +147,7 @@ def instructor_dashboard_2(request, course_id): # lint-amnesty, pylint: disable # With new instructor dashboard we need to redirect them to it instead of rendering the old one, # but we still want to check if they have access to view the dashboard before redirecting. - if not legacy_instructor_dashboard_mfe(): + if not legacy_instructor_dashboard(): return redirect(get_instructor_dashboard_url(course_key)) sections = [] @@ -825,9 +825,9 @@ def is_ecommerce_course(course_key): return sku_count > 0 -def get_instructor_dashboard_url(course_locator: CourseKey) -> str: +def get_instructor_dashboard_url(course_key: CourseKey) -> str: """ Gets instructor microfrontend URL for the current course locator. """ mfe_base_url = settings.INSTRUCTOR_MICROFRONTEND_URL - return f'{mfe_base_url}/{course_locator}/course_info' + return f'{mfe_base_url}/{course_key}/course_info' From 4ad32dc5ceaf252866f2a9cb90c8eda0eb16fce5 Mon Sep 17 00:00:00 2001 From: javier ontiveros Date: Tue, 21 Apr 2026 13:44:06 -0600 Subject: [PATCH 05/10] chore: fix comments and lint --- lms/djangoapps/courseware/tests/test_view_authentication.py | 2 +- lms/djangoapps/instructor/toggles.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/courseware/tests/test_view_authentication.py b/lms/djangoapps/courseware/tests/test_view_authentication.py index eb53d5fc4529..9a412526f276 100644 --- a/lms/djangoapps/courseware/tests/test_view_authentication.py +++ b/lms/djangoapps/courseware/tests/test_view_authentication.py @@ -20,9 +20,9 @@ StaffFactory, UserFactory, ) -from lms.djangoapps.instructor.toggles import LEGACY_INSTRUCTOR_DASHBOARD from lms.djangoapps.courseware.access import has_access from lms.djangoapps.courseware.tests.helpers import CourseAccessTestMixin, LoginEnrollmentTestCase +from lms.djangoapps.instructor.toggles import LEGACY_INSTRUCTOR_DASHBOARD from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseTestConsentRequired from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase diff --git a/lms/djangoapps/instructor/toggles.py b/lms/djangoapps/instructor/toggles.py index 577e5493df8f..84a618fbc9e3 100644 --- a/lms/djangoapps/instructor/toggles.py +++ b/lms/djangoapps/instructor/toggles.py @@ -32,8 +32,9 @@ # .. toggle_implementation: WaffleFlag # .. toggle_default: False # .. toggle_description: Waffle flag to enable the legacy instructor experience -# .. toggle_use_cases: opt_out +# .. toggle_use_cases: opt_out, temporary # .. toggle_creation_date: 2026-04-20 +# .. toggle_target_removal_date: 2026-11-01 LEGACY_INSTRUCTOR_DASHBOARD = WaffleFlag( f'{WAFFLE_FLAG_NAMESPACE}.legacy_instructor_dashboard', __name__ ) From 0595ad39bcec67db05f34fea5cfc94935d42a1d4 Mon Sep 17 00:00:00 2001 From: javier ontiveros Date: Tue, 21 Apr 2026 15:56:57 -0600 Subject: [PATCH 06/10] chore: fixed email tests --- lms/djangoapps/bulk_email/tests/test_course_optout.py | 7 +++++++ lms/djangoapps/bulk_email/tests/test_email.py | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/lms/djangoapps/bulk_email/tests/test_course_optout.py b/lms/djangoapps/bulk_email/tests/test_course_optout.py index 9c4b986fe606..811ae4b71930 100644 --- a/lms/djangoapps/bulk_email/tests/test_course_optout.py +++ b/lms/djangoapps/bulk_email/tests/test_course_optout.py @@ -13,12 +13,14 @@ from edx_ace.message import Message from edx_ace.policy import PolicyResult from edx_ace.recipient import Recipient +from edx_toggles.toggles.testutils import override_waffle_flag from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.tests.factories import AdminFactory, CourseEnrollmentFactory, UserFactory from lms.djangoapps.bulk_email.api import get_unsubscribed_link from lms.djangoapps.bulk_email.models import BulkEmailFlag from lms.djangoapps.bulk_email.policies import CourseEmailOptout +from lms.djangoapps.instructor.toggles import LEGACY_INSTRUCTOR_DASHBOARD from xmodule.modulestore.tests.django_utils import ( ModuleStoreTestCase, # lint-amnesty, pylint: disable=wrong-import-order ) @@ -26,6 +28,11 @@ @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 +# Enable legacy instructor dashboard to access email section HTML instead of getting 302 redirects. +# The 302 redirects to MFE don't affect email functionality, but these tests need to verify +# that the email section exists in the HTML, which is no longer a primary concern since +# we're testing email opt-out capabilities, not the UI redirect behavior. +@override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) class TestOptoutCourseEmails(ModuleStoreTestCase): """ Test that optouts are referenced in sending course email. diff --git a/lms/djangoapps/bulk_email/tests/test_email.py b/lms/djangoapps/bulk_email/tests/test_email.py index 9f2c6779d0a2..3422c3b6d6c2 100644 --- a/lms/djangoapps/bulk_email/tests/test_email.py +++ b/lms/djangoapps/bulk_email/tests/test_email.py @@ -16,6 +16,7 @@ from django.test.utils import override_settings from django.urls import reverse from django.utils.translation import get_language +from edx_toggles.toggles.testutils import override_waffle_flag from markupsafe import escape from common.djangoapps.course_modes.models import CourseMode @@ -29,6 +30,7 @@ ) from lms.djangoapps.bulk_email.messages import ACEEmail from lms.djangoapps.bulk_email.tasks import _get_course_email_context, _get_source_address +from lms.djangoapps.instructor.toggles import LEGACY_INSTRUCTOR_DASHBOARD from lms.djangoapps.instructor_task.subtasks import update_subtask_status from openedx.core.djangoapps.course_groups.cohorts import add_user_to_cohort from openedx.core.djangoapps.course_groups.models import CourseCohort @@ -62,6 +64,11 @@ def mock_update_subtask_status(entry_id, current_task_id, new_subtask_status): return mock_update_subtask_status +# Enable legacy instructor dashboard to access email section HTML instead of getting 302 redirects. +# The 302 redirects to MFE don't affect email functionality, but these tests need to verify +# that the email section exists in the HTML, which is no longer a primary concern since +# we're testing email sending capabilities, not the UI redirect behavior. +@override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) class EmailSendFromDashboardTestCase(SharedModuleStoreTestCase): """ Test that emails send correctly. From 90996f91798647d8d27f18a4c3a67070d4b82c55 Mon Sep 17 00:00:00 2001 From: javier ontiveros Date: Tue, 21 Apr 2026 16:26:17 -0600 Subject: [PATCH 07/10] chore: improved tests --- lms/djangoapps/bulk_email/tests/test_signals.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lms/djangoapps/bulk_email/tests/test_signals.py b/lms/djangoapps/bulk_email/tests/test_signals.py index 6089f117f819..287ff42bb838 100644 --- a/lms/djangoapps/bulk_email/tests/test_signals.py +++ b/lms/djangoapps/bulk_email/tests/test_signals.py @@ -9,12 +9,14 @@ from django.core import mail from django.core.management import call_command from django.urls import reverse +from edx_toggles.toggles.testutils import override_waffle_flag from opaque_keys.edx.keys import CourseKey from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.tests.factories import AdminFactory, CourseEnrollmentFactory, UserFactory from lms.djangoapps.bulk_email.models import BulkEmailFlag, Optout from lms.djangoapps.bulk_email.signals import force_optout_all +from lms.djangoapps.instructor.toggles import LEGACY_INSTRUCTOR_DASHBOARD from xmodule.modulestore.tests.django_utils import ( ModuleStoreTestCase, # lint-amnesty, pylint: disable=wrong-import-order ) @@ -22,6 +24,11 @@ @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 +# Enable legacy instructor dashboard to access email section HTML instead of getting 302 redirects. +# The 302 redirects to MFE don't affect email functionality, but these tests need to verify +# that the email section exists in the HTML, which is no longer a primary concern since +# we're testing email opt-out signal behavior, not the UI redirect behavior. +@override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) class TestOptoutCourseEmailsBySignal(ModuleStoreTestCase): """ Tests that the force_optout_all signal receiver opts the user out of course emails From 8ec0a36c6b859607aeaa441be0a9474854974dee Mon Sep 17 00:00:00 2001 From: javier ontiveros Date: Tue, 21 Apr 2026 17:27:02 -0600 Subject: [PATCH 08/10] chore: more test fixes --- .../instructor/tests/test_certificates.py | 17 +++++++++++++++++ lms/djangoapps/instructor/tests/test_email.py | 7 +++++++ lms/djangoapps/instructor/tests/test_filters.py | 7 +++++++ .../instructor/tests/test_proctoring.py | 7 +++++++ .../tests/views/test_instructor_dashboard.py | 7 ++++++- 5 files changed, 44 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/instructor/tests/test_certificates.py b/lms/djangoapps/instructor/tests/test_certificates.py index 9b0299e6e9fe..aa4f7673bb76 100644 --- a/lms/djangoapps/instructor/tests/test_certificates.py +++ b/lms/djangoapps/instructor/tests/test_certificates.py @@ -14,6 +14,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile from django.test.utils import override_settings from django.urls import reverse +from edx_toggles.toggles.testutils import override_waffle_flag from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.tests.factories import GlobalStaffFactory, InstructorFactory, UserFactory @@ -24,6 +25,7 @@ CertificateInvalidationFactory, GeneratedCertificateFactory, ) +from lms.djangoapps.instructor.toggles import LEGACY_INSTRUCTOR_DASHBOARD from xmodule.modulestore.tests.django_utils import ( SharedModuleStoreTestCase, # lint-amnesty, pylint: disable=wrong-import-order ) @@ -31,6 +33,11 @@ @ddt.ddt +# Enable legacy instructor dashboard to access certificate management HTML instead of getting 302 redirects. +# The 302 redirects to MFE don't affect certificate functionality, but these tests need to verify +# that the certificate management sections exist in the HTML, which is no longer a primary concern since +# we're testing certificate generation capabilities, not the UI redirect behavior. +@override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) class CertificateTaskViewTests(SharedModuleStoreTestCase): """Tests for the certificate panel of the instructor dash. """ @@ -153,6 +160,11 @@ def test_certificate_regeneration_status_handling(self, cert_status, expected_st @ddt.ddt +# Enable legacy instructor dashboard to access certificate management HTML instead of getting 302 redirects. +# The 302 redirects to MFE don't affect certificate functionality, but these tests need to verify +# that the certificate sections exist in the HTML, which is no longer a primary concern since +# we're testing certificate dashboard capabilities, not the UI redirect behavior. +@override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) class CertificatesInstructorDashTest(SharedModuleStoreTestCase): """Tests for the certificate panel of the instructor dash. """ @@ -336,6 +348,11 @@ def _assert_enable_certs_button(self, is_enabled): @override_settings(CERT_QUEUE='certificates') @ddt.ddt +# Enable legacy instructor dashboard to access certificate API HTML instead of getting 302 redirects. +# The 302 redirects to MFE don't affect certificate functionality, but these tests need to verify +# that the certificate API sections exist in the HTML, which is no longer a primary concern since +# we're testing certificate API capabilities, not the UI redirect behavior. +@override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) class CertificatesInstructorApiTest(SharedModuleStoreTestCase): """Tests for the certificates end-points in the instructor dash API. """ @classmethod diff --git a/lms/djangoapps/instructor/tests/test_email.py b/lms/djangoapps/instructor/tests/test_email.py index 07fc9c9410d1..579ebc2a503e 100644 --- a/lms/djangoapps/instructor/tests/test_email.py +++ b/lms/djangoapps/instructor/tests/test_email.py @@ -7,6 +7,7 @@ from django.urls import reverse +from edx_toggles.toggles.testutils import override_waffle_flag from opaque_keys.edx.keys import CourseKey from common.djangoapps.student.tests.factories import AdminFactory @@ -16,6 +17,7 @@ is_bulk_email_enabled_for_course, is_bulk_email_feature_enabled, ) +from lms.djangoapps.instructor.toggles import LEGACY_INSTRUCTOR_DASHBOARD from xmodule.modulestore.tests.django_utils import ( # lint-amnesty, pylint: disable=wrong-import-order TEST_DATA_MIXED_MODULESTORE, SharedModuleStoreTestCase, @@ -23,6 +25,11 @@ from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order +# Enable legacy instructor dashboard to access email management HTML instead of getting 302 redirects. +# The 302 redirects to MFE don't affect email functionality, but these tests need to verify +# that the email sections exist in the HTML, which is no longer a primary concern since +# we're testing email feature flag functionality, not the UI redirect behavior. +@override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) class TestNewInstructorDashboardEmailViewMongoBacked(SharedModuleStoreTestCase): """ Check for email view on the new instructor dashboard diff --git a/lms/djangoapps/instructor/tests/test_filters.py b/lms/djangoapps/instructor/tests/test_filters.py index 4f1324531a59..4ef3d75ef0fa 100644 --- a/lms/djangoapps/instructor/tests/test_filters.py +++ b/lms/djangoapps/instructor/tests/test_filters.py @@ -6,11 +6,13 @@ from django.http import HttpResponse from django.test import override_settings from django.urls import reverse +from edx_toggles.toggles.testutils import override_waffle_flag from openedx_filters import PipelineStep from openedx_filters.learning.filters import InstructorDashboardRenderStarted from rest_framework import status from common.djangoapps.student.tests.factories import AdminFactory, CourseAccessRoleFactory +from lms.djangoapps.instructor.toggles import LEGACY_INSTRUCTOR_DASHBOARD from openedx.core.djangolib.testing.utils import skip_unless_lms from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory @@ -86,6 +88,11 @@ def run_filter(self, context, template_name): # pylint: disable=arguments-diffe @skip_unless_lms +# Enable legacy instructor dashboard to access filter testing HTML instead of getting 302 redirects. +# The 302 redirects to MFE don't affect filter functionality, but these tests need to verify +# that the dashboard filters work properly in the HTML, which is no longer a primary concern since +# we're testing filter pipeline capabilities, not the UI redirect behavior. +@override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) class InstructorDashboardFiltersTest(ModuleStoreTestCase): """ Tests for the Open edX Filters associated with the instructor dashboard rendering process. diff --git a/lms/djangoapps/instructor/tests/test_proctoring.py b/lms/djangoapps/instructor/tests/test_proctoring.py index 173e2a8a7c05..73982bc1b4d6 100644 --- a/lms/djangoapps/instructor/tests/test_proctoring.py +++ b/lms/djangoapps/instructor/tests/test_proctoring.py @@ -10,9 +10,11 @@ from django.urls import reverse from edx_proctoring.api import create_exam from edx_proctoring.backends.tests.test_backend import TestBackendProvider +from edx_toggles.toggles.testutils import override_waffle_flag from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole from common.djangoapps.student.tests.factories import AdminFactory +from lms.djangoapps.instructor.toggles import LEGACY_INSTRUCTOR_DASHBOARD from xmodule.modulestore.tests.django_utils import ( SharedModuleStoreTestCase, # lint-amnesty, pylint: disable=wrong-import-order ) @@ -21,6 +23,11 @@ @patch.dict(settings.FEATURES, {'ENABLE_SPECIAL_EXAMS': True}) @ddt.ddt +# Enable legacy instructor dashboard to access proctoring management HTML instead of getting 302 redirects. +# The 302 redirects to MFE don't affect proctoring functionality, but these tests need to verify +# that the proctoring sections exist in the HTML, which is no longer a primary concern since +# we're testing proctoring dashboard capabilities, not the UI redirect behavior. +@override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) class TestProctoringDashboardViews(SharedModuleStoreTestCase): """ Check for Proctoring view on the new instructor dashboard diff --git a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py index 8e616423929e..49e783b0a37b 100644 --- a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py +++ b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py @@ -32,7 +32,7 @@ from lms.djangoapps.courseware.tests.factories import StudentModuleFactory from lms.djangoapps.courseware.tests.helpers import LoginEnrollmentTestCase from lms.djangoapps.grades.config.waffle import WRITABLE_GRADEBOOK -from lms.djangoapps.instructor.toggles import DATA_DOWNLOAD_V2 +from lms.djangoapps.instructor.toggles import DATA_DOWNLOAD_V2, LEGACY_INSTRUCTOR_DASHBOARD from lms.djangoapps.instructor.views.gradebook_api import calculate_page_info from openedx.core.djangoapps.course_groups.cohorts import set_course_cohorted from openedx.core.djangoapps.discussions.config.waffle import ( @@ -60,6 +60,11 @@ def intercept_renderer(path, context): @ddt.ddt +# Enable legacy instructor dashboard to access dashboard HTML instead of getting 302 redirects. +# The 302 redirects to MFE don't affect instructor functionality, but these tests need to verify +# that the dashboard sections exist in the HTML, which is no longer a primary concern since +# we're testing instructor dashboard capabilities, not the UI redirect behavior. +@override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase, XssTestMixin): """ Tests for the instructor dashboard (not legacy). From 8af723b3bea572fa064587565a3c2853d9baaf51 Mon Sep 17 00:00:00 2001 From: javier ontiveros Date: Thu, 23 Apr 2026 12:21:52 -0600 Subject: [PATCH 09/10] chore: added DEPR-38432 comment to know the things that need to be deprecated --- .../bulk_email/tests/test_course_optout.py | 7 +++---- lms/djangoapps/bulk_email/tests/test_email.py | 7 +++---- .../bulk_email/tests/test_signals.py | 7 +++---- .../instructor/tests/test_certificates.py | 21 ++++++++----------- lms/djangoapps/instructor/tests/test_email.py | 7 +++---- .../instructor/tests/test_filters.py | 7 +++---- .../instructor/tests/test_proctoring.py | 7 +++---- .../tests/views/test_instructor_dashboard.py | 7 +++---- .../instructor/views/instructor_dashboard.py | 2 ++ 9 files changed, 32 insertions(+), 40 deletions(-) diff --git a/lms/djangoapps/bulk_email/tests/test_course_optout.py b/lms/djangoapps/bulk_email/tests/test_course_optout.py index 811ae4b71930..a71b7fd832e8 100644 --- a/lms/djangoapps/bulk_email/tests/test_course_optout.py +++ b/lms/djangoapps/bulk_email/tests/test_course_optout.py @@ -28,10 +28,9 @@ @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 -# Enable legacy instructor dashboard to access email section HTML instead of getting 302 redirects. -# The 302 redirects to MFE don't affect email functionality, but these tests need to verify -# that the email section exists in the HTML, which is no longer a primary concern since -# we're testing email opt-out capabilities, not the UI redirect behavior. +# Tests for legacy views. When DEPR-38432 is picked up, these tests will require the following changes: +# Either remove or leave the specific parts that reference the legacy instructor dashboard, +# and remove the override_waffle_flag for LEGACY_INSTRUCTOR_DASHBOARD. @override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) class TestOptoutCourseEmails(ModuleStoreTestCase): """ diff --git a/lms/djangoapps/bulk_email/tests/test_email.py b/lms/djangoapps/bulk_email/tests/test_email.py index 3422c3b6d6c2..96ec03d5058e 100644 --- a/lms/djangoapps/bulk_email/tests/test_email.py +++ b/lms/djangoapps/bulk_email/tests/test_email.py @@ -64,10 +64,9 @@ def mock_update_subtask_status(entry_id, current_task_id, new_subtask_status): return mock_update_subtask_status -# Enable legacy instructor dashboard to access email section HTML instead of getting 302 redirects. -# The 302 redirects to MFE don't affect email functionality, but these tests need to verify -# that the email section exists in the HTML, which is no longer a primary concern since -# we're testing email sending capabilities, not the UI redirect behavior. +# Tests for legacy views. When DEPR-38432 is picked up, these tests will require the following changes: +# Either remove or leave the specific parts that reference the legacy instructor dashboard, +# and remove the override_waffle_flag for LEGACY_INSTRUCTOR_DASHBOARD. @override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) class EmailSendFromDashboardTestCase(SharedModuleStoreTestCase): """ diff --git a/lms/djangoapps/bulk_email/tests/test_signals.py b/lms/djangoapps/bulk_email/tests/test_signals.py index 287ff42bb838..abb299ad30f0 100644 --- a/lms/djangoapps/bulk_email/tests/test_signals.py +++ b/lms/djangoapps/bulk_email/tests/test_signals.py @@ -24,10 +24,9 @@ @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 -# Enable legacy instructor dashboard to access email section HTML instead of getting 302 redirects. -# The 302 redirects to MFE don't affect email functionality, but these tests need to verify -# that the email section exists in the HTML, which is no longer a primary concern since -# we're testing email opt-out signal behavior, not the UI redirect behavior. +# Tests for legacy views. When DEPR-38432 is picked up, these tests will require the following changes: +# Either remove or leave the specific parts that reference the legacy instructor dashboard, +# and remove the override_waffle_flag for LEGACY_INSTRUCTOR_DASHBOARD. @override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) class TestOptoutCourseEmailsBySignal(ModuleStoreTestCase): """ diff --git a/lms/djangoapps/instructor/tests/test_certificates.py b/lms/djangoapps/instructor/tests/test_certificates.py index aa4f7673bb76..170d4f9fb89a 100644 --- a/lms/djangoapps/instructor/tests/test_certificates.py +++ b/lms/djangoapps/instructor/tests/test_certificates.py @@ -33,10 +33,9 @@ @ddt.ddt -# Enable legacy instructor dashboard to access certificate management HTML instead of getting 302 redirects. -# The 302 redirects to MFE don't affect certificate functionality, but these tests need to verify -# that the certificate management sections exist in the HTML, which is no longer a primary concern since -# we're testing certificate generation capabilities, not the UI redirect behavior. +# Tests for legacy views. When DEPR-38432 is picked up, these tests will require the following changes: +# Either remove or leave the specific parts that reference the legacy instructor dashboard, +# and remove the override_waffle_flag for LEGACY_INSTRUCTOR_DASHBOARD. @override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) class CertificateTaskViewTests(SharedModuleStoreTestCase): """Tests for the certificate panel of the instructor dash. """ @@ -160,10 +159,9 @@ def test_certificate_regeneration_status_handling(self, cert_status, expected_st @ddt.ddt -# Enable legacy instructor dashboard to access certificate management HTML instead of getting 302 redirects. -# The 302 redirects to MFE don't affect certificate functionality, but these tests need to verify -# that the certificate sections exist in the HTML, which is no longer a primary concern since -# we're testing certificate dashboard capabilities, not the UI redirect behavior. +# Tests for legacy views. When DEPR-XXXX is picked up, these tests will require the following changes: +# Either remove or leave the specific parts that reference the legacy instructor dashboard, +# and remove the override_waffle_flag for LEGACY_INSTRUCTOR_DASHBOARD. @override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) class CertificatesInstructorDashTest(SharedModuleStoreTestCase): """Tests for the certificate panel of the instructor dash. """ @@ -348,10 +346,9 @@ def _assert_enable_certs_button(self, is_enabled): @override_settings(CERT_QUEUE='certificates') @ddt.ddt -# Enable legacy instructor dashboard to access certificate API HTML instead of getting 302 redirects. -# The 302 redirects to MFE don't affect certificate functionality, but these tests need to verify -# that the certificate API sections exist in the HTML, which is no longer a primary concern since -# we're testing certificate API capabilities, not the UI redirect behavior. +# Tests for legacy views. When DEPR-XXXX is picked up, these tests will require the following changes: +# Either remove or leave the specific parts that reference the legacy instructor dashboard, +# and remove the override_waffle_flag for LEGACY_INSTRUCTOR_DASHBOARD. @override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) class CertificatesInstructorApiTest(SharedModuleStoreTestCase): """Tests for the certificates end-points in the instructor dash API. """ diff --git a/lms/djangoapps/instructor/tests/test_email.py b/lms/djangoapps/instructor/tests/test_email.py index 579ebc2a503e..97360cb67737 100644 --- a/lms/djangoapps/instructor/tests/test_email.py +++ b/lms/djangoapps/instructor/tests/test_email.py @@ -25,10 +25,9 @@ from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order -# Enable legacy instructor dashboard to access email management HTML instead of getting 302 redirects. -# The 302 redirects to MFE don't affect email functionality, but these tests need to verify -# that the email sections exist in the HTML, which is no longer a primary concern since -# we're testing email feature flag functionality, not the UI redirect behavior. +# Tests for legacy views. When DEPR-38432 is picked up, these tests will require the following changes: +# Either remove or leave the specific parts that reference the legacy instructor dashboard, +# and remove the override_waffle_flag for LEGACY_INSTRUCTOR_DASHBOARD. @override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) class TestNewInstructorDashboardEmailViewMongoBacked(SharedModuleStoreTestCase): """ diff --git a/lms/djangoapps/instructor/tests/test_filters.py b/lms/djangoapps/instructor/tests/test_filters.py index 4ef3d75ef0fa..3c143e47497f 100644 --- a/lms/djangoapps/instructor/tests/test_filters.py +++ b/lms/djangoapps/instructor/tests/test_filters.py @@ -88,10 +88,9 @@ def run_filter(self, context, template_name): # pylint: disable=arguments-diffe @skip_unless_lms -# Enable legacy instructor dashboard to access filter testing HTML instead of getting 302 redirects. -# The 302 redirects to MFE don't affect filter functionality, but these tests need to verify -# that the dashboard filters work properly in the HTML, which is no longer a primary concern since -# we're testing filter pipeline capabilities, not the UI redirect behavior. +# Tests for legacy views. When DEPR-38432 is picked up, these tests will require the following changes: +# Either remove or leave the specific parts that reference the legacy instructor dashboard, +# and remove the override_waffle_flag for LEGACY_INSTRUCTOR_DASHBOARD. @override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) class InstructorDashboardFiltersTest(ModuleStoreTestCase): """ diff --git a/lms/djangoapps/instructor/tests/test_proctoring.py b/lms/djangoapps/instructor/tests/test_proctoring.py index 73982bc1b4d6..8df3f64bfe08 100644 --- a/lms/djangoapps/instructor/tests/test_proctoring.py +++ b/lms/djangoapps/instructor/tests/test_proctoring.py @@ -23,10 +23,9 @@ @patch.dict(settings.FEATURES, {'ENABLE_SPECIAL_EXAMS': True}) @ddt.ddt -# Enable legacy instructor dashboard to access proctoring management HTML instead of getting 302 redirects. -# The 302 redirects to MFE don't affect proctoring functionality, but these tests need to verify -# that the proctoring sections exist in the HTML, which is no longer a primary concern since -# we're testing proctoring dashboard capabilities, not the UI redirect behavior. +# Tests for legacy views. When DEPR-38432 is picked up, these tests will require the following changes: +# Either remove or leave the specific parts that reference the legacy instructor dashboard, +# and remove the override_waffle_flag for LEGACY_INSTRUCTOR_DASHBOARD. @override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) class TestProctoringDashboardViews(SharedModuleStoreTestCase): """ diff --git a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py index 49e783b0a37b..deac31a182f0 100644 --- a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py +++ b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py @@ -60,10 +60,9 @@ def intercept_renderer(path, context): @ddt.ddt -# Enable legacy instructor dashboard to access dashboard HTML instead of getting 302 redirects. -# The 302 redirects to MFE don't affect instructor functionality, but these tests need to verify -# that the dashboard sections exist in the HTML, which is no longer a primary concern since -# we're testing instructor dashboard capabilities, not the UI redirect behavior. +# Tests for legacy views. When DEPR-38432 is picked up, these tests will require the following changes: +# Either remove or leave the specific parts that reference the legacy instructor dashboard, +# and remove the override_waffle_flag for LEGACY_INSTRUCTOR_DASHBOARD. @override_waffle_flag(LEGACY_INSTRUCTOR_DASHBOARD, active=True) class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase, XssTestMixin): """ diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index 4975fb164995..8388e3d67338 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -150,6 +150,8 @@ def instructor_dashboard_2(request, course_id): # lint-amnesty, pylint: disable if not legacy_instructor_dashboard(): return redirect(get_instructor_dashboard_url(course_key)) + # WHEN DEPR-38432 is picked up the legacy dashboard may be removed + sections = [] if access['staff']: sections_content = [ From e2feb3a3988f9fb2905e5e644df980745b712e23 Mon Sep 17 00:00:00 2001 From: javier ontiveros Date: Thu, 23 Apr 2026 12:24:39 -0600 Subject: [PATCH 10/10] chore: added depr to toggle Co-authored-by: Copilot --- lms/djangoapps/instructor/toggles.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lms/djangoapps/instructor/toggles.py b/lms/djangoapps/instructor/toggles.py index 84a618fbc9e3..03f9094c021c 100644 --- a/lms/djangoapps/instructor/toggles.py +++ b/lms/djangoapps/instructor/toggles.py @@ -35,6 +35,7 @@ # .. toggle_use_cases: opt_out, temporary # .. toggle_creation_date: 2026-04-20 # .. toggle_target_removal_date: 2026-11-01 +# .. toggle_tickets: https://github.com/openedx/openedx-platform/issues/38432 LEGACY_INSTRUCTOR_DASHBOARD = WaffleFlag( f'{WAFFLE_FLAG_NAMESPACE}.legacy_instructor_dashboard', __name__ )