Skip to content

Commit

Permalink
feat: upgrade to Palm
Browse files Browse the repository at this point in the history
With this new release, we make use of persistent bind-mounts to make it
much easier to work on MFE forks. In addition, adding new MFEs is no
longer done with `*_MFE_APP` settings, which was awkward, but with
appropriate plugins.

Close #111.
  • Loading branch information
regisb committed May 11, 2023
1 parent c761791 commit a2eea86
Show file tree
Hide file tree
Showing 14 changed files with 324 additions and 267 deletions.
2 changes: 1 addition & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ variables:
TUTOR_PLUGIN: mfe
TUTOR_IMAGES: mfe account-dev course-authoring-dev discussions-dev authn-dev gradebook-dev learning-dev profile-dev
TUTOR_PYPI_PACKAGE: tutor-mfe
OPENEDX_RELEASE: olive
OPENEDX_RELEASE: palm
GITHUB_REPO: overhangio/tutor-mfe

include:
Expand Down
119 changes: 53 additions & 66 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ This plugin makes it possible to easily add micro frontend (MFE) applications on

In addition, this plugin comes with a few MFEs which are enabled by default:

- `Authn <https://github.com/openedx/frontend-app-authn/>`__
- `Account <https://github.com/openedx/frontend-app-account/>`__
- `Course Authoring <https://github.com/openedx/frontend-app-course-authoring/>`__
- `Discussions <https://github.com/openedx/frontend-app-discussions/>`__
- `Gradebook <https://github.com/openedx/frontend-app-gradebook/>`__
- `Learning <https://github.com/openedx/frontend-app-learning/>`__
- `Profile <https://github.com/openedx/frontend-app-profile/>`__
- `Course Authoring <https://github.com/openedx/frontend-app-course-authoring/>`__
- `Discussions <https://github.com/openedx/frontend-app-discussions/>`__
- `Authn <https://github.com/openedx/frontend-app-authn/>`__

Instructions for using each of these MFEs are given below.

Expand All @@ -20,7 +20,7 @@ Installation

::

pip install tutor-mfe
tutor plugins install mfe

Usage
-----
Expand All @@ -32,10 +32,17 @@ To enable this plugin, run::

When running the plugin in production, it is recommended that you set up a catch-all CNAME for subdomains at the DNS provider: see the `Configuring DNS Records <https://docs.tutor.overhang.io/install.html#configuring-dns-records>`__ section in the Tutor documentation for more details. This way, the plugin will work out of the box with no additional configuration. Which is to say, if your ``LMS_HOST`` is set to `myopenedx.com` the MFEs this plugin provides will be accessible under `apps.myopenedx.com` by default.

To check what the current value of `MFE_HOST` is actually set to, run the following::
To check what the current value of `MFE_HOST` is actually set to, run::

tutor config printvalue MFE_HOST

Authn
~~~~~~~~~

.. image:: https://raw.githubusercontent.com/overhangio/tutor-mfe/master/screenshots/authn.png
:alt: Authn MFE screenshot

This is a micro-frontend application responsible for the login, registration and password reset functionality.

Account
~~~~~~~
Expand All @@ -45,6 +52,22 @@ Account

An MFE to manage account-specific information for every LMS user. Each user's account page is available at ``http(s)://{{ MFE_HOST }}/account``. For instance, when running locally: https://apps.local.overhang.io/account.

Course Authoring
~~~~~~~~~~~~~~~~

.. image:: https://raw.githubusercontent.com/overhangio/tutor-mfe/master/screenshots/course-authoring.png
:alt: Course Authoring MFE screenshot

This MFE is meant for course authors and maintainers. For a given course, it exposes a "Pages & Resources" menu in Studio where one can enable or disable a variety of features, including, for example, the Wiki and Discussions. Optionally, it allows authors to replace the legacy HTML, Video, and Problem authoring tools with experimental React-based versions, as well as exposing a new proctoring interface that can be enabled if the `edx-exams <https://github.com/edx/edx-exams>`_ service is available.

Discussions
~~~~~~~~~~~

.. image:: https://raw.githubusercontent.com/overhangio/tutor-mfe/master/screenshots/discussions.png
:alt: Discussions MFE screenshot

The Discussions MFE updates the previous discussions UI with a new look and better features.

Gradebook
~~~~~~~~~

Expand All @@ -69,62 +92,41 @@ Profile

Edit and display user-specific profile information. The profile page of every user is visible at ``http(s)://{{ MFE_HOST }}/profile/u/{{ username }}``. For instance, when running locally, the profile page of the "admin" user is: http://apps.local.overhang.io/profile/u/admin.

Course Authoring
~~~~~~~~~~~~~~~~

.. image:: https://raw.githubusercontent.com/overhangio/tutor-mfe/master/screenshots/course-authoring.png
:alt: Course Authoring MFE screenshot

This MFE is meant for course authors and maintainers. For a given course, it exposes a "Pages & Resources" menu in Studio where one can enable or disable a variety of features, including, for example, the Wiki and Discussions. Optionally, it allows authors to replace the legacy HTML, Video, and Problem authoring tools with experimental React-based versions, as well as exposing a new proctoring interface that can be enabled if the `edx-exams <https://github.com/edx/edx-exams>`_ service is available.

Discussions
~~~~~~~~~~~

.. image:: https://raw.githubusercontent.com/overhangio/tutor-mfe/master/screenshots/discussions.png
:alt: Discussions MFE screenshot

The Discussions MFE updates the previous discussions UI with a new look and better features.

Authn
~~~~~~~~~

.. image:: https://raw.githubusercontent.com/overhangio/tutor-mfe/master/screenshots/authn.png
:alt: Authn MFE screenshot

This is a micro-frontend application responsible for the login, registration and password reset functionality.

MFE management
--------------

Adding new MFEs
~~~~~~~~~~~~~~~

Other Tutor plugin developers can take advantage of this plugin to deploy their own MFEs. To declare a new MFE, a new configuration setting should be created with the "_MFE_APP" suffix. This configuration setting should include the name, git repository (and optionally: git branch) and development port. For example::
.. warning:: As of Tutor v16 (Palm release) it is no longer possible to add new MFEs by creating ``*_MFE_APP`` settings. Instead, users must implement the approach described here.

Other MFE developers can take advantage of this plugin to deploy their own MFEs. To declare a new MFE, create a Tutor plugin and add your MFE configuration to the ``tutormfe.plugin.MFE_APPS`` filter. This configuration should include the name, git repository (and optionally: git branch) and development port. For example::

config = {
"defaults": {
"MYMFE_MFE_APP": {
"name": "mymfe",
"repository": "https://github.com/myorg/mymfe",
"port": 2001,
"version": "me/my-custom-branch", # optional
}
from tutormfe.plugin import MFE_APPS

@MFE_APPS.add()
def _add_my_mfe(mfes):
mfes["mymfe"] = {
"repository": "https://github.com/myorg/mymfe",
"port": 2001,
"version": "me/my-custom-branch", # optional, will default to the Open edX current tag.
}
}
return mfes

The MFE assets will then be bundled in the "mfe" Docker image whenever it is rebuilt with ``tutor images build mfe``. Assets will be served at ``http(s)://{{ MFE_HOST }}/{{ MYMFE_MFE_APP["name"] }}``. Developers are free to add extra template patches to their plugins, as usual: for instance LMS setting patches to make sure that the LMS correctly connects to the MFEs.
The MFE assets will then be bundled in the "mfe" Docker image whenever it is rebuilt with ``tutor images build mfe``. Assets will be served at ``http(s)://{{ MFE_HOST }}/mymfe``. Developers are free to add extra template patches to their plugins, as usual: for instance LMS setting patches to make sure that the LMS correctly connects to the MFEs.

Disabling individual MFEs
~~~~~~~~~~~~~~~~~~~~~~~~~

To disable an existing MFE, set its corresponding configuration setting to "null". For instance, to disable the MFEs that ship with this plugin::
To disable an existing MFE, remove the corresponding entry from the ``MFE_APPS`` filter. For instance, to disable some of the MFEs that ship with this plugin::

tutor config save --set MFE_ACCOUNT_MFE_APP=null
tutor config save --set MFE_GRADEBOOK_MFE_APP=null
tutor config save --set MFE_PROFILE_MFE_APP=null
tutor config save --set MFE_COURSE_AUTHORING_MFE_APP=null
tutor config save --set MFE_DISCUSSIONS_MFE_APP=null
tutor config save --set MFE_AUTHN_MFE_APP=null

@MFE_APPS.add()
def _remove_some_my_mfe(mfes):
mfes.pop("account")
mfes.pop("profile")
...

Adding custom translations to your MFEs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -243,31 +245,16 @@ Then, access http://apps.local.overhang.io:1995/profile/u/YOURUSERNAME

You can also bind-mount your own fork of an MFE. For example::

cd /path/to/frontend-app-profile
npm install # Ensure NPM requirements are installed into your fork.
tutor dev start --mount=. profile
tutor config save --append MOUNTS=/path/to/frontend-app-profile
tutor dev launch

The changes you make to your fork will be automatically picked up and hot-reloaded by your development server.
With this change, the "profile-dev" image will be automatically re-built during ``launch``. Your host repository will then be bind-mounted at runtime in the "profile" container. This means that changes you make to the host repository will be automatically picked up and hot-reloaded by your development server.

This works for custom MFEs, as well. For example, if you added your own MFE named frontend-app-myapp, then you can bind-mount it like so::

cd /path/to/frontend-app-myapp
npm install
tutor dev start --mount=. myapp

However, if you try to bind-mount an unknown MFE, you will see a Docker Compose error such as::

ERROR: The Compose file is invalid because:
Service myapp has neither an image nor a build context specified. At least one must be provided.

Please note that bind-mounting a fork is only available for development (``tutor dev ...``), since production MFEs are compiled and served out of a single container. If you want to use a fork of an MFE in production, then you will need to set the repository URL in ``$(tutor config printroot)/config.yml``::

MFE_PROFILE_MFE_APP
name: profile
repository: "https://github.com/YOUR_FORK_ORGANIZATION/frontend-app-profile"
port: 1995
tutor config save --append MOUNTS=/path/to/frontend-app-myapp

and then rebuild the MFE container image with ``tutor images build mfe``.
Similarly, in production, the "mfe" Docker image will be rebuilt automatically during ``tutor local launch``.

Uninstall
---------
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def load_about():
packages=find_packages(exclude=["tests*"]),
include_package_data=True,
python_requires=">=3.5",
install_requires=["tutor>=15.0.0,<16.0.0"],
install_requires=["tutor>=16.0.0,<17.0.0"],
entry_points={"tutor.plugin.v1": ["mfe = tutormfe.plugin"]},
classifiers=[
"Development Status :: 3 - Alpha",
Expand Down
2 changes: 1 addition & 1 deletion tutormfe/__about__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__version__ = "15.0.6"
__version__ = "16.0.0"

9 changes: 6 additions & 3 deletions tutormfe/patches/local-docker-compose-dev-services
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# MFE apps
{% for app in iter_values_named(suffix="MFE_APP") %}
{{ app["name"] }}:
image: "{{ DOCKER_REGISTRY }}overhangio/openedx-{{ app["name"] }}-dev:{{ MFE_VERSION }}"
{% for app_name, app in iter_mfes() %}
{{ app_name }}:
image: "{{ DOCKER_REGISTRY }}overhangio/openedx-{{ app_name }}-dev:{{ MFE_VERSION }}"
ports:
- "{{ app["port"] }}:{{ app["port"] }}"
stdin_open: true
tty: true
volumes:
- ../plugins/mfe/apps/mfe/webpack.dev-tutor.config.js:/openedx/app/webpack.dev-tutor.config.js:ro
{%- for mount in iter_mounts(MOUNTS, app_name) %}
- {{ mount }}
{%- endfor %}
restart: unless-stopped
depends_on:
- lms
Expand Down
13 changes: 8 additions & 5 deletions tutormfe/patches/openedx-cms-development-settings
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{% if MFE_COURSE_AUTHORING_MFE_APP %}
COURSE_AUTHORING_MICROFRONTEND_URL = "http://{{ MFE_HOST }}:{{ MFE_COURSE_AUTHORING_MFE_APP["port"] }}/{{ MFE_COURSE_AUTHORING_MFE_APP["name"] }}"
CORS_ORIGIN_WHITELIST.append("http://{{ MFE_HOST }}:{{ MFE_COURSE_AUTHORING_MFE_APP["port"] }}")
LOGIN_REDIRECT_WHITELIST.append("{{ MFE_HOST }}:{{ MFE_COURSE_AUTHORING_MFE_APP["port"] }}")
CSRF_TRUSTED_ORIGINS.append("{{ MFE_HOST }}:{{ MFE_COURSE_AUTHORING_MFE_APP["port"] }}")
# MFE-specific settings
{% for app_name, app in iter_mfes() %}
{% if app_name == "course-authoring" %}
COURSE_AUTHORING_MICROFRONTEND_URL = "http://{{ MFE_HOST }}:{{ app["port"] }}/course-authoring"
CORS_ORIGIN_WHITELIST.append("http://{{ MFE_HOST }}:{{ app["port"] }}")
LOGIN_REDIRECT_WHITELIST.append("{{ MFE_HOST }}:{{ app["port"] }}")
CSRF_TRUSTED_ORIGINS.append("{{ MFE_HOST }}:{{ app["port"] }}")
{% endif %}
{% endfor %}
7 changes: 5 additions & 2 deletions tutormfe/patches/openedx-cms-production-settings
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{% if MFE_COURSE_AUTHORING_MFE_APP %}
COURSE_AUTHORING_MICROFRONTEND_URL = "{% if ENABLE_HTTPS %}https://{% else %}http://{% endif %}{{ MFE_HOST }}/{{ MFE_COURSE_AUTHORING_MFE_APP["name"] }}"
# MFE-specific settings
{% for app_name, app in iter_mfes() %}
{% if app_name == "course-authoring" %}
COURSE_AUTHORING_MICROFRONTEND_URL = "{% if ENABLE_HTTPS %}https://{% else %}http://{% endif %}{{ MFE_HOST }}/course-authoring"
{% endif %}
{% endfor %}

LOGIN_REDIRECT_WHITELIST.append("{{ MFE_HOST }}")
CORS_ORIGIN_WHITELIST.append("{% if ENABLE_HTTPS %}https://{% else %}http://{% endif %}{{ MFE_HOST }}")
Expand Down
6 changes: 5 additions & 1 deletion tutormfe/patches/openedx-lms-common-settings
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
# the frontend. (5 minutes, hardcoded)
ENABLE_MFE_CONFIG_API = True
MFE_CONFIG_API_CACHE_TIMEOUT = 1
{% if MFE_AUTHN_MFE_APP %}

# MFE-specific settings
{% for app_name, app in iter_mfes() %}
{% if app_name == "authn" %}
FEATURES['ENABLE_AUTHN_MICROFRONTEND'] = True
{% endif %}
{% endfor %}
77 changes: 35 additions & 42 deletions tutormfe/patches/openedx-lms-development-settings
Original file line number Diff line number Diff line change
@@ -1,40 +1,8 @@
{% if MFE_ACCOUNT_MFE_APP %}
ACCOUNT_MICROFRONTEND_URL = "http://{{ MFE_HOST }}:{{ MFE_ACCOUNT_MFE_APP["port"] }}/{{ MFE_ACCOUNT_MFE_APP["name"] }}"
{% endif %}
{% if MFE_GRADEBOOK_MFE_APP %}
WRITABLE_GRADEBOOK_URL = "http://{{ MFE_HOST }}:{{ MFE_GRADEBOOK_MFE_APP["port"] }}/{{ MFE_GRADEBOOK_MFE_APP["name"] }}"
{% endif %}
{% if MFE_LEARNING_MFE_APP %}
LEARNING_MICROFRONTEND_URL = "http://{{ MFE_HOST }}:{{ MFE_LEARNING_MFE_APP["port"] }}/{{ MFE_LEARNING_MFE_APP["name"] }}"
{% endif %}
{% if MFE_PROFILE_MFE_APP %}
PROFILE_MICROFRONTEND_URL = "{% if ENABLE_HTTPS %}https://{% else %}http://{% endif %}{{ MFE_HOST }}:{{ MFE_PROFILE_MFE_APP["port"] }}/{{ MFE_PROFILE_MFE_APP["name"] }}/u/"
{% endif %}
{% if MFE_AUTHN_MFE_APP %}
AUTHN_MICROFRONTEND_URL = "http://{{ MFE_HOST }}:{{ MFE_AUTHN_MFE_APP["port"] }}/{{ MFE_AUTHN_MFE_APP["name"] }}"
AUTHN_MICROFRONTEND_DOMAIN = "{{ MFE_HOST }}/{{ MFE_AUTHN_MFE_APP["name"] }}"
{% endif %}

{% if MFE_DISCUSSIONS_MFE_APP %}
DISCUSSIONS_MICROFRONTEND_URL = "http://{{ MFE_HOST }}:{{ MFE_DISCUSSIONS_MFE_APP["port"] }}/{{ MFE_DISCUSSIONS_MFE_APP["name"] }}"
DISCUSSIONS_MFE_FEEDBACK_URL = None
{% endif %}

{% for app in iter_values_named(suffix="MFE_APP") %}
# {{ app["name"] }} MFE
CORS_ORIGIN_WHITELIST.append("http://{{ MFE_HOST }}:{{ app["port"] }}")
LOGIN_REDIRECT_WHITELIST.append("{{ MFE_HOST }}:{{ app["port"] }}")
CSRF_TRUSTED_ORIGINS.append("{{ MFE_HOST }}:{{ app["port"] }}")
{% endfor %}

# Dynamic config API settings
# https://openedx.github.io/frontend-platform/module-Config.html
MFE_CONFIG = {
"BASE_URL": "{{ MFE_HOST }}",
"CSRF_TOKEN_API_PATH": "/csrf/api/v1/token",
{%- if MFE_PROFILE_MFE_APP %}
"ACCOUNT_PROFILE_URL": "http://{{ MFE_HOST }}:{{ MFE_PROFILE_MFE_APP["port"] }}",
{%- endif %}
"CREDENTIALS_BASE_URL": "",
"DISCOVERY_API_BASE_URL": "{% if DISCOVERY_HOST is defined %}http://{{ DISCOVERY_HOST }}:8381{% endif %}",
"FAVICON_URL": "http://{{ LMS_HOST }}/favicon.ico",
Expand All @@ -51,14 +19,39 @@ MFE_CONFIG = {
"STUDIO_BASE_URL": "http://{{ CMS_HOST }}:8001",
"USER_INFO_COOKIE_NAME": "user-info",
"ACCESS_TOKEN_COOKIE_NAME": "edx-jwt-cookie-header-payload",
{%- if MFE_LEARNING_MFE_APP %}
"LEARNING_BASE_URL": "http://{{ MFE_HOST }}:{{ MFE_LEARNING_MFE_APP["port"] }}",
{%- endif %}
{%- if MFE_COURSE_AUTHORING_MFE_APP %}
"ENABLE_NEW_EDITOR_PAGES": True,
"ENABLE_PROGRESS_GRAPH_SETTINGS": True,
{%- endif %}
{%- if MFE_AUTHN_MFE_APP %}
"DISABLE_ENTERPRISE_LOGIN": True,
{%- endif %}
}

# MFE-specific settings
{% for app_name, app in iter_mfes() %}
{% if app_name == "authn" %}
AUTHN_MICROFRONTEND_URL = "http://{{ MFE_HOST }}:{{ app["port"] }}/authn"
AUTHN_MICROFRONTEND_DOMAIN = "{{ MFE_HOST }}/authn"
MFE_CONFIG["DISABLE_ENTERPRISE_LOGIN"] = True
{% elif app_name == "account" %}
ACCOUNT_MICROFRONTEND_URL = "http://{{ MFE_HOST }}:{{ app["port"] }}/account"
{% elif app_name == "course-authoring" %}
MFE_CONFIG["ENABLE_NEW_EDITOR_PAGES"] = True
MFE_CONFIG["ENABLE_PROGRESS_GRAPH_SETTINGS"] = True
{% elif app_name == "discussions" %}
DISCUSSIONS_MICROFRONTEND_URL = "http://{{ MFE_HOST }}:{{ app["port"] }}/discussions"
DISCUSSIONS_MFE_FEEDBACK_URL = None
{% elif app_name == "gradebook" %}
WRITABLE_GRADEBOOK_URL = "http://{{ MFE_HOST }}:{{ app["port"] }}/gradebook"
{% elif app_name == "learning" %}
LEARNING_MICROFRONTEND_URL = "http://{{ MFE_HOST }}:{{ app["port"] }}/learning"
MFE_CONFIG["LEARNING_BASE_URL"] = "http://{{ MFE_HOST }}:{{ app["port"] }}"
{% elif app_name == "profile" %}
PROFILE_MICROFRONTEND_URL = "{% if ENABLE_HTTPS %}https://{% else %}http://{% endif %}{{ MFE_HOST }}:{{ app["port"] }}/profile/u/"
MFE_CONFIG["ACCOUNT_PROFILE_URL"] = "http://{{ MFE_HOST }}:{{ app["port"] }}"
{% endif %}
{% endfor %}

# Cors configuration
{% for app_name, app in iter_mfes() %}
# {{ app_name }} MFE
CORS_ORIGIN_WHITELIST.append("http://{{ MFE_HOST }}:{{ app["port"] }}")
LOGIN_REDIRECT_WHITELIST.append("{{ MFE_HOST }}:{{ app["port"] }}")
CSRF_TRUSTED_ORIGINS.append("{{ MFE_HOST }}:{{ app["port"] }}")
{% endfor %}


0 comments on commit a2eea86

Please sign in to comment.