This is an automated email from the ASF dual-hosted git repository.

vatsrahul1001 pushed a commit to branch v3-4-test
in repository https://gitbox.apache.org/repos/asf/airflow.git

commit ca7a88b0f3b1477ece34856b51d6f56a7a158330
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri Oct 30 00:59:23 2026 +0530

    [v3-4-test] Fix dashboard Active Dags filtering (#75350) (#75949)
    
    * [v3-4-test] Fix dashboard Active Dags filtering (#75350)
    (cherry picked from commit fa160f835387cc88596aa5c0ecfdf82176249487)
    
    Co-authored-by: Anzin Mohammed <[email protected]>
    
    * UI: Fix the Dag filters test that fails lint and vitest (#75963)
    
    (cherry picked from commit f605f1040f3b08211d23d8cf91c5717343b25631)
    
    ---------
    
    Co-authored-by: Anzin Mohammed <[email protected]>
    Co-authored-by: Pushkal Gupta 
<[email protected]>
---
 .../FilterBar/filters/pausedStateParams.ts         | 19 ++++++++---------
 .../src/airflow/ui/src/constants/filterConfigs.tsx |  2 ++
 .../src/airflow/ui/src/mocks/handlers/dags.ts      |  4 ++++
 .../DagsList/DagsFilters/DagsFilters.test.tsx      | 24 +++++++++++++++++-----
 .../src/airflow/ui/src/pages/DagsList/DagsList.tsx |  4 +---
 5 files changed, 35 insertions(+), 18 deletions(-)

diff --git 
a/airflow-core/src/airflow/ui/src/components/FilterBar/filters/pausedStateParams.ts
 
b/airflow-core/src/airflow/ui/src/components/FilterBar/filters/pausedStateParams.ts
index f7543ffe6f7..dad892a5b63 100644
--- 
a/airflow-core/src/airflow/ui/src/components/FilterBar/filters/pausedStateParams.ts
+++ 
b/airflow-core/src/airflow/ui/src/components/FilterBar/filters/pausedStateParams.ts
@@ -21,9 +21,10 @@ import { SearchParamsKeys } from 
"src/constants/searchParams";
 import type { FilterValue } from "../types";
 
 /**
- * The Paused state pill covers three choices backed by the two URL params 
that already exist:
+ * The Paused state pill covers four choices backed by the two URL params that 
already exist:
  *
  *   "not_paused" → ``paused=false``              unpaused, which includes 
draining Dags
+ *   "active"     → ``scheduling_state=active``    excludes paused and 
draining Dags
  *   "paused"     → ``paused=true``
  *   "draining"   → ``scheduling_state=draining``
  *
@@ -32,29 +33,27 @@ import type { FilterValue } from "../types";
  * ``paused`` for the other two leaves existing links and the 
``hide_paused_dags_by_default``
  * seed working untouched.
  */
-export type PausedStateValue = "draining" | "not_paused" | "paused";
+export type PausedStateValue = "active" | "draining" | "not_paused" | "paused";
 
 export const pausedStateToSearchParams = (value: FilterValue): Record<string, 
string | undefined> => {
   const paused = value === "not_paused" ? "false" : undefined;
 
   return {
     [SearchParamsKeys.PAUSED]: value === "paused" ? "true" : paused,
-    [SearchParamsKeys.SCHEDULING_STATE]: value === "draining" ? "draining" : 
undefined,
+    [SearchParamsKeys.SCHEDULING_STATE]: value === "draining" || value === 
"active" ? value : undefined,
   };
 };
 
 export const pausedStateFromSearchParams = (params: URLSearchParams): 
PausedStateValue | undefined => {
   const schedulingState = params.get(SearchParamsKeys.SCHEDULING_STATE);
 
-  // Checked first: a draining Dag is also unpaused, so ``paused=false`` 
alongside it would
-  // otherwise win and widen the pill back to every unpaused Dag.
-  if (schedulingState === "draining") {
-    return "draining";
+  // Scheduling state is narrower than paused=false, which also includes 
draining Dags.
+  if (schedulingState === "draining" || schedulingState === "active") {
+    return schedulingState;
   }
 
   // ``scheduling_state=paused`` is the same set as ``paused=true``, so links 
written against the
-  // short-lived Scheduling state filter keep working. ``=active`` has no 
equivalent here — it
-  // excludes draining Dags, which no single choice does — so it falls through 
and is ignored.
+  // short-lived Scheduling state filter keep working.
   if (schedulingState === "paused") {
     return "paused";
   }
@@ -78,5 +77,5 @@ export const pausedStateToQuery = (value: PausedStateValue | 
undefined) => {
     paused = false;
   }
 
-  return { paused, schedulingState: value === "draining" ? ("draining" as 
const) : undefined };
+  return { paused, schedulingState: value === "draining" || value === "active" 
? value : undefined };
 };
diff --git a/airflow-core/src/airflow/ui/src/constants/filterConfigs.tsx 
b/airflow-core/src/airflow/ui/src/constants/filterConfigs.tsx
index 969abe1b089..975a75c4b3a 100644
--- a/airflow-core/src/airflow/ui/src/constants/filterConfigs.tsx
+++ b/airflow-core/src/airflow/ui/src/constants/filterConfigs.tsx
@@ -99,6 +99,7 @@ export const useFilterConfigs = () => {
     "common",
     "components",
     "admin",
+    "dashboard",
     "dags",
     "hitl",
   ]);
@@ -385,6 +386,7 @@ export const useFilterConfigs = () => {
       label: translate("dags:filters.pausedState"),
       options: [
         { label: translate("dags:filters.paused.active"), value: "not_paused" 
},
+        { label: translate("dashboard:stats.activeDags"), value: "active" },
         { label: translate("dags:draining.label"), value: "draining" },
         { label: translate("dags:filters.paused.paused"), value: "paused" },
       ],
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 a9f3570e6c4..41b4144c54e 100644
--- a/airflow-core/src/airflow/ui/src/mocks/handlers/dags.ts
+++ b/airflow-core/src/airflow/ui/src/mocks/handlers/dags.ts
@@ -159,6 +159,10 @@ const filterDags = ({
   // Narrower than ``paused=false``: a draining Dag is unpaused, so the two 
are not alternatives.
   if (schedulingState === "draining") {
     allDags = allDags.filter((dag) => "scheduling_state" in dag && 
dag.scheduling_state === "draining");
+  } else if (schedulingState === "active") {
+    allDags = allDags.filter(
+      (dag) => !dag.is_paused && !("scheduling_state" in dag && 
dag.scheduling_state === "draining"),
+    );
   }
 
   if (paused === "true") {
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
index e32eec62fa5..380fd374c67 100644
--- 
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
@@ -228,13 +228,27 @@ describe("Links written before draining moved into the 
pill", () => {
     await waitFor(() => expect(screen.getByText(shown)).toBeInTheDocument());
   });
 
-  it("ignores scheduling_state=active rather than filtering with no pill to 
explain it", async () => {
+  it("restores the dashboard Active Dags filter and excludes paused and 
draining Dags", async () => {
+    withDrainingDag();
     render(<AppWrapper initialEntries={["/dags?scheduling_state=active"]} />);
 
-    // No choice means "not paused and not draining", so the param is inert 
instead of narrowing
-    // the list invisibly.
-    await waitFor(() => 
expect(screen.getByText("paused_dag")).toBeInTheDocument());
+    await waitFor(() => 
expect(screen.getByText("tutorial_taskflow_api_success")).toBeInTheDocument());
+    
expect(screen.getByTestId("paused-pill")).toHaveTextContent("stats.activeDags");
+    expect(screen.queryByText("paused_dag")).not.toBeInTheDocument();
+    expect(screen.queryByText("draining_dag")).not.toBeInTheDocument();
+
+    await selectPillOption("paused", "not_paused");
+    await waitFor(() => 
expect(screen.getByTestId("paused-pill")).toHaveTextContent("filters.paused.active"));
+    await waitFor(() => 
expect(screen.getByText("draining_dag")).toBeInTheDocument());
+    expect(screen.queryByText("paused_dag")).not.toBeInTheDocument();
+
+    await selectPillOption("paused", "active");
+    await waitFor(() => 
expect(screen.getByTestId("paused-pill")).toHaveTextContent("stats.activeDags"));
+    await waitFor(() => 
expect(screen.queryByText("draining_dag")).not.toBeInTheDocument());
     
expect(screen.getByText("tutorial_taskflow_api_success")).toBeInTheDocument();
-    expect(screen.queryByTestId("paused-pill")).not.toBeInTheDocument();
+
+    fireEvent.click(screen.getByTestId("paused-pill-remove"));
+    await waitFor(() => 
expect(screen.getByText("paused_dag")).toBeInTheDocument());
+    expect(screen.getByText("draining_dag")).toBeInTheDocument();
   });
 });
diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx 
b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx
index 8b1e114e679..e7e37ff9f44 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx
@@ -146,9 +146,7 @@ export const DagsList = () => {
 
   const multiTeamEnabled = Boolean(useConfig("multi_team"));
 
-  // Derived through the pill's own mapping so the query can never filter on 
more than the pill
-  // shows. A legacy ``scheduling_state=active`` has no choice to represent 
it, and would
-  // otherwise narrow the list with nothing on screen saying why.
+  // Derived through the pill's own mapping so the query can never filter on 
more than the pill shows.
   const { paused, schedulingState } = 
pausedStateToQuery(pausedStateFromSearchParams(searchParams));
   const showFavorites = searchParams.get(FAVORITE);
 

Reply via email to