From 53e7229fa42bfd4c34ecad6d432fecfcd351296d Mon Sep 17 00:00:00 2001
From: Ward Schodts <4020491+warreee@users.noreply.github.com>
Date: Thu, 27 Aug 2026 13:33:59 +0200
Subject: [PATCH] Respect the limit search param in the task overview duration
chart
The duration chart on the task overview always requested the last 14 task
instances, ignoring the number of dag runs the user picked. The dag details
layout already writes that choice to the limit search param, and the dag
overview page reads a value of its own, so the task page was the only view
that could not be widened.
Read the same limit search param the details layout writes, falling back to
the same default of 10, so the chart follows the selector and can be shared
through the URL.
The remaining limit on the failed task instance query is left alone: that
query only reads total_entries and never renders the rows it fetches.
---
.../src/pages/Task/Overview/Overview.test.tsx | 54 ++++++++++++++++---
.../ui/src/pages/Task/Overview/Overview.tsx | 7 ++-
2 files changed, 53 insertions(+), 8 deletions(-)
diff --git a/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.test.tsx b/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.test.tsx
index 90c39ec41eb0d..add9a7796a2fc 100644
--- a/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.test.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.test.tsx
@@ -18,13 +18,32 @@
*/
import "@testing-library/jest-dom";
import { render, screen } from "@testing-library/react";
-import { describe, expect, it, vi } from "vitest";
+import type { PropsWithChildren } from "react";
+import { MemoryRouter } from "react-router-dom";
+import { beforeEach, describe, expect, it, vi } from "vitest";
import type { ReactAppResponse } from "openapi/requests/types.gen";
-import { Wrapper } from "src/utils/Wrapper";
+import { BaseWrapper, Wrapper } from "src/utils/Wrapper";
import { Overview } from "./Overview";
+const { mockUseTaskInstanceServiceGetTaskInstances } = vi.hoisted(() => ({
+ mockUseTaskInstanceServiceGetTaskInstances: vi.fn(() => ({
+ data: { task_instances: [], total_entries: 0 },
+ isLoading: false,
+ })),
+}));
+
+const wrapperWithSearch = (search: string) => {
+ const RouterWrapper = ({ children }: PropsWithChildren) => (
+
+ {children}
+
+ );
+
+ return RouterWrapper;
+};
+
vi.mock("openapi/queries", () => ({
usePluginServiceGetPlugins: () => ({
data: {
@@ -44,10 +63,7 @@ vi.mock("openapi/queries", () => ({
],
},
}),
- useTaskInstanceServiceGetTaskInstances: () => ({
- data: { task_instances: [], total_entries: 0 },
- isLoading: false,
- }),
+ useTaskInstanceServiceGetTaskInstances: mockUseTaskInstanceServiceGetTaskInstances,
}));
vi.mock("src/components/DurationChart", () => ({ DurationChart: () => null }));
@@ -79,3 +95,29 @@ describe("Task overview plugins", () => {
expect(screen.queryByText("Scoped overview plugin")).not.toBeInTheDocument();
});
});
+
+describe("Task overview duration chart limit", () => {
+ beforeEach(() => {
+ mockUseTaskInstanceServiceGetTaskInstances.mockClear();
+ });
+
+ it("requests the default number of task instances when no limit is set", () => {
+ render(, { wrapper: wrapperWithSearch("") });
+
+ expect(mockUseTaskInstanceServiceGetTaskInstances).toHaveBeenCalledWith(
+ expect.objectContaining({ limit: 10, orderBy: ["-run_after"] }),
+ undefined,
+ expect.anything(),
+ );
+ });
+
+ it("requests the number of task instances given by the limit search param", () => {
+ render(, { wrapper: wrapperWithSearch("?limit=50") });
+
+ expect(mockUseTaskInstanceServiceGetTaskInstances).toHaveBeenCalledWith(
+ expect.objectContaining({ limit: 50, orderBy: ["-run_after"] }),
+ undefined,
+ expect.anything(),
+ );
+ });
+});
diff --git a/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.tsx b/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.tsx
index 470877d1742fc..377a674f36869 100644
--- a/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.tsx
@@ -20,7 +20,7 @@ import { Box, HStack, Skeleton, VStack } from "@chakra-ui/react";
import dayjs from "dayjs";
import { useState } from "react";
import { useTranslation } from "react-i18next";
-import { useParams } from "react-router-dom";
+import { useParams, useSearchParams } from "react-router-dom";
import { usePluginServiceGetPlugins, useTaskInstanceServiceGetTaskInstances } from "openapi/queries";
import { DurationChart } from "src/components/DurationChart";
@@ -38,6 +38,9 @@ export const Overview = () => {
const { dagId = "", groupId, taskId } = useParams();
const { t: translate } = useTranslation("dag");
+ const [searchParams] = useSearchParams();
+ const limit = Number(searchParams.get(SearchParamsKeys.LIMIT) ?? "10");
+
const now = dayjs();
const [startDate, setStartDate] = useState(now.subtract(Number(defaultHour), "hour").toISOString());
const [endDate, setEndDate] = useState(now.toISOString());
@@ -62,7 +65,7 @@ export const Overview = () => {
{
dagId,
dagRunId: "~",
- limit: 14,
+ limit,
orderBy: ["-run_after"],
taskGroupId: groupId ?? undefined,
taskId: Boolean(groupId) ? undefined : taskId,