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 51c99ea856e fix(ui): wire up logical date filter on /dagruns page
(#62799) (#62848)
51c99ea856e is described below
commit 51c99ea856e4a09faca421716bea433514ddd86d
Author: Subham <[email protected]>
AuthorDate: Thu Mar 5 01:21:20 2026 +0530
fix(ui): wire up logical date filter on /dagruns page (#62799) (#62848)
---
.../src/airflow/ui/src/mocks/handlers/dag_runs.ts | 87 ++++++++++++++++++++++
.../src/airflow/ui/src/mocks/handlers/index.ts | 9 ++-
.../src/airflow/ui/src/pages/DagRuns.test.tsx | 48 ++++++++++++
airflow-core/src/airflow/ui/src/pages/DagRuns.tsx | 8 ++
4 files changed, 151 insertions(+), 1 deletion(-)
diff --git a/airflow-core/src/airflow/ui/src/mocks/handlers/dag_runs.ts
b/airflow-core/src/airflow/ui/src/mocks/handlers/dag_runs.ts
new file mode 100644
index 00000000000..fe9083a3d76
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/mocks/handlers/dag_runs.ts
@@ -0,0 +1,87 @@
+/*!
+ * 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.
+ */
+
+/* eslint-disable unicorn/no-null */
+import { http, HttpResponse, type HttpHandler } from "msw";
+
+const dagRunBeforeFilter = {
+ conf: null,
+ dag_display_name: "test_dag",
+ dag_id: "test_dag",
+ dag_run_id: "run_before_filter",
+ dag_versions: [],
+ data_interval_end: null,
+ data_interval_start: null,
+ duration: 1.5,
+ end_date: "2024-12-31T00:00:01Z",
+ logical_date: "2024-12-31T00:00:00Z",
+ partition_key: null,
+ run_after: "2024-12-31T00:00:00Z",
+ run_type: "manual",
+ start_date: "2024-12-31T00:00:00Z",
+ state: "success",
+ triggering_user_name: "admin",
+};
+
+const dagRunInRange = {
+ conf: null,
+ dag_display_name: "test_dag",
+ dag_id: "test_dag",
+ dag_run_id: "run_in_range",
+ dag_versions: [],
+ data_interval_end: null,
+ data_interval_start: null,
+ duration: 2.0,
+ end_date: "2025-01-15T00:00:01Z",
+ logical_date: "2025-01-15T00:00:00Z",
+ partition_key: null,
+ run_after: "2025-01-15T00:00:00Z",
+ run_type: "manual",
+ start_date: "2025-01-15T00:00:00Z",
+ state: "success",
+ triggering_user_name: "admin",
+};
+
+export const handlers: Array<HttpHandler> = [
+ http.get("/api/v2/dags/:dagId/dagRuns", ({ request }) => {
+ const url = new URL(request.url);
+ const logicalDateGte = url.searchParams.get("logical_date_gte");
+ const logicalDateLte = url.searchParams.get("logical_date_lte");
+
+ const allRuns = [dagRunBeforeFilter, dagRunInRange];
+
+ const filtered = allRuns.filter((run) => {
+ const logicalDate = new Date(run.logical_date);
+
+ if (logicalDateGte !== null && logicalDate < new Date(logicalDateGte)) {
+ return false;
+ }
+ if (logicalDateLte !== null && logicalDate > new Date(logicalDateLte)) {
+ return false;
+ }
+
+ return true;
+ });
+
+ return HttpResponse.json({
+ dag_runs: filtered,
+ total_entries: filtered.length,
+ });
+ }),
+];
diff --git a/airflow-core/src/airflow/ui/src/mocks/handlers/index.ts
b/airflow-core/src/airflow/ui/src/mocks/handlers/index.ts
index 0b6f1dd56c9..9f60605f2b6 100644
--- a/airflow-core/src/airflow/ui/src/mocks/handlers/index.ts
+++ b/airflow-core/src/airflow/ui/src/mocks/handlers/index.ts
@@ -18,7 +18,14 @@
*/
import { handlers as configHandlers } from "./config";
import { handlers as dagHandlers } from "./dag";
+import { handlers as dagRunsHandlers } from "./dag_runs";
import { handlers as dagsHandlers } from "./dags";
import { handlers as logHandlers } from "./log";
-export const handlers = [...configHandlers, ...dagHandlers, ...dagsHandlers,
...logHandlers];
+export const handlers = [
+ ...configHandlers,
+ ...dagHandlers,
+ ...dagRunsHandlers,
+ ...dagsHandlers,
+ ...logHandlers,
+];
diff --git a/airflow-core/src/airflow/ui/src/pages/DagRuns.test.tsx
b/airflow-core/src/airflow/ui/src/pages/DagRuns.test.tsx
new file mode 100644
index 00000000000..50d1d9d8e6a
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/pages/DagRuns.test.tsx
@@ -0,0 +1,48 @@
+/*!
+ * 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, expect, it } from "vitest";
+
+import { AppWrapper } from "src/utils/AppWrapper";
+
+// The dag_runs mock handler (see src/mocks/handlers/dag_runs.ts) returns:
+// - run_before_filter (logical_date: 2024-12-31) — excluded when filtering
Jan 2025
+// - run_in_range (logical_date: 2025-01-15) — included when filtering
Jan 2025
+describe("DagRuns logical date filter", () => {
+ it("shows all runs when no logical date filter is applied", async () => {
+ render(<AppWrapper initialEntries={["/dag_runs"]} />);
+
+ await waitFor(() =>
expect(screen.getByText("run_in_range")).toBeInTheDocument());
+ expect(screen.getByText("run_before_filter")).toBeInTheDocument();
+ });
+
+ it("filters runs by logical_date_gte and logical_date_lte URL params", async
() => {
+ render(
+ <AppWrapper
+ initialEntries={[
+
"/dag_runs?logical_date_gte=2025-01-01T00%3A00%3A00Z&logical_date_lte=2025-01-31T23%3A59%3A59Z",
+ ]}
+ />,
+ );
+
+ await waitFor(() =>
expect(screen.getByText("run_in_range")).toBeInTheDocument());
+ expect(screen.queryByText("run_before_filter")).not.toBeInTheDocument();
+ });
+});
diff --git a/airflow-core/src/airflow/ui/src/pages/DagRuns.tsx
b/airflow-core/src/airflow/ui/src/pages/DagRuns.tsx
index 99186d6102e..fba73b61f55 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagRuns.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagRuns.tsx
@@ -16,6 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
+
+/* eslint-disable max-lines */
import { Flex, HStack, Link, Text } from "@chakra-ui/react";
import type { ColumnDef } from "@tanstack/react-table";
import type { TFunction } from "i18next";
@@ -50,6 +52,8 @@ const {
DURATION_LTE: DURATION_LTE_PARAM,
END_DATE_GTE: END_DATE_GTE_PARAM,
END_DATE_LTE: END_DATE_LTE_PARAM,
+ LOGICAL_DATE_GTE: LOGICAL_DATE_GTE_PARAM,
+ LOGICAL_DATE_LTE: LOGICAL_DATE_LTE_PARAM,
PARTITION_KEY_PATTERN: PARTITION_KEY_PATTERN_PARAM,
RUN_AFTER_GTE: RUN_AFTER_GTE_PARAM,
RUN_AFTER_LTE: RUN_AFTER_LTE_PARAM,
@@ -213,6 +217,8 @@ export const DagRuns = () => {
const startDateLte = searchParams.get(START_DATE_LTE_PARAM);
const endDateGte = searchParams.get(END_DATE_GTE_PARAM);
const endDateLte = searchParams.get(END_DATE_LTE_PARAM);
+ const logicalDateGte = searchParams.get(LOGICAL_DATE_GTE_PARAM);
+ const logicalDateLte = searchParams.get(LOGICAL_DATE_LTE_PARAM);
const runAfterGte = searchParams.get(RUN_AFTER_GTE_PARAM);
const runAfterLte = searchParams.get(RUN_AFTER_LTE_PARAM);
const durationGte = searchParams.get(DURATION_GTE_PARAM);
@@ -234,6 +240,8 @@ export const DagRuns = () => {
endDateGte: endDateGte ?? undefined,
endDateLte: endDateLte ?? undefined,
limit: pageSize,
+ logicalDateGte: logicalDateGte ?? undefined,
+ logicalDateLte: logicalDateLte ?? undefined,
offset: pageIndex * pageSize,
orderBy,
partitionKeyPattern: partitionKeyPattern ?? undefined,