Skip to content

Commit 7287637

Browse files
arbrandesclaude
andcommitted
feat: add frontend-base sibling alongside legacy FPF plugin
Introduce frontend-app-sample/ next to the existing frontend-plugin-sample/. The new directory is a frontend-base app, written in TypeScript, that contributes a widgetReplace operation against the learner-dashboard's CourseList slot on the bundled tutor-mfe site. The legacy directory stays put, unchanged, and keeps targeting the per-MFE env.config.jsx flow. Have tutor-contrib-sample register both. The operator picks which one fires by enabling (or not) the frontend-base learner-dashboard app in tutor-mfe. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent ccc9a1d commit 7287637

15 files changed

Lines changed: 836 additions & 33 deletions

File tree

.github/workflows/release.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,30 @@ jobs:
160160
npm run build
161161
npm publish
162162
working-directory: './frontend-plugin-sample'
163+
164+
publish_app_to_npm:
165+
runs-on: ubuntu-latest
166+
needs: release
167+
if: github.ref_name == 'main' && needs.release.outputs.released == 'true'
168+
permissions:
169+
contents: read
170+
id-token: write
171+
172+
steps:
173+
- name: Setup | Checkout Repository on Release Ref
174+
uses: actions/checkout@v6
175+
with:
176+
ref: ${{ github.sha }}
177+
178+
- name: Setup Node.js
179+
uses: actions/setup-node@v6
180+
with:
181+
node-version-file: './frontend-app-sample/.nvmrc'
182+
183+
- name: Update the package version and publish
184+
run: |
185+
npm install
186+
npm version ${{ needs.release.outputs.version }}
187+
npm run build
188+
npm publish
189+
working-directory: './frontend-app-sample'

CLAUDE.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ This is a **sample plugin repository** that demonstrates all major Open edX plug
1414

1515
**Repository Structure:**
1616
- `backend-plugin-sample/` - Django app plugin with models, APIs, events, and filters
17-
- `frontend-plugin-sample/` - React component for MFE slot customization
18-
- `tutor-contrib-sample/` - Tutor plugin for easy deployment
17+
- `frontend-plugin-sample/` - React component plugged in via the legacy `frontend-plugin-framework` (FPF) and `env.config.jsx`
18+
- `frontend-app-sample/` - frontend-base `App` (sibling of `frontend-plugin-sample/`) that registers slot operations on the bundled site
19+
- `tutor-contrib-sample/` - Tutor plugin that installs one or the other depending on tutor-mfe's `FRONTEND_APPS` state
1920
- Each directory has comprehensive README.md files with TOCs
2021

2122
**When Making Changes:**
@@ -24,14 +25,19 @@ This is a **sample plugin repository** that demonstrates all major Open edX plug
2425
- Maintain working integration between all plugin types
2526
- Keep examples realistic but not overly complex
2627

28+
**Branch context:**
29+
This branch ships *both* frontend stacks side by side: `frontend-plugin-sample/` (legacy FPF, identical to `main`) and `frontend-app-sample/` (frontend-base App). The Tutor plugin registers both unconditionally; each one self-no-ops when its target stack isn't in play. The FPF `PLUGIN_SLOTS` contribution only renders into the legacy learner-dashboard MFE (which tutor-mfe skips when the frontend-base learner-dashboard App is enabled), and the frontend-base App's slot operation targets a slot that only exists when the frontend-base learner-dashboard App is loaded. The operator picks the active path by flipping `apps["learner-dashboard"]["enabled"]` in tutor-mfe's `FRONTEND_APPS` filter. See [Port a Frontend Plugin from frontend-plugin-framework to frontend-base](https://docs.openedx.org/en/latest/site_ops/how-tos/port-frontend-plugin-to-frontend-base.html) for the conceptual differences between the two stacks.
30+
2731
**Key Files and Their Relationships:**
2832
- `backend-plugin-sample/openedx_plugin_sample/apps.py` - Plugin registration and Django integration
2933
- `backend-plugin-sample/openedx_plugin_sample/signals.py` - Open edX Events handlers
3034
- `backend-plugin-sample/openedx_plugin_sample/pipeline.py` - Open edX Filters implementation
3135
- `backend-plugin-sample/openedx_plugin_sample/models.py` - CourseArchiveStatus model (business logic)
32-
- `backend-plugin-sample/openedx_plugin_sample/views.py` - REST API endpoints consumed by frontend
33-
- `frontend-plugin-sample/src/plugin.jsx` - React component that replaces course list slot
34-
- `tutor-contrib-sample/tutorsample/plugin.py` - Deployment configuration (currently basic template)
36+
- `backend-plugin-sample/openedx_plugin_sample/views.py` - REST API endpoints consumed by either frontend
37+
- `frontend-plugin-sample/src/plugin.jsx` - Legacy FPF React component (imports from `@edx/frontend-platform`)
38+
- `frontend-app-sample/src/CourseList.tsx` - frontend-base React component, TypeScript (imports from `@openedx/frontend-base`)
39+
- `frontend-app-sample/src/app.tsx` - frontend-base `App` with slot operations targeting the learner-dashboard, TypeScript
40+
- `tutor-contrib-sample/tutorsample/plugin.py` - Tutor plugin: backend pip install + unconditional registration of both frontend paths (FPF via `PLUGIN_SLOTS` and frontend-base via `FRONTEND_APPS` + site-config patches); each path self-no-ops when its target stack isn't active
3541

3642
## Build/Lint/Test Commands
3743
- Make sure to set the following so that test output is not too verbose: `export PYTEST_ADDOPTS="--disable-warnings --no-header --tb=short"`
@@ -69,6 +75,6 @@ Always run `make quality` and fix issues before creating a PR to ensure consiste
6975
### Open edX Plugin Patterns
7076
- **API Development**: Use `perform_create()`/`perform_update()` in viewsets for business logic
7177
- **Settings**: Use additive approach for `OPEN_EDX_FILTERS_CONFIG` to avoid plugin conflicts
72-
- **Frontend**: Use Paragon components and `getAuthenticatedHttpClient()` for platform integration
78+
- **Frontend**: Use Paragon components and import `getAuthenticatedHttpClient`/`getSiteConfig` from `@openedx/frontend-base` (not `@edx/frontend-platform`). The package's default export is the frontend-base `App`.
7379
- **Events**: Import signal handlers in `apps.py ready()` method for proper registration
7480
- **Filters**: Return dictionaries with same parameter names as input, handle all scenarios

README.md

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ This sample plugin showcases the **Open edX Hooks Extension Framework**, which a
3232
| **Django App Plugin** | Add models, APIs, views, and business logic | [How to create a plugin app](https://docs.openedx.org/projects/edx-django-utils/en/latest/plugins/how_tos/how_to_create_a_plugin_app.html) | [`backend-plugin-sample/`](./backend-plugin-sample/) | Adding new functionality, APIs, or data models |
3333
| **Events (Signals)** | React to platform events | [Open edX Events Guide](https://docs.openedx.org/projects/openedx-events/en/latest/) | [`backend-plugin-sample/openedx_plugin_sample/signals.py`](./backend-plugin-sample/openedx_plugin_sample/signals.py) | Integrating with external systems, audit logging |
3434
| **Filters** | Modify platform behavior | [Using Open edX Filters](https://docs.openedx.org/projects/openedx-filters/en/latest/how-tos/using-filters.html) | [`backend-plugin-sample/openedx_plugin_sample/pipeline.py`](./backend-plugin-sample/openedx_plugin_sample/pipeline.py) | Customizing business logic, URL redirects |
35-
| **Frontend Slots** | Customize MFE interfaces | [Frontend Plugin Slots](https://docs.openedx.org/en/latest/site_ops/how-tos/use-frontend-plugin-slots.html) | [`frontend-plugin-sample/`](./frontend-plugin-sample/) | UI customization, adding new components |
35+
| **Frontend Plugin (FPF)** | Customize MFE interfaces via [frontend-plugin-framework](https://github.com/openedx/frontend-plugin-framework) | [Frontend Plugin Slots](https://docs.openedx.org/en/latest/site_ops/how-tos/use-frontend-plugin-slots.html) | [`frontend-plugin-sample/`](./frontend-plugin-sample/) | UI customization on legacy per-MFE images |
36+
| **Frontend App (frontend-base)** | Customize MFE interfaces via the [frontend-base](https://github.com/openedx/frontend-base) Apps model | [tutor-mfe Frontend-base site](https://github.com/overhangio/tutor-mfe#frontend-base-site) | [`frontend-app-sample/`](./frontend-app-sample/) | UI customization on the bundled frontend-base site |
3637
| **Brand Packages** | Customize theming | [Open edX Brand Package Interface](https://github.com/openedx/brand-openedx) | [`brand-sample/`](./brand-sample/) | UI theming |
3738
| **Tutor Plugin** | Deploy plugins easily | [Tutor Plugin Development](https://docs.tutor.edly.io/) | [`tutor-contrib-sample/`](./tutor-contrib-sample/) | Simplified deployment and configuration |
3839

@@ -49,14 +50,26 @@ This sample plugin showcases the **Open edX Hooks Extension Framework**, which a
4950
tutor mounts add "$PWD/backend-plugin-sample"
5051

5152
# Rebuild image, run migrations, reboot containers:
52-
tutor dev launch
53-
54-
# Frontend Plugin Setup (for learner-dashboard MFE development)
55-
# Add env.config.jsx and module.config.js (see frontend-plugin-sample/README.md)
56-
# Then, install and run.
53+
tutor dev launch
54+
55+
# Frontend setup. The tutor-contrib-sample plugin registers both frontend
56+
# siblings.
57+
#
58+
# Legacy FPF: set up env.config.jsx and module.config.js,
59+
# then install and run the learner-dashboard MFE (see
60+
# frontend-plugin-sample/README.md for details).
5761
cd path/to/frontend-app-learner-dashboard && npm ci && npm run dev
62+
#
63+
# Frontend-base: flip apps["learner-dashboard"]["enabled"] = True
64+
# in your own Tutor plugin, set up a fork of frontend-template-site
65+
# with frontend-app-sample in packages/ (see frontend-app-sample/README.md
66+
# for details), then:
67+
cd path/to/frontend-site && npm i && npm run dev:packages
5868
```
5969

70+
> [!NOTE]
71+
> This carries both the legacy `frontend-plugin-sample/` (frontend-plugin-framework) and a `frontend-app-sample/` sibling that targets tutor-mfe's [frontend-base site](https://github.com/overhangio/tutor-mfe#frontend-base-site). The Tutor plugin in `tutor-contrib-sample/` registers both. For the conceptual differences between the two stacks, see [Port a Frontend Plugin from frontend-plugin-framework to frontend-base](https://docs.openedx.org/en/latest/site_ops/how-tos/port-frontend-plugin-to-frontend-base.html).
72+
6073
### Option 2: Development without Tutor
6174

6275
```bash
@@ -91,7 +104,8 @@ Use the table above to identify which type of plugin matches your needs. You can
91104
- **Backend**: Start with [`backend-plugin-sample/openedx_plugin_sample/apps.py`](./backend-plugin-sample/openedx_plugin_sample/apps.py) to understand plugin registration
92105
- **Events**: Examine [`backend-plugin-sample/openedx_plugin_sample/signals.py`](./backend-plugin-sample/openedx_plugin_sample/signals.py) for event handling patterns
93106
- **Filters**: Review [`backend-plugin-sample/openedx_plugin_sample/pipeline.py`](./backend-plugin-sample/openedx_plugin_sample/pipeline.py) for behavior modification
94-
- **Frontend**: Explore [`frontend-plugin-sample/src/plugin.jsx`](./frontend-plugin-sample/src/plugin.jsx) for UI customization
107+
- **Frontend (FPF)**: Explore [`frontend-plugin-sample/src/plugin.jsx`](./frontend-plugin-sample/src/plugin.jsx) for the legacy React component plugged into `env.config.jsx`
108+
- **Frontend (frontend-base)**: Explore [`frontend-app-sample/src/app.tsx`](./frontend-app-sample/src/app.tsx) for the App declaration and [`frontend-app-sample/src/CourseList.tsx`](./frontend-app-sample/src/CourseList.tsx) for the React component
95109

96110
### 4. Run This Sample
97111
Follow the [Quick Start Guide](#quick-start-guide) to see everything working together.
@@ -115,15 +129,22 @@ sample-plugin/
115129
│ │ ├── settings/ # Plugin settings configuration
116130
│ │ └── urls.py # URL routing
117131
│ └── tests/ # Comprehensive test examples
118-
├── frontend-plugin-sample/
119-
│ ├── README.md # Frontend plugin detailed guide
132+
├── frontend-plugin-sample/ # Legacy FPF version (env.config.jsx)
133+
│ ├── README.md
134+
│ ├── src/
135+
│ │ ├── plugin.jsx # React component for the FPF slot
136+
│ │ └── index.jsx # Named exports
137+
│ └── package.json
138+
├── frontend-app-sample/ # frontend-base version (site config; TypeScript)
139+
│ ├── README.md
120140
│ ├── src/
121-
│ │ ├── plugin.jsx # React component for MFE slot
122-
│ │ └── index.jsx # Export configuration
123-
│ └── package.json # NPM package configuration
124-
└── tutor-contrib-sample/
141+
│ │ ├── CourseList.tsx # React component
142+
│ │ ├── app.tsx # frontend-base App with slot operations
143+
│ │ └── index.ts # Default-exports the App
144+
│ └── package.json
145+
└── tutor-contrib-sample/ # Registers both siblings; each self-no-ops when inactive
125146
├── README.md # Tutor deployment guide
126-
└── sample.py # Tutor plugin configuration
147+
└── tutorsample/plugin.py # Tutor plugin configuration
127148
```
128149

129150
## Development Workflows

frontend-app-sample/.gitignore

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
build/
12+
develop-eggs/
13+
dist/
14+
downloads/
15+
eggs/
16+
.eggs/
17+
lib/
18+
lib64/
19+
parts/
20+
sdist/
21+
var/
22+
wheels/
23+
share/python-wheels/
24+
*.egg-info/
25+
.installed.cfg
26+
*.egg
27+
MANIFEST
28+
29+
# PyInstaller
30+
# Usually these files are written by a python script from a template
31+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
32+
*.manifest
33+
*.spec
34+
35+
# Installer logs
36+
pip-log.txt
37+
pip-delete-this-directory.txt
38+
39+
# Unit test / coverage reports
40+
htmlcov/
41+
.tox/
42+
.nox/
43+
.coverage
44+
.coverage.*
45+
.cache
46+
nosetests.xml
47+
coverage.xml
48+
*.cover
49+
*.py,cover
50+
.hypothesis/
51+
.pytest_cache/
52+
cover/
53+
54+
# Translations
55+
*.mo
56+
*.pot
57+
58+
# Django stuff:
59+
*.log
60+
local_settings.py
61+
db.sqlite3
62+
db.sqlite3-journal
63+
64+
# Flask stuff:
65+
instance/
66+
.webassets-cache
67+
68+
# Scrapy stuff:
69+
.scrapy
70+
71+
# Sphinx documentation
72+
docs/_build/
73+
74+
# PyBuilder
75+
.pybuilder/
76+
target/
77+
78+
# Jupyter Notebook
79+
.ipynb_checkpoints
80+
81+
# IPython
82+
profile_default/
83+
ipython_config.py
84+
85+
# pyenv
86+
# For a library or package, you might want to ignore these files since the code is
87+
# intended to run in multiple environments; otherwise, check them in:
88+
# .python-version
89+
90+
# pipenv
91+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
93+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
94+
# install all needed dependencies.
95+
#Pipfile.lock
96+
97+
# UV
98+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99+
# This is especially recommended for binary packages to ensure reproducibility, and is more
100+
# commonly ignored for libraries.
101+
#uv.lock
102+
103+
# poetry
104+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105+
# This is especially recommended for binary packages to ensure reproducibility, and is more
106+
# commonly ignored for libraries.
107+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108+
#poetry.lock
109+
110+
# pdm
111+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112+
#pdm.lock
113+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114+
# in version control.
115+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116+
.pdm.toml
117+
.pdm-python
118+
.pdm-build/
119+
120+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121+
__pypackages__/
122+
123+
# Celery stuff
124+
celerybeat-schedule
125+
celerybeat.pid
126+
127+
# SageMath parsed files
128+
*.sage.py
129+
130+
# Environments
131+
.env
132+
.venv
133+
env/
134+
venv/
135+
ENV/
136+
env.bak/
137+
venv.bak/
138+
139+
# Spyder project settings
140+
.spyderproject
141+
.spyproject
142+
143+
# Rope project settings
144+
.ropeproject
145+
146+
# mkdocs documentation
147+
/site
148+
149+
# mypy
150+
.mypy_cache/
151+
.dmypy.json
152+
dmypy.json
153+
154+
# Pyre type checker
155+
.pyre/
156+
157+
# pytype static type analyzer
158+
.pytype/
159+
160+
# Cython debug symbols
161+
cython_debug/
162+
163+
# PyCharm
164+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166+
# and can be added to the global gitignore or merged into this file. For a more nuclear
167+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
168+
#.idea/
169+
170+
# Ruff stuff:
171+
.ruff_cache/
172+
173+
# PyPI configuration file
174+
.pypirc
175+
176+
# JS Git Ignore
177+
.DS_Store
178+
.eslintcache
179+
.idea
180+
*.swp
181+
*.swo
182+
node_modules
183+
npm-debug.log
184+
coverage
185+
env.config.*
186+
187+
dist/
188+
logs
189+
190+
### Editors ###
191+
*~
192+
/temp
193+
/.vscode
194+
195+
# Local package dependencies
196+
module.config.js
197+
198+
# Local environment overrides
199+
.env.private

frontend-app-sample/.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
24

0 commit comments

Comments
 (0)