This is an automated email from the ASF dual-hosted git repository.
RNHTTR 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 80db0f249fd Backfills UI filters (#71467)
80db0f249fd is described below
commit 80db0f249fddcbfac67e63ef387a4de1294ccbe7
Author: Phinhas Asmelash <[email protected]>
AuthorDate: Tue Sep 8 05:04:53 2026 -0700
Backfills UI filters (#71467)
* added filters to the backfills tab ui
* Frontend unit tests for backfill filter component
* Documentation for backfill tab view
* Fix Backfills tab documentation for Dag UI
* clean up unwanted comments
* removed irrelevant backfill documenation
* Add backfill filter translation keys
* Refactor tests after new backfill functionality
* added filters to the backfills tab ui
* Documentation for backfill tab view
* clean up unwanted comments
* removed irrelevant backfill documenation
* Refactor tests after new backfill functionality
* Update airflow-core/docs/ui.rst
Co-authored-by: Brent Bovenzi <[email protected]>
* Update
airflow-core/src/airflow/api_fastapi/core_api/routes/ui/backfills.py
Co-authored-by: Brent Bovenzi <[email protected]>
* Update
airflow-core/src/airflow/api_fastapi/core_api/routes/ui/backfills.py
Co-authored-by: Brent Bovenzi <[email protected]>
* Rename date range values to be consistent with backfill values
* Validate reprocess behavior as reprocess behavior type
* Fix reprocess behavior type error and delete duplicate code
* Fix reprocess behavior parameter names
* Add translation keys to backfill reprocess behavior options
* Move completed at backfill filter to common filters
* Remove unused search param constants
* Add back the needed search param constants
* Added more relevant icon to backfill max_active_runs filter type
* Fix reprocess behavior casting issue and change factory for
max_active_runs
* Apply suggestion from @bbovenzi
* Static check fix for filterConfigMap
* Fix from lint checks
---------
Co-authored-by: Brent Bovenzi <[email protected]>
---
.../src/airflow/api_fastapi/common/parameters.py | 22 +++
.../api_fastapi/core_api/openapi/_private_ui.yaml | 184 +++++++++++++++++++++
.../api_fastapi/core_api/routes/ui/backfills.py | 25 ++-
.../src/airflow/ui/openapi-gen/queries/common.ts | 27 ++-
.../ui/openapi-gen/queries/ensureQueryData.ts | 48 +++++-
.../src/airflow/ui/openapi-gen/queries/prefetch.ts | 48 +++++-
.../src/airflow/ui/openapi-gen/queries/queries.ts | 48 +++++-
.../src/airflow/ui/openapi-gen/queries/suspense.ts | 48 +++++-
.../ui/openapi-gen/requests/services.gen.ts | 42 +++++
.../airflow/ui/openapi-gen/requests/types.gen.ts | 21 +++
.../airflow/ui/public/i18n/locales/en/common.json | 3 +
.../src/airflow/ui/src/constants/filterConfigs.tsx | 46 +++++-
.../ui/src/constants/reprocessBehaviourParams.ts | 6 +-
.../src/airflow/ui/src/constants/searchParams.ts | 12 ++
.../src/airflow/ui/src/mocks/handlers/backfills.ts | 86 ++++++++++
.../src/airflow/ui/src/mocks/handlers/index.ts | 2 +
.../ui/src/pages/Dag/Backfills/Backfills.test.tsx | 21 ++-
.../ui/src/pages/Dag/Backfills/Backfills.tsx | 59 ++++++-
.../Dag/Backfills/BackfillsDateFilter.test.tsx | 52 ++++++
.../src/pages/Dag/Backfills/BackfillsFilters.tsx | 48 ++++++
.../src/airflow/ui/src/utils/useFiltersHandler.ts | 6 +
.../core_api/routes/ui/test_backfills.py | 85 +++++++++-
22 files changed, 910 insertions(+), 29 deletions(-)
diff --git a/airflow-core/src/airflow/api_fastapi/common/parameters.py
b/airflow-core/src/airflow/api_fastapi/common/parameters.py
index dfc255f4683..a7a870df99b 100644
--- a/airflow-core/src/airflow/api_fastapi/common/parameters.py
+++ b/airflow-core/src/airflow/api_fastapi/common/parameters.py
@@ -1268,6 +1268,28 @@ def float_range_filter_factory(
return depends_float
+def int_range_filter_factory(
+ filter_name: str, model: Base
+) -> Callable[[int | None, int | None, int | None, int | None], RangeFilter]:
+ def depends_int(
+ lower_bound_gte: int | None = Query(alias=f"{filter_name}_gte",
default=None),
+ lower_bound_gt: int | None = Query(alias=f"{filter_name}_gt",
default=None),
+ upper_bound_lte: int | None = Query(alias=f"{filter_name}_lte",
default=None),
+ upper_bound_lt: int | None = Query(alias=f"{filter_name}_lt",
default=None),
+ ) -> RangeFilter:
+ return RangeFilter(
+ Range(
+ lower_bound_gte=lower_bound_gte,
+ lower_bound_gt=lower_bound_gt,
+ upper_bound_lte=upper_bound_lte,
+ upper_bound_lt=upper_bound_lt,
+ ),
+ getattr(model, filter_name),
+ )
+
+ return depends_int
+
+
# Common Safe DateTime
DateTimeQuery = Annotated[str, AfterValidator(_safe_parse_datetime)]
OptionalDateTimeQuery = Annotated[str | None,
AfterValidator(_safe_parse_datetime_optional)]
diff --git
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
index f713c4137dc..a51f805f09d 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
+++ b/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
@@ -1356,6 +1356,190 @@ paths:
minimum: 0
default: 0
title: Offset
+ - name: from_date_gte
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: string
+ format: date-time
+ - type: 'null'
+ title: From Date Gte
+ - name: from_date_gt
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: string
+ format: date-time
+ - type: 'null'
+ title: From Date Gt
+ - name: from_date_lte
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: string
+ format: date-time
+ - type: 'null'
+ title: From Date Lte
+ - name: from_date_lt
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: string
+ format: date-time
+ - type: 'null'
+ title: From Date Lt
+ - name: to_date_gte
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: string
+ format: date-time
+ - type: 'null'
+ title: To Date Gte
+ - name: to_date_gt
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: string
+ format: date-time
+ - type: 'null'
+ title: To Date Gt
+ - name: to_date_lte
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: string
+ format: date-time
+ - type: 'null'
+ title: To Date Lte
+ - name: to_date_lt
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: string
+ format: date-time
+ - type: 'null'
+ title: To Date Lt
+ - name: created_at_gte
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: string
+ format: date-time
+ - type: 'null'
+ title: Created At Gte
+ - name: created_at_gt
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: string
+ format: date-time
+ - type: 'null'
+ title: Created At Gt
+ - name: created_at_lte
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: string
+ format: date-time
+ - type: 'null'
+ title: Created At Lte
+ - name: created_at_lt
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: string
+ format: date-time
+ - type: 'null'
+ title: Created At Lt
+ - name: completed_at_gte
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: string
+ format: date-time
+ - type: 'null'
+ title: Completed At Gte
+ - name: completed_at_gt
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: string
+ format: date-time
+ - type: 'null'
+ title: Completed At Gt
+ - name: completed_at_lte
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: string
+ format: date-time
+ - type: 'null'
+ title: Completed At Lte
+ - name: completed_at_lt
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: string
+ format: date-time
+ - type: 'null'
+ title: Completed At Lt
+ - name: max_active_runs_gte
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: integer
+ - type: 'null'
+ title: Max Active Runs Gte
+ - name: max_active_runs_gt
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: integer
+ - type: 'null'
+ title: Max Active Runs Gt
+ - name: max_active_runs_lte
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: integer
+ - type: 'null'
+ title: Max Active Runs Lte
+ - name: max_active_runs_lt
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: integer
+ - type: 'null'
+ title: Max Active Runs Lt
+ - name: reprocess_behavior
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - $ref: '#/components/schemas/ReprocessBehavior'
+ - type: 'null'
+ title: Reprocess Behavior
- name: order_by
in: query
required: false
diff --git
a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/backfills.py
b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/backfills.py
index 02583a8355b..ff8782e9316 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/backfills.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/backfills.py
@@ -28,8 +28,11 @@ from airflow.api_fastapi.common.parameters import (
FilterParam,
QueryLimit,
QueryOffset,
+ RangeFilter,
SortParam,
+ datetime_range_filter_factory,
filter_param_factory,
+ int_range_filter_factory,
)
from airflow.api_fastapi.common.router import AirflowRouter
from airflow.api_fastapi.core_api.datamodels.backfills import
BackfillCollectionResponse, BackfillResponse
@@ -37,7 +40,7 @@ from airflow.api_fastapi.core_api.openapi.exceptions import (
create_openapi_http_exception_doc,
)
from airflow.api_fastapi.core_api.security import ReadableBackfillsFilterDep,
requires_access_backfill
-from airflow.models.backfill import Backfill
+from airflow.models.backfill import Backfill, ReprocessBehavior
backfills_router = AirflowRouter(tags=["Backfill"], prefix="/backfills")
@@ -52,6 +55,14 @@ backfills_router = AirflowRouter(tags=["Backfill"],
prefix="/backfills")
def list_backfills_ui(
limit: QueryLimit,
offset: QueryOffset,
+ start_date_range: Annotated[RangeFilter,
Depends(datetime_range_filter_factory("from_date", Backfill))],
+ end_date_range: Annotated[RangeFilter,
Depends(datetime_range_filter_factory("to_date", Backfill))],
+ created_at: Annotated[RangeFilter,
Depends(datetime_range_filter_factory("created_at", Backfill))],
+ completed_at: Annotated[RangeFilter,
Depends(datetime_range_filter_factory("completed_at", Backfill))],
+ max_active_runs: Annotated[RangeFilter,
Depends(int_range_filter_factory("max_active_runs", Backfill))],
+ reprocess_behavior: Annotated[
+ FilterParam, Depends(filter_param_factory(Backfill.reprocess_behavior,
ReprocessBehavior | None))
+ ],
order_by: Annotated[
SortParam,
Depends(SortParam(["id"], Backfill).dynamic_depends()),
@@ -66,7 +77,17 @@ def list_backfills_ui(
) -> BackfillCollectionResponse:
select_stmt, total_entries = paginated_select(
statement=select(Backfill).options(joinedload(Backfill.dag_model)),
- filters=[dag_id, active, readable_backfills_filter],
+ filters=[
+ dag_id,
+ active,
+ readable_backfills_filter,
+ start_date_range,
+ end_date_range,
+ created_at,
+ completed_at,
+ max_active_runs,
+ reprocess_behavior,
+ ],
order_by=order_by,
offset=offset,
limit=limit,
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
index 206e6c29c87..0feff40cc43 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
@@ -2,7 +2,7 @@
import { UseQueryResult } from "@tanstack/react-query";
import { AssetService, AssetStateStoreService, AuthLinksService,
BackfillService, CalendarService, ConfigService, ConnectionService,
DagParsingService, DagRunService, DagService, DagSourceService,
DagStatsService, DagVersionService, DagWarningService, DashboardService,
DeadlinesService, DependenciesService, EventLogService, ExperimentalService,
ExtraLinksService, GanttService, GridService, ImportErrorService, JobService,
LoginService, MonitorService, PartitionedDagRunService, PluginServi [...]
-import { DagRunState, DagWarningType } from "../requests/types.gen";
+import { DagRunState, DagWarningType, ReprocessBehavior } from
"../requests/types.gen";
export type AssetServiceGetAssetsDefaultResponse = Awaited<ReturnType<typeof
AssetService.getAssets>>;
export type AssetServiceGetAssetsQueryResult<TData =
AssetServiceGetAssetsDefaultResponse, TError = unknown> = UseQueryResult<TData,
TError>;
export const useAssetServiceGetAssetsKey = "AssetServiceGetAssets";
@@ -138,13 +138,34 @@ export const UseBackfillServiceListBackfillDagRunsKeyFn =
({ backfillId, limit,
export type BackfillServiceListBackfillsUiDefaultResponse =
Awaited<ReturnType<typeof BackfillService.listBackfillsUi>>;
export type BackfillServiceListBackfillsUiQueryResult<TData =
BackfillServiceListBackfillsUiDefaultResponse, TError = unknown> =
UseQueryResult<TData, TError>;
export const useBackfillServiceListBackfillsUiKey =
"BackfillServiceListBackfillsUi";
-export const UseBackfillServiceListBackfillsUiKeyFn = ({ active, dagId, limit,
offset, orderBy }: {
+export const UseBackfillServiceListBackfillsUiKeyFn = ({ active,
completedAtGt, completedAtGte, completedAtLt, completedAtLte, createdAtGt,
createdAtGte, createdAtLt, createdAtLte, dagId, fromDateGt, fromDateGte,
fromDateLt, fromDateLte, limit, maxActiveRunsGt, maxActiveRunsGte,
maxActiveRunsLt, maxActiveRunsLte, offset, orderBy, reprocessBehavior,
toDateGt, toDateGte, toDateLt, toDateLte }: {
active?: boolean;
+ completedAtGt?: string;
+ completedAtGte?: string;
+ completedAtLt?: string;
+ completedAtLte?: string;
+ createdAtGt?: string;
+ createdAtGte?: string;
+ createdAtLt?: string;
+ createdAtLte?: string;
dagId?: string;
+ fromDateGt?: string;
+ fromDateGte?: string;
+ fromDateLt?: string;
+ fromDateLte?: string;
limit?: number;
+ maxActiveRunsGt?: number;
+ maxActiveRunsGte?: number;
+ maxActiveRunsLt?: number;
+ maxActiveRunsLte?: number;
offset?: number;
orderBy?: string[];
-} = {}, queryKey?: Array<unknown>) => [useBackfillServiceListBackfillsUiKey,
...(queryKey ?? [{ active, dagId, limit, offset, orderBy }])];
+ reprocessBehavior?: ReprocessBehavior;
+ toDateGt?: string;
+ toDateGte?: string;
+ toDateLt?: string;
+ toDateLte?: string;
+} = {}, queryKey?: Array<unknown>) => [useBackfillServiceListBackfillsUiKey,
...(queryKey ?? [{ active, completedAtGt, completedAtGte, completedAtLt,
completedAtLte, createdAtGt, createdAtGte, createdAtLt, createdAtLte, dagId,
fromDateGt, fromDateGte, fromDateLt, fromDateLte, limit, maxActiveRunsGt,
maxActiveRunsGte, maxActiveRunsLt, maxActiveRunsLte, offset, orderBy,
reprocessBehavior, toDateGt, toDateGte, toDateLt, toDateLte }])];
export type ConnectionServiceGetConnectionDefaultResponse =
Awaited<ReturnType<typeof ConnectionService.getConnection>>;
export type ConnectionServiceGetConnectionQueryResult<TData =
ConnectionServiceGetConnectionDefaultResponse, TError = unknown> =
UseQueryResult<TData, TError>;
export const useConnectionServiceGetConnectionKey =
"ConnectionServiceGetConnection";
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
index bd88502f1e1..a554befbafe 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
@@ -2,7 +2,7 @@
import { type QueryClient } from "@tanstack/react-query";
import { AssetService, AssetStateStoreService, AuthLinksService,
BackfillService, CalendarService, ConfigService, ConnectionService,
DagRunService, DagService, DagSourceService, DagStatsService,
DagVersionService, DagWarningService, DashboardService, DeadlinesService,
DependenciesService, EventLogService, ExperimentalService, ExtraLinksService,
GanttService, GridService, ImportErrorService, JobService, LoginService,
MonitorService, PartitionedDagRunService, PluginService, PoolService, Pr [...]
-import { DagRunState, DagWarningType } from "../requests/types.gen";
+import { DagRunState, DagWarningType, ReprocessBehavior } from
"../requests/types.gen";
import * as Common from "./common";
/**
* Get Assets
@@ -257,19 +257,61 @@ export const
ensureUseBackfillServiceListBackfillDagRunsData = (queryClient: Que
* @param data The data for the request.
* @param data.limit
* @param data.offset
+* @param data.fromDateGte
+* @param data.fromDateGt
+* @param data.fromDateLte
+* @param data.fromDateLt
+* @param data.toDateGte
+* @param data.toDateGt
+* @param data.toDateLte
+* @param data.toDateLt
+* @param data.createdAtGte
+* @param data.createdAtGt
+* @param data.createdAtLte
+* @param data.createdAtLt
+* @param data.completedAtGte
+* @param data.completedAtGt
+* @param data.completedAtLte
+* @param data.completedAtLt
+* @param data.maxActiveRunsGte
+* @param data.maxActiveRunsGt
+* @param data.maxActiveRunsLte
+* @param data.maxActiveRunsLt
+* @param data.reprocessBehavior
* @param data.orderBy Attributes to order by, multi criteria sort is
supported. Prefix with `-` for descending order. Supported attributes: `id`
* @param data.dagId
* @param data.active
* @returns BackfillCollectionResponse Successful Response
* @throws ApiError
*/
-export const ensureUseBackfillServiceListBackfillsUiData = (queryClient:
QueryClient, { active, dagId, limit, offset, orderBy }: {
+export const ensureUseBackfillServiceListBackfillsUiData = (queryClient:
QueryClient, { active, completedAtGt, completedAtGte, completedAtLt,
completedAtLte, createdAtGt, createdAtGte, createdAtLt, createdAtLte, dagId,
fromDateGt, fromDateGte, fromDateLt, fromDateLte, limit, maxActiveRunsGt,
maxActiveRunsGte, maxActiveRunsLt, maxActiveRunsLte, offset, orderBy,
reprocessBehavior, toDateGt, toDateGte, toDateLt, toDateLte }: {
active?: boolean;
+ completedAtGt?: string;
+ completedAtGte?: string;
+ completedAtLt?: string;
+ completedAtLte?: string;
+ createdAtGt?: string;
+ createdAtGte?: string;
+ createdAtLt?: string;
+ createdAtLte?: string;
dagId?: string;
+ fromDateGt?: string;
+ fromDateGte?: string;
+ fromDateLt?: string;
+ fromDateLte?: string;
limit?: number;
+ maxActiveRunsGt?: number;
+ maxActiveRunsGte?: number;
+ maxActiveRunsLt?: number;
+ maxActiveRunsLte?: number;
offset?: number;
orderBy?: string[];
-} = {}) => queryClient.ensureQueryData({ queryKey:
Common.UseBackfillServiceListBackfillsUiKeyFn({ active, dagId, limit, offset,
orderBy }), queryFn: () => BackfillService.listBackfillsUi({ active, dagId,
limit, offset, orderBy }) });
+ reprocessBehavior?: ReprocessBehavior;
+ toDateGt?: string;
+ toDateGte?: string;
+ toDateLt?: string;
+ toDateLte?: string;
+} = {}) => queryClient.ensureQueryData({ queryKey:
Common.UseBackfillServiceListBackfillsUiKeyFn({ active, completedAtGt,
completedAtGte, completedAtLt, completedAtLte, createdAtGt, createdAtGte,
createdAtLt, createdAtLte, dagId, fromDateGt, fromDateGte, fromDateLt,
fromDateLte, limit, maxActiveRunsGt, maxActiveRunsGte, maxActiveRunsLt,
maxActiveRunsLte, offset, orderBy, reprocessBehavior, toDateGt, toDateGte,
toDateLt, toDateLte }), queryFn: () => BackfillService.listBackfillsUi({ activ
[...]
/**
* Get Connection
* Get a connection entry.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
index 8d28a3aa001..e8e799e50c3 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
@@ -2,7 +2,7 @@
import { type QueryClient } from "@tanstack/react-query";
import { AssetService, AssetStateStoreService, AuthLinksService,
BackfillService, CalendarService, ConfigService, ConnectionService,
DagRunService, DagService, DagSourceService, DagStatsService,
DagVersionService, DagWarningService, DashboardService, DeadlinesService,
DependenciesService, EventLogService, ExperimentalService, ExtraLinksService,
GanttService, GridService, ImportErrorService, JobService, LoginService,
MonitorService, PartitionedDagRunService, PluginService, PoolService, Pr [...]
-import { DagRunState, DagWarningType } from "../requests/types.gen";
+import { DagRunState, DagWarningType, ReprocessBehavior } from
"../requests/types.gen";
import * as Common from "./common";
/**
* Get Assets
@@ -257,19 +257,61 @@ export const
prefetchUseBackfillServiceListBackfillDagRuns = (queryClient: Query
* @param data The data for the request.
* @param data.limit
* @param data.offset
+* @param data.fromDateGte
+* @param data.fromDateGt
+* @param data.fromDateLte
+* @param data.fromDateLt
+* @param data.toDateGte
+* @param data.toDateGt
+* @param data.toDateLte
+* @param data.toDateLt
+* @param data.createdAtGte
+* @param data.createdAtGt
+* @param data.createdAtLte
+* @param data.createdAtLt
+* @param data.completedAtGte
+* @param data.completedAtGt
+* @param data.completedAtLte
+* @param data.completedAtLt
+* @param data.maxActiveRunsGte
+* @param data.maxActiveRunsGt
+* @param data.maxActiveRunsLte
+* @param data.maxActiveRunsLt
+* @param data.reprocessBehavior
* @param data.orderBy Attributes to order by, multi criteria sort is
supported. Prefix with `-` for descending order. Supported attributes: `id`
* @param data.dagId
* @param data.active
* @returns BackfillCollectionResponse Successful Response
* @throws ApiError
*/
-export const prefetchUseBackfillServiceListBackfillsUi = (queryClient:
QueryClient, { active, dagId, limit, offset, orderBy }: {
+export const prefetchUseBackfillServiceListBackfillsUi = (queryClient:
QueryClient, { active, completedAtGt, completedAtGte, completedAtLt,
completedAtLte, createdAtGt, createdAtGte, createdAtLt, createdAtLte, dagId,
fromDateGt, fromDateGte, fromDateLt, fromDateLte, limit, maxActiveRunsGt,
maxActiveRunsGte, maxActiveRunsLt, maxActiveRunsLte, offset, orderBy,
reprocessBehavior, toDateGt, toDateGte, toDateLt, toDateLte }: {
active?: boolean;
+ completedAtGt?: string;
+ completedAtGte?: string;
+ completedAtLt?: string;
+ completedAtLte?: string;
+ createdAtGt?: string;
+ createdAtGte?: string;
+ createdAtLt?: string;
+ createdAtLte?: string;
dagId?: string;
+ fromDateGt?: string;
+ fromDateGte?: string;
+ fromDateLt?: string;
+ fromDateLte?: string;
limit?: number;
+ maxActiveRunsGt?: number;
+ maxActiveRunsGte?: number;
+ maxActiveRunsLt?: number;
+ maxActiveRunsLte?: number;
offset?: number;
orderBy?: string[];
-} = {}) => queryClient.prefetchQuery({ queryKey:
Common.UseBackfillServiceListBackfillsUiKeyFn({ active, dagId, limit, offset,
orderBy }), queryFn: () => BackfillService.listBackfillsUi({ active, dagId,
limit, offset, orderBy }) });
+ reprocessBehavior?: ReprocessBehavior;
+ toDateGt?: string;
+ toDateGte?: string;
+ toDateLt?: string;
+ toDateLte?: string;
+} = {}) => queryClient.prefetchQuery({ queryKey:
Common.UseBackfillServiceListBackfillsUiKeyFn({ active, completedAtGt,
completedAtGte, completedAtLt, completedAtLte, createdAtGt, createdAtGte,
createdAtLt, createdAtLte, dagId, fromDateGt, fromDateGte, fromDateLt,
fromDateLte, limit, maxActiveRunsGt, maxActiveRunsGte, maxActiveRunsLt,
maxActiveRunsLte, offset, orderBy, reprocessBehavior, toDateGt, toDateGte,
toDateLt, toDateLte }), queryFn: () => BackfillService.listBackfillsUi({
active, [...]
/**
* Get Connection
* Get a connection entry.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
index 9c7971fb2d6..97d4a025a51 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
@@ -2,7 +2,7 @@
import { UseMutationOptions, UseQueryOptions, useMutation, useQuery } from
"@tanstack/react-query";
import { AssetService, AssetStateStoreService, AuthLinksService,
BackfillService, CalendarService, ConfigService, ConnectionService,
DagParsingService, DagRunService, DagService, DagSourceService,
DagStatsService, DagVersionService, DagWarningService, DashboardService,
DeadlinesService, DependenciesService, EventLogService, ExperimentalService,
ExtraLinksService, GanttService, GridService, ImportErrorService, JobService,
LoginService, MonitorService, PartitionedDagRunService, PluginServi [...]
-import { AssetStateStoreBody, BackfillPostBody, BulkBody_BulkDAGRunBody_,
BulkBody_BulkTaskInstanceBody_, BulkBody_ConnectionBody_, BulkBody_PoolBody_,
BulkBody_VariableBody_, BulkDAGRunClearBody, ClearPartitionsBody,
ClearTaskInstancesBody, ConnectionBody, ConnectionTestRequestBody,
CreateAssetEventsBody, DAGPatchBody, DAGRunClearBody, DAGRunPatchBody,
DAGRunsBatchBody, DagRunState, DagWarningType, GenerateTokenBody,
MaterializeAssetBody, PatchTaskInstanceBody, PoolBody, PoolPatchBody, [...]
+import { AssetStateStoreBody, BackfillPostBody, BulkBody_BulkDAGRunBody_,
BulkBody_BulkTaskInstanceBody_, BulkBody_ConnectionBody_, BulkBody_PoolBody_,
BulkBody_VariableBody_, BulkDAGRunClearBody, ClearPartitionsBody,
ClearTaskInstancesBody, ConnectionBody, ConnectionTestRequestBody,
CreateAssetEventsBody, DAGPatchBody, DAGRunClearBody, DAGRunPatchBody,
DAGRunsBatchBody, DagRunState, DagWarningType, GenerateTokenBody,
MaterializeAssetBody, PatchTaskInstanceBody, PoolBody, PoolPatchBody, [...]
import * as Common from "./common";
/**
* Get Assets
@@ -257,19 +257,61 @@ export const useBackfillServiceListBackfillDagRuns =
<TData = Common.BackfillSer
* @param data The data for the request.
* @param data.limit
* @param data.offset
+* @param data.fromDateGte
+* @param data.fromDateGt
+* @param data.fromDateLte
+* @param data.fromDateLt
+* @param data.toDateGte
+* @param data.toDateGt
+* @param data.toDateLte
+* @param data.toDateLt
+* @param data.createdAtGte
+* @param data.createdAtGt
+* @param data.createdAtLte
+* @param data.createdAtLt
+* @param data.completedAtGte
+* @param data.completedAtGt
+* @param data.completedAtLte
+* @param data.completedAtLt
+* @param data.maxActiveRunsGte
+* @param data.maxActiveRunsGt
+* @param data.maxActiveRunsLte
+* @param data.maxActiveRunsLt
+* @param data.reprocessBehavior
* @param data.orderBy Attributes to order by, multi criteria sort is
supported. Prefix with `-` for descending order. Supported attributes: `id`
* @param data.dagId
* @param data.active
* @returns BackfillCollectionResponse Successful Response
* @throws ApiError
*/
-export const useBackfillServiceListBackfillsUi = <TData =
Common.BackfillServiceListBackfillsUiDefaultResponse, TError = unknown,
TQueryKey extends Array<unknown> = unknown[]>({ active, dagId, limit, offset,
orderBy }: {
+export const useBackfillServiceListBackfillsUi = <TData =
Common.BackfillServiceListBackfillsUiDefaultResponse, TError = unknown,
TQueryKey extends Array<unknown> = unknown[]>({ active, completedAtGt,
completedAtGte, completedAtLt, completedAtLte, createdAtGt, createdAtGte,
createdAtLt, createdAtLte, dagId, fromDateGt, fromDateGte, fromDateLt,
fromDateLte, limit, maxActiveRunsGt, maxActiveRunsGte, maxActiveRunsLt,
maxActiveRunsLte, offset, orderBy, reprocessBehavior, toDateGt, toDateGte, [...]
active?: boolean;
+ completedAtGt?: string;
+ completedAtGte?: string;
+ completedAtLt?: string;
+ completedAtLte?: string;
+ createdAtGt?: string;
+ createdAtGte?: string;
+ createdAtLt?: string;
+ createdAtLte?: string;
dagId?: string;
+ fromDateGt?: string;
+ fromDateGte?: string;
+ fromDateLt?: string;
+ fromDateLte?: string;
limit?: number;
+ maxActiveRunsGt?: number;
+ maxActiveRunsGte?: number;
+ maxActiveRunsLt?: number;
+ maxActiveRunsLte?: number;
offset?: number;
orderBy?: string[];
-} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey:
Common.UseBackfillServiceListBackfillsUiKeyFn({ active, dagId, limit, offset,
orderBy }, queryKey), queryFn: () => BackfillService.listBackfillsUi({ active,
dagId, limit, offset, orderBy }) as TData, ...options });
+ reprocessBehavior?: ReprocessBehavior;
+ toDateGt?: string;
+ toDateGte?: string;
+ toDateLt?: string;
+ toDateLte?: string;
+} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey:
Common.UseBackfillServiceListBackfillsUiKeyFn({ active, completedAtGt,
completedAtGte, completedAtLt, completedAtLte, createdAtGt, createdAtGte,
createdAtLt, createdAtLte, dagId, fromDateGt, fromDateGte, fromDateLt,
fromDateLte, limit, maxActiveRunsGt, maxActiveRunsGte, maxActiveRunsLt,
maxActiveRunsLte, offset, orderBy, reprocessBehavior, toDateGt, [...]
/**
* Get Connection
* Get a connection entry.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
index 6fd74cd8483..e983042bf0e 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
@@ -2,7 +2,7 @@
import { UseQueryOptions, useSuspenseQuery } from "@tanstack/react-query";
import { AssetService, AssetStateStoreService, AuthLinksService,
BackfillService, CalendarService, ConfigService, ConnectionService,
DagRunService, DagService, DagSourceService, DagStatsService,
DagVersionService, DagWarningService, DashboardService, DeadlinesService,
DependenciesService, EventLogService, ExperimentalService, ExtraLinksService,
GanttService, GridService, ImportErrorService, JobService, LoginService,
MonitorService, PartitionedDagRunService, PluginService, PoolService, Pr [...]
-import { DagRunState, DagWarningType } from "../requests/types.gen";
+import { DagRunState, DagWarningType, ReprocessBehavior } from
"../requests/types.gen";
import * as Common from "./common";
/**
* Get Assets
@@ -257,19 +257,61 @@ export const
useBackfillServiceListBackfillDagRunsSuspense = <TData = Common.Bac
* @param data The data for the request.
* @param data.limit
* @param data.offset
+* @param data.fromDateGte
+* @param data.fromDateGt
+* @param data.fromDateLte
+* @param data.fromDateLt
+* @param data.toDateGte
+* @param data.toDateGt
+* @param data.toDateLte
+* @param data.toDateLt
+* @param data.createdAtGte
+* @param data.createdAtGt
+* @param data.createdAtLte
+* @param data.createdAtLt
+* @param data.completedAtGte
+* @param data.completedAtGt
+* @param data.completedAtLte
+* @param data.completedAtLt
+* @param data.maxActiveRunsGte
+* @param data.maxActiveRunsGt
+* @param data.maxActiveRunsLte
+* @param data.maxActiveRunsLt
+* @param data.reprocessBehavior
* @param data.orderBy Attributes to order by, multi criteria sort is
supported. Prefix with `-` for descending order. Supported attributes: `id`
* @param data.dagId
* @param data.active
* @returns BackfillCollectionResponse Successful Response
* @throws ApiError
*/
-export const useBackfillServiceListBackfillsUiSuspense = <TData =
Common.BackfillServiceListBackfillsUiDefaultResponse, TError = unknown,
TQueryKey extends Array<unknown> = unknown[]>({ active, dagId, limit, offset,
orderBy }: {
+export const useBackfillServiceListBackfillsUiSuspense = <TData =
Common.BackfillServiceListBackfillsUiDefaultResponse, TError = unknown,
TQueryKey extends Array<unknown> = unknown[]>({ active, completedAtGt,
completedAtGte, completedAtLt, completedAtLte, createdAtGt, createdAtGte,
createdAtLt, createdAtLte, dagId, fromDateGt, fromDateGte, fromDateLt,
fromDateLte, limit, maxActiveRunsGt, maxActiveRunsGte, maxActiveRunsLt,
maxActiveRunsLte, offset, orderBy, reprocessBehavior, toDateGt, to [...]
active?: boolean;
+ completedAtGt?: string;
+ completedAtGte?: string;
+ completedAtLt?: string;
+ completedAtLte?: string;
+ createdAtGt?: string;
+ createdAtGte?: string;
+ createdAtLt?: string;
+ createdAtLte?: string;
dagId?: string;
+ fromDateGt?: string;
+ fromDateGte?: string;
+ fromDateLt?: string;
+ fromDateLte?: string;
limit?: number;
+ maxActiveRunsGt?: number;
+ maxActiveRunsGte?: number;
+ maxActiveRunsLt?: number;
+ maxActiveRunsLte?: number;
offset?: number;
orderBy?: string[];
-} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey:
Common.UseBackfillServiceListBackfillsUiKeyFn({ active, dagId, limit, offset,
orderBy }, queryKey), queryFn: () => BackfillService.listBackfillsUi({ active,
dagId, limit, offset, orderBy }) as TData, ...options });
+ reprocessBehavior?: ReprocessBehavior;
+ toDateGt?: string;
+ toDateGte?: string;
+ toDateLt?: string;
+ toDateLte?: string;
+} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey:
Common.UseBackfillServiceListBackfillsUiKeyFn({ active, completedAtGt,
completedAtGte, completedAtLt, completedAtLte, createdAtGt, createdAtGte,
createdAtLt, createdAtLte, dagId, fromDateGt, fromDateGte, fromDateLt,
fromDateLte, limit, maxActiveRunsGt, maxActiveRunsGte, maxActiveRunsLt,
maxActiveRunsLte, offset, orderBy, reprocessBehavior, t [...]
/**
* Get Connection
* Get a connection entry.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
index 0564552f6eb..7a35da59e9b 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
@@ -691,6 +691,27 @@ export class BackfillService {
* @param data The data for the request.
* @param data.limit
* @param data.offset
+ * @param data.fromDateGte
+ * @param data.fromDateGt
+ * @param data.fromDateLte
+ * @param data.fromDateLt
+ * @param data.toDateGte
+ * @param data.toDateGt
+ * @param data.toDateLte
+ * @param data.toDateLt
+ * @param data.createdAtGte
+ * @param data.createdAtGt
+ * @param data.createdAtLte
+ * @param data.createdAtLt
+ * @param data.completedAtGte
+ * @param data.completedAtGt
+ * @param data.completedAtLte
+ * @param data.completedAtLt
+ * @param data.maxActiveRunsGte
+ * @param data.maxActiveRunsGt
+ * @param data.maxActiveRunsLte
+ * @param data.maxActiveRunsLt
+ * @param data.reprocessBehavior
* @param data.orderBy Attributes to order by, multi criteria sort is
supported. Prefix with `-` for descending order. Supported attributes: `id`
* @param data.dagId
* @param data.active
@@ -704,6 +725,27 @@ export class BackfillService {
query: {
limit: data.limit,
offset: data.offset,
+ from_date_gte: data.fromDateGte,
+ from_date_gt: data.fromDateGt,
+ from_date_lte: data.fromDateLte,
+ from_date_lt: data.fromDateLt,
+ to_date_gte: data.toDateGte,
+ to_date_gt: data.toDateGt,
+ to_date_lte: data.toDateLte,
+ to_date_lt: data.toDateLt,
+ created_at_gte: data.createdAtGte,
+ created_at_gt: data.createdAtGt,
+ created_at_lte: data.createdAtLte,
+ created_at_lt: data.createdAtLt,
+ completed_at_gte: data.completedAtGte,
+ completed_at_gt: data.completedAtGt,
+ completed_at_lte: data.completedAtLte,
+ completed_at_lt: data.completedAtLt,
+ max_active_runs_gte: data.maxActiveRunsGte,
+ max_active_runs_gt: data.maxActiveRunsGt,
+ max_active_runs_lte: data.maxActiveRunsLte,
+ max_active_runs_lt: data.maxActiveRunsLt,
+ reprocess_behavior: data.reprocessBehavior,
order_by: data.orderBy,
dag_id: data.dagId,
active: data.active
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
index da6f155d7c5..a4c17a6723d 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
@@ -3176,13 +3176,34 @@ export type CreateBackfillDryRunResponse =
DryRunBackfillCollectionResponse;
export type ListBackfillsUiData = {
active?: boolean | null;
+ completedAtGt?: string | null;
+ completedAtGte?: string | null;
+ completedAtLt?: string | null;
+ completedAtLte?: string | null;
+ createdAtGt?: string | null;
+ createdAtGte?: string | null;
+ createdAtLt?: string | null;
+ createdAtLte?: string | null;
dagId?: string | null;
+ fromDateGt?: string | null;
+ fromDateGte?: string | null;
+ fromDateLt?: string | null;
+ fromDateLte?: string | null;
limit?: number;
+ maxActiveRunsGt?: number | null;
+ maxActiveRunsGte?: number | null;
+ maxActiveRunsLt?: number | null;
+ maxActiveRunsLte?: number | null;
offset?: number;
/**
* Attributes to order by, multi criteria sort is supported. Prefix with
`-` for descending order. Supported attributes: `id`
*/
orderBy?: Array<(string)>;
+ reprocessBehavior?: ReprocessBehavior | null;
+ toDateGt?: string | null;
+ toDateGte?: string | null;
+ toDateLt?: string | null;
+ toDateLte?: string | null;
};
export type ListBackfillsUiResponse = BackfillCollectionResponse;
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
b/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
index 2bed02c1a22..43025657615 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
@@ -140,12 +140,15 @@
"filter": "Filter",
"filters": {
"addFilter": "Add Filter",
+ "completedAt": "Completed At",
"date": "Date",
"durationFrom": "Duration From",
"durationTo": "Duration To",
"endTime": "End Time",
"logicalDateFrom": "Logical Date From",
"logicalDateTo": "Logical Date To",
+ "maxActiveRunsFrom": "Max Active Runs From",
+ "maxActiveRunsTo": "Max Active Runs To",
"removeFilter": "Remove Filter",
"runAfterFrom": "Run After From",
"runAfterTo": "Run After To",
diff --git a/airflow-core/src/airflow/ui/src/constants/filterConfigs.tsx
b/airflow-core/src/airflow/ui/src/constants/filterConfigs.tsx
index bfc4fedfb74..1faabf884ab 100644
--- a/airflow-core/src/airflow/ui/src/constants/filterConfigs.tsx
+++ b/airflow-core/src/airflow/ui/src/constants/filterConfigs.tsx
@@ -16,10 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
+/* eslint-disable max-lines */
import { Box } from "@chakra-ui/react";
import { useTranslation } from "react-i18next";
import { BiTargetLock } from "react-icons/bi";
-import { FiBarChart, FiDatabase, FiTag, FiUser, FiUsers } from
"react-icons/fi";
+import { FiActivity, FiBarChart, FiDatabase, FiTag, FiUser, FiUsers } from
"react-icons/fi";
import { LuBrackets } from "react-icons/lu";
import {
MdBuild,
@@ -49,6 +50,7 @@ import { StateBadge } from "src/components/StateBadge";
import { DagIcon } from "src/assets/DagIcon";
import { TaskIcon } from "src/assets/TaskIcon";
+import { reprocessBehaviors } from "src/constants/reprocessBehaviourParams";
import {
dagRunStateOptions,
dagRunTypeOptions,
@@ -118,6 +120,13 @@ export const useFilterConfigs = () => {
label: translate("components:versionDetails.bundleVersion"),
type: FilterTypes.TEXT,
},
+ [SearchParamsKeys.COMPLETED_AT_RANGE]: {
+ endKey: SearchParamsKeys.COMPLETED_AT_LTE,
+ icon: <MdDateRange />,
+ label: translate("common:filters.completedAt"),
+ startKey: SearchParamsKeys.COMPLETED_AT_GTE,
+ type: FilterTypes.DATERANGE,
+ },
[SearchParamsKeys.CONF_CONTAINS]: {
hotkeyDisabled: true,
icon: <MdCode />,
@@ -227,6 +236,13 @@ export const useFilterConfigs = () => {
placeholder: translate("dags:filters.favoriteStatePlaceholder"),
type: FilterTypes.SELECT,
},
+ [SearchParamsKeys.FROM_RANGE]: {
+ endKey: SearchParamsKeys.FROM_DATE_LTE,
+ icon: <MdDateRange />,
+ label: translate("common:table.from"),
+ startKey: SearchParamsKeys.FROM_DATE_GTE,
+ type: FilterTypes.DATERANGE,
+ },
[SearchParamsKeys.GROUP_PATTERN]: {
hotkeyDisabled: true,
icon: <FiDatabase />,
@@ -292,6 +308,18 @@ export const useFilterConfigs = () => {
min: -1,
type: FilterTypes.NUMBER,
},
+ [SearchParamsKeys.MAX_ACTIVE_RUNS_GTE]: {
+ icon: <FiActivity />,
+ label: translate("common:filters.maxActiveRunsFrom"),
+ min: 1,
+ type: FilterTypes.NUMBER,
+ },
+ [SearchParamsKeys.MAX_ACTIVE_RUNS_LTE]: {
+ icon: <FiActivity />,
+ label: translate("common:filters.maxActiveRunsTo"),
+ min: 1,
+ type: FilterTypes.NUMBER,
+ },
[SearchParamsKeys.MISSED]: {
icon: <MdCheckCircle />,
label: translate("browse:deadlines.filters.status"),
@@ -366,6 +394,15 @@ export const useFilterConfigs = () => {
supportsAdvancedSearch: true,
type: FilterTypes.TEXT,
},
+ [SearchParamsKeys.REPROCESS_BEHAVIOR]: {
+ icon: <MdPlayArrow />,
+ label: translate("components:backfill.reprocessBehavior"),
+ options: reprocessBehaviors.map((option) => ({
+ label: translate(option.label),
+ value: option.value,
+ })),
+ type: FilterTypes.SELECT,
+ },
[SearchParamsKeys.RESPONDED_BY_USER_NAME]: {
hotkeyDisabled: true,
icon: <FiUser />,
@@ -483,6 +520,13 @@ export const useFilterConfigs = () => {
label: translate("dags:filters.timetableType"),
type: FilterTypes.MULTISELECT,
},
+ [SearchParamsKeys.TO_RANGE]: {
+ endKey: SearchParamsKeys.TO_DATE_LTE,
+ icon: <MdDateRange />,
+ label: translate("common:table.from"),
+ startKey: SearchParamsKeys.TO_DATE_GTE,
+ type: FilterTypes.DATERANGE,
+ },
[SearchParamsKeys.TRIGGERING_USER_NAME_PATTERN]: {
hotkeyDisabled: true,
icon: <FiUser />,
diff --git
a/airflow-core/src/airflow/ui/src/constants/reprocessBehaviourParams.ts
b/airflow-core/src/airflow/ui/src/constants/reprocessBehaviourParams.ts
index d19f435fdeb..957f22050d8 100644
--- a/airflow-core/src/airflow/ui/src/constants/reprocessBehaviourParams.ts
+++ b/airflow-core/src/airflow/ui/src/constants/reprocessBehaviourParams.ts
@@ -17,7 +17,7 @@
* under the License.
*/
export const reprocessBehaviors = [
- { label: "backfill.missingRuns", value: "none" },
- { label: "backfill.missingAndErroredRuns", value: "failed" },
- { label: "backfill.allRuns", value: "completed" },
+ { label: "components:backfill.missingRuns", value: "none" },
+ { label: "components:backfill.missingAndErroredRuns", value: "failed" },
+ { label: "components:backfill.allRuns", value: "completed" },
];
diff --git a/airflow-core/src/airflow/ui/src/constants/searchParams.ts
b/airflow-core/src/airflow/ui/src/constants/searchParams.ts
index b2ac5532911..5a08c1c8c11 100644
--- a/airflow-core/src/airflow/ui/src/constants/searchParams.ts
+++ b/airflow-core/src/airflow/ui/src/constants/searchParams.ts
@@ -22,6 +22,9 @@ export enum SearchParamsKeys {
BEFORE = "before",
BODY_SEARCH = "body_search",
BUNDLE_VERSION = "bundle_version",
+ COMPLETED_AT_GTE = "completed_at_gte",
+ COMPLETED_AT_LTE = "completed_at_lte",
+ COMPLETED_AT_RANGE = "completed_at_range",
CONF_CONTAINS = "conf_contains",
CONSUMING_ASSET_PATTERN = "consuming_asset_pattern",
CREATED_AT_GTE = "created_at_gte",
@@ -48,6 +51,9 @@ export enum SearchParamsKeys {
EXCLUDED_EVENTS = "excluded_events",
EXECUTOR_CLASS = "executor_class",
FAVORITE = "favorite",
+ FROM_DATE_GTE = "from_date_gte",
+ FROM_DATE_LTE = "from_date_lte",
+ FROM_RANGE = "from_range",
GANTT = "gantt",
GRAPH_DURATION_GTE = "graph-duration_gte",
GRAPH_MAP_INDEX = "graph-map_index",
@@ -71,6 +77,8 @@ export enum SearchParamsKeys {
LOGICAL_DATE_RANGE = "logical_date_range",
MAP_INDEX = "map_index",
MAPPED = "mapped",
+ MAX_ACTIVE_RUNS_GTE = "max_active_runs_gte",
+ MAX_ACTIVE_RUNS_LTE = "max_active_runs_lte",
MISSED = "missed",
NAME_PATTERN = "name_pattern",
NEEDS_REVIEW = "needs_review",
@@ -84,6 +92,7 @@ export enum SearchParamsKeys {
POOL_NAME_PATTERN = "pool_name_pattern",
QUEUE_NAME_PATTERN = "queue_name_pattern",
RENDERED_MAP_INDEX = "rendered_map_index",
+ REPROCESS_BEHAVIOR = "reprocess_behavior",
RESPONDED_BY_USER_NAME = "responded_by_user_name",
RESPONSE_RECEIVED = "response_received",
RETRIES = "retries",
@@ -109,6 +118,9 @@ export enum SearchParamsKeys {
TASK_STATE = "task_state",
TEAMS = "teams",
TIMETABLE_TYPE = "timetable_type",
+ TO_DATE_GTE = "to_date_gte",
+ TO_DATE_LTE = "to_date_lte",
+ TO_RANGE = "to_range",
TRIGGER_RULE = "trigger_rule",
TRIGGERING_USER = "triggering_user",
TRIGGERING_USER_NAME_PATTERN = "triggering_user_name_pattern",
diff --git a/airflow-core/src/airflow/ui/src/mocks/handlers/backfills.ts
b/airflow-core/src/airflow/ui/src/mocks/handlers/backfills.ts
new file mode 100644
index 00000000000..ee895e16987
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/mocks/handlers/backfills.ts
@@ -0,0 +1,86 @@
+/*!
+ * 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 { http, HttpResponse, type HttpHandler } from "msw";
+
+const backfillBeforeFilter = {
+ completed_at: "2024-12-31T01:00:00Z",
+ created_at: "2024-12-31T00:00:00Z",
+ dag_display_name: "tutorial_taskflow_api",
+ dag_id: "tutorial_taskflow_api",
+ dag_run_conf: {},
+ from_date: "2024-12-01T00:00:00Z",
+ id: 1,
+ is_paused: false,
+ max_active_runs: 10,
+ reprocess_behavior: "none",
+ to_date: "2024-12-31T00:00:00Z",
+ updated_at: "2024-12-31T01:00:00Z",
+};
+
+const backfillInRange = {
+ completed_at: "2025-01-16T01:00:00Z",
+ created_at: "2025-01-16T00:00:00Z",
+ dag_display_name: "tutorial_taskflow_api",
+ dag_id: "tutorial_taskflow_api",
+ dag_run_conf: {},
+ from_date: "2025-01-01T00:00:00Z",
+ id: 2,
+ is_paused: false,
+ max_active_runs: 10,
+ reprocess_behavior: "none",
+ to_date: "2025-01-15T00:00:00Z",
+ updated_at: "2025-01-16T01:00:00Z",
+};
+
+export const handlers: Array<HttpHandler> = [
+ http.get("/ui/backfills", ({ request }) => {
+ const url = new URL(request.url);
+ const fromDateGte = url.searchParams.get("from_date_gte");
+ const fromDateLte = url.searchParams.get("from_date_lte");
+ const toDateGte = url.searchParams.get("to_date_gte");
+ const toDateLte = url.searchParams.get("to_date_lte");
+
+ const allBackfills = [backfillBeforeFilter, backfillInRange];
+
+ const filtered = allBackfills.filter((backfill) => {
+ const fromDate = new Date(backfill.from_date);
+ const toDate = new Date(backfill.to_date);
+
+ if (fromDateGte !== null && fromDate < new Date(fromDateGte)) {
+ return false;
+ }
+ if (fromDateLte !== null && fromDate > new Date(fromDateLte)) {
+ return false;
+ }
+ if (toDateGte !== null && toDate < new Date(toDateGte)) {
+ return false;
+ }
+ if (toDateLte !== null && toDate > new Date(toDateLte)) {
+ return false;
+ }
+
+ return true;
+ });
+
+ return HttpResponse.json({
+ backfills: filtered,
+ total_entries: filtered.length,
+ });
+ }),
+];
diff --git a/airflow-core/src/airflow/ui/src/mocks/handlers/index.ts
b/airflow-core/src/airflow/ui/src/mocks/handlers/index.ts
index 3d9e36b8602..57f95730c1b 100644
--- a/airflow-core/src/airflow/ui/src/mocks/handlers/index.ts
+++ b/airflow-core/src/airflow/ui/src/mocks/handlers/index.ts
@@ -17,6 +17,7 @@
* under the License.
*/
import { handlers as assetsHandlers } from "./assets";
+import { handlers as backfillsHandlers } from "./backfills";
import { handlers as configHandlers } from "./config";
import { handlers as dagHandlers } from "./dag";
import { handlers as dagRunsHandlers } from "./dag_runs";
@@ -24,6 +25,7 @@ import { handlers as dagsHandlers } from "./dags";
import { handlers as logHandlers } from "./log";
export const handlers = [
+ ...backfillsHandlers,
...assetsHandlers,
...configHandlers,
...dagHandlers,
diff --git
a/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/Backfills.test.tsx
b/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/Backfills.test.tsx
index 698f38f2699..4ffffdf8c93 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/Backfills.test.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/Backfills.test.tsx
@@ -30,6 +30,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
import type { BackfillDagRunResponse, BackfillResponse } from
"openapi/requests/types.gen";
+import { TimezoneProvider } from "src/context/timezone";
import type * as Utils from "src/utils";
import { BaseWrapper } from "src/utils/Wrapper";
@@ -39,12 +40,14 @@ const mocks = vi.hoisted(() => ({
getBackfill: vi.fn(),
listBackfillDagRuns: vi.fn(),
listBackfills: vi.fn(),
+ listTeams: vi.fn(),
}));
vi.mock("openapi/queries", () => ({
useBackfillServiceGetBackfill: mocks.getBackfill,
useBackfillServiceListBackfillDagRuns: mocks.listBackfillDagRuns,
useBackfillServiceListBackfillsUi: mocks.listBackfills,
+ useTeamsServiceListTeams: mocks.listTeams,
}));
vi.mock("react-i18next", () => ({
@@ -128,10 +131,12 @@ const renderBackfills = (initialEntry =
"/dags/example_dag/backfills") =>
render(
<BaseWrapper>
<MemoryRouter initialEntries={[initialEntry]}>
- <Routes>
- <Route element={<Backfills />} path="/dags/:dagId/backfills" />
- <Route element={<Backfills />}
path="/dags/:dagId/backfills/:backfillId" />
- </Routes>
+ <TimezoneProvider>
+ <Routes>
+ <Route element={<Backfills />} path="/dags/:dagId/backfills" />
+ <Route element={<Backfills />}
path="/dags/:dagId/backfills/:backfillId" />
+ </Routes>
+ </TimezoneProvider>
</MemoryRouter>
</BaseWrapper>,
);
@@ -174,11 +179,17 @@ const expectDagRunsQuery = (backfillId: number) => {
return options.refetchInterval;
};
-describe("Backfills", () => {
+describe("Backfills filters", () => {
beforeEach(() => {
mocks.getBackfill.mockReset();
mocks.listBackfillDagRuns.mockReset();
mocks.listBackfills.mockReset();
+ mocks.listTeams.mockReset();
+ mocks.listTeams.mockReturnValue({
+ data: { teams: [], total_entries: 0 },
+ error: undefined,
+ isLoading: false,
+ });
});
it("opens a backfill's associated slots in a dialog", async () => {
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/Backfills.tsx
b/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/Backfills.tsx
index 27425422018..66964abd02e 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/Backfills.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/Backfills.tsx
@@ -20,19 +20,44 @@ import { Button, Text } from "@chakra-ui/react";
import type { ColumnDef } from "@tanstack/react-table";
import type { TFunction } from "i18next";
import { useTranslation } from "react-i18next";
-import { useLocation, useNavigate, useParams } from "react-router-dom";
+import { useLocation, useNavigate, useParams, useSearchParams } from
"react-router-dom";
import { useBackfillServiceListBackfillsUi } from "openapi/queries";
-import type { BackfillResponse } from "openapi/requests/types.gen";
+import type { BackfillResponse, ReprocessBehavior } from
"openapi/requests/types.gen";
import { DataTable } from "src/components/DataTable";
import { useTableURLState } from "src/components/DataTable/useTableUrlState";
import { ErrorAlert } from "src/components/ErrorAlert";
import Time from "src/components/Time";
+import { SearchParamsKeys, type SearchParamsKeysType } from
"src/constants/searchParams";
import { getDuration } from "src/utils";
import { BackfillDagRunsModal } from "./BackfillDagRunsModal";
+import { BackfillsFilters } from "./BackfillsFilters";
+
+const {
+ COMPLETED_AT_GTE: COMPLETED_AT_GTE_PARAM,
+ COMPLETED_AT_LTE: COMPLETED_AT_LTE_PARAM,
+ CREATED_AT_GTE: CREATED_AT_GTE_PARAM,
+ CREATED_AT_LTE: CREATED_AT_LTE_PARAM,
+ FROM_DATE_GTE: FROM_DATE_GTE_PARAM,
+ FROM_DATE_LTE: FROM_DATE_LTE_PARAM,
+ MAX_ACTIVE_RUNS_GTE: MAX_ACTIVE_RUNS_GTE_PARAM,
+ MAX_ACTIVE_RUNS_LTE: MAX_ACTIVE_RUNS_LTE_PARAM,
+ REPROCESS_BEHAVIOR: REPROCESS_BEHAVIOR_PARAM,
+ TO_DATE_GTE: TO_DATE_GTE_PARAM,
+ TO_DATE_LTE: TO_DATE_LTE_PARAM,
+}: SearchParamsKeysType = SearchParamsKeys;
+
+const REPROCESS_BEHAVIOR_VALUES = [
+ "failed",
+ "completed",
+ "none",
+] as const satisfies ReadonlyArray<ReprocessBehavior>;
+
+const isReprocessBehavior = (value: string | null): value is ReprocessBehavior
=>
+ (REPROCESS_BEHAVIOR_VALUES as ReadonlyArray<string | null>).includes(value);
const getColumns = (
onSelectBackfill: (backfillId: number) => void,
@@ -128,10 +153,39 @@ export const Backfills = () => {
const { backfillId, dagId = "" } = useParams();
const selectedBackfillId = Number(backfillId);
const hasSelectedBackfill = Number.isInteger(selectedBackfillId) &&
selectedBackfillId > 0;
+
+ const [searchParams] = useSearchParams();
+
+ const fromDateGte = searchParams.get(FROM_DATE_GTE_PARAM);
+ const fromDateLte = searchParams.get(FROM_DATE_LTE_PARAM);
+ const toDateGte = searchParams.get(TO_DATE_GTE_PARAM);
+ const toDateLte = searchParams.get(TO_DATE_LTE_PARAM);
+ const createdAtGte = searchParams.get(CREATED_AT_GTE_PARAM);
+ const createdAtLte = searchParams.get(CREATED_AT_LTE_PARAM);
+ const completedAtGte = searchParams.get(COMPLETED_AT_GTE_PARAM);
+ const completedAtLte = searchParams.get(COMPLETED_AT_LTE_PARAM);
+ const maxActiveRunsGte = searchParams.get(MAX_ACTIVE_RUNS_GTE_PARAM);
+ const maxActiveRunsLte = searchParams.get(MAX_ACTIVE_RUNS_LTE_PARAM);
+ const reprocessBehaviorParam = searchParams.get(REPROCESS_BEHAVIOR_PARAM);
+ const reprocessBehavior = isReprocessBehavior(reprocessBehaviorParam) ?
reprocessBehaviorParam : undefined;
+
const { data, error, isFetching, isLoading } =
useBackfillServiceListBackfillsUi({
+ completedAtGte: completedAtGte ?? undefined,
+ completedAtLte: completedAtLte ?? undefined,
+ createdAtGte: createdAtGte ?? undefined,
+ createdAtLte: createdAtLte ?? undefined,
dagId,
+ fromDateGte: fromDateGte ?? undefined,
+ fromDateLte: fromDateLte ?? undefined,
limit: pagination.pageSize,
+ maxActiveRunsGte:
+ maxActiveRunsGte !== null && maxActiveRunsGte !== "" ?
Number(maxActiveRunsGte) : undefined,
+ maxActiveRunsLte:
+ maxActiveRunsLte !== null && maxActiveRunsLte !== "" ?
Number(maxActiveRunsLte) : undefined,
offset: pagination.pageIndex * pagination.pageSize,
+ reprocessBehavior,
+ toDateGte: toDateGte ?? undefined,
+ toDateLte: toDateLte ?? undefined,
});
const onSelectBackfill = (id: number) => {
@@ -157,6 +211,7 @@ export const Backfills = () => {
return (
<>
+ <BackfillsFilters />
<ErrorAlert error={error} />
<DataTable
columns={columns}
diff --git
a/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/BackfillsDateFilter.test.tsx
b/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/BackfillsDateFilter.test.tsx
new file mode 100644
index 00000000000..e66fa301d2a
--- /dev/null
+++
b/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/BackfillsDateFilter.test.tsx
@@ -0,0 +1,52 @@
+/*!
+ * 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 "@testing-library/jest-dom";
+import { render, screen, waitFor } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+
+import { AppWrapper } from "src/utils/AppWrapper";
+
+// The backfills mock handler (see src/mocks/handlers/backfills.ts) returns:
+// - id 1 (start_date: 2024-12-01, end_date: 2024-12-31) — excluded when
filtering Jan 2025
+// - id 2 (start_date: 2025-01-01, end_date: 2025-01-15) — included when
filtering Jan 2025
+
+const getFromDateCells = () =>
+ screen.getAllByTestId("time-display").map((element) =>
element.getAttribute("datetime"));
+
+describe("Backfills start/end date filter", () => {
+ it("shows all backfills when no date filter is applied", async () => {
+ render(<AppWrapper
initialEntries={["/dags/tutorial_taskflow_api/backfills"]} />);
+
+ await waitFor(() =>
expect(getFromDateCells()).toContain("2025-01-01T00:00:00Z"));
+ expect(getFromDateCells()).toContain("2024-12-01T00:00:00Z");
+ });
+
+ it("filters backfills by from_date_gte and to_date_lte URL params", async ()
=> {
+ render(
+ <AppWrapper
+ initialEntries={[
+
"/dags/tutorial_taskflow_api/backfills?from_date_gte=2025-01-01T00%3A00%3A00Z&to_date_lte=2025-01-31T23%3A59%3A59Z",
+ ]}
+ />,
+ );
+
+ await waitFor(() =>
expect(getFromDateCells()).toContain("2025-01-01T00:00:00Z"));
+ expect(getFromDateCells()).not.toContain("2024-12-01T00:00:00Z");
+ });
+});
diff --git
a/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/BackfillsFilters.tsx
b/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/BackfillsFilters.tsx
new file mode 100644
index 00000000000..e69893ddadd
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/BackfillsFilters.tsx
@@ -0,0 +1,48 @@
+/*!
+ * 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 { VStack } from "@chakra-ui/react";
+
+import { FilterBar } from "src/components/FilterBar";
+
+import { SearchParamsKeys } from "src/constants/searchParams";
+import { useFiltersHandler, type FilterableSearchParamsKeys } from "src/utils";
+
+export const BackfillsFilters = () => {
+ const searchParamKeys: Array<FilterableSearchParamsKeys> = [
+ SearchParamsKeys.FROM_RANGE,
+ SearchParamsKeys.TO_RANGE,
+ SearchParamsKeys.CREATED_AT_RANGE,
+ SearchParamsKeys.COMPLETED_AT_RANGE,
+ SearchParamsKeys.MAX_ACTIVE_RUNS_GTE,
+ SearchParamsKeys.MAX_ACTIVE_RUNS_LTE,
+ SearchParamsKeys.REPROCESS_BEHAVIOR,
+ ];
+
+ const { filterConfigs, handleFiltersChange, initialValues } =
useFiltersHandler(searchParamKeys);
+
+ return (
+ <VStack align="start" gap={4} paddingY="4px">
+ <FilterBar
+ configs={filterConfigs}
+ initialValues={initialValues}
+ onFiltersChange={handleFiltersChange}
+ />
+ </VStack>
+ );
+};
diff --git a/airflow-core/src/airflow/ui/src/utils/useFiltersHandler.ts
b/airflow-core/src/airflow/ui/src/utils/useFiltersHandler.ts
index dfd17785c48..d28b674353a 100644
--- a/airflow-core/src/airflow/ui/src/utils/useFiltersHandler.ts
+++ b/airflow-core/src/airflow/ui/src/utils/useFiltersHandler.ts
@@ -60,6 +60,7 @@ export type FilterableSearchParamsKeys =
| SearchParamsKeys.ASSET_EVENT_DATE_RANGE
| SearchParamsKeys.BODY_SEARCH
| SearchParamsKeys.BUNDLE_VERSION
+ | SearchParamsKeys.COMPLETED_AT_RANGE
| SearchParamsKeys.CONF_CONTAINS
| SearchParamsKeys.CONSUMING_ASSET_PATTERN
| SearchParamsKeys.CREATED_AT_RANGE
@@ -76,6 +77,7 @@ export type FilterableSearchParamsKeys =
| SearchParamsKeys.EVENT_TYPE
| SearchParamsKeys.EXECUTOR_CLASS
| SearchParamsKeys.FAVORITE
+ | SearchParamsKeys.FROM_RANGE
| SearchParamsKeys.GROUP_PATTERN
| SearchParamsKeys.HOSTNAME
| SearchParamsKeys.JOB_STATE
@@ -85,6 +87,8 @@ export type FilterableSearchParamsKeys =
| SearchParamsKeys.LAST_DAG_RUN_STATE
| SearchParamsKeys.LOGICAL_DATE_RANGE
| SearchParamsKeys.MAP_INDEX
+ | SearchParamsKeys.MAX_ACTIVE_RUNS_GTE
+ | SearchParamsKeys.MAX_ACTIVE_RUNS_LTE
| SearchParamsKeys.MISSED
| SearchParamsKeys.NAME_PATTERN
| SearchParamsKeys.NEEDS_REVIEW
@@ -95,6 +99,7 @@ export type FilterableSearchParamsKeys =
| SearchParamsKeys.POOL_NAME_PATTERN
| SearchParamsKeys.QUEUE_NAME_PATTERN
| SearchParamsKeys.RENDERED_MAP_INDEX
+ | SearchParamsKeys.REPROCESS_BEHAVIOR
| SearchParamsKeys.RESPONDED_BY_USER_NAME
| SearchParamsKeys.RESPONSE_RECEIVED
| SearchParamsKeys.RUN_AFTER_RANGE
@@ -110,6 +115,7 @@ export type FilterableSearchParamsKeys =
| SearchParamsKeys.TASK_STATE
| SearchParamsKeys.TEAMS
| SearchParamsKeys.TIMETABLE_TYPE
+ | SearchParamsKeys.TO_RANGE
| SearchParamsKeys.TRIGGERING_USER_NAME_PATTERN
| SearchParamsKeys.TRY_NUMBER
| SearchParamsKeys.USER;
diff --git
a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_backfills.py
b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_backfills.py
index ebb23efff7d..36e2d94a757 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_backfills.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_backfills.py
@@ -16,13 +16,14 @@
# under the License.
from __future__ import annotations
+from datetime import timedelta
from unittest import mock
import pytest
from airflow._shared.timezones import timezone
from airflow.models import DagModel
-from airflow.models.backfill import Backfill
+from airflow.models.backfill import Backfill, ReprocessBehavior
from airflow.utils.session import provide_session
from tests_common.test_utils.asserts import assert_queries_count
@@ -160,6 +161,88 @@ class TestListBackfills(TestBackfillEndpoint):
"total_entries": total_entries,
}
+ def test_list_backfill_by_start_end_date(self, test_client, session,
testing_dag_bundle):
+ (dag,) = self._create_dag_models(count=1)
+ from_date = timezone.utcnow()
+ b1 = Backfill(dag_id=dag.dag_id, from_date=from_date,
to_date=from_date)
+ other_date = from_date + timedelta(days=1)
+ b2 = Backfill(dag_id=dag.dag_id, from_date=other_date,
to_date=other_date)
+ session.add_all([b1, b2])
+ session.commit()
+
+ response = test_client.get(
+ "/backfills",
+ params={
+ "from_date_gte": from_datetime_to_zulu(other_date),
+ "to_date_lte": from_datetime_to_zulu(other_date),
+ },
+ )
+ assert response.status_code == 200
+ assert [each["id"] for each in response.json()["backfills"]] == [b2.id]
+
+ def test_list_backfill_by_completed_at(self, test_client, session,
testing_dag_bundle):
+ (dag,) = self._create_dag_models(count=1)
+ from_date = timezone.utcnow()
+ b1 = Backfill(dag_id=dag.dag_id, from_date=from_date,
to_date=from_date)
+ other_completed_at = timezone.utcnow() + timedelta(days=1)
+ b2 = Backfill(
+ dag_id=dag.dag_id, from_date=from_date, to_date=from_date,
completed_at=other_completed_at
+ )
+ session.add_all([b1, b2])
+ session.commit()
+
+ # b1's completed_at is NULL, so it never matches a bound
+ response = test_client.get(
+ "/backfills", params={"completed_at_gte":
from_datetime_to_zulu(other_completed_at)}
+ )
+ assert response.status_code == 200
+ assert [each["id"] for each in response.json()["backfills"]] == [b2.id]
+
+ def test_list_backfill_by_created_at(self, test_client, session,
testing_dag_bundle):
+ (dag,) = self._create_dag_models(count=1)
+ from_date = timezone.utcnow()
+ b1 = Backfill(dag_id=dag.dag_id, from_date=from_date,
to_date=from_date)
+ other_created_at = timezone.utcnow() - timedelta(days=1)
+ b2 = Backfill(dag_id=dag.dag_id, from_date=from_date,
to_date=from_date, created_at=other_created_at)
+ session.add_all([b1, b2])
+ session.commit()
+
+ # b1's created_at defaults to ~now, b2's is 1 day in the past
+ response = test_client.get(
+ "/backfills", params={"created_at_lte":
from_datetime_to_zulu(other_created_at)}
+ )
+ assert response.status_code == 200
+ assert [each["id"] for each in response.json()["backfills"]] == [b2.id]
+
+ def test_list_backfill_by_reprocess_behavior(self, test_client, session,
testing_dag_bundle):
+ (dag,) = self._create_dag_models(count=1)
+ from_date = timezone.utcnow()
+ b1 = Backfill(dag_id=dag.dag_id, from_date=from_date,
to_date=from_date)
+ b2 = Backfill(
+ dag_id=dag.dag_id,
+ from_date=from_date,
+ to_date=from_date,
+ reprocess_behavior=ReprocessBehavior.COMPLETED,
+ )
+ session.add_all([b1, b2])
+ session.commit()
+
+ response = test_client.get("/backfills", params={"reprocess_behavior":
"completed"})
+ assert response.status_code == 200
+ assert [each["id"] for each in response.json()["backfills"]] == [b2.id]
+
+ def test_list_backfill_by_max_active_runs(self, test_client, session,
testing_dag_bundle):
+ (dag,) = self._create_dag_models(count=1)
+ from_date = timezone.utcnow()
+ b1 = Backfill(dag_id=dag.dag_id, from_date=from_date,
to_date=from_date) # default max_active_runs=10
+ b2 = Backfill(dag_id=dag.dag_id, from_date=from_date,
to_date=from_date, max_active_runs=3)
+ session.add_all([b1, b2])
+ session.commit()
+
+ response = test_client.get("/backfills",
params={"max_active_runs_lte": 5})
+ assert response.status_code == 200
+ assert [each["id"] for each in response.json()["backfills"]] == [b2.id]
+
def test_should_response_401(self, unauthenticated_test_client):
response = unauthenticated_test_client.get("/backfills", params={})
assert response.status_code == 401