diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ff0e970..2fb8d9df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,13 @@ instructions, because git commits are used to generate release notes: + +## v18.0.0 (2024-06-19) + +- 💥[Feature] Upgrade to Redwood (by @hinakhadim) +- [Feature] Enable `atlas pull` on all Micro-frontends. (by @omarithawi) +- 💥[Feature] Use `ATLAS_OPTIONS` for `atlas pull`. This breaks the `i18n-merge.js` custom locale Tutor MFE feature in favor of [OEP-58](https://docs.openedx.org/en/latest/developers/concepts/oep58.html) `atlas pull` options. (by @omarithawi) + ## v17.0.1 (2024-03-26) diff --git a/README.rst b/README.rst index 6ead5527..0a1a9c51 100644 --- a/README.rst +++ b/README.rst @@ -130,6 +130,7 @@ Adding new MFEs - 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 below. - As of Tutor v17 (Quince release) you must make sure that the git URL of your MFE repository ends with ``.git``. Otherwise the plugin build will fail. +- As of Tutor v18 (Redwood release) all MFEs must provide a ``make pull_translations`` command. Otherwise the plugin build will fail. Providing an empty command is enough to bypass this requirement. See the `Custom translations section <#mfe-custom-translations>`_ for more information. 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.hooks.MFE_APPS`` filter. This configuration should include the name, git repository (and optionally: git branch or tag) and development port. For example: @@ -163,32 +164,26 @@ To disable an existing MFE, remove the corresponding entry from the ``MFE_APPS`` mfes.pop("profile") return mfes -Adding custom translations to your MFEs -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This plugin makes it possible to change existing and add new translation strings to MFEs. Here is how to do it: - -1. Identify the ID of the string you would like to translate. For instance, the ID of the "Account Information" string in the account MFE is "account.settings.section.account.information" (see `source `__). -2. Create a folder and i18n file corresponding to your MFE app and language in the Tutor root. This location of this file should be ``/path/to/tutor/env/plugins/mfe/build/mfe/i18n//.json``. For instance, to add French ("fr") translation strings to the account MFE, run:: - - cd "$(tutor config printroot)/env/plugins/mfe/build/mfe/i18n/" - mkdir account - touch account/fr.json +Using custom translations to your MFEs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -3. Add your entries to this file in JSON format, where the key is the string ID and the value is the actual string. For instance: +.. _mfe-custom-translations: -.. code-block::json +During docker image build, this plugin runs ``make pull_translations`` for each Micro-frontend. This +program is used in the ``Dockerfile`` to pull translations from the `openedx/openedx-translations repository `_ via `openedx-atlas `_. - { - "account.settings.section.account.information": "Information du compte" - } +The ``make pull_translations`` command passes the ``ATLAS_OPTIONS`` environment variable to the ``atlas pull`` command. This allows specifying a custom repository or branch to pull translations from. -4. Rebuild the MFE image and restart the MFE with:: +Translations in the MFE plugin as well as other Tutor plugins can be customized with the following configuration +variables: - tutor images build mfe - tutor local start -d +- ``ATLAS_REVISION`` (default: ``"main"`` on nightly and ``"{{ OPENEDX_COMMON_VERSION }}"`` if a named release is used) +- ``ATLAS_REPOSITORY`` (default: ``"openedx/openedx-translations"``). +- ``ATLAS_OPTIONS`` (default: ``""``) Pass additional arguments to ``atlas pull``. Refer to the `atlas documentations `_ for more information. -Your custom translation strings should now appear in your app. +The +`Getting and customizing Translations `_ +section in the Tutor configuration documentation explains how to do this. Customising MFEs ~~~~~~~~~~~~~~~~ diff --git a/setup.py b/setup.py index 1234f35c..6e427edd 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.8", - install_requires=["tutor>=17.0.0,<18.0.0"], - extras_require={"dev": ["tutor[dev]>=17.0.0,<18.0.0"]}, + install_requires=["tutor>=18.0.0,<19.0.0"], + extras_require={"dev": ["tutor[dev]>=18.0.0,<19.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 dba3a77b..c6a8b8ed 100644 --- a/tutormfe/__about__.py +++ b/tutormfe/__about__.py @@ -1 +1 @@ -__version__ = "17.0.1" +__version__ = "18.0.0" diff --git a/tutormfe/hooks.py b/tutormfe/hooks.py index e62c2eb2..e23d461d 100644 --- a/tutormfe/hooks.py +++ b/tutormfe/hooks.py @@ -3,6 +3,7 @@ the tutor-mfe hooks would be created in the context of some other plugin that imports them. """ + from __future__ import annotations import typing as t diff --git a/tutormfe/patches/openedx-lms-development-settings b/tutormfe/patches/openedx-lms-development-settings index 2fcc22e8..79da7401 100644 --- a/tutormfe/patches/openedx-lms-development-settings +++ b/tutormfe/patches/openedx-lms-development-settings @@ -36,9 +36,11 @@ MFE_CONFIG["ACCOUNT_SETTINGS_URL"] = ACCOUNT_MICROFRONTEND_URL {% endif %} {% if get_mfe("course-authoring") %} -MFE_CONFIG["ENABLE_NEW_EDITOR_PAGES"] = True -MFE_CONFIG["ENABLE_PROGRESS_GRAPH_SETTINGS"] = True MFE_CONFIG["COURSE_AUTHORING_MICROFRONTEND_URL"] = "http://{{ MFE_HOST }}:{{ get_mfe("course-authoring")["port"] }}/course-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" {% endif %} {% if get_mfe("discussions") %} diff --git a/tutormfe/patches/openedx-lms-production-settings b/tutormfe/patches/openedx-lms-production-settings index 37b79471..d7b7a42a 100644 --- a/tutormfe/patches/openedx-lms-production-settings +++ b/tutormfe/patches/openedx-lms-production-settings @@ -37,9 +37,11 @@ MFE_CONFIG["ACCOUNT_SETTINGS_URL"] = ACCOUNT_MICROFRONTEND_URL {% endif %} {% if get_mfe("course-authoring") %} -MFE_CONFIG["ENABLE_NEW_EDITOR_PAGES"] = True -MFE_CONFIG["ENABLE_PROGRESS_GRAPH_SETTINGS"] = True MFE_CONFIG["COURSE_AUTHORING_MICROFRONTEND_URL"] = "{% if ENABLE_HTTPS %}https://{% else %}http://{% endif %}{{ MFE_HOST }}/course-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" {% endif %} {% if get_mfe("discussions") %} diff --git a/tutormfe/templates/mfe/apps/mfe/webpack.dev-tutor.config.js b/tutormfe/templates/mfe/apps/mfe/webpack.dev-tutor.config.js index 8a46d4c8..9bfda27a 100644 --- a/tutormfe/templates/mfe/apps/mfe/webpack.dev-tutor.config.js +++ b/tutormfe/templates/mfe/apps/mfe/webpack.dev-tutor.config.js @@ -4,7 +4,7 @@ const fs = require('fs'); const baseDevConfig = ( fs.existsSync('./webpack.dev.config.js') ? require('./webpack.dev.config.js') - : require('@edx/frontend-build/config/webpack.dev.config.js') + : require('@openedx/frontend-build/config/webpack.dev.config.js') ); module.exports = merge(baseDevConfig, { diff --git a/tutormfe/templates/mfe/build/mfe/Dockerfile b/tutormfe/templates/mfe/build/mfe/Dockerfile index 732000f2..fe62ff24 100644 --- a/tutormfe/templates/mfe/build/mfe/Dockerfile +++ b/tutormfe/templates/mfe/build/mfe/Dockerfile @@ -20,16 +20,6 @@ RUN mkdir -p /openedx/app /openedx/env WORKDIR /openedx/app ENV PATH /openedx/app/node_modules/.bin:${PATH} -######## i18n strings -FROM base AS i18n -COPY ./i18n /openedx/i18n -RUN chmod a+x /openedx/i18n/*.js -RUN echo "copying i18n data" \ - {%- for app_name, app in iter_mfes() %} - && mkdir -p /openedx/i18n/{{ app_name }} \ - {%- endfor %} - echo "done." - {% for app_name, app in iter_mfes() %} ####################### {{ app_name }} MFE ######## {{ app_name }} (git) @@ -42,14 +32,6 @@ ADD --keep-git-dir=true {{ app["repository"] }}#{{ app.get("version", MFE_COMMON FROM scratch as {{ app_name }}-src COPY --from={{ app_name }}-git /openedx/app / -######## {{ app_name }} (i18n) -FROM base AS {{ app_name }}-i18n -COPY --from={{ app_name }}-src / /openedx/app -COPY --from=i18n /openedx/i18n/{{ app_name }} /openedx/i18n/{{ app_name }} -COPY --from=i18n /openedx/i18n/i18n-merge.js /openedx/i18n/i18n-merge.js -RUN stat /openedx/app/src/i18n/messages 2> /dev/null || (echo "missing messages folder" && mkdir -p /openedx/app/src/i18n/messages) -RUN /openedx/i18n/i18n-merge.js /openedx/app/src/i18n/messages /openedx/i18n/{{ app_name }} /openedx/app/src/i18n/messages - ######## {{ app_name }} (common) FROM base AS {{ app_name }}-common COPY --from={{ app_name }}-src /package.json /openedx/app/package.json @@ -65,13 +47,8 @@ RUN --mount=type=cache,target=/root/.npm,sharing=shared npm clean-install --no-a {{ patch("mfe-dockerfile-post-npm-install") }} {{ patch("mfe-dockerfile-post-npm-install-{}".format(app_name)) }} COPY --from={{ app_name }}-src / /openedx/app -COPY --from={{ app_name }}-i18n /openedx/app/src/i18n/messages /openedx/app/src/i18n/messages -# Whenever a new MFE supports Atlas, it should be added to this list. -# When all MFEs support Atlas, this if-statement should be removed. -{% if app_name in ["communications"] %} -RUN make OPENEDX_ATLAS_PULL=true pull_translations -{% endif %} +RUN make OPENEDX_ATLAS_PULL=true ATLAS_OPTIONS="--repository={{ ATLAS_REPOSITORY }} --revision={{ ATLAS_REVISION }} {{ ATLAS_OPTIONS }}" pull_translations EXPOSE {{ app['port'] }} diff --git a/tutormfe/templates/mfe/build/mfe/i18n/i18n-merge.js b/tutormfe/templates/mfe/build/mfe/i18n/i18n-merge.js deleted file mode 100755 index c8cceda5..00000000 --- a/tutormfe/templates/mfe/build/mfe/i18n/i18n-merge.js +++ /dev/null @@ -1,47 +0,0 @@ -#! /usr/bin/env node -// Add to the current folder your custom translation strings, with the following file hierarchy: -// -// i18n/ -// / -// .json -// -// For instance, to override French strings from the payment app: -// -// i18n/ -// payment/ -// fr.json -// -// Your custom translation strings will automatically be compiled in the MFE Docker image. - -const fs = require('fs'); -const path = require('path'); - -function main() { - // Merge the messages from multiple directories and aggregate the content in a single directory. - // This is certainly not great idiomatic nodejs code. Please open a PR to improve this bit! - merge(process.argv[2], process.argv[3], process.argv[4]) -} - -function merge(dir1, dir2, outputDir) { - fs.readdirSync(dir1, { - withFileTypes: true - }).forEach(file1 => { - if (file1.isFile() && file1.name.endsWith(".json")) { - var path1 = path.resolve(path.join(dir1, file1.name)); - var data1 = require(path1); - var path2 = path.resolve(path.join(dir2, file1.name)); - fs.access(path2, (err) => { - if (err) { - return; - } - var pathOutput = path.resolve(path.join(outputDir, file1.name)); - console.log("Merging i18 strings from " + path1 + " with " + path2 + " to " + pathOutput); - var data2 = require(path2); - var final = Object.assign(data1, data2); - fs.writeFileSync(pathOutput, JSON.stringify(final, null, 2)); - }); - } - }); -} - -main() diff --git a/tutormfe/templates/mfe/tasks/lms/init b/tutormfe/templates/mfe/tasks/lms/init index 69e364f0..cf10abb6 100644 --- a/tutormfe/templates/mfe/tasks/lms/init +++ b/tutormfe/templates/mfe/tasks/lms/init @@ -24,20 +24,54 @@ site-configuration unset --domain={{ LMS_HOST }}:8000 ENABLE_PROFILE_MICROFRONTE {% if is_mfe_enabled("learning") %} (./manage.py lms waffle_flag --list | grep course_home.course_home_mfe_progress_tab) || ./manage.py lms waffle_flag --create --everyone course_home.course_home_mfe_progress_tab +(./manage.py lms waffle_flag --list | grep courseware.enable_navigation_sidebar) || ./manage.py lms waffle_flag --create --deactivate courseware.enable_navigation_sidebar +(./manage.py lms waffle_flag --list | grep courseware.always_open_auxiliary_sidebar) || ./manage.py lms waffle_flag --create --deactivate courseware.always_open_auxiliary_sidebar {% else %} ./manage.py lms waffle_delete --flags course_home.course_home_mfe_progress_tab +./manage.py lms waffle_delete --flags courseware.enable_navigation_sidebar +./manage.py lms waffle_delete --flags courseware.always_open_auxiliary_sidebar {% endif %} {% if is_mfe_enabled("course-authoring") %} +(./manage.py lms waffle_flag --list | grep contentstore.new_studio_mfe.use_new_advanced_settings_page) || ./manage.py lms waffle_flag --create --everyone contentstore.new_studio_mfe.use_new_advanced_settings_page +(./manage.py lms waffle_flag --list | grep contentstore.new_studio_mfe.use_new_certificates_page) || ./manage.py lms waffle_flag --create --everyone contentstore.new_studio_mfe.use_new_certificates_page +(./manage.py lms waffle_flag --list | grep contentstore.new_studio_mfe.use_new_course_outline_page) || ./manage.py lms waffle_flag --create --everyone contentstore.new_studio_mfe.use_new_course_outline_page +(./manage.py lms waffle_flag --list | grep contentstore.new_studio_mfe.use_new_course_team_page) || ./manage.py lms waffle_flag --create --everyone contentstore.new_studio_mfe.use_new_course_team_page +(./manage.py lms waffle_flag --list | grep contentstore.new_studio_mfe.use_new_custom_pages) || ./manage.py lms waffle_flag --create --everyone contentstore.new_studio_mfe.use_new_custom_pages +(./manage.py lms waffle_flag --list | grep contentstore.new_studio_mfe.use_new_export_page) || ./manage.py lms waffle_flag --create --everyone contentstore.new_studio_mfe.use_new_export_page +(./manage.py lms waffle_flag --list | grep contentstore.new_studio_mfe.use_new_files_uploads_page) || ./manage.py lms waffle_flag --create --everyone contentstore.new_studio_mfe.use_new_files_uploads_page +(./manage.py lms waffle_flag --list | grep contentstore.new_studio_mfe.use_new_grading_page) || ./manage.py lms waffle_flag --create --everyone contentstore.new_studio_mfe.use_new_grading_page +(./manage.py lms waffle_flag --list | grep contentstore.new_studio_mfe.use_new_group_configurations_page) || ./manage.py lms waffle_flag --create --everyone contentstore.new_studio_mfe.use_new_group_configurations_page +(./manage.py lms waffle_flag --list | grep contentstore.new_studio_mfe.use_new_import_page) || ./manage.py lms waffle_flag --create --everyone contentstore.new_studio_mfe.use_new_import_page +(./manage.py lms waffle_flag --list | grep contentstore.new_studio_mfe.use_new_schedule_details_page) || ./manage.py lms waffle_flag --create --everyone contentstore.new_studio_mfe.use_new_schedule_details_page +(./manage.py lms waffle_flag --list | grep contentstore.new_studio_mfe.use_new_textbooks_page) || ./manage.py lms waffle_flag --create --everyone contentstore.new_studio_mfe.use_new_textbooks_page +(./manage.py lms waffle_flag --list | grep contentstore.new_studio_mfe.use_new_updates_page) || ./manage.py lms waffle_flag --create --everyone contentstore.new_studio_mfe.use_new_updates_page (./manage.py lms waffle_flag --list | grep discussions.pages_and_resources_mfe) || ./manage.py lms waffle_flag --create --everyone discussions.pages_and_resources_mfe -(./manage.py lms waffle_flag --list | grep new_core_editors.use_new_text_editor) || ./manage.py lms waffle_flag --create --deactivate new_core_editors.use_new_text_editor -(./manage.py lms waffle_flag --list | grep new_core_editors.use_new_video_editor) || ./manage.py lms waffle_flag --create --deactivate new_core_editors.use_new_video_editor -(./manage.py lms waffle_flag --list | grep new_core_editors.use_new_problem_editor) || ./manage.py lms waffle_flag --create --deactivate new_core_editors.use_new_problem_editor +(./manage.py lms waffle_flag --list | grep new_core_editors.use_new_problem_editor) || ./manage.py lms waffle_flag --create --everyone new_core_editors.use_new_problem_editor +(./manage.py lms waffle_flag --list | grep new_core_editors.use_new_text_editor) || ./manage.py lms waffle_flag --create --everyone new_core_editors.use_new_text_editor +(./manage.py lms waffle_flag --list | grep new_core_editors.use_new_video_editor) || ./manage.py lms waffle_flag --create --everyone new_core_editors.use_new_video_editor +(./manage.py lms waffle_flag --list | grep new_studio_mfe.use_new_home_page) || ./manage.py lms waffle_flag --create --everyone new_studio_mfe.use_new_home_page +(./manage.py lms waffle_flag --list | grep new_studio_mfe.use_tagging_taxonomy_list_page) || ./manage.py lms waffle_flag --create --everyone new_studio_mfe.use_tagging_taxonomy_list_page {% else %} +./manage.py lms waffle_delete --flags contentstore.new_studio_mfe.use_new_advanced_settings_page +./manage.py lms waffle_delete --flags contentstore.new_studio_mfe.use_new_certificates_page +./manage.py lms waffle_delete --flags contentstore.new_studio_mfe.use_new_course_outline_page +./manage.py lms waffle_delete --flags contentstore.new_studio_mfe.use_new_course_team_page +./manage.py lms waffle_delete --flags contentstore.new_studio_mfe.use_new_custom_pages +./manage.py lms waffle_delete --flags contentstore.new_studio_mfe.use_new_export_page +./manage.py lms waffle_delete --flags contentstore.new_studio_mfe.use_new_files_uploads_page +./manage.py lms waffle_delete --flags contentstore.new_studio_mfe.use_new_grading_page +./manage.py lms waffle_delete --flags contentstore.new_studio_mfe.use_new_group_configurations_page +./manage.py lms waffle_delete --flags contentstore.new_studio_mfe.use_new_import_page +./manage.py lms waffle_delete --flags contentstore.new_studio_mfe.use_new_schedule_details_page +./manage.py lms waffle_delete --flags contentstore.new_studio_mfe.use_new_textbooks_page +./manage.py lms waffle_delete --flags contentstore.new_studio_mfe.use_new_updates_page ./manage.py lms waffle_delete --flags discussions.pages_and_resources_mfe +./manage.py lms waffle_delete --flags new_core_editors.use_new_problem_editor ./manage.py lms waffle_delete --flags new_core_editors.use_new_text_editor ./manage.py lms waffle_delete --flags new_core_editors.use_new_video_editor -./manage.py lms waffle_delete --flags new_core_editors.use_new_problem_editor +./manage.py lms waffle_delete --flags new_studio_mfe.use_new_home_page +./manage.py lms waffle_delete --flags new_studio_mfe.use_tagging_taxonomy_list_page {% endif %} {% if is_mfe_enabled("discussions") %}