diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json b/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json index 4176a91506254..071f0d1339e2a 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json @@ -74,6 +74,8 @@ "dagRun_other": "Dag Runs", "dagRunId": "Dag Run ID", "dagWarnings": "Dag warnings/errors", + "deadline_one": "Deadline", + "deadline_other": "Deadlines", "defaultToGraphView": "Default to graph view", "defaultToGridView": "Default to grid view", "delete": "Delete", 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 58626c2494248..5fd2bd3023f33 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": { @@ -171,6 +182,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/layouts/Details/Grid/Bar.tsx b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Bar.tsx index c2bb3ed6d04b3..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 @@ -16,8 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -import { Flex, Box } from "@chakra-ui/react"; -import { useParams, useSearchParams } from "react-router-dom"; +import { Flex, Box, Center, Button, Icon } from "@chakra-ui/react"; +import { LuClock, LuTriangleAlert } from "react-icons/lu"; +import { useParams, useSearchParams, useNavigate } from "react-router-dom"; import { RunTypeIcon } from "src/components/RunTypeIcon"; import { VersionIndicatorOptions } from "src/constants/showVersionIndicatorOptions"; @@ -32,6 +33,11 @@ import { type GridRunWithVersionFlags, } from "./useGridRunsWithVersionFlags"; +const ICON_GAP_PX = 4; +const ICON_HEIGHT_PX = 16; +const BAR_PADDING_BOTTOM_PX = 2; +const BAR_MIN_HEIGHT_PX = 14; + type Props = { readonly max: number; readonly onClick?: () => void; @@ -47,10 +53,24 @@ export const Bar = ({ max, onClick, run, showVersionIndicatorMode }: Props) => { const isSelected = runId === run.run_id; 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); const handleMouseLeave = () => setHoveredRunId(undefined); + const navigate = useNavigate(); + + const handleDeadlineIconClick = () => { + void navigate({ pathname: `/dags/${dagId}/runs/${run.run_id}/deadlines`, search }); + }; + + // Account for minHeight and padding-bottom so icons always appear above the rendered bar + const effectiveBarHeightPx = Math.max(barHeightPx, BAR_MIN_HEIGHT_PX) + BAR_PADDING_BOTTOM_PX; + const failedIconBottom = effectiveBarHeightPx + ICON_GAP_PX; + const deadlineIconBottom = isFailed ? failedIconBottom + ICON_HEIGHT_PX : failedIconBottom; + return ( { position="relative" transition="background-color 0.2s" > + {hasMissedDeadline ? ( +
+ +
+ ) : undefined} + {isFailed ? ( +
+ +
+ ) : undefined} {run.isBundleVersionChange && (showVersionIndicatorMode === VersionIndicatorOptions.BUNDLE_VERSION || showVersionIndicatorMode === VersionIndicatorOptions.ALL) ? ( 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 517f5d9f7d4e6..084784ce535ca 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 @@ -40,6 +40,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, @@ -166,11 +167,21 @@ export const Grid = ({ {/* Grid header, both bgs are needed to hide elements during horizontal and vertical scroll */} - + {Boolean(gridRuns?.length) && ( <> - - + + )} @@ -196,7 +207,7 @@ export const Grid = ({ > => [ + { + accessorKey: "alert_name", + header: translate("deadlines.name"), + id: "alert_name", + }, + { + accessorKey: "deadline_time", + cell: ({ row }) =>