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 ? (