openedx / openedx/frontend-app-learning

Migrate course custom (static) pages into the learning MFE

Open
#2,056 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
70
Forks
335
Avg merge
1d 17h
Merged PRs (30d)
35

Description

Context

Course teams can add custom static HTML pages to a course - things like a syllabus or a resources page - that show up as tabs in the course navigation. These are backed by the StaticTabBlock XBlock (xmodule/html_block.py, tab type static_tab). Authoring already moved to the authoring MFE; the learner-facing render is the piece that's still legacy.

Today the learning MFE shows the tab in its nav but links out to the old server-rendered page. The tab list comes from CourseTabSerializer (lms/djangoapps/course_home_api/course_metadata/serializers.py), whose url for a static tab resolves to the legacy StaticCourseTabView at /courses/{course_id}/{tab_slug}/. So clicking a custom page leaves the MFE and loads courseware/static_tab.html. This issue is about rendering those pages inside the learning MFE so the legacy view can go away.

What exists today

  • Legacy render: StaticCourseTabView (lms/djangoapps/courseware/views/views.py:475) at /courses/{course_id}/{tab_slug}/. It resolves the tab to a block and renders the block's STUDENT_VIEW fragment via get_static_tab_fragment (views.py:1298).
  • The block is addressable: get_static_tab_fragment builds the usage key as course.id.make_usage_key('static_tab', tab.url_slug), i.e. block-v1:{course}+type@static_tab+block@{url_slug}. That means it can be rendered chromelessly through the existing render_xblock endpoint at /xblock/{usage_key}.
  • MFE nav: the learning MFE already renders the tab list in src/course-tabs/ (CourseTabLinksList.tsx), one { url, title, slug } link per tab. For a static tab the url is the legacy page.
  • Iframe precedent: the MFE already embeds XBlock content through render_xblock for units - src/courseware/course/sequence/Unit/ContentIFrame.jsx and getIFrameUrl in Unit/urls.ts, which builds ${LMS_BASE_URL}/xblock/{id}. The same mechanism covers a static tab.

Proposed approach

Render the custom page as an in-MFE tab that iframes the static tab block, reusing the unit iframe machinery:

  1. Backend (openedx-platform): give the MFE what it needs to render in place instead of linking out. Extend the tab data (CourseTabSerializer or a companion field) so a static tab carries its block usage key, and point the tab's url at an in-MFE route rather than /courses/{id}/{slug}/.
  2. MFE route + page: add a route for custom pages and a component that iframes /xblock/{static_tab_usage_key} via the existing ContentIFrame path. Keep the tab highlighted in the nav while it's open.
  3. Retire the legacy view: once the MFE serves these, the StaticCourseTabView render, its static_tab.html template, and the legacy tab url can be removed. That removal is tracked back in openedx/openedx-platform#38936.

Open questions

  • Access control: StaticCourseTabView runs get_course_with_access and shows limited-access warnings. Confirm render_xblock enforces the same access for a static_tab block, or add it.
  • Multiple pages: a course can have several static tabs. The route should key off the tab slug and resolve the right block.
  • Styling / chrome: the legacy page renders inside the LMS course chrome; in the MFE it should sit inside the course tab layout with the block iframed as content. Confirm the block's own CSS/JS load correctly through render_xblock.
  • Height / resize: reuse useIFrameBehavior so the iframe resizes to content, same as units.
  • Transition: decide whether to ship the MFE route behind the existing tab data first and drop the legacy view in a follow-up, or do both together.

References

  • Tracker: openedx/openedx-platform#38936 (Course Home: Custom Pages row).
  • Legacy view: lms/djangoapps/courseware/views/views.py:475 (StaticCourseTabView), :1298 (get_static_tab_fragment); template lms/templates/courseware/static_tab.html; route lms/urls.py:785-790.
  • Block: xmodule/html_block.py (StaticTabBlock), xmodule/tabs.py:297 (StaticTab).
  • Tab serialization: lms/djangoapps/course_home_api/course_metadata/serializers.py (CourseTabSerializer).
  • MFE iframe pattern: src/courseware/course/sequence/Unit/ContentIFrame.jsx, Unit/urls.ts (getIFrameUrl), Unit/hooks/useIFrameBehavior.ts; nav in src/course-tabs/.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with CourseTabSerializer and src/course-tabs/, then compare the existing iframe flow in ContentIFrame.jsx, Unit/urls.ts, and useIFrameBehavior.ts. Trace StaticCourseTabView and get_static_tab_fragment in the platform files to verify access behavior and usage-key handling. Done means static tabs open inside the learning MFE with correct navigation state, access behavior, and iframe sizing, without relying on the legacy page.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, react
Domain
api, backend, frontend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.