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

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


The following commit(s) were added to refs/heads/v3-1-test by this push:
     new 09add6ed452 Add optional pending dag runs check to auto refresh 
(#56014) (#56648)
09add6ed452 is described below

commit 09add6ed45263426c3ffdcf08d8db967398fc591
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Wed Oct 15 04:31:17 2025 -0700

    Add optional pending dag runs check to auto refresh (#56014) (#56648)
    
    * Add optional pending dag runs check to auto refresh
    
    * Readd hasActiveRun check for structure
    
    (cherry picked from commit a6506f2b4681a0eceddfc68f82ebaa51c7cb85bc)
    
    Co-authored-by: Brent Bovenzi <[email protected]>
---
 .../ui/src/components/NeedsReviewButton.tsx        |  7 ++---
 .../airflow/ui/src/layouts/Details/Grid/Grid.tsx   | 18 ++++--------
 airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx  |  2 +-
 .../airflow/ui/src/pages/Dag/Overview/Overview.tsx |  8 +-----
 .../src/airflow/ui/src/pages/DagsList/DagCard.tsx  |  6 ++--
 .../ui/src/pages/Dashboard/Health/Health.tsx       |  2 +-
 .../HistoricalMetrics/HistoricalMetrics.tsx        |  2 +-
 .../pages/Dashboard/PoolSummary/PoolSummary.tsx    |  2 +-
 .../airflow/ui/src/pages/Dashboard/Stats/Stats.tsx |  2 +-
 .../ui/src/pages/Task/Overview/Overview.tsx        | 11 ++------
 .../src/airflow/ui/src/queries/useGridRuns.ts      |  4 +--
 .../src/airflow/ui/src/queries/useGridStructure.ts |  3 +-
 airflow-core/src/airflow/ui/src/utils/query.ts     | 33 +++++++++++++++++++---
 13 files changed, 54 insertions(+), 46 deletions(-)

diff --git a/airflow-core/src/airflow/ui/src/components/NeedsReviewButton.tsx 
b/airflow-core/src/airflow/ui/src/components/NeedsReviewButton.tsx
index 6ceb54a3d05..c0ec70f44dd 100644
--- a/airflow-core/src/airflow/ui/src/components/NeedsReviewButton.tsx
+++ b/airflow-core/src/airflow/ui/src/components/NeedsReviewButton.tsx
@@ -27,16 +27,15 @@ import { StatsCard } from "./StatsCard";
 
 export const NeedsReviewButton = ({
   dagId,
-  refreshInterval,
   runId,
   taskId,
 }: {
   readonly dagId?: string;
-  readonly refreshInterval?: number | false;
   readonly runId?: string;
   readonly taskId?: string;
 }) => {
-  const hookAutoRefresh = useAutoRefresh({ dagId });
+  const refetchInterval = useAutoRefresh({ checkPendingRuns: true, dagId });
+
   const { data: hitlStatsData, isLoading } = 
useTaskInstanceServiceGetHitlDetails(
     {
       dagId: dagId ?? "~",
@@ -47,7 +46,7 @@ export const NeedsReviewButton = ({
     },
     undefined,
     {
-      refetchInterval: refreshInterval ?? hookAutoRefresh,
+      refetchInterval,
     },
   );
 
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx 
b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx
index 8c8311e1bf7..99b7b664483 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx
@@ -51,7 +51,6 @@ export const Grid = ({ limit, runType, showGantt, 
triggeringUser }: Props) => {
   const gridRef = useRef<HTMLDivElement>(null);
 
   const [selectedIsVisible, setSelectedIsVisible] = useState<boolean | 
undefined>();
-  const [hasActiveRun, setHasActiveRun] = useState<boolean | undefined>();
   const { openGroupIds, toggleGroupId } = useOpenGroups();
   const { dagId = "", runId = "" } = useParams();
 
@@ -69,19 +68,14 @@ export const Grid = ({ limit, runType, showGantt, 
triggeringUser }: Props) => {
     }
   }, [runId, gridRuns, selectedIsVisible, setSelectedIsVisible]);
 
-  useEffect(() => {
-    if (gridRuns) {
-      const run = gridRuns.some((dr: GridRunsResponse) => 
isStatePending(dr.state));
-
-      if (!run) {
-        setHasActiveRun(false);
-      }
-    }
-  }, [gridRuns, setHasActiveRun]);
+  const { data: dagStructure } = useGridStructure({
+    hasActiveRun: gridRuns?.some((dr) => isStatePending(dr.state)),
+    limit,
+    runType,
+    triggeringUser,
+  });
 
-  const { data: dagStructure } = useGridStructure({ hasActiveRun, limit, 
runType, triggeringUser });
   // calculate dag run bar heights relative to max
-
   const max = Math.max.apply(
     undefined,
     gridRuns === undefined
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 efd17cdecd0..c7bb74e6835 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx
@@ -92,7 +92,7 @@ export const Dag = () => {
   );
 
   const { tabs: processedTabs } = useRequiredActionTabs({ dagId }, tabs, {
-    refetchInterval: isStatePending(latestRun?.state) ? refetchInterval : 
false,
+    refetchInterval,
   });
 
   const displayTabs = processedTabs.filter((tab) => {
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx 
b/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx
index 8de8220a417..8cebcc4ba0b 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx
@@ -35,7 +35,6 @@ import TimeRangeSelector from 
"src/components/TimeRangeSelector";
 import { TrendCountButton } from "src/components/TrendCountButton";
 import { SearchParamsKeys } from "src/constants/searchParams";
 import { useGridRuns } from "src/queries/useGridRuns.ts";
-import { isStatePending, useAutoRefresh } from "src/utils";
 
 const FailedLogs = lazy(() => import("./FailedLogs"));
 
@@ -76,14 +75,9 @@ export const Overview = () => {
     timestampLte: endDate,
   });
 
-  const refetchInterval = useAutoRefresh({});
-
   return (
     <Box m={4} spaceY={4}>
-      <NeedsReviewButton
-        dagId={dagId}
-        refreshInterval={gridRuns?.some((dr) => isStatePending(dr.state)) ? 
refetchInterval : false}
-      />
+      <NeedsReviewButton dagId={dagId} />
       <Box my={2}>
         <TimeRangeSelector
           defaultValue={defaultHour}
diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/DagCard.tsx 
b/airflow-core/src/airflow/ui/src/pages/DagsList/DagCard.tsx
index b9646d4f2b7..bda8b7dc4ec 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagsList/DagCard.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/DagCard.tsx
@@ -43,7 +43,7 @@ export const DagCard = ({ dag }: Props) => {
   const { t: translate } = useTranslation(["common", "dag"]);
   const [latestRun] = dag.latest_dag_runs;
 
-  const refetchInterval = useAutoRefresh({ isPaused: dag.is_paused });
+  const refetchInterval = useAutoRefresh({});
 
   return (
     <Box borderColor="border.emphasized" borderRadius={8} borderWidth={1} 
overflow="hidden">
@@ -95,7 +95,9 @@ export const DagCard = ({ dag }: Props) => {
                   startDate={latestRun.start_date}
                   state={latestRun.state}
                 />
-                {isStatePending(latestRun.state) && Boolean(refetchInterval) ? 
<Spinner /> : undefined}
+                {isStatePending(latestRun.state) && !dag.is_paused && 
Boolean(refetchInterval) ? (
+                  <Spinner />
+                ) : undefined}
               </RouterLink>
             </Link>
           ) : undefined}
diff --git a/airflow-core/src/airflow/ui/src/pages/Dashboard/Health/Health.tsx 
b/airflow-core/src/airflow/ui/src/pages/Dashboard/Health/Health.tsx
index cb8d87f482b..054ceed5505 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dashboard/Health/Health.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dashboard/Health/Health.tsx
@@ -27,7 +27,7 @@ import { useAutoRefresh } from "src/utils";
 import { HealthBadge } from "./HealthBadge";
 
 export const Health = () => {
-  const refetchInterval = useAutoRefresh({});
+  const refetchInterval = useAutoRefresh({ checkPendingRuns: true });
 
   const { data, error, isLoading } = useMonitorServiceGetHealth(undefined, {
     refetchInterval,
diff --git 
a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/HistoricalMetrics.tsx
 
b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/HistoricalMetrics.tsx
index 2ccae75e919..560102ced34 100644
--- 
a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/HistoricalMetrics.tsx
+++ 
b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/HistoricalMetrics.tsx
@@ -41,7 +41,7 @@ export const HistoricalMetrics = () => {
   const [endDate, setEndDate] = useState(now.toISOString());
   const [assetSortBy, setAssetSortBy] = useState("-timestamp");
 
-  const refetchInterval = useAutoRefresh({});
+  const refetchInterval = useAutoRefresh({ checkPendingRuns: true });
 
   const { data, error, isLoading } = useDashboardServiceHistoricalMetrics(
     {
diff --git 
a/airflow-core/src/airflow/ui/src/pages/Dashboard/PoolSummary/PoolSummary.tsx 
b/airflow-core/src/airflow/ui/src/pages/Dashboard/PoolSummary/PoolSummary.tsx
index e2d897b01b1..be08ecb15c4 100644
--- 
a/airflow-core/src/airflow/ui/src/pages/Dashboard/PoolSummary/PoolSummary.tsx
+++ 
b/airflow-core/src/airflow/ui/src/pages/Dashboard/PoolSummary/PoolSummary.tsx
@@ -30,7 +30,7 @@ import { type Slots, slotKeys } from "src/utils/slots";
 
 export const PoolSummary = () => {
   const { t: translate } = useTranslation("dashboard");
-  const refetchInterval = useAutoRefresh({});
+  const refetchInterval = useAutoRefresh({ checkPendingRuns: true });
   const { data: authLinks } = useAuthLinksServiceGetAuthMenus();
   const hasPoolsAccess = authLinks?.authorized_menu_items.includes("Pools");
 
diff --git a/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/Stats.tsx 
b/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/Stats.tsx
index 5cfc7b2d7c2..e1027f050b5 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/Stats.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/Stats.tsx
@@ -29,7 +29,7 @@ import { DAGImportErrors } from "./DAGImportErrors";
 import { PluginImportErrors } from "./PluginImportErrors";
 
 export const Stats = () => {
-  const refetchInterval = useAutoRefresh({});
+  const refetchInterval = useAutoRefresh({ checkPendingRuns: true });
   const { data: statsData, isLoading: isStatsLoading } = 
useDashboardServiceDagStats(undefined, {
     refetchInterval,
   });
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 6eb82b710aa..d003174b987 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
@@ -54,7 +54,7 @@ export const Overview = () => {
       taskId: Boolean(groupId) ? undefined : taskId,
     });
 
-  const { data: taskInstances, isLoading: isLoadingTaskInstances } = 
useTaskInstanceServiceGetTaskInstances(
+  const { data: tiData, isLoading: isLoadingTaskInstances } = 
useTaskInstanceServiceGetTaskInstances(
     {
       dagId,
       dagRunId: "~",
@@ -72,12 +72,7 @@ export const Overview = () => {
 
   return (
     <Box m={4} spaceY={4}>
-      <NeedsReviewButton
-        refreshInterval={
-          taskInstances?.task_instances.some((ti) => isStatePending(ti.state)) 
? refetchInterval : false
-        }
-        taskId={taskId}
-      />
+      <NeedsReviewButton taskId={taskId} />
       <Box my={2}>
         <TimeRangeSelector
           defaultValue={defaultHour}
@@ -111,7 +106,7 @@ export const Overview = () => {
           {isLoadingTaskInstances ? (
             <Skeleton height="200px" w="full" />
           ) : (
-            <DurationChart 
entries={taskInstances?.task_instances.slice().reverse()} kind="Task Instance" 
/>
+            <DurationChart entries={tiData?.task_instances.slice().reverse()} 
kind="Task Instance" />
           )}
         </Box>
       </SimpleGrid>
diff --git a/airflow-core/src/airflow/ui/src/queries/useGridRuns.ts 
b/airflow-core/src/airflow/ui/src/queries/useGridRuns.ts
index af077b7b1af..90b31563f94 100644
--- a/airflow-core/src/airflow/ui/src/queries/useGridRuns.ts
+++ b/airflow-core/src/airflow/ui/src/queries/useGridRuns.ts
@@ -33,7 +33,7 @@ export const useGridRuns = ({
 }) => {
   const { dagId = "" } = useParams();
 
-  const defaultRefetchInterval = useAutoRefresh({ dagId });
+  const refetchInterval = useAutoRefresh({ dagId });
 
   const { data: GridRuns, ...rest } = useGridServiceGetGridRuns(
     {
@@ -47,7 +47,7 @@ export const useGridRuns = ({
     {
       placeholderData: (prev) => prev,
       refetchInterval: (query) =>
-        query.state.data?.some((run) => isStatePending(run.state)) && 
defaultRefetchInterval,
+        query.state.data?.some((run) => isStatePending(run.state)) && 
refetchInterval,
     },
   );
 
diff --git a/airflow-core/src/airflow/ui/src/queries/useGridStructure.ts 
b/airflow-core/src/airflow/ui/src/queries/useGridStructure.ts
index f312b028e67..a6b7f99fdf9 100644
--- a/airflow-core/src/airflow/ui/src/queries/useGridStructure.ts
+++ b/airflow-core/src/airflow/ui/src/queries/useGridStructure.ts
@@ -23,7 +23,7 @@ import type { DagRunType } from "openapi/requests/types.gen";
 import { useAutoRefresh } from "src/utils";
 
 export const useGridStructure = ({
-  hasActiveRun = undefined,
+  hasActiveRun,
   limit,
   runType,
   triggeringUser,
@@ -47,7 +47,6 @@ export const useGridStructure = ({
     },
     undefined,
     {
-      placeholderData: (prev) => prev,
       refetchInterval: hasActiveRun ? refetchInterval : false,
     },
   );
diff --git a/airflow-core/src/airflow/ui/src/utils/query.ts 
b/airflow-core/src/airflow/ui/src/utils/query.ts
index 5becaa0ac29..10ca41df30d 100644
--- a/airflow-core/src/airflow/ui/src/utils/query.ts
+++ b/airflow-core/src/airflow/ui/src/utils/query.ts
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { useDagServiceGetDagDetails } from "openapi/queries";
+import { useDagRunServiceGetDagRuns, useDagServiceGetDagDetails } from 
"openapi/queries";
 import type { TaskInstanceState } from "openapi/requests/types.gen";
 import { useConfig } from "src/queries/useConfig";
 
@@ -30,7 +30,14 @@ export const isStatePending = (state?: TaskInstanceState | 
null) =>
   state === "restarting" ||
   !Boolean(state);
 
-export const useAutoRefresh = ({ dagId, isPaused }: { dagId?: string; 
isPaused?: boolean }) => {
+// checkPendingRuns=false assumes that the component is already handling 
pending, setting to true will have useAutoRefresh handle it
+export const useAutoRefresh = ({
+  checkPendingRuns,
+  dagId,
+}: {
+  checkPendingRuns?: boolean;
+  dagId?: string;
+}) => {
   const autoRefreshInterval = useConfig("auto_refresh_interval") as number | 
undefined;
   const { data: dag } = useDagServiceGetDagDetails(
     {
@@ -40,9 +47,27 @@ export const useAutoRefresh = ({ dagId, isPaused }: { 
dagId?: string; isPaused?:
     { enabled: dagId !== undefined },
   );
 
-  const paused = isPaused ?? dag?.is_paused;
+  const { data: dagRunData } = useDagRunServiceGetDagRuns(
+    {
+      dagId: dagId ?? "~",
+      state: ["running", "queued"],
+    },
+    undefined,
+    // Scale back refetching to 10x longer if there are no pending runs (eg: 
every 3 secs for active runs, otherwise 30 secs)
+    {
+      enabled: checkPendingRuns,
+      refetchInterval: (query) =>
+        autoRefreshInterval !== undefined &&
+        ((query.state.data?.dag_runs ?? []).length > 0
+          ? autoRefreshInterval * 1000
+          : autoRefreshInterval * 10 * 1000),
+    },
+  );
+
+  const pendingRuns = checkPendingRuns ? (dagRunData?.dag_runs ?? []).length > 
1 : true;
+  const paused = Boolean(dagId) ? dag?.is_paused : false;
 
-  const canRefresh = autoRefreshInterval !== undefined && !paused;
+  const canRefresh = autoRefreshInterval !== undefined && !paused && 
pendingRuns;
 
   // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
   return (canRefresh ? autoRefreshInterval * 1000 : false) as number | false;

Reply via email to