This is an automated email from the ASF dual-hosted git repository. rahulvats pushed a commit to branch backport-62583 in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 3624241c0358ded242bdf7e652999a384c58e54d Author: Richard Wu <[email protected]> AuthorDate: Thu Apr 2 04:53:14 2026 -0600 Deadlines endpoints (#62583) * feat: add deadlines UI endpoints * refactor(deadlines): streamline deadline response models and update related endpoints (cherry picked from commit 855c7ec6304734cebf2001d0aa2f65871805c643) --- .../api_fastapi/core_api/datamodels/ui/deadline.py | 20 ++ .../api_fastapi/core_api/openapi/_private_ui.yaml | 229 ++++++++++++++++++++- .../api_fastapi/core_api/routes/ui/deadlines.py | 141 +++++++++++-- .../src/airflow/ui/openapi-gen/queries/common.ts | 28 ++- .../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 ++++- .../airflow/ui/openapi-gen/requests/schemas.gen.ts | 80 ++++++- .../ui/openapi-gen/requests/services.gen.ts | 62 +++++- .../airflow/ui/openapi-gen/requests/types.gen.ts | 77 ++++++- .../core_api/routes/ui/test_deadlines.py | 225 ++++++++++++++++++-- 12 files changed, 981 insertions(+), 73 deletions(-) diff --git a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/deadline.py b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/deadline.py index 1ceddeac318..8b9370c4e10 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/deadline.py +++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/deadline.py @@ -33,6 +33,8 @@ class DeadlineResponse(BaseModel): deadline_time: datetime missed: bool created_at: datetime + dag_id: str = Field(validation_alias=AliasPath("dagrun", "dag_id")) + dag_run_id: str = Field(validation_alias=AliasPath("dagrun", "run_id")) alert_name: str | None = Field(validation_alias=AliasPath("deadline_alert", "name"), default=None) alert_description: str | None = Field( validation_alias=AliasPath("deadline_alert", "description"), default=None @@ -44,3 +46,21 @@ class DeadlineCollectionResponse(BaseModel): deadlines: Iterable[DeadlineResponse] total_entries: int + + +class DeadlineAlertResponse(BaseModel): + """DeadlineAlert serializer for responses.""" + + id: UUID + name: str | None = None + description: str | None = None + reference_type: str = Field(validation_alias=AliasPath("reference", "reference_type")) + interval: float = Field(description="Interval in seconds between deadline evaluations.") + created_at: datetime + + +class DeadlineAlertCollectionResponse(BaseModel): + """DeadlineAlert Collection serializer for responses.""" + + deadline_alerts: Iterable[DeadlineAlertResponse] + total_entries: int 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 87ed72c730b..96bc973483a 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 @@ -587,9 +587,15 @@ paths: get: tags: - Deadlines - summary: Get Dag Run Deadlines - description: Get all deadlines for a specific DAG run. - operationId: get_dag_run_deadlines + summary: Get Deadlines + description: 'Get deadlines for a DAG run. + + + This endpoint allows specifying `~` as the dag_id and dag_run_id to retrieve + Deadlines for all + + DAGs and DAG runs.' + operationId: get_deadlines security: - OAuth2PasswordBearer: [] - HTTPBearer: [] @@ -631,13 +637,93 @@ paths: type: string description: 'Attributes to order by, multi criteria sort is supported. Prefix with `-` for descending order. Supported attributes: `id, deadline_time, - created_at, alert_name`' + created_at, last_updated_at, missed, dag_id, dag_run_id, alert_name`' default: - deadline_time title: Order By description: 'Attributes to order by, multi criteria sort is supported. Prefix with `-` for descending order. Supported attributes: `id, deadline_time, - created_at, alert_name`' + created_at, last_updated_at, missed, dag_id, dag_run_id, alert_name`' + - name: missed + in: query + required: false + schema: + anyOf: + - type: boolean + - type: 'null' + title: Missed + - name: deadline_time_gte + in: query + required: false + schema: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Deadline Time Gte + - name: deadline_time_gt + in: query + required: false + schema: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Deadline Time Gt + - name: deadline_time_lte + in: query + required: false + schema: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Deadline Time Lte + - name: deadline_time_lt + in: query + required: false + schema: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Deadline Time Lt + - name: last_updated_at_gte + in: query + required: false + schema: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Last Updated At Gte + - name: last_updated_at_gt + in: query + required: false + schema: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Last Updated At Gt + - name: last_updated_at_lte + in: query + required: false + schema: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Last Updated At Lte + - name: last_updated_at_lt + in: query + required: false + schema: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Last Updated At Lt responses: '200': description: Successful Response @@ -645,6 +731,80 @@ paths: application/json: schema: $ref: '#/components/schemas/DeadlineCollectionResponse' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPExceptionResponse' + description: Bad Request + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPExceptionResponse' + description: Not Found + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /ui/dags/{dag_id}/deadlineAlerts: + get: + tags: + - Deadlines + summary: Get Dag Deadline Alerts + description: Get all deadline alerts defined on a DAG. + operationId: get_dag_deadline_alerts + security: + - OAuth2PasswordBearer: [] + - HTTPBearer: [] + parameters: + - name: dag_id + in: path + required: true + schema: + type: string + title: Dag Id + - 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: order_by + in: query + required: false + schema: + type: array + items: + type: string + description: 'Attributes to order by, multi criteria sort is supported. + Prefix with `-` for descending order. Supported attributes: `id, created_at, + name, interval`' + default: + - created_at + title: Order By + description: 'Attributes to order by, multi criteria sort is supported. Prefix + with `-` for descending order. Supported attributes: `id, created_at, name, + interval`' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/DeadlineAlertCollectionResponse' '404': content: application/json: @@ -2121,6 +2281,57 @@ components: - queued_dag_count title: DashboardDagStatsResponse description: Dashboard DAG Stats serializer for responses. + DeadlineAlertCollectionResponse: + properties: + deadline_alerts: + items: + $ref: '#/components/schemas/DeadlineAlertResponse' + type: array + title: Deadline Alerts + total_entries: + type: integer + title: Total Entries + type: object + required: + - deadline_alerts + - total_entries + title: DeadlineAlertCollectionResponse + description: DeadlineAlert Collection serializer for responses. + DeadlineAlertResponse: + properties: + id: + type: string + format: uuid + title: Id + name: + anyOf: + - type: string + - type: 'null' + title: Name + description: + anyOf: + - type: string + - type: 'null' + title: Description + reference_type: + type: string + title: Reference Type + interval: + type: number + title: Interval + description: Interval in seconds between deadline evaluations. + created_at: + type: string + format: date-time + title: Created At + type: object + required: + - id + - reference_type + - interval + - created_at + title: DeadlineAlertResponse + description: DeadlineAlert serializer for responses. DeadlineCollectionResponse: properties: deadlines: @@ -2154,6 +2365,12 @@ components: type: string format: date-time title: Created At + dag_id: + type: string + title: Dag Id + dag_run_id: + type: string + title: Dag Run Id alert_name: anyOf: - type: string @@ -2170,6 +2387,8 @@ components: - deadline_time - missed - created_at + - dag_id + - dag_run_id title: DeadlineResponse description: Deadline serializer for responses. EdgeResponse: diff --git a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/deadlines.py b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/deadlines.py index 5449cf6e953..7e4e44e855b 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/deadlines.py +++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/deadlines.py @@ -21,26 +21,39 @@ from typing import Annotated from fastapi import Depends, HTTPException, status from sqlalchemy import select -from sqlalchemy.orm import joinedload +from sqlalchemy.orm import contains_eager, noload from airflow.api_fastapi.auth.managers.models.resource_details import DagAccessEntity from airflow.api_fastapi.common.db.common import SessionDep, paginated_select -from airflow.api_fastapi.common.parameters import QueryLimit, QueryOffset, SortParam +from airflow.api_fastapi.common.parameters import ( + FilterParam, + QueryLimit, + QueryOffset, + RangeFilter, + SortParam, + datetime_range_filter_factory, + filter_param_factory, +) from airflow.api_fastapi.common.router import AirflowRouter -from airflow.api_fastapi.core_api.datamodels.ui.deadline import DeadlineCollectionResponse +from airflow.api_fastapi.core_api.datamodels.ui.deadline import ( + DeadlineAlertCollectionResponse, + DeadlineCollectionResponse, +) from airflow.api_fastapi.core_api.openapi.exceptions import create_openapi_http_exception_doc -from airflow.api_fastapi.core_api.security import requires_access_dag +from airflow.api_fastapi.core_api.security import ReadableDagRunsFilterDep, requires_access_dag from airflow.models.dagrun import DagRun from airflow.models.deadline import Deadline from airflow.models.deadline_alert import DeadlineAlert +from airflow.models.serialized_dag import SerializedDagModel -deadlines_router = AirflowRouter(prefix="/dags/{dag_id}/dagRuns/{dag_run_id}/deadlines", tags=["Deadlines"]) +deadlines_router = AirflowRouter(prefix="/dags/{dag_id}", tags=["Deadlines"]) @deadlines_router.get( - "", + "/dagRuns/{dag_run_id}/deadlines", responses=create_openapi_http_exception_doc( [ + status.HTTP_400_BAD_REQUEST, status.HTTP_404_NOT_FOUND, ] ), @@ -53,52 +66,138 @@ deadlines_router = AirflowRouter(prefix="/dags/{dag_id}/dagRuns/{dag_run_id}/dea ), ], ) -def get_dag_run_deadlines( +def get_deadlines( dag_id: str, dag_run_id: str, session: SessionDep, limit: QueryLimit, offset: QueryOffset, + readable_dag_runs_filter: ReadableDagRunsFilterDep, order_by: Annotated[ SortParam, Depends( SortParam( - ["id", "deadline_time", "created_at"], + ["id", "deadline_time", "created_at", "last_updated_at", "missed"], Deadline, to_replace={ + "dag_id": DagRun.dag_id, + "dag_run_id": DagRun.run_id, "alert_name": DeadlineAlert.name, }, ).dynamic_depends(default="deadline_time") ), ], + missed: Annotated[FilterParam[bool | None], Depends(filter_param_factory(Deadline.missed, bool | None))], + deadline_time: Annotated[RangeFilter, Depends(datetime_range_filter_factory("deadline_time", Deadline))], + last_updated_at: Annotated[ + RangeFilter, Depends(datetime_range_filter_factory("last_updated_at", Deadline)) + ], ) -> DeadlineCollectionResponse: - """Get all deadlines for a specific DAG run.""" - dag_run = session.scalar(select(DagRun).where(DagRun.dag_id == dag_id, DagRun.run_id == dag_run_id)) - - if not dag_run: - raise HTTPException( - status.HTTP_404_NOT_FOUND, - f"No DAG run found for dag_id={dag_id} dag_run_id={dag_run_id}", - ) + """ + Get deadlines for a DAG run. + This endpoint allows specifying `~` as the dag_id and dag_run_id to retrieve Deadlines for all + DAGs and DAG runs. + """ query = ( select(Deadline) .join(Deadline.dagrun) .outerjoin(Deadline.deadline_alert) - .where(Deadline.dagrun_id == dag_run.id) - .where(DagRun.dag_id == dag_id) - .options(joinedload(Deadline.deadline_alert)) + .options( + contains_eager(Deadline.dagrun).options(noload(DagRun.deadlines)), + contains_eager(Deadline.deadline_alert), + noload(Deadline.callback), + ) ) + if dag_run_id != "~": + if dag_id == "~": + raise HTTPException( + status.HTTP_400_BAD_REQUEST, + "dag_id is required when dag_run_id is specified", + ) + query = query.where(DagRun.dag_id == dag_id, DagRun.run_id == dag_run_id) + elif dag_id != "~": + query = query.where(DagRun.dag_id == dag_id) + deadlines_select, total_entries = paginated_select( statement=query, - filters=None, + filters=[readable_dag_runs_filter, missed, deadline_time, last_updated_at], order_by=order_by, offset=offset, limit=limit, session=session, ) - deadlines = session.scalars(deadlines_select) + deadlines = session.scalars(deadlines_select).unique() + + if dag_run_id != "~" and total_entries == 0: + dag_run = session.scalar(select(DagRun).where(DagRun.dag_id == dag_id, DagRun.run_id == dag_run_id)) + if not dag_run: + raise HTTPException( + status.HTTP_404_NOT_FOUND, + f"DagRun with dag_id: `{dag_id}` and run_id: `{dag_run_id}` was not found", + ) return DeadlineCollectionResponse(deadlines=deadlines, total_entries=total_entries) + + +@deadlines_router.get( + "/deadlineAlerts", + responses=create_openapi_http_exception_doc( + [ + status.HTTP_404_NOT_FOUND, + ] + ), + dependencies=[ + Depends( + requires_access_dag( + method="GET", + ) + ), + ], +) +def get_dag_deadline_alerts( + dag_id: str, + session: SessionDep, + limit: QueryLimit, + offset: QueryOffset, + order_by: Annotated[ + SortParam, + Depends( + SortParam( + ["id", "created_at", "name", "interval"], + DeadlineAlert, + ).dynamic_depends(default="created_at") + ), + ], +) -> DeadlineAlertCollectionResponse: + """Get all deadline alerts defined on a DAG.""" + serialized_dag = session.scalar( + select(SerializedDagModel) + .where(SerializedDagModel.dag_id == dag_id) + .order_by(SerializedDagModel.id.desc()) + ) + + if not serialized_dag: + raise HTTPException( + status.HTTP_404_NOT_FOUND, + f"Dag with id {dag_id} was not found", + ) + + query = select(DeadlineAlert).where( + DeadlineAlert.serialized_dag_id == serialized_dag.id, + ) + + alerts_select, total_entries = paginated_select( + statement=query, + filters=None, + order_by=order_by, + offset=offset, + limit=limit, + session=session, + ) + + alerts = session.scalars(alerts_select) + + return DeadlineAlertCollectionResponse(deadline_alerts=alerts, total_entries=total_entries) 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 612a3d56747..d37cf4fdad4 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts @@ -822,16 +822,34 @@ export type DashboardServiceDagStatsDefaultResponse = Awaited<ReturnType<typeof export type DashboardServiceDagStatsQueryResult<TData = DashboardServiceDagStatsDefaultResponse, TError = unknown> = UseQueryResult<TData, TError>; export const useDashboardServiceDagStatsKey = "DashboardServiceDagStats"; export const UseDashboardServiceDagStatsKeyFn = (queryKey?: Array<unknown>) => [useDashboardServiceDagStatsKey, ...(queryKey ?? [])]; -export type DeadlinesServiceGetDagRunDeadlinesDefaultResponse = Awaited<ReturnType<typeof DeadlinesService.getDagRunDeadlines>>; -export type DeadlinesServiceGetDagRunDeadlinesQueryResult<TData = DeadlinesServiceGetDagRunDeadlinesDefaultResponse, TError = unknown> = UseQueryResult<TData, TError>; -export const useDeadlinesServiceGetDagRunDeadlinesKey = "DeadlinesServiceGetDagRunDeadlines"; -export const UseDeadlinesServiceGetDagRunDeadlinesKeyFn = ({ dagId, dagRunId, limit, offset, orderBy }: { +export type DeadlinesServiceGetDeadlinesDefaultResponse = Awaited<ReturnType<typeof DeadlinesService.getDeadlines>>; +export type DeadlinesServiceGetDeadlinesQueryResult<TData = DeadlinesServiceGetDeadlinesDefaultResponse, TError = unknown> = UseQueryResult<TData, TError>; +export const useDeadlinesServiceGetDeadlinesKey = "DeadlinesServiceGetDeadlines"; +export const UseDeadlinesServiceGetDeadlinesKeyFn = ({ dagId, dagRunId, deadlineTimeGt, deadlineTimeGte, deadlineTimeLt, deadlineTimeLte, lastUpdatedAtGt, lastUpdatedAtGte, lastUpdatedAtLt, lastUpdatedAtLte, limit, missed, offset, orderBy }: { dagId: string; dagRunId: string; + deadlineTimeGt?: string; + deadlineTimeGte?: string; + deadlineTimeLt?: string; + deadlineTimeLte?: string; + lastUpdatedAtGt?: string; + lastUpdatedAtGte?: string; + lastUpdatedAtLt?: string; + lastUpdatedAtLte?: string; limit?: number; + missed?: boolean; offset?: number; orderBy?: string[]; -}, queryKey?: Array<unknown>) => [useDeadlinesServiceGetDagRunDeadlinesKey, ...(queryKey ?? [{ dagId, dagRunId, limit, offset, orderBy }])]; +}, queryKey?: Array<unknown>) => [useDeadlinesServiceGetDeadlinesKey, ...(queryKey ?? [{ dagId, dagRunId, deadlineTimeGt, deadlineTimeGte, deadlineTimeLt, deadlineTimeLte, lastUpdatedAtGt, lastUpdatedAtGte, lastUpdatedAtLt, lastUpdatedAtLte, limit, missed, offset, orderBy }])]; +export type DeadlinesServiceGetDagDeadlineAlertsDefaultResponse = Awaited<ReturnType<typeof DeadlinesService.getDagDeadlineAlerts>>; +export type DeadlinesServiceGetDagDeadlineAlertsQueryResult<TData = DeadlinesServiceGetDagDeadlineAlertsDefaultResponse, TError = unknown> = UseQueryResult<TData, TError>; +export const useDeadlinesServiceGetDagDeadlineAlertsKey = "DeadlinesServiceGetDagDeadlineAlerts"; +export const UseDeadlinesServiceGetDagDeadlineAlertsKeyFn = ({ dagId, limit, offset, orderBy }: { + dagId: string; + limit?: number; + offset?: number; + orderBy?: string[]; +}, queryKey?: Array<unknown>) => [useDeadlinesServiceGetDagDeadlineAlertsKey, ...(queryKey ?? [{ dagId, limit, offset, orderBy }])]; export type StructureServiceStructureDataDefaultResponse = Awaited<ReturnType<typeof StructureService.structureData>>; export type StructureServiceStructureDataQueryResult<TData = StructureServiceStructureDataDefaultResponse, TError = unknown> = UseQueryResult<TData, TError>; export const useStructureServiceStructureDataKey = "StructureServiceStructureData"; 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 8bc9e9df8e6..24bcb69c78a 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts @@ -1560,24 +1560,62 @@ export const ensureUseDashboardServiceHistoricalMetricsData = (queryClient: Quer */ export const ensureUseDashboardServiceDagStatsData = (queryClient: QueryClient) => queryClient.ensureQueryData({ queryKey: Common.UseDashboardServiceDagStatsKeyFn(), queryFn: () => DashboardService.dagStats() }); /** -* Get Dag Run Deadlines -* Get all deadlines for a specific DAG run. +* Get Deadlines +* Get deadlines for a DAG run. +* +* This endpoint allows specifying `~` as the dag_id and dag_run_id to retrieve Deadlines for all +* DAGs and DAG runs. * @param data The data for the request. * @param data.dagId * @param data.dagRunId * @param data.limit * @param data.offset -* @param data.orderBy Attributes to order by, multi criteria sort is supported. Prefix with `-` for descending order. Supported attributes: `id, deadline_time, created_at, alert_name` +* @param data.orderBy Attributes to order by, multi criteria sort is supported. Prefix with `-` for descending order. Supported attributes: `id, deadline_time, created_at, last_updated_at, missed, dag_id, dag_run_id, alert_name` +* @param data.missed +* @param data.deadlineTimeGte +* @param data.deadlineTimeGt +* @param data.deadlineTimeLte +* @param data.deadlineTimeLt +* @param data.lastUpdatedAtGte +* @param data.lastUpdatedAtGt +* @param data.lastUpdatedAtLte +* @param data.lastUpdatedAtLt * @returns DeadlineCollectionResponse Successful Response * @throws ApiError */ -export const ensureUseDeadlinesServiceGetDagRunDeadlinesData = (queryClient: QueryClient, { dagId, dagRunId, limit, offset, orderBy }: { +export const ensureUseDeadlinesServiceGetDeadlinesData = (queryClient: QueryClient, { dagId, dagRunId, deadlineTimeGt, deadlineTimeGte, deadlineTimeLt, deadlineTimeLte, lastUpdatedAtGt, lastUpdatedAtGte, lastUpdatedAtLt, lastUpdatedAtLte, limit, missed, offset, orderBy }: { dagId: string; dagRunId: string; + deadlineTimeGt?: string; + deadlineTimeGte?: string; + deadlineTimeLt?: string; + deadlineTimeLte?: string; + lastUpdatedAtGt?: string; + lastUpdatedAtGte?: string; + lastUpdatedAtLt?: string; + lastUpdatedAtLte?: string; + limit?: number; + missed?: boolean; + offset?: number; + orderBy?: string[]; +}) => queryClient.ensureQueryData({ queryKey: Common.UseDeadlinesServiceGetDeadlinesKeyFn({ dagId, dagRunId, deadlineTimeGt, deadlineTimeGte, deadlineTimeLt, deadlineTimeLte, lastUpdatedAtGt, lastUpdatedAtGte, lastUpdatedAtLt, lastUpdatedAtLte, limit, missed, offset, orderBy }), queryFn: () => DeadlinesService.getDeadlines({ dagId, dagRunId, deadlineTimeGt, deadlineTimeGte, deadlineTimeLt, deadlineTimeLte, lastUpdatedAtGt, lastUpdatedAtGte, lastUpdatedAtLt, lastUpdatedAtLte, limit, misse [...] +/** +* Get Dag Deadline Alerts +* Get all deadline alerts defined on a DAG. +* @param data The data for the request. +* @param data.dagId +* @param data.limit +* @param data.offset +* @param data.orderBy Attributes to order by, multi criteria sort is supported. Prefix with `-` for descending order. Supported attributes: `id, created_at, name, interval` +* @returns DeadlineAlertCollectionResponse Successful Response +* @throws ApiError +*/ +export const ensureUseDeadlinesServiceGetDagDeadlineAlertsData = (queryClient: QueryClient, { dagId, limit, offset, orderBy }: { + dagId: string; limit?: number; offset?: number; orderBy?: string[]; -}) => queryClient.ensureQueryData({ queryKey: Common.UseDeadlinesServiceGetDagRunDeadlinesKeyFn({ dagId, dagRunId, limit, offset, orderBy }), queryFn: () => DeadlinesService.getDagRunDeadlines({ dagId, dagRunId, limit, offset, orderBy }) }); +}) => queryClient.ensureQueryData({ queryKey: Common.UseDeadlinesServiceGetDagDeadlineAlertsKeyFn({ dagId, limit, offset, orderBy }), queryFn: () => DeadlinesService.getDagDeadlineAlerts({ dagId, limit, offset, orderBy }) }); /** * Structure Data * Get Structure Data. 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 f4cb6f482bd..b6c6318a978 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts @@ -1560,24 +1560,62 @@ export const prefetchUseDashboardServiceHistoricalMetrics = (queryClient: QueryC */ export const prefetchUseDashboardServiceDagStats = (queryClient: QueryClient) => queryClient.prefetchQuery({ queryKey: Common.UseDashboardServiceDagStatsKeyFn(), queryFn: () => DashboardService.dagStats() }); /** -* Get Dag Run Deadlines -* Get all deadlines for a specific DAG run. +* Get Deadlines +* Get deadlines for a DAG run. +* +* This endpoint allows specifying `~` as the dag_id and dag_run_id to retrieve Deadlines for all +* DAGs and DAG runs. * @param data The data for the request. * @param data.dagId * @param data.dagRunId * @param data.limit * @param data.offset -* @param data.orderBy Attributes to order by, multi criteria sort is supported. Prefix with `-` for descending order. Supported attributes: `id, deadline_time, created_at, alert_name` +* @param data.orderBy Attributes to order by, multi criteria sort is supported. Prefix with `-` for descending order. Supported attributes: `id, deadline_time, created_at, last_updated_at, missed, dag_id, dag_run_id, alert_name` +* @param data.missed +* @param data.deadlineTimeGte +* @param data.deadlineTimeGt +* @param data.deadlineTimeLte +* @param data.deadlineTimeLt +* @param data.lastUpdatedAtGte +* @param data.lastUpdatedAtGt +* @param data.lastUpdatedAtLte +* @param data.lastUpdatedAtLt * @returns DeadlineCollectionResponse Successful Response * @throws ApiError */ -export const prefetchUseDeadlinesServiceGetDagRunDeadlines = (queryClient: QueryClient, { dagId, dagRunId, limit, offset, orderBy }: { +export const prefetchUseDeadlinesServiceGetDeadlines = (queryClient: QueryClient, { dagId, dagRunId, deadlineTimeGt, deadlineTimeGte, deadlineTimeLt, deadlineTimeLte, lastUpdatedAtGt, lastUpdatedAtGte, lastUpdatedAtLt, lastUpdatedAtLte, limit, missed, offset, orderBy }: { dagId: string; dagRunId: string; + deadlineTimeGt?: string; + deadlineTimeGte?: string; + deadlineTimeLt?: string; + deadlineTimeLte?: string; + lastUpdatedAtGt?: string; + lastUpdatedAtGte?: string; + lastUpdatedAtLt?: string; + lastUpdatedAtLte?: string; + limit?: number; + missed?: boolean; + offset?: number; + orderBy?: string[]; +}) => queryClient.prefetchQuery({ queryKey: Common.UseDeadlinesServiceGetDeadlinesKeyFn({ dagId, dagRunId, deadlineTimeGt, deadlineTimeGte, deadlineTimeLt, deadlineTimeLte, lastUpdatedAtGt, lastUpdatedAtGte, lastUpdatedAtLt, lastUpdatedAtLte, limit, missed, offset, orderBy }), queryFn: () => DeadlinesService.getDeadlines({ dagId, dagRunId, deadlineTimeGt, deadlineTimeGte, deadlineTimeLt, deadlineTimeLte, lastUpdatedAtGt, lastUpdatedAtGte, lastUpdatedAtLt, lastUpdatedAtLte, limit, missed, [...] +/** +* Get Dag Deadline Alerts +* Get all deadline alerts defined on a DAG. +* @param data The data for the request. +* @param data.dagId +* @param data.limit +* @param data.offset +* @param data.orderBy Attributes to order by, multi criteria sort is supported. Prefix with `-` for descending order. Supported attributes: `id, created_at, name, interval` +* @returns DeadlineAlertCollectionResponse Successful Response +* @throws ApiError +*/ +export const prefetchUseDeadlinesServiceGetDagDeadlineAlerts = (queryClient: QueryClient, { dagId, limit, offset, orderBy }: { + dagId: string; limit?: number; offset?: number; orderBy?: string[]; -}) => queryClient.prefetchQuery({ queryKey: Common.UseDeadlinesServiceGetDagRunDeadlinesKeyFn({ dagId, dagRunId, limit, offset, orderBy }), queryFn: () => DeadlinesService.getDagRunDeadlines({ dagId, dagRunId, limit, offset, orderBy }) }); +}) => queryClient.prefetchQuery({ queryKey: Common.UseDeadlinesServiceGetDagDeadlineAlertsKeyFn({ dagId, limit, offset, orderBy }), queryFn: () => DeadlinesService.getDagDeadlineAlerts({ dagId, limit, offset, orderBy }) }); /** * Structure Data * Get Structure Data. 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 8e9ef5aa29d..657fc0364a2 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts @@ -1560,24 +1560,62 @@ export const useDashboardServiceHistoricalMetrics = <TData = Common.DashboardSer */ export const useDashboardServiceDagStats = <TData = Common.DashboardServiceDagStatsDefaultResponse, TError = unknown, TQueryKey extends Array<unknown> = unknown[]>(queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: Common.UseDashboardServiceDagStatsKeyFn(queryKey), queryFn: () => DashboardService.dagStats() as TData, ...options }); /** -* Get Dag Run Deadlines -* Get all deadlines for a specific DAG run. +* Get Deadlines +* Get deadlines for a DAG run. +* +* This endpoint allows specifying `~` as the dag_id and dag_run_id to retrieve Deadlines for all +* DAGs and DAG runs. * @param data The data for the request. * @param data.dagId * @param data.dagRunId * @param data.limit * @param data.offset -* @param data.orderBy Attributes to order by, multi criteria sort is supported. Prefix with `-` for descending order. Supported attributes: `id, deadline_time, created_at, alert_name` +* @param data.orderBy Attributes to order by, multi criteria sort is supported. Prefix with `-` for descending order. Supported attributes: `id, deadline_time, created_at, last_updated_at, missed, dag_id, dag_run_id, alert_name` +* @param data.missed +* @param data.deadlineTimeGte +* @param data.deadlineTimeGt +* @param data.deadlineTimeLte +* @param data.deadlineTimeLt +* @param data.lastUpdatedAtGte +* @param data.lastUpdatedAtGt +* @param data.lastUpdatedAtLte +* @param data.lastUpdatedAtLt * @returns DeadlineCollectionResponse Successful Response * @throws ApiError */ -export const useDeadlinesServiceGetDagRunDeadlines = <TData = Common.DeadlinesServiceGetDagRunDeadlinesDefaultResponse, TError = unknown, TQueryKey extends Array<unknown> = unknown[]>({ dagId, dagRunId, limit, offset, orderBy }: { +export const useDeadlinesServiceGetDeadlines = <TData = Common.DeadlinesServiceGetDeadlinesDefaultResponse, TError = unknown, TQueryKey extends Array<unknown> = unknown[]>({ dagId, dagRunId, deadlineTimeGt, deadlineTimeGte, deadlineTimeLt, deadlineTimeLte, lastUpdatedAtGt, lastUpdatedAtGte, lastUpdatedAtLt, lastUpdatedAtLte, limit, missed, offset, orderBy }: { dagId: string; dagRunId: string; + deadlineTimeGt?: string; + deadlineTimeGte?: string; + deadlineTimeLt?: string; + deadlineTimeLte?: string; + lastUpdatedAtGt?: string; + lastUpdatedAtGte?: string; + lastUpdatedAtLt?: string; + lastUpdatedAtLte?: string; + limit?: number; + missed?: boolean; + offset?: number; + orderBy?: string[]; +}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: Common.UseDeadlinesServiceGetDeadlinesKeyFn({ dagId, dagRunId, deadlineTimeGt, deadlineTimeGte, deadlineTimeLt, deadlineTimeLte, lastUpdatedAtGt, lastUpdatedAtGte, lastUpdatedAtLt, lastUpdatedAtLte, limit, missed, offset, orderBy }, queryKey), queryFn: () => DeadlinesService.getDeadlines({ dagId, dagRunId, deadlineTimeGt, deadlineTimeGte, deadlineTimeLt, [...] +/** +* Get Dag Deadline Alerts +* Get all deadline alerts defined on a DAG. +* @param data The data for the request. +* @param data.dagId +* @param data.limit +* @param data.offset +* @param data.orderBy Attributes to order by, multi criteria sort is supported. Prefix with `-` for descending order. Supported attributes: `id, created_at, name, interval` +* @returns DeadlineAlertCollectionResponse Successful Response +* @throws ApiError +*/ +export const useDeadlinesServiceGetDagDeadlineAlerts = <TData = Common.DeadlinesServiceGetDagDeadlineAlertsDefaultResponse, TError = unknown, TQueryKey extends Array<unknown> = unknown[]>({ dagId, limit, offset, orderBy }: { + dagId: string; limit?: number; offset?: number; orderBy?: string[]; -}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: Common.UseDeadlinesServiceGetDagRunDeadlinesKeyFn({ dagId, dagRunId, limit, offset, orderBy }, queryKey), queryFn: () => DeadlinesService.getDagRunDeadlines({ dagId, dagRunId, limit, offset, orderBy }) as TData, ...options }); +}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: Common.UseDeadlinesServiceGetDagDeadlineAlertsKeyFn({ dagId, limit, offset, orderBy }, queryKey), queryFn: () => DeadlinesService.getDagDeadlineAlerts({ dagId, limit, offset, orderBy }) as TData, ...options }); /** * Structure Data * Get Structure Data. 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 c4a41691b1a..4f5e12fd437 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts @@ -1560,24 +1560,62 @@ export const useDashboardServiceHistoricalMetricsSuspense = <TData = Common.Dash */ export const useDashboardServiceDagStatsSuspense = <TData = Common.DashboardServiceDagStatsDefaultResponse, TError = unknown, TQueryKey extends Array<unknown> = unknown[]>(queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: Common.UseDashboardServiceDagStatsKeyFn(queryKey), queryFn: () => DashboardService.dagStats() as TData, ...options }); /** -* Get Dag Run Deadlines -* Get all deadlines for a specific DAG run. +* Get Deadlines +* Get deadlines for a DAG run. +* +* This endpoint allows specifying `~` as the dag_id and dag_run_id to retrieve Deadlines for all +* DAGs and DAG runs. * @param data The data for the request. * @param data.dagId * @param data.dagRunId * @param data.limit * @param data.offset -* @param data.orderBy Attributes to order by, multi criteria sort is supported. Prefix with `-` for descending order. Supported attributes: `id, deadline_time, created_at, alert_name` +* @param data.orderBy Attributes to order by, multi criteria sort is supported. Prefix with `-` for descending order. Supported attributes: `id, deadline_time, created_at, last_updated_at, missed, dag_id, dag_run_id, alert_name` +* @param data.missed +* @param data.deadlineTimeGte +* @param data.deadlineTimeGt +* @param data.deadlineTimeLte +* @param data.deadlineTimeLt +* @param data.lastUpdatedAtGte +* @param data.lastUpdatedAtGt +* @param data.lastUpdatedAtLte +* @param data.lastUpdatedAtLt * @returns DeadlineCollectionResponse Successful Response * @throws ApiError */ -export const useDeadlinesServiceGetDagRunDeadlinesSuspense = <TData = Common.DeadlinesServiceGetDagRunDeadlinesDefaultResponse, TError = unknown, TQueryKey extends Array<unknown> = unknown[]>({ dagId, dagRunId, limit, offset, orderBy }: { +export const useDeadlinesServiceGetDeadlinesSuspense = <TData = Common.DeadlinesServiceGetDeadlinesDefaultResponse, TError = unknown, TQueryKey extends Array<unknown> = unknown[]>({ dagId, dagRunId, deadlineTimeGt, deadlineTimeGte, deadlineTimeLt, deadlineTimeLte, lastUpdatedAtGt, lastUpdatedAtGte, lastUpdatedAtLt, lastUpdatedAtLte, limit, missed, offset, orderBy }: { dagId: string; dagRunId: string; + deadlineTimeGt?: string; + deadlineTimeGte?: string; + deadlineTimeLt?: string; + deadlineTimeLte?: string; + lastUpdatedAtGt?: string; + lastUpdatedAtGte?: string; + lastUpdatedAtLt?: string; + lastUpdatedAtLte?: string; + limit?: number; + missed?: boolean; + offset?: number; + orderBy?: string[]; +}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: Common.UseDeadlinesServiceGetDeadlinesKeyFn({ dagId, dagRunId, deadlineTimeGt, deadlineTimeGte, deadlineTimeLt, deadlineTimeLte, lastUpdatedAtGt, lastUpdatedAtGte, lastUpdatedAtLt, lastUpdatedAtLte, limit, missed, offset, orderBy }, queryKey), queryFn: () => DeadlinesService.getDeadlines({ dagId, dagRunId, deadlineTimeGt, deadlineTimeGte, deadlin [...] +/** +* Get Dag Deadline Alerts +* Get all deadline alerts defined on a DAG. +* @param data The data for the request. +* @param data.dagId +* @param data.limit +* @param data.offset +* @param data.orderBy Attributes to order by, multi criteria sort is supported. Prefix with `-` for descending order. Supported attributes: `id, created_at, name, interval` +* @returns DeadlineAlertCollectionResponse Successful Response +* @throws ApiError +*/ +export const useDeadlinesServiceGetDagDeadlineAlertsSuspense = <TData = Common.DeadlinesServiceGetDagDeadlineAlertsDefaultResponse, TError = unknown, TQueryKey extends Array<unknown> = unknown[]>({ dagId, limit, offset, orderBy }: { + dagId: string; limit?: number; offset?: number; orderBy?: string[]; -}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: Common.UseDeadlinesServiceGetDagRunDeadlinesKeyFn({ dagId, dagRunId, limit, offset, orderBy }, queryKey), queryFn: () => DeadlinesService.getDagRunDeadlines({ dagId, dagRunId, limit, offset, orderBy }) as TData, ...options }); +}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: Common.UseDeadlinesServiceGetDagDeadlineAlertsKeyFn({ dagId, limit, offset, orderBy }, queryKey), queryFn: () => DeadlinesService.getDagDeadlineAlerts({ dagId, limit, offset, orderBy }) as TData, ...options }); /** * Structure Data * Get Structure Data. diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts index be72276746c..a53beeaf6e9 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts @@ -7844,6 +7844,76 @@ export const $DashboardDagStatsResponse = { description: 'Dashboard DAG Stats serializer for responses.' } as const; +export const $DeadlineAlertCollectionResponse = { + properties: { + deadline_alerts: { + items: { + '$ref': '#/components/schemas/DeadlineAlertResponse' + }, + type: 'array', + title: 'Deadline Alerts' + }, + total_entries: { + type: 'integer', + title: 'Total Entries' + } + }, + type: 'object', + required: ['deadline_alerts', 'total_entries'], + title: 'DeadlineAlertCollectionResponse', + description: 'DeadlineAlert Collection serializer for responses.' +} as const; + +export const $DeadlineAlertResponse = { + properties: { + id: { + type: 'string', + format: 'uuid', + title: 'Id' + }, + name: { + anyOf: [ + { + type: 'string' + }, + { + type: 'null' + } + ], + title: 'Name' + }, + description: { + anyOf: [ + { + type: 'string' + }, + { + type: 'null' + } + ], + title: 'Description' + }, + reference_type: { + type: 'string', + title: 'Reference Type' + }, + interval: { + type: 'number', + title: 'Interval', + description: 'Interval in seconds between deadline evaluations.' + }, + created_at: { + type: 'string', + format: 'date-time', + title: 'Created At' + } + }, + type: 'object', + required: ['id', 'reference_type', 'interval', 'created_at'], + title: 'DeadlineAlertResponse', + description: 'DeadlineAlert serializer for responses.' +} as const; + export const $DeadlineCollectionResponse = { properties: { deadlines: { @@ -7885,6 +7955,14 @@ export const $DeadlineResponse = { format: 'date-time', title: 'Created At' }, + dag_id: { + type: 'string', + title: 'Dag Id' + }, + dag_run_id: { + type: 'string', + title: 'Dag Run Id' + }, alert_name: { anyOf: [ { @@ -7909,7 +7987,7 @@ export const $DeadlineResponse = { } }, type: 'object', - required: ['id', 'deadline_time', 'missed', 'created_at'], + required: ['id', 'deadline_time', 'missed', 'created_at', 'dag_id', 'dag_run_id'], title: 'DeadlineResponse', description: 'Deadline serializer for responses.' } as const; 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 6e701bf68dc..71112f0e6ad 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 @@ -3,7 +3,7 @@ import type { CancelablePromise } from './core/CancelablePromise'; import { OpenAPI } from './core/OpenAPI'; import { request as __request } from './core/request'; -import type { GetAssetsData, GetAssetsResponse, GetAssetAliasesData, GetAssetAliasesResponse, GetAssetAliasData, GetAssetAliasResponse, GetAssetEventsData, GetAssetEventsResponse, CreateAssetEventData, CreateAssetEventResponse, MaterializeAssetData, MaterializeAssetResponse, GetAssetQueuedEventsData, GetAssetQueuedEventsResponse, DeleteAssetQueuedEventsData, DeleteAssetQueuedEventsResponse, GetAssetData, GetAssetResponse, GetDagAssetQueuedEventsData, GetDagAssetQueuedEventsResponse, Dele [...] +import type { GetAssetsData, GetAssetsResponse, GetAssetAliasesData, GetAssetAliasesResponse, GetAssetAliasData, GetAssetAliasResponse, GetAssetEventsData, GetAssetEventsResponse, CreateAssetEventData, CreateAssetEventResponse, MaterializeAssetData, MaterializeAssetResponse, GetAssetQueuedEventsData, GetAssetQueuedEventsResponse, DeleteAssetQueuedEventsData, DeleteAssetQueuedEventsResponse, GetAssetData, GetAssetResponse, GetDagAssetQueuedEventsData, GetDagAssetQueuedEventsResponse, Dele [...] export class AssetService { /** @@ -3989,18 +3989,30 @@ export class DashboardService { export class DeadlinesService { /** - * Get Dag Run Deadlines - * Get all deadlines for a specific DAG run. + * Get Deadlines + * Get deadlines for a DAG run. + * + * This endpoint allows specifying `~` as the dag_id and dag_run_id to retrieve Deadlines for all + * DAGs and DAG runs. * @param data The data for the request. * @param data.dagId * @param data.dagRunId * @param data.limit * @param data.offset - * @param data.orderBy Attributes to order by, multi criteria sort is supported. Prefix with `-` for descending order. Supported attributes: `id, deadline_time, created_at, alert_name` + * @param data.orderBy Attributes to order by, multi criteria sort is supported. Prefix with `-` for descending order. Supported attributes: `id, deadline_time, created_at, last_updated_at, missed, dag_id, dag_run_id, alert_name` + * @param data.missed + * @param data.deadlineTimeGte + * @param data.deadlineTimeGt + * @param data.deadlineTimeLte + * @param data.deadlineTimeLt + * @param data.lastUpdatedAtGte + * @param data.lastUpdatedAtGt + * @param data.lastUpdatedAtLte + * @param data.lastUpdatedAtLt * @returns DeadlineCollectionResponse Successful Response * @throws ApiError */ - public static getDagRunDeadlines(data: GetDagRunDeadlinesData): CancelablePromise<GetDagRunDeadlinesResponse> { + public static getDeadlines(data: GetDeadlinesData): CancelablePromise<GetDeadlinesResponse> { return __request(OpenAPI, { method: 'GET', url: '/ui/dags/{dag_id}/dagRuns/{dag_run_id}/deadlines', @@ -4008,6 +4020,46 @@ export class DeadlinesService { dag_id: data.dagId, dag_run_id: data.dagRunId }, + query: { + limit: data.limit, + offset: data.offset, + order_by: data.orderBy, + missed: data.missed, + deadline_time_gte: data.deadlineTimeGte, + deadline_time_gt: data.deadlineTimeGt, + deadline_time_lte: data.deadlineTimeLte, + deadline_time_lt: data.deadlineTimeLt, + last_updated_at_gte: data.lastUpdatedAtGte, + last_updated_at_gt: data.lastUpdatedAtGt, + last_updated_at_lte: data.lastUpdatedAtLte, + last_updated_at_lt: data.lastUpdatedAtLt + }, + errors: { + 400: 'Bad Request', + 404: 'Not Found', + 422: 'Validation Error' + } + }); + } + + /** + * Get Dag Deadline Alerts + * Get all deadline alerts defined on a DAG. + * @param data The data for the request. + * @param data.dagId + * @param data.limit + * @param data.offset + * @param data.orderBy Attributes to order by, multi criteria sort is supported. Prefix with `-` for descending order. Supported attributes: `id, created_at, name, interval` + * @returns DeadlineAlertCollectionResponse Successful Response + * @throws ApiError + */ + public static getDagDeadlineAlerts(data: GetDagDeadlineAlertsData): CancelablePromise<GetDagDeadlineAlertsResponse> { + return __request(OpenAPI, { + method: 'GET', + url: '/ui/dags/{dag_id}/deadlineAlerts', + path: { + dag_id: data.dagId + }, query: { limit: data.limit, offset: data.offset, 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 36228389032..1076854cd6e 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 @@ -1920,6 +1920,29 @@ export type DashboardDagStatsResponse = { queued_dag_count: number; }; +/** + * DeadlineAlert Collection serializer for responses. + */ +export type DeadlineAlertCollectionResponse = { + deadline_alerts: Array<DeadlineAlertResponse>; + total_entries: number; +}; + +/** + * DeadlineAlert serializer for responses. + */ +export type DeadlineAlertResponse = { + id: string; + name?: string | null; + description?: string | null; + reference_type: string; + /** + * Interval in seconds between deadline evaluations. + */ + interval: number; + created_at: string; +}; + /** * Deadline Collection serializer for responses. */ @@ -1936,6 +1959,8 @@ export type DeadlineResponse = { deadline_time: string; missed: boolean; created_at: string; + dag_id: string; + dag_run_id: string; alert_name?: string | null; alert_description?: string | null; }; @@ -3598,18 +3623,39 @@ export type HistoricalMetricsResponse = HistoricalMetricDataResponse; export type DagStatsResponse2 = DashboardDagStatsResponse; -export type GetDagRunDeadlinesData = { +export type GetDeadlinesData = { dagId: string; dagRunId: string; + deadlineTimeGt?: string | null; + deadlineTimeGte?: string | null; + deadlineTimeLt?: string | null; + deadlineTimeLte?: string | null; + lastUpdatedAtGt?: string | null; + lastUpdatedAtGte?: string | null; + lastUpdatedAtLt?: string | null; + lastUpdatedAtLte?: string | null; limit?: number; + missed?: boolean | null; offset?: number; /** - * Attributes to order by, multi criteria sort is supported. Prefix with `-` for descending order. Supported attributes: `id, deadline_time, created_at, alert_name` + * Attributes to order by, multi criteria sort is supported. Prefix with `-` for descending order. Supported attributes: `id, deadline_time, created_at, last_updated_at, missed, dag_id, dag_run_id, alert_name` */ orderBy?: Array<(string)>; }; -export type GetDagRunDeadlinesResponse = DeadlineCollectionResponse; +export type GetDeadlinesResponse = DeadlineCollectionResponse; + +export type GetDagDeadlineAlertsData = { + dagId: string; + limit?: number; + offset?: number; + /** + * Attributes to order by, multi criteria sort is supported. Prefix with `-` for descending order. Supported attributes: `id, created_at, name, interval` + */ + orderBy?: Array<(string)>; +}; + +export type GetDagDeadlineAlertsResponse = DeadlineAlertCollectionResponse; export type StructureDataData = { dagId: string; @@ -6872,12 +6918,35 @@ export type $OpenApiTs = { }; '/ui/dags/{dag_id}/dagRuns/{dag_run_id}/deadlines': { get: { - req: GetDagRunDeadlinesData; + req: GetDeadlinesData; res: { /** * Successful Response */ 200: DeadlineCollectionResponse; + /** + * Bad Request + */ + 400: HTTPExceptionResponse; + /** + * Not Found + */ + 404: HTTPExceptionResponse; + /** + * Validation Error + */ + 422: HTTPValidationError; + }; + }; + }; + '/ui/dags/{dag_id}/deadlineAlerts': { + get: { + req: GetDagDeadlineAlertsData; + res: { + /** + * Successful Response + */ + 200: DeadlineAlertCollectionResponse; /** * Not Found */ diff --git a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_deadlines.py b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_deadlines.py index 4dec594eb64..52c61514398 100644 --- a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_deadlines.py +++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_deadlines.py @@ -42,6 +42,7 @@ from tests_common.test_utils.db import ( pytestmark = pytest.mark.db_test DAG_ID = "test_deadlines_dag" +DAG_ID_2 = "test_deadlines_dag_2" # Each run represents a different deadline scenario tested below. RUN_EMPTY = "run_empty" # no deadlines @@ -50,6 +51,7 @@ RUN_MISSED = "run_missed" # 1 deadline, missed=True RUN_ALERT = "run_alert" # 1 deadline linked to a DeadlineAlert RUN_MULTI = "run_multi" # 3 deadlines added out-of-order (ordering test) RUN_OTHER = "run_other" # has 1 deadline; used to verify per-run isolation +RUN_DAG2 = "run_dag2" # belongs to DAG_ID_2, which is used for cross-dag filter tests ALERT_NAME = "SLA Breach Alert" ALERT_DESCRIPTION = "Fires when SLA is breached" @@ -195,6 +197,28 @@ def setup(dag_maker, session): ) ) + # Second DAG with two deadlines (for cross-DAG filter verification) + with dag_maker(DAG_ID_2, serialized=True, session=session): + EmptyOperator(task_id="task") + + run_dag2 = dag_maker.create_dagrun( + run_id=RUN_DAG2, + state=DagRunState.SUCCESS, + run_type=DagRunType.SCHEDULED, + logical_date=timezone.datetime(2024, 11, 7), + triggered_by=DagRunTriggeredByType.TEST, + ) + # Two non-missed deadlines in Jan 2025 (avoids disturbing deadline_time filter tests) + for dl_time in [timezone.datetime(2025, 1, 10), timezone.datetime(2025, 1, 20)]: + session.add( + Deadline( + deadline_time=dl_time, + callback=_cb(), + dagrun_id=run_dag2.id, + deadline_alert_id=None, + ) + ) + dag_maker.sync_dagbag_to_db() session.commit() yield @@ -208,10 +232,6 @@ def setup(dag_maker, session): class TestGetDagRunDeadlines: """Tests for GET /dags/{dag_id}/dagRuns/{dag_run_id}/deadlines.""" - # ------------------------------------------------------------------ - # 200 – happy paths - # ------------------------------------------------------------------ - def test_no_deadlines_returns_empty_list(self, test_client): response = test_client.get(f"/dags/{DAG_ID}/dagRuns/{RUN_EMPTY}/deadlines") assert response.status_code == 200 @@ -227,6 +247,8 @@ class TestGetDagRunDeadlines: assert deadline1["missed"] is False assert deadline1["alert_name"] is None assert deadline1["alert_description"] is None + assert deadline1["dag_id"] == DAG_ID + assert deadline1["dag_run_id"] == RUN_SINGLE assert "id" in deadline1 assert "created_at" in deadline1 @@ -283,10 +305,6 @@ class TestGetDagRunDeadlines: assert response.status_code == 200 assert response.json()["total_entries"] == 1 - # ------------------------------------------------------------------ - # 404 - # ------------------------------------------------------------------ - @pytest.mark.parametrize( ("dag_id", "run_id"), [ @@ -299,10 +317,6 @@ class TestGetDagRunDeadlines: response = test_client.get(f"/dags/{dag_id}/dagRuns/{run_id}/deadlines") assert response.status_code == 404 - # ------------------------------------------------------------------ - # 401 / 403 - # ------------------------------------------------------------------ - def test_should_response_401(self, unauthenticated_test_client): response = unauthenticated_test_client.get(f"/dags/{DAG_ID}/dagRuns/{RUN_EMPTY}/deadlines") assert response.status_code == 401 @@ -310,3 +324,190 @@ class TestGetDagRunDeadlines: def test_should_response_403(self, unauthorized_test_client): response = unauthorized_test_client.get(f"/dags/{DAG_ID}/dagRuns/{RUN_EMPTY}/deadlines") assert response.status_code == 403 + + +class TestGetDeadlines: + """Tests for GET /dags/{dag_id}/dagRuns/{dag_run_id}/deadlines with ~ wildcards.""" + + def test_returns_all_deadlines(self, test_client): + """All deadlines across all DAGs and runs are returned""" + response = test_client.get("/dags/~/dagRuns/~/deadlines") + assert response.status_code == 200 + data = response.json() + # DAG_ID: 7 deadlines (run_single, run_missed, run_alert, 3×run_multi, run_other) + # DAG_ID_2: 2 deadlines (run_dag2) + assert data["total_entries"] == 9 + assert len(data["deadlines"]) == 9 + assert {dl["dag_id"] for dl in data["deadlines"]} == {DAG_ID, DAG_ID_2} + for dl in data["deadlines"]: + assert "id" in dl + assert "dag_run_id" in dl + assert "deadline_time" in dl + assert "missed" in dl + + def test_filter_by_dag_id(self, test_client): + """Specifying a dag_id returns only that DAG's deadlines, not other DAGs'.""" + response = test_client.get(f"/dags/{DAG_ID}/dagRuns/~/deadlines") + assert response.status_code == 200 + data = response.json() + assert data["total_entries"] == 7 + for dl in data["deadlines"]: + assert dl["dag_id"] == DAG_ID + + # Cross-check: the second DAG's deadlines are isolated + dag2_response = test_client.get(f"/dags/{DAG_ID_2}/dagRuns/~/deadlines") + assert dag2_response.json()["total_entries"] == 2 + for dl in dag2_response.json()["deadlines"]: + assert dl["dag_id"] == DAG_ID_2 + + def test_filter_missed_true(self, test_client): + """Only missed deadlines are returned when missed=true.""" + response = test_client.get("/dags/~/dagRuns/~/deadlines", params={"missed": "true"}) + assert response.status_code == 200 + data = response.json() + assert data["total_entries"] == 1 + assert data["deadlines"][0]["missed"] is True + + def test_filter_missed_false(self, test_client): + """Only non-missed (pending) deadlines are returned when missed=false.""" + response = test_client.get("/dags/~/dagRuns/~/deadlines", params={"missed": "false"}) + assert response.status_code == 200 + data = response.json() + # DAG_ID non-missed: 6; DAG_ID_2 non-missed: 2 + assert data["total_entries"] == 8 + for dl in data["deadlines"]: + assert dl["missed"] is False + + def test_filter_deadline_time_gte(self, test_client): + """deadline_time_gte filters out deadlines before the given time.""" + response = test_client.get( + "/dags/~/dagRuns/~/deadlines", params={"deadline_time_gte": "2025-02-01T00:00:00Z"} + ) + assert response.status_code == 200 + data = response.json() + # Deadline times at/after 2025-02-01: run_multi 2025-02-01, 2025-03-01, run_other 2025-06-01 = 3 + assert data["total_entries"] == 3 + + def test_filter_deadline_time_lte(self, test_client): + """deadline_time_lte filters out deadlines after the given time.""" + response = test_client.get( + "/dags/~/dagRuns/~/deadlines", params={"deadline_time_lte": "2024-12-31T23:59:59Z"} + ) + assert response.status_code == 200 + data = response.json() + # Only run_missed's 2024-12-01 is before 2025 + assert data["total_entries"] == 1 + + def test_filter_last_updated_at_gte(self, test_client): + """last_updated_at_gte filters deadlines updated before the given time.""" + response = test_client.get( + "/dags/~/dagRuns/~/deadlines", params={"last_updated_at_gte": "2099-01-01T00:00:00Z"} + ) + assert response.status_code == 200 + assert response.json()["total_entries"] == 0 + + def test_filter_last_updated_at_lte(self, test_client): + """last_updated_at_lte filters deadlines updated after the given time.""" + response = test_client.get( + "/dags/~/dagRuns/~/deadlines", params={"last_updated_at_lte": "2099-01-01T00:00:00Z"} + ) + assert response.status_code == 200 + assert response.json()["total_entries"] == 9 + + def test_ordered_by_deadline_time_ascending_by_default(self, test_client): + """Deadlines are ordered by deadline_time ascending by default.""" + response = test_client.get("/dags/~/dagRuns/~/deadlines") + assert response.status_code == 200 + times = [dl["deadline_time"] for dl in response.json()["deadlines"]] + assert times == sorted(times) + + def test_pagination(self, test_client): + """limit and offset work correctly.""" + all_resp = test_client.get("/dags/~/dagRuns/~/deadlines") + all_ids = [dl["id"] for dl in all_resp.json()["deadlines"]] + + page1 = test_client.get("/dags/~/dagRuns/~/deadlines", params={"limit": 3, "offset": 0}).json()[ + "deadlines" + ] + page2 = test_client.get("/dags/~/dagRuns/~/deadlines", params={"limit": 3, "offset": 3}).json()[ + "deadlines" + ] + + assert [dl["id"] for dl in page1] == all_ids[:3] + assert [dl["id"] for dl in page2] == all_ids[3:6] + + def test_alert_name_present_when_linked(self, test_client): + """Deadlines linked to a DeadlineAlert include alert_name and alert_description.""" + response = test_client.get(f"/dags/{DAG_ID}/dagRuns/~/deadlines") + assert response.status_code == 200 + deadlines = response.json()["deadlines"] + alerts = [dl for dl in deadlines if dl["alert_name"] is not None] + assert len(alerts) == 1 + assert alerts[0]["alert_name"] == ALERT_NAME + assert alerts[0]["alert_description"] == ALERT_DESCRIPTION + + def test_filter_nonexistent_dag_returns_empty(self, test_client): + """Filtering by a dag_id that doesn't exist returns an empty list.""" + response = test_client.get("/dags/nonexistent_dag/dagRuns/~/deadlines") + assert response.status_code == 200 + assert response.json() == {"deadlines": [], "total_entries": 0} + + def test_dag_run_id_without_dag_id_returns_400(self, test_client): + """Specifying a concrete dag_run_id with dag_id=~ is a bad request.""" + response = test_client.get(f"/dags/~/dagRuns/{RUN_SINGLE}/deadlines") + assert response.status_code == 400 + + def test_should_response_401(self, unauthenticated_test_client): + response = unauthenticated_test_client.get("/dags/~/dagRuns/~/deadlines") + assert response.status_code == 401 + + def test_should_response_403(self, unauthorized_test_client): + response = unauthorized_test_client.get("/dags/~/dagRuns/~/deadlines") + assert response.status_code == 403 + + +class TestGetDagDeadlineAlerts: + """Tests for GET /dags/{dag_id}/deadlineAlerts.""" + + def test_returns_deadline_alerts_for_dag(self, test_client): + """Returns all deadline alerts defined on the DAG.""" + response = test_client.get(f"/dags/{DAG_ID}/deadlineAlerts") + assert response.status_code == 200 + data = response.json() + assert data["total_entries"] == 1 + assert len(data["deadline_alerts"]) == 1 + + def test_alert_response_fields(self, test_client): + """Alert response includes expected fields with correct values.""" + response = test_client.get(f"/dags/{DAG_ID}/deadlineAlerts") + assert response.status_code == 200 + alert = response.json()["deadline_alerts"][0] + assert alert["name"] == ALERT_NAME + assert alert["description"] == ALERT_DESCRIPTION + assert alert["interval"] == 3600.0 + assert alert["reference_type"] == "DagRunQueuedAtDeadline" + assert "id" in alert + assert "created_at" in alert + + def test_dag_with_no_alerts_returns_empty_list(self, test_client, dag_maker, session): + """A DAG with no deadline alerts returns an empty list.""" + with dag_maker("dag_no_alerts", serialized=True, session=session): + EmptyOperator(task_id="task") + dag_maker.sync_dagbag_to_db() + session.commit() + + response = test_client.get("/dags/dag_no_alerts/deadlineAlerts") + assert response.status_code == 200 + assert response.json() == {"deadline_alerts": [], "total_entries": 0} + + def test_should_response_404_for_nonexistent_dag(self, test_client): + response = test_client.get("/dags/nonexistent_dag/deadlineAlerts") + assert response.status_code == 404 + + def test_should_response_401(self, unauthenticated_test_client): + response = unauthenticated_test_client.get(f"/dags/{DAG_ID}/deadlineAlerts") + assert response.status_code == 401 + + def test_should_response_403(self, unauthorized_test_client): + response = unauthorized_test_client.get(f"/dags/{DAG_ID}/deadlineAlerts") + assert response.status_code == 403
