Skip to content

feat: add exec ed course uuid to learner home serializer - #428

Closed
jono-booth wants to merge 1 commit into
release-ulmofrom
jb/exec-ed-course-uuid
Closed

feat: add exec ed course uuid to learner home serializer#428
jono-booth wants to merge 1 commit into
release-ulmofrom
jb/exec-ed-course-uuid

Conversation

@jono-booth

@jono-booth jono-booth commented Aug 14, 2026

Copy link
Copy Markdown

Description

Adds a courseUuid field to the Learner Home CourseRunSerializer, resolved via the existing get_course_uuid_for_course() catalog utility (already used by common.djangoapps.entitlements) for every enrollment — not gated to Executive Education modes. It's null whenever the catalog can't resolve a UUID (e.g. no catalog integration configured, which is the common case in most test/local environments).

This mirrors how homeUrl and other delegating fields on this serializer are handled: no mode-specific branching, just a straight lookup.

Consumers decide whether they care about the value — the actual Executive Education gating lives downstream, in frontend-app-learner-dashboard's existing isExecEd2UCourse check, which is what decides whether to append courseUuid as a course_id query param on the dashboard CTA at all.

This is step 1 of a 3-repo change for ENT-9254 (see below). Today, a learner clicking "Start Course" on their edx.org dashboard for an enrolled ExecEd/GetSmarter presentation lands on a generic Titan/GetSmarter profile page and has to click "View Course" a second time to reach the OLC (Online Learning Campus). Titan can skip that extra hop and redirect straight into the OLC if it's given an identifier for the course.

  • Which edX user roles will this change impact? Learner — in practice only Executive Education learners see any behavior change downstream, since that's the only mode the frontend acts on. No visible change for anyone else; the extra catalog lookup is the only cost for other enrollments.

Supporting information

  • Jira: ENT-9254 — EdX-ExecEd Dashboard redirect to OLC without stopping in Titan

Related changes in other repos:

  1. frontend-app-learner-dashboard: extends useCardExecEdTrackingParam to append courseUuid as a course_id query param on the "Start/Resume Course" link, alongside the existing org_id#26
  2. titan: reads that param in Spree::UsersControllerDecorator#show and redirects straight to the OLC instead of rendering the profile page, when it matches a course — #3903

This PR is safe to merge standalone — it only adds a new field to an existing API response; nothing consumes it yet.

Testing instructions

  1. Enroll a test user in any course.
  2. Hit the Learner Home API (/api/learner_home/init or equivalent) and confirm the corresponding courseRun entry has a courseUuid (non-null whenever the catalog service can resolve one for that course, regardless of mode).

Covered by lms/djangoapps/learner_home/test_serializers.py::TestCourseRunSerializer::test_with_data, which mocks the catalog call so courseUuid is populated, same treatment as every other field on this serializer (no dedicated test, matching homeUrl — neither has branching behavior worth testing beyond the general smoke test).

Verified with a real test run: lms/djangoapps/learner_home/ (112 tests) passes in full.

Other information

  • Depends on nothing merged elsewhere; the frontend and Titan follow-ups depend on this.
  • No database migration.
  • Adds one catalog lookup (cached, already used elsewhere in the codebase for entitlements) to every Learner Home course-run serialization, not just Executive Education ones.

Copilot AI lite review requested due to automatic review settings August 14, 2026 08:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the Learner Home CourseRunSerializer API response to include a courseUuid value (sourced from the Catalog service) for Executive Education (GetSmarter/Titan) enrollments, enabling the frontend to construct Titan redirect URLs with the needed catalog identifier.

Changes:

  • Added a courseUuid serializer method field, populated only for Executive Education enrollment modes.
  • Introduced an EXECUTIVE_EDUCATION_MODES allowlist to gate when the catalog lookup occurs.
  • Updated and expanded serializer tests to verify the field is populated only for Executive Education modes and that non-ExecEd modes do not query the catalog.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
lms/djangoapps/learner_home/serializers.py Adds courseUuid to CourseRunSerializer, conditionally fetching the catalog UUID for ExecEd modes only.
lms/djangoapps/learner_home/test_serializers.py Updates tests to cover ExecEd/non-ExecEd behavior and ensures catalog lookup is avoided when not needed.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

courseUuid is currently computed for all enrollments (triggering catalog lookup on the common path) and the described gating/no-call behavior isn’t enforced by tests.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment on lines +119 to +121
def get_courseUuid(self, instance):
course_uuid = get_course_uuid_for_course(instance.course_id)
return str(course_uuid) if course_uuid else None
Comment on lines +206 to +210
@mock.patch("lms.djangoapps.learner_home.serializers.get_course_uuid_for_course")
def test_with_data(self, mock_get_course_uuid_for_course):
# Tests courseUuid is populated
mock_get_course_uuid_for_course.return_value = uuid4()
input_data = self.create_test_enrollment(course_mode=CourseMode.EXECUTIVE_EDUCATION)
Copilot AI review requested due to automatic review settings September 2, 2026 11:33
@jono-booth
jono-booth force-pushed the jb/exec-ed-course-uuid branch from 11b5ce0 to a86c2ed Compare September 2, 2026 11:33
@jono-booth
jono-booth force-pushed the jb/exec-ed-course-uuid branch from a86c2ed to fea4a9d Compare September 2, 2026 11:36

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

courseUuid lookup currently runs for all enrollments (not just ExecEd) and the updated tests don’t yet enforce the intended ExecEd-only behavior and “no catalog call” constraint for non-ExecEd.

Review details

Suppressed comments (4)

lms/djangoapps/learner_home/serializers.py:121

  • courseUuid is currently looked up via Catalog for every enrollment, which both contradicts the PR description (only ExecEd enrollments should trigger a lookup) and adds an avoidable external call for audit/verified/etc. Please gate the lookup to Executive Education enrollment modes and return None otherwise.
    def get_courseUuid(self, instance):
        course_uuid = get_course_uuid_for_course(instance.course_id)
        return str(course_uuid) if course_uuid else None

lms/djangoapps/learner_home/test_serializers.py:234

  • test_course_uuid currently uses the default audit enrollment, but the new field is intended to be populated only for ExecEd enrollments. This test should create an Executive Education enrollment so it matches the intended behavior and remains stable if courseUuid is gated by mode.
    @mock.patch("lms.djangoapps.learner_home.serializers.get_course_uuid_for_course")
    def test_course_uuid(self, mock_get_course_uuid_for_course):
        # Given the catalog has a UUID for this course
        course_uuid = uuid4()
        mock_get_course_uuid_for_course.return_value = course_uuid

lms/djangoapps/learner_home/test_serializers.py:210

  • test_with_data now patches get_course_uuid_for_course but doesn't configure the mock and still uses the default (audit) enrollment; once courseUuid is correctly only populated for ExecEd enrollments, this test will start failing (courseUuid should be None for audit). Update the test to exercise an ExecEd enrollment and set an explicit UUID return value so the assertions are meaningful.

This issue also appears in the following locations of the same file:

  • line 230
  • line 245
    def test_with_data(self):
        input_data = self.create_test_enrollment()
        input_context = self.create_test_context(input_data.course.id)

        output_data = CourseRunSerializer(input_data, context=input_context).data

lms/djangoapps/learner_home/test_serializers.py:249

  • The tests added for courseUuid don't currently assert the key requirement that non-ExecEd enrollments should not call the Catalog lookup and should return None for courseUuid. Also, test_missing_course_uuid should use an ExecEd enrollment since courseUuid should be null-by-design for normal enrollments regardless of catalog configuration.
    def is_progress_url_matching_course_home_mfe_progress_tab_is_active(self):
        """
        Compares the progress URL generated by CourseRunSerializer to the expected progress URL.

        :return: True if the generated progress URL matches the expected, False otherwise.
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 2, 2026 11:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The serializer currently performs the catalog UUID lookup for all enrollments and the added test asserts UUID population for a non-ExecEd enrollment, conflicting with the stated requirement that only ExecEd modes should populate courseUuid and trigger the lookup.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

lms/djangoapps/learner_home/serializers.py:121

  • courseUuid is currently looked up for every enrollment, which contradicts the PR description (catalog lookup should only fire for Executive Education modes) and can add an unnecessary external/cached API call in the common (audit/verified/etc.) case. Gate the lookup on the enrollment's mode and return None without calling the catalog for non-ExecEd enrollments.
    def get_courseUuid(self, instance):
        course_uuid = get_course_uuid_for_course(instance.course_id)
        return str(course_uuid) if course_uuid else None
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines +230 to +244
@mock.patch("lms.djangoapps.learner_home.serializers.get_course_uuid_for_course")
def test_course_uuid(self, mock_get_course_uuid_for_course):
# Given the catalog has a UUID for this course
course_uuid = uuid4()
mock_get_course_uuid_for_course.return_value = course_uuid
input_data = self.create_test_enrollment()
input_context = self.create_test_context(input_data.course.id)

# When I serialize
output_data = CourseRunSerializer(input_data, context=input_context).data

# Then courseUuid is the stringified catalog UUID, looked up by course_id
self.assertEqual(output_data["courseUuid"], str(course_uuid))
mock_get_course_uuid_for_course.assert_called_once_with(input_data.course_id)

@jono-booth jono-booth closed this Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants