Skip to content

feat: add instructor dashboard overrides to replicate canvas and rapid-response-report plugins behaviour in MFE#58

Merged
Anas12091101 merged 16 commits into
mainfrom
anas/add-instructor-dashboard-overrides
Jul 1, 2026
Merged

feat: add instructor dashboard overrides to replicate canvas and rapid-response-report plugins behaviour in MFE#58
Anas12091101 merged 16 commits into
mainfrom
anas/add-instructor-dashboard-overrides

Conversation

@Anas12091101

@Anas12091101 Anas12091101 commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds Canvas Integration and Rapid Response Reports pages to the instructor dashboard MFE across the three MIT ODL deployments (mitxonline, mitx, xpro), replicating the behaviour of the legacy ol_openedx_canvas_integration and ol_openedx_rapid_response_reports plugins under the OEP-65 frontend-base shell.

What's included

A shared instructor-dashboard/ module under frontend/shared/src/:

  • CanvasIntegrationPage.tsx — Canvas LMS enrollment sync, assignment grade export/listing, and result rendering
  • CanvasPendingTasks.tsx — polls the list_canvas_tasks endpoint and renders a "Pending Tasks" table (mirroring the dashboard's Data Downloads pending-tasks UX), auto-stopping polling on terminal task states
  • RapidResponseReportsPage.tsx — grouped rapid-response run report download links
  • index.tsx — the createInstructorDashboardCustomApp() factory

Tab gating happens in the backend, not the MFE

The factory registers the two pages as route-only widgets (2 slot operations on the instructorDashboard.routes slot). The nav tabs are injected by the LMS via the InstructorDashboardTabsRequested filter in the plugins, so a tab surfaces only where its backend actually applies — matching the legacy server-side gating:

  • Canvas → only for Canvas-linked courses (canvas_id set)
  • Rapid Responses → only on deployments where the plugin is installed

This avoids showing tabs whose backend APIs aren't present. (An earlier iteration registered the tabs in the MFE directly; that was replaced by the backend filter.)

Site config integration

createInstructorDashboardCustomApp() is added to all 6 site configs (build + dev for mitxonline / mitx / xpro), after the base instructorDashboardApp.

Plugin version pins

The pip package lists pin the plugin versions that provide the backend tab filters and endpoints:

  • ol-openedx-canvas-integration==0.8.0
  • ol-openedx-rapid-response-reports==0.5.0

Other changes

  • frontend/README.md — documents deployment prerequisites (the pinned plugin versions; FRONTEND_SITE_CONFIG + ENABLE_MFE_CONFIG_API provisioned via ol-infrastructure).
  • Per-site-project .gitignore — ignores the generated shared/ snapshot.
  • Removed the unused styles/styleLoader.tsx helper.

Backend dependency

Requires the matching open-edx-plugins changes — the Canvas / Rapid Response tab filters and the list_canvas_tasks / rapid_response_runs endpoints (open-edx-plugins PR #819). No edx-platform cherry-pick is needed for the MFE.

Not included

Header/footer overrides were split out into a separate PR (#73).

Related issue: https://github.com/mitodl/hq/issues/11581

Screenshots

Screen.Recording.2026-06-19.at.8.41.00.PM.mov

How can this be tested?

  • The following config should be present in your LMS (./manage.py lms shell):
In [1]: settings.ENABLE_MFE_CONFIG_API
Out[1]: True

In [2]: settings.FRONTEND_SITE_CONFIG
Out[2]:
{'siteId': 'mitol',
 'siteName': 'MIT Learn (dev)',
 'baseUrl': 'http://apps.local.openedx.io:8080',
 'lmsBaseUrl': 'http://local.openedx.io:8000',
 'loginUrl': 'http://local.openedx.io:8000/login',
 'logoutUrl': 'http://local.openedx.io:8000/logout',
 'headerLogoImageUrl': 'http://local.openedx.io:8000/static/mitxonline-theme/images/logo.svg',
 'commonAppConfig': {'mitolHeader': {'mitLearnBaseUrl': 'https://learn.mit.edu',
   'marketingSiteBaseUrl': 'http://local.openedx.io:8000'},
  'mitolFooter': {'copyrightText': '© 2026 Massachusetts Institute of Technology',
   'aboutUrl': 'https://learn.mit.edu/about',
   'termsOfServiceUrl': 'http://local.openedx.io:8000/tos',
   'privacyPolicyUrl': 'http://local.openedx.io:8000/privacy',
   'accessibilityUrl': 'https://accessibility.mit.edu/',
   'supportUrl': 'https://mitxonline.zendesk.com/hc/en-us',
   'honorCodeUrl': 'http://local.openedx.io:8000/honor',
   'footerLogoUrl': 'http://local.openedx.io:8000/static/mitxonline-theme/images/mit-logo.svg',
   'footerLogoDestination': 'https://openlearning.mit.edu'}}}
  • Because the MFE routes mount under /apps, the LMS must build the tab links with the same prefix — set INSTRUCTOR_MICROFRONTEND_URL to a value ending in /apps/instructor-dashboard (prod sets this in ol-infrastructure).
  • Verify the runtime-config endpoint returns config JSON (not 404):
    curl -s -w "\nHTTP %{http_code}\n" http://local.openedx.io:8000/api/frontend_site_config/v1/
  • Install & run the Site Project:
    cd deployments/mit-ol/mfe_slot_config/frontend/mitxonline
    cp -R ../shared ./shared
    npm install      # pulls @openedx/frontend-app-instructor-dashboard etc. from npm
    npm run dev      # openedx dev → webpack-dev-server on :8080
  • Open the instructor dashboard: http://apps.local.openedx.io:8080/apps/instructor-dashboard/<course-key>/course_info and verify it loads.
  • Install and set up the Canvas (ol-openedx-canvas-integration) and Rapid Response Reports (ol-openedx-rapid-response-reports) plugins.
  • Verify the Canvas tab appears only for a Canvas-linked course (canvas_id set), and the Rapid Responses tab appears when its plugin is installed.
  • Verify both plugins' functionality works (enrollment sync, grade export, pending-task polling; rapid-response run downloads).

Copilot AI review requested due to automatic review settings June 11, 2026 15:55
@Anas12091101
Anas12091101 marked this pull request as draft June 11, 2026 15:56

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request adds custom Canvas Integration and Rapid Response Reports tabs to the OpenEdX Instructor Dashboard across the mitx, mitxonline, and xpro sites. Feedback on the implementation focuses on improving robustness and consistency: in CanvasIntegrationPage.tsx, safely handling potential null or non-object values in renderTable, memoizing columns to prevent unnecessary re-renders, and URL-encoding the assignment ID parameter; in RapidResponseReportsPage.tsx, using optional chaining to safely access response data and specifying a fixed timezone (such as UTC) to ensure consistent date grouping for all users.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +89 to +108
const renderTable = (data: any) => {
if (!data || (Array.isArray(data) && data.length === 0)) {
return <p>No results found.</p>;
}
const items = Array.isArray(data) ? data : [data];
if (items.length === 0) return <p>No results found.</p>;
const columns = Object.keys(items[0]).map(col => ({
Header: col,
accessor: col,
}));
return (
<DataTable
itemCount={items.length}
data={items}
columns={columns}
>
<DataTable.Table />
</DataTable>
);
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

There are two issues here:

  1. Correctness/Safety: If items[0] is null, undefined, or not an object (e.g., a string), Object.keys(items[0]) will throw a TypeError or return unexpected numeric keys. We should safely check that items[0] is a non-null object before extracting keys.
  2. Performance: DataTable (built on react-table) uses reference equality for columns. Recreating columns on every render can cause performance issues or infinite render loops. It is highly recommended to lift columns and data to the component level and memoize them using useMemo.

Here is a safer implementation of renderTable that guards against non-object items. Consider also refactoring this to use useMemo at the component level for columns and data.

  const renderTable = (data: any) => {
    if (!data || (Array.isArray(data) && data.length === 0)) {
      return <p>No results found.</p>;
    }
    const items = Array.isArray(data) ? data : [data];
    if (items.length === 0 || !items[0] || typeof items[0] !== 'object') {
      return <p>No results found.</p>;
    }
    const columns = Object.keys(items[0]).map(col => ({
      Header: col,
      accessor: col,
    }));
    return (
      <DataTable
        itemCount={items.length}
        data={items}
        columns={columns}
      >
        <DataTable.Table />
      </DataTable>
    );
  };

try {
const client = getAuthenticatedHttpClient();
const response = await client.get(`${baseUrl}/rapid_response_runs`);
const runs: ProblemRun[] = Array.isArray(response.data) ? response.data : response.data.problem_runs || [];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

If response.data is null or undefined (e.g., if the API returns an empty response or a 204 No Content), evaluating response.data.problem_runs will throw a TypeError: Cannot read properties of null. Use optional chaining to safely access problem_runs.

Suggested change
const runs: ProblemRun[] = Array.isArray(response.data) ? response.data : response.data.problem_runs || [];
const runs: ProblemRun[] = Array.isArray(response.data) ? response.data : response.data?.problem_runs || [];

setError('Please select an assignment first.');
return;
}
const data = await makeRequest(`list_canvas_grades?assignment_id=${selectedAssignment}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To prevent potential issues with special characters or spaces in assignment IDs, it is safer to URL-encode the selectedAssignment parameter using encodeURIComponent.

Suggested change
const data = await makeRequest(`list_canvas_grades?assignment_id=${selectedAssignment}`);
const data = await makeRequest('list_canvas_grades?assignment_id=' + encodeURIComponent(selectedAssignment));

Comment on lines +39 to +43
const date = new Date(run.created).toLocaleDateString('en-US', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

toLocaleDateString is timezone-dependent and uses the user's local browser timezone by default. This can cause runs created at the same UTC time to be grouped under different dates for different instructors depending on their local timezone. To ensure consistent grouping across all users, specify a fixed timezone (such as 'UTC').

          const date = new Date(run.created).toLocaleDateString('en-US', {
            year: 'numeric',
            month: '2-digit',
            day: '2-digit',
            timeZone: 'UTC',
          });

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 adds two custom instructor dashboard tabs — Canvas Integration (enrollment sync and grade export) and Rapid Response Reports (download links for rapid response runs) — to the MFE-based instructor dashboard across all three MIT ODL deployments (mitxonline, mitx, xpro). It replicates behavior previously provided by Django-template-based plugins (ol_openedx_canvas_integration and ol_openedx_rapid_response_reports) in the new frontend-base slot architecture.

Changes:

  • Adds a new instructor-dashboard/ shared module with CanvasIntegrationPage, RapidResponseReportsPage, and a createInstructorDashboardCustomApp() factory that registers tab navigation entries and routed page content via WidgetOperationTypes.APPEND slot operations.
  • Wires the custom app into all 6 site config files (build + dev for each deployment) after the base instructorDashboardApp.

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
frontend/shared/src/instructor-dashboard/index.tsx App factory registering 4 slot operations (2 tab entries + 2 route pages) using a PlaceholderSlot pattern
frontend/shared/src/instructor-dashboard/CanvasIntegrationPage.tsx Canvas enrollment list/sync and grade export UI with DataTable display
frontend/shared/src/instructor-dashboard/RapidResponseReportsPage.tsx Rapid response runs fetched on mount, grouped by date, with download links
frontend/mitxonline/site.config.build.tsx Adds createInstructorDashboardCustomApp() import and registration
frontend/mitxonline/site.config.dev.tsx Same for dev config
frontend/mitx/site.config.build.tsx Same for MITx build config
frontend/mitx/site.config.dev.tsx Same for MITx dev config
frontend/xpro/site.config.build.tsx Same for xPro build config
frontend/xpro/site.config.dev.tsx Same for xPro dev config

Comment on lines +131 to +133
<Button variant="outline-primary" onClick={handleOverloadEnrollments} disabled={loading}>
Overload Enrollment List Using Canvas
</Button>
@Anas12091101
Anas12091101 force-pushed the anas/add-instructor-dashboard-overrides branch from be40e31 to 0be1299 Compare June 17, 2026 07:31
@gitguardian

gitguardian Bot commented Jun 19, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 2 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secrets in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
5823683 Triggered Zapier Webhook URL 6a7c467 deployments/mit-ol/settings/cms/models/aqueduct.py View secret
33931195 Triggered Generic Password 6a7c467 local-dev/manifests/infra/mariadb.yaml View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@Anas12091101
Anas12091101 marked this pull request as ready for review June 19, 2026 14:19
Anas12091101 and others added 3 commits June 19, 2026 20:33
…ared snapshot

Commit 6a7c467 ("adjust after rebase") accidentally committed the local-dev
snapshot copy (frontend/mitxonline/shared) and dropped the canonical
frontend/shared. That broke the @shared/* tsconfig alias used by all site
projects and the Dagger --shared-src mount (mitx/xpro builds), and made the PR
show shared/src/footer/index.tsx as deleted.

Relocate the snapshot content back to the canonical frontend/shared and add an
anchored /shared/ entry to each site project's .gitignore so the local-dev
copy (cp -R ../shared ./shared for `npm run dev`) can't be committed again.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Anas12091101 Anas12091101 added the Needs Review An open Pull Request that is ready for review label Jun 22, 2026
@Anas12091101
Anas12091101 requested a review from asadali145 June 22, 2026 09:09
@asadali145
asadali145 requested a review from Copilot June 22, 2026 10:12
@asadali145 asadali145 self-assigned this Jun 22, 2026
Per review, the MIT OL header/footer customizations (shared/src/footer,
shared/src/header, shared/src/styles/mitxonline.scss) are split into their own
branch/PR (anas/mfe-header-footer-overrides). This branch keeps only the
instructor-dashboard MFE work; the site configs still wire createMITOLFooterApp
and createMITxOnlineHeaderApp from main's (now-unmodified) shared components.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

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

Copilot reviewed 25 out of 25 changed files in this pull request and generated 3 comments.

Comment thread deployments/mit-ol/pip_package_lists/master/mitxonline.txt
…ashboard-overrides

# Conflicts:
#	deployments/mit-ol/pip_package_lists/master/mitx-staging.txt
#	deployments/mit-ol/pip_package_lists/ulmo/mitx-staging.txt
#	deployments/mit-ol/pip_package_lists/ulmo/mitx.txt
#	deployments/mit-ol/pip_package_lists/verawood/mitx-staging.txt
#	deployments/mit-ol/pip_package_lists/verawood/mitx.txt

@asadali145 asadali145 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just a question after code review. I'll test it after that.

// ---------------------------------------------------------------------------

const SLOT = {
tabs: "org.openedx.frontend.slot.instructorDashboard.tabs.v1",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is unused, it can be a single const ROUTES_SLOT_ID="org.openedx.frontend.slot.instructorDashboard.routes.v1".

@asadali145

Copy link
Copy Markdown
Contributor

@Anas12091101 are these any docs to run this MFE apart from the testing instructions? Also, what if we want to do some development inside the instructor dashboard? How are we supposed to run the MFE in that case? Can the new slot overrides be used in dev mode?

@asadali145 asadali145 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM! There are some conflicts and I have a question on docs and dev mode.

Comment on lines +25 to +29
// commonAppConfig (mitolHeader / mitolFooter) is loaded at runtime from the LMS
// frontend_site_config API rather than hardcoded here. The dev server proxies
// /api/frontend_site_config/v1 to lmsBaseUrl; the response is deep-merged over
// this static config. Requires ENABLE_MFE_CONFIG_API + FRONTEND_SITE_CONFIG set
// on the LMS.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why this comment?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I added it for anyone who might wonder why runtimeConfigJsonUrl is defined there. Since it's not immediately obvious, I thought a short comment would provide the context and save future readers from having to dig through the implementation.

@Anas12091101

Copy link
Copy Markdown
Contributor Author

@Anas12091101 are these any docs to run this MFE apart from the testing instructions?

There are some docs under the mitxonline folder:
https://github.com/mitodl/lehrer/blob/main/deployments/mit-ol/mfe_slot_config/frontend/mitxonline/README.md#local-development-outside-dagger

There's also a shared README, but it doesn't currently include instructions for running frontend-base. We can add those there.

Also, what if we want to do some development inside the instructor dashboard? How are we supposed to run the MFE in that case? Can the new slot overrides be used in dev mode?

When I started working on this, I ran the Instructor Dashboard MFE directly, just like any other MFE, and it worked fine. We can continue doing local development that way. For slot overrides, we can use the existing site.config.dev.tsx file to override the slots during development.

…ashboard-overrides

# Conflicts:
#	deployments/mit-ol/pip_package_lists/master/mitx-staging.txt
#	deployments/mit-ol/pip_package_lists/master/mitx.txt
#	deployments/mit-ol/pip_package_lists/master/mitxonline.txt
#	deployments/mit-ol/pip_package_lists/verawood/mitx-staging.txt
#	deployments/mit-ol/pip_package_lists/verawood/mitx.txt
@asadali145

Copy link
Copy Markdown
Contributor

There's also a shared README, but it doesn't currently include instructions for running frontend-base. We can add those there.
When I started working on this, I ran the Instructor Dashboard MFE directly, just like any other MFE, and it worked fine. We can continue doing local development that way. For slot overrides, we can use the existing site.config.dev.tsx file to override the slots during development.

Can we document both of these? Maybe in a new PR?

@asadali145 asadali145 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@Anas12091101
Anas12091101 merged commit 586e539 into main Jul 1, 2026
4 checks passed
@Anas12091101 Anas12091101 added waiting on author and removed Needs Review An open Pull Request that is ready for review labels Jul 1, 2026
@blarghmatey
blarghmatey deleted the anas/add-instructor-dashboard-overrides branch July 16, 2026 18:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants