diff --git a/airflow-core/src/airflow/ui/src/layouts/BaseLayout.tsx b/airflow-core/src/airflow/ui/src/layouts/BaseLayout.tsx index 062caca76d342..f40cea8c54cd3 100644 --- a/airflow-core/src/airflow/ui/src/layouts/BaseLayout.tsx +++ b/airflow-core/src/airflow/ui/src/layouts/BaseLayout.tsx @@ -29,7 +29,6 @@ import { useConfig } from "src/queries/useConfig"; import { Nav } from "./Nav"; export const BaseLayout = ({ children }: PropsWithChildren) => { - const instanceName = useConfig("instance_name"); const { i18n } = useTranslation(); const { data: pluginData } = usePluginServiceGetPlugins(); const theme = useConfig("theme") as unknown as { icon?: string; icon_dark_mode?: string } | undefined; @@ -39,10 +38,6 @@ export const BaseLayout = ({ children }: PropsWithChildren) => { .flatMap((plugin) => plugin.react_apps) .filter((reactApp: ReactAppResponse) => reactApp.destination === "base") ?? []; - if (typeof instanceName === "string") { - document.title = instanceName; - } - useEffect(() => { const html = document.documentElement; diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx b/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx index 95634cb9245e6..aa7e421b3a8c9 100644 --- a/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx @@ -32,7 +32,7 @@ import { usePluginTabs } from "src/hooks/usePluginTabs"; import { useRequiredActionTabs } from "src/hooks/useRequiredActionTabs"; import { DetailsLayout } from "src/layouts/Details/DetailsLayout"; import { useRefreshOnNewDagRuns } from "src/queries/useRefreshOnNewDagRuns"; -import { isStatePending, useAutoRefresh } from "src/utils"; +import { isStatePending, useAutoRefresh, useDocumentTitle } from "src/utils"; import { DagNotFound } from "./DagNotFound"; import { Header } from "./Header"; @@ -86,6 +86,8 @@ export const Dag = () => { }, ); + useDocumentTitle(dag?.dag_display_name ?? dagId); + // Ensures continuous refresh to detect new runs when there's no // pending state and new runs are initiated from other page useRefreshOnNewDagRuns(dagId, hasPendingRuns, dag?.is_paused); diff --git a/airflow-core/src/airflow/ui/src/utils/index.ts b/airflow-core/src/airflow/ui/src/utils/index.ts index 486cc4bee66df..c15a50f6dd754 100644 --- a/airflow-core/src/airflow/ui/src/utils/index.ts +++ b/airflow-core/src/airflow/ui/src/utils/index.ts @@ -22,6 +22,7 @@ export { getDuration, renderDuration } from "./datetimeUtils"; export { createErrorToaster, getErrorStatus } from "./errorHandling"; export { getMetaKey } from "./getMetaKey"; export { useContainerWidth } from "./useContainerWidth"; +export { useDocumentTitle } from "./useDocumentTitle"; export { useFiltersHandler, type FilterableSearchParamsKeys } from "./useFiltersHandler"; export * from "./query"; export { STATE_PRIORITY, sortStateEntries } from "./stateUtils"; diff --git a/airflow-core/src/airflow/ui/src/utils/useDocumentTitle.ts b/airflow-core/src/airflow/ui/src/utils/useDocumentTitle.ts new file mode 100644 index 0000000000000..e94f0d9c18c08 --- /dev/null +++ b/airflow-core/src/airflow/ui/src/utils/useDocumentTitle.ts @@ -0,0 +1,38 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { useEffect } from "react"; + +import { useConfig } from "src/queries/useConfig"; + +export const useDocumentTitle = (pageTitle?: string | null) => { + const instanceConfig = useConfig("instance_name"); + const instanceName = typeof instanceConfig === "string" ? instanceConfig : "Airflow"; + + useEffect(() => { + const previousTitle = document.title; + + if (typeof pageTitle === "string" && pageTitle.length > 0) { + document.title = `${pageTitle} - ${instanceName}`; + } + + return () => { + document.title = previousTitle; + }; + }, [pageTitle, instanceName]); +};