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

pierrejeambrun 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 7c648ab63a4 Add filter with run_after field in dags/:dagid/runs page 
(#62797)
7c648ab63a4 is described below

commit 7c648ab63a45acbbe64bdb8d5ff69c79617eb636
Author: Prajwal7842 <[email protected]>
AuthorDate: Wed Mar 18 22:45:02 2026 +0530

    Add filter with run_after field in dags/:dagid/runs page (#62797)
    
    * Add filter for run_after field in dags/:dagid/runs page
    
    * Use existing date time picker for run after filter
    
    * Fix overflow for filter when vertical space is limited
---
 .../src/airflow/ui/src/constants/localStorage.ts   |  2 +
 .../ui/src/layouts/Details/DetailsLayout.tsx       | 10 +++
 .../airflow/ui/src/layouts/Details/Grid/Grid.tsx   | 13 +++-
 .../ui/src/layouts/Details/PanelButtons.tsx        | 71 +++++++++++++++++++++-
 .../src/airflow/ui/src/queries/useGridRuns.ts      |  6 ++
 5 files changed, 99 insertions(+), 3 deletions(-)

diff --git a/airflow-core/src/airflow/ui/src/constants/localStorage.ts 
b/airflow-core/src/airflow/ui/src/constants/localStorage.ts
index 72fd47b0909..ab8398438d9 100644
--- a/airflow-core/src/airflow/ui/src/constants/localStorage.ts
+++ b/airflow-core/src/airflow/ui/src/constants/localStorage.ts
@@ -34,6 +34,8 @@ export const dagRunsLimitKey = (dagId: string) => 
`dag_runs_limit-${dagId}`;
 export const runTypeFilterKey = (dagId: string) => `run_type_filter-${dagId}`;
 export const triggeringUserFilterKey = (dagId: string) => 
`triggering_user_filter-${dagId}`;
 export const dagRunStateFilterKey = (dagId: string) => 
`dag_run_state_filter-${dagId}`;
+export const runAfterGteKey = (dagId: string) => `run_after_gte-${dagId}`;
+export const runAfterLteKey = (dagId: string) => `run_after_lte-${dagId}`;
 export const showGanttKey = (dagId: string) => `show_gantt-${dagId}`;
 export const dependenciesKey = (dagId: string) => `dependencies-${dagId}`;
 export const directionKey = (dagId: string) => `direction-${dagId}`;
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx 
b/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
index 71635ea6dbf..075f710b10f 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
@@ -48,6 +48,8 @@ import {
   dagRunStateFilterKey,
   dagViewKey,
   DEFAULT_DAG_VIEW_KEY,
+  runAfterGteKey,
+  runAfterLteKey,
   runTypeFilterKey,
   showGanttKey,
   triggeringUserFilterKey,
@@ -77,6 +79,8 @@ export const DetailsLayout = ({ children, error, isLoading, 
tabs }: Props) => {
   const panelGroupRef = useRef<ImperativePanelGroupHandle | null>(null);
   const [dagView, setDagView] = useLocalStorage<"graph" | 
"grid">(dagViewKey(dagId), defaultDagView);
   const [limit, setLimit] = useLocalStorage<number>(dagRunsLimitKey(dagId), 
10);
+  const [runAfterGte, setRunAfterGte] = useLocalStorage<string | 
undefined>(runAfterGteKey(dagId), undefined);
+  const [runAfterLte, setRunAfterLte] = useLocalStorage<string | 
undefined>(runAfterLteKey(dagId), undefined);
   const [runTypeFilter, setRunTypeFilter] = useLocalStorage<DagRunType | 
undefined>(
     runTypeFilterKey(dagId),
     undefined,
@@ -163,10 +167,14 @@ export const DetailsLayout = ({ children, error, 
isLoading, tabs }: Props) => {
                   dagView={dagView}
                   limit={limit}
                   panelGroupRef={panelGroupRef}
+                  runAfterGte={runAfterGte}
+                  runAfterLte={runAfterLte}
                   runTypeFilter={runTypeFilter}
                   setDagRunStateFilter={setDagRunStateFilter}
                   setDagView={setDagView}
                   setLimit={setLimit}
+                  setRunAfterGte={setRunAfterGte}
+                  setRunAfterLte={setRunAfterLte}
                   setRunTypeFilter={setRunTypeFilter}
                   setShowGantt={setShowGantt}
                   setShowVersionIndicatorMode={setShowVersionIndicatorMode}
@@ -182,6 +190,8 @@ export const DetailsLayout = ({ children, error, isLoading, 
tabs }: Props) => {
                     <Grid
                       dagRunState={dagRunStateFilter}
                       limit={limit}
+                      runAfterGte={runAfterGte}
+                      runAfterLte={runAfterLte}
                       runType={runTypeFilter}
                       showGantt={Boolean(runId) && showGantt}
                       showVersionIndicatorMode={showVersionIndicatorMode}
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 07537c2cb2b..dbe30598010 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
@@ -53,6 +53,8 @@ dayjs.extend(dayjsDuration);
 type Props = {
   readonly dagRunState?: DagRunState | undefined;
   readonly limit: number;
+  readonly runAfterGte?: string;
+  readonly runAfterLte?: string;
   readonly runType?: DagRunType | undefined;
   readonly showGantt?: boolean;
   readonly showVersionIndicatorMode?: VersionIndicatorOptions;
@@ -62,6 +64,8 @@ type Props = {
 export const Grid = ({
   dagRunState,
   limit,
+  runAfterGte,
+  runAfterLte,
   runType,
   showGantt,
   showVersionIndicatorMode,
@@ -82,7 +86,14 @@ export const Grid = ({
   const depthParam = searchParams.get("depth");
   const depth = depthParam !== null && depthParam !== "" ? 
parseInt(depthParam, 10) : undefined;
 
-  const { data: gridRuns, isLoading } = useGridRuns({ dagRunState, limit, 
runType, triggeringUser });
+  const { data: gridRuns, isLoading } = useGridRuns({
+    dagRunState,
+    limit,
+    runAfterGte,
+    runAfterLte,
+    runType,
+    triggeringUser,
+  });
 
   // Check if the selected dag run is inside of the grid response, if not, 
we'll update the grid filters
   // Eventually we should redo the api endpoint to make this work better
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/PanelButtons.tsx 
b/airflow-core/src/airflow/ui/src/layouts/Details/PanelButtons.tsx
index 3b2334b5170..860f9a4d200 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/PanelButtons.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/PanelButtons.tsx
@@ -43,6 +43,9 @@ import { useLocalStorage } from "usehooks-ts";
 
 import type { DagRunState, DagRunType } from "openapi/requests/types.gen";
 import { DagVersionSelect } from "src/components/DagVersionSelect";
+import { DateRangeCalendar } from 
"src/components/FilterBar/filters/DateRangeCalendar";
+import { DateRangeInputs } from 
"src/components/FilterBar/filters/DateRangeInputs";
+import type { DateRangeValue } from "src/components/FilterBar/types";
 import { directionOptions, type Direction } from 
"src/components/Graph/useGraphLayout";
 import { RunTypeIcon } from "src/components/RunTypeIcon";
 import { SearchBar } from "src/components/SearchBar";
@@ -53,6 +56,7 @@ import { Checkbox } from "src/components/ui/Checkbox";
 import { dependenciesKey, directionKey } from "src/constants/localStorage";
 import type { VersionIndicatorOptions } from 
"src/constants/showVersionIndicatorOptions";
 import { dagRunTypeOptions, dagRunStateOptions } from 
"src/constants/stateOptions";
+import { useDateRangeFilter } from "src/hooks/useDateRangeFilter";
 import { useContainerWidth } from "src/utils/useContainerWidth";
 
 import { DagRunSelect } from "./DagRunSelect";
@@ -66,10 +70,14 @@ type Props = {
   readonly dagView: "graph" | "grid";
   readonly limit: number;
   readonly panelGroupRef: React.RefObject<ImperativePanelGroupHandle | null>;
+  readonly runAfterGte: string | undefined;
+  readonly runAfterLte: string | undefined;
   readonly runTypeFilter: DagRunType | undefined;
   readonly setDagRunStateFilter: 
React.Dispatch<React.SetStateAction<DagRunState | undefined>>;
   readonly setDagView: (x: "graph" | "grid") => void;
   readonly setLimit: React.Dispatch<React.SetStateAction<number>>;
+  readonly setRunAfterGte: React.Dispatch<React.SetStateAction<string | 
undefined>>;
+  readonly setRunAfterLte: React.Dispatch<React.SetStateAction<string | 
undefined>>;
   readonly setRunTypeFilter: React.Dispatch<React.SetStateAction<DagRunType | 
undefined>>;
   readonly setShowGantt: React.Dispatch<React.SetStateAction<boolean>>;
   readonly setShowVersionIndicatorMode: 
React.Dispatch<React.SetStateAction<VersionIndicatorOptions>>;
@@ -117,10 +125,14 @@ export const PanelButtons = ({
   dagView,
   limit,
   panelGroupRef,
+  runAfterGte,
+  runAfterLte,
   runTypeFilter,
   setDagRunStateFilter,
   setDagView,
   setLimit,
+  setRunAfterGte,
+  setRunAfterLte,
   setRunTypeFilter,
   setShowGantt,
   setShowVersionIndicatorMode,
@@ -129,7 +141,7 @@ export const PanelButtons = ({
   showVersionIndicatorMode,
   triggeringUserFilter,
 }: Props) => {
-  const { t: translate } = useTranslation(["components", "dag"]);
+  const { t: translate } = useTranslation(["common", "components", "dag"]);
   const { dagId = "", runId } = useParams();
   const { fitView } = useReactFlow();
   const shouldShowToggleButtons = Boolean(runId);
@@ -201,6 +213,30 @@ export const PanelButtons = ({
     setTriggeringUserFilter(trimmedValue === "" ? undefined : trimmedValue);
   };
 
+  const runAfterRange: DateRangeValue = {
+    endDate: runAfterLte,
+    startDate: runAfterGte,
+  };
+
+  const handleRunAfterRangeChange = (next: DateRangeValue) => {
+    setRunAfterGte(next.startDate);
+    setRunAfterLte(next.endDate);
+  };
+
+  const {
+    editingState,
+    endDateValue,
+    getFieldError,
+    handleDateClick: handleRunAfterDateClick,
+    handleInputChange: handleRunAfterInputChange,
+    setEditingState,
+    startDateValue,
+  } = useDateRangeFilter({
+    onChange: handleRunAfterRangeChange,
+    translate,
+    value: runAfterRange,
+  });
+
   const handleFocus = (view: string) => {
     if (panelGroupRef.current) {
       const newLayout = view === "graph" ? [70, 30] : [30, 70];
@@ -274,7 +310,14 @@ export const PanelButtons = ({
               <Popover.Positioner>
                 <Popover.Content>
                   <Popover.Arrow />
-                  <Popover.Body display="flex" flexDirection="column" gap={4} 
p={2}>
+                  <Popover.Body
+                    display="flex"
+                    flexDirection="column"
+                    gap={4}
+                    maxH="70vh"
+                    overflowY="auto"
+                    p={2}
+                  >
                     {dagView === "graph" ? (
                       <>
                         <DagVersionSelect />
@@ -470,6 +513,30 @@ export const PanelButtons = ({
                             
placeholder={translate("common:dagRun.triggeringUser")}
                           />
                         </VStack>
+                        <VStack alignItems="flex-start">
+                          <Text fontSize="xs" mb={1}>
+                            {translate("common:dagRun.runAfter")}
+                          </Text>
+                          <DateRangeInputs
+                            editingState={editingState}
+                            endDateValue={endDateValue}
+                            getFieldError={getFieldError}
+                            handleInputChange={handleRunAfterInputChange}
+                            onChange={handleRunAfterRangeChange}
+                            setEditingState={setEditingState}
+                            startDateValue={startDateValue}
+                            translate={translate}
+                            value={runAfterRange}
+                          />
+                          <DateRangeCalendar
+                            currentMonth={editingState.currentMonth}
+                            onDateClick={handleRunAfterDateClick}
+                            onMonthChange={(month) =>
+                              setEditingState((prev) => ({ ...prev, 
currentMonth: month }))
+                            }
+                            value={runAfterRange}
+                          />
+                        </VStack>
                         {shouldShowToggleButtons ? (
                           <VStack alignItems="flex-start" px={1}>
                             <Checkbox checked={showGantt} onChange={() => 
setShowGantt(!showGantt)} size="sm">
diff --git a/airflow-core/src/airflow/ui/src/queries/useGridRuns.ts 
b/airflow-core/src/airflow/ui/src/queries/useGridRuns.ts
index 35508b8ecfa..69f3df332ae 100644
--- a/airflow-core/src/airflow/ui/src/queries/useGridRuns.ts
+++ b/airflow-core/src/airflow/ui/src/queries/useGridRuns.ts
@@ -25,11 +25,15 @@ import { isStatePending, useAutoRefresh } from "src/utils";
 export const useGridRuns = ({
   dagRunState,
   limit,
+  runAfterGte,
+  runAfterLte,
   runType,
   triggeringUser,
 }: {
   dagRunState?: DagRunState | undefined;
   limit: number;
+  runAfterGte?: string;
+  runAfterLte?: string;
   runType?: DagRunType | undefined;
   triggeringUser?: string | undefined;
 }) => {
@@ -42,6 +46,8 @@ export const useGridRuns = ({
       dagId,
       limit,
       orderBy: ["-run_after"],
+      runAfterGte: runAfterGte ?? undefined,
+      runAfterLte: runAfterLte ?? undefined,
       runType: runType ? [runType] : undefined,
       state: dagRunState ? [dagRunState] : undefined,
       triggeringUser: triggeringUser ?? undefined,

Reply via email to