Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v19.0.0 Upgrade to Sumac #227

Open
wants to merge 12 commits into
base: release
Choose a base branch
from
2 changes: 1 addition & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -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

Expand Down
12 changes: 10 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,20 @@ instructions, because git commits are used to generate release notes:

<!-- scriv-insert-here -->

<a id='changelog-18.1.0'></a>
## v18.1.0 (2024-12-10)
<a id='changelog-19.0.0'></a>
## 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)

<a id='changelog-18.1.0'></a>
## 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.
Expand Down
212 changes: 195 additions & 17 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://github.com/openedx/frontend-app-authn/>`__
- `Authoring <https://github.com/openedx/frontend-app-authoring/>`__
- `Account <https://github.com/openedx/frontend-app-account/>`__
- `Communications <https://github.com/openedx/frontend-app-communications/>`__
- `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/>`__
- `Learner Dashboard <https://github.com/openedx/frontend-app-learner-dashboard/>`__
Expand Down Expand Up @@ -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/release/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.openedx.io/account.

Authn
~~~~~

Expand All @@ -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/release/media/account.png
:alt: Account MFE screenshot
.. image:: https://raw.githubusercontent.com/overhangio/tutor-mfe/release/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 <https://github.com/edx/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
~~~~~~~~~~~~~~
Expand All @@ -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/release/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 <https://github.com/edx/edx-exams>`_ service is available.

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

Expand All @@ -85,7 +86,7 @@ Gradebook
.. image:: https://raw.githubusercontent.com/overhangio/tutor-mfe/release/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
~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -117,7 +118,7 @@ Profile
.. image:: https://raw.githubusercontent.com/overhangio/tutor-mfe/release/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
Expand Down Expand Up @@ -307,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: () => (
<h1>This is the footer.</h1>
),
},
}"""
)
])

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 <https://github.com/openedx/frontend-plugin-framework/#>`_ 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 ``<h1>`` 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: () => (
<h1>This is the footer.</h1>
),
},
}"""
)

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: () => (
<h1>This is the Profile MFE's footer.</h1>
),
},
}"""
)
])

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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -335,7 +461,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::

Expand Down Expand Up @@ -410,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 <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import>`_.

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 <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import>`_ 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 <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import#importing_defaults>`_.

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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
File renamed without changes
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion tutormfe/__about__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "18.1.0"
__version__ = "19.0.0"
2 changes: 2 additions & 0 deletions tutormfe/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
10 changes: 5 additions & 5 deletions tutormfe/patches/openedx-cms-development-settings
Original file line number Diff line number Diff line change
@@ -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 %}
4 changes: 2 additions & 2 deletions tutormfe/patches/openedx-cms-production-settings
Original file line number Diff line number Diff line change
@@ -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 }}")
Expand Down
5 changes: 3 additions & 2 deletions tutormfe/patches/openedx-lms-development-settings
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,13 @@ 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"
MFE_CONFIG["ENABLE_TAGGING_TAXONOMY_PAGES"] = "true"
MFE_CONFIG["MEILISEARCH_ENABLED"] = "true"
{% endif %}

{% if get_mfe("discussions") %}
Expand Down
5 changes: 3 additions & 2 deletions tutormfe/patches/openedx-lms-production-settings
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@ 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"
MFE_CONFIG["ENABLE_TAGGING_TAXONOMY_PAGES"] = "true"
MFE_CONFIG["MEILISEARCH_ENABLED"] = "true"
{% endif %}

{% if get_mfe("discussions") %}
Expand Down
Loading
Loading