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;