This is an automated email from the ASF dual-hosted git repository.
pierrejeambrun pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new b3cfad92f3d Fix Gantt tooltip showing wrong end date on
queued/scheduled bars (#68570)
b3cfad92f3d is described below
commit b3cfad92f3df5d36690cf66a9da2aa31590111b5
Author: Aaryan123456679 <[email protected]>
AuthorDate: Thu Jul 2 21:15:20 2026 +0530
Fix Gantt tooltip showing wrong end date on queued/scheduled bars (#68570)
* UI: Fix Gantt tooltip showing wrong end date on queued/scheduled bars
The tooltip on the queued and scheduled segments of a Gantt bar reported
the end of that segment (e.g. the moment queueing ended) as the task's
End Date, instead of the task's actual end. This was a regression from
the start-date fix in #68176, which made the Start Date consistent across
segments but left the End Date per-segment.
Carry the task's effective end (end_date, or "now" while running) on every
segment of a try as `end_when`, mirroring `start_when`, and use it for the
tooltip's End Date so all segments of a try report the same start and end.
closes: #68174
* UI: Make end_when symmetric with start_when in Gantt tooltip data
Address review: derive a single effectiveEndDate (the raw end_date, or
"now" while a started task is still running) and gate end_when on it the
same way start_when is gated on start_date. Finished tasks now keep the
API's raw end_date string instead of a re-serialized dayjs timestamp, and
the tryWhenForTooltip block no longer depends on endMs ordering. Pin the
running-task test to a fixed "now" via fake timers.
* UI: Remove noisy comments from Gantt tooltip data transform
Address review: drop the verbose inline comments around effectiveEndDate
and tryWhenForTooltip; the code is self-explanatory.
---
.../ui/src/layouts/Details/Gantt/GanttTimeline.tsx | 2 +-
.../ui/src/layouts/Details/Gantt/utils.test.ts | 66 +++++++++++++++++++++-
.../airflow/ui/src/layouts/Details/Gantt/utils.ts | 7 ++-
3 files changed, 72 insertions(+), 3 deletions(-)
diff --git
a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/GanttTimeline.tsx
b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/GanttTimeline.tsx
index 43c28dba56b..940a80854e2 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/GanttTimeline.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/GanttTimeline.tsx
@@ -85,7 +85,7 @@ const toTooltipSummary = (
return {
child_states: null,
- max_end_date: dayjs(segment.x[1]).toISOString(),
+ max_end_date: segment.end_when ?? dayjs(segment.x[1]).toISOString(),
min_start_date: segment.start_when ?? dayjs(segment.x[0]).toISOString(),
state: segment.state ?? null,
task_display_name: segment.y,
diff --git
a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.test.ts
b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.test.ts
index d725dfc8be6..f39c751d50d 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.test.ts
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
import dayjs from "dayjs";
-import { describe, expect, it } from "vitest";
+import { describe, expect, it, vi } from "vitest";
import type { LightGridTaskInstanceSummary } from "openapi/requests/types.gen";
import type { GridTask } from "src/layouts/Details/Grid/utils";
@@ -237,6 +237,70 @@ describe("transformGanttData", () => {
expect(result[2]?.state).toBe("success");
});
+ it("carries the task's actual start and end on every segment of the try", ()
=> {
+ const result = transformGanttData({
+ allTries: [
+ {
+ end_date: "2024-03-14T10:05:00+00:00",
+ is_mapped: false,
+ queued_dttm: "2024-03-14T09:59:00+00:00",
+ scheduled_dttm: "2024-03-14T09:58:00+00:00",
+ start_date: "2024-03-14T10:00:00+00:00",
+ state: "success",
+ task_display_name: "task_1",
+ task_id: "task_1",
+ try_number: 1,
+ },
+ ],
+ flatNodes: [{ depth: 0, id: "task_1", is_mapped: false, label: "task_1"
}],
+ gridSummaries: [],
+ });
+
+ expect(result).toHaveLength(3);
+ // The scheduled, queued, and execution bars all report the task's real
start_date/end_date
+ // (the raw API strings) so the tooltip is consistent no matter which
segment is hovered
+ // (regression from #68174).
+ for (const segment of result) {
+ expect(segment.start_when).toBe("2024-03-14T10:00:00+00:00");
+ expect(segment.end_when).toBe("2024-03-14T10:05:00+00:00");
+ }
+ });
+
+ it("uses the current time as end_when on every segment while the task is
still running", () => {
+ const now = new Date("2024-03-14T10:30:00.000Z");
+
+ vi.useFakeTimers();
+ vi.setSystemTime(now);
+
+ try {
+ const result = transformGanttData({
+ allTries: [
+ {
+ end_date: null,
+ is_mapped: false,
+ queued_dttm: "2024-03-14T09:59:00+00:00",
+ scheduled_dttm: null,
+ start_date: "2024-03-14T10:00:00+00:00",
+ state: "running",
+ task_display_name: "task_1",
+ task_id: "task_1",
+ try_number: 1,
+ },
+ ],
+ flatNodes: [{ depth: 0, id: "task_1", is_mapped: false, label:
"task_1" }],
+ gridSummaries: [],
+ });
+
+ // Queued + execution bars, both reporting the same "now" end so the
tooltip is consistent.
+ expect(result.length).toBeGreaterThan(0);
+ for (const segment of result) {
+ expect(segment.end_when).toBe(now.toISOString());
+ }
+ } finally {
+ vi.useRealTimers();
+ }
+ });
+
it("produces 2 segments when only queued_dttm is present", () => {
const result = transformGanttData({
allTries: [
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.ts
b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.ts
index 9502cd31e78..e8d2ba18b0b 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.ts
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.ts
@@ -28,6 +28,8 @@ import { renderDuration } from "src/utils/datetimeUtils";
import { buildTaskInstanceUrl } from "src/utils/links";
export type GanttDataItem = {
+ /** Effective task end (end_date, or "now" while running) — consistent
across all segments of the same try. */
+ end_when?: string | null;
isGroup?: boolean | null;
isMapped?: boolean | null;
/** Source try times for tooltips (matches TaskInstance `*_when` fields). */
@@ -135,11 +137,14 @@ export const transformGanttData = ({
const queuedMs = queuedDttm === null ? undefined :
dayjs(queuedDttm).valueOf();
const scheduledMs = scheduledDttm === null ? undefined :
dayjs(scheduledDttm).valueOf();
- // Include scheduled/queued/start times in tooltip data whenever
the timestamps exist.
+ const effectiveEndDate =
+ endDate ?? (hasTaskRunning && startDate !== null ? new
Date().toISOString() : null);
+
const tryWhenForTooltip = {
...(scheduledMs === undefined ? {} : { scheduled_when:
scheduledDttm }),
...(queuedMs === undefined ? {} : { queued_when: queuedDttm }),
...(startDate === null ? {} : { start_when: startDate }),
+ ...(effectiveEndDate === null ? {} : { end_when:
effectiveEndDate }),
};
let endMs: number;