This is an automated email from the ASF dual-hosted git repository.

bbovenzi pushed a commit to branch v3-3-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-3-test by this push:
     new c8aff5e1287 [v3-3-test] UI: Show duration chart tooltips in the 
selected timezone (#72024) (#72339)
c8aff5e1287 is described below

commit c8aff5e12871048072c5e25bb5c17249f5cfede8
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Mon Aug 31 17:51:59 2026 -0400

    [v3-3-test] UI: Show duration chart tooltips in the selected timezone 
(#72024) (#72339)
    
    The Last N Dag Runs / Task Instances chart formatted its bar labels in the
    browser's local timezone while the x-axis ticks and the rest of the UI 
follow
    the timezone picked in the header. Anyone whose machine sits in a different
    timezone read a tooltip timestamp that did not identify the run they were
    pointing at, which makes the chart misleading exactly when it is used to
    compare runs.
    (cherry picked from commit f7a2f4e6b0de898f97768313583cddf0cf1c61ef)
    
    
    Related: #72018
    
    Co-authored-by: rjgoyln <[email protected]>
---
 .../ui/src/components/DurationChart.test.tsx       | 65 ++++++++++++++++++++++
 .../airflow/ui/src/components/DurationChart.tsx    |  5 +-
 2 files changed, 66 insertions(+), 4 deletions(-)

diff --git a/airflow-core/src/airflow/ui/src/components/DurationChart.test.tsx 
b/airflow-core/src/airflow/ui/src/components/DurationChart.test.tsx
new file mode 100644
index 00000000000..0eea99c0535
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/components/DurationChart.test.tsx
@@ -0,0 +1,65 @@
+/*!
+ * 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 { render } from "@testing-library/react";
+import { Bar } from "react-chartjs-2";
+import { describe, expect, it, vi } from "vitest";
+
+import type { GridRunsResponse } from "openapi/requests/types.gen";
+import { TimezoneContext } from "src/context/timezone";
+import { Wrapper } from "src/utils/Wrapper";
+
+import { DurationChart } from "./DurationChart";
+
+const makeRun = (runAfter: string): GridRunsResponse => ({
+  dag_id: "tutorial_dag",
+  duration: 60,
+  end_date: `${runAfter.slice(0, -1)}:01:00Z`,
+  has_missed_deadline: false,
+  has_note: false,
+  queued_at: runAfter,
+  run_after: runAfter,
+  run_id: runAfter,
+  run_type: "scheduled",
+  start_date: runAfter,
+  state: "success",
+});
+
+// A Dag scheduled twice a day: the two runs land 12 hours apart on the same 
date.
+const entries = [makeRun("2026-08-20T08:30:00Z"), 
makeRun("2026-08-20T20:30:00Z")];
+
+const renderChart = (selectedTimezone: string) => {
+  render(
+    <TimezoneContext.Provider value={{ selectedTimezone, setSelectedTimezone: 
vi.fn() }}>
+      <DurationChart entries={entries} kind="Dag Run" />
+    </TimezoneContext.Provider>,
+    { wrapper: Wrapper },
+  );
+
+  return vi.mocked(Bar).mock.calls.at(-1)?.[0].data.labels;
+};
+
+describe("DurationChart", () => {
+  it.each([
+    { expected: ["2026-08-20 08:30:00", "2026-08-20 20:30:00"], timezone: 
"UTC" },
+    { expected: ["2026-08-20 17:30:00", "2026-08-21 05:30:00"], timezone: 
"Asia/Tokyo" },
+    { expected: ["2026-08-20 04:30:00", "2026-08-20 16:30:00"], timezone: 
"America/New_York" },
+  ])("labels each bar in the $timezone timezone", ({ expected, timezone }) => {
+    expect(renderChart(timezone)).toEqual(expected);
+  });
+});
diff --git a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx 
b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx
index b558249ff1d..2153e4cbfe9 100644
--- a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx
+++ b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx
@@ -38,7 +38,6 @@ import type { TaskInstanceResponse, GridRunsResponse } from 
"openapi/requests/ty
 import { useTimezone } from "src/context/timezone";
 import { getComputedCSSVariableValue } from "src/theme";
 import {
-  DEFAULT_DATETIME_FORMAT,
   formatDate,
   getDurationTickStep,
   renderCompactDuration,
@@ -195,9 +194,7 @@ export const DurationChart = ({
                 label: translate("durationChart.runDuration"),
               },
             ],
-            labels: entries.map((entry: RunResponse) =>
-              dayjs(entry.run_after).format(DEFAULT_DATETIME_FORMAT),
-            ),
+            labels: entries.map((entry: RunResponse) => 
formatDate(entry.run_after, selectedTimezone)),
           }}
           datasetIdKey="id"
           options={{

Reply via email to