Skip to content

feat: course optimizer extended report - #426

Open
nsprenkle wants to merge 4 commits into
release-ulmofrom
nsprenkle/course-optimizer-extended-report
Open

feat: course optimizer extended report#426
nsprenkle wants to merge 4 commits into
release-ulmofrom
nsprenkle/course-optimizer-extended-report

Conversation

@nsprenkle

@nsprenkle nsprenkle commented Aug 13, 2026

Copy link
Copy Markdown
Member

Description

Provides support for the Course Re-Run Assistant / CORA / extended course optimizer report project. Specifically:

  1. Creates a waffle flag contentstore.enable_course_optimizer_extended_report for enabling this feature. Off by default.
  2. Creates 2 new routes:
    1. POST /api/contentstore/v0/course_analysis_report/{course_id} to post course material to our xpert-ai-workflows endpoint for analysis.
    2. GET /api/contentstore/v0/course_analysis_report_status/{course_id} to check the status of a given analysis.
  3. Establishes a shared secret, COURSE_ANALYSIS_WORKFLOW_API_KEY for authentication to the xpert-ai-workflows service.

Supporting information

Tickets:

Supporting PRs:

Testing instructions

  1. Enable the contentstore.enable_course_optimizer_extended_report waffle flag.
  2. Install the course-optimizer-report plugin in frontend-app-learning from the supporting PR.
  3. Run xpert-ai-workflows/content/cora from the supporting PR.
  4. Navigate to course optimizer for an enabled course / user (e.g. http://localhost:2001/course/course-v1:edX+DemoX+Demo_Course/optimizer)
  5. Click the new "scan now" button.
  6. Verify that the report completes.

Deadline

ASAP

Studio proxies both kicking off a Course Optimizer extended-analysis run
(CourseAnalysisReportView, generating the course export server-side via the
existing create_export_tarball) and polling its status
(CourseAnalysisReportStatusView) to the xpert-ai-workflows backend, keyed by
a shared secret -- the browser only ever talks to Studio.
…lows

Reached via Docker's host-gateway alias, since it runs on the host machine
rather than in the devstack docker network.
Gates the Course Optimizer extended-report section in the authoring MFE,
matching enable_course_optimizer's existing CourseWaffleFlag pattern.
Without this, the frontend's default for this flag would apply
unconditionally everywhere, since it's absent from every course waffle
flags API response until a real flag exists to answer for it.
@nsprenkle
nsprenkle marked this pull request as ready for review August 14, 2026 14:51
Copilot AI lite review requested due to automatic review settings August 14, 2026 14:51

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

Adds Studio-side support for the Course Re-Run Assistant (CORA) / Course Optimizer “extended report” by introducing a feature flag, new API endpoints that proxy to the xpert-ai-workflows backend, and the required configuration/settings.

Changes:

  • Adds a course-scoped waffle flag contentstore.enable_course_optimizer_extended_report and exposes it via the course waffle flags API.
  • Introduces two new v0 REST endpoints to start an extended analysis run and to fetch the latest run status (server-to-server proxy to xpert-ai-workflows).
  • Adds CMS settings for the backend base URL, API key, and request timeout (plus devstack defaults) and adds unit tests for the new endpoints.

Reviewed changes

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

Show a summary per file
File Description
cms/envs/devstack.py Adds devstack defaults for the xpert-ai-workflows URL and shared secret.
cms/envs/common.py Defines documented CMS settings for workflow URL/API key/timeout.
cms/djangoapps/contentstore/toggles.py Adds the new course-level waffle flag and helper function.
cms/djangoapps/contentstore/rest_api/v1/views/tests/test_course_waffle_flags.py Updates expected waffle-flag response to include the new flag.
cms/djangoapps/contentstore/rest_api/v1/serializers/course_waffle_flags.py Exposes the new flag via the serializer method field.
cms/djangoapps/contentstore/rest_api/v0/views/course_optimizer.py Implements the two new proxy endpoints for extended report run + status.
cms/djangoapps/contentstore/rest_api/v0/views/__init__.py Re-exports new views for v0 API.
cms/djangoapps/contentstore/rest_api/v0/urls.py Registers the new v0 routes.
cms/djangoapps/contentstore/rest_api/v0/tests/test_course_optimizer.py Adds unit tests covering authz and proxy behavior for the new endpoints.
Suppressed comments (2)

cms/djangoapps/contentstore/rest_api/v0/views/course_optimizer.py:540

  • _COURSE_ANALYSIS_REPORT_REQUEST_TIMEOUT_SECONDS is not defined here, so this endpoint will raise a NameError before making the request. Use the existing COURSE_ANALYSIS_WORKFLOW_REQUEST_TIMEOUT_SECONDS setting (as the POST endpoint does) or define the constant.
            response = requests.get(
                f'{settings.COURSE_ANALYSIS_WORKFLOW_URL}/courses/{course_id}/runs/latest',
                headers={'X-Api-Key': settings.COURSE_ANALYSIS_WORKFLOW_API_KEY},
                timeout=_COURSE_ANALYSIS_REPORT_REQUEST_TIMEOUT_SECONDS,
            )

cms/djangoapps/contentstore/rest_api/v0/views/course_optimizer.py:536

  • Same as the POST endpoint: the status proxy should also be gated by contentstore.enable_course_optimizer_extended_report so the extended-report feature is truly off by default at the API layer.
        course_key = CourseKey.from_string(course_id)
        if not has_course_author_access(request.user, course_key):
            self.permission_denied(request)

        try:
            response = requests.get(

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

Comment on lines +515 to +516
)
def get(self, request: Request, course_id: str):
Comment on lines +471 to +476
course_key = CourseKey.from_string(course_id)
if not has_course_author_access(request.user, course_key):
self.permission_denied(request)

course_block = modulestore().get_course(course_key)
tarball = create_export_tarball(course_block, course_key, {})
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