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 abdc023e780 `is_scheduled` REST API parameter (#72029)
abdc023e780 is described below
commit abdc023e780e1533ab797853fd0447edcc21a4fe
Author: Ryan Hatter <[email protected]>
AuthorDate: Tue Aug 25 09:18:23 2026 -0400
`is_scheduled` REST API parameter (#72029)
* add failing tests for #53741
* cycle 1: address failing tests for #53741
* apply deterministic prek fixes
* apply deterministic prek fixes
* apply deterministic prek fixes
---------
Co-authored-by: Roborchestra Bot <[email protected]>
---
.../core_api/openapi/v2-rest-api-generated.yaml | 11 ++
.../api_fastapi/core_api/routes/public/dags.py | 10 +
.../src/airflow/ui/openapi-gen/queries/common.ts | 5 +-
.../ui/openapi-gen/queries/ensureQueryData.ts | 6 +-
.../src/airflow/ui/openapi-gen/queries/prefetch.ts | 6 +-
.../src/airflow/ui/openapi-gen/queries/queries.ts | 6 +-
.../src/airflow/ui/openapi-gen/queries/suspense.ts | 6 +-
.../ui/openapi-gen/requests/services.gen.ts | 2 +
.../airflow/ui/openapi-gen/requests/types.gen.ts | 4 +
.../core_api/routes/public/test_dags.py | 214 +++++++++++++++++++++
10 files changed, 260 insertions(+), 10 deletions(-)
diff --git
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
index bd5a6ce52b0..0eb4d2423dc 100644
---
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
+++
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
@@ -3641,6 +3641,17 @@ paths:
- OAuth2PasswordBearer: []
- HTTPBearer: []
parameters:
+ - name: is_scheduled
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: boolean
+ - type: 'null'
+ description: Filter Dags by whether their timetable can create
scheduled
+ runs.
+ title: Is Scheduled
+ description: Filter Dags by whether their timetable can create
scheduled runs.
- name: limit
in: query
required: false
diff --git
a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/dags.py
b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/dags.py
index df66d97d4d1..1b941c967dd 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/dags.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/dags.py
@@ -136,6 +136,10 @@ def get_dags(
FilterParam[list[str] | None],
Depends(filter_param_factory(DagModel.timetable_type, list[str],
FilterOptionEnum.IN)),
],
+ is_scheduled: Annotated[
+ bool | None,
+ Query(description="Filter Dags by whether their timetable can create
scheduled runs."),
+ ] = None,
) -> DAGCollectionResponse:
"""Get all Dags."""
query = generate_dag_with_latest_run_query(
@@ -148,6 +152,12 @@ def get_dags(
order_by=order_by,
dag_ids=readable_dags_filter.value,
)
+ if is_scheduled is not None:
+ unscheduled_timetable_types = ("NullTimetable", "PartitionedAtRuntime")
+ if is_scheduled:
+ query =
query.where(DagModel.timetable_type.not_in(unscheduled_timetable_types))
+ else:
+ query =
query.where(DagModel.timetable_type.in_(unscheduled_timetable_types))
dags_select, total_entries = paginated_select(
statement=query,
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 7d86b648d4a..5daf6e1d6d9 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
@@ -307,7 +307,7 @@ export const UseDagWarningServiceListDagWarningsKeyFn = ({
dagId, limit, offset,
export type DagServiceGetDagsDefaultResponse = Awaited<ReturnType<typeof
DagService.getDags>>;
export type DagServiceGetDagsQueryResult<TData =
DagServiceGetDagsDefaultResponse, TError = unknown> = UseQueryResult<TData,
TError>;
export const useDagServiceGetDagsKey = "DagServiceGetDags";
-export const UseDagServiceGetDagsKeyFn = ({ assetDependency, bundleName,
bundleVersion, dagDisplayNamePattern, dagDisplayNamePrefixPattern,
dagIdPattern, dagIdPrefixPattern, dagRunEndDateGt, dagRunEndDateGte,
dagRunEndDateLt, dagRunEndDateLte, dagRunStartDateGt, dagRunStartDateGte,
dagRunStartDateLt, dagRunStartDateLte, dagRunState, excludeStale,
hasAssetSchedule, hasImportErrors, isFavorite, lastDagRunState, limit, offset,
orderBy, owners, paused, tags, tagsMatchMode, timetableType }: {
+export const UseDagServiceGetDagsKeyFn = ({ assetDependency, bundleName,
bundleVersion, dagDisplayNamePattern, dagDisplayNamePrefixPattern,
dagIdPattern, dagIdPrefixPattern, dagRunEndDateGt, dagRunEndDateGte,
dagRunEndDateLt, dagRunEndDateLte, dagRunStartDateGt, dagRunStartDateGte,
dagRunStartDateLt, dagRunStartDateLte, dagRunState, excludeStale,
hasAssetSchedule, hasImportErrors, isFavorite, isScheduled, lastDagRunState,
limit, offset, orderBy, owners, paused, tags, tagsMatchMode, timet [...]
assetDependency?: string;
bundleName?: string;
bundleVersion?: string;
@@ -328,6 +328,7 @@ export const UseDagServiceGetDagsKeyFn = ({
assetDependency, bundleName, bundleV
hasAssetSchedule?: boolean;
hasImportErrors?: boolean;
isFavorite?: boolean;
+ isScheduled?: boolean;
lastDagRunState?: DagRunState;
limit?: number;
offset?: number;
@@ -337,7 +338,7 @@ export const UseDagServiceGetDagsKeyFn = ({
assetDependency, bundleName, bundleV
tags?: string[];
tagsMatchMode?: "any" | "all";
timetableType?: string[];
-} = {}, queryKey?: Array<unknown>) => [useDagServiceGetDagsKey, ...(queryKey
?? [{ assetDependency, bundleName, bundleVersion, dagDisplayNamePattern,
dagDisplayNamePrefixPattern, dagIdPattern, dagIdPrefixPattern, dagRunEndDateGt,
dagRunEndDateGte, dagRunEndDateLt, dagRunEndDateLte, dagRunStartDateGt,
dagRunStartDateGte, dagRunStartDateLt, dagRunStartDateLte, dagRunState,
excludeStale, hasAssetSchedule, hasImportErrors, isFavorite, lastDagRunState,
limit, offset, orderBy, owners, paused, [...]
+} = {}, queryKey?: Array<unknown>) => [useDagServiceGetDagsKey, ...(queryKey
?? [{ assetDependency, bundleName, bundleVersion, dagDisplayNamePattern,
dagDisplayNamePrefixPattern, dagIdPattern, dagIdPrefixPattern, dagRunEndDateGt,
dagRunEndDateGte, dagRunEndDateLt, dagRunEndDateLte, dagRunStartDateGt,
dagRunStartDateGte, dagRunStartDateLt, dagRunStartDateLte, dagRunState,
excludeStale, hasAssetSchedule, hasImportErrors, isFavorite, isScheduled,
lastDagRunState, limit, offset, orderBy, own [...]
export type DagServiceGetDagDefaultResponse = Awaited<ReturnType<typeof
DagService.getDag>>;
export type DagServiceGetDagQueryResult<TData =
DagServiceGetDagDefaultResponse, TError = unknown> = UseQueryResult<TData,
TError>;
export const useDagServiceGetDagKey = "DagServiceGetDag";
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 40fb19af300..2695e959e00 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
@@ -585,6 +585,7 @@ export const ensureUseDagWarningServiceListDagWarningsData
= (queryClient: Query
* Get Dags
* Get all Dags.
* @param data The data for the request.
+* @param data.isScheduled Filter Dags by whether their timetable can create
scheduled runs.
* @param data.limit
* @param data.offset
* @param data.tags
@@ -617,7 +618,7 @@ export const ensureUseDagWarningServiceListDagWarningsData
= (queryClient: Query
* @returns DAGCollectionResponse Successful Response
* @throws ApiError
*/
-export const ensureUseDagServiceGetDagsData = (queryClient: QueryClient, {
assetDependency, bundleName, bundleVersion, dagDisplayNamePattern,
dagDisplayNamePrefixPattern, dagIdPattern, dagIdPrefixPattern, dagRunEndDateGt,
dagRunEndDateGte, dagRunEndDateLt, dagRunEndDateLte, dagRunStartDateGt,
dagRunStartDateGte, dagRunStartDateLt, dagRunStartDateLte, dagRunState,
excludeStale, hasAssetSchedule, hasImportErrors, isFavorite, lastDagRunState,
limit, offset, orderBy, owners, paused, tags, ta [...]
+export const ensureUseDagServiceGetDagsData = (queryClient: QueryClient, {
assetDependency, bundleName, bundleVersion, dagDisplayNamePattern,
dagDisplayNamePrefixPattern, dagIdPattern, dagIdPrefixPattern, dagRunEndDateGt,
dagRunEndDateGte, dagRunEndDateLt, dagRunEndDateLte, dagRunStartDateGt,
dagRunStartDateGte, dagRunStartDateLt, dagRunStartDateLte, dagRunState,
excludeStale, hasAssetSchedule, hasImportErrors, isFavorite, isScheduled,
lastDagRunState, limit, offset, orderBy, owners, pau [...]
assetDependency?: string;
bundleName?: string;
bundleVersion?: string;
@@ -638,6 +639,7 @@ export const ensureUseDagServiceGetDagsData = (queryClient:
QueryClient, { asset
hasAssetSchedule?: boolean;
hasImportErrors?: boolean;
isFavorite?: boolean;
+ isScheduled?: boolean;
lastDagRunState?: DagRunState;
limit?: number;
offset?: number;
@@ -647,7 +649,7 @@ export const ensureUseDagServiceGetDagsData = (queryClient:
QueryClient, { asset
tags?: string[];
tagsMatchMode?: "any" | "all";
timetableType?: string[];
-} = {}) => queryClient.ensureQueryData({ queryKey:
Common.UseDagServiceGetDagsKeyFn({ assetDependency, bundleName, bundleVersion,
dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern,
dagIdPrefixPattern, dagRunEndDateGt, dagRunEndDateGte, dagRunEndDateLt,
dagRunEndDateLte, dagRunStartDateGt, dagRunStartDateGte, dagRunStartDateLt,
dagRunStartDateLte, dagRunState, excludeStale, hasAssetSchedule,
hasImportErrors, isFavorite, lastDagRunState, limit, offset, orderBy, owners,
pause [...]
+} = {}) => queryClient.ensureQueryData({ queryKey:
Common.UseDagServiceGetDagsKeyFn({ assetDependency, bundleName, bundleVersion,
dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern,
dagIdPrefixPattern, dagRunEndDateGt, dagRunEndDateGte, dagRunEndDateLt,
dagRunEndDateLte, dagRunStartDateGt, dagRunStartDateGte, dagRunStartDateLt,
dagRunStartDateLte, dagRunState, excludeStale, hasAssetSchedule,
hasImportErrors, isFavorite, isScheduled, lastDagRunState, limit, offset,
orderBy, [...]
/**
* Get Dag
* Get basic information about a Dag.
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 431b7480b0d..5e0cffc8e6d 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
@@ -585,6 +585,7 @@ export const prefetchUseDagWarningServiceListDagWarnings =
(queryClient: QueryCl
* Get Dags
* Get all Dags.
* @param data The data for the request.
+* @param data.isScheduled Filter Dags by whether their timetable can create
scheduled runs.
* @param data.limit
* @param data.offset
* @param data.tags
@@ -617,7 +618,7 @@ export const prefetchUseDagWarningServiceListDagWarnings =
(queryClient: QueryCl
* @returns DAGCollectionResponse Successful Response
* @throws ApiError
*/
-export const prefetchUseDagServiceGetDags = (queryClient: QueryClient, {
assetDependency, bundleName, bundleVersion, dagDisplayNamePattern,
dagDisplayNamePrefixPattern, dagIdPattern, dagIdPrefixPattern, dagRunEndDateGt,
dagRunEndDateGte, dagRunEndDateLt, dagRunEndDateLte, dagRunStartDateGt,
dagRunStartDateGte, dagRunStartDateLt, dagRunStartDateLte, dagRunState,
excludeStale, hasAssetSchedule, hasImportErrors, isFavorite, lastDagRunState,
limit, offset, orderBy, owners, paused, tags, tags [...]
+export const prefetchUseDagServiceGetDags = (queryClient: QueryClient, {
assetDependency, bundleName, bundleVersion, dagDisplayNamePattern,
dagDisplayNamePrefixPattern, dagIdPattern, dagIdPrefixPattern, dagRunEndDateGt,
dagRunEndDateGte, dagRunEndDateLt, dagRunEndDateLte, dagRunStartDateGt,
dagRunStartDateGte, dagRunStartDateLt, dagRunStartDateLte, dagRunState,
excludeStale, hasAssetSchedule, hasImportErrors, isFavorite, isScheduled,
lastDagRunState, limit, offset, orderBy, owners, pause [...]
assetDependency?: string;
bundleName?: string;
bundleVersion?: string;
@@ -638,6 +639,7 @@ export const prefetchUseDagServiceGetDags = (queryClient:
QueryClient, { assetDe
hasAssetSchedule?: boolean;
hasImportErrors?: boolean;
isFavorite?: boolean;
+ isScheduled?: boolean;
lastDagRunState?: DagRunState;
limit?: number;
offset?: number;
@@ -647,7 +649,7 @@ export const prefetchUseDagServiceGetDags = (queryClient:
QueryClient, { assetDe
tags?: string[];
tagsMatchMode?: "any" | "all";
timetableType?: string[];
-} = {}) => queryClient.prefetchQuery({ queryKey:
Common.UseDagServiceGetDagsKeyFn({ assetDependency, bundleName, bundleVersion,
dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern,
dagIdPrefixPattern, dagRunEndDateGt, dagRunEndDateGte, dagRunEndDateLt,
dagRunEndDateLte, dagRunStartDateGt, dagRunStartDateGte, dagRunStartDateLt,
dagRunStartDateLte, dagRunState, excludeStale, hasAssetSchedule,
hasImportErrors, isFavorite, lastDagRunState, limit, offset, orderBy, owners,
paused, [...]
+} = {}) => queryClient.prefetchQuery({ queryKey:
Common.UseDagServiceGetDagsKeyFn({ assetDependency, bundleName, bundleVersion,
dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern,
dagIdPrefixPattern, dagRunEndDateGt, dagRunEndDateGte, dagRunEndDateLt,
dagRunEndDateLte, dagRunStartDateGt, dagRunStartDateGte, dagRunStartDateLt,
dagRunStartDateLte, dagRunState, excludeStale, hasAssetSchedule,
hasImportErrors, isFavorite, isScheduled, lastDagRunState, limit, offset,
orderBy, ow [...]
/**
* Get Dag
* Get basic information about a Dag.
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 bd447ebeed2..494f13de344 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
@@ -585,6 +585,7 @@ export const useDagWarningServiceListDagWarnings = <TData =
Common.DagWarningSer
* Get Dags
* Get all Dags.
* @param data The data for the request.
+* @param data.isScheduled Filter Dags by whether their timetable can create
scheduled runs.
* @param data.limit
* @param data.offset
* @param data.tags
@@ -617,7 +618,7 @@ export const useDagWarningServiceListDagWarnings = <TData =
Common.DagWarningSer
* @returns DAGCollectionResponse Successful Response
* @throws ApiError
*/
-export const useDagServiceGetDags = <TData =
Common.DagServiceGetDagsDefaultResponse, TError = unknown, TQueryKey extends
Array<unknown> = unknown[]>({ assetDependency, bundleName, bundleVersion,
dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern,
dagIdPrefixPattern, dagRunEndDateGt, dagRunEndDateGte, dagRunEndDateLt,
dagRunEndDateLte, dagRunStartDateGt, dagRunStartDateGte, dagRunStartDateLt,
dagRunStartDateLte, dagRunState, excludeStale, hasAssetSchedule,
hasImportErrors, [...]
+export const useDagServiceGetDags = <TData =
Common.DagServiceGetDagsDefaultResponse, TError = unknown, TQueryKey extends
Array<unknown> = unknown[]>({ assetDependency, bundleName, bundleVersion,
dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern,
dagIdPrefixPattern, dagRunEndDateGt, dagRunEndDateGte, dagRunEndDateLt,
dagRunEndDateLte, dagRunStartDateGt, dagRunStartDateGte, dagRunStartDateLt,
dagRunStartDateLte, dagRunState, excludeStale, hasAssetSchedule,
hasImportErrors, [...]
assetDependency?: string;
bundleName?: string;
bundleVersion?: string;
@@ -638,6 +639,7 @@ export const useDagServiceGetDags = <TData =
Common.DagServiceGetDagsDefaultResp
hasAssetSchedule?: boolean;
hasImportErrors?: boolean;
isFavorite?: boolean;
+ isScheduled?: boolean;
lastDagRunState?: DagRunState;
limit?: number;
offset?: number;
@@ -647,7 +649,7 @@ export const useDagServiceGetDags = <TData =
Common.DagServiceGetDagsDefaultResp
tags?: string[];
tagsMatchMode?: "any" | "all";
timetableType?: string[];
-} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey:
Common.UseDagServiceGetDagsKeyFn({ assetDependency, bundleName, bundleVersion,
dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern,
dagIdPrefixPattern, dagRunEndDateGt, dagRunEndDateGte, dagRunEndDateLt,
dagRunEndDateLte, dagRunStartDateGt, dagRunStartDateGte, dagRunStartDateLt,
dagRunStartDateLte, dagRunState, excludeStale, hasAssetSch [...]
+} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey:
Common.UseDagServiceGetDagsKeyFn({ assetDependency, bundleName, bundleVersion,
dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern,
dagIdPrefixPattern, dagRunEndDateGt, dagRunEndDateGte, dagRunEndDateLt,
dagRunEndDateLte, dagRunStartDateGt, dagRunStartDateGte, dagRunStartDateLt,
dagRunStartDateLte, dagRunState, excludeStale, hasAssetSch [...]
/**
* Get Dag
* Get basic information about a Dag.
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 27f4bdce056..af032fad3f3 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
@@ -585,6 +585,7 @@ export const useDagWarningServiceListDagWarningsSuspense =
<TData = Common.DagWa
* Get Dags
* Get all Dags.
* @param data The data for the request.
+* @param data.isScheduled Filter Dags by whether their timetable can create
scheduled runs.
* @param data.limit
* @param data.offset
* @param data.tags
@@ -617,7 +618,7 @@ export const useDagWarningServiceListDagWarningsSuspense =
<TData = Common.DagWa
* @returns DAGCollectionResponse Successful Response
* @throws ApiError
*/
-export const useDagServiceGetDagsSuspense = <TData =
Common.DagServiceGetDagsDefaultResponse, TError = unknown, TQueryKey extends
Array<unknown> = unknown[]>({ assetDependency, bundleName, bundleVersion,
dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern,
dagIdPrefixPattern, dagRunEndDateGt, dagRunEndDateGte, dagRunEndDateLt,
dagRunEndDateLte, dagRunStartDateGt, dagRunStartDateGte, dagRunStartDateLt,
dagRunStartDateLte, dagRunState, excludeStale, hasAssetSchedule, hasImport [...]
+export const useDagServiceGetDagsSuspense = <TData =
Common.DagServiceGetDagsDefaultResponse, TError = unknown, TQueryKey extends
Array<unknown> = unknown[]>({ assetDependency, bundleName, bundleVersion,
dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern,
dagIdPrefixPattern, dagRunEndDateGt, dagRunEndDateGte, dagRunEndDateLt,
dagRunEndDateLte, dagRunStartDateGt, dagRunStartDateGte, dagRunStartDateLt,
dagRunStartDateLte, dagRunState, excludeStale, hasAssetSchedule, hasImport [...]
assetDependency?: string;
bundleName?: string;
bundleVersion?: string;
@@ -638,6 +639,7 @@ export const useDagServiceGetDagsSuspense = <TData =
Common.DagServiceGetDagsDef
hasAssetSchedule?: boolean;
hasImportErrors?: boolean;
isFavorite?: boolean;
+ isScheduled?: boolean;
lastDagRunState?: DagRunState;
limit?: number;
offset?: number;
@@ -647,7 +649,7 @@ export const useDagServiceGetDagsSuspense = <TData =
Common.DagServiceGetDagsDef
tags?: string[];
tagsMatchMode?: "any" | "all";
timetableType?: string[];
-} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey:
Common.UseDagServiceGetDagsKeyFn({ assetDependency, bundleName, bundleVersion,
dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern,
dagIdPrefixPattern, dagRunEndDateGt, dagRunEndDateGte, dagRunEndDateLt,
dagRunEndDateLte, dagRunStartDateGt, dagRunStartDateGte, dagRunStartDateLt,
dagRunStartDateLte, dagRunState, excludeStale, has [...]
+} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey:
Common.UseDagServiceGetDagsKeyFn({ assetDependency, bundleName, bundleVersion,
dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern,
dagIdPrefixPattern, dagRunEndDateGt, dagRunEndDateGte, dagRunEndDateLt,
dagRunEndDateLte, dagRunStartDateGt, dagRunStartDateGte, dagRunStartDateLt,
dagRunStartDateLte, dagRunState, excludeStale, has [...]
/**
* Get Dag
* Get basic information about a Dag.
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 9b8bc8f5b07..806b388fb1f 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
@@ -1663,6 +1663,7 @@ export class DagService {
* Get Dags
* Get all Dags.
* @param data The data for the request.
+ * @param data.isScheduled Filter Dags by whether their timetable can
create scheduled runs.
* @param data.limit
* @param data.offset
* @param data.tags
@@ -1700,6 +1701,7 @@ export class DagService {
method: 'GET',
url: '/api/v2/dags',
query: {
+ is_scheduled: data.isScheduled,
limit: data.limit,
offset: data.offset,
tags: data.tags,
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 91336b9c42d..1b280d7a567 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
@@ -3490,6 +3490,10 @@ export type GetDagsData = {
*/
hasImportErrors?: boolean | null;
isFavorite?: boolean | null;
+ /**
+ * Filter Dags by whether their timetable can create scheduled runs.
+ */
+ isScheduled?: boolean | null;
lastDagRunState?: DagRunState | null;
limit?: number;
offset?: number;
diff --git
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py
index 8873c82e273..d608c47bf53 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py
@@ -264,6 +264,220 @@ class TestDagEndpoint:
class TestGetDags(TestDagEndpoint):
"""Unit tests for Get DAGs."""
+ def _update_and_assert_dag_fields(self, session, expected_fields):
+ """Persist DAG controls and query them back before exercising the
API."""
+ for dag_id, fields in expected_fields.items():
+ dag_model = session.get(DagModel, dag_id)
+ for field, value in fields.items():
+ setattr(dag_model, field, value)
+ session.commit()
+ session.expire_all()
+
+ persisted = {
+ dag_model.dag_id: dag_model
+ for dag_model in session.scalars(
+ select(DagModel).where(DagModel.dag_id.in_(expected_fields))
+ ).all()
+ }
+ assert set(persisted) == set(expected_fields)
+ for dag_id, fields in expected_fields.items():
+ for field, value in fields.items():
+ assert getattr(persisted[dag_id], field) == value
+
+ def test_get_dags_filter_is_scheduled_combines_with_paused(self, session,
test_client):
+ self._update_and_assert_dag_fields(
+ session,
+ {
+ DAG1_ID: {"timetable_type": "NullTimetable", "is_paused":
True},
+ DAG2_ID: {"timetable_type": "NullTimetable", "is_paused":
False},
+ DAG3_ID: {"timetable_type": "CronTriggerTimetable",
"is_paused": True},
+ },
+ )
+
+ response = test_client.get(
+ "/dags",
+ params={"is_scheduled": False, "paused": True, "exclude_stale":
False},
+ )
+
+ assert response.status_code == 200
+ body = response.json()
+ assert body["total_entries"] == 1
+ assert [dag["dag_id"] for dag in body["dags"]] == [DAG1_ID]
+
+ @pytest.mark.parametrize(
+ "query_params",
+ [
+ pytest.param({}, id="no-other-parameters"),
+ pytest.param({"limit": 100}, id="with-unrelated-parameter"),
+ ],
+ )
+ def test_get_dags_without_is_scheduled_returns_all_dags(self, test_client,
query_params):
+ query_params = {**query_params, "exclude_stale": False}
+
+ response = test_client.get("/dags", params=query_params)
+
+ assert response.status_code == 200
+ body = response.json()
+ assert body["total_entries"] == 3
+ assert [dag["dag_id"] for dag in body["dags"]] == [DAG1_ID, DAG2_ID,
DAG3_ID]
+
+ @pytest.mark.parametrize(
+ ("timetable_type", "is_scheduled", "other_timetable_type"),
+ [
+ pytest.param("NullTimetable", False, "CronTriggerTimetable",
id="null"),
+ pytest.param(
+ "PartitionedAtRuntime",
+ False,
+ "CronTriggerTimetable",
+ id="partitioned-at-runtime",
+ ),
+ pytest.param("CronTriggerTimetable", True, "NullTimetable",
id="cron"),
+ pytest.param("OnceTimetable", True, "NullTimetable", id="once"),
+ pytest.param("my_plugin.timetables.Custom", True, "NullTimetable",
id="plugin"),
+ ],
+ )
+ def test_get_dags_filter_is_scheduled_by_timetable_type(
+ self,
+ session,
+ test_client,
+ timetable_type,
+ is_scheduled,
+ other_timetable_type,
+ ):
+ self._update_and_assert_dag_fields(
+ session,
+ {
+ DAG1_ID: {"timetable_type": timetable_type},
+ DAG2_ID: {"timetable_type": other_timetable_type},
+ DAG3_ID: {"timetable_type": other_timetable_type},
+ },
+ )
+
+ response = test_client.get(
+ "/dags",
+ params={"is_scheduled": is_scheduled, "exclude_stale": False},
+ )
+
+ assert response.status_code == 200
+ body = response.json()
+ assert body["total_entries"] == 1
+ assert [dag["dag_id"] for dag in body["dags"]] == [DAG1_ID]
+
+ @pytest.mark.parametrize(
+ "timetable_summary",
+ [
+ pytest.param("None", id="None"),
+ pytest.param(None, id="NoneValue"),
+ pytest.param("", id="empty"),
+ pytest.param("2 2 * * *", id="cron-summary"),
+ ],
+ )
+ def test_get_dags_filter_is_scheduled_ignores_summary(self, session,
test_client, timetable_summary):
+ self._update_and_assert_dag_fields(
+ session,
+ {
+ DAG1_ID: {
+ "timetable_type": "NullTimetable",
+ "timetable_summary": timetable_summary,
+ },
+ DAG2_ID: {"timetable_type": "CronTriggerTimetable"},
+ DAG3_ID: {"timetable_type": "CronTriggerTimetable"},
+ },
+ )
+
+ response = test_client.get(
+ "/dags",
+ params={"is_scheduled": False, "exclude_stale": False},
+ )
+
+ assert response.status_code == 200
+ body = response.json()
+ assert body["total_entries"] == 1
+ assert [dag["dag_id"] for dag in body["dags"]] == [DAG1_ID]
+
+ def test_get_dags_filter_is_scheduled_true(self, session, test_client):
+ self._update_and_assert_dag_fields(
+ session,
+ {
+ DAG1_ID: {"timetable_type": "NullTimetable"},
+ DAG2_ID: {"timetable_type": "NullTimetable"},
+ DAG3_ID: {"timetable_type": "CronTriggerTimetable"},
+ },
+ )
+
+ response = test_client.get(
+ "/dags",
+ params={"is_scheduled": True, "exclude_stale": False},
+ )
+
+ assert response.status_code == 200
+ body = response.json()
+ assert body["total_entries"] == 1
+ assert [dag["dag_id"] for dag in body["dags"]] == [DAG3_ID]
+
+ def test_get_dags_filter_is_scheduled_combines_with_asset_filter(self,
session, test_client):
+ self._create_asset_test_data(session)
+ self._update_and_assert_dag_fields(
+ session,
+ {
+ ASSET_DEP_DAG_ID: {"timetable_type":
"AssetTriggeredTimetable"},
+ ASSET_DEP_DAG2_ID: {"timetable_type":
"AssetTriggeredTimetable"},
+ ASSET_SCHEDULED_DAG_ID: {"timetable_type":
"AssetTriggeredTimetable"},
+ DAG1_ID: {"timetable_type": "NullTimetable"},
+ DAG2_ID: {"timetable_type": "NullTimetable"},
+ DAG3_ID: {"timetable_type": "CronTriggerTimetable"},
+ },
+ )
+
+ response = test_client.get(
+ "/dags",
+ params={
+ "is_scheduled": True,
+ "has_asset_schedule": False,
+ "exclude_stale": False,
+ },
+ )
+
+ assert response.status_code == 200
+ body = response.json()
+ assert body["total_entries"] == 1
+ assert [dag["dag_id"] for dag in body["dags"]] == [DAG3_ID]
+
+ def test_get_dags_filter_is_scheduled_preserves_total_when_paginated(self,
session, test_client):
+ self._update_and_assert_dag_fields(
+ session,
+ {
+ DAG1_ID: {"timetable_type": "NullTimetable"},
+ DAG2_ID: {"timetable_type": "NullTimetable"},
+ DAG3_ID: {"timetable_type": "CronTriggerTimetable"},
+ },
+ )
+
+ response = test_client.get(
+ "/dags",
+ params={"is_scheduled": False, "exclude_stale": False, "limit": 1},
+ )
+
+ assert response.status_code == 200
+ body = response.json()
+ assert body["total_entries"] == 2
+ assert [dag["dag_id"] for dag in body["dags"]] == [DAG1_ID]
+
+ def test_get_dags_filter_is_scheduled_rejects_invalid_boolean(self,
test_client):
+ response = test_client.get("/dags", params={"is_scheduled":
"not-a-boolean"})
+
+ assert response.status_code == 422
+
+ def test_get_dags_filter_is_scheduled_response_401(self,
unauthenticated_test_client):
+ response = unauthenticated_test_client.get("/dags",
params={"is_scheduled": False})
+
+ assert response.status_code == 401
+
+ def test_get_dags_filter_is_scheduled_response_403(self,
unauthorized_test_client):
+ response = unauthorized_test_client.get("/dags",
params={"is_scheduled": False})
+
+ assert response.status_code == 403
+
@pytest.mark.parametrize(
("query_params", "expected_total_entries", "expected_ids"),
[