From 830f07bb332c254a739e0d1f80c7a913d9835297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=96=86=E5=AE=B8?= Date: Sat, 10 May 2025 03:18:33 +0800 Subject: [PATCH 1/5] UI: implement navigation on bar click --- .../ui/src/components/DurationChart.tsx | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx index 6a4e86ff1bd5e..e83cf18a38fee 100644 --- a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx +++ b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx @@ -26,11 +26,13 @@ import { BarElement, Filler, Tooltip, + type ChartEvent, } from "chart.js"; import type { PartialEventContext } from "chartjs-plugin-annotation"; import annotationPlugin from "chartjs-plugin-annotation"; import dayjs from "dayjs"; import { Bar } from "react-chartjs-2"; +import { useNavigate } from "react-router-dom"; import type { TaskInstanceResponse, DAGRunResponse } from "openapi/requests/types.gen"; import { system } from "src/theme"; @@ -64,6 +66,8 @@ export const DurationChart = ({ readonly entries: Array | undefined; readonly kind: "Dag Run" | "Task Instance"; }) => { + const navigate = useNavigate(); + if (!entries) { return undefined; } @@ -141,6 +145,24 @@ export const DurationChart = ({ }} datasetIdKey="id" options={{ + onClick: (_event, elements) => { + const [element] = elements; + + if (element && typeof element.index === "number") { + const entry = entries[element.index]; + + if (kind === "Dag Run") { + const run = entry as DAGRunResponse; + + navigate(`/dags/${run.dag_id}/runs/${run.dag_run_id}`); + } + } + }, + onHover: (event: ChartEvent, elements) => { + const target = event.native?.target as HTMLElement; + + target.style.cursor = elements.length > 0 ? "pointer" : "default"; + }, plugins: { annotation: { annotations: { @@ -148,6 +170,21 @@ export const DurationChart = ({ runAnnotation, }, }, + tooltip: { + callbacks: { + label: (context) => { + const entry = entries[context.dataIndex]; + + if (!entry) { + return ""; + } + + const duration = Number(context.raw).toFixed(3); + + return `Run duration: ${duration}`; + }, + }, + }, }, responsive: true, scales: { @@ -158,7 +195,6 @@ export const DurationChart = ({ }, title: { align: "end", display: true, text: "Run After" }, }, - y: { title: { align: "end", display: true, text: "Duration (seconds)" }, }, From 84613957609060d92fd44d92c8016b4e6b597e70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=96=86=E5=AE=B8?= Date: Sat, 10 May 2025 03:55:16 +0800 Subject: [PATCH 2/5] use default tooltip --- .../airflow/ui/src/components/DurationChart.tsx | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx index e83cf18a38fee..dfc8febf7a180 100644 --- a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx +++ b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx @@ -170,21 +170,6 @@ export const DurationChart = ({ runAnnotation, }, }, - tooltip: { - callbacks: { - label: (context) => { - const entry = entries[context.dataIndex]; - - if (!entry) { - return ""; - } - - const duration = Number(context.raw).toFixed(3); - - return `Run duration: ${duration}`; - }, - }, - }, }, responsive: true, scales: { From 933ad81784561e0a4210007114266c84ef99210e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=96=86=E5=AE=B8?= Date: Sat, 10 May 2025 22:55:08 +0800 Subject: [PATCH 3/5] fix: remove unnecessary type assertion and add TI overview click-to-navigate function --- .../src/airflow/ui/src/components/DurationChart.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx index dfc8febf7a180..fcc336858fd84 100644 --- a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx +++ b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx @@ -148,14 +148,14 @@ export const DurationChart = ({ onClick: (_event, elements) => { const [element] = elements; - if (element && typeof element.index === "number") { + if (!element) { + return; + } + if (kind === "Dag Run") { const entry = entries[element.index]; + const run = entry as DAGRunResponse; - if (kind === "Dag Run") { - const run = entry as DAGRunResponse; - - navigate(`/dags/${run.dag_id}/runs/${run.dag_run_id}`); - } + navigate(`/dags/${run.dag_id}/runs/${run.dag_run_id}`); } }, onHover: (event: ChartEvent, elements) => { From 2353d217f54d3e027f65d8c53c1a40dd17a2d47b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=96=86=E5=AE=B8?= Date: Sun, 11 May 2025 01:12:50 +0800 Subject: [PATCH 4/5] refactor: optimize hover handling in DurationChart --- .../airflow/ui/src/components/DurationChart.tsx | 14 +++++++++----- 1 file changed, 9 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 fcc336858fd84..83d1eabefa5d3 100644 --- a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx +++ b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx @@ -26,7 +26,6 @@ import { BarElement, Filler, Tooltip, - type ChartEvent, } from "chart.js"; import type { PartialEventContext } from "chartjs-plugin-annotation"; import annotationPlugin from "chartjs-plugin-annotation"; @@ -156,12 +155,17 @@ export const DurationChart = ({ const run = entry as DAGRunResponse; navigate(`/dags/${run.dag_id}/runs/${run.dag_run_id}`); + } else { + const entry = entries[element.index]; + const taskInstance = entry as TaskInstanceResponse; + + navigate( + `/dags/${taskInstance.dag_id}/runs/${taskInstance.dag_run_id}/tasks/${taskInstance.task_id}`, + ); } }, - onHover: (event: ChartEvent, elements) => { - const target = event.native?.target as HTMLElement; - - target.style.cursor = elements.length > 0 ? "pointer" : "default"; + onHover: (_event, elements, chart) => { + chart.canvas.style.cursor = elements.length > 0 ? "pointer" : "default"; }, plugins: { annotation: { From 21f0a792e890b4ea2d0e94cdd29c6e75212eb946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=96=86=E5=AE=B8?= Date: Sun, 11 May 2025 04:28:04 +0800 Subject: [PATCH 5/5] nit:Use switch statement and extract common path --- .../ui/src/components/DurationChart.tsx | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx index 83d1eabefa5d3..a10af96f4174c 100644 --- a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx +++ b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx @@ -150,18 +150,22 @@ export const DurationChart = ({ if (!element) { return; } - if (kind === "Dag Run") { - const entry = entries[element.index]; - const run = entry as DAGRunResponse; - - navigate(`/dags/${run.dag_id}/runs/${run.dag_run_id}`); - } else { - const entry = entries[element.index]; - const taskInstance = entry as TaskInstanceResponse; - - navigate( - `/dags/${taskInstance.dag_id}/runs/${taskInstance.dag_run_id}/tasks/${taskInstance.task_id}`, - ); + + const entry = entries[element.index]; + const baseUrl = `/dags/${entry?.dag_id}/runs/${entry?.dag_run_id}`; + + switch (kind) { + case "Dag Run": { + navigate(baseUrl); + break; + } + case "Task Instance": { + const taskInstance = entry as TaskInstanceResponse; + + navigate(`${baseUrl}/tasks/${taskInstance.task_id}`); + break; + } + default: } }, onHover: (_event, elements, chart) => {