This is an automated email from the ASF dual-hosted git repository.
ephraimanierobi pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-1-test by this push:
new f31715ce56c Fix react apps plugins router (#61206)
f31715ce56c is described below
commit f31715ce56cc72cc5d97513e943367a5fb86db47
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Thu Jan 29 14:30:11 2026 +0100
Fix react apps plugins router (#61206)
---
.../src/airflow/ui/src/pages/ExternalView.tsx | 6 ++++--
.../src/airflow/ui/src/pages/ReactPlugin.tsx | 24 ++++++++++++++--------
2 files changed, 19 insertions(+), 11 deletions(-)
diff --git a/airflow-core/src/airflow/ui/src/pages/ExternalView.tsx
b/airflow-core/src/airflow/ui/src/pages/ExternalView.tsx
index a1c80cdf2f3..c74d9b25a4b 100644
--- a/airflow-core/src/airflow/ui/src/pages/ExternalView.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/ExternalView.tsx
@@ -18,7 +18,7 @@
*/
import { Box } from "@chakra-ui/react";
import { useTranslation } from "react-i18next";
-import { useParams } from "react-router-dom";
+import { useLocation, useParams } from "react-router-dom";
import { usePluginServiceGetPlugins } from "openapi/queries";
import { ProgressBar } from "src/components/ui";
@@ -32,6 +32,8 @@ export const ExternalView = () => {
const { page } = useParams();
const { data: pluginData, isLoading } = usePluginServiceGetPlugins();
+ const { pathname } = useLocation();
+
const externalView =
page === "legacy-fab-views"
? {
@@ -82,7 +84,7 @@ export const ExternalView = () => {
m={-2} // Compensate for parent padding
minHeight={0}
>
- <ReactPlugin reactApp={reactApp} />
+ <ReactPlugin key={pathname} reactApp={reactApp} />
</Box>
);
}
diff --git a/airflow-core/src/airflow/ui/src/pages/ReactPlugin.tsx
b/airflow-core/src/airflow/ui/src/pages/ReactPlugin.tsx
index 1c1800044f7..12144d17869 100644
--- a/airflow-core/src/airflow/ui/src/pages/ReactPlugin.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/ReactPlugin.tsx
@@ -34,19 +34,27 @@ type PluginComponentType = FC<{
export const ReactPlugin = ({ reactApp }: { readonly reactApp:
ReactAppResponse }) => {
const { dagId, mapIndex, runId, taskId } = useParams();
- const Plugin = lazy(() =>
- // We are assuming the plugin manager is trusted and the bundle_url is safe
+ // If the plugin component was already registered on the global object by a
previous load,
+ // render it directly without going through Suspense/lazy (avoids flashing
the spinner).
+ const existing = (globalThis as Record<string, unknown>)[reactApp.name];
+
+ if (typeof existing === "function") {
+ const Plugin = existing as PluginComponentType;
+
+ return <Plugin dagId={dagId} mapIndex={mapIndex} runId={runId}
taskId={taskId} />;
+ }
+
+ // Otherwise, lazy-load the bundle once. When it resolves, it must set a
function component
+ // under globalThis[reactApp.name], which we then use as the default export.
+ const LazyPlugin = lazy(() =>
import(/* @vite-ignore */ reactApp.bundle_url)
.then(() => {
- // Store components in globalThis[reactApp.name] to avoid conflicts
with the shared globalThis.AirflowPlugin
- // global variable.
let pluginComponent = (globalThis as Record<string,
unknown>)[reactApp.name] as
| PluginComponentType
| undefined;
if (pluginComponent === undefined) {
pluginComponent = (globalThis as Record<string,
unknown>).AirflowPlugin as PluginComponentType;
-
(globalThis as Record<string, unknown>)[reactApp.name] =
pluginComponent;
}
@@ -59,15 +67,13 @@ export const ReactPlugin = ({ reactApp }: { readonly
reactApp: ReactAppResponse
.catch((error: unknown) => {
console.error("Component Failed Loading:", error);
- return {
- default: ErrorPage,
- };
+ return { default: ErrorPage };
}),
);
return (
<Suspense fallback={<Spinner />}>
- <Plugin dagId={dagId} mapIndex={mapIndex} runId={runId} taskId={taskId}
/>
+ <LazyPlugin dagId={dagId} mapIndex={mapIndex} runId={runId}
taskId={taskId} />
</Suspense>
);
};