Skip to content

feat(settings): add course notification preferences to dashboard settings - #3960

Open
AhtishamShahid wants to merge 2 commits into
mainfrom
feat/learn-notification-preferences
Open

AhtishamShahid wants to merge 2 commits into
mainfrom
feat/learn-notification-preferences

Conversation

@AhtishamShahid

@AhtishamShahid AhtishamShahid commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

What are the relevant tickets?

Relates to mitodl/hq#12930 — the Open edX notifications gear links to a settings anchor that had no notification settings behind it.

Depends on mitodl/mitxonline#3897, which adds the API this one calls and carries the Fixes for that issue. Merge it first; without it every request here 404s.

Description (What does it do?)

Learners could only manage MITx Online course notifications on MITx Online's own account page. This adds a Notifications section to /dashboard/settings, so they can do it where they already are.

This PR is frontend only. Open edX owns the preference data and MITx Online proxies it, exactly as it does for enrollments and certificates; the section reads and writes through the existing /mitxonline/* gateway route. Learn stores nothing.

Each row toggles on-site and email delivery for one notification type, with an email cadence select that stays visible but disabled while email is off. Channels the LMS reports as non-editable render disabled. When preferences are unavailable — no courseware account yet, the LMS has the feature off, a throttle, or a failed read — the section shows a notice in place of the controls, so the #notifications anchor still resolves.

SimpleSelect in ol-components gains disabled and inputProps in its prop type. Both were already forwarded to the underlying MUI Select at runtime; only the Pick omitted them.

How can this be tested?

  1. Sign in on MIT Learn and go to /dashboard/settings.
  2. Confirm the Notifications section lists your course notification types grouped by app (Discussions, Grading, Updates), each with On site and Email checkboxes.
  3. Toggle Email on a row: the cadence select next to it becomes enabled. Change it, reload, and confirm the new cadence persisted.
  4. Toggle Email back off: the select stays in place, disabled, still showing the stored cadence.
  5. With an account that has no linked Open edX user, confirm the section reads "Your course account is still being set up. Please check back shortly." rather than showing empty controls.
  6. With a screen reader, confirm the cadence select announces "Email frequency for <row>".

Validation run on this branch: 17 unit tests for the section, 836 across the DashboardPage, api and SimpleSelect suites, plus typecheck and lint-check — all passing. Manually exercised against a local stack (Keycloak + APISIX + Tutor Open edX) with the MITx Online branch from #3897 applied. Full-suite CI has not been run yet, and nothing has been tested against a deployed environment, since the API is not deployed anywhere.

Additional Context

Review feedback from the first pass is addressed in 0df9b61d: an accessible name on the cadence select, a row's controls disabled while its own write is in flight (a fast double-click previously re-sent the same value), data-testid keyed by app as well as type so a type appearing under two apps cannot collide, and a distinct message for a throttled read.

Not covered here: Learn has no notification state of its own, so the section is empty for anyone without a MITx Online courseware account. It says so rather than hiding itself, matching MITx Online's behaviour for the same learner.

A direct Learn → Open edX version was prototyped and dropped. It works only when the learner already holds an LMS session in that browser — signing in to Learn does not create one — so it would have needed either a redirect out to the LMS or a Learn-side credential store. Worth revisiting if Learn takes on more learner-level Open edX features.

…ings

Learners could manage MITx Online course notifications only on MITx
Online's own account settings page. This brings that section to MIT
Learn's dashboard settings page, reading and writing the same Open edX
state through the MITx Online API that the gateway already proxies.

Each row toggles on-site and email delivery for one notification type,
with an email cadence select that stays visible but disabled while
email delivery is off. Channels the LMS marks non-editable render
disabled, and the section renders a notice in place of the controls
when preferences are unavailable, keeping the #notifications anchor
resolvable either way.
@AhtishamShahid
AhtishamShahid requested a review from a team as a code owner September 18, 2026 03:14
Copilot AI balanced review requested due to automatic review settings September 18, 2026 03:14
@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown

OpenAPI Changes

1 changes: 0 error, 1 warning, 0 info

View full changelog

Unexpected changes? Ensure your branch is up-to-date with main (consider rebasing).

Copilot AI 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.

🟡 Changes recommended

Rapid checkbox interactions can lose user intent, and cadence selects lack accessible names.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds Open edX course notification preferences to MIT Learn’s dashboard settings.

Changes:

  • Adds notification delivery and cadence controls with unavailable-state notices.
  • Adds MITx Online query and mutation hooks.
  • Extends SimpleSelect disabled-state typing and adds tests.
File summaries
File Description
frontends/ol-components/src/components/SimpleSelect/SimpleSelect.tsx Exposes the existing disabled prop.
frontends/main/src/app-pages/DashboardPage/SettingsContent.tsx Adds notification settings to the dashboard.
frontends/main/src/app-pages/DashboardPage/SettingsContent.test.tsx Mocks unavailable notification preferences.
frontends/main/src/app-pages/DashboardPage/NotificationPreferences.tsx Implements notification controls and notices.
frontends/main/src/app-pages/DashboardPage/NotificationPreferences.test.tsx Tests rendering, updates, and fallback states.
frontends/api/src/mitxonline/test-utils/urls.ts Adds notification endpoint test URLs.
frontends/api/src/mitxonline/hooks/notificationPreferences/queries.ts Defines preference types and query configuration.
frontends/api/src/mitxonline/hooks/notificationPreferences/index.ts Adds query and update hooks.
Review details
  • Files reviewed: 8/8 changed files
  • Comments generated: 2
  • Review effort level: Balanced

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

Comment on lines +230 to +235
<SimpleSelect
size="small"
name={`cadence-${notificationApp}-${notificationType}`}
options={CADENCE_OPTIONS}
value={config.email_cadence}
disabled={!config.email || emailLocked}

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.

done in 0df9b61 - added aria-label via inputProps, had to expose inputProps on SimpleSelect in ol-components first.

config={types[type]}
nonEditable={lockedChannelsFor(group, type)}
showEmail={showEmail}
onChange={(update) => updatePreference.mutate(update)}

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.

done in 0df9b61 - a row's controls are disabled while that row's own write is in flight.

const emailLocked = nonEditable.includes("email")

return (
<Row data-testid={`notification-row-${notificationType}`}>

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.

Minor point, but data-testid={notification-row-${notificationType}} is keyed only by notificationType, not by app, so if a type name like grouped_notification repeats across multiple notification apps (which can happen for edX's per-app digest type), two rows get the same test id. This wouldn't be an issue in prod, but getByTestId/findByTestId in future tests will throw on "multiple elements found." Might be worth writing it as notification-row-${notificationApp}-${notificationType}, which would also mean updating rowFor in NotificationPreferences.test.tsx:66 to take the app too, plus the 10 places it's called.

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.

good catch, fixed in 0df9b61 - id is notification-row-${app}-${type} now, rowFor takes the app too.

const status = (error as AxiosError)?.response?.status
return status === 409
? "Your course account is still being set up. Please check back shortly."
: "We could not load your notification settings. Please try again later."

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.

Also minor: a 429 on this GET falls through to the generic "could not load" message. You could add a status === 429 branch here with its own message — the code that sends the PUT already does this for its own errors, so you'd just be matching that.

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.

done in 0df9b61 - 429 has its own message on the read now, same as the PUT already did.

@alexfigtree alexfigtree 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.

Quick flag before this goes further — this depends on a backend endpoint (GET/PUT /api/notification-preferences/ on MITx Online) that doesn't exist on mitxonline's main yet. The implementation lived in mitodl/mitxonline#3897, which was closed today "in favour of" this PR, but its backend files (openedx/api.py, urls.py, views.py, etc.) don't seem to have landed anywhere else. As merged, every GET here would 404.

Also, the description's backend verification claims ("verified end-to-end against a local LMS," "199 backend / 29 frontend tests pass") look copied from #3897 — worth clarifying they don't apply to this diff, which is frontend-only.

Is a follow-up mitxonline PR on the way, or should #3897 be reopened instead of closed?

Four findings from review:

- The cadence select announced no accessible name: `name` does not label
  MUI's combobox, and the row group's label does not reach each control.
  It now carries "Email frequency for <row>", which needed `inputProps`
  on ol-components' SimpleSelect (already forwarded at runtime, only the
  prop type omitted it).
- Clicking a checkbox twice quickly sent the same inverted value twice,
  because both handlers read the last fetched state. A row's controls are
  now disabled while that row's write is in flight — keyed on the
  mutation's variables, so the rest of the section stays usable.
- The row test id was keyed only by notification type, so a type that
  appears under two apps produced duplicate ids. It now includes the app.
- A throttled read fell through to the generic failure message; 429 gets
  its own, matching what the write path already does.
@AhtishamShahid

Copy link
Copy Markdown
Contributor Author

you were right on both. 3897 is reopened and backend only now, the UI moved here, so this PR is blocked on it. description is rewritten too, those backend claims were copied over by mistake.

This branch has not been deployed

No deployments
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.

3 participants