diff --git a/tutormfe/hooks.py b/tutormfe/hooks.py index d88c7b44..cefe1188 100644 --- a/tutormfe/hooks.py +++ b/tutormfe/hooks.py @@ -11,7 +11,12 @@ from tutor.core.hooks import Filter MFE_ATTRS_TYPE = t.Dict[t.Literal["repository", "port", "version"], t.Union["str", int]] +FRONTEND_TEMPLATE_SITE_ATTRS_TYPE = t.Dict MFE_APPS: Filter[dict[str, MFE_ATTRS_TYPE], []] = Filter() +# TODO: This will hold the list of which apps are "enabled" so we can switch between mfe +# and frontend-base ones +FRONTEND_APPS: Filter[dict[str, t.Dict], []] = Filter() + PLUGIN_SLOTS: Filter[list[tuple[str, str, str]], []] = Filter() diff --git a/tutormfe/plugin.py b/tutormfe/plugin.py index 798f2c72..443f827a 100644 --- a/tutormfe/plugin.py +++ b/tutormfe/plugin.py @@ -5,7 +5,7 @@ from glob import glob import importlib_resources -from tutor import fmt +from tutor import fmt, config as tutor_config from tutor import hooks as tutor_hooks from tutor.__about__ import __version_suffix__ from tutor.bindmount import iter_mounts @@ -13,7 +13,7 @@ from tutor.types import Config, get_typed from .__about__ import __version__ -from .hooks import MFE_APPS, MFE_ATTRS_TYPE, PLUGIN_SLOTS +from .hooks import MFE_APPS, MFE_ATTRS_TYPE, FRONTEND_APPS, FRONTEND_TEMPLATE_SITE_ATTRS_TYPE, PLUGIN_SLOTS # Handle version suffix in main mode, just like tutor core if __version_suffix__: @@ -76,6 +76,11 @@ "repository": "https://github.com/openedx/frontend-app-profile.git", "port": 1995, }, + "template-site": { + "repository": "https://github.com/WGU-Open-edX/frontend-template-site.git", + "version": "initial", + "port": 8080, + } } @@ -95,6 +100,14 @@ def get_mfes() -> dict[str, MFE_ATTRS_TYPE]: return MFE_APPS.apply({}) +@tutor_hooks.lru_cache +def get_frontend_apps() -> dict[str, FRONTEND_TEMPLATE_SITE_ATTRS_TYPE]: + """ + This function is cached for performance. + """ + return FRONTEND_APPS.apply({}) + + class MFEMountData: """Stores categorized mounted and unmounted MFEs.""" @@ -129,6 +142,29 @@ def iter_mfes() -> t.Iterable[tuple[str, MFE_ATTRS_TYPE]]: """ yield from get_mfes().items() +# Iter throgh all mfes and adds the unique frontend apps, +# so we can have a list of all the things that are unique that needs +# to be added to Caddyfile, for example instructor dashboard that was +# created as frontend-base app but didn't exist as a MFE before +# so it returns the whole mfes list plus the unique frontend apps that are not in the mfe list +def iter_frontend_apps() -> t.Iterable[tuple[str, FRONTEND_TEMPLATE_SITE_ATTRS_TYPE]]: + """ + Yield: + + (name, dict) + """ + mfes = get_mfes() + frontend_apps = get_frontend_apps() + + # First yield all MFEs + for name, attrs in mfes.items(): + yield (name, attrs) + + # Then yield frontend apps that are not already MFEs + for name, attrs in frontend_apps.items(): + if name not in mfes: + yield (name, attrs) + def iter_plugin_slots(mfe_name: str) -> t.Iterable[tuple[str, str]]: """ @@ -142,6 +178,9 @@ def iter_plugin_slots(mfe_name: str) -> t.Iterable[tuple[str, str]]: def is_mfe_enabled(mfe_name: str) -> bool: return mfe_name in get_mfes() +def is_frontend_app_enabled(app_name: str) -> bool: + return app_name in get_frontend_apps() + def get_mfe(mfe_name: str) -> t.Union[MFE_ATTRS_TYPE, t.Any]: return get_mfes().get(mfe_name, {}) @@ -152,8 +191,10 @@ def get_mfe(mfe_name: str) -> t.Union[MFE_ATTRS_TYPE, t.Any]: [ ("get_mfe", get_mfe), ("iter_mfes", iter_mfes), + ("iter_frontend_apps", iter_frontend_apps), ("iter_plugin_slots", iter_plugin_slots), ("is_mfe_enabled", is_mfe_enabled), + ("is_frontend_app_enabled", is_frontend_app_enabled), ("MFEMountData", MFEMountData), ] ) @@ -217,6 +258,8 @@ def _mounted_mfe_image_management() -> None: tutor_hooks.Filters.CLI_DO_INIT_TASKS.add_item(("lms", task_file.read())) REPO_PREFIX = "frontend-app-" +# TODO: for now leave this and then find better semantic namings +FRONTEND_TEMPLATE_SITE_PREFIX = "frontend-" @tutor_hooks.Filters.COMPOSE_MOUNTS.add() @@ -229,12 +272,12 @@ def _mount_frontend_apps( in dev mode, because in production, all MFEs are built and hosted on the singular 'mfe' service container. """ - if path_basename.startswith(REPO_PREFIX): + if path_basename.startswith(REPO_PREFIX) or path_basename.startswith(FRONTEND_TEMPLATE_SITE_PREFIX): # Assumption: # For each repo named frontend-app-APPNAME, there is an associated # docker-compose service named APPNAME. If this assumption is broken, # then Tutor will try to mount the repo in a service that doesn't exist. - app_name = path_basename[len(REPO_PREFIX) :] + app_name = path_basename[len(REPO_PREFIX) :] if path_basename.startswith(REPO_PREFIX) else path_basename[len(FRONTEND_TEMPLATE_SITE_PREFIX) :] volumes += [(app_name, "/openedx/app")] return volumes @@ -244,9 +287,9 @@ def _mount_frontend_apps_on_build( mounts: list[tuple[str, str]], host_path: str ) -> list[tuple[str, str]]: path_basename = os.path.basename(host_path) - if path_basename.startswith(REPO_PREFIX): + if path_basename.startswith(REPO_PREFIX) or path_basename.startswith(FRONTEND_TEMPLATE_SITE_PREFIX): # Bind-mount repo at build-time, both for prod and dev images - app_name = path_basename[len(REPO_PREFIX) :] + app_name = path_basename[len(REPO_PREFIX) :] if path_basename.startswith(REPO_PREFIX) else path_basename[len(FRONTEND_TEMPLATE_SITE_PREFIX) :] mounts.append(("mfe", f"{app_name}-src")) mounts.append((f"{app_name}-dev", f"{app_name}-src")) return mounts diff --git a/tutormfe/templates/mfe/apps/mfe/Caddyfile b/tutormfe/templates/mfe/apps/mfe/Caddyfile index 115d57c5..6aca2bfc 100644 --- a/tutormfe/templates/mfe/apps/mfe/Caddyfile +++ b/tutormfe/templates/mfe/apps/mfe/Caddyfile @@ -26,15 +26,22 @@ redir @authoring /authoring/{re.authoring.1} permanent {% endif %} - {% for app_name, app in iter_mfes() %} + {% for app_name, app in iter_frontend_apps() %} @mfe_{{ app_name }} { path /{{ app_name }} /{{ app_name }}/* } handle @mfe_{{ app_name }} { uri strip_prefix /{{ app_name }} + {%- if is_frontend_app_enabled(app_name) %} + # {{ app_name }} - using frontend-apps approach + root * /openedx/dist/template-site + {%- else %} + # {{ app_name }} - using traditional MFE approach root * /openedx/dist/{{ app_name }} + {%- endif %} try_files /{path} /index.html file_server } + {% endfor %} } diff --git a/tutormfe/templates/mfe/build/mfe/Dockerfile b/tutormfe/templates/mfe/build/mfe/Dockerfile index 7a470822..a503b2fb 100644 --- a/tutormfe/templates/mfe/build/mfe/Dockerfile +++ b/tutormfe/templates/mfe/build/mfe/Dockerfile @@ -50,8 +50,9 @@ RUN --mount=type=cache,target=/root/.npm,sharing=shared npm clean-install --no-a {{ patch("mfe-dockerfile-post-npm-install-{}".format(app_name)) }} COPY --from={{ app_name }}-src / /openedx/app +{% if app_name != "template-site" %} RUN make OPENEDX_ATLAS_PULL=true ATLAS_OPTIONS="--repository={{ ATLAS_REPOSITORY }} --revision={{ ATLAS_REVISION }} {{ ATLAS_OPTIONS }}" pull_translations - +{% endif %} EXPOSE {{ app['port'] }} # Configuration needed at build time