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 ab1629006f Update gantt chart UI to display queued state of tasks
(#28686)
ab1629006f is described below
commit ab1629006fd60a85ba582f715f8f51f63f0fa9f9
Author: NeedMilk! <[email protected]>
AuthorDate: Wed Jan 18 16:58:29 2023 -0800
Update gantt chart UI to display queued state of tasks (#28686)
* Update gantt chart UI to display queued state of tasks
* Fix style issue raise by static check
* Add comments to lines related to drawing boxes in gantt chart
Co-authored-by: eladkal <[email protected]>
---
airflow/www/static/js/gantt.js | 61 ++++++++++++++++++++++++++++-----
airflow/www/static/js/task_instances.js | 24 +++++++++++++
2 files changed, 76 insertions(+), 9 deletions(-)
diff --git a/airflow/www/static/js/gantt.js b/airflow/www/static/js/gantt.js
index be87fb8177..46c709c5cb 100644
--- a/airflow/www/static/js/gantt.js
+++ b/airflow/www/static/js/gantt.js
@@ -32,7 +32,7 @@
/* global d3, document, moment, data $ */
-import tiTooltip from './task_instances';
+import tiTooltip, { taskQueuedStateTooltip } from './task_instances';
import callModal from './callModal';
const replacements = {
@@ -92,11 +92,16 @@ moment.fn.strftime = function (format) {
d3.gantt = () => {
const FIT_TIME_DOMAIN_MODE = 'fit';
- const tip = d3.tip()
+ const executionTip = d3.tip()
.attr('class', 'tooltip d3-tip')
.offset([-10, 0])
.html((d) => tiTooltip(d, null, { includeTryNumber: true }));
+ const queuedStateTip = d3.tip()
+ .attr('class', 'tooltip d3-tip')
+ .offset([-10, 0])
+ .html((d) => taskQueuedStateTooltip(d));
+
let margin = {
top: 20,
right: 40,
@@ -115,6 +120,7 @@ d3.gantt = () => {
let tickFormat = '%H:%M';
const keyFunction = (d) => d.start_date + d.task_id + d.end_date;
+ const filterTaskWithValidQueuedDttm = (tasks) => tasks.filter((d) =>
!!d.queued_dttm);
let x = d3
.time
@@ -130,6 +136,7 @@ d3.gantt = () => {
.rangeRoundBands([0, height - margin.top - margin.bottom], 0.1);
const rectTransform = (d) => `translate(${x(d.start_date.valueOf()) +
yAxisLeftOffset},${y(d.task_id)})`;
+ const queuedRectTransform = (d) => `translate(${x(d.queued_dttm.valueOf()) +
yAxisLeftOffset},${y(d.task_id)})`;
// We can't use d3.time.format as that uses local time, so instead we use
// moment as that handles our "global" timezone.
@@ -157,9 +164,17 @@ d3.gantt = () => {
if (!(a.end_date instanceof moment)) {
a.end_date = moment(a.end_date);
}
+ if (a.queued_dttm && !(a.queued_dttm instanceof moment)) {
+ a.queued_dttm = moment(a.queued_dttm);
+ }
});
timeDomainEnd = moment.max(tasks.map((a) => a.end_date)).valueOf();
- timeDomainStart = moment.min(tasks.map((a) => a.start_date)).valueOf();
+ timeDomainStart = moment.min(tasks.map((a) => {
+ if (a.queued_dttm) {
+ return moment.min([a.queued_dttm, a.start_date]);
+ }
+ return a.start_date;
+ })).valueOf();
}
};
@@ -198,11 +213,12 @@ d3.gantt = () => {
.attr('height', height + margin.top + margin.bottom)
.attr('transform', `translate(${margin.left}, ${margin.top})`);
+ // Draw all task instances with their corresponding states as boxes in
gantt chart.
svg.selectAll('.chart')
.data(tasks, keyFunction).enter()
.append('rect')
- .on('mouseover', tip.show)
- .on('mouseout', tip.hide)
+ .on('mouseover', executionTip.show)
+ .on('mouseout', executionTip.hide)
.on('click', (d) => {
callModal({
taskId: d.task_id,
@@ -212,12 +228,24 @@ d3.gantt = () => {
mapIndex: d.map_index,
});
})
- .attr('class', (d) => d.state || 'null')
+ .attr('class', (d) => `${d.state || 'null'} all-tasks`)
.attr('y', 0)
.attr('transform', rectTransform)
.attr('height', () => y.rangeBand())
.attr('width', (d) => d3.max([x(d.end_date.valueOf()) -
x(d.start_date.valueOf()), 1]));
+ // Draw queued states of task instances with valid queued date time as
boxes in gantt chart.
+ svg.selectAll('.chart')
+ .data(filterTaskWithValidQueuedDttm(tasks), keyFunction).enter()
+ .append('rect')
+ .on('mouseover', queuedStateTip.show)
+ .on('mouseout', queuedStateTip.hide)
+ .attr('class', 'queued tasks-with-queued-dttm')
+ .attr('y', 0)
+ .attr('transform', queuedRectTransform)
+ .attr('height', () => y.rangeBand())
+ .attr('width', (d) => d3.max([x(d.start_date.valueOf()) -
x(d.queued_dttm.valueOf()), 1]));
+
svg.append('g')
.attr('class', 'x axis')
.attr('transform', `translate(${yAxisLeftOffset}, ${height - margin.top
- margin.bottom})`)
@@ -226,7 +254,8 @@ d3.gantt = () => {
svg.append('g').attr('class', 'y axis').transition().attr('transform',
`translate(${yAxisLeftOffset}, 0)`)
.call(yAxis);
- svg.call(tip);
+ svg.call(executionTip);
+ svg.call(queuedStateTip);
return gantt;
}
@@ -238,8 +267,8 @@ d3.gantt = () => {
const svg = d3.select('.chart');
const ganttChartGroup = svg.select('.gantt-chart');
- const rect = ganttChartGroup.selectAll('rect').data(tasks, keyFunction);
-
+ const rect = ganttChartGroup.selectAll('.all-tasks').data(tasks,
keyFunction);
+ // Redraw all task instances with their corresponding states as boxes in
gantt chart.
rect.enter()
.insert('rect', ':first-child')
.attr('rx', 5)
@@ -251,7 +280,21 @@ d3.gantt = () => {
.attr('height', () => y.rangeBand())
.attr('width', (d) => d3.max([x(d.end_date.valueOf()) -
x(d.start_date.valueOf()), 1]));
+ const queuedStateRect =
ganttChartGroup.selectAll('.tasks-with-queued-dttm').data(filterTaskWithValidQueuedDttm(tasks),
keyFunction);
+ // Redraw queued states of task instances with valid queued date time as
boxes in gantt chart.
+ queuedStateRect.enter()
+ .insert('rect', ':first-child')
+ .attr('rx', 5)
+ .attr('ry', 5)
+ .attr('class', 'queued')
+ .transition()
+ .attr('y', 0)
+ .attr('transform', queuedRectTransform)
+ .attr('height', () => y.rangeBand())
+ .attr('width', (d) => d3.max([x(d.start_date.valueOf()) -
x(d.queued_dttm.valueOf()), 1]));
+
rect.exit().remove();
+ queuedStateRect.exit().remove();
svg.select('.x').transition().call(xAxis);
svg.select('.y').transition().call(yAxis);
diff --git a/airflow/www/static/js/task_instances.js
b/airflow/www/static/js/task_instances.js
index f91c383143..6258a345bd 100644
--- a/airflow/www/static/js/task_instances.js
+++ b/airflow/www/static/js/task_instances.js
@@ -141,5 +141,29 @@ export function taskNoInstanceTooltip(taskId, task) {
return tt;
}
+export function taskQueuedStateTooltip(ti) {
+ let tt = '';
+ tt += '<strong>Status:</strong> Queued<br><br>';
+ if (ti.task_id) {
+ tt += `Task_id: ${escapeHtml(ti.task_id)}<br>`;
+ }
+ tt += `Run: ${formatDateTime(ti.execution_date)}<br>`;
+ if (ti.run_id !== undefined) {
+ tt += `Run Id: <nobr>${escapeHtml(ti.run_id)}</nobr><br>`;
+ }
+ if (ti.operator !== undefined) {
+ tt += `Operator: ${escapeHtml(ti.operator)}<br>`;
+ }
+ if (ti.start_date && ti.queued_dttm) {
+ const startDate = ti.start_date instanceof moment ? ti.start_date :
moment(ti.start_date);
+ const queuedDate = ti.queued_dttm instanceof moment ? ti.queued_dttm :
moment(ti.queued_dttm);
+ const duration = startDate.diff(queuedDate, 'second', true); // Set the
floating point result flag to true.
+ tt += `Duration: ${escapeHtml(convertSecsToHumanReadable(duration))}<br>`;
+ // dagTZ has been defined in dag.html
+ tt += generateTooltipDateTimes(ti.queued_dttm, ti.start_date, dagTZ ||
'UTC');
+ }
+ return tt;
+}
+
window.tiTooltip = tiTooltip;
window.taskNoInstanceTooltip = taskNoInstanceTooltip;