This is an automated email from the ASF dual-hosted git repository.
pierrejeambrun 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 4ed8bdd9ac6 Update UI list deadlines endpoint (#62374)
4ed8bdd9ac6 is described below
commit 4ed8bdd9ac6fdd4ce95521c5ea243b504326e760
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Tue Feb 24 11:05:42 2026 +0100
Update UI list deadlines endpoint (#62374)
* Adjustments to ui list deadlines endpoint
* Update tests
---
.../api_fastapi/core_api/datamodels/ui/deadline.py | 18 ++++-
.../api_fastapi/core_api/openapi/_private_ui.yaml | 61 ++++++++++++++---
.../api_fastapi/core_api/routes/ui/deadlines.py | 74 +++++++++++++--------
.../src/airflow/ui/openapi-gen/queries/common.ts | 9 ++-
.../ui/openapi-gen/queries/ensureQueryData.ts | 16 +++--
.../src/airflow/ui/openapi-gen/queries/prefetch.ts | 16 +++--
.../src/airflow/ui/openapi-gen/queries/queries.ts | 16 +++--
.../src/airflow/ui/openapi-gen/queries/suspense.ts | 16 +++--
.../airflow/ui/openapi-gen/requests/schemas.gen.ts | 22 ++++++-
.../ui/openapi-gen/requests/services.gen.ts | 16 +++--
.../airflow/ui/openapi-gen/requests/types.gen.ts | 24 +++++--
.../core_api/routes/ui/test_deadlines.py | 76 ++++++++++++++--------
12 files changed, 264 insertions(+), 100 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 61391885bc4..625d5ae7585 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
@@ -17,18 +17,30 @@
from __future__ import annotations
+from collections.abc import Iterable
from datetime import datetime
from uuid import UUID
+from pydantic import AliasPath, Field
+
from airflow.api_fastapi.core_api.base import BaseModel
class DeadlineResponse(BaseModel):
- """Deadline data for the DAG run deadlines tab."""
+ """Deadline serializer for responses."""
id: UUID
deadline_time: datetime
missed: bool
created_at: datetime
- alert_name: str | None = None
- alert_description: str | None = None
+ 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
+ )
+
+
+class DealineCollectionResponse(BaseModel):
+ """Deadline Collection serializer for responses."""
+
+ deadlines: Iterable[DeadlineResponse]
+ 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 fd4e56aef64..e6698ad40d1 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
@@ -554,7 +554,7 @@ paths:
security:
- OAuth2PasswordBearer: []
- HTTPBearer: []
- /ui/deadlines/{dag_id}/{run_id}:
+ /ui/dags/{dag_id}/dagRuns/{dag_run_id}/deadlines:
get:
tags:
- Deadlines
@@ -571,22 +571,51 @@ paths:
schema:
type: string
title: Dag Id
- - name: run_id
+ - name: dag_run_id
in: path
required: true
schema:
type: string
- title: Run Id
+ title: Dag Run 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,
deadline_time,
+ created_at, 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`'
responses:
'200':
description: Successful Response
content:
application/json:
schema:
- type: array
- items:
- $ref: '#/components/schemas/DeadlineResponse'
- title: Response Get Dag Run Deadlines
+ $ref: '#/components/schemas/DealineCollectionResponse'
'404':
content:
application/json:
@@ -2081,7 +2110,23 @@ components:
- missed
- created_at
title: DeadlineResponse
- description: Deadline data for the DAG run deadlines tab.
+ description: Deadline serializer for responses.
+ DealineCollectionResponse:
+ properties:
+ deadlines:
+ items:
+ $ref: '#/components/schemas/DeadlineResponse'
+ type: array
+ title: Deadlines
+ total_entries:
+ type: integer
+ title: Total Entries
+ type: object
+ required:
+ - deadlines
+ - total_entries
+ title: DealineCollectionResponse
+ description: Deadline Collection serializer for responses.
EdgeResponse:
properties:
source_id:
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 03aeed13e4f..ffe6ae5ddd2 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
@@ -17,24 +17,28 @@
from __future__ import annotations
+from typing import Annotated
+
from fastapi import Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.orm import joinedload
from airflow.api_fastapi.auth.managers.models.resource_details import
DagAccessEntity
-from airflow.api_fastapi.common.db.common import SessionDep
+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.router import AirflowRouter
-from airflow.api_fastapi.core_api.datamodels.ui.deadline import
DeadlineResponse
+from airflow.api_fastapi.core_api.datamodels.ui.deadline import
DealineCollectionResponse
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.models.dagrun import DagRun
from airflow.models.deadline import Deadline
+from airflow.models.deadline_alert import DeadlineAlert
-deadlines_router = AirflowRouter(prefix="/deadlines", tags=["Deadlines"])
+deadlines_router =
AirflowRouter(prefix="/dags/{dag_id}/dagRuns/{dag_run_id}/deadlines",
tags=["Deadlines"])
@deadlines_router.get(
- "/{dag_id}/{run_id}",
+ "",
responses=create_openapi_http_exception_doc(
[
status.HTTP_404_NOT_FOUND,
@@ -51,36 +55,50 @@ deadlines_router = AirflowRouter(prefix="/deadlines",
tags=["Deadlines"])
)
def get_dag_run_deadlines(
dag_id: str,
- run_id: str,
+ dag_run_id: str,
session: SessionDep,
-) -> list[DeadlineResponse]:
+ limit: QueryLimit,
+ offset: QueryOffset,
+ order_by: Annotated[
+ SortParam,
+ Depends(
+ SortParam(
+ ["id", "deadline_time", "created_at"],
+ Deadline,
+ to_replace={
+ "alert_name": DeadlineAlert.name,
+ },
+ ).dynamic_depends(default="deadline_time")
+ ),
+ ],
+) -> DealineCollectionResponse:
"""Get all deadlines for a specific DAG run."""
- dag_run = session.scalar(select(DagRun).where(DagRun.dag_id == dag_id,
DagRun.run_id == run_id))
+ 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} run_id={run_id}",
+ f"No DAG run found for dag_id={dag_id} dag_run_id={dag_run_id}",
)
- deadlines = (
- session.scalars(
- select(Deadline)
- .where(Deadline.dagrun_id == dag_run.id)
- .options(joinedload(Deadline.deadline_alert))
- .order_by(Deadline.deadline_time.asc())
- )
- .unique()
- .all()
+ 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))
)
- return [
- DeadlineResponse(
- id=d.id,
- deadline_time=d.deadline_time,
- missed=d.missed,
- created_at=d.created_at,
- alert_name=d.deadline_alert.name if d.deadline_alert else None,
- alert_description=d.deadline_alert.description if d.deadline_alert
else None,
- )
- for d in deadlines
- ]
+ deadlines_select, total_entries = paginated_select(
+ statement=query,
+ filters=None,
+ order_by=order_by,
+ offset=offset,
+ limit=limit,
+ session=session,
+ )
+
+ deadlines = session.scalars(deadlines_select)
+
+ return DealineCollectionResponse(deadlines=deadlines,
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 705a35f4cca..4f32c3773cf 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
@@ -824,10 +824,13 @@ export const UseDashboardServiceDagStatsKeyFn =
(queryKey?: Array<unknown>) => [
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, runId }: {
+export const UseDeadlinesServiceGetDagRunDeadlinesKeyFn = ({ dagId, dagRunId,
limit, offset, orderBy }: {
dagId: string;
- runId: string;
-}, queryKey?: Array<unknown>) => [useDeadlinesServiceGetDagRunDeadlinesKey,
...(queryKey ?? [{ dagId, runId }])];
+ dagRunId: string;
+ limit?: number;
+ offset?: number;
+ orderBy?: string[];
+}, queryKey?: Array<unknown>) => [useDeadlinesServiceGetDagRunDeadlinesKey,
...(queryKey ?? [{ dagId, dagRunId, 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 a16531feb37..13bd08260c7 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
@@ -1562,14 +1562,20 @@ export const ensureUseDashboardServiceDagStatsData =
(queryClient: QueryClient)
* Get all deadlines for a specific DAG run.
* @param data The data for the request.
* @param data.dagId
-* @param data.runId
-* @returns DeadlineResponse Successful Response
+* @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`
+* @returns DealineCollectionResponse Successful Response
* @throws ApiError
*/
-export const ensureUseDeadlinesServiceGetDagRunDeadlinesData = (queryClient:
QueryClient, { dagId, runId }: {
+export const ensureUseDeadlinesServiceGetDagRunDeadlinesData = (queryClient:
QueryClient, { dagId, dagRunId, limit, offset, orderBy }: {
dagId: string;
- runId: string;
-}) => queryClient.ensureQueryData({ queryKey:
Common.UseDeadlinesServiceGetDagRunDeadlinesKeyFn({ dagId, runId }), queryFn:
() => DeadlinesService.getDagRunDeadlines({ dagId, runId }) });
+ dagRunId: 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 }) });
/**
* 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 a4b5afb54df..5320a7f8d59 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
@@ -1562,14 +1562,20 @@ export const prefetchUseDashboardServiceDagStats =
(queryClient: QueryClient) =>
* Get all deadlines for a specific DAG run.
* @param data The data for the request.
* @param data.dagId
-* @param data.runId
-* @returns DeadlineResponse Successful Response
+* @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`
+* @returns DealineCollectionResponse Successful Response
* @throws ApiError
*/
-export const prefetchUseDeadlinesServiceGetDagRunDeadlines = (queryClient:
QueryClient, { dagId, runId }: {
+export const prefetchUseDeadlinesServiceGetDagRunDeadlines = (queryClient:
QueryClient, { dagId, dagRunId, limit, offset, orderBy }: {
dagId: string;
- runId: string;
-}) => queryClient.prefetchQuery({ queryKey:
Common.UseDeadlinesServiceGetDagRunDeadlinesKeyFn({ dagId, runId }), queryFn:
() => DeadlinesService.getDagRunDeadlines({ dagId, runId }) });
+ dagRunId: 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 }) });
/**
* 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 7bafcd6b706..62c9073d5d4 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
@@ -1562,14 +1562,20 @@ export const useDashboardServiceDagStats = <TData =
Common.DashboardServiceDagSt
* Get all deadlines for a specific DAG run.
* @param data The data for the request.
* @param data.dagId
-* @param data.runId
-* @returns DeadlineResponse Successful Response
+* @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`
+* @returns DealineCollectionResponse Successful Response
* @throws ApiError
*/
-export const useDeadlinesServiceGetDagRunDeadlines = <TData =
Common.DeadlinesServiceGetDagRunDeadlinesDefaultResponse, TError = unknown,
TQueryKey extends Array<unknown> = unknown[]>({ dagId, runId }: {
+export const useDeadlinesServiceGetDagRunDeadlines = <TData =
Common.DeadlinesServiceGetDagRunDeadlinesDefaultResponse, TError = unknown,
TQueryKey extends Array<unknown> = unknown[]>({ dagId, dagRunId, limit, offset,
orderBy }: {
dagId: string;
- runId: string;
-}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey:
Common.UseDeadlinesServiceGetDagRunDeadlinesKeyFn({ dagId, runId }, queryKey),
queryFn: () => DeadlinesService.getDagRunDeadlines({ dagId, runId }) as TData,
...options });
+ dagRunId: 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 });
/**
* 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 867f08383cf..d8058605e46 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
@@ -1562,14 +1562,20 @@ export const useDashboardServiceDagStatsSuspense =
<TData = Common.DashboardServ
* Get all deadlines for a specific DAG run.
* @param data The data for the request.
* @param data.dagId
-* @param data.runId
-* @returns DeadlineResponse Successful Response
+* @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`
+* @returns DealineCollectionResponse Successful Response
* @throws ApiError
*/
-export const useDeadlinesServiceGetDagRunDeadlinesSuspense = <TData =
Common.DeadlinesServiceGetDagRunDeadlinesDefaultResponse, TError = unknown,
TQueryKey extends Array<unknown> = unknown[]>({ dagId, runId }: {
+export const useDeadlinesServiceGetDagRunDeadlinesSuspense = <TData =
Common.DeadlinesServiceGetDagRunDeadlinesDefaultResponse, TError = unknown,
TQueryKey extends Array<unknown> = unknown[]>({ dagId, dagRunId, limit, offset,
orderBy }: {
dagId: string;
- runId: string;
-}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey:
Common.UseDeadlinesServiceGetDagRunDeadlinesKeyFn({ dagId, runId }, queryKey),
queryFn: () => DeadlinesService.getDagRunDeadlines({ dagId, runId }) as TData,
...options });
+ dagRunId: 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 });
/**
* 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 5a784efaed8..2e1a39ad880 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
@@ -7897,7 +7897,27 @@ export const $DeadlineResponse = {
type: 'object',
required: ['id', 'deadline_time', 'missed', 'created_at'],
title: 'DeadlineResponse',
- description: 'Deadline data for the DAG run deadlines tab.'
+ description: 'Deadline serializer for responses.'
+} as const;
+
+export const $DealineCollectionResponse = {
+ properties: {
+ deadlines: {
+ items: {
+ '$ref': '#/components/schemas/DeadlineResponse'
+ },
+ type: 'array',
+ title: 'Deadlines'
+ },
+ total_entries: {
+ type: 'integer',
+ title: 'Total Entries'
+ }
+ },
+ type: 'object',
+ required: ['deadlines', 'total_entries'],
+ title: 'DealineCollectionResponse',
+ description: 'Deadline Collection serializer for responses.'
} as const;
export const $EdgeResponse = {
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 99a9f0d0e23..929afa7e5e6 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
@@ -3972,17 +3972,25 @@ export class DeadlinesService {
* Get all deadlines for a specific DAG run.
* @param data The data for the request.
* @param data.dagId
- * @param data.runId
- * @returns DeadlineResponse Successful Response
+ * @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`
+ * @returns DealineCollectionResponse Successful Response
* @throws ApiError
*/
public static getDagRunDeadlines(data: GetDagRunDeadlinesData):
CancelablePromise<GetDagRunDeadlinesResponse> {
return __request(OpenAPI, {
method: 'GET',
- url: '/ui/deadlines/{dag_id}/{run_id}',
+ url: '/ui/dags/{dag_id}/dagRuns/{dag_run_id}/deadlines',
path: {
dag_id: data.dagId,
- run_id: data.runId
+ dag_run_id: data.dagRunId
+ },
+ query: {
+ limit: data.limit,
+ offset: data.offset,
+ order_by: data.orderBy
},
errors: {
404: 'Not Found',
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 9c68149385e..a0e7c2e8d77 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
@@ -1928,7 +1928,7 @@ export type DashboardDagStatsResponse = {
};
/**
- * Deadline data for the DAG run deadlines tab.
+ * Deadline serializer for responses.
*/
export type DeadlineResponse = {
id: string;
@@ -1939,6 +1939,14 @@ export type DeadlineResponse = {
alert_description?: string | null;
};
+/**
+ * Deadline Collection serializer for responses.
+ */
+export type DealineCollectionResponse = {
+ deadlines: Array<DeadlineResponse>;
+ total_entries: number;
+};
+
/**
* Edge serializer for responses.
*/
@@ -3549,10 +3557,16 @@ export type DagStatsResponse2 =
DashboardDagStatsResponse;
export type GetDagRunDeadlinesData = {
dagId: string;
- runId: string;
+ dagRunId: string;
+ limit?: number;
+ 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`
+ */
+ orderBy?: Array<(string)>;
};
-export type GetDagRunDeadlinesResponse = Array<DeadlineResponse>;
+export type GetDagRunDeadlinesResponse = DealineCollectionResponse;
export type StructureDataData = {
dagId: string;
@@ -6798,14 +6812,14 @@ export type $OpenApiTs = {
};
};
};
- '/ui/deadlines/{dag_id}/{run_id}': {
+ '/ui/dags/{dag_id}/dagRuns/{dag_run_id}/deadlines': {
get: {
req: GetDagRunDeadlinesData;
res: {
/**
* Successful Response
*/
- 200: Array<DeadlineResponse>;
+ 200: DealineCollectionResponse;
/**
* 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 4d3184a6094..4dec594eb64 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
@@ -30,6 +30,7 @@ from airflow.sdk.definitions.deadline import DeadlineReference
from airflow.utils.state import DagRunState
from airflow.utils.types import DagRunTriggeredByType, DagRunType
+from tests_common.test_utils.asserts import assert_queries_count
from tests_common.test_utils.db import (
clear_db_dags,
clear_db_deadline,
@@ -205,63 +206,82 @@ def setup(dag_maker, session):
class TestGetDagRunDeadlines:
- """Tests for GET /deadlines/{dag_id}/{run_id}."""
+ """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"/deadlines/{DAG_ID}/{RUN_EMPTY}")
+ response =
test_client.get(f"/dags/{DAG_ID}/dagRuns/{RUN_EMPTY}/deadlines")
assert response.status_code == 200
- assert response.json() == []
+ assert response.json() == {"deadlines": [], "total_entries": 0}
def test_single_deadline_without_alert(self, test_client):
- response = test_client.get(f"/deadlines/{DAG_ID}/{RUN_SINGLE}")
+ response =
test_client.get(f"/dags/{DAG_ID}/dagRuns/{RUN_SINGLE}/deadlines")
assert response.status_code == 200
data = response.json()
- assert len(data) == 1
- assert data[0]["deadline_time"] == "2025-01-01T12:00:00Z"
- assert data[0]["missed"] is False
- assert data[0]["alert_name"] is None
- assert data[0]["alert_description"] is None
- assert "id" in data[0]
- assert "created_at" in data[0]
+ assert data["total_entries"] == 1
+ deadline1 = data["deadlines"][0]
+ assert deadline1["deadline_time"] == "2025-01-01T12:00:00Z"
+ assert deadline1["missed"] is False
+ assert deadline1["alert_name"] is None
+ assert deadline1["alert_description"] is None
+ assert "id" in deadline1
+ assert "created_at" in deadline1
def test_missed_deadline_is_reflected(self, test_client):
- response = test_client.get(f"/deadlines/{DAG_ID}/{RUN_MISSED}")
+ response =
test_client.get(f"/dags/{DAG_ID}/dagRuns/{RUN_MISSED}/deadlines")
assert response.status_code == 200
data = response.json()
- assert len(data) == 1
- assert data[0]["missed"] is True
+ assert data["total_entries"] == 1
+ assert data["deadlines"][0]["missed"] is True
def test_deadline_with_alert_name_and_description(self, test_client):
- response = test_client.get(f"/deadlines/{DAG_ID}/{RUN_ALERT}")
+ with assert_queries_count(4):
+ response =
test_client.get(f"/dags/{DAG_ID}/dagRuns/{RUN_ALERT}/deadlines")
assert response.status_code == 200
data = response.json()
- assert len(data) == 1
- assert data[0]["alert_name"] == ALERT_NAME
- assert data[0]["alert_description"] == ALERT_DESCRIPTION
+ assert data["total_entries"] == 1
+ assert data["deadlines"][0]["alert_name"] == ALERT_NAME
+ assert data["deadlines"][0]["alert_description"] == ALERT_DESCRIPTION
def test_deadlines_ordered_by_deadline_time_ascending(self, test_client):
- response = test_client.get(f"/deadlines/{DAG_ID}/{RUN_MULTI}")
+ with assert_queries_count(4):
+ response =
test_client.get(f"/dags/{DAG_ID}/dagRuns/{RUN_MULTI}/deadlines")
assert response.status_code == 200
data = response.json()
- assert len(data) == 3
- returned_times = [d["deadline_time"] for d in data]
+ assert data["total_entries"] == 3
+ returned_times = [d["deadline_time"] for d in data["deadlines"]]
assert returned_times == sorted(returned_times)
+ @pytest.mark.parametrize(
+ "order_by",
+ ["deadline_time", "id", "created_at", "alert_name"],
+ ids=["deadline_time", "id", "created_at", "alert_name"],
+ )
+ def test_should_response_200_order_by(self, test_client, order_by):
+ url = f"/dags/{DAG_ID}/dagRuns/{RUN_MULTI}/deadlines"
+ with assert_queries_count(8):
+ response_asc = test_client.get(url, params={"order_by": order_by})
+ response_desc = test_client.get(url, params={"order_by":
f"-{order_by}"})
+ assert response_asc.status_code == 200
+ assert response_desc.status_code == 200
+ ids_asc = [d["id"] for d in response_asc.json()["deadlines"]]
+ ids_desc = [d["id"] for d in response_desc.json()["deadlines"]]
+ assert ids_desc == list(reversed(ids_asc))
+
def test_only_returns_deadlines_for_requested_run(self, test_client):
"""Deadlines belonging to a different run must not appear in the
response."""
# RUN_EMPTY has no deadlines; RUN_OTHER has one — querying RUN_EMPTY
must return [].
- response = test_client.get(f"/deadlines/{DAG_ID}/{RUN_EMPTY}")
+ response =
test_client.get(f"/dags/{DAG_ID}/dagRuns/{RUN_EMPTY}/deadlines")
assert response.status_code == 200
- assert response.json() == []
+ assert response.json() == {"deadlines": [], "total_entries": 0}
# And querying RUN_OTHER returns only its own deadline.
- response = test_client.get(f"/deadlines/{DAG_ID}/{RUN_OTHER}")
+ response =
test_client.get(f"/dags/{DAG_ID}/dagRuns/{RUN_OTHER}/deadlines")
assert response.status_code == 200
- assert len(response.json()) == 1
+ assert response.json()["total_entries"] == 1
# ------------------------------------------------------------------
# 404
@@ -276,7 +296,7 @@ class TestGetDagRunDeadlines:
],
)
def test_should_response_404(self, test_client, dag_id, run_id):
- response = test_client.get(f"/deadlines/{dag_id}/{run_id}")
+ response =
test_client.get(f"/dags/{dag_id}/dagRuns/{run_id}/deadlines")
assert response.status_code == 404
# ------------------------------------------------------------------
@@ -284,9 +304,9 @@ class TestGetDagRunDeadlines:
# ------------------------------------------------------------------
def test_should_response_401(self, unauthenticated_test_client):
- response =
unauthenticated_test_client.get(f"/deadlines/{DAG_ID}/{RUN_EMPTY}")
+ response =
unauthenticated_test_client.get(f"/dags/{DAG_ID}/dagRuns/{RUN_EMPTY}/deadlines")
assert response.status_code == 401
def test_should_response_403(self, unauthorized_test_client):
- response =
unauthorized_test_client.get(f"/deadlines/{DAG_ID}/{RUN_EMPTY}")
+ response =
unauthorized_test_client.get(f"/dags/{DAG_ID}/dagRuns/{RUN_EMPTY}/deadlines")
assert response.status_code == 403