Skip to content

feat: apply ADR 0036 (nested JSON normalization) across 6 standardized APIs#38773

Open
taimoor-ahmed-1 wants to merge 6 commits into
openedx:feat/axim-api_improvementsfrom
edly-io:feat/apply_nested_json_adr_on_apis
Open

feat: apply ADR 0036 (nested JSON normalization) across 6 standardized APIs#38773
taimoor-ahmed-1 wants to merge 6 commits into
openedx:feat/axim-api_improvementsfrom
edly-io:feat/apply_nested_json_adr_on_apis

Conversation

@taimoor-ahmed-1

Copy link
Copy Markdown
Contributor

Summary

Applies ADR 0036 — Reduce Deeply Nested JSON via Minimal/Flattened Views to the 6 APIs recently standardized under the FC-0118 effort.

Each commit is scoped to one area:

Commit Area Related PR Type
1 Xblock v1 #38723 feat?view=minimal
2 CourseHome v3 #38694 feat?fields= on list action
3 CourseHome v4 #38684 docs — audit, out of scope (flat list → ADR 0032)
4 Enrollment v2 #38724 feat?view=minimal collapses embedded course_details
5 Course Detail v3 #38708 feat?view=minimal + ?fields=
6 AuthorGrading v3 #38726 docs — audit, largely out of scope (small flat object)

What ADR 0036 hooks were added

  • Xblock v1 (?view=minimal) — drops heavy/contextual fields (data, metadata, fields, student_view_data, edited_on, published) from the tree-shaped xblock response, keeping only structural fields (id, display_name, category, children, has_children, studio_url). Default response unchanged.
  • CourseHome v3 (?fields=) — the list action returns a wide StudioHomeSerializer payload with ~25 top-level keys; ?fields= restricts to an explicit CSV subset.
  • Enrollment v2 (?view=minimal) — each enrollment row's embedded course_details sub-object (with course_modes list) is collapsed to a single course_id string. Useful for S2S / AI agents that only need enrollment-to-course mapping.
  • Course Detail v3 (?view=minimal + ?fields=) — wide ~40-field response with embedded instructor_info and learning_info. ?view=minimal drops the heavy text/embedded fields; ?fields= restricts to a CSV subset; both compose (?view= first, then ?fields=).

Shared utilities

The apply_field_selection() helper landed in cms/.../rest_api/v3/utils.py (commit 2) is reused by Course Detail (commit 5). Any future v3 viewset that needs ?fields= should import it directly.

Tests added

Test class File Cases
TestXblockViewSetMinimalView v1/views/tests/test_xblock_viewset.py 4
TestHomeViewSetFieldSelection v3/views/tests/test_home.py 2
TestEnrollmentViewSetMinimalView v2/tests/test_views.py 2
TestCourseDetailsViewSetNestedJsonNormalization v3/views/tests/test_course_details.py 3

11 new tests total, all mocked + MongoDB-free.

Default response shapes preserved everywhere

Every `feat:` commit adds an opt-in query parameter. No default response shape was changed — existing Studio frontend / MFE clients keep receiving the same payloads. Per ADR 0036's MFE-frontend exception, this matches the "explicit opt-in" pattern for endpoints already in production with frontend consumers.

Out-of-scope commits

Two areas (CourseHome v4, AuthorGrading v3) don't have meaningful ADR 0036 anti-patterns: CourseHome v4 is a flat paginated list governed by ADR 0032; AuthorGrading v3 is a tiny serializer with a single graders list of small fixed-shape items. Those commits add a documentation audit confirming the scope decision rather than introducing code that has no fields to drop.

Test plan

  • `pytest cms/djangoapps/contentstore/rest_api/v1/views/tests/test_xblock_viewset.py::TestXblockViewSetMinimalView -v`
  • `pytest cms/djangoapps/contentstore/rest_api/v3/views/tests/test_home.py::TestHomeViewSetFieldSelection -v`
  • `pytest cms/djangoapps/contentstore/rest_api/v3/views/tests/test_course_details.py::TestCourseDetailsViewSetNestedJsonNormalization -v`
  • `pytest openedx/core/djangoapps/enrollments/v2/tests/test_views.py::TestEnrollmentViewSetMinimalView -v`
  • Smoke-check the 4 endpoints with both default and `?view=minimal` / `?fields=` query parameters
  • Confirm existing test suites still pass for all 6 affected views (no default-shape regressions)

🤖 Generated with Claude Code

Taimoor Ahmed added 6 commits June 17, 2026 12:43
Adds a ``?view=minimal`` query parameter on ``XblockViewSet.retrieve``
that filters the (tree-shaped) xblock response down to a small set of
structural fields — id, display_name, category, children, has_children,
studio_url — dropping heavy/contextual fields such as data, metadata,
fields, student_view_data, edited_on, published.

Default response shape is unchanged (full xblock payload) to avoid
breaking the existing Studio frontend. The pre-existing ``?fields=...``
query parameter retains its legacy "type of response" semantics
(?fields=graderType, ?fields=ancestorInfo, ?fields=customReadToken);
ADR 0036's CSV-subset interpretation is deferred to a future API
version to avoid breaking those callers.

Adds 4 regression tests covering: default response untouched, minimal
strips heavy fields, minimal keeps structural fields, minimal is a
no-op for non-dict response bodies (legacy ?fields=graderType path).
The v3 ``HomeViewSet.list`` action returns a wide ``StudioHomeSerializer``
payload with ~25 top-level keys (courses, archived_courses, libraries,
allowed_organizations, allowed_organizations_for_libraries, plus Studio
settings). Adds a ``?fields=`` query parameter so clients can request a
subset of those keys explicitly.

The flat-list ``courses`` and ``libraries`` actions are out of scope
(each returns a single-key dict wrapping a list of small items — no
nested or wide structure to filter).

Adds a shared ``apply_field_selection`` helper to ``v3/utils.py`` so
future v3 viewsets can opt into the same convention without
re-implementing it. Adds 2 regression tests: default keeps all keys,
``?fields=courses,libraries`` restricts to exactly those keys.
Adds an ADR 0036 entry to ``HomeCoursesViewSet``'s compliance list
explicitly marking this endpoint as out of scope.

Rationale: the v4 home endpoint returns a flat paginated list
governed by ADR 0032 (DefaultPagination 7-field envelope). ADR 0036
excludes flat lists from its ``?view=`` / ``?depth=`` / minimal-by-default
requirements — those apply to tree-shaped responses or wide flat
objects with embedded sub-objects. Each course item is a thin 9-field
record with no nested children, no embedded sub-objects, and no tree
structure to bound.

Per-item ``?fields=`` subset filtering remains a possible follow-up
(would require a dynamic-fields serializer mixin and per-field schema
documentation) but is deferred to keep the v4 contract stable for the
existing Studio frontend.
…details)

Each enrollment record returned by the v2 ``EnrollmentViewSet.list`` and
``EnrollmentRetrieveView.get`` actions embeds a full ``course_details``
sub-object (which itself includes a ``course_modes`` list and other
heavy course-overview fields). Server-to-server callers and AI agents
that only need to know which courses a user is enrolled in shouldn't
have to parse the embedded sub-object on every row.

Adds a ``?view=minimal`` query parameter on both actions that collapses
the embedded ``course_details`` to a single ``course_id`` string. The
enrollment-level fields (``created``, ``mode``, ``is_active``, ``user``)
are preserved. Default response shape is unchanged.

Adds 2 regression tests (mocked, MongoDB-free): default list keeps the
full shape, ``?view=minimal`` collapses each row's course_details to a
flat course_id and the heavy fields are dropped.
The v3 ``CourseDetailsViewSet.retrieve`` action returns a wide
``CourseDetailsSerializer`` payload with ~40 top-level fields plus a
nested ``instructor_info`` sub-object (instructor names, bios, image
URLs) and a ``learning_info`` long-form list. The default full payload
is preserved; two new opt-in query parameters apply ADR 0036:

  * ``?view=minimal``  drops heavy fields (overview, syllabus,
    description, short_description, instructor_info, learning_info,
    banner_image_name / banner_image_asset_path, video_thumbnail
    assets, license) — leaving only identification (course_id, org,
    run, title, subtitle, language), schedule (start_date, end_date,
    enrollment_start, enrollment_end, certificate_available_date),
    and flags (self_paced, certificates_display_behavior, has_changes).

  * ``?fields=a,b,c``  keeps an explicit CSV subset of top-level keys.
    Composes with ``?view=minimal`` — the preset is applied first,
    then the explicit subset.

Reuses ``apply_field_selection`` from ``v3/utils.py`` (introduced in
the CourseHome v3 commit) so the convention is consistent across v3.

Adds 3 regression tests (mocked, MongoDB-free): default keeps all
fields, ?view=minimal drops the heavy/embedded ones and keeps
identification/schedule/flags, ?fields=course_id,title restricts to
exactly those keys.
Adds an ADR 0036 entry to ``AuthoringGradingViewSet``'s compliance list.

Rationale: the v3 grading response is a single top-level ``graders``
list of small fixed-shape objects (type, min_count, drop_count,
short_label, weight, id) — no tree nesting, no embedded sub-objects,
no ``children`` field, no wide flat object. ``?view=minimal`` and
``?fields=`` would have no fields to drop; the only ADR 0036 concern
that applies is anti-pattern #3 (unbounded child list).

In practice each course has ≤8 graders (Homework, Lab, Exam, etc.)
and the update flow is exercised only by course-authoring staff,
so the real-world payload is always small. The hard cap is enforced
upstream by ``CourseGradingModel.update_from_json``; documented as a
note rather than re-implemented at the view layer.
@taimoor-ahmed-1 taimoor-ahmed-1 requested a review from a team as a code owner June 17, 2026 08:01
@openedx-webhooks openedx-webhooks added the open-source-contribution PR author is not from Axim or 2U label Jun 17, 2026
@openedx-webhooks

Copy link
Copy Markdown

Thanks for the pull request, @taimoor-ahmed-1!

This repository is currently maintained by @openedx/wg-maintenance-openedx-platform.

Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review.

🔘 Get product approval

If you haven't already, check this list to see if your contribution needs to go through the product review process.

  • If it does, you'll need to submit a product proposal for your contribution, and have it reviewed by the Product Working Group.
    • This process (including the steps you'll need to take) is documented here.
  • If it doesn't, simply proceed with the next step.
🔘 Provide context

To help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:

  • Dependencies

    This PR must be merged before / after / at the same time as ...

  • Blockers

    This PR is waiting for OEP-1234 to be accepted.

  • Timeline information

    This PR must be merged by XX date because ...

  • Partner information

    This is for a course on edx.org.

  • Supporting documentation
  • Relevant Open edX discussion forum threads
🔘 Get a green build

If one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green.

Details
Where can I find more information?

If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources:

When can I expect my changes to be merged?

Our goal is to get community contributions seen and reviewed as efficiently as possible.

However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:

  • The size and impact of the changes that it introduces
  • The need for product review
  • Maintenance status of the parent repository

💡 As a result it may take up to several weeks or months to complete a review and merge your PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

open-source-contribution PR author is not from Axim or 2U

Projects

Status: Needs Triage

Development

Successfully merging this pull request may close these issues.

3 participants