From e593169ec463482045d16898ec3be226bf6278fc Mon Sep 17 00:00:00 2001 From: imrichardwu Date: Fri, 13 Feb 2026 16:55:52 -0700 Subject: [PATCH 01/11] 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 963ad545f53afaf1e56bf3e3ad5db94500056544 Mon Sep 17 00:00:00 2001 From: imrichardwu Date: Fri, 13 Feb 2026 17:21:23 -0700 Subject: [PATCH 02/11] 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 03/11] 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 04/11] 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: Thu, 19 Feb 2026 13:26:58 -0700 Subject: [PATCH 05/11] feat: add deadlines UI - DeadlineIcon, Deadlines page, i18n, and router - Added DeadlineIcon asset to indicate missed deadlines in the Gantt chart - Added Deadlines page component with sortable/filterable table - Added deadlines tab to the DAG run details page (Run.tsx) - Registered deadlines route in router - Added i18n translations for deadlines UI elements - Updated Bar.tsx to show DeadlineIcon for missed deadlines - Updated openapi-gen client files with deadlines API types and service --- .../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 + 14 files changed, 426 insertions(+), 9 deletions(-) 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/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 ? ( -
+
) : undefined} @@ -105,11 +100,10 @@ export const Bar = ({ max, onClick, run }: Props) => { m={0} minH={0} minW={0} - onClick={handleFailedIconClick} p={0} variant="ghost" > - +
) : undefined} From 435cb3ed59725bacc3f0e92a082a91699005f1fc Mon Sep 17 00:00:00 2001 From: imrichardwu Date: Wed, 25 Feb 2026 12:17:22 -0700 Subject: [PATCH 10/11] refactor(ui): simplify failed state icon rendering in Bar component --- .../airflow/ui/src/layouts/Details/Grid/Bar.tsx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) 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 8d60a637d5024..35e824498c17e 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 @@ -92,19 +92,7 @@ export const Bar = ({ max, onClick, run }: Props) => { ) : undefined} {isFailed ? (
- +
) : undefined} Date: Fri, 27 Feb 2026 09:08:34 -0700 Subject: [PATCH 11/11] refactor: remove redundant BAR_HEIGHT constant from Bar component --- airflow-core/src/airflow/ui/src/layouts/Details/Grid/Bar.tsx | 2 -- 1 file changed, 2 deletions(-) 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 4987b906e3f27..e4c80f6abadd7 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 @@ -25,7 +25,6 @@ import { VersionIndicatorOptions } from "src/constants/showVersionIndicatorOptio import { useHover } from "src/context/hover"; import { GridButton } from "./GridButton"; - import { BundleVersionIndicator, DagVersionIndicator } from "./VersionIndicator"; import { BAR_HEIGHT } from "./constants"; import { @@ -34,7 +33,6 @@ import { type GridRunWithVersionFlags, } from "./useGridRunsWithVersionFlags"; -const BAR_HEIGHT = 100; const ICON_GAP_PX = 4; const ICON_HEIGHT_PX = 16; const BAR_PADDING_BOTTOM_PX = 2;