Skip to content

Commit

Permalink
Make Gantt tooltip the same as Tree and Graph view (apache#8220)
Browse files Browse the repository at this point in the history
To make this work I needed to change the fields exposed via the json
representation to those expected via the `tiTooltip` function.

Closes apache#8210
  • Loading branch information
ashb committed Apr 9, 2020
1 parent 0ccd5b9 commit 08c9b0a
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 80 deletions.
13 changes: 1 addition & 12 deletions airflow/www/static/css/gantt.css
Expand Up @@ -28,26 +28,15 @@ rect {
stroke: #000;
cursor: pointer;
}

.d3-tip {
line-height: 1;
font-weight: bold;
padding: 5px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 2px;
font-size: 12px;
}

/* Creates a small triangle extender for the tooltip */
.d3-tip::after {
box-sizing: border-box;
display: inline;
font-size: 8px;
width: 100%;
line-height: 1;
color: rgba(0, 0, 0, 0.8);
content: "\25BC";
pointer-events: none;
position: absolute;
text-align: center;
}
Expand Down
56 changes: 18 additions & 38 deletions airflow/www/static/js/gantt-chart-d3v2.js
Expand Up @@ -90,21 +90,9 @@ d3.gantt = function() {
var FIT_TIME_DOMAIN_MODE = "fit";
var FIXED_TIME_DOMAIN_MODE = "fixed";
var tip = d3.tip()
.attr('class', 'd3-tip')
.attr('class', 'tooltip d3-tip')
.offset([-10, 0])
.html(function(d) {
var s = ""
s += "<div class='row'>";
s += "<div class='col-md-3'>start:<br/>end:<br/>duration:<br/>try_number:</div>"
s += "<div class='col-md-9'><span style='color: #AAA'> "
s += d.isoStart + "<br/>";
s += d.isoEnd + "<br/>";
s += convertSecsToHumanReadable(d.duration) + "<br/>";
s += d.try_number + "<br/>";
s += "</span></div>";
s += "</div>";
return s;
})
.html(d => tiTooltip(d, { includeTryNumber: true }));

var margin = {
top : 20,
Expand All @@ -118,18 +106,17 @@ d3.gantt = function() {
var timeDomainEnd = d3.time.hour.offset(new Date(),+3);
var timeDomainMode = FIT_TIME_DOMAIN_MODE;// fixed or fit
var taskTypes = [];
var taskStatus = [];
var height = document.body.clientHeight - margin.top - margin.bottom-5;
var width = $('.gantt').width() - margin.right - margin.left-5;

var tickFormat = "%H:%M";

var keyFunction = function(d) {
return d.startDate + d.taskName + d.endDate;
return d.start_date + d.task_id + d.end_date;
};

var rectTransform = function(d) {
return "translate(" + (x(d.startDate) + yAxisLeftOffset) + "," + y(d.taskName) + ")";
return "translate(" + (x(d.start_date.valueOf()) + yAxisLeftOffset) + "," + y(d.task_id) + ")";
};

function tickFormater(d) {
Expand All @@ -154,14 +141,16 @@ d3.gantt = function() {
timeDomainEnd = d3.time.hour.offset(new Date(), +3);
return;
}
tasks.sort(function(a, b) {
return a.endDate - b.endDate;
tasks.forEach((a) => {
if (!(a.start_date instanceof moment)) {
a.start_date = moment(a.start_date);
}
if (!(a.end_date instanceof moment)) {
a.end_date = moment(a.end_date);
}
});
timeDomainEnd = tasks[tasks.length - 1].endDate;
tasks.sort(function(a, b) {
return a.startDate - b.startDate;
});
timeDomainStart = tasks[0].startDate;
timeDomainEnd = moment.max(tasks.map((a) => a.end_date)).valueOf()
timeDomainStart = moment.min(tasks.map((a) => a.start_date)).valueOf()
}
};

Expand Down Expand Up @@ -196,17 +185,16 @@ d3.gantt = function() {
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
.on('click', function(d) {
call_modal(d.taskName, d.executionDate, d.extraLinks);
call_modal(d.task_id, d.execution_date, d.extraLinks);
})
.attr("class", function(d){
if(taskStatus[d.status] == null){ return "null";}
return taskStatus[d.status];
return d.state || "null";
})
.attr("y", 0)
.attr("transform", rectTransform)
.attr("height", function(d) { return y.rangeBand(); })
.attr("width", function(d) {
return d3.max([x(d.endDate) - x(d.startDate), 1]);
return d3.max([x(d.end_date.valueOf()) - x(d.start_date.valueOf()), 1]);
});


Expand Down Expand Up @@ -238,15 +226,14 @@ d3.gantt = function() {
.attr("rx", 5)
.attr("ry", 5)
.attr("class", function(d){
if(taskStatus[d.status] == null){ return "bar";}
return taskStatus[d.status];
return d.state || "null";
})
.transition()
.attr("y", 0)
.attr("transform", rectTransform)
.attr("height", function(d) { return y.rangeBand(); })
.attr("width", function(d) {
return d3.max([x(d.endDate) - x(d.startDate), 1]);
return d3.max([x(d.end_date.valueOf()) - x(d.start_date.valueOf()), 1]);
});


Expand Down Expand Up @@ -292,13 +279,6 @@ d3.gantt = function() {
return gantt;
};

gantt.taskStatus = function(value) {
if (!arguments.length)
return taskStatus;
taskStatus = value;
return gantt;
};

gantt.width = function(value) {
if (!arguments.length)
return width;
Expand Down
18 changes: 13 additions & 5 deletions airflow/www/static/js/task-instances.js
Expand Up @@ -60,21 +60,29 @@ function generateTooltipDateTimes(startDate, endDate, dagTZ) {
return tooltipHTML;
}

export default function tiTooltip(ti) {
export default function tiTooltip(ti, {includeTryNumber = false} = {}) {
let tt = '';
if(ti.task_id !== undefined) {
if (ti.task_id !== undefined) {
tt += `Task_id: ${escapeHtml(ti.task_id)}<br>`;
}
tt += `Run: ${formatDateTime(ti.execution_date)}<br>`;
if(ti.run_id !== undefined) {
if (ti.run_id !== undefined) {
tt += `Run Id: <nobr>${escapeHtml(ti.run_id)}</nobr><br>`;
}
if(ti.operator !== undefined) {
if (ti.operator !== undefined) {
tt += `Operator: ${escapeHtml(ti.operator)}<br>`;
}
// Don't translate/format this, keep it as the full ISO8601 date
tt += `Started: ${escapeHtml(ti.start_date)}<br>`;
if (ti.start_date instanceof moment) {
tt += `Started: ${escapeHtml(ti.start_date.toISOString())}<br>`;
} else {
tt += `Started: ${escapeHtml(ti.start_date)}<br>`;
}
tt += `Duration: ${escapeHtml(convertSecsToHumanReadable(ti.duration))}<br>`;

if (includeTryNumber) {
tt += `Try Number: ${escapeHtml(ti.try_number)}<br>`;
}
tt += generateTooltipDateTimes(ti.start_date, ti.end_date, dagTZ); // dagTZ has been defined in dag.html
return tt;
}
Expand Down
2 changes: 1 addition & 1 deletion airflow/www/templates/airflow/gantt.html
Expand Up @@ -51,6 +51,7 @@
<script src="{{ url_for_asset('d3.min.js') }}"></script>
<script src="{{ url_for_asset('d3-tip.js') }}"></script>/
<script src="{{ url_for_asset('ganttChartD3v2.js') }}"></script>
<script src="{{ url_for_asset('task-instances.js') }}"></script>
<script>
$( document ).ready(function() {
var dag_id = '{{ dag.dag_id }}';
Expand All @@ -59,7 +60,6 @@
data = {{ data |tojson|safe }};
var gantt = d3.gantt()
.taskTypes(data.taskNames)
.taskStatus(data.taskStatus)
.height(data.height)
.selector('.gantt')
.tickFormat("%H:%M:%S");
Expand Down
32 changes: 8 additions & 24 deletions airflow/www/views.py
Expand Up @@ -1952,13 +1952,16 @@ def gantt(self, session=None):

# determine bars to show in the gantt chart
gantt_bar_items = []

tasks = []
for ti in tis:
end_date = ti.end_date or timezone.utcnow()
# prev_attempted_tries will reflect the currently running try_number
# or the try_number of the last complete run
# https://issues.apache.org/jira/browse/AIRFLOW-2143
try_count = ti.prev_attempted_tries
gantt_bar_items.append((ti.task_id, ti.start_date, end_date, ti.state, try_count))
tasks.append(alchemy_to_dict(ti))

tf_count = 0
try_count = 1
Expand All @@ -1973,40 +1976,21 @@ def gantt(self, session=None):
prev_task_id = tf.task_id
gantt_bar_items.append((tf.task_id, start_date, end_date, State.FAILED, try_count))
tf_count = tf_count + 1
d = alchemy_to_dict(tf)
d['state'] = State.FAILED
d['operator'] = dag.get_task(tf.task_id).task_type
d['try_number'] = try_count
tasks.append(d)

task_types = {}
extra_links = {}
for t in dag.tasks:
task_types[t.task_id] = t.task_type
extra_links[t.task_id] = t.extra_links

tasks = []
for gantt_bar_item in gantt_bar_items:
task_id = gantt_bar_item[0]
start_date = gantt_bar_item[1]
end_date = gantt_bar_item[2]
state = gantt_bar_item[3]
try_count = gantt_bar_item[4]
tasks.append({
'startDate': wwwutils.epoch(start_date),
'endDate': wwwutils.epoch(end_date),
'isoStart': start_date.isoformat()[:-4],
'isoEnd': end_date.isoformat()[:-4],
'taskName': task_id,
'taskType': task_types[ti.task_id],
'duration': (end_date - start_date).total_seconds(),
'status': state,
'executionDate': dttm.isoformat(),
'try_number': try_count,
'extraLinks': extra_links[ti.task_id],
})

states = {task['status']: task['status'] for task in tasks}

data = {
'taskNames': [ti.task_id for ti in tis],
'tasks': tasks,
'taskStatus': states,
'height': len(tis) * 25 + 25,
}

Expand Down

0 comments on commit 08c9b0a

Please sign in to comment.