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"),
         [

Reply via email to