This is an automated email from the ASF dual-hosted git repository.
vatsrahul1001 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 912bbe58050 Fix Dag list run badges and counters not refreshing after
completion (#74734)
912bbe58050 is described below
commit 912bbe58050604b1d0080f0122fe27dc380724e1
Author: Anzin Mohammed <[email protected]>
AuthorDate: Sun Oct 18 13:18:46 2026 +0530
Fix Dag list run badges and counters not refreshing after completion
(#74734)
* Fix Dag run state refresh
* Fix MSW option in Dag refresh regression tests
---
.../ui/src/queries/useDagRunStateCounts.tsx | 2 +-
.../src/airflow/ui/src/queries/useDags.test.tsx | 101 +++++++++++++++++++++
.../src/airflow/ui/src/queries/useDags.tsx | 2 +-
3 files changed, 103 insertions(+), 2 deletions(-)
diff --git a/airflow-core/src/airflow/ui/src/queries/useDagRunStateCounts.tsx
b/airflow-core/src/airflow/ui/src/queries/useDagRunStateCounts.tsx
index 238f54cc563..c763d61918e 100644
--- a/airflow-core/src/airflow/ui/src/queries/useDagRunStateCounts.tsx
+++ b/airflow-core/src/airflow/ui/src/queries/useDagRunStateCounts.tsx
@@ -30,7 +30,7 @@ export const useDagRunStateCounts = ({
// need to be loaded before it knows whether to poll — avoids a
chicken-and-egg.
readonly dags: ReadonlyArray<DAGWithLatestDagRunsResponse> | undefined;
}) => {
- const refetchInterval = useAutoRefresh({ checkPendingRuns: true });
+ const refetchInterval = useAutoRefresh({});
const hasPendingRun =
dags?.some((dag) => !dag.is_paused && dag.latest_dag_runs.some((run) =>
isStatePending(run.state))) ??
false;
diff --git a/airflow-core/src/airflow/ui/src/queries/useDags.test.tsx
b/airflow-core/src/airflow/ui/src/queries/useDags.test.tsx
new file mode 100644
index 00000000000..ed98797b8ed
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/queries/useDags.test.tsx
@@ -0,0 +1,101 @@
+/*!
+ * 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 type { PropsWithChildren } from "react";
+
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { act, cleanup, renderHook, waitFor } from "@testing-library/react";
+import { http, HttpResponse } from "msw";
+import { setupServer } from "msw/node";
+import { afterAll, afterEach, beforeAll, expect, it, vi } from "vitest";
+
+import { successDag } from "src/mocks/handlers/dags";
+
+import { useDagRunStateCounts } from "./useDagRunStateCounts";
+import { useDags } from "./useDags";
+
+const server = setupServer();
+
+beforeAll(() => server.listen({ onUnhandledFrame: "error" }));
+afterEach(() => {
+ cleanup();
+ server.resetHandlers();
+ vi.useRealTimers();
+});
+afterAll(() => server.close());
+
+it.each(["latest run", "run counts"])("refreshes %s when no runs are pending",
async (target) => {
+ vi.useFakeTimers({ shouldAdvanceTime: true });
+ let completed = false;
+ const client = new QueryClient({ defaultOptions: { queries: { retry: false }
} });
+ const wrapper = ({ children }: PropsWithChildren) => (
+ <QueryClientProvider client={client}>{children}</QueryClientProvider>
+ );
+
+ server.use(
+ http.get("/ui/config", () => HttpResponse.json({ auto_refresh_interval: 1
})),
+ http.get("/api/v2/dags/~/dagRuns", () => HttpResponse.json({ dag_runs: [],
total_entries: 0 })),
+ http.get("/ui/dags", () =>
+ HttpResponse.json({
+ dags: [
+ {
+ ...successDag,
+ latest_dag_runs: successDag.latest_dag_runs.map((run) => ({
+ ...run,
+ state: completed ? "success" : "queued",
+ })),
+ },
+ ],
+ total_entries: 1,
+ }),
+ ),
+ http.get("/ui/dags/run_state_counts", () =>
+ HttpResponse.json({
+ dags: [{ dag_id: successDag.dag_id, state_counts: { success: completed
? 1 : 0 } }],
+ }),
+ ),
+ );
+
+ const { result } = renderHook(
+ () => {
+ const dags = useDags({ dagRunsLimit: 1 });
+ const counts = useDagRunStateCounts({ dagIds: [successDag.dag_id], dags:
dags.data?.dags });
+
+ return { counts, dags };
+ },
+ { wrapper },
+ );
+
+ await waitFor(() => {
+
expect(result.current.dags.data?.dags[0]?.latest_dag_runs[0]?.state).toBe("queued");
+ expect(result.current.counts.data).toBeDefined();
+ });
+ completed = true;
+ await act(async () => {
+ await vi.advanceTimersByTimeAsync(12_000);
+ });
+
+ if (target === "latest run") {
+
expect(result.current.dags.data?.dags[0]?.latest_dag_runs[0]?.state).toBe("success");
+ } else {
+ expect(result.current.counts.data).toEqual({
+ dags: [{ dag_id: successDag.dag_id, state_counts: { success: 1 } }],
+ });
+ }
+ client.clear();
+});
diff --git a/airflow-core/src/airflow/ui/src/queries/useDags.tsx
b/airflow-core/src/airflow/ui/src/queries/useDags.tsx
index a9e6f283453..80c740f9ae0 100644
--- a/airflow-core/src/airflow/ui/src/queries/useDags.tsx
+++ b/airflow-core/src/airflow/ui/src/queries/useDags.tsx
@@ -68,7 +68,7 @@ export const useDags = ({
teams?: Array<string>;
timetableType?: Array<string>;
}) => {
- const refetchInterval = useAutoRefresh({ checkPendingRuns: true });
+ const refetchInterval = useAutoRefresh({});
const { data, error, isLoading } = useDagServiceGetDagsUi(
{