From 514aea9abfd8b9738ff26abe267a4e16670bbe54 Mon Sep 17 00:00:00 2001 From: yuseok89 Date: Sat, 31 Jan 2026 01:25:21 +0900 Subject: [PATCH] fix(ui): use ISO dates in Gantt chart for cross-browser consistency (#61250) (cherry picked from commit 705107621ca10fed635b0374ab1b9ca46b11766f) --- .../ui/src/layouts/Details/Gantt/Gantt.tsx | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx index 54d2c476b4161..2dc9ec6c0f646 100644 --- a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx +++ b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx @@ -52,7 +52,6 @@ import { useGridStructure } from "src/queries/useGridStructure"; import { useGridTiSummaries } from "src/queries/useGridTISummaries"; import { getComputedCSSVariableValue } from "src/theme"; import { isStatePending, useAutoRefresh } from "src/utils"; -import { DEFAULT_DATETIME_FORMAT_WITH_TZ, formatDate } from "src/utils/datetimeUtils"; import { createHandleBarClick, createHandleBarHover, createChartOptions } from "./utils"; @@ -144,8 +143,6 @@ export const Gantt = ({ limit, runType, triggeringUser }: Props) => { const isLoading = runsLoading || structureLoading || summariesLoading || tiLoading; - const currentTime = dayjs().tz(selectedTimezone).format(DEFAULT_DATETIME_FORMAT_WITH_TZ); - const data = useMemo(() => { if (isLoading || runId === "") { return []; @@ -159,15 +156,15 @@ export const Gantt = ({ limit, runType, triggeringUser }: Props) => { const gridSummary = gridSummaries.find((ti) => ti.task_id === node.id); if ((node.isGroup ?? node.is_mapped) && gridSummary) { - // Use min/max times from grid summary + // Use min/max times from grid summary; ISO so time scale and bar positions render consistently across browsers return { isGroup: node.isGroup, isMapped: node.is_mapped, state: gridSummary.state, taskId: gridSummary.task_id, x: [ - formatDate(gridSummary.min_start_date, selectedTimezone, DEFAULT_DATETIME_FORMAT_WITH_TZ), - formatDate(gridSummary.max_end_date, selectedTimezone, DEFAULT_DATETIME_FORMAT_WITH_TZ), + dayjs(gridSummary.min_start_date).toISOString(), + dayjs(gridSummary.max_end_date).toISOString(), ], y: gridSummary.task_id, }; @@ -177,17 +174,14 @@ export const Gantt = ({ limit, runType, triggeringUser }: Props) => { if (taskInstance) { const hasTaskRunning = isStatePending(taskInstance.state); - const endTime = hasTaskRunning ? currentTime : taskInstance.end_date; + const endTime = hasTaskRunning ? dayjs().toISOString() : taskInstance.end_date; return { isGroup: node.isGroup, isMapped: node.is_mapped, state: taskInstance.state, taskId: taskInstance.task_id, - x: [ - formatDate(taskInstance.start_date, selectedTimezone, DEFAULT_DATETIME_FORMAT_WITH_TZ), - formatDate(endTime, selectedTimezone, DEFAULT_DATETIME_FORMAT_WITH_TZ), - ], + x: [dayjs(taskInstance.start_date).toISOString(), dayjs(endTime).toISOString()], y: taskInstance.task_id, }; } @@ -196,7 +190,7 @@ export const Gantt = ({ limit, runType, triggeringUser }: Props) => { return undefined; }) .filter((item) => item !== undefined); - }, [flatNodes, gridTiSummaries, taskInstancesData, selectedTimezone, isLoading, runId, currentTime]); + }, [flatNodes, gridTiSummaries, taskInstancesData, isLoading, runId]); // Get all unique states and their colors const states = [...new Set(data.map((item) => item.state ?? "none"))];