This is an automated email from the ASF dual-hosted git repository.
bbovenzi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 6cf83327d46 UI: Drop the loading spinner for base React plugins
(#74762)
6cf83327d46 is described below
commit 6cf83327d4687e121f6b2e332db300a7ce3491a3
Author: Julian LaNeve <[email protected]>
AuthorDate: Thu Oct 15 08:44:43 2026 -0400
UI: Drop the loading spinner for base React plugins (#74762)
A plugin with destination "base" renders above the content of every
page. While its bundle loads, ReactPlugin's Suspense fallback put a bare
spinner in the top left corner, so every full page load showed a stray
spinner until the bundle arrived. Base plugins now render nothing until
they are ready. Plugins on other destinations keep the spinner, since
they fill a page or tab that would otherwise be empty.
---
.../src/airflow/ui/src/pages/ReactPlugin.test.tsx | 17 +++++++++++++++++
airflow-core/src/airflow/ui/src/pages/ReactPlugin.tsx | 4 +++-
2 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/airflow-core/src/airflow/ui/src/pages/ReactPlugin.test.tsx
b/airflow-core/src/airflow/ui/src/pages/ReactPlugin.test.tsx
index 6878743c55c..adbf65ee8ce 100644
--- a/airflow-core/src/airflow/ui/src/pages/ReactPlugin.test.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/ReactPlugin.test.tsx
@@ -189,3 +189,20 @@ describe("loadPlugin", () => {
expect(component).not.toBe(componentA);
});
});
+
+const loading = (destination: ReactAppResponse["destination"]) =>
+ ({ bundle_url: "http://localhost/never-loaded.js", destination, name:
"Unloaded" }) as ReactAppResponse;
+
+describe("ReactPlugin loading fallback", () => {
+ it("shows a spinner while a page plugin's bundle loads", () => {
+ const { container } = render(<ReactPlugin reactApp={loading("dag")} />, {
wrapper: Wrapper });
+
+ expect(container.querySelector(".chakra-spinner")).toBeInTheDocument();
+ });
+
+ it("shows nothing while a base plugin's bundle loads", () => {
+ const { container } = render(<ReactPlugin reactApp={loading("base")} />, {
wrapper: Wrapper });
+
+ expect(container.querySelector(".chakra-spinner")).not.toBeInTheDocument();
+ });
+});
diff --git a/airflow-core/src/airflow/ui/src/pages/ReactPlugin.tsx
b/airflow-core/src/airflow/ui/src/pages/ReactPlugin.tsx
index f454b6c806c..3d7b55a2d92 100644
--- a/airflow-core/src/airflow/ui/src/pages/ReactPlugin.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/ReactPlugin.tsx
@@ -150,9 +150,11 @@ export const ReactPlugin = ({ reactApp }: { readonly
reactApp: ReactAppResponse
// 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(() => loadPlugin(reactApp));
+ // A base plugin sits above every page, so a spinner there shows on every
full page load.
+ const fallback = reactApp.destination === "base" ? null : <Spinner />;
return (
- <Suspense fallback={<Spinner />}>
+ <Suspense fallback={fallback}>
<LazyPlugin {...pluginProps} />
</Suspense>
);