This is an automated email from the ASF dual-hosted git repository.
pierrejeambrun pushed a commit to branch v3-2-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-2-test by this push:
new 255bf4d87cd Invalidate queries on dag run add/delete (#64269) (#64464)
255bf4d87cd is described below
commit 255bf4d87cd19f22133b30a37ceeb1eb96912f2a
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Mon Mar 30 13:53:06 2026 +0200
Invalidate queries on dag run add/delete (#64269) (#64464)
(cherry picked from commit a5fc63858331f95c145b3d761f747e776c694bbf)
Co-authored-by: Brent Bovenzi <[email protected]>
---
airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx | 23 ++++++--
.../airflow/ui/src/queries/gridViewQueryKeys.ts | 34 +++++++++++
.../src/airflow/ui/src/queries/useClearRun.ts | 10 ++--
.../ui/src/queries/useClearTaskInstances.ts | 8 ++-
.../src/airflow/ui/src/queries/useDeleteDag.ts | 7 ++-
.../src/airflow/ui/src/queries/useDeleteDagRun.ts | 6 +-
.../src/airflow/ui/src/queries/usePatchDagRun.ts | 8 ++-
.../airflow/ui/src/queries/usePatchTaskInstance.ts | 8 ++-
.../ui/src/queries/useRefreshOnNewDagRuns.ts | 65 +++++++++++-----------
.../src/airflow/ui/src/queries/useTrigger.ts | 21 ++-----
10 files changed, 124 insertions(+), 66 deletions(-)
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx
b/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx
index 15fa137bc62..f6039c68f32 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
import { ReactFlowProvider } from "@xyflow/react";
-import { useState } from "react";
+import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { FiBarChart, FiCode, FiUser, FiCalendar } from "react-icons/fi";
import { LuChartColumn } from "react-icons/lu";
@@ -59,6 +59,11 @@ export const Dag = () => {
const refetchInterval = useAutoRefresh({ dagId });
const [hasPendingRuns, setHasPendingRuns] = useState<boolean |
undefined>(false);
+ const previousLatestRunIdRef = useRef("");
+
+ useEffect(() => {
+ previousLatestRunIdRef.current = "";
+ }, [dagId]);
const {
data: dag,
@@ -96,9 +101,19 @@ export const Dag = () => {
undefined,
{
enabled: Boolean(dagId),
- refetchInterval: (query) => {
- if (query.state.data && isStatePending(query.state.data.state)) {
- setHasPendingRuns(true);
+ refetchInterval: ({ state: { data } }) => {
+ if (data) {
+ const { run_id: runId, state } = data;
+ const runIdChanged =
+ previousLatestRunIdRef.current !== "" &&
previousLatestRunIdRef.current !== runId;
+
+ if (runIdChanged || isStatePending(state)) {
+ setHasPendingRuns(true);
+ }
+
+ previousLatestRunIdRef.current = runId;
+ } else {
+ previousLatestRunIdRef.current = "";
}
return hasPendingRuns ? refetchInterval : false;
diff --git a/airflow-core/src/airflow/ui/src/queries/gridViewQueryKeys.ts
b/airflow-core/src/airflow/ui/src/queries/gridViewQueryKeys.ts
new file mode 100644
index 00000000000..42060cac610
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/queries/gridViewQueryKeys.ts
@@ -0,0 +1,34 @@
+/*!
+ * 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 {
+ UseDagRunServiceGetDagRunsKeyFn,
+ UseDagServiceGetDagDetailsKeyFn,
+ UseDagServiceGetLatestRunInfoKeyFn,
+ UseGridServiceGetGridRunsKeyFn,
+ UseTaskInstanceServiceGetTaskInstancesKeyFn,
+} from "openapi/queries";
+
+export const gridQueryKeys = (dagId: string) =>
+ [
+ UseGridServiceGetGridRunsKeyFn({ dagId }, [{ dagId }]),
+ UseDagServiceGetDagDetailsKeyFn({ dagId }, [{ dagId }]),
+ UseDagServiceGetLatestRunInfoKeyFn({ dagId }, [{ dagId }]),
+ UseDagRunServiceGetDagRunsKeyFn({ dagId }, [{ dagId }]),
+ UseTaskInstanceServiceGetTaskInstancesKeyFn({ dagId, dagRunId: "~" }, [{
dagId, dagRunId: "~" }]),
+ ] as const;
diff --git a/airflow-core/src/airflow/ui/src/queries/useClearRun.ts
b/airflow-core/src/airflow/ui/src/queries/useClearRun.ts
index 1749955525e..f4b23c90bd4 100644
--- a/airflow-core/src/airflow/ui/src/queries/useClearRun.ts
+++ b/airflow-core/src/airflow/ui/src/queries/useClearRun.ts
@@ -23,13 +23,12 @@ import {
useDagRunServiceClearDagRun,
UseDagRunServiceGetDagRunKeyFn,
useDagRunServiceGetDagRunsKey,
- UseDagServiceGetDagDetailsKeyFn,
UseGanttServiceGetGanttDataKeyFn,
useTaskInstanceServiceGetTaskInstancesKey,
- UseGridServiceGetGridRunsKeyFn,
} from "openapi/queries";
import { createErrorToaster } from "src/utils";
+import { gridQueryKeys } from "./gridViewQueryKeys";
import { useClearDagRunDryRunKey } from "./useClearDagRunDryRun";
export const useClearDagRun = ({
@@ -58,15 +57,16 @@ export const useClearDagRun = ({
const onSuccess = async () => {
const queryKeys = [
[useTaskInstanceServiceGetTaskInstancesKey],
- UseDagServiceGetDagDetailsKeyFn({ dagId }),
UseDagRunServiceGetDagRunKeyFn({ dagId, dagRunId }),
[useDagRunServiceGetDagRunsKey],
[useClearDagRunDryRunKey, dagId],
- UseGridServiceGetGridRunsKeyFn({ dagId }, [{ dagId }]),
UseGanttServiceGetGanttDataKeyFn({ dagId, runId: dagRunId }),
];
- await Promise.all(queryKeys.map((key) => queryClient.invalidateQueries({
queryKey: key })));
+ await Promise.all([
+ ...gridQueryKeys(dagId).map((key) => queryClient.invalidateQueries({
queryKey: key })),
+ ...queryKeys.map((key) => queryClient.invalidateQueries({ queryKey: key
})),
+ ]);
onSuccessConfirm();
};
diff --git a/airflow-core/src/airflow/ui/src/queries/useClearTaskInstances.ts
b/airflow-core/src/airflow/ui/src/queries/useClearTaskInstances.ts
index b3dc26c9de7..b02cc1bbac6 100644
--- a/airflow-core/src/airflow/ui/src/queries/useClearTaskInstances.ts
+++ b/airflow-core/src/airflow/ui/src/queries/useClearTaskInstances.ts
@@ -25,12 +25,12 @@ import {
UseGanttServiceGetGanttDataKeyFn,
UseTaskInstanceServiceGetMappedTaskInstanceKeyFn,
useTaskInstanceServicePostClearTaskInstances,
- UseGridServiceGetGridRunsKeyFn,
} from "openapi/queries";
import type { ApiError } from "openapi/requests";
import type { ClearTaskInstancesBody, TaskInstanceCollectionResponse } from
"openapi/requests/types.gen";
import { toaster } from "src/components/ui";
+import { gridQueryKeys } from "./gridViewQueryKeys";
import { useClearTaskInstancesDryRunKey } from "./useClearTaskInstancesDryRun";
import { usePatchTaskInstanceDryRunKey } from "./usePatchTaskInstanceDryRun";
@@ -116,11 +116,13 @@ export const useClearTaskInstances = ({
[useDagRunServiceGetDagRunsKey],
[useClearTaskInstancesDryRunKey, dagId],
[usePatchTaskInstanceDryRunKey, dagId, dagRunId],
- UseGridServiceGetGridRunsKeyFn({ dagId }, [{ dagId }]),
UseGanttServiceGetGanttDataKeyFn({ dagId, runId: dagRunId }),
];
- await Promise.all(queryKeys.map((key) => queryClient.invalidateQueries({
queryKey: key })));
+ await Promise.all([
+ ...gridQueryKeys(variables.dagId).map((key) =>
queryClient.invalidateQueries({ queryKey: key })),
+ ...queryKeys.map((key) => queryClient.invalidateQueries({ queryKey: key
})),
+ ]);
onSuccessConfirm();
};
diff --git a/airflow-core/src/airflow/ui/src/queries/useDeleteDag.ts
b/airflow-core/src/airflow/ui/src/queries/useDeleteDag.ts
index 28518dd01ef..7ff6b10496e 100644
--- a/airflow-core/src/airflow/ui/src/queries/useDeleteDag.ts
+++ b/airflow-core/src/airflow/ui/src/queries/useDeleteDag.ts
@@ -23,6 +23,7 @@ import { useDagServiceDeleteDag, useDagServiceGetDagsUiKey }
from "openapi/queri
import { useDagServiceGetDagKey } from "openapi/queries";
import { toaster } from "src/components/ui";
import { createErrorToaster } from "src/utils";
+import { gridQueryKeys } from "src/queries/gridViewQueryKeys";
export const useDeleteDag = ({
dagId,
@@ -46,7 +47,11 @@ export const useDeleteDag = ({
};
const onSuccess = async () => {
- const queryKeys = [[useDagServiceGetDagKey, { dagId }],
[useDagServiceGetDagsUiKey]];
+ const queryKeys = [
+ [useDagServiceGetDagKey, { dagId }],
+ [useDagServiceGetDagsUiKey],
+ ...gridQueryKeys(dagId),
+ ];
await Promise.all(queryKeys.map((key) => queryClient.invalidateQueries({
queryKey: key })));
diff --git a/airflow-core/src/airflow/ui/src/queries/useDeleteDagRun.ts
b/airflow-core/src/airflow/ui/src/queries/useDeleteDagRun.ts
index 91de2ba4f75..b98bbbe5120 100644
--- a/airflow-core/src/airflow/ui/src/queries/useDeleteDagRun.ts
+++ b/airflow-core/src/airflow/ui/src/queries/useDeleteDagRun.ts
@@ -28,6 +28,7 @@ import {
} from "openapi/queries";
import { toaster } from "src/components/ui";
import { createErrorToaster } from "src/utils";
+import { gridQueryKeys } from "src/queries/gridViewQueryKeys";
type DeleteDagRunParams = {
dagId: string;
@@ -58,7 +59,10 @@ export const useDeleteDagRun = ({ dagId, dagRunId,
onSuccessConfirm }: DeleteDag
[useTaskInstanceServiceGetHitlDetailsKey],
];
- await Promise.all(queryKeys.map((key) => queryClient.invalidateQueries({
queryKey: key })));
+ await Promise.all([
+ ...queryKeys.map((key) => queryClient.invalidateQueries({ queryKey: key
})),
+ ...gridQueryKeys(dagId).map((key) => queryClient.invalidateQueries({
queryKey: key })),
+ ]);
toaster.create({
description:
translate("dags:runAndTaskActions.delete.success.description", {
diff --git a/airflow-core/src/airflow/ui/src/queries/usePatchDagRun.ts
b/airflow-core/src/airflow/ui/src/queries/usePatchDagRun.ts
index 0c46147a7b4..6b78396f891 100644
--- a/airflow-core/src/airflow/ui/src/queries/usePatchDagRun.ts
+++ b/airflow-core/src/airflow/ui/src/queries/usePatchDagRun.ts
@@ -24,10 +24,10 @@ import {
useDagRunServiceGetDagRunsKey,
useDagRunServicePatchDagRun,
useTaskInstanceServiceGetTaskInstancesKey,
- UseGridServiceGetGridRunsKeyFn,
} from "openapi/queries";
import { createErrorToaster } from "src/utils";
+import { gridQueryKeys } from "./gridViewQueryKeys";
import { useClearDagRunDryRunKey } from "./useClearDagRunDryRun";
export const usePatchDagRun = ({
@@ -59,10 +59,12 @@ export const usePatchDagRun = ({
[useDagRunServiceGetDagRunsKey],
[useTaskInstanceServiceGetTaskInstancesKey, { dagId, dagRunId }],
[useClearDagRunDryRunKey, dagId],
- UseGridServiceGetGridRunsKeyFn({ dagId }, [{ dagId }]),
];
- await Promise.all(queryKeys.map((key) => queryClient.invalidateQueries({
queryKey: key })));
+ await Promise.all([
+ ...gridQueryKeys(dagId).map((key) => queryClient.invalidateQueries({
queryKey: key })),
+ ...queryKeys.map((key) => queryClient.invalidateQueries({ queryKey: key
})),
+ ]);
if (onSuccess) {
onSuccess();
diff --git a/airflow-core/src/airflow/ui/src/queries/usePatchTaskInstance.ts
b/airflow-core/src/airflow/ui/src/queries/usePatchTaskInstance.ts
index 402e8d63540..6b7a2a1d1ca 100644
--- a/airflow-core/src/airflow/ui/src/queries/usePatchTaskInstance.ts
+++ b/airflow-core/src/airflow/ui/src/queries/usePatchTaskInstance.ts
@@ -24,10 +24,10 @@ import {
UseTaskInstanceServiceGetTaskInstanceKeyFn,
useTaskInstanceServiceGetTaskInstancesKey,
useTaskInstanceServicePatchTaskInstance,
- UseGridServiceGetGridRunsKeyFn,
} from "openapi/queries";
import { createErrorToaster } from "src/utils";
+import { gridQueryKeys } from "./gridViewQueryKeys";
import { useClearTaskInstancesDryRunKey } from "./useClearTaskInstancesDryRun";
import { usePatchTaskInstanceDryRunKey } from "./usePatchTaskInstanceDryRun";
@@ -65,10 +65,12 @@ export const usePatchTaskInstance = ({
[useTaskInstanceServiceGetTaskInstancesKey],
[usePatchTaskInstanceDryRunKey, dagId, dagRunId, { mapIndex, taskId }],
[useClearTaskInstancesDryRunKey, dagId],
- UseGridServiceGetGridRunsKeyFn({ dagId }, [{ dagId }]),
];
- await Promise.all(queryKeys.map((key) => queryClient.invalidateQueries({
queryKey: key })));
+ await Promise.all([
+ ...gridQueryKeys(dagId).map((key) => queryClient.invalidateQueries({
queryKey: key })),
+ ...queryKeys.map((key) => queryClient.invalidateQueries({ queryKey: key
})),
+ ]);
if (onSuccess) {
onSuccess();
diff --git a/airflow-core/src/airflow/ui/src/queries/useRefreshOnNewDagRuns.ts
b/airflow-core/src/airflow/ui/src/queries/useRefreshOnNewDagRuns.ts
index 8c3314ea940..2fb7673ad29 100644
--- a/airflow-core/src/airflow/ui/src/queries/useRefreshOnNewDagRuns.ts
+++ b/airflow-core/src/airflow/ui/src/queries/useRefreshOnNewDagRuns.ts
@@ -21,56 +21,59 @@ import { useEffect, useRef } from "react";
import {
useDagServiceGetDagDetailsKey,
- UseDagRunServiceGetDagRunsKeyFn,
- UseDagServiceGetDagDetailsKeyFn,
- useDagServiceGetDagsUi,
- UseTaskInstanceServiceGetTaskInstancesKeyFn,
- UseGridServiceGetDagStructureKeyFn,
- UseGridServiceGetGridRunsKeyFn,
+ useDagServiceGetDagsUiKey,
useDagServiceGetLatestRunInfo,
} from "openapi/queries";
+import { gridQueryKeys } from "./gridViewQueryKeys";
import { useConfig } from "./useConfig";
export const useRefreshOnNewDagRuns = (dagId: string, hasPendingRuns: boolean
| undefined) => {
const queryClient = useQueryClient();
- const previousDagRunIdRef = useRef<string>("");
+ const hasSyncedLatestRunRef = useRef(false);
+ const previousLatestRunSignatureRef = useRef("");
const autoRefreshInterval = useConfig("auto_refresh_interval") as number;
+ const pollIntervalMs = Boolean(autoRefreshInterval) ? autoRefreshInterval *
1000 : 5000;
+
const { data: latestDagRun } = useDagServiceGetLatestRunInfo({ dagId },
undefined, {
- enabled: Boolean(dagId) && !hasPendingRuns,
- refetchInterval: Boolean(autoRefreshInterval) ? autoRefreshInterval * 1000
: 5000,
+ enabled: Boolean(dagId),
+ refetchInterval: Boolean(dagId) && !hasPendingRuns ? pollIntervalMs :
false,
});
useEffect(() => {
- const latestDagRunId = latestDagRun?.run_id;
+ hasSyncedLatestRunRef.current = false;
+ previousLatestRunSignatureRef.current = "";
+ }, [dagId]);
- if (latestDagRunId !== undefined && previousDagRunIdRef.current === "") {
- previousDagRunIdRef.current = latestDagRunId;
+ useEffect(() => {
+ if (!dagId) {
+ return;
+ }
+ if (latestDagRun === undefined) {
return;
}
- if (
- latestDagRunId !== undefined &&
- previousDagRunIdRef.current !== "" &&
- previousDagRunIdRef.current !== latestDagRunId
- ) {
- previousDagRunIdRef.current = latestDagRunId;
+ const signature = latestDagRun?.run_id ?? "";
+
+ if (!hasSyncedLatestRunRef.current) {
+ hasSyncedLatestRunRef.current = true;
+ previousLatestRunSignatureRef.current = signature;
- const queryKeys = [
- [useDagServiceGetDagsUi],
- [useDagServiceGetDagDetailsKey],
- UseDagServiceGetDagDetailsKeyFn({ dagId }, [{ dagId }]),
- UseDagRunServiceGetDagRunsKeyFn({ dagId }, [{ dagId }]),
- UseTaskInstanceServiceGetTaskInstancesKeyFn({ dagId, dagRunId: "~" },
[{ dagId, dagRunId: "~" }]),
- UseGridServiceGetDagStructureKeyFn({ dagId }, [{ dagId }]),
- UseGridServiceGetGridRunsKeyFn({ dagId }, [{ dagId }]),
- ];
+ return;
+ }
- queryKeys.forEach((key) => {
- void queryClient.invalidateQueries({ queryKey: key });
- });
+ if (previousLatestRunSignatureRef.current === signature) {
+ return;
}
- }, [latestDagRun, dagId, queryClient]);
+
+ previousLatestRunSignatureRef.current = signature;
+
+ void Promise.all([
+ queryClient.invalidateQueries({ queryKey: [useDagServiceGetDagsUiKey] }),
+ queryClient.invalidateQueries({ queryKey:
[useDagServiceGetDagDetailsKey] }),
+ ...gridQueryKeys(dagId).map((key) => queryClient.invalidateQueries({
queryKey: key })),
+ ]);
+ }, [dagId, latestDagRun, queryClient]);
};
diff --git a/airflow-core/src/airflow/ui/src/queries/useTrigger.ts
b/airflow-core/src/airflow/ui/src/queries/useTrigger.ts
index 16252358afa..c7af554129c 100644
--- a/airflow-core/src/airflow/ui/src/queries/useTrigger.ts
+++ b/airflow-core/src/airflow/ui/src/queries/useTrigger.ts
@@ -21,17 +21,12 @@ import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "react-router-dom";
-import {
- UseDagRunServiceGetDagRunsKeyFn,
- useDagRunServiceTriggerDagRun,
- useDagServiceGetDagsUiKey,
- UseTaskInstanceServiceGetTaskInstancesKeyFn,
- UseGridServiceGetGridRunsKeyFn,
-} from "openapi/queries";
+import { useDagRunServiceTriggerDagRun, useDagServiceGetDagsUiKey } from
"openapi/queries";
import type { TriggerDagRunResponse } from "openapi/requests/types.gen";
import type { DagRunTriggerParams } from "src/components/TriggerDag/types";
import { toaster } from "src/components/ui";
import { createErrorToaster } from "src/utils";
+import { gridQueryKeys } from "src/queries/gridViewQueryKeys";
export const useTrigger = ({ dagId, onSuccessConfirm }: { dagId: string;
onSuccessConfirm: () => void }) => {
const queryClient = useQueryClient();
@@ -41,14 +36,10 @@ export const useTrigger = ({ dagId, onSuccessConfirm }: {
dagId: string; onSucce
const { dagId: selectedDagId } = useParams();
const onSuccess = async (dagRun: TriggerDagRunResponse) => {
- const queryKeys = [
- [useDagServiceGetDagsUiKey],
- UseDagRunServiceGetDagRunsKeyFn({ dagId }, [{ dagId }]),
- UseTaskInstanceServiceGetTaskInstancesKeyFn({ dagId, dagRunId: "~" }, [{
dagId, dagRunId: "~" }]),
- UseGridServiceGetGridRunsKeyFn({ dagId }, [{ dagId }]),
- ];
-
- await Promise.all(queryKeys.map((key) => queryClient.invalidateQueries({
queryKey: key })));
+ await Promise.all([
+ queryClient.invalidateQueries({ queryKey: [useDagServiceGetDagsUiKey] }),
+ ...gridQueryKeys(dagId).map((key) => queryClient.invalidateQueries({
queryKey: key })),
+ ]);
toaster.create({
description: translate("triggerDag.toaster.success.description"),