lineageController.js 11 KB
/*
 * 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.
 */

'use strict';

angular.module('dgc.lineage').controller('LineageController', ['$element', '$scope', '$state', '$stateParams', 'lodash', 'LineageResource', 'd3', 'DetailsResource', '$q',
    function($element, $scope, $state, $stateParams, _, LineageResource, d3, DetailsResource, $q) {
        var guidsList = [];

        function getLineageData(tableData, callRender) {
            LineageResource.get({
                tableName: tableData.tableName,
                type: tableData.type
            }, function lineageSuccess(response) {
                if (!_.isEmpty(response.results.values.vertices)) {
                    var allGuids = loadProcess(response.results.values.edges, response.results.values.vertices);
                    allGuids.then(function(res) {
                        guidsList = res;
                        $scope.lineageData = transformData(response.results);
                        if (callRender) {
                            render();
                        }
                    });
                }
                $scope.requested = false;
            });
        }

        function loadProcess(edges, vertices) {

            var urlCalls = [];
            var deferred = $q.defer();
            for (var guid in edges) {
                if (!vertices.hasOwnProperty(guid)) {
                    urlCalls.push(DetailsResource.get({
                        id: guid
                    }).$promise);
                }

            }
            $q.all(urlCalls)
                .then(function(results) {
                    deferred.resolve(results);
                });
            return deferred.promise;
        }

        $scope.type = $element.parent().attr('data-table-type');
        $scope.requested = false;

        function render() {
            renderGraph($scope.lineageData, {
                element: $element[0],
                height: $element[0].offsetHeight,
                width: $element[0].offsetWidth
            });
            $scope.rendered = true;
        }

        $scope.$on('render-lineage', function(event, lineageData) {
            if (lineageData.type === $scope.type) {
                if (!$scope.lineageData) {
                    if (!$scope.requested) {
                        getLineageData(lineageData, true);
                        $scope.requested = true;
                    }
                } else {
                    render();
                }
            }
        });

        function transformData(metaData) {
            var edges = metaData.values.edges,
                vertices = metaData.values.vertices,
                nodes = {};

            function getNode(guid) {
                var name, type, tip;
                if (vertices.hasOwnProperty(guid)) {
                    name = vertices[guid].values.name;
                    type = vertices[guid].values.vertexId.values.typeName;
                } else {
                    var loadProcess = getLoadProcessTypes(guid);
                    if (typeof loadProcess !== "undefined") {
                        name = loadProcess.name;
                        type = loadProcess.typeName;
                        tip = loadProcess.tip;
                    } else {
                        name = 'Load Process';
                        type = 'Load Process';
                    }
                }
                var vertex = {
                    guid: guid,
                    name: name,
                    type: type,
                    tip: tip
                };
                if (!nodes.hasOwnProperty(guid)) {
                    nodes[guid] = vertex;
                }
                return nodes[guid];
            }

            function getLoadProcessTypes(guid) {
                var procesRes = [];
                angular.forEach(guidsList, function(value) {
                    if (value.id.id === guid) {
                        procesRes.name = value.values.name;
                        procesRes.typeName = value.typeName;
                        procesRes.tip = value.values.queryText;
                    }
                });
                return procesRes;
            }

            function attachParent(edge, node) {
                edge.forEach(function eachPoint(childGuid) {
                    var childNode = getNode(childGuid);
                    node.children = node.children || [];
                    node.children.push(childNode);
                    childNode.parent = node.guid;
                });
            }

            /* Loop through all edges and attach them to correct parent */
            for (var guid in edges) {
                var edge = edges[guid],
                    node = getNode(guid);

                /* Attach parent to each endpoint of edge */
                attachParent(edge, node);
            }

            /* Return the first node w/o parent, this is root node*/
            return _.find(nodes, function(node) {
                return !node.hasOwnProperty('parent');
            });
        }

        function renderGraph(data, container) {
            // ************** Generate the tree diagram	 *****************
            var element = d3.select(container.element),
                width = Math.max(container.width, 960),
                height = Math.max(container.height, 350);

            var margin = {
                top: 100,
                right: 80,
                bottom: 30,
                left: 80
            };
            width = width - margin.right - margin.left;
            height = height - margin.top - margin.bottom;

            var i = 0;

            var tree = d3.layout.tree()
                .size([height, width]);

            var diagonal = d3.svg.diagonal()
                .projection(function(d) {
                    return [d.y, d.x];
                });

            /* Initialize tooltip */
            var tooltip = d3.tip()
                .attr('class', 'd3-tip')
                .html(function(d) {
                    return '<pre class="alert alert-success">' + d.tip + '</pre>';
                });

            var svg = element.select('svg')
                .attr('width', width + margin.right + margin.left)
                .attr('height', height + margin.top + margin.bottom)
                /* Invoke the tip in the context of your visualization */
                .call(tooltip)
                .select('g')
                .attr('transform',
                    'translate(' + margin.left + ',' + margin.right + ')');
            //arrow
            svg.append("svg:defs").append("svg:marker").attr("id", "arrow").attr("viewBox", "0 0 10 10").attr("refX", 26).attr("refY", 5).attr("markerUnits", "strokeWidth").attr("markerWidth", 6).attr("markerHeight", 9).attr("orient", "auto").append("svg:path").attr("d", "M 0 0 L 10 5 L 0 10 z");

            //marker for input type graph
            svg.append("svg:defs")
                .append("svg:marker")
                .attr("id", "input-arrow")
                .attr("viewBox", "0 0 10 10")
                .attr("refX", -15)
                .attr("refY", 5)
                .attr("markerUnits", "strokeWidth")
                .attr("markerWidth", 6)
                .attr("markerHeight", 9)
                .attr("orient", "auto")
                .append("svg:path")
                .attr("d", "M -2 5 L 8 0 L 8 10 z");

            var root = data;

            function update(source) {

                // Compute the new tree layout.
                var nodes = tree.nodes(source).reverse(),
                    links = tree.links(nodes);

                // Normalize for fixed-depth.
                nodes.forEach(function(d) {
                    d.y = d.depth * 180;
                });

                // Declare the nodes…
                var node = svg.selectAll('g.node')
                    .data(nodes, function(d) {
                        return d.id || (d.id = ++i);
                    });

                // Enter the nodes.
                var nodeEnter = node.enter().append('g')
                    .attr('class', 'node')
                    .attr('transform', function(d) {
                        return 'translate(' + d.y + ',' + d.x + ')';
                    });

                nodeEnter.append("image")
                    .attr("xlink:href", function(d) {
                        //return d.icon;
                        return d.type === 'Table' ? '../img/tableicon.png' : '../img/process.png';
                    })
                    .on('mouseover', function(d) {
                        if (d.type === 'LoadProcess') {
                            tooltip.show(d);
                        }
                    })
                    .on('mouseout', function(d) {
                        if (d.type === 'LoadProcess') {
                            tooltip.hide(d);
                        }
                    })
                    .attr("x", "-18px")
                    .attr("y", "-18px")
                    .attr("width", "34px")
                    .attr("height", "34px");

                nodeEnter.append('text')
                    .attr('x', function(d) {
                        return d.children || d._children ?
                            (5) * -1 : +15;
                    })
                    .attr('dy', '-1.75em')
                    .attr('text-anchor', function(d) {
                        return d.children || d._children ? 'middle' : 'middle';
                    })
                    .text(function(d) {
                        return d.name;
                    })

                .style('fill-opacity', 1);

                // Declare the links…
                var link = svg.selectAll('path.link')
                    .data(links, function(d) {
                        return d.target.id;
                    });

                link.enter().insert('path', 'g')
                    .attr('class', 'link')
                    //.style('stroke', function(d) { return d.target.level; })
                    .style('stroke', 'green')
                    .attr('d', diagonal);

                if ($scope.type === 'inputs') {
                    link.attr("marker-start", "url(#input-arrow)"); //if input
                } else {
                    link.attr("marker-end", "url(#arrow)"); //if input
                }

            }

            update(root);
        }

    }
]);