You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by ry...@apache.org on 2021/03/02 19:06:48 UTC
[airflow] branch master updated: remove inline tree js (#14552)
This is an automated email from the ASF dual-hosted git repository.
ryanahamilton pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/master by this push:
new 8801a0c remove inline tree js (#14552)
8801a0c is described below
commit 8801a0cc3b39cf3d2a3e5ef6af004d763bdb0b93
Author: Brent Bovenzi <br...@gmail.com>
AuthorDate: Tue Mar 2 13:06:31 2021 -0600
remove inline tree js (#14552)
---
airflow/www/static/js/tree.js | 390 ++++++++++++++++++++++++++++++++
airflow/www/templates/airflow/tree.html | 377 +-----------------------------
airflow/www/webpack.config.js | 2 +-
3 files changed, 398 insertions(+), 371 deletions(-)
diff --git a/airflow/www/static/js/tree.js b/airflow/www/static/js/tree.js
new file mode 100644
index 0000000..efc69ae
--- /dev/null
+++ b/airflow/www/static/js/tree.js
@@ -0,0 +1,390 @@
+/* eslint-disable func-names */
+/* eslint-disable no-underscore-dangle */
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/* global treeData, document, window, $, d3, moment, call_modal_dag, call_modal, */
+import { escapeHtml } from './main';
+import tiTooltip from './task_instances';
+import getMetaValue from './meta_value';
+
+function toDateString(ts) {
+ const dt = new Date(ts * 1000);
+ return dt.toISOString();
+}
+
+function isDagRun(d) {
+ return d.run_id !== undefined;
+}
+
+function nodeClass(d) {
+ let sclass = 'node';
+ if (d.children === undefined && d._children === undefined) sclass += ' leaf';
+ else {
+ sclass += ' parent';
+ if (d.children === undefined) sclass += ' collapsed';
+ else sclass += ' expanded';
+ }
+ return sclass;
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ $('span.status_square').tooltip({ html: true });
+
+ const now = Date.now() / 1000;
+ const devicePixelRatio = window.devicePixelRatio || 1;
+ // JSON.parse is faster for large payloads than an object literal
+ const data = JSON.parse(treeData);
+ const barHeight = 20;
+ const axisHeight = 50;
+ const squareX = parseInt(500 * devicePixelRatio, 10);
+ const squareSize = 10;
+ const squareSpacing = 2;
+ const margin = {
+ top: barHeight / 2 + axisHeight, right: 0, bottom: 0, left: barHeight / 2,
+ };
+ const width = parseInt(960 * devicePixelRatio, 10) - margin.left - margin.right;
+ const barWidth = width * 0.9;
+
+ let i = 0;
+ const duration = 400;
+ let root;
+
+ const tree = d3.layout.tree().nodeSize([0, 25]);
+ const nodes = tree.nodes(data);
+ const nodeobj = {};
+
+ function populateTaskInstanceProperties(node) {
+ // populate task instance properties for display purpose
+ let j;
+ for (j = 0; j < node.instances.length; j += 1) {
+ const dataInstance = data.instances[j];
+ const row = node.instances[j];
+
+ if (row === null) {
+ node.instances[j] = {
+ task_id: node.name,
+ execution_date: dataInstance.execution_date,
+ };
+ continue;
+ }
+
+ const taskInstance = {
+ state: row[0],
+ try_number: row[1],
+ start_ts: row[2],
+ duration: row[3],
+ };
+ node.instances[j] = taskInstance;
+
+ taskInstance.task_id = node.name;
+ taskInstance.operator = node.operator;
+ taskInstance.execution_date = dataInstance.execution_date;
+ taskInstance.external_trigger = dataInstance.external_trigger;
+
+ // compute start_date and end_date if applicable
+ if (taskInstance.start_ts !== null) {
+ taskInstance.start_date = toDateString(taskInstance.start_ts);
+ if (taskInstance.state === 'running') {
+ taskInstance.duration = now - taskInstance.start_ts;
+ } else if (taskInstance.duration !== null) {
+ taskInstance.end_date = toDateString(taskInstance.start_ts + taskInstance.duration);
+ }
+ }
+ }
+ }
+
+ for (i = 0; i < nodes.length; i += 1) {
+ const node = nodes[i];
+ nodeobj[node.name] = node;
+
+ if (node.name === '[DAG]') {
+ // skip synthetic root node since it's doesn't contain actual task instances
+ continue;
+ }
+
+ if (node.start_ts !== undefined) {
+ node.start_date = toDateString(node.start_ts);
+ }
+ if (node.end_ts !== undefined) {
+ node.end_date = toDateString(node.end_ts);
+ }
+ if (node.depends_on_past === undefined) {
+ node.depends_on_past = false;
+ }
+
+ populateTaskInstanceProperties(node);
+ }
+
+ const diagonal = d3.svg.diagonal()
+ .projection((d) => [d.y, d.x]);
+
+ const taskTip = d3.tip()
+ .attr('class', 'tooltip d3-tip')
+ .html((toolTipHtml) => toolTipHtml);
+
+ const svg = d3.select('#tree-svg')
+ // .attr("width", width + margin.left + margin.right)
+ .append('g')
+ .attr('class', 'level')
+ .attr('transform', `translate(${margin.left},${margin.top})`);
+
+ data.x0 = 0;
+ data.y0 = 0;
+
+ const baseNode = nodes.length === 1 ? nodes[0] : nodes[1];
+ const numSquare = baseNode.instances.length;
+
+ const xScale = d3.scale.linear()
+ .range([
+ squareSize / 2,
+ (numSquare * squareSize) + ((numSquare - 1) * squareSpacing) - (squareSize / 2),
+ ]);
+
+ d3.select('#tree-svg')
+ .insert('g')
+ .attr('transform',
+ `translate(${squareX + margin.left}, ${axisHeight})`)
+ .attr('class', 'axis')
+ .call(
+ d3.svg.axis()
+ .scale(xScale)
+ .orient('top')
+ // show a tick every 5 instances
+ .ticks(Math.floor(numSquare / 5) || 1)
+ .tickFormat((d) => {
+ if (!numSquare || (d > 0 && numSquare < 3)) {
+ // don't render ticks when there are no instances or when the ticks would overlap
+ return '';
+ }
+ const tickIndex = d === 1 ? numSquare - 1 : Math.round(d * numSquare);
+ return moment(baseNode.instances[tickIndex].execution_date).format('MMM DD, HH:mm');
+ }),
+ )
+ .selectAll('text')
+ .attr('transform', 'rotate(-30)')
+ .style('text-anchor', 'start')
+ .call(taskTip);
+
+ function update(source) {
+ // Compute the flattened node list. TODO use d3.layout.hierarchy.
+ const nodes = tree.nodes(root);
+
+ const height = Math.max(500, nodes.length * barHeight + margin.top + margin.bottom);
+ const width = squareX
+ + (numSquare * (squareSize + squareSpacing))
+ + margin.left + margin.right + 50;
+ d3.select('#tree-svg').transition()
+ .duration(duration)
+ .attr('height', height)
+ .attr('width', width);
+
+ d3.select(self.frameElement).transition()
+ .duration(duration)
+ .style('height', `${height}px`);
+
+ // Compute the "layout".
+ nodes.forEach((n, i) => {
+ n.x = i * barHeight;
+ });
+
+ // Update the nodes…
+ const node = svg.selectAll('g.node')
+ .data(nodes, (d) => d.id || (d.id = ++i));
+
+ const nodeEnter = node.enter().append('g')
+ .attr('class', nodeClass)
+ .attr('transform', () => `translate(${source.y0},${source.x0})`)
+ .style('opacity', 1e-6);
+
+ nodeEnter.append('circle')
+ .attr('r', (barHeight / 3))
+ .attr('class', 'task')
+ .attr('data-toggle', 'tooltip')
+ .on('mouseover', function (d) {
+ let tt = '';
+ if (d.operator !== undefined) {
+ if (d.operator !== undefined) {
+ tt += `operator: ${escapeHtml(d.operator)}<br>`;
+ }
+
+ tt += `depends_on_past: ${escapeHtml(d.depends_on_past)}<br>`;
+ tt += `upstream: ${escapeHtml(d.num_dep)}<br>`;
+ tt += `retries: ${escapeHtml(d.retries)}<br>`;
+ tt += `owner: ${escapeHtml(d.owner)}<br>`;
+ tt += `start_date: ${escapeHtml(d.start_date)}<br>`;
+ tt += `end_date: ${escapeHtml(d.end_date)}<br>`;
+ }
+ taskTip.direction('e');
+ taskTip.show(tt, this);
+ d3.select(this).transition()
+ .style('stroke-width', 3);
+ })
+ .on('mouseout', function (d) {
+ taskTip.hide(d);
+ d3.select(this).transition()
+ .style('stroke-width', (d) => (isDagRun(d) ? '2' : '1'));
+ })
+ .attr('height', barHeight)
+ .attr('width', (d) => barWidth - d.y)
+ .style('fill', (d) => d.ui_color)
+ .attr('task_id', (d) => d.name)
+ .on('click', toggles);
+
+ const text = nodeEnter.append('text')
+ .attr('dy', 3.5)
+ .attr('dx', barHeight / 2)
+ .text((d) => d.name);
+
+ const isBlur = getMetaValue('blur');
+ if (isBlur === 'True') text.attr('class', 'blur');
+
+ nodeEnter.append('g')
+ .attr('class', 'stateboxes')
+ .attr('transform',
+ (d) => `translate(${squareX - d.y},0)`)
+ .selectAll('rect')
+ .data((d) => d.instances)
+ .enter()
+ .append('rect')
+ .on('click', (d) => {
+ if (d.task_id === undefined) call_modal_dag(d);
+ else if (nodeobj[d.task_id].operator === 'SubDagOperator') {
+ // I'm pretty sure that true is not a valid subdag id, which is what call_modal wants
+ call_modal(
+ d.task_id,
+ d.execution_date,
+ nodeobj[d.task_id].extra_links,
+ d.try_number,
+ true,
+ );
+ } else {
+ call_modal(
+ d.task_id,
+ d.execution_date,
+ nodeobj[d.task_id].extra_links,
+ d.try_number,
+ undefined,
+ );
+ }
+ })
+ .attr('class', (d) => `state ${d.state}`)
+ .attr('data-toggle', 'tooltip')
+ .attr('rx', (d) => (isDagRun(d) ? '5' : '1'))
+ .attr('ry', (d) => (isDagRun(d) ? '5' : '1'))
+ .style('shape-rendering', (d) => (isDagRun(d) ? 'auto' : 'crispEdges'))
+ .style('stroke-width', (d) => (isDagRun(d) ? '2' : '1'))
+ .style('stroke-opacity', (d) => (d.external_trigger ? '0' : '1'))
+ .on('mouseover', function (d) {
+ const tt = tiTooltip(d);
+ taskTip.direction('n');
+ taskTip.show(tt, this);
+ d3.select(this).transition()
+ .style('stroke-width', 3);
+ })
+ .on('mouseout', function (d) {
+ taskTip.hide(d);
+ d3.select(this).transition()
+ .style('stroke-width', (dd) => (isDagRun(dd) ? '2' : '1'));
+ })
+ .attr('x', (d, j) => (j * (squareSize + squareSpacing)))
+ .attr('y', -squareSize / 2)
+ .attr('width', 10)
+ .attr('height', 10);
+
+ // Transition nodes to their new position.
+ nodeEnter.transition()
+ .duration(duration)
+ .attr('transform', (d) => `translate(${d.y},${d.x})`)
+ .style('opacity', 1);
+
+ node.transition()
+ .duration(duration)
+ .attr('class', nodeClass)
+ .attr('transform', (d) => `translate(${d.y},${d.x})`)
+ .style('opacity', 1);
+
+ // Transition exiting nodes to the parent's new position.
+ node.exit().transition()
+ .duration(duration)
+ .attr('transform', () => `translate(${source.y},${source.x})`)
+ .style('opacity', 1e-6)
+ .remove();
+
+ // Update the links…
+ const link = svg.selectAll('path.link')
+ .data(tree.links(nodes), (d) => d.target.id);
+
+ // Enter any new links at the parent's previous position.
+ link.enter().insert('path', 'g')
+ .attr('class', 'link')
+ .attr('d', () => {
+ const o = { x: source.x0, y: source.y0 };
+ return diagonal({ source: o, target: o });
+ })
+ .transition()
+ .duration(duration)
+ .attr('d', diagonal);
+
+ // Transition links to their new position.
+ link.transition()
+ .duration(duration)
+ .attr('d', diagonal);
+
+ // Transition exiting nodes to the parent's new position.
+ link.exit().transition()
+ .duration(duration)
+ .attr('d', () => {
+ const o = { x: source.x, y: source.y };
+ return diagonal({ source: o, target: o });
+ })
+ .remove();
+
+ // Stash the old positions for transition.
+ nodes.forEach((d) => {
+ d.x0 = d.x;
+ d.y0 = d.y;
+ });
+
+ $('#loading').remove();
+ }
+
+ update(root = data);
+
+ function toggles(clicked) {
+ // Collapse nodes with the same task id
+ d3.selectAll(`[task_id='${clicked.name}']`).each((d) => {
+ if (clicked !== d && d.children) {
+ d._children = d.children;
+ d.children = null;
+ update(d);
+ }
+ });
+
+ // Toggle clicked node
+ if (clicked._children) {
+ clicked.children = clicked._children;
+ clicked._children = null;
+ } else {
+ clicked._children = clicked.children;
+ clicked.children = null;
+ }
+ update(clicked);
+ }
+});
diff --git a/airflow/www/templates/airflow/tree.html b/airflow/www/templates/airflow/tree.html
index bd8a3a7..d6ca845 100644
--- a/airflow/www/templates/airflow/tree.html
+++ b/airflow/www/templates/airflow/tree.html
@@ -20,6 +20,11 @@
{% extends "airflow/dag.html" %}
{% block page_title %}{{ dag.dag_id }} - Tree - {{ appbuilder.app_name }}{% endblock %}
+{% block head_meta %}
+ {{ super() }}
+ <meta name="blur" content="{{ blur }}">
+{% endblock %}
+
{% block head_css %}
{{ super() }}
<link rel="stylesheet" type="text/css" href="{{ url_for_asset('tree.css') }}">
@@ -87,376 +92,8 @@
{{ super() }}
<script src="{{ url_for_asset('d3.min.js') }}"></script>
<script src="{{ url_for_asset('d3-tip.js') }}"></script>
- <script src="{{ url_for_asset('taskInstances.js') }}"></script>
+ <script src="{{ url_for_asset('tree.js') }}"></script>
<script>
- $('span.status_square').tooltip({html: true});
-
- function ts_to_dtstr(ts) {
- var dt = new Date(ts * 1000);
- return dt.toISOString();
- }
-
- function is_dag_run(d) {
- return d.run_id != undefined;
- }
-
- var now_ts = Date.now()/1000;
-
- function populate_taskinstance_properties(node) {
- // populate task instance properties for display purpose
- var j;
- for (j=0; j<node.instances.length; j++) {
- var dr_instance = data.instances[j];
- var row = node.instances[j];
-
- if (row === null) {
- node.instances[j] = {
- task_id: node.name,
- execution_date: dr_instance.execution_date,
- };
- continue;
- }
-
- var task_instance = {
- state: row[0],
- try_number: row[1],
- start_ts: row[2],
- duration: row[3],
- };
- node.instances[j] = task_instance;
-
- task_instance.task_id = node.name;
- task_instance.operator = node.operator;
- task_instance.execution_date = dr_instance.execution_date;
- task_instance.external_trigger = dr_instance.external_trigger;
-
- // compute start_date and end_date if applicable
- if (task_instance.start_ts !== null) {
- task_instance.start_date = ts_to_dtstr(task_instance.start_ts);
- if (task_instance.state === "running") {
- task_instance.duration = now_ts - task_instance.start_ts;
- } else if (task_instance.duration !== null) {
- task_instance.end_date = ts_to_dtstr(task_instance.start_ts + task_instance.duration);
- }
- }
- }
- }
-
- var devicePixelRatio = window.devicePixelRatio || 1;
- // JSON.parse is faster for large payloads than an object literal (because the JSON grammar is simpler!)
- var data = JSON.parse({{ data|tojson }});
- var barHeight = 20;
- var axisHeight = 50;
- var square_x = parseInt(500 * devicePixelRatio);
- var square_size = 10;
- var square_spacing = 2;
- var margin = {top: barHeight/2 + axisHeight, right: 0, bottom: 0, left: barHeight/2},
- width = parseInt(960 * devicePixelRatio) - margin.left - margin.right,
- barWidth = width * 0.9;
-
- var i = 0,
- duration = 400,
- root;
-
- var tree = d3.layout.tree().nodeSize([0, 25]);
- var nodes = tree.nodes(data);
- var nodeobj = {};
- for (i=0; i<nodes.length; i++) {
- var node = nodes[i];
- nodeobj[node.name] = node;
-
- if (node.name === "[DAG]") {
- // skip synthetic root node since it's doesn't contain actual task instances
- continue;
- }
-
- if (node.start_ts !== undefined) {
- node.start_date = ts_to_dtstr(node.start_ts);
- }
- if (node.end_ts !== undefined) {
- node.end_date = ts_to_dtstr(node.end_ts);
- }
- if (node.depends_on_past === undefined) {
- node.depends_on_past = false;
- }
-
- populate_taskinstance_properties(node);
- }
-
- var diagonal = d3.svg.diagonal()
- .projection(function(d) { return [d.y, d.x]; });
-
- const taskTip = d3.tip()
- .attr('class', 'tooltip d3-tip')
- .html(function(toolTipHtml) {
- return toolTipHtml;
- });
-
- var svg = d3.select("#tree-svg")
- //.attr("width", width + margin.left + margin.right)
- .append("g")
- .attr("class", "level")
- .attr("transform", `translate(${margin.left},${margin.top})`);
-
- data.x0 = 0;
- data.y0 = 0;
-
- if (nodes.length == 1)
- var base_node = nodes[0];
- else
- var base_node = nodes[1];
-
- var num_square = base_node.instances.length;
-
- var xScale = d3.scale.linear()
- .range([
- square_size/2,
- (num_square * square_size) + ((num_square-1) * square_spacing) - (square_size/2)
- ]);
-
- d3.select("#tree-svg")
- .insert("g")
- .attr("transform",
- `translate(${square_x + margin.left}, ${axisHeight})`)
- .attr("class", "axis").call(
- d3.svg.axis()
- .scale(xScale)
- .orient("top")
- // show a tick every 5 instances
- .ticks(Math.floor(num_square / 5) || 1)
- .tickFormat((d, i) => {
- if (!num_square || (d > 0 && num_square < 3)) {
- // don't render ticks when there are no instances or when the ticks would overlap
- return '';
- }
- var tickIndex = d === 1 ? num_square - 1 : Math.round(d * num_square);
- return moment(base_node.instances[tickIndex].execution_date).format('MMM DD, HH:mm');
- })
- )
- .selectAll("text")
- .attr("transform", "rotate(-30)")
- .style("text-anchor", "start").call(taskTip);
-
- function node_class(d) {
- var sclass = "node";
- if (d.children === undefined && d._children === undefined)
- sclass += " leaf";
- else {
- sclass += " parent";
- if (d.children === undefined)
- sclass += " collapsed"
- else
- sclass += " expanded"
- }
- return sclass;
- }
-
- update(root = data);
-
- function update(source) {
-
- // Compute the flattened node list. TODO use d3.layout.hierarchy.
- var nodes = tree.nodes(root);
-
- var height = Math.max(500, nodes.length * barHeight + margin.top + margin.bottom);
- var width = square_x + (num_square * (square_size + square_spacing)) + margin.left + margin.right + 50;
- d3.select("#tree-svg").transition()
- .duration(duration)
- .attr("height", height)
- .attr("width", width);
-
- d3.select(self.frameElement).transition()
- .duration(duration)
- .style("height", `${height}px`);
-
- // Compute the "layout".
- nodes.forEach(function(n, i) {
- n.x = i * barHeight;
- });
-
- // Update the nodes…
- var node = svg.selectAll("g.node")
- .data(nodes, function(d) { return d.id || (d.id = ++i); });
-
- var nodeEnter = node.enter().append("g")
- .attr("class", node_class)
- .attr("transform", function() {
- return `translate(${source.y0},${source.x0})`;
- })
- .style("opacity", 1e-6);
-
- nodeEnter.append("circle")
- .attr("r", (barHeight / 3))
- .attr("class", "task")
- .attr("data-toggle", "tooltip")
- .on("mouseover", function(d) {
- var tt = "";
- if (d.operator != undefined) {
- if (d.operator != undefined) {
- tt += `operator: ${escapeHtml(d.operator)}<br>`;
- }
-
- tt += `depends_on_past: ${escapeHtml(d.depends_on_past)}<br>`;
- tt += `upstream: ${escapeHtml(d.num_dep)}<br>`;
- tt += `retries: ${escapeHtml(d.retries)}<br>`;
- tt += `owner: ${escapeHtml(d.owner)}<br>`;
- tt += `start_date: ${escapeHtml(d.start_date)}<br>`;
- tt += `end_date: ${escapeHtml(d.end_date)}<br>`;
- }
- taskTip.direction('e')
- taskTip.show(tt, this)
- d3.select(this).transition()
- .style('stroke-width', 3)
- })
- .on('mouseout', function(d) {
- taskTip.hide(d)
- d3.select(this).transition()
- .style("stroke-width", function(d) {return is_dag_run(d)? "2": "1"})
- })
- .attr("height", barHeight)
- .attr("width", function(d) {return barWidth - d.y;})
- .style("fill", function(d) {return d.ui_color;})
- .attr("task_id", function(d){return d.name})
- .on("click", toggles);
-
- text = nodeEnter.append("text")
- .attr("dy", 3.5)
- .attr("dx", barHeight/2)
- .text(function(d) { return d.name; });
- {% if blur %}
- text.attr("class", "blur");
- {% endif %}
-
- nodeEnter.append('g')
- .attr("class", "stateboxes")
- .attr("transform",
- function(d) { return `translate(${square_x - d.y},0)`; })
- .selectAll("rect").data(function(d) { return d.instances; })
- .enter()
- .append('rect')
- .on("click", function(d){
- if(d.task_id === undefined)
- call_modal_dag(d);
- else if(nodeobj[d.task_id].operator=='SubDagOperator')
- // I'm pretty sure that true is not a valid subdag id, which is what call_modal wants
- call_modal(d.task_id, d.execution_date, nodeobj[d.task_id].extra_links, d.try_number, true);
- else
- call_modal(d.task_id, d.execution_date, nodeobj[d.task_id].extra_links, d.try_number, undefined);
- })
- .attr("class", function(d) {return `state ${d.state}`})
- .attr("data-toggle", "tooltip")
- .attr("rx", function(d) {return is_dag_run(d)? "5": "1"})
- .attr("ry", function(d) {return is_dag_run(d)? "5": "1"})
- .style("shape-rendering", function(d) {return is_dag_run(d)? "auto": "crispEdges"})
- .style("stroke-width", function(d) {return is_dag_run(d)? "2": "1"})
- .style("stroke-opacity", function(d) {return d.external_trigger ? "0": "1"})
- .on("mouseover", function(d){
- var tt = tiTooltip(d);
- taskTip.direction('n');
- taskTip.show(tt, this);
- d3.select(this).transition()
- .style('stroke-width', 3)
- })
- .on('mouseout', function(d) {
- taskTip.hide(d)
- d3.select(this).transition()
- .style("stroke-width", function(d) {return is_dag_run(d)? "2": "1"})
- })
- .attr('x', function(d, i) {return (i*(square_size+square_spacing));})
- .attr('y', -square_size/2)
- .attr('width', 10)
- .attr('height', 10);
-
-
- // Transition nodes to their new position.
- nodeEnter.transition()
- .duration(duration)
- .attr("transform", function(d) { return `translate(${d.y},${d.x})`; })
- .style("opacity", 1);
-
- node.transition()
- .duration(duration)
- .attr("class", node_class)
- .attr("transform", function(d) { return `translate(${d.y},${d.x})`; })
- .style("opacity", 1);
-
- // Transition exiting nodes to the parent's new position.
- node.exit().transition()
- .duration(duration)
- .attr("transform", function() { return `translate(${source.y},${source.x})`; })
- .style("opacity", 1e-6)
- .remove();
-
- // Update the links…
- var link = svg.selectAll("path.link")
- .data(tree.links(nodes), function(d) { return d.target.id; });
-
- // Enter any new links at the parent's previous position.
- link.enter().insert("path", "g")
- .attr("class", "link")
- .attr("d", function() {
- var o = {x: source.x0, y: source.y0};
- return diagonal({source: o, target: o});
- })
- .transition()
- .duration(duration)
- .attr("d", diagonal);
-
- // Transition links to their new position.
- link.transition()
- .duration(duration)
- .attr("d", diagonal);
-
- // Transition exiting nodes to the parent's new position.
- link.exit().transition()
- .duration(duration)
- .attr("d", function() {
- var o = {x: source.x, y: source.y};
- return diagonal({source: o, target: o});
- })
- .remove();
-
- // Stash the old positions for transition.
- nodes.forEach(function(d) {
- d.x0 = d.x;
- d.y0 = d.y;
- });
-
- $('#loading').remove()
- }
-
- function toggles(clicked_d) {
- // Collapse nodes with the same task id
- d3.selectAll(`[task_id='${clicked_d.name}']`).each(function(d){
- if(clicked_d != d && d.children) {
- d._children = d.children;
- d.children = null;
- update(d);
- }
- });
-
- // Toggle clicked node
- if(clicked_d._children) {
- clicked_d.children = clicked_d._children;
- clicked_d._children = null;
- } else {
- clicked_d._children = clicked_d.children;
- clicked_d.children = null;
- }
- update(clicked_d);
- }
- // Toggle children on click.
- function click(d) {
- if (d.children || d._children){
- if (d.children) {
- d._children = d.children;
- d.children = null;
- } else {
- d.children = d._children;
- d._children = null;
- }
- update(d);
- }
- }
+ const treeData = {{ data|tojson }}
</script>
{% endblock %}
diff --git a/airflow/www/webpack.config.js b/airflow/www/webpack.config.js
index 3101b30..6742131 100644
--- a/airflow/www/webpack.config.js
+++ b/airflow/www/webpack.config.js
@@ -52,7 +52,7 @@ const config = {
switch: `${CSS_DIR}/switch.css`,
taskInstances: `${JS_DIR}/task_instances.js`,
taskInstance: `${JS_DIR}/task_instance.js`,
- tree: `${CSS_DIR}/tree.css`,
+ tree: [`${CSS_DIR}/tree.css`, `${JS_DIR}/tree.js`],
circles: `${JS_DIR}/circles.js`,
durationChart: `${JS_DIR}/duration_chart.js`,
trigger: `${JS_DIR}/trigger.js`,