From 9f4acb88c092e3500bac8ca62bd1ecab6a013a9a Mon Sep 17 00:00:00 2001 From: imrichardwu Date: Fri, 13 Feb 2026 16:55:52 -0700 Subject: [PATCH 1/6] Add FailedIcon component and DurationChart failed icon plugin This commit introduces a new `FailedIcon` component for representing failure states in the UI, along with a plugin for the DurationChart that visually indicates failed indices. The `FailedIcon` is created using Chakra UI's icon system, while the plugin draws a custom icon on the chart for failed data points. --- .../src/airflow/ui/src/assets/FailedIcon.tsx | 41 ++++++++++ .../DurationChart/failedIconPlugin.ts | 74 +++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 airflow-core/src/airflow/ui/src/assets/FailedIcon.tsx create mode 100644 airflow-core/src/airflow/ui/src/components/DurationChart/failedIconPlugin.ts diff --git a/airflow-core/src/airflow/ui/src/assets/FailedIcon.tsx b/airflow-core/src/airflow/ui/src/assets/FailedIcon.tsx new file mode 100644 index 0000000000000..5bc0b4e9bfb48 --- /dev/null +++ b/airflow-core/src/airflow/ui/src/assets/FailedIcon.tsx @@ -0,0 +1,41 @@ +/*! + * 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. + */ +import { createIcon } from "@chakra-ui/react"; + +/** + * Warning/error icon (triangle with exclamation) for failed state. + * Use in UI components; for Canvas (e.g. Chart.js) use canvas drawing or an image. + */ +export const FailedIcon = createIcon({ + defaultProps: { + height: "1em", + width: "1em", + }, + displayName: "Failed Icon", + path: ( + + + + ), + viewBox: "0 0 16 16", +}); diff --git a/airflow-core/src/airflow/ui/src/components/DurationChart/failedIconPlugin.ts b/airflow-core/src/airflow/ui/src/components/DurationChart/failedIconPlugin.ts new file mode 100644 index 0000000000000..87f38e06c40da --- /dev/null +++ b/airflow-core/src/airflow/ui/src/components/DurationChart/failedIconPlugin.ts @@ -0,0 +1,74 @@ +/*! + * 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. + */ +import type { Chart } from "chart.js"; + +const FAILED_ICON_PLUGIN_ID = "durationChartFailedIcon"; +const ICON_SIZE = 14; +const ICON_OFFSET = 4; + +export const createFailedIconPlugin = (failedIndices: Array, failedIconColor: string) => ({ + afterDatasetsDraw(chart: Chart) { + if (failedIndices.length === 0) { + return; + } + + const { ctx } = chart; + const meta = chart.getDatasetMeta(1); + + if (meta.data.length === 0) { + return; + } + + failedIndices.forEach((index) => { + const element = meta.data[index]; + + if (!element) { + return; + } + + const { x, y } = element.getProps(["x", "y"], true) as { x: number; y: number }; + const iconX = x; + const iconY = y - ICON_OFFSET - ICON_SIZE; + + ctx.save(); + + const half = ICON_SIZE / 2; + + ctx.beginPath(); + ctx.moveTo(iconX, iconY + ICON_SIZE); + ctx.lineTo(iconX - half, iconY); + ctx.lineTo(iconX + half, iconY); + ctx.closePath(); + ctx.fillStyle = failedIconColor; + ctx.fill(); + ctx.strokeStyle = failedIconColor; + ctx.lineWidth = 1; + ctx.stroke(); + + ctx.fillStyle = "white"; + ctx.font = `bold ${ICON_SIZE * 0.6}px sans-serif`; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillText("!", iconX, iconY + ICON_SIZE * 0.55); + + ctx.restore(); + }); + }, + id: FAILED_ICON_PLUGIN_ID, +}); From b3b0aa9122b94a3e3de4328b673d4a57ca151bb6 Mon Sep 17 00:00:00 2001 From: imrichardwu Date: Fri, 13 Feb 2026 17:21:23 -0700 Subject: [PATCH 2/6] feat: add fail icon if status is failed --- .../src/airflow/ui/src/layouts/Details/Grid/Bar.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Bar.tsx b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Bar.tsx index 6b52198f51025..956e16652b97d 100644 --- a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Bar.tsx +++ b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Bar.tsx @@ -16,16 +16,18 @@ * specific language governing permissions and limitations * under the License. */ -import { Flex, Box } from "@chakra-ui/react"; +import { Flex, Box, Center } from "@chakra-ui/react"; import { useParams, useSearchParams } from "react-router-dom"; import type { GridRunsResponse } from "openapi/requests"; +import { FailedIcon } from "src/assets/FailedIcon"; import { RunTypeIcon } from "src/components/RunTypeIcon"; import { useHover } from "src/context/hover"; import { GridButton } from "./GridButton"; const BAR_HEIGHT = 100; +const ICON_GAP_PX = 4; type Props = { readonly max: number; @@ -41,6 +43,8 @@ export const Bar = ({ max, onClick, run }: Props) => { const isSelected = runId === run.run_id; const isHovered = hoveredRunId === run.run_id; const search = searchParams.toString(); + const isFailed = (run.state ?? "").toLowerCase() === "failed"; + const barHeightPx = max > 0 ? (run.duration / max) * BAR_HEIGHT : 0; const handleMouseEnter = () => setHoveredRunId(run.run_id); const handleMouseLeave = () => setHoveredRunId(undefined); @@ -53,6 +57,11 @@ export const Bar = ({ max, onClick, run }: Props) => { position="relative" transition="background-color 0.2s" > + {isFailed ? ( +
+ +
+ ) : undefined} Date: Fri, 13 Feb 2026 17:25:47 -0700 Subject: [PATCH 3/6] fix: adjust fail icon padding to be dynamic --- .../ui/src/layouts/Details/Grid/Grid.tsx | 29 +++++++++++++------ .../ui/src/layouts/Details/Grid/constants.ts | 8 ++++- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx index 481dcf08033f5..49d1b9058f6e6 100644 --- a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx +++ b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx @@ -39,6 +39,7 @@ import { TaskInstancesColumn } from "./TaskInstancesColumn"; import { TaskNames } from "./TaskNames"; import { GRID_HEADER_HEIGHT_PX, + GRID_HEADER_ICON_SPACE_PX, GRID_HEADER_PADDING_PX, GRID_OUTER_PADDING_PX, ROW_HEIGHT, @@ -150,22 +151,32 @@ export const Grid = ({ dagRunState, limit, runType, showGantt, triggeringUser }: {/* Grid header, both bgs are needed to hide elements during horizontal and vertical scroll */} - + {Boolean(gridRuns?.length) && ( <> - - + + )} {/* Duration bars */} - - - - - + + + + + {gridRuns?.map((dr: GridRunsResponse) => ( ))} @@ -174,7 +185,7 @@ export const Grid = ({ dagRunState, limit, runType, showGantt, triggeringUser }: Date: Fri, 13 Feb 2026 17:47:11 -0700 Subject: [PATCH 4/6] allow user to click on fail icon and redirect to task instances --- .../ui/src/layouts/Details/Grid/Bar.tsx | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Bar.tsx b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Bar.tsx index 956e16652b97d..2a20c7541ecb9 100644 --- a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Bar.tsx +++ b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Bar.tsx @@ -17,7 +17,9 @@ * under the License. */ import { Flex, Box, Center } from "@chakra-ui/react"; +import { Button } from "@chakra-ui/react"; import { useParams, useSearchParams } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import type { GridRunsResponse } from "openapi/requests"; import { FailedIcon } from "src/assets/FailedIcon"; @@ -49,6 +51,12 @@ export const Bar = ({ max, onClick, run }: Props) => { const handleMouseEnter = () => setHoveredRunId(run.run_id); const handleMouseLeave = () => setHoveredRunId(undefined); + const navigate = useNavigate(); + + const handleFailedIconClick = () => { + void navigate({ pathname: `/dags/${dagId}/runs/${run.run_id}`, search }); + }; + return ( { > {isFailed ? (
- +
) : undefined} Date: Mon, 16 Feb 2026 17:55:45 -0700 Subject: [PATCH 5/6] feat: add deadlines feature for DAG runs - Introduced a new DeadlinesService to fetch deadlines for specific DAG runs. - Added a new endpoint to the OpenAPI specification for retrieving DAG run deadlines. - Created a Deadlines component to display deadlines in a table format with sorting and filtering options. - Integrated a DeadlineIcon to visually indicate missed deadlines in the Gantt chart. - Updated the UI to include a new tab for deadlines in the DAG run details page. - Enhanced the existing services and types to accommodate the new deadlines feature. - Added translations for deadlines-related UI elements. --- .../core_api/datamodels/ui/common.py | 1 + .../core_api/datamodels/ui/deadline.py | 34 ++++ .../core_api/openapi/_private_ui.yaml | 84 ++++++++++ .../core_api/routes/ui/__init__.py | 2 + .../core_api/routes/ui/deadlines.py | 86 ++++++++++ .../api_fastapi/core_api/routes/ui/grid.py | 10 +- .../airflow/ui/openapi-gen/queries/common.ts | 9 +- .../ui/openapi-gen/queries/ensureQueryData.ts | 15 +- .../ui/openapi-gen/queries/prefetch.ts | 15 +- .../airflow/ui/openapi-gen/queries/queries.ts | 15 +- .../ui/openapi-gen/queries/suspense.ts | 15 +- .../ui/openapi-gen/requests/schemas.gen.ts | 56 ++++++- .../ui/openapi-gen/requests/services.gen.ts | 29 +++- .../ui/openapi-gen/requests/types.gen.ts | 39 +++++ .../ui/public/i18n/locales/en/dag.json | 12 ++ .../airflow/ui/src/assets/DeadlineIcon.tsx | 42 +++++ .../ui/src/layouts/Details/Grid/Bar.tsx | 31 +++- .../airflow/ui/src/pages/Run/Deadlines.tsx | 152 ++++++++++++++++++ .../src/airflow/ui/src/pages/Run/Run.tsx | 3 +- airflow-core/src/airflow/ui/src/router.tsx | 2 + 20 files changed, 642 insertions(+), 10 deletions(-) create mode 100644 airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/deadline.py create mode 100644 airflow-core/src/airflow/api_fastapi/core_api/routes/ui/deadlines.py create mode 100644 airflow-core/src/airflow/ui/src/assets/DeadlineIcon.tsx create mode 100644 airflow-core/src/airflow/ui/src/pages/Run/Deadlines.tsx 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 a18042b4960f8..2c5832f3246e8 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 0000000000000..61391885bc441 --- /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 72832270c4d54..4e64fbb60927f 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: @@ -1917,6 +1962,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: @@ -2095,6 +2175,9 @@ components: - type: 'null' run_type: $ref: '#/components/schemas/DagRunType' + has_missed_deadline: + type: boolean + title: Has Missed Deadline duration: type: number title: Duration @@ -2109,6 +2192,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 3a3cadc46f905..ce8744785cd43 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.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(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 0000000000000..03aeed13e4fde --- /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 694f12e02944c..3a3eeea223dd7 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 @@ 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.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 a80d4d158ac34..056e3001e844b 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/openapi-react-query-codegen@1.6.2 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, TaskInstanceService, TaskService, TeamsService, VariableService, VersionService, XcomService } from "../requests/services.gen"; +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, TaskInstanceService, TaskService, TeamsService, VariableService, VersionService, XcomService } from "../requests/services.gen"; import { DagRunState, DagWarningType } from "../requests/types.gen"; export type AssetServiceGetAssetsDefaultResponse = Awaited>; export type AssetServiceGetAssetsQueryResult = UseQueryResult; @@ -806,6 +806,13 @@ export type DashboardServiceDagStatsDefaultResponse = Awaited = UseQueryResult; export const useDashboardServiceDagStatsKey = "DashboardServiceDagStats"; export const UseDashboardServiceDagStatsKeyFn = (queryKey?: Array) => [useDashboardServiceDagStatsKey, ...(queryKey ?? [])]; +export type DeadlinesServiceGetDagRunDeadlinesDefaultResponse = Awaited>; +export type DeadlinesServiceGetDagRunDeadlinesQueryResult = UseQueryResult; +export const useDeadlinesServiceGetDagRunDeadlinesKey = "DeadlinesServiceGetDagRunDeadlines"; +export const UseDeadlinesServiceGetDagRunDeadlinesKeyFn = ({ dagId, runId }: { + dagId: string; + runId: string; +}, queryKey?: Array) => [useDeadlinesServiceGetDagRunDeadlinesKey, ...(queryKey ?? [{ dagId, runId }])]; export type StructureServiceStructureDataDefaultResponse = Awaited>; export type StructureServiceStructureDataQueryResult = UseQueryResult; 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 e7a407bc6cc52..8467319072c80 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/openapi-react-query-codegen@1.6.2 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, TeamsService, VariableService, VersionService, XcomService } from "../requests/services.gen"; +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, TaskInstanceService, TaskService, TeamsService, VariableService, VersionService, XcomService } from "../requests/services.gen"; import { DagRunState, DagWarningType } from "../requests/types.gen"; import * as Common from "./common"; /** @@ -1530,6 +1530,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 47fd99d5f55bf..0fa80b7c55ddf 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/openapi-react-query-codegen@1.6.2 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, TeamsService, VariableService, VersionService, XcomService } from "../requests/services.gen"; +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, TaskInstanceService, TaskService, TeamsService, VariableService, VersionService, XcomService } from "../requests/services.gen"; import { DagRunState, DagWarningType } from "../requests/types.gen"; import * as Common from "./common"; /** @@ -1530,6 +1530,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 c2b6754a18e37..ea011909b1dd1 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/openapi-react-query-codegen@1.6.2 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, TaskInstanceService, TaskService, TeamsService, VariableService, VersionService, XcomService } from "../requests/services.gen"; +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, TaskInstanceService, TaskService, TeamsService, VariableService, VersionService, XcomService } from "../requests/services.gen"; 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"; /** @@ -1530,6 +1530,19 @@ export const useDashboardServiceHistoricalMetrics = = unknown[]>(queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ 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 = = unknown[]>({ dagId, runId }: { + dagId: string; + runId: string; +}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ 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 b7771202b2071..662efab3b619f 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/openapi-react-query-codegen@1.6.2 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, TeamsService, VariableService, VersionService, XcomService } from "../requests/services.gen"; +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, TaskInstanceService, TaskService, TeamsService, VariableService, VersionService, XcomService } from "../requests/services.gen"; import { DagRunState, DagWarningType } from "../requests/types.gen"; import * as Common from "./common"; /** @@ -1530,6 +1530,19 @@ export const useDashboardServiceHistoricalMetricsSuspense = = unknown[]>(queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ 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 = = unknown[]>({ dagId, runId }: { + dagId: string; + runId: string; +}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ 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 a533f31c31f4a..e94b701654254 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 @@ -7778,6 +7778,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: { @@ -8051,6 +8101,10 @@ export const $GridRunsResponse = { run_type: { '$ref': '#/components/schemas/DagRunType' }, + has_missed_deadline: { + type: 'boolean', + title: 'Has Missed Deadline' + }, duration: { type: 'number', title: 'Duration', @@ -8058,7 +8112,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 e5128eba0d85e..ea789d5483896 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, DeleteDagAssetQueuedEventsData, DeleteDagAssetQueuedEventsResponse, GetDagAssetQueuedEventData, GetDagAssetQueuedEventResponse, DeleteDagAssetQueuedEventData, DeleteDagAssetQueuedEventResponse, NextRunAssetsData, NextRunAssetsResponse, ListBackfillsData, ListBackfillsResponse, CreateBackfillData, CreateBackfillResponse, GetBackfillData, GetBackfillResponse, PauseBackfillData, PauseBackfillResponse, UnpauseBackfillData, UnpauseBackfillResponse, CancelBackfillData, CancelBackfillResponse, CreateBackfillDryRunData, CreateBackfillDryRunResponse, ListBackfillsUiData, ListBackfillsUiResponse, DeleteConnectionData, DeleteConnectionResponse, GetConnectionData, GetConnectionResponse, PatchConnectionData, PatchConnectionResponse, GetConnectionsData, GetConnectionsResponse, PostConnectionData, PostConnectionResponse, BulkConnectionsData, BulkConnectionsResponse, TestConnectionData, TestConnectionResponse, CreateDefaultConnectionsResponse, HookMetaDataResponse, GetDagRunData, GetDagRunResponse, DeleteDagRunData, DeleteDagRunResponse, PatchDagRunData, PatchDagRunResponse, GetUpstreamAssetEventsData, GetUpstreamAssetEventsResponse, ClearDagRunData, ClearDagRunResponse, GetDagRunsData, GetDagRunsResponse, TriggerDagRunData, TriggerDagRunResponse, WaitDagRunUntilFinishedData, WaitDagRunUntilFinishedResponse, GetListDagRunsBatchData, GetListDagRunsBatchResponse, GetDagSourceData, GetDagSourceResponse, GetDagStatsData, GetDagStatsResponse, GetConfigData, GetConfigResponse, GetConfigValueData, GetConfigValueResponse, GetConfigsResponse, ListDagWarningsData, ListDagWarningsResponse, GetDagsData, GetDagsResponse, PatchDagsData, PatchDagsResponse, GetDagData, GetDagResponse, PatchDagData, PatchDagResponse, DeleteDagData, DeleteDagResponse, GetDagDetailsData, GetDagDetailsResponse, FavoriteDagData, FavoriteDagResponse, UnfavoriteDagData, UnfavoriteDagResponse, GetDagTagsData, GetDagTagsResponse, GetDagsUiData, GetDagsUiResponse, GetLatestRunInfoData, GetLatestRunInfoResponse, GetEventLogData, GetEventLogResponse, GetEventLogsData, GetEventLogsResponse, GetExtraLinksData, GetExtraLinksResponse, GetTaskInstanceData, GetTaskInstanceResponse, PatchTaskInstanceData, PatchTaskInstanceResponse, DeleteTaskInstanceData, DeleteTaskInstanceResponse, GetMappedTaskInstancesData, GetMappedTaskInstancesResponse, GetTaskInstanceDependenciesByMapIndexData, GetTaskInstanceDependenciesByMapIndexResponse, GetTaskInstanceDependenciesData, GetTaskInstanceDependenciesResponse, GetTaskInstanceTriesData, GetTaskInstanceTriesResponse, GetMappedTaskInstanceTriesData, GetMappedTaskInstanceTriesResponse, GetMappedTaskInstanceData, GetMappedTaskInstanceResponse, PatchTaskInstanceByMapIndexData, PatchTaskInstanceByMapIndexResponse, GetTaskInstancesData, GetTaskInstancesResponse, BulkTaskInstancesData, BulkTaskInstancesResponse, GetTaskInstancesBatchData, GetTaskInstancesBatchResponse, GetTaskInstanceTryDetailsData, GetTaskInstanceTryDetailsResponse, GetMappedTaskInstanceTryDetailsData, GetMappedTaskInstanceTryDetailsResponse, PostClearTaskInstancesData, PostClearTaskInstancesResponse, PatchTaskInstanceDryRunByMapIndexData, PatchTaskInstanceDryRunByMapIndexResponse, PatchTaskInstanceDryRunData, PatchTaskInstanceDryRunResponse, GetLogData, GetLogResponse, GetExternalLogUrlData, GetExternalLogUrlResponse, UpdateHitlDetailData, UpdateHitlDetailResponse, GetHitlDetailData, GetHitlDetailResponse, GetHitlDetailTryDetailData, GetHitlDetailTryDetailResponse, GetHitlDetailsData, GetHitlDetailsResponse, GetImportErrorData, GetImportErrorResponse, GetImportErrorsData, GetImportErrorsResponse, GetJobsData, GetJobsResponse, GetPluginsData, GetPluginsResponse, ImportErrorsResponse, DeletePoolData, DeletePoolResponse, GetPoolData, GetPoolResponse, PatchPoolData, PatchPoolResponse, GetPoolsData, GetPoolsResponse, PostPoolData, PostPoolResponse, BulkPoolsData, BulkPoolsResponse, GetProvidersData, GetProvidersResponse, GetXcomEntryData, GetXcomEntryResponse, UpdateXcomEntryData, UpdateXcomEntryResponse, DeleteXcomEntryData, DeleteXcomEntryResponse, GetXcomEntriesData, GetXcomEntriesResponse, CreateXcomEntryData, CreateXcomEntryResponse, GetTasksData, GetTasksResponse, GetTaskData, GetTaskResponse, DeleteVariableData, DeleteVariableResponse, GetVariableData, GetVariableResponse, PatchVariableData, PatchVariableResponse, GetVariablesData, GetVariablesResponse, PostVariableData, PostVariableResponse, BulkVariablesData, BulkVariablesResponse, ReparseDagFileData, ReparseDagFileResponse, GetDagVersionData, GetDagVersionResponse, GetDagVersionsData, GetDagVersionsResponse, GetHealthResponse, GetVersionResponse, LoginData, LoginResponse, LogoutResponse, GetAuthMenusResponse, GetCurrentUserInfoResponse, GetDependenciesData, GetDependenciesResponse, HistoricalMetricsData, HistoricalMetricsResponse, DagStatsResponse2, StructureDataData, StructureDataResponse2, GetDagStructureData, GetDagStructureResponse, GetGridRunsData, GetGridRunsResponse, GetGridTiSummariesData, GetGridTiSummariesResponse, GetGanttDataData, GetGanttDataResponse, GetCalendarData, GetCalendarResponse, ListTeamsData, ListTeamsResponse } from './types.gen'; +import type { GetAssetsData, GetAssetsResponse, GetAssetAliasesData, GetAssetAliasesResponse, GetAssetAliasData, GetAssetAliasResponse, GetAssetEventsData, GetAssetEventsResponse, CreateAssetEventData, CreateAssetEventResponse, MaterializeAssetData, MaterializeAssetResponse, GetAssetQueuedEventsData, GetAssetQueuedEventsResponse, DeleteAssetQueuedEventsData, DeleteAssetQueuedEventsResponse, GetAssetData, GetAssetResponse, GetDagAssetQueuedEventsData, GetDagAssetQueuedEventsResponse, DeleteDagAssetQueuedEventsData, DeleteDagAssetQueuedEventsResponse, GetDagAssetQueuedEventData, GetDagAssetQueuedEventResponse, DeleteDagAssetQueuedEventData, DeleteDagAssetQueuedEventResponse, NextRunAssetsData, NextRunAssetsResponse, ListBackfillsData, ListBackfillsResponse, CreateBackfillData, CreateBackfillResponse, GetBackfillData, GetBackfillResponse, PauseBackfillData, PauseBackfillResponse, UnpauseBackfillData, UnpauseBackfillResponse, CancelBackfillData, CancelBackfillResponse, CreateBackfillDryRunData, CreateBackfillDryRunResponse, ListBackfillsUiData, ListBackfillsUiResponse, DeleteConnectionData, DeleteConnectionResponse, GetConnectionData, GetConnectionResponse, PatchConnectionData, PatchConnectionResponse, GetConnectionsData, GetConnectionsResponse, PostConnectionData, PostConnectionResponse, BulkConnectionsData, BulkConnectionsResponse, TestConnectionData, TestConnectionResponse, CreateDefaultConnectionsResponse, HookMetaDataResponse, GetDagRunData, GetDagRunResponse, DeleteDagRunData, DeleteDagRunResponse, PatchDagRunData, PatchDagRunResponse, GetUpstreamAssetEventsData, GetUpstreamAssetEventsResponse, ClearDagRunData, ClearDagRunResponse, GetDagRunsData, GetDagRunsResponse, TriggerDagRunData, TriggerDagRunResponse, WaitDagRunUntilFinishedData, WaitDagRunUntilFinishedResponse, GetListDagRunsBatchData, GetListDagRunsBatchResponse, GetDagSourceData, GetDagSourceResponse, GetDagStatsData, GetDagStatsResponse, GetConfigData, GetConfigResponse, GetConfigValueData, GetConfigValueResponse, GetConfigsResponse, ListDagWarningsData, ListDagWarningsResponse, GetDagsData, GetDagsResponse, PatchDagsData, PatchDagsResponse, GetDagData, GetDagResponse, PatchDagData, PatchDagResponse, DeleteDagData, DeleteDagResponse, GetDagDetailsData, GetDagDetailsResponse, FavoriteDagData, FavoriteDagResponse, UnfavoriteDagData, UnfavoriteDagResponse, GetDagTagsData, GetDagTagsResponse, GetDagsUiData, GetDagsUiResponse, GetLatestRunInfoData, GetLatestRunInfoResponse, GetEventLogData, GetEventLogResponse, GetEventLogsData, GetEventLogsResponse, GetExtraLinksData, GetExtraLinksResponse, GetTaskInstanceData, GetTaskInstanceResponse, PatchTaskInstanceData, PatchTaskInstanceResponse, DeleteTaskInstanceData, DeleteTaskInstanceResponse, GetMappedTaskInstancesData, GetMappedTaskInstancesResponse, GetTaskInstanceDependenciesByMapIndexData, GetTaskInstanceDependenciesByMapIndexResponse, GetTaskInstanceDependenciesData, GetTaskInstanceDependenciesResponse, GetTaskInstanceTriesData, GetTaskInstanceTriesResponse, GetMappedTaskInstanceTriesData, GetMappedTaskInstanceTriesResponse, GetMappedTaskInstanceData, GetMappedTaskInstanceResponse, PatchTaskInstanceByMapIndexData, PatchTaskInstanceByMapIndexResponse, GetTaskInstancesData, GetTaskInstancesResponse, BulkTaskInstancesData, BulkTaskInstancesResponse, GetTaskInstancesBatchData, GetTaskInstancesBatchResponse, GetTaskInstanceTryDetailsData, GetTaskInstanceTryDetailsResponse, GetMappedTaskInstanceTryDetailsData, GetMappedTaskInstanceTryDetailsResponse, PostClearTaskInstancesData, PostClearTaskInstancesResponse, PatchTaskInstanceDryRunByMapIndexData, PatchTaskInstanceDryRunByMapIndexResponse, PatchTaskInstanceDryRunData, PatchTaskInstanceDryRunResponse, GetLogData, GetLogResponse, GetExternalLogUrlData, GetExternalLogUrlResponse, UpdateHitlDetailData, UpdateHitlDetailResponse, GetHitlDetailData, GetHitlDetailResponse, GetHitlDetailTryDetailData, GetHitlDetailTryDetailResponse, GetHitlDetailsData, GetHitlDetailsResponse, GetImportErrorData, GetImportErrorResponse, GetImportErrorsData, GetImportErrorsResponse, GetJobsData, GetJobsResponse, GetPluginsData, GetPluginsResponse, ImportErrorsResponse, DeletePoolData, DeletePoolResponse, GetPoolData, GetPoolResponse, PatchPoolData, PatchPoolResponse, GetPoolsData, GetPoolsResponse, PostPoolData, PostPoolResponse, BulkPoolsData, BulkPoolsResponse, GetProvidersData, GetProvidersResponse, GetXcomEntryData, GetXcomEntryResponse, UpdateXcomEntryData, UpdateXcomEntryResponse, DeleteXcomEntryData, DeleteXcomEntryResponse, GetXcomEntriesData, GetXcomEntriesResponse, CreateXcomEntryData, CreateXcomEntryResponse, GetTasksData, GetTasksResponse, GetTaskData, GetTaskResponse, DeleteVariableData, DeleteVariableResponse, GetVariableData, GetVariableResponse, PatchVariableData, PatchVariableResponse, GetVariablesData, GetVariablesResponse, PostVariableData, PostVariableResponse, BulkVariablesData, BulkVariablesResponse, ReparseDagFileData, ReparseDagFileResponse, GetDagVersionData, GetDagVersionResponse, GetDagVersionsData, GetDagVersionsResponse, GetHealthResponse, GetVersionResponse, LoginData, LoginResponse, LogoutResponse, GetAuthMenusResponse, GetCurrentUserInfoResponse, GetDependenciesData, GetDependenciesResponse, HistoricalMetricsData, HistoricalMetricsResponse, DagStatsResponse2, GetDagRunDeadlinesData, GetDagRunDeadlinesResponse, StructureDataData, StructureDataResponse2, GetDagStructureData, GetDagStructureResponse, GetGridRunsData, GetGridRunsResponse, GetGridTiSummariesData, GetGridTiSummariesResponse, GetGanttDataData, GetGanttDataResponse, GetCalendarData, GetCalendarResponse, ListTeamsData, ListTeamsResponse } from './types.gen'; export class AssetService { /** @@ -3913,6 +3913,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 { + 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 0c3ecfe7ce03b..19380361a6b7c 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 @@ -1922,6 +1922,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. */ @@ -1984,6 +1996,7 @@ export type GridRunsResponse = { run_after: string; state: DagRunState | null; run_type: DagRunType; + has_missed_deadline: boolean; readonly duration: number; }; @@ -3456,6 +3469,13 @@ export type HistoricalMetricsResponse = HistoricalMetricDataResponse; export type DagStatsResponse2 = DashboardDagStatsResponse; +export type GetDagRunDeadlinesData = { + dagId: string; + runId: string; +}; + +export type GetDagRunDeadlinesResponse = Array; + export type StructureDataData = { dagId: string; depth?: number | null; @@ -6662,6 +6682,25 @@ export type $OpenApiTs = { }; }; }; + '/ui/deadlines/{dag_id}/{run_id}': { + get: { + req: GetDagRunDeadlinesData; + res: { + /** + * Successful Response + */ + 200: Array; + /** + * Not Found + */ + 404: HTTPExceptionResponse; + /** + * Validation Error + */ + 422: HTTPValidationError; + }; + }; + }; '/ui/structure/structure_data': { get: { req: StructureDataData; diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/en/dag.json b/airflow-core/src/airflow/ui/public/i18n/locales/en/dag.json index 04380b149a8b2..aace2601cbab0 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/en/dag.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/en/dag.json @@ -40,6 +40,17 @@ "parseDuration": "Parse Duration:", "parsedAt": "Parsed at:" }, + "deadlines": { + "createdAt": "Created At", + "deadlineTime": "Deadline Time", + "description": "Description", + "missed": "Missed", + "name": "Name", + "noDeadlines": "No deadlines for this DAG run.", + "onTrack": "On Track", + "showMissedOnly": "Show missed only", + "status": "Status" + }, "extraLinks": "Extra Links", "grid": { "buttons": { @@ -161,6 +172,7 @@ "backfills": "Backfills", "calendar": "Calendar", "code": "Code", + "deadlines": "Deadlines", "details": "Details", "logs": "Logs", "mappedTaskInstances_one": "Task Instance [{{count}}]", diff --git a/airflow-core/src/airflow/ui/src/assets/DeadlineIcon.tsx b/airflow-core/src/airflow/ui/src/assets/DeadlineIcon.tsx new file mode 100644 index 0000000000000..7627eaf8d1e75 --- /dev/null +++ b/airflow-core/src/airflow/ui/src/assets/DeadlineIcon.tsx @@ -0,0 +1,42 @@ +/*! + * 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. + */ +import { createIcon } from "@chakra-ui/react"; + +/** + * Clock with warning indicator icon for missed deadlines. + * Visually distinct from the triangle FailedIcon. + */ +export const DeadlineIcon = createIcon({ + defaultProps: { + height: "1em", + width: "1em", + }, + displayName: "Deadline Icon", + path: ( + + + + + ), + viewBox: "0 0 16 16", +}); diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Bar.tsx b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Bar.tsx index 2a20c7541ecb9..d274d337eb85a 100644 --- a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Bar.tsx +++ b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Bar.tsx @@ -22,6 +22,7 @@ import { useParams, useSearchParams } from "react-router-dom"; import { useNavigate } from "react-router-dom"; import type { GridRunsResponse } from "openapi/requests"; +import { DeadlineIcon } from "src/assets/DeadlineIcon"; import { FailedIcon } from "src/assets/FailedIcon"; import { RunTypeIcon } from "src/components/RunTypeIcon"; import { useHover } from "src/context/hover"; @@ -30,6 +31,7 @@ import { GridButton } from "./GridButton"; const BAR_HEIGHT = 100; const ICON_GAP_PX = 4; +const ICON_HEIGHT_PX = 16; type Props = { readonly max: number; @@ -46,6 +48,7 @@ export const Bar = ({ max, onClick, run }: Props) => { const isHovered = hoveredRunId === run.run_id; const search = searchParams.toString(); const isFailed = (run.state ?? "").toLowerCase() === "failed"; + const hasMissedDeadline = Boolean(run.has_missed_deadline); const barHeightPx = max > 0 ? (run.duration / max) * BAR_HEIGHT : 0; const handleMouseEnter = () => setHoveredRunId(run.run_id); @@ -57,6 +60,14 @@ export const Bar = ({ max, onClick, run }: Props) => { void navigate({ pathname: `/dags/${dagId}/runs/${run.run_id}`, search }); }; + const handleDeadlineIconClick = () => { + void navigate({ pathname: `/dags/${dagId}/runs/${run.run_id}/deadlines`, search }); + }; + + // When both icons are present, stack the deadline icon above the failed icon + const failedIconBottom = barHeightPx + ICON_GAP_PX; + const deadlineIconBottom = isFailed ? failedIconBottom + ICON_HEIGHT_PX : failedIconBottom; + return ( { position="relative" transition="background-color 0.2s" > + {hasMissedDeadline ? ( +
+ +
+ ) : undefined} {isFailed ? ( -
+