From 79d89dbce1e6548c7ff79b143d8a1128cb36edff Mon Sep 17 00:00:00 2001 From: Pierre Jeambrun Date: Fri, 21 Nov 2025 15:54:40 +0100 Subject: [PATCH] [v3-1-test] Fix duration chart duration format (#58561) (cherry picked from commit 2512aa40e0372374de2a9493141f24c4566cb8b9) Co-authored-by: Pierre Jeambrun --- .../ui/src/components/DurationChart.tsx | 24 ++++++++++++++++--- .../src/airflow/ui/src/utils/datetimeUtils.ts | 7 ++++-- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx index c0fd7cf796b97..5699dcc8c8d6b 100644 --- a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx +++ b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx @@ -36,7 +36,7 @@ import { useNavigate } from "react-router-dom"; import type { TaskInstanceResponse, GridRunsResponse } from "openapi/requests/types.gen"; import { getComputedCSSVariableValue } from "src/theme"; -import { DEFAULT_DATETIME_FORMAT } from "src/utils/datetimeUtils"; +import { DEFAULT_DATETIME_FORMAT, renderDuration } from "src/utils/datetimeUtils"; ChartJS.register( CategoryScale, @@ -94,7 +94,7 @@ export const DurationChart = ({ borderColor: "grey", borderWidth: 1, label: { - content: (ctx: PartialEventContext) => average(ctx, 1).toFixed(2), + content: (ctx: PartialEventContext) => renderDuration(average(ctx, 1), false) ?? "0", display: true, position: "end", }, @@ -106,7 +106,7 @@ export const DurationChart = ({ borderColor: "grey", borderWidth: 1, label: { - content: (ctx: PartialEventContext) => average(ctx, 0).toFixed(2), + content: (ctx: PartialEventContext) => renderDuration(average(ctx, 0), false) ?? "0", display: true, position: "end", }, @@ -200,6 +200,17 @@ export const DurationChart = ({ runAnnotation, }, }, + tooltip: { + callbacks: { + label: (context) => { + const datasetLabel = context.dataset.label ?? ""; + + const formatted = renderDuration(context.parsed.y, false) ?? "0"; + + return datasetLabel ? `${datasetLabel}: ${formatted}` : formatted; + }, + }, + }, }, responsive: true, scales: { @@ -211,6 +222,13 @@ export const DurationChart = ({ title: { align: "end", display: true, text: translate("common:dagRun.runAfter") }, }, y: { + ticks: { + callback: (value) => { + const num = typeof value === "number" ? value : Number(value); + + return renderDuration(num, false) ?? "0"; + }, + }, title: { align: "end", display: true, text: translate("common:duration") }, }, }, diff --git a/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts b/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts index abd598e3debce..8c2f28c954180 100644 --- a/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts +++ b/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts @@ -26,13 +26,16 @@ dayjs.extend(tz); export const DEFAULT_DATETIME_FORMAT = "YYYY-MM-DD HH:mm:ss"; export const DEFAULT_DATETIME_FORMAT_WITH_TZ = `${DEFAULT_DATETIME_FORMAT} z`; -export const renderDuration = (durationSeconds: number | null | undefined): string | undefined => { +export const renderDuration = ( + durationSeconds: number | null | undefined, + withMilliseconds: boolean = true, +): string | undefined => { if (durationSeconds === null || durationSeconds === undefined || durationSeconds <= 0.01) { return undefined; } // If under 60 seconds, render milliseconds - if (durationSeconds < 60) { + if (durationSeconds < 60 && withMilliseconds) { return dayjs.duration(Number(durationSeconds.toFixed(3)), "seconds").format("HH:mm:ss.SSS"); }