feat: add instructor dashboard MFE support for canvas and rapid response reports plugins#819
Conversation
…nse reports plugins
a0fc075 to
340c7ec
Compare
The frontend-base site projects mount the instructor dashboard routes under /apps (wrapWithAppsPath), so the LMS-provided tab hrefs must be /apps/instructor-dashboard/<course>/<tab> to resolve. Update both tab filters and their tests accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The view lazily imports get_run_data_for_course from rapid_response_xblock, which is a separate, undeclared plugin. CI installs plugins alphabetically and cumulatively, so ol_openedx_rapid_response_reports tests run before rapid_response_xblock is installed, making @patch("rapid_response_xblock.utils...") fail to import its target. Inject a stub module via sys.modules so the tests are deterministic regardless of whether the xblock is installed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
280e680 to
79a696a
Compare
There was a problem hiding this comment.
Pull request overview
This PR adds frontend-base instructor dashboard MFE support for the Canvas integration and Rapid Response Reports plugins by registering instructor-dashboard tabs via the InstructorDashboardTabsRequested Open edX Filter and exposing plugin data via lightweight JSON endpoints (while keeping legacy dashboard integration intact).
Changes:
- Added Open edX Filters pipeline steps to register MFE instructor dashboard tabs for Canvas (course-gated by
canvas_id) and Rapid Responses (deployment-gated by plugin install). - Added JSON API endpoints consumed by the MFE (
list_canvas_tasks,rapid_response_runs) plus associated tests. - Updated plugin docs/changelogs and bumped plugin versions.
Reviewed changes
Copilot reviewed 18 out of 19 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/ol_openedx_rapid_response_reports/tests/test_pipeline.py | New tests for Rapid Responses tab registration pipeline step. |
| src/ol_openedx_rapid_response_reports/tests/test_api.py | New tests for the Rapid Responses JSON endpoint behavior and permissions. |
| src/ol_openedx_rapid_response_reports/tests/init.py | Test package init (added/updated). |
| src/ol_openedx_rapid_response_reports/pyproject.toml | Version bump and pytest configuration for plugin tests. |
| src/ol_openedx_rapid_response_reports/ol_openedx_rapid_response_reports/urls.py | Exposes rapid_response_runs endpoint under the plugin URL mount. |
| src/ol_openedx_rapid_response_reports/ol_openedx_rapid_response_reports/settings/common.py | Registers the instructor-dashboard tabs filter pipeline step in OPEN_EDX_FILTERS_CONFIG. |
| src/ol_openedx_rapid_response_reports/ol_openedx_rapid_response_reports/pipeline.py | Adds the Rapid Responses instructor dashboard tab pipeline step. |
| src/ol_openedx_rapid_response_reports/ol_openedx_rapid_response_reports/app.py | Wires COMMON settings so filter registration happens when installed. |
| src/ol_openedx_rapid_response_reports/ol_openedx_rapid_response_reports/api.py | Adds list_rapid_response_runs JSON endpoint. |
| src/ol_openedx_rapid_response_reports/CHANGELOG.rst | Documents the new MFE support and endpoint in the release notes. |
| src/ol_openedx_canvas_integration/tests/test_views.py | New tests for the Canvas list_canvas_tasks JSON endpoint. |
| src/ol_openedx_canvas_integration/tests/test_pipeline.py | New tests for Canvas tab registration pipeline step, including failure swallowing. |
| src/ol_openedx_canvas_integration/README.rst | Clarifies cherry-pick requirement applies only to the legacy instructor dashboard. |
| src/ol_openedx_canvas_integration/pyproject.toml | Version bump for the Canvas plugin. |
| src/ol_openedx_canvas_integration/ol_openedx_canvas_integration/views.py | Adds list_canvas_tasks endpoint for MFE task-status polling. |
| src/ol_openedx_canvas_integration/ol_openedx_canvas_integration/urls.py | Routes list_canvas_tasks. |
| src/ol_openedx_canvas_integration/ol_openedx_canvas_integration/settings/lms/common.py | Registers the Canvas tab pipeline step in OPEN_EDX_FILTERS_CONFIG. |
| src/ol_openedx_canvas_integration/ol_openedx_canvas_integration/pipeline.py | Adds the Canvas instructor dashboard tab pipeline step (course-gated). |
| src/ol_openedx_canvas_integration/CHANGELOG.rst | Documents the new MFE support and endpoint in the release notes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import logging | ||
|
|
||
| from django.utils.translation import gettext as _ | ||
| from openedx_filters import PipelineStep | ||
|
|
||
| log = logging.getLogger(__name__) |
| for task in tasks_qs: | ||
| feature = extract_task_features(task) | ||
| # Override the message for Canvas task types with the plugin's formatter | ||
| # so the result reads e.g. "N grades and M assignments updated or created". | ||
| if task.task_type in CANVAS_TASK_TYPES and task.task_output: | ||
| try: |
There was a problem hiding this comment.
The endpoint does filter to Canvas task types. It calls get_filtered_instructor_tasks (task_helpers.py), whose history query is constrained by task_type__in=CANVAS_TASK_TYPES; the Canvas-specific message override is also gated by CANVAS_TASK_TYPES. The only un-typed rows come from the union with the platforms get_running_instructor_tasks, which is intentional — it mirrors the legacy Canvas "Pending Tasks" view that surfaces any currently-running task. This helper is pre-existing (added in #387) and reused as-is here. If we want strictly Canvas-typed rows even for the running set, I can add a final task_type in CANVAS_TASK_TYPES` filter — happy to do that if you prefer.
asadali145
left a comment
There was a problem hiding this comment.
This is just the code review. Overall looks good. I just have a few minor suggestions.
| "Failed to evaluate Canvas instructor tab for course %s", course_key | ||
| ) | ||
|
|
||
| return {"tabs": tabs} |
There was a problem hiding this comment.
We should return all args received in run_filter as a dict at the end, so that if there is any other pipeline step, it gets all the args.
| """ | ||
|
|
||
| def run_filter(self, tabs, course_key, **kwargs): # noqa: ARG002 | ||
| try: |
There was a problem hiding this comment.
Can we remove this broad try-except to be specific if we need it?
| "tab_id": CANVAS_TAB_ID, | ||
| "title": _("Canvas"), | ||
| "url": ( | ||
| f"/apps/instructor-dashboard/{course_key}/{CANVAS_TAB_ID}" |
There was a problem hiding this comment.
Can we use settings INSTRUCTOR_MICROFRONTEND_URL?
| "url": ( | ||
| f"/apps/instructor-dashboard/{course_key}/{CANVAS_TAB_ID}" | ||
| ), | ||
| "sort_order": CANVAS_TAB_SORT_ORDER, |
There was a problem hiding this comment.
If the idea is to display this at the end of the list, maybe we should generate this using existing sort_orders?
| CANVAS_TAB_ID = "canvas_integration" | ||
| CANVAS_TAB_SORT_ORDER = 110 |
There was a problem hiding this comment.
Can we move these to constants?
| course_id, | ||
| ): | ||
| """Return JSON list of rapid response runs for the given course.""" | ||
| from rapid_response_xblock.utils import get_run_data_for_course # noqa: PLC0415 |
There was a problem hiding this comment.
This is a general comment. I don't see rapid_response_xblock as a dependency in pyproject.toml.
| RAPID_RESPONSE_TAB_ID = "rapid_response" | ||
| RAPID_RESPONSE_TAB_SORT_ORDER = 120 |
There was a problem hiding this comment.
Please move these to constants.
| tabs.append( | ||
| { | ||
| "tab_id": RAPID_RESPONSE_TAB_ID, | ||
| "title": _("Rapid Responses"), |
There was a problem hiding this comment.
Same, move this to constants.
| "tab_id": RAPID_RESPONSE_TAB_ID, | ||
| "title": _("Rapid Responses"), | ||
| "url": ( | ||
| f"/apps/instructor-dashboard/{course_key}/{RAPID_RESPONSE_TAB_ID}" |
| @@ -0,0 +1,103 @@ | |||
| """Tests for the rapid response reports MFE endpoint.""" | |||
There was a problem hiding this comment.
| """Tests for the rapid response reports MFE endpoint.""" | |
| """Tests for the rapid response reports API""" |
| # so the result reads e.g. "N grades and M assignments updated or created". | ||
| if task.task_type in CANVAS_TASK_TYPES and task.task_output: | ||
| try: | ||
| feature["task_message"] = get_task_output_formatted_message( |
There was a problem hiding this comment.
Bug: The list_canvas_tasks API endpoint incorrectly includes all running instructor tasks for a course, leaking task information between instructors.
Severity: MEDIUM
Suggested Fix
Modify get_running_instructor_tasks or its usage within get_filtered_instructor_tasks to filter the running tasks by the current user (request.user). This will ensure the API only returns tasks initiated by the user making the request.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: src/ol_openedx_canvas_integration/ol_openedx_canvas_integration/views.py#L213
Potential issue: The `list_canvas_tasks` API endpoint leaks information about tasks run
by other instructors. The view calls `get_filtered_instructor_tasks`, which combines two
querysets. One is correctly filtered by the current user. The other, from
`get_running_instructor_tasks`, includes all running instructor tasks for the course,
regardless of which user initiated them. As a result, an instructor calling this API may
see details of tasks run by other instructors in the same course, including tasks
unrelated to Canvas.
| full page reload. | ||
| """ | ||
|
|
||
| def run_filter(self, tabs, course_key, **kwargs): |
There was a problem hiding this comment.
Can we use the original signature here? I am not sure how it is working as the 2nd arg is user in the original definition.
| def run_filter(self, tabs, course_key, **kwargs): | |
| def run_filter(self, tabs, user, course_key): |
| ) | ||
|
|
||
| # Return every argument so any subsequent pipeline step gets the full set. | ||
| return {"tabs": tabs, "course_key": course_key, **kwargs} |
There was a problem hiding this comment.
This will change with the definition.
| (``createInstructorDashboardCustomApp`` in the shared MFE module). | ||
| """ | ||
|
|
||
| def run_filter(self, tabs, course_key, **kwargs): |
There was a problem hiding this comment.
Same comments as in canvas plugin.
…oard-mfe-apis # Conflicts: # uv.lock
What are the relevant tickets?
https://github.com/mitodl/hq/issues/11581
Description (What does it do?)
Adds instructor-dashboard MFE (OEP-65 / frontend-base) support to the
ol_openedx_canvas_integrationandol_openedx_rapid_response_reportsplugins.Historically these plugins integrated with the legacy (Django-rendered)
instructor dashboard via
PluginContexts(context_api.plugin_context), whichinjected tab sections and their data server-side. The new frontend-base
instructor dashboard MFE has no such context, so this PR makes each plugin
expose its tab and data through the supported extension points the MFE consumes:
InstructorDashboardTabsRequestedOpen edX Filter(
org.openedx.learning.instructor.dashboard.tabs.requested.v1).The result is self-contained in the plugins — no edx-platform cherry-pick is
required for the MFE (the cherry-pick remains only for the legacy dashboard).
How tab visibility works
canvas_idset in Advanced Settings (per-course)Testing
OPEN_EDX_FILTERS_CONFIGcontains both pipeline steps underorg.openedx.learning.instructor.dashboard.tabs.requested.v1.{"canvas_id": <id>}in a course's Advanced Settings → theCanvas tab appears for that course only.
GET .../canvas/api/list_canvas_tasksreturns
{"tasks": [...]}.GET .../instructor/api/rapid_response_runsreturns the run list.canvas_id;either tab on a deployment without the plugin).
Backward compatibility
PluginContextsintegration remains).
OPEN_EDX_FILTERS_CONFIG.Screenshots (if appropriate):
Screen.Recording.2026-06-19.at.8.41.00.PM.mov