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

bbovenzi 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 98f129f0cd4 fix(ui): Improve DurationChart labels and disable 
animation during auto-refresh (#62835)
98f129f0cd4 is described below

commit 98f129f0cd43838c87dbe30d0f49a8c2b57513a8
Author: Antonio Mello <[email protected]>
AuthorDate: Wed Mar 11 14:42:00 2026 -0300

    fix(ui): Improve DurationChart labels and disable animation during 
auto-refresh (#62835)
    
    * fix(ui): improve DurationChart labels and disable animation during 
auto-refresh
    
    - Shorten x-axis tick labels based on the time span of displayed entries:
      show "HH:mm:ss" when all runs are within 24 hours, or "MMM DD HH:mm"
      for longer ranges. Full datetime remains in tooltips.
    - Use formatDate with the user's selected timezone for correct label
      rendering across all timezone configurations.
    - Disable chart animation during auto-refresh to reduce visual noise
      when data updates automatically.
    
    Closes: #54786
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * fix(ui): apply formatting fixes for ts-compile-lint-ui CI hook
    
    Import ordering and line-length adjustments applied by eslint/prettier.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    ---------
    
    Co-authored-by: Claude Opus 4.6 <[email protected]>
---
 .../airflow/ui/src/components/DurationChart.tsx    | 26 +++++++++++++++++++++-
 .../airflow/ui/src/pages/Dag/Overview/Overview.tsx | 10 ++++++++-
 2 files changed, 34 insertions(+), 2 deletions(-)

diff --git a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx 
b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx
index 186ebb5f6f1..8008307fabf 100644
--- a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx
+++ b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx
@@ -35,8 +35,9 @@ import { useTranslation } from "react-i18next";
 import { useNavigate } from "react-router-dom";
 
 import type { TaskInstanceResponse, GridRunsResponse } from 
"openapi/requests/types.gen";
+import { useTimezone } from "src/context/timezone";
 import { getComputedCSSVariableValue } from "src/theme";
-import { DEFAULT_DATETIME_FORMAT, renderDuration } from 
"src/utils/datetimeUtils";
+import { DEFAULT_DATETIME_FORMAT, formatDate, renderDuration } from 
"src/utils/datetimeUtils";
 import { buildTaskInstanceUrl } from "src/utils/links";
 
 ChartJS.register(
@@ -69,15 +70,35 @@ const getDuration = (start: string, end: string | null) => {
   return dayjs.duration(endDate.diff(startDate)).asSeconds();
 };
 
+const getTickLabelFormat = (entries: Array<RunResponse>): string => {
+  if (entries.length < 2) {
+    return "HH:mm:ss";
+  }
+
+  const first = dayjs(entries[0]?.run_after);
+  const last = dayjs(entries[entries.length - 1]?.run_after);
+
+  if (!first.isValid() || !last.isValid()) {
+    return "MMM DD";
+  }
+
+  const diffInDays = Math.abs(last.diff(first, "day"));
+
+  return diffInDays < 1 ? "HH:mm:ss" : "MMM DD HH:mm";
+};
+
 export const DurationChart = ({
   entries,
+  isAutoRefreshing = false,
   kind,
 }: {
   readonly entries: Array<RunResponse> | undefined;
+  readonly isAutoRefreshing?: boolean;
   readonly kind: "Dag Run" | "Task Instance";
 }) => {
   const { t: translate } = useTranslation(["components", "common"]);
   const navigate = useNavigate();
+  const { selectedTimezone } = useTimezone();
   const [queuedColorToken] = useToken("colors", ["queued.solid"]);
 
   // Get states and create color tokens for them
@@ -175,6 +196,7 @@ export const DurationChart = ({
         }}
         datasetIdKey="id"
         options={{
+          animation: isAutoRefreshing ? false : undefined,
           onClick: (_event, elements) => {
             const [element] = elements;
 
@@ -239,6 +261,8 @@ export const DurationChart = ({
             x: {
               stacked: true,
               ticks: {
+                callback: (_value, index) =>
+                  formatDate(entries[index]?.run_after, selectedTimezone, 
getTickLabelFormat(entries)),
                 maxTicksLimit: 3,
               },
               title: { align: "end", display: true, text: 
translate("common:dagRun.runAfter") },
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx 
b/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx
index 043ae4d1b15..7ea155fe847 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx
@@ -36,6 +36,7 @@ import { TrendCountButton } from 
"src/components/TrendCountButton";
 import { dagRunsLimitKey } from "src/constants/localStorage";
 import { SearchParamsKeys } from "src/constants/searchParams";
 import { useGridRuns } from "src/queries/useGridRuns.ts";
+import { isStatePending, useAutoRefresh } from "src/utils";
 
 const FailedLogs = lazy(() => import("./FailedLogs"));
 
@@ -68,6 +69,9 @@ export const Overview = () => {
     state: ["failed"],
   });
   const { data: gridRuns, isLoading: isLoadingRuns } = useGridRuns({ limit });
+  const refetchInterval = useAutoRefresh({ dagId });
+  const isAutoRefreshing =
+    Boolean(refetchInterval) && (gridRuns ?? []).some((run) => 
isStatePending(run.state));
   const { data: assetEventsData, isLoading: isLoadingAssetEvents } = 
useAssetServiceGetAssetEvents({
     limit,
     orderBy: [assetSortBy],
@@ -125,7 +129,11 @@ export const Overview = () => {
           {isLoadingRuns ? (
             <Skeleton height="200px" w="full" />
           ) : (
-            <DurationChart entries={gridRuns?.slice().reverse()} kind="Dag 
Run" />
+            <DurationChart
+              entries={gridRuns?.slice().reverse()}
+              isAutoRefreshing={isAutoRefreshing}
+              kind="Dag Run"
+            />
           )}
         </Box>
         {assetEventsData && assetEventsData.total_entries > 0 ? (

Reply via email to