From 6a00aac7834ca4dd6b5bdfc2b9b2e484a43b23d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Behmo?= Date: Fri, 20 Sep 2024 14:38:32 +0200 Subject: [PATCH 1/8] feat: rename "course-authoring" MFE to "authoring" Existing URLs are (301, permanently) redirected to the new ones. We are doing this because the course-authoring MFE has been migrated to a new repository. Close #224. --- .gitlab-ci.yml | 2 +- README.rst | 29 +++++++++--------- .../20240920_143739_regis_authoring_rename.md | 1 + media/{course-authoring.png => authoring.png} | Bin .../patches/openedx-cms-development-settings | 10 +++--- .../patches/openedx-cms-production-settings | 4 +-- .../patches/openedx-lms-development-settings | 4 +-- .../patches/openedx-lms-production-settings | 4 +-- tutormfe/plugin.py | 8 ++--- tutormfe/templates/mfe/apps/mfe/Caddyfile | 6 ++++ tutormfe/templates/mfe/build/mfe/Dockerfile | 2 +- tutormfe/templates/mfe/tasks/lms/init | 2 +- 12 files changed, 40 insertions(+), 32 deletions(-) create mode 100644 changelog.d/20240920_143739_regis_authoring_rename.md rename media/{course-authoring.png => authoring.png} (100%) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6ec2e55e..7742ae6b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,6 @@ variables: TUTOR_PLUGIN: mfe - TUTOR_IMAGES: mfe authn-dev account-dev communications-dev course-authoring-dev discussions-dev gradebook-dev learner-dashboard-dev learning-dev ora-grading-dev profile-dev + TUTOR_IMAGES: mfe account-dev authn-dev authoring-dev communications-dev discussions-dev gradebook-dev learner-dashboard-dev learning-dev ora-grading-dev profile-dev TUTOR_PYPI_PACKAGE: tutor-mfe GITHUB_REPO: overhangio/tutor-mfe diff --git a/README.rst b/README.rst index 240950ee..aace8302 100644 --- a/README.rst +++ b/README.rst @@ -6,9 +6,9 @@ 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 `__ +- `Authoring `__ - `Account `__ - `Communications `__ -- `Course Authoring `__ - `Discussions `__ - `Gradebook `__ - `Learner Dashboard `__ @@ -39,6 +39,14 @@ To check what the current value of `MFE_HOST` is actually set to, run:: tutor config printvalue MFE_HOST +Account +~~~~~~~ + +.. image:: https://raw.githubusercontent.com/overhangio/tutor-mfe/master/media/account.png + :alt: Account MFE screenshot + +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.edly.io/account. + Authn ~~~~~ @@ -47,13 +55,14 @@ Authn This is a micro-frontend application responsible for the login, registration and password reset functionality. -Account -~~~~~~~ +Authoring +~~~~~~~~~ -.. image:: https://raw.githubusercontent.com/overhangio/tutor-mfe/master/media/account.png - :alt: Account MFE screenshot +.. image:: https://raw.githubusercontent.com/overhangio/tutor-mfe/master/media/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 `_ service is available. -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.edly.io/account. Communications ~~~~~~~~~~~~~~ @@ -63,14 +72,6 @@ Communications The Communications micro-frontend exposes an interface for course teams to communicate with learners. It achieves this by allowing instructors to send out emails in bulk, either by scheduling them or on demand. -Course Authoring -~~~~~~~~~~~~~~~~ - -.. image:: https://raw.githubusercontent.com/overhangio/tutor-mfe/master/media/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 `_ service is available. - Discussions ~~~~~~~~~~~ diff --git a/changelog.d/20240920_143739_regis_authoring_rename.md b/changelog.d/20240920_143739_regis_authoring_rename.md new file mode 100644 index 00000000..377e5242 --- /dev/null +++ b/changelog.d/20240920_143739_regis_authoring_rename.md @@ -0,0 +1 @@ +- 💥[Feature] Rename course-authoring MFE to "authoring". Existing URLs are redirected for backward compatibility. (by @regisb) diff --git a/media/course-authoring.png b/media/authoring.png similarity index 100% rename from media/course-authoring.png rename to media/authoring.png diff --git a/tutormfe/patches/openedx-cms-development-settings b/tutormfe/patches/openedx-cms-development-settings index ec895df5..9b712402 100644 --- a/tutormfe/patches/openedx-cms-development-settings +++ b/tutormfe/patches/openedx-cms-development-settings @@ -1,7 +1,7 @@ # MFE-specific settings -{% if get_mfe("course-authoring") %} -COURSE_AUTHORING_MICROFRONTEND_URL = "http://{{ MFE_HOST }}:{{ get_mfe('course-authoring')["port"] }}/course-authoring" -CORS_ORIGIN_WHITELIST.append("http://{{ MFE_HOST }}:{{ get_mfe('course-authoring')["port"] }}") -LOGIN_REDIRECT_WHITELIST.append("{{ MFE_HOST }}:{{ get_mfe('course-authoring')["port"] }}") -CSRF_TRUSTED_ORIGINS.append("http://{{ MFE_HOST }}:{{ get_mfe('course-authoring')["port"] }}") +{% if get_mfe("authoring") %} +COURSE_AUTHORING_MICROFRONTEND_URL = "http://{{ MFE_HOST }}:{{ get_mfe('authoring')["port"] }}/authoring" +CORS_ORIGIN_WHITELIST.append("http://{{ MFE_HOST }}:{{ get_mfe('authoring')["port"] }}") +LOGIN_REDIRECT_WHITELIST.append("{{ MFE_HOST }}:{{ get_mfe('authoring')["port"] }}") +CSRF_TRUSTED_ORIGINS.append("http://{{ MFE_HOST }}:{{ get_mfe('authoring')["port"] }}") {% endif %} diff --git a/tutormfe/patches/openedx-cms-production-settings b/tutormfe/patches/openedx-cms-production-settings index b1b5e163..e3866092 100644 --- a/tutormfe/patches/openedx-cms-production-settings +++ b/tutormfe/patches/openedx-cms-production-settings @@ -1,6 +1,6 @@ # MFE-specific settings -{% if get_mfe("course-authoring") %} -COURSE_AUTHORING_MICROFRONTEND_URL = "{% if ENABLE_HTTPS %}https://{% else %}http://{% endif %}{{ MFE_HOST }}/course-authoring" +{% if get_mfe("authoring") %} +COURSE_AUTHORING_MICROFRONTEND_URL = "{% if ENABLE_HTTPS %}https://{% else %}http://{% endif %}{{ MFE_HOST }}/authoring" {% endif %} LOGIN_REDIRECT_WHITELIST.append("{{ MFE_HOST }}") diff --git a/tutormfe/patches/openedx-lms-development-settings b/tutormfe/patches/openedx-lms-development-settings index 79da7401..ce484dde 100644 --- a/tutormfe/patches/openedx-lms-development-settings +++ b/tutormfe/patches/openedx-lms-development-settings @@ -35,8 +35,8 @@ ACCOUNT_MICROFRONTEND_URL = "http://{{ MFE_HOST }}:{{ get_mfe("account")["port"] MFE_CONFIG["ACCOUNT_SETTINGS_URL"] = ACCOUNT_MICROFRONTEND_URL {% endif %} -{% if get_mfe("course-authoring") %} -MFE_CONFIG["COURSE_AUTHORING_MICROFRONTEND_URL"] = "http://{{ MFE_HOST }}:{{ get_mfe("course-authoring")["port"] }}/course-authoring" +{% if get_mfe("authoring") %} +MFE_CONFIG["COURSE_AUTHORING_MICROFRONTEND_URL"] = "http://{{ MFE_HOST }}:{{ get_mfe("authoring")["port"] }}/authoring" MFE_CONFIG["ENABLE_ASSETS_PAGE"] = "true" MFE_CONFIG["ENABLE_HOME_PAGE_COURSE_API_V2"] = "true" MFE_CONFIG["ENABLE_PROGRESS_GRAPH_SETTINGS"] = "true" diff --git a/tutormfe/patches/openedx-lms-production-settings b/tutormfe/patches/openedx-lms-production-settings index d7b7a42a..baaf6026 100644 --- a/tutormfe/patches/openedx-lms-production-settings +++ b/tutormfe/patches/openedx-lms-production-settings @@ -36,8 +36,8 @@ ACCOUNT_MICROFRONTEND_URL = "{% if ENABLE_HTTPS %}https://{% else %}http://{% en MFE_CONFIG["ACCOUNT_SETTINGS_URL"] = ACCOUNT_MICROFRONTEND_URL {% endif %} -{% if get_mfe("course-authoring") %} -MFE_CONFIG["COURSE_AUTHORING_MICROFRONTEND_URL"] = "{% if ENABLE_HTTPS %}https://{% else %}http://{% endif %}{{ MFE_HOST }}/course-authoring" +{% if get_mfe("authoring") %} +MFE_CONFIG["COURSE_AUTHORING_MICROFRONTEND_URL"] = "{% if ENABLE_HTTPS %}https://{% else %}http://{% endif %}{{ MFE_HOST }}/authoring" MFE_CONFIG["ENABLE_ASSETS_PAGE"] = "true" MFE_CONFIG["ENABLE_HOME_PAGE_COURSE_API_V2"] = "true" MFE_CONFIG["ENABLE_PROGRESS_GRAPH_SETTINGS"] = "true" diff --git a/tutormfe/plugin.py b/tutormfe/plugin.py index 9f8a6487..6cc34fc9 100644 --- a/tutormfe/plugin.py +++ b/tutormfe/plugin.py @@ -35,6 +35,10 @@ "repository": "https://github.com/openedx/frontend-app-authn.git", "port": 1999, }, + "authoring": { + "repository": "https://github.com/openedx/frontend-app-authoring.git", + "port": 2001, + }, "account": { "repository": "https://github.com/openedx/frontend-app-account.git", "port": 1997, @@ -43,10 +47,6 @@ "repository": "https://github.com/openedx/frontend-app-communications.git", "port": 1984, }, - "course-authoring": { - "repository": "https://github.com/openedx/frontend-app-course-authoring.git", - "port": 2001, - }, "discussions": { "repository": "https://github.com/openedx/frontend-app-discussions.git", "port": 2002, diff --git a/tutormfe/templates/mfe/apps/mfe/Caddyfile b/tutormfe/templates/mfe/apps/mfe/Caddyfile index 19081c99..115d57c5 100644 --- a/tutormfe/templates/mfe/apps/mfe/Caddyfile +++ b/tutormfe/templates/mfe/apps/mfe/Caddyfile @@ -20,6 +20,12 @@ header_up Host {{ LMS_HOST }} } + {% if is_mfe_enabled("authoring") %} + # redirect /course-authoring to /authoring, for backward compatibility + @authoring path_regexp authoring /course-authoring/(.*) + redir @authoring /authoring/{re.authoring.1} permanent + {% endif %} + {% for app_name, app in iter_mfes() %} @mfe_{{ app_name }} { path /{{ app_name }} /{{ app_name }}/* diff --git a/tutormfe/templates/mfe/build/mfe/Dockerfile b/tutormfe/templates/mfe/build/mfe/Dockerfile index ad89839c..29cc7efd 100644 --- a/tutormfe/templates/mfe/build/mfe/Dockerfile +++ b/tutormfe/templates/mfe/build/mfe/Dockerfile @@ -12,7 +12,7 @@ RUN apt update \ python g++ \ # required for image-webpack-loader (on arm) libpng-dev \ - # required for building node-canvas (on arm, for course-authoring) + # required for building node-canvas (on arm, for authoring) # https://www.npmjs.com/package/canvas libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev diff --git a/tutormfe/templates/mfe/tasks/lms/init b/tutormfe/templates/mfe/tasks/lms/init index 74cd7c70..76247c3b 100644 --- a/tutormfe/templates/mfe/tasks/lms/init +++ b/tutormfe/templates/mfe/tasks/lms/init @@ -35,7 +35,7 @@ grep courseware.always_open_auxiliary_sidebar /tmp/lms_waffle_flags.txt || ./man courseware.always_open_auxiliary_sidebar {% endif %} -{% if is_mfe_enabled("course-authoring") %} +{% if is_mfe_enabled("authoring") %} grep contentstore.new_studio_mfe.use_new_advanced_settings_page /tmp/lms_waffle_flags.txt || ./manage.py lms waffle_flag --create --everyone contentstore.new_studio_mfe.use_new_advanced_settings_page grep contentstore.new_studio_mfe.use_new_certificates_page /tmp/lms_waffle_flags.txt || ./manage.py lms waffle_flag --create --everyone contentstore.new_studio_mfe.use_new_certificates_page grep contentstore.new_studio_mfe.use_new_course_outline_page /tmp/lms_waffle_flags.txt || ./manage.py lms waffle_flag --create --everyone contentstore.new_studio_mfe.use_new_course_outline_page From eb2ecdae240d0b92fa91d9f3cce3644ea002a69f Mon Sep 17 00:00:00 2001 From: "Adolfo R. Brandes" Date: Thu, 10 Oct 2024 09:06:21 -0400 Subject: [PATCH 2/8] feat: upgrade to Node 20 --- changelog.d/20241010_090821_arbrandes_node_20_upgrade.md | 2 ++ tutormfe/templates/mfe/build/mfe/Dockerfile | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 changelog.d/20241010_090821_arbrandes_node_20_upgrade.md diff --git a/changelog.d/20241010_090821_arbrandes_node_20_upgrade.md b/changelog.d/20241010_090821_arbrandes_node_20_upgrade.md new file mode 100644 index 00000000..17ea5056 --- /dev/null +++ b/changelog.d/20241010_090821_arbrandes_node_20_upgrade.md @@ -0,0 +1,2 @@ + +- [Feature] Upgrade to Node 20. (by @arbrandes) diff --git a/tutormfe/templates/mfe/build/mfe/Dockerfile b/tutormfe/templates/mfe/build/mfe/Dockerfile index 29cc7efd..d68505c8 100644 --- a/tutormfe/templates/mfe/build/mfe/Dockerfile +++ b/tutormfe/templates/mfe/build/mfe/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 # https://hub.docker.com/_/node/tags -FROM docker.io/node:18.19.0-bullseye-slim AS base +FROM docker.io/node:20.18.0-bullseye-slim AS base RUN apt update \ && apt install -y git \ From cd0b60bd2b68f06f1bf3c957942dd4e5e3788efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Behmo?= Date: Thu, 17 Oct 2024 08:35:13 +0200 Subject: [PATCH 3/8] docs: *.local.edly.io -> *.local.openedx.io The default URL to run a local platform switched from local.edly.io to local.openedx.io. This changes makes it clearer for everyone that Tutor is to run Open edX. See: https://github.com/overhangio/tutor/issues/1120 --- README.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index aace8302..18bb1c04 100644 --- a/README.rst +++ b/README.rst @@ -45,7 +45,7 @@ Account .. image:: https://raw.githubusercontent.com/overhangio/tutor-mfe/master/media/account.png :alt: Account MFE screenshot -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.edly.io/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.openedx.io/account. Authn ~~~~~ @@ -86,7 +86,7 @@ Gradebook .. image:: https://raw.githubusercontent.com/overhangio/tutor-mfe/master/media/gradebook.png :alt: Gradebook MFE screenshot -This instructor-only MFE is for viewing individual and aggregated grade results for a course. To access this MFE, go to a course → Instructor tab → Student Admin → View gradebook. The URL should be: ``http(s)://{{ MFE_HOST }}/gradebook/{{ course ID }}``. When running locally, the gradebook of the demo course is available at: http://apps.local.edly.io/gradebook/course-v1:edX+DemoX+Demo_Course +This instructor-only MFE is for viewing individual and aggregated grade results for a course. To access this MFE, go to a course → Instructor tab → Student Admin → View gradebook. The URL should be: ``http(s)://{{ MFE_HOST }}/gradebook/{{ course ID }}``. When running locally, the gradebook of the demo course is available at: http://apps.local.openedx.io/gradebook/course-v1:edX+DemoX+Demo_Course Learner Dashboard ~~~~~~~~~~~~~~~~~ @@ -118,7 +118,7 @@ Profile .. image:: https://raw.githubusercontent.com/overhangio/tutor-mfe/master/media/profile.png :alt: Profile MFE screenshot -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.edly.io/profile/u/admin. +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.openedx.io/profile/u/admin. MFE management @@ -336,7 +336,7 @@ Tutor makes it possible to run any MFE in development mode. For instance, to run tutor dev start profile -Then, access http://apps.local.edly.io:1995/profile/u/YOURUSERNAME +Then, access http://apps.local.openedx.io:1995/profile/u/YOURUSERNAME You can also bind-mount your own fork of an MFE. For example:: From 00cb27730d15c47df13c3c1307fddfaaeeeb03a4 Mon Sep 17 00:00:00 2001 From: "Adolfo R. Brandes" Date: Tue, 10 Dec 2024 03:24:10 -0300 Subject: [PATCH 4/8] feat: support frontend plugins via env.config.jsx (#240) This provides a mechanism to configure frontend plugins using a new PLUGIN_SLOTS filter and a series of patches that modify a base `env.config.jsx`, in such a way that multiple plugins can take advantage of the file (including for purposes beyond frontend plugins) without clobbering each other. --- README.rst | 177 ++++++++++++++++++ ...72451_arbrandes_frontend_plugin_support.md | 1 + tutormfe/hooks.py | 2 + tutormfe/plugin.py | 24 ++- tutormfe/templates/mfe/build/mfe/Dockerfile | 1 + .../templates/mfe/build/mfe/env.config.jsx | 51 +++++ 6 files changed, 249 insertions(+), 7 deletions(-) create mode 100644 changelog.d/20241111_172451_arbrandes_frontend_plugin_support.md create mode 100644 tutormfe/templates/mfe/build/mfe/env.config.jsx diff --git a/README.rst b/README.rst index 16c56eb0..a9222e49 100644 --- a/README.rst +++ b/README.rst @@ -308,6 +308,131 @@ In case you need to run additional instructions just before the build step you c You can find more patches in the `patch catalog <#template-patch-catalog>`_ below. +Using Frontend Plugin Slots +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It's possible to take advantage of this plugin's hooks to configure frontend plugin slots. Let's say you want to replace the entire footer with a simple message. Where before you might have had to fork ``frontend-component-footer``, the following is all that's currently needed: + +.. code-block:: python + + from tutormfe.hooks import PLUGIN_SLOTS + + PLUGIN_SLOTS.add_items([ + # Hide the default footer + ( + "all", + "footer_slot", + """ + { + op: PLUGIN_OPERATIONS.Hide, + widgetId: 'default_contents', + }""" + ), + # Insert a custom footer + ( + "all", + "footer_slot", + """ + { + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'custom_footer', + type: DIRECT_PLUGIN, + RenderWidget: () => ( +

This is the footer.

+ ), + }, + }""" + ) + ]) + +Let's take a closer look at what's happening here. To begin with, we're using tutormfe's own ``PLUGIN_SLOTS`` filter. It's a regular Tutor filter, but you won't find it in the main ``tutor`` package: + +.. code-block:: python + + from tutormfe.hooks import PLUGIN_SLOTS + +Next up, we're adding actual slot configuration, starting by hiding the default footer. The first parameter in a filter item specifies which MFE to apply the slot configuration to; for example: ``"learner-dashboard"``, or ``"learning"``. We're using ``"all"`` here, which is a special case: it means the slot configuration should be applied to all MFEs that actually have that slot. (If a particular MFE doesn't have the slot, it will just ignore its configuration.) + +The second parameter, ``"footer_slot"``, is the name of the slot as defined in the code of the MFE itself. + +.. code-block:: python + + PLUGIN_SLOTS.add_items([ + # Hide the default footer + ( + "all", + "footer_slot", + """ + { + op: PLUGIN_OPERATIONS.Hide, + widgetId: 'default_contents', + }""" + ), + +The last parameter to ``add_item()`` is a big string with the actual slot configuration, which will be interpreted as JSX. What we're doing there is hiding the default contents of the footer with a ``PLUGIN_OPERATIONS.Hide``. (You can refer to the `frontend-plugin-framework README `_ for a full description of the possible plugin types and operations.) And the ``default_contents`` widget ID we're targetting always refers to what's in an unconfigured slot by default. + +In the second filter item, we once again target the ``"footer_slot"`` on ``"all"`` MFEs. This time, we use ``PLUGIN_OPERATIONS.Insert`` to add our custom JSX component, comprised of a simple ``

`` message we're defining in an anonymous function. We give it a widgetID of ``custom_footer``: + +.. code-block:: python + + # Insert a custom footer + ( + "all", + "footer_slot", + """ + { + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'custom_footer', + type: DIRECT_PLUGIN, + RenderWidget: () => ( +

This is the footer.

+ ), + }, + }""" + ) + +That's it! If you rebuild the ``mfe`` image after enabling the plugin (via ``tutor images build mfe`` or ``tutor local launch``), "This is the footer." should appear at the bottom of every MFE. + +It's also possible to target a specific MFE's footer. For instance: + +.. code-block:: python + + PLUGIN_SLOTS.add_items([ + # Hide the custom footer + ( + "profile", + "footer_slot", + """ + { + op: PLUGIN_OPERATIONS.Hide, + widgetId: 'custom_footer', + }""" + ), + # Insert a footer just for the Profile MFE + ( + "profile", + "footer_slot", + """ + { + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'custom_profile_footer', + type: DIRECT_PLUGIN, + RenderWidget: () => ( +

This is the Profile MFE's footer.

+ ), + }, + }""" + ) + ]) + +Note that here we're assuming you didn't remove the global footer configuration defined by the filter items targeting ``"all"``, so you have to hide ``custom_footer`` instead of ``default_contents``. If you were to rebuild the MFE image now, the Profile MFE's footer would say "This is the Profile MFE's footer", whereas all the others would still contain the global "This is the footer." message. + +For more complex frontend plugins, you should make use of ``mfe-env-config-*`` patches to define your JSX components separately. For instance, you could create an NPM plugin package, install it via ``mfe-dockerfile-post-npm-install``, import the desired components via ``mfe-env-config-buildtime-imports``, then refer to them with the ``PLUGIN_SLOTS`` filter as described above. Refer to the `patch catalog <#template-patch-catalog>`_ below for more details. + + Installing from a private npm registry ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -411,6 +536,58 @@ This is the list of all patches used across tutor-mfe (outside of any plugin). A cd tutor-mfe git grep "{{ patch" -- tutormfe/templates +mfe-env-config-buildtime-imports +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use this patch for any static imports you need in ``env.config.jsx``. They will be available here if you used the `mfe-docker-post-npm-install patch <#mfe-docker-post-npm-install>`_ to install an NPM package for all MFEs. + +It gets rendered at the very top of the file. You should use normal `ES6 import syntax `_. + +Note that if you want to only import a module for a particular MFE, doing it here won't work: you'll probably want to use the ``mfe-env-config-runtime-definitions-{}`` patch described below. + +File changed: ``tutormfe/templates/mfe/build/mfe/env.config.jsx`` + +mfe-env-config-buildtime-definitions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use this patch for arbitrary ``env.config.jsx`` javascript code that gets evaluated at build time. It is particularly useful for defining slightly more complex components for use in plugin slots. + +There's no version of this patch that runs per MFE. If you want to define MFE-specific code, you should use the MFE-specific ``mfe-env-config-runtime-definitions-{}`` to achieve the same effect. + +File changed: ``tutormfe/templates/mfe/build/mfe/env.config.jsx`` + +mfe-env-config-runtime-definitions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This patch gets rendered inside an ``async`` function in ``env.config.jsx`` that runs in the browser, allowing you to define conditional imports for external modules that may only be available at runtime. Just make sure to use `import() function `_ syntax: + +.. code-block:: javascript + + const mymodule1 = await import('mymodule1'); + const { default: myComponent } = await import('mymodule2'); + +Note the second line in the example above: default module exports work a little differently with ``import()``. To use the default export you can destructure the imported module, but you have to explicitly rename the ``default`` key, as `documented in MDN `_. + +Warning: if the dynamic import of a module fails for whatever reason, ``env.config.jsx`` execution will fail silently. + +File changed: ``tutormfe/templates/mfe/build/mfe/env.config.jsx`` + +mfe-env-config-runtime-definitions-{} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +With this patch you can conditionally import modules or define code for specific MFEs in ``env.config.jsx``. This is a useful place to put an import if you're using the ``mfe-docker-post-npm-install-*`` patch to install a plugin that only works on a particular MFE. + +As above, make sure to use the ``import()`` function. + +File changed: ``tutormfe/templates/mfe/build/mfe/env.config.jsx`` + +mfe-env-config-runtime-final +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +At this point, ``env.config.jsx`` is ready to return the ``config`` object to the initialization code at runtime. You can use this patch to do anything to the object, including using modules that were imported dynamically earlier. + +File changed: ``tutormfe/templates/mfe/build/mfe/env.config.jsx`` + mfe-lms-development-settings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/changelog.d/20241111_172451_arbrandes_frontend_plugin_support.md b/changelog.d/20241111_172451_arbrandes_frontend_plugin_support.md new file mode 100644 index 00000000..4248ed29 --- /dev/null +++ b/changelog.d/20241111_172451_arbrandes_frontend_plugin_support.md @@ -0,0 +1 @@ +- [Improvement] Adds support for frontend plugin slot configuration via env.config.jsx. (by @arbrandes) diff --git a/tutormfe/hooks.py b/tutormfe/hooks.py index e23d461d..d88c7b44 100644 --- a/tutormfe/hooks.py +++ b/tutormfe/hooks.py @@ -13,3 +13,5 @@ MFE_ATTRS_TYPE = t.Dict[t.Literal["repository", "port", "version"], t.Union["str", int]] MFE_APPS: Filter[dict[str, MFE_ATTRS_TYPE], []] = Filter() + +PLUGIN_SLOTS: Filter[list[tuple[str, str, str]], []] = Filter() diff --git a/tutormfe/plugin.py b/tutormfe/plugin.py index 81153235..a3b8c8ad 100644 --- a/tutormfe/plugin.py +++ b/tutormfe/plugin.py @@ -13,7 +13,7 @@ from tutor.types import Config, get_typed from .__about__ import __version__ -from .hooks import MFE_APPS, MFE_ATTRS_TYPE +from .hooks import MFE_APPS, MFE_ATTRS_TYPE, PLUGIN_SLOTS # Handle version suffix in main mode, just like tutor core if __version_suffix__: @@ -82,7 +82,7 @@ def _add_core_mfe_apps(apps: dict[str, MFE_ATTRS_TYPE]) -> dict[str, MFE_ATTRS_T return apps -@functools.lru_cache(maxsize=None) +@tutor_hooks.lru_cache def get_mfes() -> dict[str, MFE_ATTRS_TYPE]: """ This function is cached for performance. @@ -90,12 +90,12 @@ def get_mfes() -> dict[str, MFE_ATTRS_TYPE]: return MFE_APPS.apply({}) -@tutor_hooks.Actions.PLUGIN_LOADED.add() -def _clear_get_mfes_cache(_name: str) -> None: +@tutor_hooks.lru_cache +def get_plugin_slots(mfe_name: str) -> list[tuple[str, str]]: """ - Don't forget to clear cache, or we'll have some strange surprises... + This function is cached for performance. """ - get_mfes.cache_clear() + return [i[-2:] for i in PLUGIN_SLOTS.iterate() if i[0] == mfe_name] def iter_mfes() -> t.Iterable[tuple[str, MFE_ATTRS_TYPE]]: @@ -107,11 +107,20 @@ def iter_mfes() -> t.Iterable[tuple[str, MFE_ATTRS_TYPE]]: yield from get_mfes().items() +def iter_plugin_slots(mfe_name: str) -> t.Iterable[tuple[str, str]]: + """ + Yield: + + (slot_name, plugin_config) + """ + yield from get_plugin_slots(mfe_name) + + def is_mfe_enabled(mfe_name: str) -> bool: return mfe_name in get_mfes() -def get_mfe(mfe_name: str) -> MFE_ATTRS_TYPE: +def get_mfe(mfe_name: str) -> t.Union[MFE_ATTRS_TYPE, t.Any]: return get_mfes().get(mfe_name, {}) @@ -120,6 +129,7 @@ def get_mfe(mfe_name: str) -> MFE_ATTRS_TYPE: [ ("get_mfe", get_mfe), ("iter_mfes", iter_mfes), + ("iter_plugin_slots", iter_plugin_slots), ("is_mfe_enabled", is_mfe_enabled), ] ) diff --git a/tutormfe/templates/mfe/build/mfe/Dockerfile b/tutormfe/templates/mfe/build/mfe/Dockerfile index d68505c8..680e1804 100644 --- a/tutormfe/templates/mfe/build/mfe/Dockerfile +++ b/tutormfe/templates/mfe/build/mfe/Dockerfile @@ -62,6 +62,7 @@ ENV PUBLIC_PATH='/{{ app_name }}/' # So we point to a relative url that will be a proxy for the LMS. ENV MFE_CONFIG_API_URL=/api/mfe_config/v1 ARG ENABLE_NEW_RELIC=false +COPY env.config.jsx /openedx/app {{ patch("mfe-dockerfile-pre-npm-build") }} {{ patch("mfe-dockerfile-pre-npm-build-{}".format(app_name)) }} diff --git a/tutormfe/templates/mfe/build/mfe/env.config.jsx b/tutormfe/templates/mfe/build/mfe/env.config.jsx new file mode 100644 index 00000000..e7d84194 --- /dev/null +++ b/tutormfe/templates/mfe/build/mfe/env.config.jsx @@ -0,0 +1,51 @@ +{{- patch("mfe-env-config-buildtime-imports") }} + +function addPlugins(config, slot_name, plugins) { + if (slot_name in config.pluginSlots === false) { + config.pluginSlots[slot_name] = { + keepDefault: true, + plugins: [] + }; + } + + config.pluginSlots[slot_name].plugins.push(...plugins); +} + +{{- patch("mfe-env-config-buildtime-definitions") }} + +async function setConfig () { + let config = { + pluginSlots: {} + }; + + try { + /* We can't assume FPF exists, as it's not declared as a dependency in all + * MFEs, so we import it dynamically. In addition, for dynamic imports to + * work with Webpack all of the code that actually uses the imported module + * needs to be inside the `try{}` block. + */ + const { DIRECT_PLUGIN, PLUGIN_OPERATIONS } = await import('@openedx/frontend-plugin-framework'); + + {{- patch("mfe-env-config-runtime-definitions") }} + + {%- for slot_name, plugin_config in iter_plugin_slots("all") %} + addPlugins(config, '{{ slot_name }}', [{{ plugin_config }}]); + {%- endfor %} + + {%- for app_name, _ in iter_mfes() %} + if (process.env.APP_ID == '{{ app_name }}') { + {{- patch("mfe-env-config-runtime-definitions-{}".format(app_name)) }} + + {%- for slot_name, plugin_config in iter_plugin_slots(app_name) %} + addPlugins(config, '{{ slot_name }}', [{{ plugin_config }}]); + {%- endfor %} + } + {%- endfor %} + + {{- patch("mfe-env-config-runtime-final") }} + } catch { } + + return config; +} + +export default setConfig; From 8ff8357969b8ff88b4e2e457d9ede0c8d62b891b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Behmo?= Date: Tue, 10 Dec 2024 11:38:39 +0100 Subject: [PATCH 5/8] v18.1.0 --- CHANGELOG.md | 11 +++++++++++ changelog.d/20240920_143739_regis_authoring_rename.md | 1 - .../20241010_090821_arbrandes_node_20_upgrade.md | 2 -- ...241111_172451_arbrandes_frontend_plugin_support.md | 1 - changelog.d/20241113_224236_hina.khadim.md | 1 - .../20241119_121705_dawoud.sheraz_branch_rename.md | 3 --- tutormfe/__about__.py | 2 +- 7 files changed, 12 insertions(+), 9 deletions(-) delete mode 100644 changelog.d/20240920_143739_regis_authoring_rename.md delete mode 100644 changelog.d/20241010_090821_arbrandes_node_20_upgrade.md delete mode 100644 changelog.d/20241111_172451_arbrandes_frontend_plugin_support.md delete mode 100644 changelog.d/20241113_224236_hina.khadim.md delete mode 100644 changelog.d/20241119_121705_dawoud.sheraz_branch_rename.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 43744d8c..2d03b645 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,17 @@ instructions, because git commits are used to generate release notes: + +## v18.1.0 (2024-12-10) + +- 💥[Feature] Rename course-authoring MFE to "authoring". Existing URLs are redirected for backward compatibility. (by @regisb) +- [Feature] Upgrade to Node 20. (by @arbrandes) +- [Improvement] Adds support for frontend plugin slot configuration via env.config.jsx. (by @arbrandes) +- 💥 [Deprecation] Drop support for python 3.8 and set Python 3.9 as the minimum supported python version. (by @hinakhadim) +- 💥[Improvement] Rename Tutor's two branches (by @DawoudSheraz): + * Rename **master** to **release**, as this branch runs the latest official Open edX release tag. + * Rename **nightly** to **main**, as this branch runs the Open edX master branches, which are the basis for the next Open edX release. + ## v18.0.2 (2024-10-29) diff --git a/changelog.d/20240920_143739_regis_authoring_rename.md b/changelog.d/20240920_143739_regis_authoring_rename.md deleted file mode 100644 index 377e5242..00000000 --- a/changelog.d/20240920_143739_regis_authoring_rename.md +++ /dev/null @@ -1 +0,0 @@ -- 💥[Feature] Rename course-authoring MFE to "authoring". Existing URLs are redirected for backward compatibility. (by @regisb) diff --git a/changelog.d/20241010_090821_arbrandes_node_20_upgrade.md b/changelog.d/20241010_090821_arbrandes_node_20_upgrade.md deleted file mode 100644 index 17ea5056..00000000 --- a/changelog.d/20241010_090821_arbrandes_node_20_upgrade.md +++ /dev/null @@ -1,2 +0,0 @@ - -- [Feature] Upgrade to Node 20. (by @arbrandes) diff --git a/changelog.d/20241111_172451_arbrandes_frontend_plugin_support.md b/changelog.d/20241111_172451_arbrandes_frontend_plugin_support.md deleted file mode 100644 index 4248ed29..00000000 --- a/changelog.d/20241111_172451_arbrandes_frontend_plugin_support.md +++ /dev/null @@ -1 +0,0 @@ -- [Improvement] Adds support for frontend plugin slot configuration via env.config.jsx. (by @arbrandes) diff --git a/changelog.d/20241113_224236_hina.khadim.md b/changelog.d/20241113_224236_hina.khadim.md deleted file mode 100644 index 24bce687..00000000 --- a/changelog.d/20241113_224236_hina.khadim.md +++ /dev/null @@ -1 +0,0 @@ -- 💥 [Deprecation] Drop support for python 3.8 and set Python 3.9 as the minimum supported python version. (by @hinakhadim) \ No newline at end of file diff --git a/changelog.d/20241119_121705_dawoud.sheraz_branch_rename.md b/changelog.d/20241119_121705_dawoud.sheraz_branch_rename.md deleted file mode 100644 index fb6e264c..00000000 --- a/changelog.d/20241119_121705_dawoud.sheraz_branch_rename.md +++ /dev/null @@ -1,3 +0,0 @@ -- 💥[Improvement] Rename Tutor's two branches (by @DawoudSheraz): - * Rename **master** to **release**, as this branch runs the latest official Open edX release tag. - * Rename **nightly** to **main**, as this branch runs the Open edX master branches, which are the basis for the next Open edX release. \ No newline at end of file diff --git a/tutormfe/__about__.py b/tutormfe/__about__.py index 93eb58ca..c84c510d 100644 --- a/tutormfe/__about__.py +++ b/tutormfe/__about__.py @@ -1 +1 @@ -__version__ = "18.0.2" +__version__ = "18.1.0" From efa43e76b5c626c660e8ddf81f5908919e6aa295 Mon Sep 17 00:00:00 2001 From: hinakhadim Date: Mon, 21 Oct 2024 14:43:29 +0500 Subject: [PATCH 6/8] v19.0.0 Upgrade to Sumac --- CHANGELOG.md | 12 ++++++++++-- setup.py | 4 ++-- tutormfe/__about__.py | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d03b645..46e5e06c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,12 +19,20 @@ instructions, because git commits are used to generate release notes: - -## v18.1.0 (2024-12-10) + +## v19.0.0 (2024-10-30) - 💥[Feature] Rename course-authoring MFE to "authoring". Existing URLs are redirected for backward compatibility. (by @regisb) + - [Feature] Upgrade to Node 20. (by @arbrandes) + +- 💥[Feature] Upgrade to Sumac (by @hinakhadim) + - [Improvement] Adds support for frontend plugin slot configuration via env.config.jsx. (by @arbrandes) + + +## v18.1.0 (2024-12-10) + - 💥 [Deprecation] Drop support for python 3.8 and set Python 3.9 as the minimum supported python version. (by @hinakhadim) - 💥[Improvement] Rename Tutor's two branches (by @DawoudSheraz): * Rename **master** to **release**, as this branch runs the latest official Open edX release tag. diff --git a/setup.py b/setup.py index 66240b7a..b542d77e 100644 --- a/setup.py +++ b/setup.py @@ -40,8 +40,8 @@ def load_about(): packages=find_packages(exclude=["tests*"]), include_package_data=True, python_requires=">=3.9", - install_requires=["tutor>=18.0.0,<19.0.0"], - extras_require={"dev": ["tutor[dev]>=18.0.0,<19.0.0"]}, + install_requires=["tutor>=19.0.0,<20.0.0"], + extras_require={"dev": ["tutor[dev]>=19.0.0,<20.0.0"]}, entry_points={"tutor.plugin.v1": ["mfe = tutormfe.plugin"]}, classifiers=[ "Development Status :: 5 - Production/Stable", diff --git a/tutormfe/__about__.py b/tutormfe/__about__.py index c84c510d..0122a6fa 100644 --- a/tutormfe/__about__.py +++ b/tutormfe/__about__.py @@ -1 +1 @@ -__version__ = "18.1.0" +__version__ = "19.0.0" From 4c95da52c162d68456a987e67cc17e10e8545b5a Mon Sep 17 00:00:00 2001 From: Jillian Date: Sat, 2 Nov 2024 02:55:38 +1030 Subject: [PATCH 7/8] chore: enable meilisearch in MFE config (#231) --- tutormfe/patches/openedx-lms-development-settings | 1 + tutormfe/patches/openedx-lms-production-settings | 1 + 2 files changed, 2 insertions(+) diff --git a/tutormfe/patches/openedx-lms-development-settings b/tutormfe/patches/openedx-lms-development-settings index ce484dde..280039ee 100644 --- a/tutormfe/patches/openedx-lms-development-settings +++ b/tutormfe/patches/openedx-lms-development-settings @@ -41,6 +41,7 @@ MFE_CONFIG["ENABLE_ASSETS_PAGE"] = "true" MFE_CONFIG["ENABLE_HOME_PAGE_COURSE_API_V2"] = "true" MFE_CONFIG["ENABLE_PROGRESS_GRAPH_SETTINGS"] = "true" MFE_CONFIG["ENABLE_TAGGING_TAXONOMY_PAGES"] = "true" +MFE_CONFIG["MEILISEARCH_ENABLED"] = "true" {% endif %} {% if get_mfe("discussions") %} diff --git a/tutormfe/patches/openedx-lms-production-settings b/tutormfe/patches/openedx-lms-production-settings index baaf6026..c97d9686 100644 --- a/tutormfe/patches/openedx-lms-production-settings +++ b/tutormfe/patches/openedx-lms-production-settings @@ -42,6 +42,7 @@ MFE_CONFIG["ENABLE_ASSETS_PAGE"] = "true" MFE_CONFIG["ENABLE_HOME_PAGE_COURSE_API_V2"] = "true" MFE_CONFIG["ENABLE_PROGRESS_GRAPH_SETTINGS"] = "true" MFE_CONFIG["ENABLE_TAGGING_TAXONOMY_PAGES"] = "true" +MFE_CONFIG["MEILISEARCH_ENABLED"] = "true" {% endif %} {% if get_mfe("discussions") %} From f51e3cc2a54ce3e728640e5e9f52a2235d55182f Mon Sep 17 00:00:00 2001 From: "Adolfo R. Brandes" Date: Mon, 18 Nov 2024 15:58:09 -0300 Subject: [PATCH 8/8] fix: the navigation sidebar should be enabled by default --- tutormfe/templates/mfe/tasks/lms/init | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutormfe/templates/mfe/tasks/lms/init b/tutormfe/templates/mfe/tasks/lms/init index 76247c3b..45f69a37 100644 --- a/tutormfe/templates/mfe/tasks/lms/init +++ b/tutormfe/templates/mfe/tasks/lms/init @@ -26,7 +26,7 @@ grep learner_home_mfe.enabled /tmp/lms_waffle_flags.txt || ./manage.py lms waffl {% if is_mfe_enabled("learning") %} grep course_home.course_home_mfe_progress_tab /tmp/lms_waffle_flags.txt || ./manage.py lms waffle_flag --create --everyone course_home.course_home_mfe_progress_tab -grep courseware.enable_navigation_sidebar /tmp/lms_waffle_flags.txt || ./manage.py lms waffle_flag --create --deactivate courseware.enable_navigation_sidebar +grep courseware.enable_navigation_sidebar /tmp/lms_waffle_flags.txt || ./manage.py lms waffle_flag --create --everyone courseware.enable_navigation_sidebar grep courseware.always_open_auxiliary_sidebar /tmp/lms_waffle_flags.txt || ./manage.py lms waffle_flag --create --deactivate courseware.always_open_auxiliary_sidebar {% else %} ./manage.py lms waffle_delete --flags \