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 fc7204297f9 Fix paused filter not showing all dags (#62269)
fc7204297f9 is described below
commit fc7204297f992982fd6cfafa7e411e8c812d4b17
Author: Guan-Ming (Wesley) Chiu <[email protected]>
AuthorDate: Tue Feb 24 06:46:25 2026 +0800
Fix paused filter not showing all dags (#62269)
Signed-off-by: Guan-Ming (Wesley) Chiu
<[email protected]>
---
.../src/airflow/ui/src/mocks/handlers/dags.ts | 231 ++++++++++-----------
.../DagsList/DagsFilters/DagsFilters.test.tsx | 73 +++++++
.../src/pages/DagsList/DagsFilters/DagsFilters.tsx | 2 +-
3 files changed, 181 insertions(+), 125 deletions(-)
diff --git a/airflow-core/src/airflow/ui/src/mocks/handlers/dags.ts
b/airflow-core/src/airflow/ui/src/mocks/handlers/dags.ts
index 9b9a6a205f3..2c97bef0c8b 100644
--- a/airflow-core/src/airflow/ui/src/mocks/handlers/dags.ts
+++ b/airflow-core/src/airflow/ui/src/mocks/handlers/dags.ts
@@ -16,76 +16,112 @@
* specific language governing permissions and limitations
* under the License.
*/
-
-/* eslint-disable unicorn/no-null */
import { http, HttpResponse, type HttpHandler } from "msw";
+const successDag = {
+ dag_display_name: "tutorial_taskflow_api_success",
+ dag_id: "tutorial_taskflow_api_success",
+ file_token:
+
".eJw9yUsOgCAMBcC7cAB7JPISizR82kCJcnvjxtUsJlDWxlQwPEvhjU7TV0pk27N2goxU9f7lB80qxxPXJF-uQ1CjY5avI0wO2-EFouohiw.fhdU5u0Pb7lElEd-AUUXqjHSsdo",
+ fileloc: "/airflow/dags/tutorial_taskflow_api.py",
+ has_import_errors: false,
+ has_task_concurrency_limits: false,
+ is_favorite: true,
+ is_paused: false,
+ is_stale: false,
+ last_parsed_time: "2025-01-13T07:34:01.593459Z",
+ latest_dag_runs: [
+ {
+ dag_id: "tutorial_taskflow_api",
+ end_date: "2025-01-13T04:34:12.143831Z",
+ id: 1,
+ logical_date: "2025-01-13T04:33:58.396323Z",
+ run_id: "manual__2025-01-13T04:33:58.387988+00:00",
+ start_date: "2025-01-13T04:33:58.496197Z",
+ state: "success",
+ },
+ ],
+ max_active_runs: 16,
+ max_active_tasks: 16,
+ max_consecutive_failed_dag_runs: 0,
+ owners: ["airflow"],
+ pending_actions: [],
+ tags: [{ dag_id: "tutorial_taskflow_api_success", name: "example" }],
+ timetable_description: "Never, external triggers only",
+};
+
+const failedDag = {
+ dag_display_name: "tutorial_taskflow_api_failed",
+ dag_id: "tutorial_taskflow_api_failed",
+ file_token:
+
".eJw9yUsOgCAMBcC7cAB7JPISizR82kCJcnvjxtUsJlDWxlQwPEvhjU7TV0pk27N2goxU9f7lB80qxxPXJF-uQ1CjY5avI0wO2-EFouohiw.fhdU5u0Pb7lElEd-AUUXqjHSsdo",
+ fileloc: "/airflow/dags/tutorial_taskflow_api_failed.py",
+ has_import_errors: false,
+ has_task_concurrency_limits: false,
+ is_favorite: false,
+ is_paused: false,
+ is_stale: false,
+ last_parsed_time: "2025-01-13T07:34:01.593459Z",
+ latest_dag_runs: [
+ {
+ dag_id: "tutorial_taskflow_api",
+ end_date: "2025-01-13T04:34:12.143831Z",
+ id: 2,
+ logical_date: "2025-01-13T04:33:58.396323Z",
+ run_id: "manual__2025-01-13T04:33:58.387988+00:00",
+ start_date: "2025-01-13T04:33:58.496197Z",
+ state: "success",
+ },
+ ],
+ max_active_runs: 16,
+ max_active_tasks: 16,
+ max_consecutive_failed_dag_runs: 0,
+ owners: ["airflow"],
+ pending_actions: [],
+ tags: [{ dag_id: "tutorial_taskflow_api_failed", name: "example" }],
+ timetable_description: "Never, external triggers only",
+};
+
+const pausedDag = {
+ dag_display_name: "paused_dag",
+ dag_id: "paused_dag",
+ file_token:
+
".eJw9yUsOgCAMBcC7cAB7JPISizR82kCJcnvjxtUsJlDWxlQwPEvhjU7TV0pk27N2goxU9f7lB80qxxPXJF-uQ1CjY5avI0wO2-EFouohiw.fhdU5u0Pb7lElEd-AUUXqjHSsdo",
+ fileloc: "/airflow/dags/paused_dag.py",
+ has_import_errors: false,
+ has_task_concurrency_limits: false,
+ is_favorite: false,
+ is_paused: true,
+ is_stale: false,
+ last_parsed_time: "2025-01-13T07:34:01.593459Z",
+ latest_dag_runs: [],
+ max_active_runs: 16,
+ max_active_tasks: 16,
+ max_consecutive_failed_dag_runs: 0,
+ owners: ["airflow"],
+ pending_actions: [],
+ tags: [{ dag_id: "paused_dag", name: "example" }],
+ timetable_description: "Never, external triggers only",
+};
+
+const filterDagsByPaused = (paused: string | null) => {
+ const allDags = [successDag, failedDag, pausedDag];
+
+ if (paused === "true") {
+ return allDags.filter((dag) => dag.is_paused);
+ }
+ if (paused === "false") {
+ return allDags.filter((dag) => !dag.is_paused);
+ }
+
+ return allDags;
+};
+
export const handlers: Array<HttpHandler> = [
http.get("/ui/dags", ({ request }) => {
const url = new URL(request.url);
const lastDagRunState = url.searchParams.get("last_dag_run_state");
- const successDag = {
- dag_display_name: "tutorial_taskflow_api_success",
- dag_id: "tutorial_taskflow_api_success",
- file_token:
-
".eJw9yUsOgCAMBcC7cAB7JPISizR82kCJcnvjxtUsJlDWxlQwPEvhjU7TV0pk27N2goxU9f7lB80qxxPXJF-uQ1CjY5avI0wO2-EFouohiw.fhdU5u0Pb7lElEd-AUUXqjHSsdo",
- fileloc: "/airflow/dags/tutorial_taskflow_api.py",
- has_import_errors: false,
- has_task_concurrency_limits: false,
- is_favorite: true,
- is_paused: false,
- is_stale: false,
- last_parsed_time: "2025-01-13T07:34:01.593459Z",
- latest_dag_runs: [
- {
- dag_id: "tutorial_taskflow_api",
- end_date: "2025-01-13T04:34:12.143831Z",
- id: 1,
- logical_date: "2025-01-13T04:33:58.396323Z",
- run_id: "manual__2025-01-13T04:33:58.387988+00:00",
- start_date: "2025-01-13T04:33:58.496197Z",
- state: "success",
- },
- ],
- max_active_runs: 16,
- max_active_tasks: 16,
- max_consecutive_failed_dag_runs: 0,
- owners: ["airflow"],
- pending_actions: [],
- tags: [{ dag_id: "tutorial_taskflow_api_success", name: "example" }],
- timetable_description: "Never, external triggers only",
- };
- const failedDag = {
- dag_display_name: "tutorial_taskflow_api_failed",
- dag_id: "tutorial_taskflow_api_failed",
- file_token:
-
".eJw9yUsOgCAMBcC7cAB7JPISizR82kCJcnvjxtUsJlDWxlQwPEvhjU7TV0pk27N2goxU9f7lB80qxxPXJF-uQ1CjY5avI0wO2-EFouohiw.fhdU5u0Pb7lElEd-AUUXqjHSsdo",
- fileloc: "/airflow/dags/tutorial_taskflow_api_failed.py",
- has_import_errors: false,
- has_task_concurrency_limits: false,
- is_favorite: false,
- is_paused: false,
- is_stale: false,
- last_parsed_time: "2025-01-13T07:34:01.593459Z",
- latest_dag_runs: [
- {
- dag_id: "tutorial_taskflow_api",
- end_date: "2025-01-13T04:34:12.143831Z",
- id: 2,
- logical_date: "2025-01-13T04:33:58.396323Z",
- run_id: "manual__2025-01-13T04:33:58.387988+00:00",
- start_date: "2025-01-13T04:33:58.496197Z",
- state: "success",
- },
- ],
- max_active_runs: 16,
- max_active_tasks: 16,
- max_consecutive_failed_dag_runs: 0,
- owners: ["airflow"],
- pending_actions: [],
- tags: [{ dag_id: "tutorial_taskflow_api_failed", name: "example" }],
- timetable_description: "Never, external triggers only",
- };
+ const paused = url.searchParams.get("paused");
if (lastDagRunState === "success") {
return HttpResponse.json({
@@ -97,71 +133,18 @@ export const handlers: Array<HttpHandler> = [
dags: [failedDag],
total_entries: 1,
});
- } else {
- return HttpResponse.json({
- dags: [failedDag],
- total_entries: 1,
- });
}
+
+ const dags = filterDagsByPaused(paused);
+
+ return HttpResponse.json({
+ dags,
+ total_entries: dags.length,
+ });
}),
http.get("/api/v2/dags", ({ request }) => {
const url = new URL(request.url);
const lastDagRunState = url.searchParams.get("last_dag_run_state");
- const failedDag = {
- dag_display_name: "tutorial_taskflow_api_failed",
- dag_id: "tutorial_taskflow_api_failed",
- description: null,
- file_token:
-
".eJw9yUsOgCAMBcC7cAB7JPISizR82kCJcnvjxtUsJlDWxlQwPEvhjU7TV0pk27N2goxU9f7lB80qxxPXJF-uQ1CjY5avI0wO2-EFouohiw.fhdU5u0Pb7lElEd-AUUXqjHSsdo",
- fileloc: "/airflow/dags/tutorial_taskflow_api_failed.py",
- has_import_errors: false,
- has_task_concurrency_limits: false,
- is_favorite: false,
- is_paused: false,
- is_stale: false,
- last_expired: null,
- last_parsed_time: "2025-01-13T06:45:33.009609Z",
- max_active_runs: 16,
- max_active_tasks: 16,
- max_consecutive_failed_dag_runs: 0,
- next_dagrun: null,
- next_dagrun_create_after: null,
- next_dagrun_data_interval_end: null,
- next_dagrun_data_interval_start: null,
- owners: ["airflow"],
- pending_actions: [],
- tags: [{ dag_id: "tutorial_taskflow_api_failed", name: "example" }],
- timetable_description: "Never, external triggers only",
- timetable_summary: null,
- };
-
- const successDag = {
- dag_display_name: "tutorial_taskflow_api_success",
- dag_id: "tutorial_taskflow_api_success",
- description: null,
- file_token:
-
".eJw9yUsOgCAMBcC7cAB7JPISizR82kCJcnvjxtUsJlDWxlQwPEvhjU7TV0pk27N2goxU9f7lB80qxxPXJF-uQ1CjY5avI0wO2-EFouohiw.fhdU5u0Pb7lElEd-AUUXqjHSsdo",
- fileloc: "/airflow/dags/tutorial_taskflow_api_success.py",
- has_import_errors: false,
- has_task_concurrency_limits: false,
- is_favorite: true,
- is_paused: false,
- is_stale: false,
- last_expired: null,
- last_parsed_time: "2025-01-13T06:45:33.009609Z",
- max_active_runs: 16,
- max_active_tasks: 16,
- max_consecutive_failed_dag_runs: 0,
- next_dagrun: null,
- next_dagrun_create_after: null,
- next_dagrun_data_interval_end: null,
- next_dagrun_data_interval_start: null,
- owners: ["airflow"],
- pending_actions: [],
- tags: [{ dag_id: "tutorial_taskflow_api_success", name: "example" }],
- timetable_description: "Never, external triggers only",
- timetable_summary: null,
- };
if (lastDagRunState === "failed") {
return HttpResponse.json({
diff --git
a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/DagsFilters.test.tsx
b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/DagsFilters.test.tsx
new file mode 100644
index 00000000000..4a28ebe8087
--- /dev/null
+++
b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/DagsFilters.test.tsx
@@ -0,0 +1,73 @@
+/*!
+ * 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, waitFor } from "@testing-library/react";
+import { describe, it, expect, vi } from "vitest";
+
+import { AppWrapper } from "src/utils/AppWrapper";
+
+const mockConfig: Record<string, unknown> = {
+ auto_refresh_interval: 3,
+ default_wrap: false,
+ enable_swagger_ui: true,
+ hide_paused_dags_by_default: true,
+ instance_name: "Airflow",
+ multi_team: false,
+ page_size: 15,
+ require_confirmation_dag_change: false,
+ test_connection: "Disabled",
+};
+
+vi.mock("src/queries/useConfig", () => ({
+ useConfig: (key: string) => mockConfig[key],
+}));
+
+describe("Paused filter with hide_paused_dags_by_default enabled", () => {
+ it("defaults to showing only active dags", async () => {
+ render(<AppWrapper initialEntries={["/dags"]} />);
+
+ await waitFor(() =>
expect(screen.getByText("tutorial_taskflow_api_success")).toBeInTheDocument());
+ expect(screen.queryByText("paused_dag")).not.toBeInTheDocument();
+ });
+
+ it("shows all dags after clicking All filter", async () => {
+ render(<AppWrapper initialEntries={["/dags"]} />);
+
+ await waitFor(() =>
expect(screen.getByText("tutorial_taskflow_api_success")).toBeInTheDocument());
+ expect(screen.queryByText("paused_dag")).not.toBeInTheDocument();
+
+ // There are two "All" buttons (StateFilters and PausedFilter).
+ // The second one belongs to PausedFilter.
+ const allButtons = screen.getAllByText("filters.paused.all");
+
+ allButtons[1]?.click();
+ await waitFor(() =>
expect(screen.getByText("paused_dag")).toBeInTheDocument());
+
expect(screen.getByText("tutorial_taskflow_api_success")).toBeInTheDocument();
+ });
+
+ it("shows only paused dags after clicking Paused filter", async () => {
+ render(<AppWrapper initialEntries={["/dags"]} />);
+
+ await waitFor(() =>
expect(screen.getByText("tutorial_taskflow_api_success")).toBeInTheDocument());
+
+ screen.getByText("filters.paused.paused").click();
+ await waitFor(() =>
expect(screen.getByText("paused_dag")).toBeInTheDocument());
+ await waitFor(() =>
expect(screen.queryByText("tutorial_taskflow_api_success")).not.toBeInTheDocument());
+ });
+});
diff --git
a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/DagsFilters.tsx
b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/DagsFilters.tsx
index 3a1ee4fdf58..e9066d8c180 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/DagsFilters.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/DagsFilters.tsx
@@ -90,7 +90,7 @@ export const DagsFilters = () => {
};
const handlePausedChange = (value: BooleanFilterValue) => {
- if (value === "all") {
+ if (value === "all" && !hidePausedDagsByDefault) {
searchParams.delete(PAUSED_PARAM);
} else {
searchParams.set(PAUSED_PARAM, value);