Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions airflow-core/newsfragments/69728.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add timetable type filtering to the Dags list.
4 changes: 4 additions & 0 deletions airflow-core/src/airflow/api_fastapi/common/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -1321,6 +1321,10 @@ def depends_float(
_PrefixSearchParam,
Depends(prefix_search_param_factory(DagModel.dag_display_name, "dag_display_name_prefix_pattern")),
]
QueryTimetableTypePrefixPatternSearch = Annotated[
_PrefixSearchParam,
Depends(prefix_search_param_factory(DagModel.timetable_type, "timetable_type_prefix_pattern")),
]
QueryBundleNameFilter = Annotated[
FilterParam[str | None],
Depends(filter_param_factory(DagModel.bundle_name, str | None, filter_name="bundle_name")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ class DAGWithLatestDagRunsCollectionResponse(BaseModel):
dags: list[DAGWithLatestDagRunsResponse]


class DagTimetableTypeCollectionResponse(BaseModel):
"""Timetable types used by Dags."""

timetable_types: list[str]
total_entries: int


class DAGRunStateCountsResponse(BaseModel):
"""Per-Dag counts of DagRuns grouped by state."""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,14 @@ paths:
description: Filter Dags by asset dependency (name or URI)
title: Asset Dependency
description: Filter Dags by asset dependency (name or URI)
- name: timetable_type
in: query
required: false
schema:
type: array
items:
type: string
title: Timetable Type
- name: has_pending_actions
in: query
required: false
Expand All @@ -572,6 +580,70 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
/ui/dags/timetable_types:
get:
tags:
- DAG
summary: Get Dag Timetable Types
description: Get timetable types used by readable Dags.
operationId: get_dag_timetable_types_ui
security:
- OAuth2PasswordBearer: []
- HTTPBearer: []
parameters:
- name: limit
in: query
required: false
schema:
type: integer
minimum: 0
default: 50
title: Limit
- name: offset
in: query
required: false
schema:
type: integer
minimum: 0
default: 0
title: Offset
- name: timetable_type_prefix_pattern
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: "Prefix match \u2014 returns items whose value starts with\
\ the given string (case-sensitive, index-friendly). Use the pipe `|`\
\ operator for OR logic (e.g. `dag1|dag2`). Use `~` to match all. Wildcard\
\ characters (`%`, `_`) are treated as literal characters. Trailing non-alphanumeric\
\ characters in the prefix are stripped before matching so the range scan\
\ stays index-compatible under locale-aware collations \u2014 e.g. `test_`\
\ effectively matches items starting with `test`, and `s3://` matches\
\ items starting with `s3`."
title: Timetable Type Prefix Pattern
description: "Prefix match \u2014 returns items whose value starts with the\
\ given string (case-sensitive, index-friendly). Use the pipe `|` operator\
\ for OR logic (e.g. `dag1|dag2`). Use `~` to match all. Wildcard characters\
\ (`%`, `_`) are treated as literal characters. Trailing non-alphanumeric\
\ characters in the prefix are stripped before matching so the range scan\
\ stays index-compatible under locale-aware collations \u2014 e.g. `test_`\
\ effectively matches items starting with `test`, and `s3://` matches items\
\ starting with `s3`."
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/DagTimetableTypeCollectionResponse'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
/ui/dags/{dag_id}/latest_run:
get:
tags:
Expand Down Expand Up @@ -2760,6 +2832,22 @@ components:
- dag_display_name
title: DagTagResponse
description: Dag Tag serializer for responses.
DagTimetableTypeCollectionResponse:
properties:
timetable_types:
items:
type: string
type: array
title: Timetable Types
total_entries:
type: integer
title: Total Entries
type: object
required:
- timetable_types
- total_entries
title: DagTimetableTypeCollectionResponse
description: Timetable types used by Dags.
DagVersionResponse:
properties:
id:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@

from __future__ import annotations

from collections.abc import Sequence
from typing import Annotated

from fastapi import Depends, HTTPException, Query, status
from sqlalchemy import func, literal, select, union_all
from sqlalchemy import false, func, literal, select, union_all
from sqlalchemy.orm import defaultload

from airflow.api_fastapi.auth.managers.models.resource_details import DagAccessEntity
Expand Down Expand Up @@ -52,6 +53,7 @@
QueryPendingActionsFilter,
QueryTagsFilter,
QueryTeamsFilter,
QueryTimetableTypePrefixPatternSearch,
SortParam,
filter_param_factory,
)
Expand All @@ -61,6 +63,7 @@
from airflow.api_fastapi.core_api.datamodels.ui.dags import (
DAGRunStateCountsResponse,
DAGsRunStateCountsCollectionResponse,
DagTimetableTypeCollectionResponse,
DAGWithLatestDagRunsCollectionResponse,
DAGWithLatestDagRunsResponse,
)
Expand Down Expand Up @@ -130,6 +133,10 @@ def get_dags(
is_favorite: QueryFavoriteFilter,
has_asset_schedule: QueryHasAssetScheduleFilter,
asset_dependency: QueryAssetDependencyFilter,
timetable_type: Annotated[
FilterParam[list[str] | None],
Depends(filter_param_factory(DagModel.timetable_type, list[str], FilterOptionEnum.IN)),
Comment on lines +137 to +138

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an exact match. To make this a more useful feature I think we need to follow how tags work and create an endpoint to query all timetables that exist amongst all the dags to give a typeahead dropdown UX

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

A new search bar only for tags? Is this what we are expecting from this UI?
I feel the UI is looking a little cluttered with multiple search bars for each different category.
@pierrejeambrun fyi

],
has_pending_actions: QueryPendingActionsFilter,
readable_dags_filter: ReadableDagsFilterDep,
session: SessionDep,
Expand Down Expand Up @@ -165,6 +172,7 @@ def get_dags(
is_favorite,
has_asset_schedule,
asset_dependency,
timetable_type,
has_pending_actions,
readable_dags_filter,
bundle_name,
Expand Down Expand Up @@ -275,6 +283,40 @@ def get_dags(
)


@dags_router.get(
"/timetable_types",
dependencies=[Depends(requires_access_dag(method="GET"))],
operation_id="get_dag_timetable_types_ui",
)
def get_dag_timetable_types(
limit: QueryLimit,
offset: QueryOffset,
timetable_type_prefix_pattern: QueryTimetableTypePrefixPatternSearch,
readable_dags_filter: ReadableDagsFilterDep,
session: SessionDep,
) -> DagTimetableTypeCollectionResponse:
"""Get timetable types used by readable Dags."""
query = (
select(DagModel.timetable_type)
.where(DagModel.is_stale == false(), DagModel.timetable_type != "")
.group_by(DagModel.timetable_type)
)
timetable_types_select, total_entries = paginated_select(
statement=query,
filters=[timetable_type_prefix_pattern, readable_dags_filter],
offset=offset,
limit=limit,
session=session,
)
timetable_types: Sequence[str] = session.scalars(
timetable_types_select.order_by(DagModel.timetable_type)
).all()
return DagTimetableTypeCollectionResponse(
timetable_types=list(timetable_types),
total_entries=total_entries,
)


@dags_router.get(
"/{dag_id}/latest_run",
responses=create_openapi_http_exception_doc([status.HTTP_404_NOT_FOUND]),
Expand Down
13 changes: 11 additions & 2 deletions airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ export const UseDagServiceGetDagTagsKeyFn = ({ limit, offset, orderBy, tagNamePa
export type DagServiceGetDagsUiDefaultResponse = Awaited<ReturnType<typeof DagService.getDagsUi>>;
export type DagServiceGetDagsUiQueryResult<TData = DagServiceGetDagsUiDefaultResponse, TError = unknown> = UseQueryResult<TData, TError>;
export const useDagServiceGetDagsUiKey = "DagServiceGetDagsUi";
export const UseDagServiceGetDagsUiKeyFn = ({ assetDependency, bundleName, bundleVersion, dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern, dagIdPrefixPattern, dagIds, dagRunsLimit, dagRunState, excludeStale, hasAssetSchedule, hasImportErrors, hasPendingActions, isFavorite, lastDagRunState, limit, offset, orderBy, owners, paused, tags, tagsMatchMode, teams }: {
export const UseDagServiceGetDagsUiKeyFn = ({ assetDependency, bundleName, bundleVersion, dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern, dagIdPrefixPattern, dagIds, dagRunsLimit, dagRunState, excludeStale, hasAssetSchedule, hasImportErrors, hasPendingActions, isFavorite, lastDagRunState, limit, offset, orderBy, owners, paused, tags, tagsMatchMode, teams, timetableType }: {
assetDependency?: string;
bundleName?: string;
bundleVersion?: string;
Expand All @@ -367,7 +367,16 @@ export const UseDagServiceGetDagsUiKeyFn = ({ assetDependency, bundleName, bundl
tags?: string[];
tagsMatchMode?: "any" | "all";
teams?: string[];
} = {}, queryKey?: Array<unknown>) => [useDagServiceGetDagsUiKey, ...(queryKey ?? [{ assetDependency, bundleName, bundleVersion, dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern, dagIdPrefixPattern, dagIds, dagRunsLimit, dagRunState, excludeStale, hasAssetSchedule, hasImportErrors, hasPendingActions, isFavorite, lastDagRunState, limit, offset, orderBy, owners, paused, tags, tagsMatchMode, teams }])];
timetableType?: string[];
} = {}, queryKey?: Array<unknown>) => [useDagServiceGetDagsUiKey, ...(queryKey ?? [{ assetDependency, bundleName, bundleVersion, dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern, dagIdPrefixPattern, dagIds, dagRunsLimit, dagRunState, excludeStale, hasAssetSchedule, hasImportErrors, hasPendingActions, isFavorite, lastDagRunState, limit, offset, orderBy, owners, paused, tags, tagsMatchMode, teams, timetableType }])];
export type DagServiceGetDagTimetableTypesUiDefaultResponse = Awaited<ReturnType<typeof DagService.getDagTimetableTypesUi>>;
export type DagServiceGetDagTimetableTypesUiQueryResult<TData = DagServiceGetDagTimetableTypesUiDefaultResponse, TError = unknown> = UseQueryResult<TData, TError>;
export const useDagServiceGetDagTimetableTypesUiKey = "DagServiceGetDagTimetableTypesUi";
export const UseDagServiceGetDagTimetableTypesUiKeyFn = ({ limit, offset, timetableTypePrefixPattern }: {
limit?: number;
offset?: number;
timetableTypePrefixPattern?: string;
} = {}, queryKey?: Array<unknown>) => [useDagServiceGetDagTimetableTypesUiKey, ...(queryKey ?? [{ limit, offset, timetableTypePrefixPattern }])];
export type DagServiceGetLatestRunInfoDefaultResponse = Awaited<ReturnType<typeof DagService.getLatestRunInfo>>;
export type DagServiceGetLatestRunInfoQueryResult<TData = DagServiceGetLatestRunInfoDefaultResponse, TError = unknown> = UseQueryResult<TData, TError>;
export const useDagServiceGetLatestRunInfoKey = "DagServiceGetLatestRunInfo";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -702,11 +702,12 @@ export const ensureUseDagServiceGetDagTagsData = (queryClient: QueryClient, { li
* @param data.isFavorite
* @param data.hasAssetSchedule Filter Dags with asset-based scheduling
* @param data.assetDependency Filter Dags by asset dependency (name or URI)
* @param data.timetableType
* @param data.hasPendingActions
* @returns DAGWithLatestDagRunsCollectionResponse Successful Response
* @throws ApiError
*/
export const ensureUseDagServiceGetDagsUiData = (queryClient: QueryClient, { assetDependency, bundleName, bundleVersion, dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern, dagIdPrefixPattern, dagIds, dagRunsLimit, dagRunState, excludeStale, hasAssetSchedule, hasImportErrors, hasPendingActions, isFavorite, lastDagRunState, limit, offset, orderBy, owners, paused, tags, tagsMatchMode, teams }: {
export const ensureUseDagServiceGetDagsUiData = (queryClient: QueryClient, { assetDependency, bundleName, bundleVersion, dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern, dagIdPrefixPattern, dagIds, dagRunsLimit, dagRunState, excludeStale, hasAssetSchedule, hasImportErrors, hasPendingActions, isFavorite, lastDagRunState, limit, offset, orderBy, owners, paused, tags, tagsMatchMode, teams, timetableType }: {
assetDependency?: string;
bundleName?: string;
bundleVersion?: string;
Expand All @@ -731,7 +732,23 @@ export const ensureUseDagServiceGetDagsUiData = (queryClient: QueryClient, { ass
tags?: string[];
tagsMatchMode?: "any" | "all";
teams?: string[];
} = {}) => queryClient.ensureQueryData({ queryKey: Common.UseDagServiceGetDagsUiKeyFn({ assetDependency, bundleName, bundleVersion, dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern, dagIdPrefixPattern, dagIds, dagRunsLimit, dagRunState, excludeStale, hasAssetSchedule, hasImportErrors, hasPendingActions, isFavorite, lastDagRunState, limit, offset, orderBy, owners, paused, tags, tagsMatchMode, teams }), queryFn: () => DagService.getDagsUi({ assetDependency, bundleName, bundleVersion, dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern, dagIdPrefixPattern, dagIds, dagRunsLimit, dagRunState, excludeStale, hasAssetSchedule, hasImportErrors, hasPendingActions, isFavorite, lastDagRunState, limit, offset, orderBy, owners, paused, tags, tagsMatchMode, teams }) });
timetableType?: string[];
} = {}) => queryClient.ensureQueryData({ queryKey: Common.UseDagServiceGetDagsUiKeyFn({ assetDependency, bundleName, bundleVersion, dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern, dagIdPrefixPattern, dagIds, dagRunsLimit, dagRunState, excludeStale, hasAssetSchedule, hasImportErrors, hasPendingActions, isFavorite, lastDagRunState, limit, offset, orderBy, owners, paused, tags, tagsMatchMode, teams, timetableType }), queryFn: () => DagService.getDagsUi({ assetDependency, bundleName, bundleVersion, dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern, dagIdPrefixPattern, dagIds, dagRunsLimit, dagRunState, excludeStale, hasAssetSchedule, hasImportErrors, hasPendingActions, isFavorite, lastDagRunState, limit, offset, orderBy, owners, paused, tags, tagsMatchMode, teams, timetableType }) });
/**
* Get Dag Timetable Types
* Get timetable types used by readable Dags.
* @param data The data for the request.
* @param data.limit
* @param data.offset
* @param data.timetableTypePrefixPattern Prefix match — returns items whose value starts with the given string (case-sensitive, index-friendly). Use the pipe `|` operator for OR logic (e.g. `dag1|dag2`). Use `~` to match all. Wildcard characters (`%`, `_`) are treated as literal characters. Trailing non-alphanumeric characters in the prefix are stripped before matching so the range scan stays index-compatible under locale-aware collations — e.g. `test_` effectively matches items starting with `test`, and `s3://` matches items starting with `s3`.
* @returns DagTimetableTypeCollectionResponse Successful Response
* @throws ApiError
*/
export const ensureUseDagServiceGetDagTimetableTypesUiData = (queryClient: QueryClient, { limit, offset, timetableTypePrefixPattern }: {
limit?: number;
offset?: number;
timetableTypePrefixPattern?: string;
} = {}) => queryClient.ensureQueryData({ queryKey: Common.UseDagServiceGetDagTimetableTypesUiKeyFn({ limit, offset, timetableTypePrefixPattern }), queryFn: () => DagService.getDagTimetableTypesUi({ limit, offset, timetableTypePrefixPattern }) });
/**
* Get Latest Run Info
* Get latest run.
Expand Down
21 changes: 19 additions & 2 deletions airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -702,11 +702,12 @@ export const prefetchUseDagServiceGetDagTags = (queryClient: QueryClient, { limi
* @param data.isFavorite
* @param data.hasAssetSchedule Filter Dags with asset-based scheduling
* @param data.assetDependency Filter Dags by asset dependency (name or URI)
* @param data.timetableType
* @param data.hasPendingActions
* @returns DAGWithLatestDagRunsCollectionResponse Successful Response
* @throws ApiError
*/
export const prefetchUseDagServiceGetDagsUi = (queryClient: QueryClient, { assetDependency, bundleName, bundleVersion, dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern, dagIdPrefixPattern, dagIds, dagRunsLimit, dagRunState, excludeStale, hasAssetSchedule, hasImportErrors, hasPendingActions, isFavorite, lastDagRunState, limit, offset, orderBy, owners, paused, tags, tagsMatchMode, teams }: {
export const prefetchUseDagServiceGetDagsUi = (queryClient: QueryClient, { assetDependency, bundleName, bundleVersion, dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern, dagIdPrefixPattern, dagIds, dagRunsLimit, dagRunState, excludeStale, hasAssetSchedule, hasImportErrors, hasPendingActions, isFavorite, lastDagRunState, limit, offset, orderBy, owners, paused, tags, tagsMatchMode, teams, timetableType }: {
assetDependency?: string;
bundleName?: string;
bundleVersion?: string;
Expand All @@ -731,7 +732,23 @@ export const prefetchUseDagServiceGetDagsUi = (queryClient: QueryClient, { asset
tags?: string[];
tagsMatchMode?: "any" | "all";
teams?: string[];
} = {}) => queryClient.prefetchQuery({ queryKey: Common.UseDagServiceGetDagsUiKeyFn({ assetDependency, bundleName, bundleVersion, dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern, dagIdPrefixPattern, dagIds, dagRunsLimit, dagRunState, excludeStale, hasAssetSchedule, hasImportErrors, hasPendingActions, isFavorite, lastDagRunState, limit, offset, orderBy, owners, paused, tags, tagsMatchMode, teams }), queryFn: () => DagService.getDagsUi({ assetDependency, bundleName, bundleVersion, dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern, dagIdPrefixPattern, dagIds, dagRunsLimit, dagRunState, excludeStale, hasAssetSchedule, hasImportErrors, hasPendingActions, isFavorite, lastDagRunState, limit, offset, orderBy, owners, paused, tags, tagsMatchMode, teams }) });
timetableType?: string[];
} = {}) => queryClient.prefetchQuery({ queryKey: Common.UseDagServiceGetDagsUiKeyFn({ assetDependency, bundleName, bundleVersion, dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern, dagIdPrefixPattern, dagIds, dagRunsLimit, dagRunState, excludeStale, hasAssetSchedule, hasImportErrors, hasPendingActions, isFavorite, lastDagRunState, limit, offset, orderBy, owners, paused, tags, tagsMatchMode, teams, timetableType }), queryFn: () => DagService.getDagsUi({ assetDependency, bundleName, bundleVersion, dagDisplayNamePattern, dagDisplayNamePrefixPattern, dagIdPattern, dagIdPrefixPattern, dagIds, dagRunsLimit, dagRunState, excludeStale, hasAssetSchedule, hasImportErrors, hasPendingActions, isFavorite, lastDagRunState, limit, offset, orderBy, owners, paused, tags, tagsMatchMode, teams, timetableType }) });
/**
* Get Dag Timetable Types
* Get timetable types used by readable Dags.
* @param data The data for the request.
* @param data.limit
* @param data.offset
* @param data.timetableTypePrefixPattern Prefix match — returns items whose value starts with the given string (case-sensitive, index-friendly). Use the pipe `|` operator for OR logic (e.g. `dag1|dag2`). Use `~` to match all. Wildcard characters (`%`, `_`) are treated as literal characters. Trailing non-alphanumeric characters in the prefix are stripped before matching so the range scan stays index-compatible under locale-aware collations — e.g. `test_` effectively matches items starting with `test`, and `s3://` matches items starting with `s3`.
* @returns DagTimetableTypeCollectionResponse Successful Response
* @throws ApiError
*/
export const prefetchUseDagServiceGetDagTimetableTypesUi = (queryClient: QueryClient, { limit, offset, timetableTypePrefixPattern }: {
limit?: number;
offset?: number;
timetableTypePrefixPattern?: string;
} = {}) => queryClient.prefetchQuery({ queryKey: Common.UseDagServiceGetDagTimetableTypesUiKeyFn({ limit, offset, timetableTypePrefixPattern }), queryFn: () => DagService.getDagTimetableTypesUi({ limit, offset, timetableTypePrefixPattern }) });
/**
* Get Latest Run Info
* Get latest run.
Expand Down
Loading
Loading