diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/en/dag.json b/airflow-core/src/airflow/ui/public/i18n/locales/en/dag.json
index 58626c2494248..9477d1f981af3 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/en/dag.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/en/dag.json
@@ -52,6 +52,9 @@
"buttons": {
"advanced": "Advanced",
"dagDocs": "Dag Docs"
+ },
+ "status": {
+ "deactivated": "Deactivated"
}
},
"logs": {
diff --git a/airflow-core/src/airflow/ui/src/components/DagDeactivatedBadge.tsx b/airflow-core/src/airflow/ui/src/components/DagDeactivatedBadge.tsx
new file mode 100644
index 0000000000000..ad915b256bee5
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/components/DagDeactivatedBadge.tsx
@@ -0,0 +1,26 @@
+/*!
+ * 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 { Badge } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
+
+export const DagDeactivatedBadge = () => {
+ const { t: translate } = useTranslation("dag");
+
+ return {translate("header.status.deactivated")};
+};
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/DagBreadcrumb.tsx b/airflow-core/src/airflow/ui/src/layouts/Details/DagBreadcrumb.tsx
index d877b4daa1b9b..822e8d7e663d6 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/DagBreadcrumb.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/DagBreadcrumb.tsx
@@ -27,6 +27,7 @@ import {
useTaskServiceGetTask,
} from "openapi/queries";
import { BreadcrumbStats } from "src/components/BreadcrumbStats";
+import { DagDeactivatedBadge } from "src/components/DagDeactivatedBadge";
import { StateBadge } from "src/components/StateBadge";
import { TogglePause } from "src/components/TogglePause";
import { isStatePending, useAutoRefresh } from "src/utils";
@@ -65,7 +66,9 @@ export const DagBreadcrumb = () => {
[
{
label: dag?.dag_display_name ?? dagId,
- labelExtra: (
+ labelExtra: dag?.is_stale ? (
+
+ ) : (
{
const [defaultDagView] = useLocalStorage<"graph" | "grid">(DEFAULT_DAG_VIEW_KEY, "grid");
const panelGroupRef = useRef(null);
const [dagView, setDagView] = useLocalStorage<"graph" | "grid">(dagViewKey(dagId), defaultDagView);
- const [limit, setLimit] = useLocalStorage(dagRunsLimitKey(dagId), 10);
+ const [limit, setLimit] = useLocalStorage(dagRunsLimitKey(dagId), 10);
const [runAfterGte, setRunAfterGte] = useLocalStorage(runAfterGteKey(dagId), undefined);
const [runAfterLte, setRunAfterLte] = useLocalStorage(runAfterLteKey(dagId), undefined);
const [runTypeFilter, setRunTypeFilter] = useLocalStorage(
@@ -94,9 +94,9 @@ export const DetailsLayout = ({ children, error, isLoading, tabs }: Props) => {
undefined,
);
- const [showGantt, setShowGantt] = useLocalStorage(showGanttKey(dagId), false);
+ const [showGantt, setShowGantt] = useLocalStorage(showGanttKey(dagId), false);
// Global setting: applies to all Dags (intentionally not scoped to dagId)
- const [showVersionIndicatorMode, setShowVersionIndicatorMode] = useLocalStorage(
+ const [showVersionIndicatorMode, setShowVersionIndicatorMode] = useLocalStorage(
`version_indicator_display_mode`,
VersionIndicatorOptions.ALL,
);
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Header.test.tsx b/airflow-core/src/airflow/ui/src/pages/Dag/Header.test.tsx
new file mode 100644
index 0000000000000..9d68c7b8e851c
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Header.test.tsx
@@ -0,0 +1,65 @@
+/*!
+ * 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 "@testing-library/jest-dom";
+import { render, screen } from "@testing-library/react";
+import type { DAGDetailsResponse } from "openapi-gen/requests/types.gen";
+import { describe, expect, it } from "vitest";
+
+import i18n from "src/i18n/config";
+import { MOCK_DAG } from "src/mocks/handlers/dag";
+import { Wrapper } from "src/utils/Wrapper";
+
+import { Header } from "./Header";
+
+const mockDag = {
+ ...MOCK_DAG,
+ active_runs_count: 0,
+ allowed_run_types: [],
+ bundle_name: "dags-folder",
+ bundle_version: "1",
+ default_args: {},
+ fileloc: "/files/dags/stale_dag.py",
+ is_favorite: false,
+ is_stale: true,
+ last_parse_duration: 0.23,
+ // `null` matches the API response shape for DAGs without version metadata.
+ // eslint-disable-next-line unicorn/no-null
+ latest_dag_version: null,
+ next_dagrun_logical_date: "2024-08-22T00:00:00+00:00",
+ next_dagrun_run_after: "2024-08-22T19:00:00+00:00",
+ owner_links: {},
+ relative_fileloc: "stale_dag.py",
+ tags: [],
+ timetable_partitioned: false,
+ timetable_summary: "* * * * *",
+} as unknown as DAGDetailsResponse;
+
+describe("Header", () => {
+ it("shows a deactivated badge and hides stale-only next actions for stale dags", () => {
+ render(
+
+
+ ,
+ );
+
+ expect(screen.getByText(i18n.t("dag:header.status.deactivated"))).toBeInTheDocument();
+ expect(screen.queryByText(i18n.t("dag:dagDetails.nextRun"))).not.toBeInTheDocument();
+ expect(screen.queryByRole("button", { name: "Reparse Dag" })).not.toBeInTheDocument();
+ });
+});
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Header.tsx b/airflow-core/src/airflow/ui/src/pages/Dag/Header.tsx
index 6af9129a31641..67be5da796fdc 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Header.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Header.tsx
@@ -26,6 +26,7 @@ import { DagIcon } from "src/assets/DagIcon";
import { DeleteDagButton } from "src/components/DagActions/DeleteDagButton";
import { FavoriteDagButton } from "src/components/DagActions/FavoriteDagButton";
import { ParseDagButton } from "src/components/DagActions/ParseDagButton";
+import { DagDeactivatedBadge } from "src/components/DagDeactivatedBadge";
import DagRunInfo from "src/components/DagRunInfo";
import { DagVersion } from "src/components/DagVersion";
import DisplayMarkdownButton from "src/components/DisplayMarkdownButton";
@@ -56,6 +57,22 @@ export const Header = ({
const { t: translate } = useTranslation(["common", "dag"]);
// We would still like to show the dagId even if the dag object hasn't loaded yet
const { dagId } = useParams();
+ const isStale = dag?.is_stale;
+
+ const nextRunStat =
+ isStale
+ ? []
+ : [
+ {
+ label: translate("dagDetails.nextRun"),
+ value: Boolean(dag?.next_dagrun_run_after) ? (
+
+ ) : undefined,
+ },
+ ];
const stats = [
{
@@ -88,15 +105,7 @@ export const Header = ({
) : undefined,
},
- {
- label: translate("dagDetails.nextRun"),
- value: Boolean(dag?.next_dagrun_run_after) ? (
-
- ) : undefined,
- },
+ ...nextRunStat,
{
label: translate("dagDetails.maxActiveRuns"),
value:
@@ -132,7 +141,7 @@ export const Header = ({
/>
)}
-
+ {isStale ? undefined : }
>
)
@@ -140,8 +149,12 @@ export const Header = ({
icon={}
stats={stats}
subTitle={
- dag !== undefined && (
-
+ isStale ? (
+
+ ) : (
+ dag !== undefined && (
+
+ )
)
}
title={dag?.dag_display_name ?? dagId}