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;

Reply via email to