diff --git a/airflow-core/docs/administration-and-deployment/plugins.rst b/airflow-core/docs/administration-and-deployment/plugins.rst index b9965b543490c..0619f08bb9b4b 100644 --- a/airflow-core/docs/administration-and-deployment/plugins.rst +++ b/airflow-core/docs/administration-and-deployment/plugins.rst @@ -360,6 +360,7 @@ definitions in Airflow. # It can also be put inside of an existing page, the supported views are ["dashboard", "dag_overview", "task_overview"]. You can position # element in the existing page via the css `order` rule which will determine the flex order. # Use "base" to mount the app in the base layout (e.g. a toolbar strip); the host uses a flex container so you can set ``order`` in your root JSX to control position. + # The server preloads the bundle of a "base" app from a plugin with no team, so it downloads alongside the UI's own scripts. "destination": "task", # Optional icon, url to an svg file. "icon": "https://example.com/icon.svg", diff --git a/airflow-core/src/airflow/api_fastapi/core_api/app.py b/airflow-core/src/airflow/api_fastapi/core_api/app.py index d3a66a5b68dd8..8480251d7698d 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/app.py +++ b/airflow-core/src/airflow/api_fastapi/core_api/app.py @@ -139,6 +139,24 @@ def route(request: Request, namespace: str) -> Response: ) +def base_react_app_bundle_urls() -> list[str]: + """ + Bundle URLs of the React apps that render on every page, for the server to preload. + + Only plugins without a team are included: the page is rendered before the user is known, and + the plugins endpoint hides a team-scoped plugin from users outside its team. + """ + from airflow import plugins_manager + + return [ + react_app["bundle_url"] + for plugin in plugins_manager.get_plugin_info(attrs_to_dump=["react_apps"]) + if plugin["team_name"] is None + for react_app in plugin["react_apps"] + if react_app.get("destination") == "base" and isinstance(react_app.get("bundle_url"), str) + ] + + def init_views(app: FastAPI) -> None: """Init views by registering the different routers.""" from airflow.api_fastapi.core_api.routes.public import public_router @@ -156,6 +174,7 @@ def init_views(app: FastAPI) -> None: Path(directory).mkdir(exist_ok=True) templates = Jinja2Templates(directory=directory) + preload_bundle_urls = base_react_app_bundle_urls() # Ahead of the static mounts below so plugin-overridden locales take precedence. init_ui_translation_views(app, dev_mode=dev_mode, dist_directory=directory) @@ -207,7 +226,10 @@ def webapp(request: Request, rest_of_path: str): return templates.TemplateResponse( request, "/index.html", - {"backend_server_base_url": request.base_url.path}, + { + "backend_server_base_url": request.base_url.path, + "preload_bundle_urls": preload_bundle_urls, + }, media_type="text/html", ) diff --git a/airflow-core/src/airflow/ui/dev/index.html b/airflow-core/src/airflow/ui/dev/index.html index 10afd373f8fae..2eda7ac0e5ed1 100644 --- a/airflow-core/src/airflow/ui/dev/index.html +++ b/airflow-core/src/airflow/ui/dev/index.html @@ -4,6 +4,9 @@