This is an automated email from the ASF dual-hosted git repository.
ferruzzi 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 3130fff7ba1 Add DAG run missed-deadline metadata to Grid API (#62189)
3130fff7ba1 is described below
commit 3130fff7ba1386a49d9d8bdeeef205e3e58e6a86
Author: Richard Wu <[email protected]>
AuthorDate: Fri Feb 20 16:32:49 2026 -0700
Add DAG run missed-deadline metadata to Grid API (#62189)
* feat: add deadlines API endpoint and has_missed_deadline field for DAG
runs
- Added DeadlinesService and new /deadlines route for fetching DAG run
deadlines
- Added deadline datamodels (DeadlineResponse, DeadlineCollectionResponse)
- Added has_missed_deadline field to GridDAGRunwithTIDetails
- Registered deadlines router in UI routes
- Updated OpenAPI spec and regenerated openapi-gen client files
---
.../api_fastapi/core_api/datamodels/ui/common.py | 1 +
.../api_fastapi/core_api/datamodels/ui/deadline.py | 34 +++
.../api_fastapi/core_api/openapi/_private_ui.yaml | 84 ++++++
.../api_fastapi/core_api/routes/ui/__init__.py | 2 +
.../api_fastapi/core_api/routes/ui/deadlines.py | 86 ++++++
.../airflow/api_fastapi/core_api/routes/ui/grid.py | 10 +-
.../src/airflow/ui/openapi-gen/queries/common.ts | 9 +-
.../ui/openapi-gen/queries/ensureQueryData.ts | 15 +-
.../src/airflow/ui/openapi-gen/queries/prefetch.ts | 15 +-
.../src/airflow/ui/openapi-gen/queries/queries.ts | 15 +-
.../src/airflow/ui/openapi-gen/queries/suspense.ts | 15 +-
.../airflow/ui/openapi-gen/requests/schemas.gen.ts | 56 +++-
.../ui/openapi-gen/requests/services.gen.ts | 29 +-
.../airflow/ui/openapi-gen/requests/types.gen.ts | 39 +++
.../core_api/routes/ui/test_deadlines.py | 292 +++++++++++++++++++++
.../api_fastapi/core_api/routes/ui/test_grid.py | 4 +
16 files changed, 698 insertions(+), 8 deletions(-)
diff --git
a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/common.py
b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/common.py
index a18042b4960..2c5832f3246 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/common.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/common.py
@@ -79,6 +79,7 @@ class GridRunsResponse(BaseModel):
run_after: datetime
state: DagRunState | None
run_type: DagRunType
+ has_missed_deadline: bool
@computed_field
def duration(self) -> float:
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
new file mode 100644
index 00000000000..61391885bc4
--- /dev/null
+++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/deadline.py
@@ -0,0 +1,34 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from __future__ import annotations
+
+from datetime import datetime
+from uuid import UUID
+
+from airflow.api_fastapi.core_api.base import BaseModel
+
+
+class DeadlineResponse(BaseModel):
+ """Deadline data for the DAG run deadlines tab."""
+
+ id: UUID
+ deadline_time: datetime
+ missed: bool
+ created_at: datetime
+ alert_name: str | None = None
+ alert_description: str | None = None
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 d047a7ce3be..c3f307afbe7 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
@@ -478,6 +478,51 @@ paths:
security:
- OAuth2PasswordBearer: []
- HTTPBearer: []
+ /ui/deadlines/{dag_id}/{run_id}:
+ get:
+ tags:
+ - Deadlines
+ summary: Get Dag Run Deadlines
+ description: Get all deadlines for a specific DAG run.
+ operationId: get_dag_run_deadlines
+ security:
+ - OAuth2PasswordBearer: []
+ - HTTPBearer: []
+ parameters:
+ - name: dag_id
+ in: path
+ required: true
+ schema:
+ type: string
+ title: Dag Id
+ - name: run_id
+ in: path
+ required: true
+ schema:
+ type: string
+ title: Run Id
+ responses:
+ '200':
+ description: Successful Response
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/DeadlineResponse'
+ title: Response Get Dag Run Deadlines
+ '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/structure/structure_data:
get:
tags:
@@ -1925,6 +1970,41 @@ components:
- queued_dag_count
title: DashboardDagStatsResponse
description: Dashboard DAG Stats serializer for responses.
+ DeadlineResponse:
+ properties:
+ id:
+ type: string
+ format: uuid
+ title: Id
+ deadline_time:
+ type: string
+ format: date-time
+ title: Deadline Time
+ missed:
+ type: boolean
+ title: Missed
+ created_at:
+ type: string
+ format: date-time
+ title: Created At
+ alert_name:
+ anyOf:
+ - type: string
+ - type: 'null'
+ title: Alert Name
+ alert_description:
+ anyOf:
+ - type: string
+ - type: 'null'
+ title: Alert Description
+ type: object
+ required:
+ - id
+ - deadline_time
+ - missed
+ - created_at
+ title: DeadlineResponse
+ description: Deadline data for the DAG run deadlines tab.
EdgeResponse:
properties:
source_id:
@@ -2103,6 +2183,9 @@ components:
- type: 'null'
run_type:
$ref: '#/components/schemas/DagRunType'
+ has_missed_deadline:
+ type: boolean
+ title: Has Missed Deadline
duration:
type: number
title: Duration
@@ -2117,6 +2200,7 @@ components:
- run_after
- state
- run_type
+ - has_missed_deadline
- duration
title: GridRunsResponse
description: Base Node serializer for responses.
diff --git
a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/__init__.py
b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/__init__.py
index 3a3cadc46f9..ce8744785cd 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/__init__.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/__init__.py
@@ -25,6 +25,7 @@ from airflow.api_fastapi.core_api.routes.ui.config import
config_router
from airflow.api_fastapi.core_api.routes.ui.connections import
connections_router
from airflow.api_fastapi.core_api.routes.ui.dags import dags_router
from airflow.api_fastapi.core_api.routes.ui.dashboard import dashboard_router
+from airflow.api_fastapi.core_api.routes.ui.deadlines import deadlines_router
from airflow.api_fastapi.core_api.routes.ui.dependencies import
dependencies_router
from airflow.api_fastapi.core_api.routes.ui.gantt import gantt_router
from airflow.api_fastapi.core_api.routes.ui.grid import grid_router
@@ -40,6 +41,7 @@ ui_router.include_router(connections_router)
ui_router.include_router(dags_router)
ui_router.include_router(dependencies_router)
ui_router.include_router(dashboard_router)
+ui_router.include_router(deadlines_router)
ui_router.include_router(structure_router)
ui_router.include_router(backfills_router)
ui_router.include_router(grid_router)
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
new file mode 100644
index 00000000000..03aeed13e4f
--- /dev/null
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/deadlines.py
@@ -0,0 +1,86 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from __future__ import annotations
+
+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.router import AirflowRouter
+from airflow.api_fastapi.core_api.datamodels.ui.deadline import
DeadlineResponse
+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
+
+deadlines_router = AirflowRouter(prefix="/deadlines", tags=["Deadlines"])
+
+
+@deadlines_router.get(
+ "/{dag_id}/{run_id}",
+ responses=create_openapi_http_exception_doc(
+ [
+ status.HTTP_404_NOT_FOUND,
+ ]
+ ),
+ dependencies=[
+ Depends(
+ requires_access_dag(
+ method="GET",
+ access_entity=DagAccessEntity.RUN,
+ )
+ ),
+ ],
+)
+def get_dag_run_deadlines(
+ dag_id: str,
+ run_id: str,
+ session: SessionDep,
+) -> list[DeadlineResponse]:
+ """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))
+ 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}",
+ )
+
+ 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()
+ )
+
+ 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
+ ]
diff --git a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/grid.py
b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/grid.py
index 9c7436ba462..7762656415e 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/grid.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/grid.py
@@ -22,7 +22,7 @@ from typing import TYPE_CHECKING, Annotated, Any
import structlog
from fastapi import Depends, HTTPException, status
-from sqlalchemy import select
+from sqlalchemy import exists, select
from sqlalchemy.orm import joinedload
from airflow.api_fastapi.auth.managers.models.resource_details import
DagAccessEntity
@@ -60,6 +60,7 @@ from airflow.api_fastapi.core_api.services.ui.task_group
import (
)
from airflow.models.dag_version import DagVersion
from airflow.models.dagrun import DagRun
+from airflow.models.deadline import Deadline
from airflow.models.serialized_dag import SerializedDagModel
from airflow.models.taskinstance import TaskInstance
@@ -275,6 +276,12 @@ def get_grid_runs(
) -> list[GridRunsResponse]:
"""Get info about a run for the grid."""
# Retrieve, sort the previous DAG Runs
+ has_missed_deadline = (
+ exists()
+ .where(Deadline.dagrun_id == DagRun.id, Deadline.missed.is_(True))
+ .correlate(DagRun)
+ .label("has_missed_deadline")
+ )
base_query = select(
DagRun.dag_id,
DagRun.run_id,
@@ -284,6 +291,7 @@ def get_grid_runs(
DagRun.run_after,
DagRun.state,
DagRun.run_type,
+ has_missed_deadline,
).where(DagRun.dag_id == dag_id)
# This comparison is to fall back to DAG timetable when no order_by is
provided
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 823c49cdb1a..f660990ec61 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
@@ -1,7 +1,7 @@
// generated with @7nohe/[email protected]
import { UseQueryResult } from "@tanstack/react-query";
-import { AssetService, AuthLinksService, BackfillService, CalendarService,
ConfigService, ConnectionService, DagParsingService, DagRunService, DagService,
DagSourceService, DagStatsService, DagVersionService, DagWarningService,
DashboardService, DependenciesService, EventLogService, ExperimentalService,
ExtraLinksService, GanttService, GridService, ImportErrorService, JobService,
LoginService, MonitorService, PluginService, PoolService, ProviderService,
StructureService, TaskInstanceServ [...]
+import { AssetService, AuthLinksService, BackfillService, CalendarService,
ConfigService, ConnectionService, DagParsingService, DagRunService, DagService,
DagSourceService, DagStatsService, DagVersionService, DagWarningService,
DashboardService, DeadlinesService, DependenciesService, EventLogService,
ExperimentalService, ExtraLinksService, GanttService, GridService,
ImportErrorService, JobService, LoginService, MonitorService, PluginService,
PoolService, ProviderService, StructureService [...]
import { DagRunState, DagWarningType } from "../requests/types.gen";
export type AssetServiceGetAssetsDefaultResponse = Awaited<ReturnType<typeof
AssetService.getAssets>>;
export type AssetServiceGetAssetsQueryResult<TData =
AssetServiceGetAssetsDefaultResponse, TError = unknown> = UseQueryResult<TData,
TError>;
@@ -807,6 +807,13 @@ 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, runId }: {
+ dagId: string;
+ runId: string;
+}, queryKey?: Array<unknown>) => [useDeadlinesServiceGetDagRunDeadlinesKey,
...(queryKey ?? [{ dagId, runId }])];
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 459b6fb4d6e..20d67fcd1aa 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
@@ -1,7 +1,7 @@
// generated with @7nohe/[email protected]
import { type QueryClient } from "@tanstack/react-query";
-import { AssetService, AuthLinksService, BackfillService, CalendarService,
ConfigService, ConnectionService, DagRunService, DagService, DagSourceService,
DagStatsService, DagVersionService, DagWarningService, DashboardService,
DependenciesService, EventLogService, ExperimentalService, ExtraLinksService,
GanttService, GridService, ImportErrorService, JobService, LoginService,
MonitorService, PluginService, PoolService, ProviderService, StructureService,
TaskInstanceService, TaskService, T [...]
+import { AssetService, AuthLinksService, BackfillService, CalendarService,
ConfigService, ConnectionService, DagRunService, DagService, DagSourceService,
DagStatsService, DagVersionService, DagWarningService, DashboardService,
DeadlinesService, DependenciesService, EventLogService, ExperimentalService,
ExtraLinksService, GanttService, GridService, ImportErrorService, JobService,
LoginService, MonitorService, PluginService, PoolService, ProviderService,
StructureService, TaskInstanceServi [...]
import { DagRunState, DagWarningType } from "../requests/types.gen";
import * as Common from "./common";
/**
@@ -1532,6 +1532,19 @@ 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.
+* @param data The data for the request.
+* @param data.dagId
+* @param data.runId
+* @returns DeadlineResponse Successful Response
+* @throws ApiError
+*/
+export const ensureUseDeadlinesServiceGetDagRunDeadlinesData = (queryClient:
QueryClient, { dagId, runId }: {
+ dagId: string;
+ runId: string;
+}) => queryClient.ensureQueryData({ queryKey:
Common.UseDeadlinesServiceGetDagRunDeadlinesKeyFn({ dagId, runId }), queryFn:
() => DeadlinesService.getDagRunDeadlines({ dagId, runId }) });
+/**
* Structure Data
* Get Structure Data.
* @param data The data for the request.
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 72772c79656..bed863f93cd 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
@@ -1,7 +1,7 @@
// generated with @7nohe/[email protected]
import { type QueryClient } from "@tanstack/react-query";
-import { AssetService, AuthLinksService, BackfillService, CalendarService,
ConfigService, ConnectionService, DagRunService, DagService, DagSourceService,
DagStatsService, DagVersionService, DagWarningService, DashboardService,
DependenciesService, EventLogService, ExperimentalService, ExtraLinksService,
GanttService, GridService, ImportErrorService, JobService, LoginService,
MonitorService, PluginService, PoolService, ProviderService, StructureService,
TaskInstanceService, TaskService, T [...]
+import { AssetService, AuthLinksService, BackfillService, CalendarService,
ConfigService, ConnectionService, DagRunService, DagService, DagSourceService,
DagStatsService, DagVersionService, DagWarningService, DashboardService,
DeadlinesService, DependenciesService, EventLogService, ExperimentalService,
ExtraLinksService, GanttService, GridService, ImportErrorService, JobService,
LoginService, MonitorService, PluginService, PoolService, ProviderService,
StructureService, TaskInstanceServi [...]
import { DagRunState, DagWarningType } from "../requests/types.gen";
import * as Common from "./common";
/**
@@ -1532,6 +1532,19 @@ 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.
+* @param data The data for the request.
+* @param data.dagId
+* @param data.runId
+* @returns DeadlineResponse Successful Response
+* @throws ApiError
+*/
+export const prefetchUseDeadlinesServiceGetDagRunDeadlines = (queryClient:
QueryClient, { dagId, runId }: {
+ dagId: string;
+ runId: string;
+}) => queryClient.prefetchQuery({ queryKey:
Common.UseDeadlinesServiceGetDagRunDeadlinesKeyFn({ dagId, runId }), queryFn:
() => DeadlinesService.getDagRunDeadlines({ dagId, runId }) });
+/**
* Structure Data
* Get Structure Data.
* @param data The data for the request.
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 717486d0ece..434f0af6dcd 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
@@ -1,7 +1,7 @@
// generated with @7nohe/[email protected]
import { UseMutationOptions, UseQueryOptions, useMutation, useQuery } from
"@tanstack/react-query";
-import { AssetService, AuthLinksService, BackfillService, CalendarService,
ConfigService, ConnectionService, DagParsingService, DagRunService, DagService,
DagSourceService, DagStatsService, DagVersionService, DagWarningService,
DashboardService, DependenciesService, EventLogService, ExperimentalService,
ExtraLinksService, GanttService, GridService, ImportErrorService, JobService,
LoginService, MonitorService, PluginService, PoolService, ProviderService,
StructureService, TaskInstanceServ [...]
+import { AssetService, AuthLinksService, BackfillService, CalendarService,
ConfigService, ConnectionService, DagParsingService, DagRunService, DagService,
DagSourceService, DagStatsService, DagVersionService, DagWarningService,
DashboardService, DeadlinesService, DependenciesService, EventLogService,
ExperimentalService, ExtraLinksService, GanttService, GridService,
ImportErrorService, JobService, LoginService, MonitorService, PluginService,
PoolService, ProviderService, StructureService [...]
import { BackfillPostBody, BulkBody_BulkTaskInstanceBody_,
BulkBody_ConnectionBody_, BulkBody_PoolBody_, BulkBody_VariableBody_,
ClearTaskInstancesBody, ConnectionBody, CreateAssetEventsBody, DAGPatchBody,
DAGRunClearBody, DAGRunPatchBody, DAGRunsBatchBody, DagRunState,
DagWarningType, PatchTaskInstanceBody, PoolBody, PoolPatchBody,
TaskInstancesBatchBody, TriggerDAGRunPostBody, UpdateHITLDetailPayload,
VariableBody, XComCreateBody, XComUpdateBody } from "../requests/types.gen";
import * as Common from "./common";
/**
@@ -1532,6 +1532,19 @@ 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.
+* @param data The data for the request.
+* @param data.dagId
+* @param data.runId
+* @returns DeadlineResponse Successful Response
+* @throws ApiError
+*/
+export const useDeadlinesServiceGetDagRunDeadlines = <TData =
Common.DeadlinesServiceGetDagRunDeadlinesDefaultResponse, TError = unknown,
TQueryKey extends Array<unknown> = unknown[]>({ dagId, runId }: {
+ 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 });
+/**
* Structure Data
* Get Structure Data.
* @param data The data for the request.
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 57aa208df95..05a2d3941ab 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
@@ -1,7 +1,7 @@
// generated with @7nohe/[email protected]
import { UseQueryOptions, useSuspenseQuery } from "@tanstack/react-query";
-import { AssetService, AuthLinksService, BackfillService, CalendarService,
ConfigService, ConnectionService, DagRunService, DagService, DagSourceService,
DagStatsService, DagVersionService, DagWarningService, DashboardService,
DependenciesService, EventLogService, ExperimentalService, ExtraLinksService,
GanttService, GridService, ImportErrorService, JobService, LoginService,
MonitorService, PluginService, PoolService, ProviderService, StructureService,
TaskInstanceService, TaskService, T [...]
+import { AssetService, AuthLinksService, BackfillService, CalendarService,
ConfigService, ConnectionService, DagRunService, DagService, DagSourceService,
DagStatsService, DagVersionService, DagWarningService, DashboardService,
DeadlinesService, DependenciesService, EventLogService, ExperimentalService,
ExtraLinksService, GanttService, GridService, ImportErrorService, JobService,
LoginService, MonitorService, PluginService, PoolService, ProviderService,
StructureService, TaskInstanceServi [...]
import { DagRunState, DagWarningType } from "../requests/types.gen";
import * as Common from "./common";
/**
@@ -1532,6 +1532,19 @@ 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.
+* @param data The data for the request.
+* @param data.dagId
+* @param data.runId
+* @returns DeadlineResponse Successful Response
+* @throws ApiError
+*/
+export const useDeadlinesServiceGetDagRunDeadlinesSuspense = <TData =
Common.DeadlinesServiceGetDagRunDeadlinesDefaultResponse, TError = unknown,
TQueryKey extends Array<unknown> = unknown[]>({ dagId, runId }: {
+ 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 });
+/**
* Structure Data
* Get Structure Data.
* @param data The data for the request.
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 8e6a9ff3e68..b9e305f07c9 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
@@ -7820,6 +7820,56 @@ export const $DashboardDagStatsResponse = {
description: 'Dashboard DAG Stats serializer for responses.'
} as const;
+export const $DeadlineResponse = {
+ properties: {
+ id: {
+ type: 'string',
+ format: 'uuid',
+ title: 'Id'
+ },
+ deadline_time: {
+ type: 'string',
+ format: 'date-time',
+ title: 'Deadline Time'
+ },
+ missed: {
+ type: 'boolean',
+ title: 'Missed'
+ },
+ created_at: {
+ type: 'string',
+ format: 'date-time',
+ title: 'Created At'
+ },
+ alert_name: {
+ anyOf: [
+ {
+ type: 'string'
+ },
+ {
+ type: 'null'
+ }
+ ],
+ title: 'Alert Name'
+ },
+ alert_description: {
+ anyOf: [
+ {
+ type: 'string'
+ },
+ {
+ type: 'null'
+ }
+ ],
+ title: 'Alert Description'
+ }
+ },
+ type: 'object',
+ required: ['id', 'deadline_time', 'missed', 'created_at'],
+ title: 'DeadlineResponse',
+ description: 'Deadline data for the DAG run deadlines tab.'
+} as const;
+
export const $EdgeResponse = {
properties: {
source_id: {
@@ -8093,6 +8143,10 @@ export const $GridRunsResponse = {
run_type: {
'$ref': '#/components/schemas/DagRunType'
},
+ has_missed_deadline: {
+ type: 'boolean',
+ title: 'Has Missed Deadline'
+ },
duration: {
type: 'number',
title: 'Duration',
@@ -8100,7 +8154,7 @@ export const $GridRunsResponse = {
}
},
type: 'object',
- required: ['dag_id', 'run_id', 'queued_at', 'start_date', 'end_date',
'run_after', 'state', 'run_type', 'duration'],
+ required: ['dag_id', 'run_id', 'queued_at', 'start_date', 'end_date',
'run_after', 'state', 'run_type', 'has_missed_deadline', 'duration'],
title: 'GridRunsResponse',
description: 'Base Node 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 ccc3d86fc0d..63107cb97b6 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 {
/**
@@ -3917,6 +3917,33 @@ export class DashboardService {
}
+export class DeadlinesService {
+ /**
+ * Get Dag Run Deadlines
+ * 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
+ * @throws ApiError
+ */
+ public static getDagRunDeadlines(data: GetDagRunDeadlinesData):
CancelablePromise<GetDagRunDeadlinesResponse> {
+ return __request(OpenAPI, {
+ method: 'GET',
+ url: '/ui/deadlines/{dag_id}/{run_id}',
+ path: {
+ dag_id: data.dagId,
+ run_id: data.runId
+ },
+ errors: {
+ 404: 'Not Found',
+ 422: 'Validation Error'
+ }
+ });
+ }
+
+}
+
export class StructureService {
/**
* Structure Data
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 ac57f493ff6..aa596d50253 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
@@ -1925,6 +1925,18 @@ export type DashboardDagStatsResponse = {
queued_dag_count: number;
};
+/**
+ * Deadline data for the DAG run deadlines tab.
+ */
+export type DeadlineResponse = {
+ id: string;
+ deadline_time: string;
+ missed: boolean;
+ created_at: string;
+ alert_name?: string | null;
+ alert_description?: string | null;
+};
+
/**
* Edge serializer for responses.
*/
@@ -1987,6 +1999,7 @@ export type GridRunsResponse = {
run_after: string;
state: DagRunState | null;
run_type: DagRunType;
+ has_missed_deadline: boolean;
readonly duration: number;
};
@@ -3463,6 +3476,13 @@ export type HistoricalMetricsResponse =
HistoricalMetricDataResponse;
export type DagStatsResponse2 = DashboardDagStatsResponse;
+export type GetDagRunDeadlinesData = {
+ dagId: string;
+ runId: string;
+};
+
+export type GetDagRunDeadlinesResponse = Array<DeadlineResponse>;
+
export type StructureDataData = {
dagId: string;
depth?: number | null;
@@ -6677,6 +6697,25 @@ export type $OpenApiTs = {
};
};
};
+ '/ui/deadlines/{dag_id}/{run_id}': {
+ get: {
+ req: GetDagRunDeadlinesData;
+ res: {
+ /**
+ * Successful Response
+ */
+ 200: Array<DeadlineResponse>;
+ /**
+ * Not Found
+ */
+ 404: HTTPExceptionResponse;
+ /**
+ * Validation Error
+ */
+ 422: HTTPValidationError;
+ };
+ };
+ };
'/ui/structure/structure_data': {
get: {
req: StructureDataData;
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
new file mode 100644
index 00000000000..4d3184a6094
--- /dev/null
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_deadlines.py
@@ -0,0 +1,292 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from __future__ import annotations
+
+import pytest
+from sqlalchemy import select
+
+from airflow._shared.timezones import timezone
+from airflow.models.deadline import Deadline
+from airflow.models.deadline_alert import DeadlineAlert
+from airflow.models.serialized_dag import SerializedDagModel
+from airflow.providers.standard.operators.empty import EmptyOperator
+from airflow.sdk.definitions.callback import AsyncCallback
+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.db import (
+ clear_db_dags,
+ clear_db_deadline,
+ clear_db_deadline_alert,
+ clear_db_runs,
+ clear_db_serialized_dags,
+)
+
+pytestmark = pytest.mark.db_test
+
+DAG_ID = "test_deadlines_dag"
+
+# Each run represents a different deadline scenario tested below.
+RUN_EMPTY = "run_empty" # no deadlines
+RUN_SINGLE = "run_single" # 1 deadline, not missed, no alert
+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
+
+ALERT_NAME = "SLA Breach Alert"
+ALERT_DESCRIPTION = "Fires when SLA is breached"
+
+_CALLBACK_PATH =
"tests.unit.api_fastapi.core_api.routes.ui.test_deadlines._noop_callback"
+
+
+async def _noop_callback(**kwargs):
+ """No-op callback used to satisfy Deadline creation in tests."""
+
+
+def _cb() -> AsyncCallback:
+ return AsyncCallback(_CALLBACK_PATH)
+
+
[email protected](autouse=True)
+def setup(dag_maker, session):
+ clear_db_deadline()
+ clear_db_deadline_alert()
+ clear_db_runs()
+ clear_db_dags()
+ clear_db_serialized_dags()
+
+ with dag_maker(DAG_ID, serialized=True, session=session):
+ EmptyOperator(task_id="task")
+
+ # ---- create runs -------------------------------------------------------
+ dag_maker.create_dagrun(
+ run_id=RUN_EMPTY,
+ state=DagRunState.SUCCESS,
+ run_type=DagRunType.SCHEDULED,
+ logical_date=timezone.datetime(2024, 11, 1),
+ triggered_by=DagRunTriggeredByType.TEST,
+ )
+
+ run_single = dag_maker.create_dagrun(
+ run_id=RUN_SINGLE,
+ state=DagRunState.SUCCESS,
+ run_type=DagRunType.SCHEDULED,
+ logical_date=timezone.datetime(2024, 11, 2),
+ triggered_by=DagRunTriggeredByType.TEST,
+ )
+
+ run_missed = dag_maker.create_dagrun(
+ run_id=RUN_MISSED,
+ state=DagRunState.SUCCESS,
+ run_type=DagRunType.SCHEDULED,
+ logical_date=timezone.datetime(2024, 11, 3),
+ triggered_by=DagRunTriggeredByType.TEST,
+ )
+
+ run_alert = dag_maker.create_dagrun(
+ run_id=RUN_ALERT,
+ state=DagRunState.SUCCESS,
+ run_type=DagRunType.SCHEDULED,
+ logical_date=timezone.datetime(2024, 11, 4),
+ triggered_by=DagRunTriggeredByType.TEST,
+ )
+
+ run_multi = dag_maker.create_dagrun(
+ run_id=RUN_MULTI,
+ state=DagRunState.SUCCESS,
+ run_type=DagRunType.SCHEDULED,
+ logical_date=timezone.datetime(2024, 11, 5),
+ triggered_by=DagRunTriggeredByType.TEST,
+ )
+
+ run_other = dag_maker.create_dagrun(
+ run_id=RUN_OTHER,
+ state=DagRunState.SUCCESS,
+ run_type=DagRunType.SCHEDULED,
+ logical_date=timezone.datetime(2024, 11, 6),
+ triggered_by=DagRunTriggeredByType.TEST,
+ )
+
+ # ---- deadlines ---------------------------------------------------------
+
+ # run_empty: intentionally no deadlines
+
+ # run_single: one active, non-missed deadline with no alert
+ session.add(
+ Deadline(
+ deadline_time=timezone.datetime(2025, 1, 1, 12, 0, 0),
+ callback=_cb(),
+ dagrun_id=run_single.id,
+ deadline_alert_id=None,
+ )
+ )
+
+ # run_missed: one missed deadline
+ missed_dl = Deadline(
+ deadline_time=timezone.datetime(2024, 12, 1),
+ callback=_cb(),
+ dagrun_id=run_missed.id,
+ deadline_alert_id=None,
+ )
+ missed_dl.missed = True
+ session.add(missed_dl)
+
+ # run_alert: one deadline linked to a DeadlineAlert
+ serialized_dag =
session.scalar(select(SerializedDagModel).where(SerializedDagModel.dag_id ==
DAG_ID))
+ alert = DeadlineAlert(
+ serialized_dag_id=serialized_dag.id,
+ name=ALERT_NAME,
+ description=ALERT_DESCRIPTION,
+ reference=DeadlineReference.DAGRUN_QUEUED_AT.serialize_reference(),
+ interval=3600.0,
+ callback_def={"path": _CALLBACK_PATH},
+ )
+ session.add(alert)
+ session.flush()
+ session.add(
+ Deadline(
+ deadline_time=timezone.datetime(2025, 1, 1, 12, 0, 0),
+ callback=_cb(),
+ dagrun_id=run_alert.id,
+ deadline_alert_id=alert.id,
+ )
+ )
+
+ # run_multi: three deadlines intentionally added in non-chronological order
+ for dl_time in [
+ timezone.datetime(2025, 3, 1),
+ timezone.datetime(2025, 1, 1),
+ timezone.datetime(2025, 2, 1),
+ ]:
+ session.add(
+ Deadline(
+ deadline_time=dl_time,
+ callback=_cb(),
+ dagrun_id=run_multi.id,
+ deadline_alert_id=None,
+ )
+ )
+
+ # run_other: one deadline (for isolation verification)
+ session.add(
+ Deadline(
+ deadline_time=timezone.datetime(2025, 6, 1),
+ callback=_cb(),
+ dagrun_id=run_other.id,
+ deadline_alert_id=None,
+ )
+ )
+
+ dag_maker.sync_dagbag_to_db()
+ session.commit()
+ yield
+ clear_db_deadline()
+ clear_db_deadline_alert()
+ clear_db_runs()
+ clear_db_dags()
+ clear_db_serialized_dags()
+
+
+class TestGetDagRunDeadlines:
+ """Tests for GET /deadlines/{dag_id}/{run_id}."""
+
+ # ------------------------------------------------------------------
+ # 200 – happy paths
+ # ------------------------------------------------------------------
+
+ def test_no_deadlines_returns_empty_list(self, test_client):
+ response = test_client.get(f"/deadlines/{DAG_ID}/{RUN_EMPTY}")
+ assert response.status_code == 200
+ assert response.json() == []
+
+ def test_single_deadline_without_alert(self, test_client):
+ response = test_client.get(f"/deadlines/{DAG_ID}/{RUN_SINGLE}")
+ 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]
+
+ def test_missed_deadline_is_reflected(self, test_client):
+ response = test_client.get(f"/deadlines/{DAG_ID}/{RUN_MISSED}")
+ assert response.status_code == 200
+ data = response.json()
+ assert len(data) == 1
+ assert data[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}")
+ 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
+
+ def test_deadlines_ordered_by_deadline_time_ascending(self, test_client):
+ response = test_client.get(f"/deadlines/{DAG_ID}/{RUN_MULTI}")
+ assert response.status_code == 200
+ data = response.json()
+ assert len(data) == 3
+ returned_times = [d["deadline_time"] for d in data]
+ assert returned_times == sorted(returned_times)
+
+ 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}")
+ assert response.status_code == 200
+ assert response.json() == []
+
+ # And querying RUN_OTHER returns only its own deadline.
+ response = test_client.get(f"/deadlines/{DAG_ID}/{RUN_OTHER}")
+ assert response.status_code == 200
+ assert len(response.json()) == 1
+
+ # ------------------------------------------------------------------
+ # 404
+ # ------------------------------------------------------------------
+
+ @pytest.mark.parametrize(
+ ("dag_id", "run_id"),
+ [
+ pytest.param("nonexistent_dag", RUN_EMPTY, id="wrong_dag_id"),
+ pytest.param(DAG_ID, "nonexistent_run", id="wrong_run_id"),
+ pytest.param("nonexistent_dag", "nonexistent_run",
id="both_wrong"),
+ ],
+ )
+ def test_should_response_404(self, test_client, dag_id, run_id):
+ response = test_client.get(f"/deadlines/{dag_id}/{run_id}")
+ assert response.status_code == 404
+
+ # ------------------------------------------------------------------
+ # 401 / 403
+ # ------------------------------------------------------------------
+
+ def test_should_response_401(self, unauthenticated_test_client):
+ response =
unauthenticated_test_client.get(f"/deadlines/{DAG_ID}/{RUN_EMPTY}")
+ 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}")
+ assert response.status_code == 403
diff --git
a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_grid.py
b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_grid.py
index fd67e7e3267..ed4d6fb2e0e 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_grid.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_grid.py
@@ -63,6 +63,7 @@ GRID_RUN_1 = {
"dag_id": "test_dag",
"duration": 283996800.0,
"end_date": "2024-12-31T00:00:00Z",
+ "has_missed_deadline": False,
"run_after": "2024-11-30T00:00:00Z",
"run_id": "run_1",
"run_type": "scheduled",
@@ -74,6 +75,7 @@ GRID_RUN_2 = {
"dag_id": "test_dag",
"duration": 283996800.0,
"end_date": "2024-12-31T00:00:00Z",
+ "has_missed_deadline": False,
"run_after": "2024-11-30T00:00:00Z",
"run_id": "run_2",
"run_type": "manual",
@@ -611,6 +613,7 @@ class TestGetGridDataEndpoint:
"dag_id": "test_dag",
"duration": 283996800.0,
"end_date": "2024-12-31T00:00:00Z",
+ "has_missed_deadline": False,
"run_after": "2024-11-30T00:00:00Z",
"run_id": "run_1",
"run_type": "scheduled",
@@ -621,6 +624,7 @@ class TestGetGridDataEndpoint:
"dag_id": "test_dag",
"duration": 283996800.0,
"end_date": "2024-12-31T00:00:00Z",
+ "has_missed_deadline": False,
"run_after": "2024-11-30T00:00:00Z",
"run_id": "run_2",
"run_type": "manual",