feat: decouple notifications panel using widget registry mechanism#1885
feat: decouple notifications panel using widget registry mechanism#1885awais-ansari wants to merge 1 commit intoopenedx:masterfrom
Conversation
005f525 to
e50a017
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #1885 +/- ##
==========================================
- Coverage 91.13% 88.74% -2.39%
==========================================
Files 349 343 -6
Lines 5807 5747 -60
Branches 1376 1374 -2
==========================================
- Hits 5292 5100 -192
- Misses 496 613 +117
- Partials 19 34 +15 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@brian-smith-tcril Please review this PR. |
e50a017 to
1b6e9df
Compare
arbrandes
left a comment
There was a problem hiding this comment.
First off: this is amazingly well documented. Congrats, and thank you! I'm probably going to start using this PR as a benchmark for how complicated frontend architectural decisions should be implemented.
Second: this is mostly an architecture review based on the ADR, ARCHITECTURE.md,and a glance at USE_CASE_VERIFICATION.md. I have not looked closely at the code, yet.
Lastly, I'm coming at this "from the future". Prior to the Willow cutoff in October of this year, frontend-app-learning will be converted to frontend-base, and this is one of the situations where the new slot architecture will help (or, at least, affect). Some of the inline comments are just FYIs related to that, and a couple are meant to help with the transition later. Either way, we should keep this in mind.
| @@ -0,0 +1,35 @@ | |||
| { | |||
There was a problem hiding this comment.
Two things:
- Let's just call this directory
widgets, instead ofexternal-widgets. It's what frontend-base apps are doing. - I suggest avoiding having this as a separate package while in the repo, if for no other reason than that our Github repos not being properly tooled to be monorepos. In other words, let's drop the package.json.
There was a problem hiding this comment.
Re: avoiding separate package
This decoupling reminds me of what we did with https://github.com/openedx/frontend-app-learning/tree/master/src/plugin-slots/SequenceNavigationSlot and https://github.com/openedx/frontend-app-learning/tree/master/src/plugin-slots/CourseBreadcrumbsSlot. We kept the old components around, but instead of directly importing/using them we provided example FPF configs showing how to import and use them.
| // See https://stackoverflow.com/questions/72382316/jest-encountered-an-unexpected-token-react-markdown | ||
| 'react-markdown': '<rootDir>/node_modules/react-markdown/react-markdown.min.js', | ||
| '@src/(.*)': '<rootDir>/src/$1', | ||
| '@edx/learning-mfe-widget': '<rootDir>/src/sidebar-widget-sdk.js', |
There was a problem hiding this comment.
The added boilerplate is another reason not to attempt a monorepo structure.
| UPGRADE: 20, | ||
| DISCUSSIONS: 10, |
There was a problem hiding this comment.
These priorities would ideally be defaults that the widgets themselves set.
Another way to read it is: if the sidebar is going to be truly generic, it should not know anything in advance about the Upgrade widget (or discussions, for that matter).
| * The upgrade widget is no longer built-in — configure it via SIDEBAR_WIDGETS | ||
| * in env.config.jsx using the @edx/learning-upgrade-widget package. |
There was a problem hiding this comment.
Nit: let's not even mention other widgets outside their own scope in the codebase.
| * @param {Object} context - Contains courseId, unitId, topic, courseHomeMeta | ||
| * @returns {boolean} Whether discussions are available | ||
| */ | ||
| export const discussionsIsAvailable = ({ topic }) => !!(topic?.id && topic?.enabledInContext); |
There was a problem hiding this comment.
Is there a way to have this widget-specific code moved to its own scope (i.e., its own directory) in the codebase?
| | Discussions available | `'DISCUSSIONS'` | `'DISCUSSIONS'` | Discussions opens | | ||
| | Only Upgrade | `'UPGRADE'` | `'UPGRADE'` | Upgrade opens | | ||
| | Neither available | `null` | `'COURSE_OUTLINE'` | Course Outline opens (fallback) | |
There was a problem hiding this comment.
Let's make this more generic. For example, instead of Discussions or Upgrade, refer to generic widgets and priorities, and how they interact. Alternatively, keep Discussions and Upgrade, but specify their default priorities, instead of assuming the reader knows.
| **Scenario 1: Previous unit had DISCUSSIONS open** | ||
| - New unit has discussions → **Stays on Discussions** | ||
| - New unit NO discussions, YES upgrade → **Switches to Upgrade** | ||
| - New unit NO discussions, NO upgrade → **Closes right sidebar, Course Outline auto-opens** |
There was a problem hiding this comment.
Is this logic hard-coded upstream, or is it emergent behavior based on the widget's self-reported priorities and other properties?
My point, also made in other comments, is that we should never have a hard-coded if (UPGRADE) outside the scope of the Upgrade widget itself.
| - **Location**: Right side of screen | ||
| - **Component**: `Sidebar` (rendered via `RightSidebarSlot`) | ||
| - **Purpose**: Contextual tools and information panels | ||
| - **State Management**: `SidebarContextProvider` + widget registry system |
There was a problem hiding this comment.
I very much like the registry idea, but my first question when seeing it was: why not offer the same registry for the left sidebar?
Then I remembered that this is exactly one of the things we tried to address with frontend-base. I suspect that during the conversion the registry will be made obsolete - or just much thinner - due to the frontend-base provider hoisting and slot layout systems.
In other words: never mind, for now. Just be aware that this is one of the configuration APIs that will change after the conversion.
| - i18n message keys (`notification.*`) | ||
|
|
||
|
|
||
| ### 3. External package `@edx/learning-upgrade-widget` is created |
There was a problem hiding this comment.
As mentioned elsewhere, let's just make this a subdirectory of widgets/: no need to have a separate package (with all the monorepo headaches it brings) until it is in a different repository.
| 2. Set `ENABLE_LEGACY_UPGRADE_WIDGET: false` | ||
| 3. Add the package widget to `SIDEBAR_WIDGETS` in `env.config.jsx` | ||
|
|
||
| The package skeleton lives in `src/external-widgets/learning-upsell-widget/` until promoted to its own repository. |
There was a problem hiding this comment.
If my suggestion from another comment is accepted, we should add:
### 3. Move the Discussions widget code into its own directory
It would be a sibling to the Upgrade widget.
Summary
Removes the tightly coupled upgrade/upsell ("notifications") panel from the Learning MFE core and replaces it with a pluggable widget registry system. The right sidebar now supports dynamically-registered external widgets, making it easy to add, remove, or replace sidebar panels without forking the MFE. Proposal
Motivation
The built-in upgrade panel (NotificationTray) caused several issues:
Architecture: Pluggable Widget Registry
The right sidebar now operates on a widget registry (SIDEBARS) built at runtime from:
Built-in widgets: only DISCUSSIONS are included by default.
External widgets: installed via npm and registered via SIDEBAR_WIDGETS in env.config.jsx
Each widget provides:
Rename: notifications → upgrade
All internal references to the old "Notifications" panel are renamed for clarity:
Extracted Upgrade Widget Package
The upgrade panel is extracted into a standalone package skeleton at learning-upgrade-widget. Currently, the package skeleton lives in
src/external-widgets/learning-upsell-widget/until promoted to its own repository.Configuration
New Widget SDK
sidebar-widget-sdk.js is the sole contract between the host MFE and external widget packages :
Two-Sidebar Collaboration
The architecture docs in ARCHITECTURE.md fully describe the shared currentSidebar state between the LEFT (Course Outline) and RIGHT (widget panels) sidebars, including:
Testing
All use cases verified and documented in USE_CASE_VERIFICATION.md, covering: