refactor: centralize learning MFE URL-building logic (#26689)

In preparation for switching LMS/Studio over
from serving legacy courseware URLs in certain
places (for example, resume_course_url) to serving
learning micro-frontend URLs.

TNL-7796
This commit is contained in:
Kyle McCormick
2021-02-24 08:50:15 -05:00
committed by GitHub
parent 441bfffd9b
commit 558d2eb52c
14 changed files with 67 additions and 75 deletions

View File

@@ -1,8 +0,0 @@
"""Deprecated import support. Auto-generated by import_shims/generate_shims.sh."""
# pylint: disable=redefined-builtin,wrong-import-position,wildcard-import,useless-suppression,line-too-long
from import_shims.warn import warn_deprecated_import
warn_deprecated_import('course_home_api.utils', 'lms.djangoapps.course_home_api.utils')
from lms.djangoapps.course_home_api.utils import *

View File

@@ -1,8 +0,0 @@
"""Deprecated import support. Auto-generated by import_shims/generate_shims.sh."""
# pylint: disable=redefined-builtin,wrong-import-position,wildcard-import,useless-suppression,line-too-long
from import_shims.warn import warn_deprecated_import
warn_deprecated_import('courseware.url_helpers', 'lms.djangoapps.courseware.url_helpers')
from lms.djangoapps.courseware.url_helpers import *

View File

@@ -31,7 +31,6 @@ from lms.djangoapps.course_home_api.toggles import (
course_home_mfe_dates_tab_is_active,
course_home_mfe_outline_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.context_processor import user_timezone_locale_prefs
from lms.djangoapps.courseware.courses import get_course_date_blocks, get_course_info_section, get_course_with_access
@@ -46,6 +45,7 @@ from openedx.features.course_experience.course_updates import (
dismiss_current_update_for_user,
get_current_update_for_user
)
from openedx.features.course_experience.url_helpers import get_learning_mfe_home_url
from openedx.features.course_experience.utils import get_course_outline_block_tree, get_start_block
from openedx.features.discounts.utils import generate_offer_data
from xmodule.course_module import COURSE_VISIBILITY_PUBLIC, COURSE_VISIBILITY_PUBLIC_OUTLINE
@@ -189,7 +189,7 @@ class OutlineTabView(RetrieveAPIView):
dates_tab_link = request.build_absolute_uri(reverse('dates', args=[course.id]))
if course_home_mfe_dates_tab_is_active(course.id):
dates_tab_link = get_microfrontend_url(course_key=course.id, view_name='dates')
dates_tab_link = get_learning_mfe_home_url(course_key=course.id, view_name='dates')
# Set all of the defaults
access_expiration = None

View File

@@ -1,23 +0,0 @@
"""Utility functions for course home"""
from django.conf import settings
def get_microfrontend_url(course_key, view_name=None):
"""
Takes in a course key and view name, returns the appropriate course home mfe route
"""
mfe_link = f'{settings.LEARNING_MICROFRONTEND_URL}/course/{course_key}'
if view_name:
mfe_link += f'/{view_name}'
return mfe_link
def is_request_from_learning_mfe(request):
"""
Returns whether the given request was made by the frontend-app-learning MFE.
"""
return (settings.LEARNING_MICROFRONTEND_URL and
request.META.get('HTTP_REFERER', '').startswith(settings.LEARNING_MICROFRONTEND_URL))

View File

@@ -12,9 +12,9 @@ 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.utils import get_microfrontend_url
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
from common.djangoapps.student.models import CourseEnrollment
from xmodule.tabs import CourseTab, CourseTabList, course_reverse_func_from_name_func, key_checker
@@ -44,7 +44,7 @@ class CoursewareTab(EnrolledTab):
def __init__(self, tab_dict):
def link_func(course, reverse_func):
if course_home_mfe_outline_tab_is_active(course.id):
return get_microfrontend_url(course_key=course.id, view_name='home')
return get_learning_mfe_home_url(course_key=course.id, view_name='home')
else:
reverse_name_func = lambda course: default_course_url_name(course.id)
url_func = course_reverse_func_from_name_func(reverse_name_func)
@@ -326,7 +326,7 @@ class DatesTab(EnrolledTab):
def __init__(self, tab_dict):
def link_func(course, reverse_func):
if course_home_mfe_dates_tab_is_active(course.id):
return get_microfrontend_url(course_key=course.id, view_name=self.view_name)
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)])

View File

@@ -61,7 +61,6 @@ from lms.djangoapps.courseware.toggles import (
REDIRECT_TO_COURSEWARE_MICROFRONTEND,
)
from lms.djangoapps.courseware.url_helpers import get_microfrontend_url, get_redirect_url
from lms.djangoapps.courseware.user_state_client import DjangoXBlockUserStateClient
from lms.djangoapps.courseware.views.index import show_courseware_mfe_link
from lms.djangoapps.experiments.testutils import override_experiment_waffle_flag
@@ -88,6 +87,7 @@ from openedx.features.course_experience import (
RELATIVE_DATES_FLAG
)
from openedx.features.course_experience.tests.views.helpers import add_course_mode
from openedx.features.course_experience.url_helpers import get_learning_mfe_courseware_url, get_legacy_courseware_url
from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseTestConsentRequired
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.student.roles import CourseStaffRole
@@ -258,7 +258,7 @@ class TestJumpTo(ModuleStoreTestCase):
)
expected_url += "?{}".format(urlencode({'activate_block_id': six.text_type(staff_only_vertical.location)}))
assert expected_url == get_redirect_url(course_key, usage_key, request)
assert expected_url == get_legacy_courseware_url(course_key, usage_key, request)
@ddt.ddt
@@ -548,9 +548,9 @@ class ViewsTestCase(BaseViewsTestCase):
def test_get_redirect_url(self):
# test the course location
assert u'/courses/{course_key}/courseware?{activate_block_id}'.format(course_key=text_type(self.course_key), activate_block_id=urlencode({'activate_block_id': text_type(self.course.location)})) == get_redirect_url(self.course_key, self.course.location) # pylint: disable=line-too-long
assert u'/courses/{course_key}/courseware?{activate_block_id}'.format(course_key=text_type(self.course_key), activate_block_id=urlencode({'activate_block_id': text_type(self.course.location)})) == get_legacy_courseware_url(self.course_key, self.course.location) # pylint: disable=line-too-long
# test a section location
assert u'/courses/{course_key}/courseware/Chapter_1/Sequential_1/?{activate_block_id}'.format(course_key=text_type(self.course_key), activate_block_id=urlencode({'activate_block_id': text_type(self.section.location)})) == get_redirect_url(self.course_key, self.section.location) # pylint: disable=line-too-long
assert u'/courses/{course_key}/courseware/Chapter_1/Sequential_1/?{activate_block_id}'.format(course_key=text_type(self.course_key), activate_block_id=urlencode({'activate_block_id': text_type(self.section.location)})) == get_legacy_courseware_url(self.course_key, self.section.location) # pylint: disable=line-too-long
def test_invalid_course_id(self):
response = self.client.get('/courses/MITx/3.091X/')
@@ -3349,16 +3349,16 @@ class TestShowCoursewareMFE(TestCase):
course_key = CourseKey.from_string("course-v1:OpenEdX+MFE+2020")
section_key = UsageKey.from_string("block-v1:OpenEdX+MFE+2020+type@sequential+block@Introduction")
unit_id = "block-v1:OpenEdX+MFE+2020+type@vertical+block@Getting_To_Know_You"
assert get_microfrontend_url(course_key) == (
assert get_learning_mfe_courseware_url(course_key) == (
'https://learningmfe.openedx.org'
'/course/course-v1:OpenEdX+MFE+2020'
)
assert get_microfrontend_url(course_key, section_key, '') == (
assert get_learning_mfe_courseware_url(course_key, section_key, '') == (
'https://learningmfe.openedx.org'
'/course/course-v1:OpenEdX+MFE+2020'
'/block-v1:OpenEdX+MFE+2020+type@sequential+block@Introduction'
)
assert get_microfrontend_url(course_key, section_key, unit_id) == (
assert get_learning_mfe_courseware_url(course_key, section_key, unit_id) == (
'https://learningmfe.openedx.org'
'/course/course-v1:OpenEdX+MFE+2020'
'/block-v1:OpenEdX+MFE+2020+type@sequential+block@Introduction'

View File

@@ -12,8 +12,8 @@ from mock import patch
from six.moves.urllib.parse import urlencode
from lms.djangoapps.courseware.field_overrides import OverrideModulestoreFieldData
from lms.djangoapps.courseware.url_helpers import get_redirect_url
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.features.course_experience.url_helpers import get_legacy_courseware_url
from common.djangoapps.student.tests.factories import AdminFactory, CourseEnrollmentFactory, UserFactory
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
@@ -175,7 +175,7 @@ class RenderXBlockTestMixin(six.with_metaclass(ABCMeta, object)):
self.setup_user(admin=True, enroll=True, login=True)
with check_mongo_calls(mongo_calls):
url = get_redirect_url(self.course.id, self.block_to_be_tested.location)
url = get_legacy_courseware_url(self.course.id, self.block_to_be_tested.location)
response = self.client.get(url)
expected_elements = self.block_specific_chrome_html_elements + self.COURSEWARE_CHROME_HTML_ELEMENTS
for chrome_element in expected_elements:

View File

@@ -44,6 +44,7 @@ from openedx.features.course_experience import (
default_course_url_name
)
from openedx.features.course_experience.views.course_sock import CourseSockFragmentView
from openedx.features.course_experience.url_helpers import get_learning_mfe_courseware_url
from openedx.features.enterprise_support.api import data_sharing_consent_required
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.util.views import ensure_valid_course_key
@@ -65,7 +66,6 @@ from ..model_data import FieldDataCache
from ..module_render import get_module_for_descriptor, toc_for_course
from ..permissions import MASQUERADE_AS_STUDENT
from ..toggles import COURSEWARE_MICROFRONTEND_COURSE_TEAM_PREVIEW, REDIRECT_TO_COURSEWARE_MICROFRONTEND
from ..url_helpers import get_microfrontend_url
from .views import CourseTabView
log = logging.getLogger("edx.courseware.views.index")
@@ -203,7 +203,7 @@ class CoursewareIndex(View):
unit_key = None
except InvalidKeyError:
unit_key = None
url = get_microfrontend_url(
url = get_learning_mfe_courseware_url(
self.course_key,
self.section.location if self.section else None,
unit_key

View File

@@ -56,7 +56,7 @@ 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.utils import get_microfrontend_url, is_request_from_learning_mfe
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
from lms.djangoapps.courseware.courses import (
@@ -84,7 +84,6 @@ from lms.djangoapps.courseware.permissions import ( # lint-amnesty, pylint: dis
VIEW_COURSEWARE,
VIEW_XQA_INTERFACE
)
from lms.djangoapps.courseware.url_helpers import get_redirect_url
from lms.djangoapps.courseware.user_state_client import DjangoXBlockUserStateClient
from lms.djangoapps.experiments.utils import get_experiment_user_metadata_context
from lms.djangoapps.grades.api import CourseGradeFactory
@@ -114,6 +113,7 @@ from openedx.features.content_type_gating.models import ContentTypeGatingConfig
from openedx.features.course_duration_limits.access import generate_course_expired_fragment
from openedx.features.course_experience import DISABLE_UNIFIED_COURSE_TAB_FLAG, course_home_url_name
from openedx.features.course_experience.course_tools import CourseToolsPluginManager
from openedx.features.course_experience.url_helpers import get_legacy_courseware_url
from openedx.features.course_experience.utils import dates_banner_should_display
from openedx.features.course_experience.views.course_dates import CourseDatesFragmentView
from openedx.features.course_experience.waffle import ENABLE_COURSE_ABOUT_SIDEBAR_HTML
@@ -396,7 +396,7 @@ def jump_to(_request, course_id, location):
except InvalidKeyError:
raise Http404(u"Invalid course_key or usage_key") # lint-amnesty, pylint: disable=raise-missing-from
try:
redirect_url = get_redirect_url(course_key, usage_key, _request)
redirect_url = get_legacy_courseware_url(course_key, usage_key, _request)
except ItemNotFoundError:
raise Http404(u"No data at this location: {0}".format(usage_key)) # lint-amnesty, pylint: disable=raise-missing-from
except NoPathToItem:
@@ -1016,7 +1016,7 @@ def dates(request, course_id):
course_key = CourseKey.from_string(course_id)
if course_home_mfe_dates_tab_is_active(course_key) and not request.user.is_staff:
microfrontend_url = get_microfrontend_url(course_key=course_key, view_name=COURSE_DATES_NAME)
microfrontend_url = get_learning_mfe_home_url(course_key=course_key, view_name=COURSE_DATES_NAME)
raise Redirect(microfrontend_url)
# Enable NR tracing for this view based on course

View File

@@ -21,7 +21,6 @@ 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 is_masquerading, setup_masquerade
@@ -29,6 +28,7 @@ from lms.djangoapps.courseware.masquerade import is_masquerading, setup_masquera
from openedx.core.djangoapps.schedules.utils import reset_self_paced_schedule
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
from openedx.features.course_experience.api.v1.serializers import CourseDeadlinesMobileSerializer
from openedx.features.course_experience.url_helpers import get_learning_mfe_home_url
from openedx.features.course_experience.utils import dates_banner_should_display
log = logging.getLogger(__name__)
@@ -91,7 +91,7 @@ def reset_course_deadlines(request):
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')
body_link = get_learning_mfe_home_url(course_key=str(course_key), view_name='dates')
else:
body_link = '{}{}'.format(settings.LMS_ROOT_URL, reverse('dates', args=[str(course_key)]))

View File

@@ -1,8 +1,9 @@
"""
Module to define url helpers functions
Helper functions for logic related to learning (courseare & course home) URLs.
Centralizdd in openedx/features/course_experience instead of lms/djangoapps/courseware
because the Studio course outline may need these utilities.
"""
import six
from django.conf import settings
from django.urls import reverse
@@ -12,12 +13,13 @@ from xmodule.modulestore.django import modulestore
from xmodule.modulestore.search import navigation_index, path_to_location
def get_redirect_url(course_key, usage_key, request=None):
""" Returns the redirect url back to courseware
def get_legacy_courseware_url(course_key, usage_key, request=None):
"""
Return a str with the URL for the specified legacy (LMS-rendered) coursweare content.
Args:
course_id(str): Course Id string
location(str): The location id of course component
usage_key(str): The location id of course component
Raises:
ItemNotFoundError if no data at the location or NoPathToItem if location not in any class
@@ -55,9 +57,9 @@ def get_redirect_url(course_key, usage_key, request=None):
return redirect_url
def get_microfrontend_url(course_key, sequence_key=None, unit_key=None):
def get_learning_mfe_courseware_url(course_key, sequence_key=None, unit_key=None):
"""
Return a str with the URL for the specified content in the Courseware MFE.
Return a str with the URL for the specified coursweare content in the Learning MFE.
The micro-frontend determines the user's position in the vertical via
a separate API call, so all we need here is the course_key, section, and
@@ -74,7 +76,7 @@ def get_microfrontend_url(course_key, sequence_key=None, unit_key=None):
We're building a URL like this:
http://localhost:2000/course-v1:edX+DemoX+Demo_Course/block-v1:edX+DemoX+Demo_Course+type@sequential+block@19a30717eff543078a5d94ae9d6c18a5/block-v1:edX+DemoX+Demo_Course+type@vertical+block@4a1bba2a403f40bca5ec245e945b0d76
http://localhost:2000/course/course-v1:edX+DemoX+Demo_Course/block-v1:edX+DemoX+Demo_Course+type@sequential+block@19a30717eff543078a5d94ae9d6c18a5/block-v1:edX+DemoX+Demo_Course+type@vertical+block@4a1bba2a403f40bca5ec245e945b0d76
`course_key`, `sequence_key`, and `unit_key` can be either OpaqueKeys or
strings. They're only ever used to concatenate a URL string.
@@ -88,3 +90,32 @@ def get_microfrontend_url(course_key, sequence_key=None, unit_key=None):
mfe_link += '/{}'.format(unit_key)
return mfe_link
def get_learning_mfe_home_url(course_key, view_name=None):
"""
Given a course run key and view name, return the appropriate course home (MFE) URL.
We're building a URL like this:
http://localhost:2000/course/course-v1:edX+DemoX+Demo_Course/dates
`course_key` can be either an OpaqueKey or a string.
`view_name` is an optional string.
"""
mfe_link = f'{settings.LEARNING_MICROFRONTEND_URL}/course/{course_key}'
if view_name:
mfe_link += f'/{view_name}'
return mfe_link
def is_request_from_learning_mfe(request):
"""
Returns whether the given request was made by the frontend-app-learning MFE.
"""
return (
settings.LEARNING_MICROFRONTEND_URL and
request.META.get('HTTP_REFERER', '').startswith(settings.LEARNING_MICROFRONTEND_URL)
)

View File

@@ -15,7 +15,7 @@ from web_fragments.fragment import Fragment
from lms.djangoapps.courseware.courses import get_course_date_blocks, get_course_with_access
from lms.djangoapps.courseware.tabs import DatesTab
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 openedx.features.course_experience.url_helpers import get_learning_mfe_home_url
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
@@ -35,7 +35,7 @@ class CourseDatesFragmentView(EdxFragmentView):
dates_tab_enabled = DatesTab.is_enabled(course, request.user)
if course_home_mfe_dates_tab_is_active(course_key):
dates_tab_link = get_microfrontend_url(course_key=course.id, view_name='dates')
dates_tab_link = get_learning_mfe_home_url(course_key=course.id, view_name='dates')
else:
dates_tab_link = reverse('dates', args=[course.id])

View File

@@ -15,7 +15,6 @@ from opaque_keys.edx.keys import CourseKey
from web_fragments.fragment import Fragment
from lms.djangoapps.course_home_api.toggles import course_home_mfe_outline_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 can_self_enroll_in_course, get_course_info_section, get_course_with_access
from lms.djangoapps.course_goals.api import (
@@ -33,6 +32,7 @@ from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
from openedx.core.djangoapps.util.maintenance_banner import add_maintenance_banner
from openedx.features.course_duration_limits.access import generate_course_expired_fragment
from openedx.features.course_experience.course_tools import CourseToolsPluginManager
from openedx.features.course_experience.url_helpers import get_learning_mfe_home_url
from openedx.features.discounts.utils import get_first_purchase_offer_banner_fragment
from openedx.features.discounts.utils import format_strikeout_price
from common.djangoapps.student.models import CourseEnrollment
@@ -72,7 +72,7 @@ class CourseHomeView(CourseTabView):
def render_to_fragment(self, request, course=None, tab=None, **kwargs): # lint-amnesty, pylint: disable=arguments-differ, unused-argument
course_id = six.text_type(course.id)
if course_home_mfe_outline_tab_is_active(course.id) and not request.user.is_staff:
microfrontend_url = get_microfrontend_url(course_key=course_id, view_name="home")
microfrontend_url = get_learning_mfe_home_url(course_key=course_id, view_name="home")
raise Redirect(microfrontend_url)
home_fragment_view = CourseHomeFragmentView()
return home_fragment_view.render_to_fragment(request, course_id=course_id, **kwargs)

View File

@@ -11,8 +11,8 @@ from django.urls import reverse
from django.utils.translation import ngettext, gettext as _
from common.lib.xmodule.xmodule.util.misc import is_xblock_an_assignment
from lms.djangoapps.course_home_api.utils import is_request_from_learning_mfe
from openedx.core.lib.mobile_utils import is_request_from_mobile_app
from openedx.features.course_experience.url_helpers import is_request_from_learning_mfe
from openedx.features.course_experience.utils import dates_banner_should_display
log = logging.getLogger(__name__)