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

@@ -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

@@ -0,0 +1,121 @@
"""
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
from six.moves.urllib.parse import urlencode
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.search import navigation_index, path_to_location
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
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
Returns:
Redirect url string
"""
(
course_key, chapter, section, vertical_unused,
position, final_target_id
) = path_to_location(modulestore(), usage_key, request)
# choose the appropriate view (and provide the necessary args) based on the
# args provided by the redirect.
# Rely on index to do all error handling and access control.
if chapter is None:
redirect_url = reverse('courseware', args=(six.text_type(course_key), ))
elif section is None:
redirect_url = reverse('courseware_chapter', args=(six.text_type(course_key), chapter))
elif position is None:
redirect_url = reverse(
'courseware_section',
args=(six.text_type(course_key), chapter, section)
)
else:
# Here we use the navigation_index from the position returned from
# path_to_location - we can only navigate to the topmost vertical at the
# moment
redirect_url = reverse(
'courseware_position',
args=(six.text_type(course_key), chapter, section, navigation_index(position))
)
redirect_url += "?{}".format(urlencode({'activate_block_id': six.text_type(final_target_id)}))
return redirect_url
def get_learning_mfe_courseware_url(course_key, sequence_key=None, unit_key=None):
"""
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
vertical IDs to format it's URL. For simplicity and performance reasons,
this method does not inspect the modulestore to try to figure out what
Unit/Vertical a sequence is in. If you try to pass in a unit_key without
a sequence_key, the value will just be ignored and you'll get a URL pointing
to just the course_key.
It is also capable of determining our section and vertical if they're not
present. Fully specifying it all is preferable, though, as the
micro-frontend can save itself some work, resulting in a better user
experience.
We're building a URL like this:
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.
"""
mfe_link = '{}/course/{}'.format(settings.LEARNING_MICROFRONTEND_URL, course_key)
if sequence_key:
mfe_link += '/{}'.format(sequence_key)
if unit_key:
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)