diff --git a/.gitignore b/.gitignore index d6f1202bf4ce..1866979edc93 100644 --- a/.gitignore +++ b/.gitignore @@ -136,6 +136,7 @@ build \#*\# .env/ openedx/core/djangoapps/django_comment_common/comment_client/python +openedx/core/djangoapps/cache_toolbox/__pycache__ autodeploy.properties .ws_migrations_complete dist diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index c7d7ce7b177e..e71f3f07ec0d 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -30,6 +30,7 @@ from pytz import UTC from openedx.core.djangoapps.waffle_utils.models import WaffleFlagCourseOverrideModel from rest_framework import status +from rest_framework.test import APIClient from web_fragments.fragment import Fragment from xblock.core import XBlock from xblock.fields import Scope, String @@ -76,7 +77,10 @@ from lms.djangoapps.courseware.tests.factories import StudentModuleFactory from lms.djangoapps.courseware.tests.helpers import MasqueradeMixin, get_expiration_banner_text, set_preview_mode from lms.djangoapps.courseware.testutils import RenderXBlockTestMixin -from lms.djangoapps.courseware.toggles import COURSEWARE_OPTIMIZED_RENDER_XBLOCK +from lms.djangoapps.courseware.toggles import ( + COURSEWARE_MICROFRONTEND_SEARCH_ENABLED, + COURSEWARE_OPTIMIZED_RENDER_XBLOCK, +) from lms.djangoapps.courseware.user_state_client import DjangoXBlockUserStateClient from lms.djangoapps.courseware.views.views import ( BasePublicVideoXBlockView, @@ -3683,3 +3687,41 @@ def test_get_template_and_context(self): assert template == 'public_video_share_embed.html' assert context['fragment'] == fragment assert context['course'] == self.course + + +class TestCoursewareMFESearchAPI(SharedModuleStoreTestCase): + """ + Tests the endpoint to fetch the Courseware Search waffle flag enabled status. + """ + + def setUp(self): + super().setUp() + + self.course = CourseFactory.create() + + self.client = APIClient() + self.apiUrl = reverse('courseware_search_enabled_view', kwargs={'course_id': str(self.course.id)}) + + @override_waffle_flag(COURSEWARE_MICROFRONTEND_SEARCH_ENABLED, active=True) + def test_courseware_mfe_search_enabled(self): + """ + Getter to check if user is allowed to use Courseware Search. + """ + + response = self.client.get(self.apiUrl, content_type='application/json') + body = json.loads(response.content.decode('utf-8')) + + self.assertEqual(response.status_code, 200) + self.assertEqual(body, {'enabled': True}) + + @override_waffle_flag(COURSEWARE_MICROFRONTEND_SEARCH_ENABLED, active=False) + def test_is_mfe_search_disabled(self): + """ + Getter to check if user is allowed to use Courseware Search. + """ + + response = self.client.get(self.apiUrl, content_type='application/json') + body = json.loads(response.content.decode('utf-8')) + + self.assertEqual(response.status_code, 200) + self.assertEqual(body, {'enabled': False}) diff --git a/lms/djangoapps/courseware/toggles.py b/lms/djangoapps/courseware/toggles.py index 3e1375512323..78cecac7f584 100644 --- a/lms/djangoapps/courseware/toggles.py +++ b/lms/djangoapps/courseware/toggles.py @@ -55,6 +55,19 @@ f'{WAFFLE_FLAG_NAMESPACE}.mfe_progress_milestones_streak_celebration', __name__ ) +# .. toggle_name: courseware.mfe_courseware_search +# .. toggle_implementation: WaffleFlag +# .. toggle_default: False +# .. toggle_description: Enables Courseware Search on Learning MFE +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2023-09-28 +# .. toggle_target_removal_date: None +# .. toggle_tickets: KBK-20 +# .. toggle_warning: None. +COURSEWARE_MICROFRONTEND_SEARCH_ENABLED = CourseWaffleFlag( + f'{WAFFLE_FLAG_NAMESPACE}.mfe_courseware_search', __name__ +) + # .. toggle_name: courseware.mfe_progress_milestones_streak_discount_enabled # .. toggle_implementation: CourseWaffleFlag # .. toggle_default: False @@ -153,3 +166,10 @@ def course_is_invitation_only(courselike) -> bool: def learning_assistant_is_active(course_key): return COURSEWARE_LEARNING_ASSISTANT.is_enabled(course_key) + + +def courseware_mfe_search_is_enabled(course_key=None): + """ + Return whether the courseware.mfe_courseware_search flag is on. + """ + return COURSEWARE_MICROFRONTEND_SEARCH_ENABLED.is_enabled(course_key) diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index 085e9e5a19f4..fdc90a46a513 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -18,8 +18,8 @@ from django.core.exceptions import PermissionDenied from django.db import transaction from django.db.models import Q, prefetch_related_objects -from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden from django.shortcuts import redirect +from django.http import JsonResponse, Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden from django.template.context_processors import csrf from django.urls import reverse from django.utils.decorators import method_decorator @@ -38,8 +38,8 @@ from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey, UsageKey from openedx_filters.learning.filters import CourseAboutRenderStarted -from pytz import UTC from requests.exceptions import ConnectionError, Timeout # pylint: disable=redefined-builtin +from pytz import UTC from rest_framework import status from rest_framework.decorators import api_view, throttle_classes from rest_framework.response import Response @@ -87,7 +87,7 @@ from lms.djangoapps.courseware.model_data import FieldDataCache from lms.djangoapps.courseware.models import BaseStudentModuleHistory, StudentModule from lms.djangoapps.courseware.permissions import MASQUERADE_AS_STUDENT, VIEW_COURSE_HOME, VIEW_COURSEWARE -from lms.djangoapps.courseware.toggles import course_is_invitation_only +from lms.djangoapps.courseware.toggles import course_is_invitation_only, courseware_mfe_search_is_enabled from lms.djangoapps.courseware.user_state_client import DjangoXBlockUserStateClient from lms.djangoapps.courseware.utils import ( _use_new_financial_assistance_flow, @@ -2275,3 +2275,15 @@ def get_learner_username(learner_identifier): learner = User.objects.filter(Q(username=learner_identifier) | Q(email=learner_identifier)).first() if learner: return learner.username + + +@api_view(['GET']) +def courseware_mfe_search_enabled(request, course_id=None): + """ + Simple GET endpoint to expose whether the course may use Courseware Search. + """ + + course_key = CourseKey.from_string(course_id) if course_id else None + + payload = {"enabled": courseware_mfe_search_is_enabled(course_key)} + return JsonResponse(payload) diff --git a/lms/urls.py b/lms/urls.py index dc673054df9b..8b314f9f1ea2 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -752,6 +752,16 @@ ), ] +urlpatterns += [ + re_path( + r'^courses/{}/courseware-search/enabled/$'.format( + settings.COURSE_ID_PATTERN, + ), + courseware_views.courseware_mfe_search_enabled, + name='courseware_search_enabled_view', + ), +] + urlpatterns += [ re_path( r'^courses/{}/lti_tab/(?P[^/]+)/$'.format(