http://git-wip-us.apache.org/repos/asf/nifi/blob/dc934cbb/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/provenance/nf-provenance-lineage.js ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/provenance/nf-provenance-lineage.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/provenance/nf-provenance-lineage.js index d20737c..ddada58 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/provenance/nf-provenance-lineage.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/provenance/nf-provenance-lineage.js @@ -15,1383 +15,1416 @@ * limitations under the License. */ -/* global nf, d3 */ - -nf.ng.ProvenanceLineage = function () { +/* global nf, top, define, module, require, exports */ + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define(['jquery', + 'd3', + 'nf.Common', + 'nf.Dialog', + 'nf.ErrorHandler'], + function ($, d3, common, dialog, errorHandler) { + return (nf.ng.ProvenanceLineage = factory($, d3, common, dialog, errorHandler)); + }); + } else if (typeof exports === 'object' && typeof module === 'object') { + module.exports = (nf.ng.ProvenanceLineage = + factory(require('jquery'), + require('d3'), + require('nf.Common'), + require('nf.Dialog'), + require('nf.ErrorHandler'))); + } else { + nf.ng.ProvenanceLineage = factory(root.$, + root.d3, + root.nf.Common, + root.nf.Dialog, + root.nf.ErrorHandler); + } +}(this, function ($, d3, common, dialog, errorHandler) { 'use strict'; - /** - * Configuration object used to hold a number of configuration items. - */ - var config = { - sliderTickCount: 75, - urls: { - lineage: '../nifi-api/provenance/lineage' - } - }; + var mySelf = function () { + 'use strict'; - /** - * Initializes the lineage query dialog. - */ - var initLineageQueryDialog = function () { - // initialize the dialog - $('#lineage-query-dialog').modal({ - scrollableContentStyle: 'scrollable', - headerText: 'Computing FlowFile lineage...' - }); - }; + /** + * Configuration object used to hold a number of configuration items. + */ + var config = { + sliderTickCount: 75, + urls: { + lineage: '../nifi-api/provenance/lineage' + + } + }; + + /** + * Initializes the lineage query dialog. + */ + var initLineageQueryDialog = function () { + // initialize the dialog + $('#lineage-query-dialog').modal({ + scrollableContentStyle: 'scrollable', + headerText: 'Computing FlowFile lineage...' + }); + }; - var downloadSvgFile = function(svgString){ - var link = document.getElementById("image-download-link"); - var downloadSupported = typeof link.download != 'undefined'; - var fileName ='lineage.svg'; + var downloadSvgFile = function (svgString) { + var link = document.getElementById("image-download-link"); + var downloadSupported = typeof link.download != 'undefined'; + var fileName = 'lineage.svg'; - if (downloadSupported) { - var DOMURL = self.URL || self.webkitURL || self; - var svg = new Blob([svgString], {type: "image/svg+xml;charset=utf-8"}); + if (downloadSupported) { + var DOMURL = self.URL || self.webkitURL || self; + var svg = new Blob([svgString], {type: "image/svg+xml;charset=utf-8"}); - if (window.navigator.msSaveOrOpenBlob) { - window.navigator.msSaveOrOpenBlob(svg, fileName); + if (window.navigator.msSaveOrOpenBlob) { + window.navigator.msSaveOrOpenBlob(svg, fileName); + } else { + var url = DOMURL.createObjectURL(svg); + link.href = url; + link.download = fileName; + link.click(); + } } else { - var url = DOMURL.createObjectURL(svg); - link.href = url; - link.download = fileName; - link.click(); + window.open('data:image/svg+xml;charset=utf-8,' + encodeURI(svgString)); } - } else { - window.open('data:image/svg+xml;charset=utf-8,' + encodeURI(svgString)); - } - }; + }; - /** - * Appends the items to the context menu. - * - * items = [{class: ..., text: ..., click: function() {...}}, ...] - * - * @param {array} items - */ - var addContextMenuItems = function (items) { - var contextMenu = $('#provenance-lineage-context-menu'); - - $.each(items, function (_, item) { - if (typeof item.click === 'function') { - var menuItem = $('<div class="context-menu-item"></div>').on('click', item.click).on('mouseenter', function () { - $(this).addClass('hover'); - }).on('mouseleave', function () { - $(this).removeClass('hover'); - }).appendTo(contextMenu); - - // add the img and the text - $('<div class="context-menu-item-img"></div>').addClass(item['class']).appendTo(menuItem); - $('<div class="context-menu-item-text"></div>').text(item['text']).appendTo(menuItem); - $('<div class="clear"></div>').appendTo(menuItem); - } - }); - }; + /** + * Appends the items to the context menu. + * + * items = [{class: ..., text: ..., click: function() {...}}, ...] + * + * @param {array} items + */ + var addContextMenuItems = function (items) { + var contextMenu = $('#provenance-lineage-context-menu'); + + $.each(items, function (_, item) { + if (typeof item.click === 'function') { + var menuItem = $('<div class="context-menu-item"></div>').on('click', item.click).on('mouseenter', function () { + $(this).addClass('hover'); + }).on('mouseleave', function () { + $(this).removeClass('hover'); + }).appendTo(contextMenu); + + // add the img and the text + $('<div class="context-menu-item-img"></div>').addClass(item['class']).appendTo(menuItem); + $('<div class="context-menu-item-text"></div>').text(item['text']).appendTo(menuItem); + $('<div class="clear"></div>').appendTo(menuItem); + } + }); + }; + + /** + * Submits the specified lineage request. + * + * @param {type} lineageRequest + * @returns {deferred} + */ + var submitLineage = function (lineageRequest) { + var lineageEntity = { + 'lineage': { + 'request': lineageRequest + } + }; + + return $.ajax({ + type: 'POST', + url: config.urls.lineage, + data: JSON.stringify(lineageEntity), + dataType: 'json', + contentType: 'application/json' + }).fail(errorHandler.handleAjaxError); + }; - /** - * Submits the specified lineage request. - * - * @param {type} lineageRequest - * @returns {deferred} - */ - var submitLineage = function (lineageRequest) { - var lineageEntity = { - 'lineage': { - 'request': lineageRequest + /** + * Gets the specified lineage. + * + * @param {type} lineage + * @returns {deferred} + */ + var getLineage = function (lineage) { + var url = lineage.uri; + if (common.isDefinedAndNotNull(lineage.request.clusterNodeId)) { + url += '?' + $.param({ + clusterNodeId: lineage.request.clusterNodeId + }); } + + return $.ajax({ + type: 'GET', + url: url, + dataType: 'json' + }).fail(errorHandler.handleAjaxError); }; - return $.ajax({ - type: 'POST', - url: config.urls.lineage, - data: JSON.stringify(lineageEntity), - dataType: 'json', - contentType: 'application/json' - }).fail(nf.Common.handleAjaxError); - }; + /** + * Cancels the specified lineage. + * + * @param {type} lineage + * @returns {deferred} + */ + var cancelLineage = function (lineage) { + var url = lineage.uri; + if (common.isDefinedAndNotNull(lineage.request.clusterNodeId)) { + url += '?' + $.param({ + clusterNodeId: lineage.request.clusterNodeId + }); + } - /** - * Gets the specified lineage. - * - * @param {type} lineage - * @returns {deferred} - */ - var getLineage = function (lineage) { - var url = lineage.uri; - if (nf.Common.isDefinedAndNotNull(lineage.request.clusterNodeId)) { - url += '?' + $.param({ - clusterNodeId: lineage.request.clusterNodeId - }); - } + return $.ajax({ + type: 'DELETE', + url: url, + dataType: 'json' + }).fail(errorHandler.handleAjaxError); + }; - return $.ajax({ - type: 'GET', - url: url, - dataType: 'json' - }).fail(nf.Common.handleAjaxError); - }; + var DEFAULT_NODE_SPACING = 100; + var DEFAULT_LEVEL_DIFFERENCE = 120; - /** - * Cancels the specified lineage. - * - * @param {type} lineage - * @returns {deferred} - */ - var cancelLineage = function (lineage) { - var url = lineage.uri; - if (nf.Common.isDefinedAndNotNull(lineage.request.clusterNodeId)) { - url += '?' + $.param({ - clusterNodeId: lineage.request.clusterNodeId - }); - } + /** + * Renders the lineage in the specified results. + * + * @param {object} lineageResults + * @param {integer} eventId + * @param {string} clusterNodeId The id of the node in the cluster where this event/flowfile originated + */ + var renderLineage = function (lineageResults, eventId, clusterNodeId, provenanceTableCtrl) { + // get the container + var lineageContainer = $('#provenance-lineage'); + var width = lineageContainer.width(); + var height = lineageContainer.height(); + + // record the min/max event time + var minMillis; + var minTimestamp; + var maxMillis; + + // data lookups + var nodeLookup = d3.map(); + var linkLookup = d3.map(); + + var locateDescendants = function (nodeIds, descendants, depth) { + $.each(nodeIds, function (_, nodeId) { + var node = nodeLookup.get(nodeId); - return $.ajax({ - type: 'DELETE', - url: url, - dataType: 'json' - }).fail(nf.Common.handleAjaxError); - }; + var children = []; + $.each(node.outgoing, function (_, link) { + children.push(link.target.id); + descendants.add(link.target.id); + }); - var DEFAULT_NODE_SPACING = 100; - var DEFAULT_LEVEL_DIFFERENCE = 120; - - /** - * Renders the lineage in the specified results. - * - * @param {object} lineageResults - * @param {integer} eventId - * @param {string} clusterNodeId The id of the node in the cluster where this event/flowfile originated - */ - var renderLineage = function (lineageResults, eventId, clusterNodeId, provenanceTableCtrl) { - // get the container - var lineageContainer = $('#provenance-lineage'); - var width = lineageContainer.width(); - var height = lineageContainer.height(); - - // record the min/max event time - var minMillis; - var minTimestamp; - var maxMillis; - - // data lookups - var nodeLookup = d3.map(); - var linkLookup = d3.map(); - - var locateDescendants = function (nodeIds, descendants, depth) { - $.each(nodeIds, function (_, nodeId) { - var node = nodeLookup.get(nodeId); - - var children = []; - $.each(node.outgoing, function (_, link) { - children.push(link.target.id); - descendants.add(link.target.id); + if (common.isUndefined(depth)) { + locateDescendants(children, descendants); + } else if (depth > 1) { + locateDescendants(children, descendants, depth - 1); + } }); + }; - if (nf.Common.isUndefined(depth)) { - locateDescendants(children, descendants); - } else if (depth > 1) { - locateDescendants(children, descendants, depth - 1); - } - }); - }; + var positionNodes = function (nodeIds, depth, parents, levelDifference) { + var immediateSet = d3.set(nodeIds); + var childSet = d3.set(); + var descendantSet = d3.set(); - var positionNodes = function (nodeIds, depth, parents, levelDifference) { - var immediateSet = d3.set(nodeIds); - var childSet = d3.set(); - var descendantSet = d3.set(); + // locate children + locateDescendants(nodeIds, childSet, 1); - // locate children - locateDescendants(nodeIds, childSet, 1); + // locate all descendants (including children) + locateDescendants(nodeIds, descendantSet); - // locate all descendants (including children) - locateDescendants(nodeIds, descendantSet); + // push off processing a node until its deepest point + // by removing any descendants from the immediate nodes. + // in this case, a link is panning multiple levels + descendantSet.forEach(function (d) { + immediateSet.remove(d); + }); - // push off processing a node until its deepest point - // by removing any descendants from the immediate nodes. - // in this case, a link is panning multiple levels - descendantSet.forEach(function (d) { - immediateSet.remove(d); - }); + // convert the children to an array to ensure consistent + // order when performing index of checks below + var children = childSet.values().sort(d3.descending); - // convert the children to an array to ensure consistent - // order when performing index of checks below - var children = childSet.values().sort(d3.descending); + // convert the immediate to allow for sorting below + var immediate = immediateSet.values(); - // convert the immediate to allow for sorting below - var immediate = immediateSet.values(); + // attempt to identify fan in/out cases + var nodesWithTwoParents = 0; + $.each(immediate, function (_, nodeId) { + var node = nodeLookup.get(nodeId); - // attempt to identify fan in/out cases - var nodesWithTwoParents = 0; - $.each(immediate, function (_, nodeId) { - var node = nodeLookup.get(nodeId); + // identify fanning cases + if (node.incoming.length > 3) { + levelDifference = DEFAULT_LEVEL_DIFFERENCE; + } else if (node.incoming.length >= 2) { + nodesWithTwoParents++; + } + }); - // identify fanning cases - if (node.incoming.length > 3) { + // increate the level difference if more than two nodes have two or more parents + if (nodesWithTwoParents > 2) { levelDifference = DEFAULT_LEVEL_DIFFERENCE; - } else if (node.incoming.length >= 2) { - nodesWithTwoParents++; } - }); - // increate the level difference if more than two nodes have two or more parents - if (nodesWithTwoParents > 2) { - levelDifference = DEFAULT_LEVEL_DIFFERENCE; - } - - // attempt to sort the nodes to provide an optimum layout - if (parents.length === 1) { - immediate = immediate.sort(function (one, two) { - var oneNode = nodeLookup.get(one); - var twoNode = nodeLookup.get(two); - - // try to order by children - if (oneNode.outgoing.length > 0 && twoNode.outgoing.length > 0) { - var oneIndex = children.indexOf(oneNode.outgoing[0].target.id); - var twoIndex = children.indexOf(twoNode.outgoing[0].target.id); - if (oneIndex !== twoIndex) { - return oneIndex - twoIndex; + // attempt to sort the nodes to provide an optimum layout + if (parents.length === 1) { + immediate = immediate.sort(function (one, two) { + var oneNode = nodeLookup.get(one); + var twoNode = nodeLookup.get(two); + + // try to order by children + if (oneNode.outgoing.length > 0 && twoNode.outgoing.length > 0) { + var oneIndex = children.indexOf(oneNode.outgoing[0].target.id); + var twoIndex = children.indexOf(twoNode.outgoing[0].target.id); + if (oneIndex !== twoIndex) { + return oneIndex - twoIndex; + } } - } - // try to order by parents - if (oneNode.incoming.length > 0 && twoNode.incoming.length > 0) { - var oneIndex = oneNode.incoming[0].source.index; - var twoIndex = twoNode.incoming[0].source.index; - if (oneIndex !== twoIndex) { - return oneIndex - twoIndex; + // try to order by parents + if (oneNode.incoming.length > 0 && twoNode.incoming.length > 0) { + var oneIndex = oneNode.incoming[0].source.index; + var twoIndex = twoNode.incoming[0].source.index; + if (oneIndex !== twoIndex) { + return oneIndex - twoIndex; + } } - } - // type of node - if (oneNode.type !== twoNode.type) { - return oneNode.type > twoNode.type ? 1 : -1; - } - - // type of event - if (oneNode.eventType !== twoNode.eventType) { - return oneNode.eventType > twoNode.eventType ? 1 : -1; - } + // type of node + if (oneNode.type !== twoNode.type) { + return oneNode.type > twoNode.type ? 1 : -1; + } - // timestamp - return oneNode.millis - twoNode.millis; - }); - } else if (parents.length > 1) { - immediate = immediate.sort(function (one, two) { - var oneNode = nodeLookup.get(one); - var twoNode = nodeLookup.get(two); - - // try to order by parents - if (oneNode.incoming.length > 0 && twoNode.incoming.length > 0) { - var oneIndex = oneNode.incoming[0].source.index; - var twoIndex = twoNode.incoming[0].source.index; - if (oneIndex !== twoIndex) { - return oneIndex - twoIndex; + // type of event + if (oneNode.eventType !== twoNode.eventType) { + return oneNode.eventType > twoNode.eventType ? 1 : -1; } - } - // try to order by children - if (oneNode.outgoing.length > 0 && twoNode.outgoing.length > 0) { - var oneIndex = children.indexOf(oneNode.outgoing[0].target.id); - var twoIndex = children.indexOf(twoNode.outgoing[0].target.id); - if (oneIndex !== twoIndex) { - return oneIndex - twoIndex; + // timestamp + return oneNode.millis - twoNode.millis; + }); + } else if (parents.length > 1) { + immediate = immediate.sort(function (one, two) { + var oneNode = nodeLookup.get(one); + var twoNode = nodeLookup.get(two); + + // try to order by parents + if (oneNode.incoming.length > 0 && twoNode.incoming.length > 0) { + var oneIndex = oneNode.incoming[0].source.index; + var twoIndex = twoNode.incoming[0].source.index; + if (oneIndex !== twoIndex) { + return oneIndex - twoIndex; + } } - } - // node type - if (oneNode.type !== twoNode.type) { - return oneNode.type > twoNode.type ? 1 : -1; - } + // try to order by children + if (oneNode.outgoing.length > 0 && twoNode.outgoing.length > 0) { + var oneIndex = children.indexOf(oneNode.outgoing[0].target.id); + var twoIndex = children.indexOf(twoNode.outgoing[0].target.id); + if (oneIndex !== twoIndex) { + return oneIndex - twoIndex; + } + } - // event type - if (oneNode.eventType !== twoNode.eventType) { - return oneNode.eventType > twoNode.eventType ? 1 : -1; - } + // node type + if (oneNode.type !== twoNode.type) { + return oneNode.type > twoNode.type ? 1 : -1; + } - // timestamp - return oneNode.millis - twoNode.millis; - }); - } + // event type + if (oneNode.eventType !== twoNode.eventType) { + return oneNode.eventType > twoNode.eventType ? 1 : -1; + } - var originX = width / 2; - if (parents.length > 0) { - originX = d3.mean(parents, function (parentId) { - var parent = nodeLookup.get(parentId); - return parent.x; - }); - } + // timestamp + return oneNode.millis - twoNode.millis; + }); + } - var depthWidth = (immediate.length - 1) * DEFAULT_NODE_SPACING; - $.each(immediate, function (i, nodeId) { - var node = nodeLookup.get(nodeId); + var originX = width / 2; + if (parents.length > 0) { + originX = d3.mean(parents, function (parentId) { + var parent = nodeLookup.get(parentId); + return parent.x; + }); + } - // set the y position based on the depth - node.y = levelDifference + depth - 25; + var depthWidth = (immediate.length - 1) * DEFAULT_NODE_SPACING; + $.each(immediate, function (i, nodeId) { + var node = nodeLookup.get(nodeId); - // ensure the children won't position on top of one another - // based on the number of parent nodes - if (immediate.length <= parents.length) { - if (node.incoming.length === 1) { - var parent = node.incoming[0].source; - if (parent.outgoing.length === 1) { - node.x = parent.x; + // set the y position based on the depth + node.y = levelDifference + depth - 25; + + // ensure the children won't position on top of one another + // based on the number of parent nodes + if (immediate.length <= parents.length) { + if (node.incoming.length === 1) { + var parent = node.incoming[0].source; + if (parent.outgoing.length === 1) { + node.x = parent.x; + return; + } + } else if (node.incoming.length > 1) { + var nodesOnPreviousLevel = $.grep(node.incoming, function (link) { + return (node.y - link.source.y) <= DEFAULT_LEVEL_DIFFERENCE; + }); + node.x = d3.mean(nodesOnPreviousLevel, function (link) { + return link.source.x; + }); return; } - } else if (node.incoming.length > 1) { - var nodesOnPreviousLevel = $.grep(node.incoming, function (link) { - return (node.y - link.source.y) <= DEFAULT_LEVEL_DIFFERENCE; - }); - node.x = d3.mean(nodesOnPreviousLevel, function (link) { - return link.source.x; - }); - return; } - } - // evenly space the nodes under the origin - node.x = (i * DEFAULT_NODE_SPACING) + originX - (depthWidth / 2); - }); + // evenly space the nodes under the origin + node.x = (i * DEFAULT_NODE_SPACING) + originX - (depthWidth / 2); + }); - // sort the immediate nodes after positioning by the x coordinate - // so they can be shifted accordingly if necessary - var sortedImmediate = immediate.slice().sort(function (one, two) { - var nodeOne = nodeLookup.get(one); - var nodeTwo = nodeLookup.get(two); - return nodeOne.x - nodeTwo.x; - }); + // sort the immediate nodes after positioning by the x coordinate + // so they can be shifted accordingly if necessary + var sortedImmediate = immediate.slice().sort(function (one, two) { + var nodeOne = nodeLookup.get(one); + var nodeTwo = nodeLookup.get(two); + return nodeOne.x - nodeTwo.x; + }); - // adjust the x positioning if necessary to avoid positioning on top - // of one another, only need to consider the x coordinate since the - // y coordinate will be the same for each node on this row - for (var i = 0; i < sortedImmediate.length - 1; i++) { - var first = nodeLookup.get(sortedImmediate[i]); - var second = nodeLookup.get(sortedImmediate[i + 1]); - var difference = second.x - first.x; + // adjust the x positioning if necessary to avoid positioning on top + // of one another, only need to consider the x coordinate since the + // y coordinate will be the same for each node on this row + for (var i = 0; i < sortedImmediate.length - 1; i++) { + var first = nodeLookup.get(sortedImmediate[i]); + var second = nodeLookup.get(sortedImmediate[i + 1]); + var difference = second.x - first.x; - if (difference < DEFAULT_NODE_SPACING) { - second.x += (DEFAULT_NODE_SPACING - difference); + if (difference < DEFAULT_NODE_SPACING) { + second.x += (DEFAULT_NODE_SPACING - difference); + } } - } - // if there are children to position - if (children.length > 0) { - var childLevelDifference = DEFAULT_LEVEL_DIFFERENCE / 3; + // if there are children to position + if (children.length > 0) { + var childLevelDifference = DEFAULT_LEVEL_DIFFERENCE / 3; - // resort the immediate values after each node has been positioned - immediate = immediate.sort(function (one, two) { - var oneNode = nodeLookup.get(one); - var twoNode = nodeLookup.get(two); - return oneNode.x - twoNode.x; - }); + // resort the immediate values after each node has been positioned + immediate = immediate.sort(function (one, two) { + var oneNode = nodeLookup.get(one); + var twoNode = nodeLookup.get(two); + return oneNode.x - twoNode.x; + }); - // mark each nodes index so subsequent recursive calls can position children accordingly - var nodesWithTwoChildren = 0; - $.each(immediate, function (i, nodeId) { - var node = nodeLookup.get(nodeId); - node.index = i; + // mark each nodes index so subsequent recursive calls can position children accordingly + var nodesWithTwoChildren = 0; + $.each(immediate, function (i, nodeId) { + var node = nodeLookup.get(nodeId); + node.index = i; + + // precompute the next level difference since we have easy access to going here + if (node.outgoing.length > 3) { + childLevelDifference = DEFAULT_LEVEL_DIFFERENCE; + } else if (node.outgoing.length >= 2) { + nodesWithTwoChildren++; + } + }); - // precompute the next level difference since we have easy access to going here - if (node.outgoing.length > 3) { + // if there are at least two immediate nodes with two or more children, increase the level difference + if (nodesWithTwoChildren > 2) { childLevelDifference = DEFAULT_LEVEL_DIFFERENCE; - } else if (node.outgoing.length >= 2) { - nodesWithTwoChildren++; } - }); - // if there are at least two immediate nodes with two or more children, increase the level difference - if (nodesWithTwoChildren > 2) { - childLevelDifference = DEFAULT_LEVEL_DIFFERENCE; + // position the children + positionNodes(children, levelDifference + depth, immediate, childLevelDifference); } + }; - // position the children - positionNodes(children, levelDifference + depth, immediate, childLevelDifference); - } - }; + var addLineage = function (nodes, links, provenanceTableCtrl) { + // add the new nodes + $.each(nodes, function (_, node) { + if (nodeLookup.has(node.id)) { + return; + } - var addLineage = function (nodes, links, provenanceTableCtrl) { - // add the new nodes - $.each(nodes, function (_, node) { - if (nodeLookup.has(node.id)) { - return; - } + // add values to the node to support rendering + $.extend(node, { + x: 0, + y: 0, + visible: true + }); - // add values to the node to support rendering - $.extend(node, { - x: 0, - y: 0, - visible: true + // store the node in a lookup + nodeLookup.set(node.id, node); }); - // store the node in a lookup - nodeLookup.set(node.id, node); - }); - - // add the new links - $.each(links, function (_, link) { - // create the link object - var linkObj = { - id: link.sourceId + '-' + link.targetId, - source: nodeLookup.get(link.sourceId), - target: nodeLookup.get(link.targetId), - flowFileUuid: link.flowFileUuid, - millis: link.millis, - visible: true - }; - - linkLookup.set(linkObj.id, linkObj); - }); - - refresh(provenanceTableCtrl); - }; + // add the new links + $.each(links, function (_, link) { + // create the link object + var linkObj = { + id: link.sourceId + '-' + link.targetId, + source: nodeLookup.get(link.sourceId), + target: nodeLookup.get(link.targetId), + flowFileUuid: link.flowFileUuid, + millis: link.millis, + visible: true + }; + + linkLookup.set(linkObj.id, linkObj); + }); - var refresh = function (provenanceTableCtrl) { - // consider all nodes as starting points - var startNodes = d3.set(nodeLookup.keys()); + refresh(provenanceTableCtrl); + }; - // go through the nodes to reset their outgoing links - nodeLookup.forEach(function (id, node) { - node.outgoing = []; - node.incoming = []; + var refresh = function (provenanceTableCtrl) { + // consider all nodes as starting points + var startNodes = d3.set(nodeLookup.keys()); - // ensure this event has an event time - if (nf.Common.isUndefined(minMillis) || minMillis > node.millis) { - minMillis = node.millis; - minTimestamp = node.timestamp; - } - if (nf.Common.isUndefined(maxMillis) || maxMillis < node.millis) { - maxMillis = node.millis; - } - }); + // go through the nodes to reset their outgoing links + nodeLookup.forEach(function (id, node) { + node.outgoing = []; + node.incoming = []; - // go through the links in order to compute the new layout - linkLookup.forEach(function (id, link) { - // updating the nodes connections - link.source.outgoing.push(link); - link.target.incoming.push(link); + // ensure this event has an event time + if (common.isUndefined(minMillis) || minMillis > node.millis) { + minMillis = node.millis; + minTimestamp = node.timestamp; + } + if (common.isUndefined(maxMillis) || maxMillis < node.millis) { + maxMillis = node.millis; + } + }); - // remove the target from being a potential starting node - startNodes.remove(link.target.id); - }); + // go through the links in order to compute the new layout + linkLookup.forEach(function (id, link) { + // updating the nodes connections + link.source.outgoing.push(link); + link.target.incoming.push(link); - // position the nodes - positionNodes(startNodes.values(), 1, [], 50); + // remove the target from being a potential starting node + startNodes.remove(link.target.id); + }); - // update the slider min/max/step values - var step = (maxMillis - minMillis) / config.sliderTickCount; - slider.slider('option', 'min', minMillis).slider('option', 'max', maxMillis).slider('option', 'step', step).slider('value', maxMillis); + // position the nodes + positionNodes(startNodes.values(), 1, [], 50); - // populate the event timeline - $('#event-time').text(formatEventTime(maxMillis, provenanceTableCtrl)); + // update the slider min/max/step values + var step = (maxMillis - minMillis) / config.sliderTickCount; + slider.slider('option', 'min', minMillis).slider('option', 'max', maxMillis).slider('option', 'step', step).slider('value', maxMillis); - // update the layout - update(provenanceTableCtrl); - }; + // populate the event timeline + $('#event-time').text(formatEventTime(maxMillis, provenanceTableCtrl)); - // formats the specified millis - var formatEventTime = function (millis, provenanceTableCtrl) { - // get the current user time to properly convert the server time - var now = new Date(); + // update the layout + update(provenanceTableCtrl); + }; - // conver the user offset to millis - var userTimeOffset = now.getTimezoneOffset() * 60 * 1000; + // formats the specified millis + var formatEventTime = function (millis, provenanceTableCtrl) { + // get the current user time to properly convert the server time + var now = new Date(); - // create the proper date by adjusting by the offsets - var date = new Date(millis + userTimeOffset + provenanceTableCtrl.serverTimeOffset); - return nf.Common.formatDateTime(date); - }; + // conver the user offset to millis + var userTimeOffset = now.getTimezoneOffset() * 60 * 1000; - // handle context menu clicks... - $('#provenance-lineage-context-menu').on('click', function () { - $(this).hide().empty(); - }); - - // handle zoom behavior - var lineageZoom = d3.behavior.zoom() - .scaleExtent([0.2, 8]) - .on('zoom', function () { - d3.select('g.lineage').attr('transform', function () { - return 'translate(' + d3.event.translate + ') scale(' + d3.event.scale + ')'; - }); - }); - - // build the svg img - var svg = d3.select('#provenance-lineage-container').append('svg:svg') - .attr('width', '100%') - .attr('height', '100%') - .call(lineageZoom) - .on('dblclick.zoom', null) - .on('mousedown', function (d) { - // hide the context menu if necessary - d3.selectAll('circle.context').classed('context', false); - $('#provenance-lineage-context-menu').hide().empty(); - }) - .on('contextmenu', function () { - var contextMenu = $('#provenance-lineage-context-menu'); - - // if there is something to show in the context menu - if (!contextMenu.is(':empty')) { - var position = d3.mouse(this); - - // show the context menu - contextMenu.css({ - 'left': position[0] + 'px', - 'top': position[1] + 'px' - }).show(); - } + // create the proper date by adjusting by the offsets + var date = new Date(millis + userTimeOffset + provenanceTableCtrl.serverTimeOffset); + return common.formatDateTime(date); + }; - // prevent the native default context menu - d3.event.preventDefault(); + // handle context menu clicks... + $('#provenance-lineage-context-menu').on('click', function () { + $(this).hide().empty(); }); - svg.append('rect') - .attr({ - 'width': '100%', - 'height': '100%', - 'fill': '#f9fafb' - }); + // handle zoom behavior + var lineageZoom = d3.behavior.zoom() + .scaleExtent([0.2, 8]) + .on('zoom', function () { + d3.select('g.lineage').attr('transform', function () { + return 'translate(' + d3.event.translate + ') scale(' + d3.event.scale + ')'; + }); + }); - svg.append('defs').selectAll('marker') - .data(['FLOWFILE', 'FLOWFILE-SELECTED', 'EVENT', 'EVENT-SELECTED']) - .enter().append('marker') - .attr({ - 'id': function (d) { - return d; - }, - 'viewBox': '0 -3 6 6', - 'refX': function (d) { - if (d.indexOf('FLOWFILE') >= 0) { - return 16; - } else { - return 11; - } - }, - 'refY': 0, - 'markerWidth': 6, - 'markerHeight': 6, - 'orient': 'auto', - 'fill': function (d) { - if (d.indexOf('SELECTED') >= 0) { - return '#ba554a'; - } else { - return '#000000'; + // build the svg img + var svg = d3.select('#provenance-lineage-container').append('svg:svg') + .attr('width', '100%') + .attr('height', '100%') + .call(lineageZoom) + .on('dblclick.zoom', null) + .on('mousedown', function (d) { + // hide the context menu if necessary + d3.selectAll('circle.context').classed('context', false); + $('#provenance-lineage-context-menu').hide().empty(); + }) + .on('contextmenu', function () { + var contextMenu = $('#provenance-lineage-context-menu'); + + // if there is something to show in the context menu + if (!contextMenu.is(':empty')) { + var position = d3.mouse(this); + + // show the context menu + contextMenu.css({ + 'left': position[0] + 'px', + 'top': position[1] + 'px' + }).show(); } - } - }) - .append('path') - .attr('d', 'M0,-3 L6,0 L0,3'); - - // group everything together - var lineageContainer = svg.append('g') - .attr({ - 'transform': 'translate(0, 0) scale(1)', - 'pointer-events': 'all', - 'class': 'lineage' - }); - - // select the nodes and links - var nodes = lineageContainer.selectAll('g.node'); - var links = lineageContainer.selectAll('path.link'); - - var previousMillis = maxMillis; - var slide = function (event, ui) { - if (previousMillis > ui.value) { - // the slider is descending - // determine the nodes to hide - var nodesToHide = nodes.filter(function (d) { - return d.millis > ui.value && d.millis <= previousMillis; + // prevent the native default context menu + d3.event.preventDefault(); }); - var linksToHide = links.filter(function (d) { - return d.millis > ui.value && d.millis <= previousMillis; + + svg.append('rect') + .attr({ + 'width': '100%', + 'height': '100%', + 'fill': '#f9fafb' }); - // hide applicable nodes and lines - linksToHide.transition().duration(400).style('opacity', 0); - nodesToHide.transition().delay(200).duration(400).style('opacity', 0); - } else { - // the slider is ascending + svg.append('defs').selectAll('marker') + .data(['FLOWFILE', 'FLOWFILE-SELECTED', 'EVENT', 'EVENT-SELECTED']) + .enter().append('marker') + .attr({ + 'id': function (d) { + return d; + }, + 'viewBox': '0 -3 6 6', + 'refX': function (d) { + if (d.indexOf('FLOWFILE') >= 0) { + return 16; + } else { + return 11; + } + }, + 'refY': 0, + 'markerWidth': 6, + 'markerHeight': 6, + 'orient': 'auto', + 'fill': function (d) { + if (d.indexOf('SELECTED') >= 0) { + return '#ba554a'; + } else { + return '#000000'; + } + } + }) + .append('path') + .attr('d', 'M0,-3 L6,0 L0,3'); - // determine the nodes to show - var nodesToShow = nodes.filter(function (d) { - return d.millis <= ui.value && d.millis > previousMillis; - }); - var linksToShow = links.filter(function (d) { - return d.millis <= ui.value && d.millis > previousMillis; + // group everything together + var lineageContainer = svg.append('g') + .attr({ + 'transform': 'translate(0, 0) scale(1)', + 'pointer-events': 'all', + 'class': 'lineage' }); - // show applicable nodes and lines - nodesToShow.transition().duration(400).style('opacity', 1); - linksToShow.transition().delay(200).duration(400).style('opacity', 1); - } + // select the nodes and links + var nodes = lineageContainer.selectAll('g.node'); + var links = lineageContainer.selectAll('path.link'); - // update the event time - $('#event-time').text(formatEventTime(ui.value, provenanceTableCtrl)); + var previousMillis = maxMillis; + var slide = function (event, ui) { + if (previousMillis > ui.value) { + // the slider is descending - // update the previous value - previousMillis = ui.value; - }; + // determine the nodes to hide + var nodesToHide = nodes.filter(function (d) { + return d.millis > ui.value && d.millis <= previousMillis; + }); + var linksToHide = links.filter(function (d) { + return d.millis > ui.value && d.millis <= previousMillis; + }); - // set up a slider for the showing the timeline of events - var slider = $('#provenance-lineage-slider').slider({ - min: minMillis, - max: maxMillis, - step: (maxMillis - minMillis) / config.sliderTickCount, - value: maxMillis, - change: slide, - slide: slide - }); - - // renders flowfile nodes - var renderFlowFile = function (flowfiles) { - flowfiles.classed('flowfile', true); - - // node - flowfiles.append('circle') - .attr({ - 'r': 16, - 'fill': '#fff', - 'stroke': '#000', - 'stroke-width': 1.0 - }) - .on('mousedown', function (d) { - // empty context menu if necessary - $('#provenance-lineage-context-menu').hide().empty(); + // hide applicable nodes and lines + linksToHide.transition().duration(400).style('opacity', 0); + nodesToHide.transition().delay(200).duration(400).style('opacity', 0); + } else { + // the slider is ascending - // prevents the drag event when something other than the - // left button is clicked - if (d3.event.button !== 0) { - d3.event.stopPropagation(); - } - }, true) - .on('mouseover', function (d) { - links.filter(function (linkDatum) { - return d.id === linkDatum.flowFileUuid; - }) - .classed('selected', true) - .attr('marker-end', function (d) { - return 'url(#' + d.target.type + '-SELECTED)'; - }); - }) - .on('mouseout', function (d) { - links.filter(function (linkDatum) { - return d.id === linkDatum.flowFileUuid; - }).classed('selected', false) - .attr('marker-end', function (d) { - return 'url(#' + d.target.type + ')'; - }); - }); + // determine the nodes to show + var nodesToShow = nodes.filter(function (d) { + return d.millis <= ui.value && d.millis > previousMillis; + }); + var linksToShow = links.filter(function (d) { + return d.millis <= ui.value && d.millis > previousMillis; + }); - var icon = flowfiles.append('g') - .attr({ - 'class': 'flowfile-icon', - 'transform': function (d) { - return 'translate(-9,-9)'; - } - }).append('text') - .attr({ - 'font-family': 'flowfont', - 'font-size': '18px', - 'fill': '#ad9897', - 'transform': function (d) { - return 'translate(0,15)'; - } - }) - .on('mousedown', function (d) { - // empty context menu if necessary - $('#provenance-lineage-context-menu').hide().empty(); + // show applicable nodes and lines + nodesToShow.transition().duration(400).style('opacity', 1); + linksToShow.transition().delay(200).duration(400).style('opacity', 1); + } - // prevents the drag event when something other than the - // left button is clicked - if (d3.event.button !== 0) { - d3.event.stopPropagation(); - } - }, true) - .on('mouseover', function (d) { - links.filter(function (linkDatum) { - return d.id === linkDatum.flowFileUuid; - }) - .classed('selected', true) - .attr('marker-end', function (d) { - return 'url(#' + d.target.type + '-SELECTED)'; - }); - }) - .on('mouseout', function (d) { - links.filter(function (linkDatum) { - return d.id === linkDatum.flowFileUuid; - }).classed('selected', false) - .attr('marker-end', function (d) { - return 'url(#' + d.target.type + ')'; - }); - }) - .text(function(d) { return '\ue808' }); - }; + // update the event time + $('#event-time').text(formatEventTime(ui.value, provenanceTableCtrl)); - // renders event nodes - var renderEvent = function (events, provenanceTableCtrl) { - events - .classed('event', true) - .append('circle') - .classed('selected', function (d) { - return d.id === eventId; - }) - .attr({ - 'r': 8, - 'fill': '#aabbc3', - 'stroke': '#000', - 'stroke-width': 1.0, - 'id': function (d) { - return 'event-node-' + d.id; - } - }) - .on('contextmenu', function (d) { - // select the current node for a visible cue - d3.select(this).classed('context', true); + // update the previous value + previousMillis = ui.value; + }; - // empty an previous contents - in case they right click on the - // node twice without closing the previous context menu - $('#provenance-lineage-context-menu').hide().empty(); + // set up a slider for the showing the timeline of events + var slider = $('#provenance-lineage-slider').slider({ + min: minMillis, + max: maxMillis, + step: (maxMillis - minMillis) / config.sliderTickCount, + value: maxMillis, + change: slide, + slide: slide + }); - var menuItems = [{ - 'class': 'lineage-view-event', - 'text': 'View details', - 'click': function () { - provenanceTableCtrl.showEventDetails(d.id, clusterNodeId); + // renders flowfile nodes + var renderFlowFile = function (flowfiles) { + flowfiles.classed('flowfile', true); + + // node + flowfiles.append('circle') + .attr({ + 'r': 16, + 'fill': '#fff', + 'stroke': '#000', + 'stroke-width': 1.0 + }) + .on('mousedown', function (d) { + // empty context menu if necessary + $('#provenance-lineage-context-menu').hide().empty(); + + // prevents the drag event when something other than the + // left button is clicked + if (d3.event.button !== 0) { + d3.event.stopPropagation(); } - }]; - - // if this is a spawn event show appropriate actions - if (d.eventType === 'SPAWN' || d.eventType === 'CLONE' || d.eventType === 'FORK' || d.eventType === 'JOIN' || d.eventType === 'REPLAY') { - // starts the lineage expansion process - var expandLineage = function (lineageRequest) { - var lineageProgress = $('#lineage-percent-complete'); - - // add support to cancel outstanding requests - when the button is pressed we - // could be in one of two stages, 1) waiting to GET the status or 2) - // in the process of GETting the status. Handle both cases by cancelling - // the setTimeout (1) and by setting a flag to indicate that a request has - // been request so we can ignore the results (2). - - var cancelled = false; - var lineage = null; - var lineageTimer = null; - - // update the progress bar value - provenanceTableCtrl.updateProgress(lineageProgress, 0); - - // show the 'searching...' dialog - $('#lineage-query-dialog').modal('setButtonModel', [{ - buttonText: 'Cancel', - color: { - base: '#E3E8EB', - hover: '#C7D2D7', - text: '#004849' - }, - handler: { - click: function () { - cancelled = true; - - // we are waiting for the next poll attempt - if (lineageTimer !== null) { - // cancel it - clearTimeout(lineageTimer); - - // cancel the provenance - closeDialog(); - } - } - } - }]).modal('show'); + }, true) + .on('mouseover', function (d) { + links.filter(function (linkDatum) { + return d.id === linkDatum.flowFileUuid; + }) + .classed('selected', true) + .attr('marker-end', function (d) { + return 'url(#' + d.target.type + '-SELECTED)'; + }); + }) + .on('mouseout', function (d) { + links.filter(function (linkDatum) { + return d.id === linkDatum.flowFileUuid; + }).classed('selected', false) + .attr('marker-end', function (d) { + return 'url(#' + d.target.type + ')'; + }); + }); + var icon = flowfiles.append('g') + .attr({ + 'class': 'flowfile-icon', + 'transform': function (d) { + return 'translate(-9,-9)'; + } + }).append('text') + .attr({ + 'font-family': 'flowfont', + 'font-size': '18px', + 'fill': '#ad9897', + 'transform': function (d) { + return 'translate(0,15)'; + } + }) + .on('mousedown', function (d) { + // empty context menu if necessary + $('#provenance-lineage-context-menu').hide().empty(); + + // prevents the drag event when something other than the + // left button is clicked + if (d3.event.button !== 0) { + d3.event.stopPropagation(); + } + }, true) + .on('mouseover', function (d) { + links.filter(function (linkDatum) { + return d.id === linkDatum.flowFileUuid; + }) + .classed('selected', true) + .attr('marker-end', function (d) { + return 'url(#' + d.target.type + '-SELECTED)'; + }); + }) + .on('mouseout', function (d) { + links.filter(function (linkDatum) { + return d.id === linkDatum.flowFileUuid; + }).classed('selected', false) + .attr('marker-end', function (d) { + return 'url(#' + d.target.type + ')'; + }); + }) + .text(function (d) { + return '\ue808' + }); + }; - // closes the searching dialog and cancels the query on the server - var closeDialog = function () { - // cancel the provenance results since we've successfully processed the results - if (nf.Common.isDefinedAndNotNull(lineage)) { - cancelLineage(lineage); - } + // renders event nodes + var renderEvent = function (events, provenanceTableCtrl) { + events + .classed('event', true) + .append('circle') + .classed('selected', function (d) { + return d.id === eventId; + }) + .attr({ + 'r': 8, + 'fill': '#aabbc3', + 'stroke': '#000', + 'stroke-width': 1.0, + 'id': function (d) { + return 'event-node-' + d.id; + } + }) + .on('contextmenu', function (d) { + // select the current node for a visible cue + d3.select(this).classed('context', true); - // close the dialog - $('#lineage-query-dialog').modal('hide'); - }; + // empty an previous contents - in case they right click on the + // node twice without closing the previous context menu + $('#provenance-lineage-context-menu').hide().empty(); - // polls for the event lineage - var pollLineage = function () { - getLineage(lineage).done(function (response) { - lineage = response.lineage; + var menuItems = [{ + 'class': 'lineage-view-event', + 'text': 'View details', + 'click': function () { + provenanceTableCtrl.showEventDetails(d.id, clusterNodeId); + } + }]; + + // if this is a spawn event show appropriate actions + if (d.eventType === 'SPAWN' || d.eventType === 'CLONE' || d.eventType === 'FORK' || d.eventType === 'JOIN' || d.eventType === 'REPLAY') { + // starts the lineage expansion process + var expandLineage = function (lineageRequest) { + var lineageProgress = $('#lineage-percent-complete'); + + // add support to cancel outstanding requests - when the button is pressed we + // could be in one of two stages, 1) waiting to GET the status or 2) + // in the process of GETting the status. Handle both cases by cancelling + // the setTimeout (1) and by setting a flag to indicate that a request has + // been request so we can ignore the results (2). + + var cancelled = false; + var lineage = null; + var lineageTimer = null; + + // update the progress bar value + provenanceTableCtrl.updateProgress(lineageProgress, 0); + + // show the 'searching...' dialog + $('#lineage-query-dialog').modal('setButtonModel', [{ + buttonText: 'Cancel', + color: { + base: '#E3E8EB', + hover: '#C7D2D7', + text: '#004849' + }, + handler: { + click: function () { + cancelled = true; + + // we are waiting for the next poll attempt + if (lineageTimer !== null) { + // cancel it + clearTimeout(lineageTimer); + + // cancel the provenance + closeDialog(); + } + } + } + }]).modal('show'); - // process the lineage - processLineage(); - }).fail(closeDialog); - }; - // processes the event lineage - var processLineage = function () { - // if the request was cancelled just ignore the current response - if (cancelled === true) { - closeDialog(); - return; - } + // closes the searching dialog and cancels the query on the server + var closeDialog = function () { + // cancel the provenance results since we've successfully processed the results + if (common.isDefinedAndNotNull(lineage)) { + cancelLineage(lineage); + } - // close the dialog if the results contain an error - if (!nf.Common.isEmpty(lineage.results.errors)) { - var errors = lineage.results.errors; - nf.Dialog.showOkDialog({ - headerText: 'Process Lineage', - dialogContent: nf.Common.formatUnorderedList(errors) - }); + // close the dialog + $('#lineage-query-dialog').modal('hide'); + }; - closeDialog(); - return; - } + // polls for the event lineage + var pollLineage = function () { + getLineage(lineage).done(function (response) { + lineage = response.lineage; - // update the precent complete - provenanceTableCtrl.updateProgress(lineageProgress, lineage.percentCompleted); + // process the lineage + processLineage(); + }).fail(closeDialog); + }; - // process the results if they are finished - if (lineage.finished === true) { - var results = lineage.results; + // processes the event lineage + var processLineage = function () { + // if the request was cancelled just ignore the current response + if (cancelled === true) { + closeDialog(); + return; + } - // ensure the events haven't aged off - if (results.nodes.length > 0) { - // update the lineage graph - renderEventLineage(results); - } else { - // inform the user that no results were found - nf.Dialog.showOkDialog({ - headerText: 'Lineage Results', - dialogContent: 'The lineage search has completed successfully but there no results were found. The events may have aged off.' + // close the dialog if the results contain an error + if (!common.isEmpty(lineage.results.errors)) { + var errors = lineage.results.errors; + dialog.showOkDialog({ + headerText: 'Process Lineage', + dialogContent: common.formatUnorderedList(errors) }); - } - // close the searching.. dialog - closeDialog(); - } else { - lineageTimer = setTimeout(function () { - // clear the timer since we've been invoked - lineageTimer = null; + closeDialog(); + return; + } - // for the lineage - pollLineage(); - }, 2000); - } - }; + // update the precent complete + provenanceTableCtrl.updateProgress(lineageProgress, lineage.percentCompleted); + + // process the results if they are finished + if (lineage.finished === true) { + var results = lineage.results; + + // ensure the events haven't aged off + if (results.nodes.length > 0) { + // update the lineage graph + renderEventLineage(results); + } else { + // inform the user that no results were found + dialog.showOkDialog({ + headerText: 'Lineage Results', + dialogContent: 'The lineage search has completed successfully but there no results were found. The events may have aged off.' + }); + } - // once the query is submitted wait until its finished - submitLineage(lineageRequest).done(function (response) { - lineage = response.lineage; - - // process the lineage, if its not done computing wait 1 second before checking again - processLineage(1); - }).fail(closeDialog); - }; - - // handles updating the lineage graph - var renderEventLineage = function (lineageResults) { - addLineage(lineageResults.nodes, lineageResults.links, provenanceTableCtrl); - }; - - // collapses the lineage for the specified event in the specified direction - var collapseLineage = function (eventId, provenanceTableCtrl) { - // get the event in question and collapse in the appropriate direction - provenanceTableCtrl.getEventDetails(eventId, clusterNodeId).done(function (response) { - var provenanceEvent = response.provenanceEvent; - var eventUuid = provenanceEvent.flowFileUuid; - var eventUuids = d3.set(provenanceEvent.childUuids); - - // determines if the specified event should be removable based on if the collapsing is fanning in/out - var allowEventRemoval = function (fanIn, node) { - if (fanIn) { - return node.id !== eventId; + // close the searching.. dialog + closeDialog(); } else { - return node.flowFileUuid !== eventUuid && $.inArray(eventUuid, node.parentUuids) === -1; - } - }; + lineageTimer = setTimeout(function () { + // clear the timer since we've been invoked + lineageTimer = null; - // determines if the specified link should be removable based on if the collapsing is fanning in/out - var allowLinkRemoval = function (fanIn, link) { - if (fanIn) { - return true; - } else { - return link.flowFileUuid !== eventUuid; + // for the lineage + pollLineage(); + }, 2000); } }; - // the event is fan in if the flowfile uuid is in the children - var fanIn = $.inArray(eventUuid, provenanceEvent.childUuids) >= 0; + // once the query is submitted wait until its finished + submitLineage(lineageRequest).done(function (response) { + lineage = response.lineage; - // collapses the specified uuids - var collapse = function (uuids) { - var newUuids = false; + // process the lineage, if its not done computing wait 1 second before checking again + processLineage(1); + }).fail(closeDialog); + }; - // consider each node for being collapsed - $.each(nodeLookup.values(), function (_, node) { - // if this node is in the uuids remove it unless its the original event or is part of this and another lineage - if (uuids.has(node.flowFileUuid) && allowEventRemoval(fanIn, node)) { - // remove it from the look lookup - nodeLookup.remove(node.id); + // handles updating the lineage graph + var renderEventLineage = function (lineageResults) { + addLineage(lineageResults.nodes, lineageResults.links, provenanceTableCtrl); + }; - // include all related outgoing flow file uuids - $.each(node.outgoing, function (_, outgoing) { - if (!uuids.has(outgoing.flowFileUuid)) { - uuids.add(outgoing.flowFileUuid); - newUuids = true; - } - }); + // collapses the lineage for the specified event in the specified direction + var collapseLineage = function (eventId, provenanceTableCtrl) { + // get the event in question and collapse in the appropriate direction + provenanceTableCtrl.getEventDetails(eventId, clusterNodeId).done(function (response) { + var provenanceEvent = response.provenanceEvent; + var eventUuid = provenanceEvent.flowFileUuid; + var eventUuids = d3.set(provenanceEvent.childUuids); + + // determines if the specified event should be removable based on if the collapsing is fanning in/out + var allowEventRemoval = function (fanIn, node) { + if (fanIn) { + return node.id !== eventId; + } else { + return node.flowFileUuid !== eventUuid && $.inArray(eventUuid, node.parentUuids) === -1; } - }); + }; + + // determines if the specified link should be removable based on if the collapsing is fanning in/out + var allowLinkRemoval = function (fanIn, link) { + if (fanIn) { + return true; + } else { + return link.flowFileUuid !== eventUuid; + } + }; + + // the event is fan in if the flowfile uuid is in the children + var fanIn = $.inArray(eventUuid, provenanceEvent.childUuids) >= 0; + + // collapses the specified uuids + var collapse = function (uuids) { + var newUuids = false; + + // consider each node for being collapsed + $.each(nodeLookup.values(), function (_, node) { + // if this node is in the uuids remove it unless its the original event or is part of this and another lineage + if (uuids.has(node.flowFileUuid) && allowEventRemoval(fanIn, node)) { + // remove it from the look lookup + nodeLookup.remove(node.id); + + // include all related outgoing flow file uuids + $.each(node.outgoing, function (_, outgoing) { + if (!uuids.has(outgoing.flowFileUuid)) { + uuids.add(outgoing.flowFileUuid); + newUuids = true; + } + }); + } + }); - // update the link data - $.each(linkLookup.values(), function (_, link) { - // if this link is in the uuids remove it - if (uuids.has(link.flowFileUuid) && allowLinkRemoval(fanIn, link)) { - // remove it from the link lookup - linkLookup.remove(link.id); - - // add a related uuid that needs to be collapse - var next = link.target; - if (!uuids.has(next.flowFileUuid)) { - uuids.add(next.flowFileUuid); - newUuids = true; + // update the link data + $.each(linkLookup.values(), function (_, link) { + // if this link is in the uuids remove it + if (uuids.has(link.flowFileUuid) && allowLinkRemoval(fanIn, link)) { + // remove it from the link lookup + linkLookup.remove(link.id); + + // add a related uuid that needs to be collapse + var next = link.target; + if (!uuids.has(next.flowFileUuid)) { + uuids.add(next.flowFileUuid); + newUuids = true; + } } + }); + + // collapse any related uuids + if (newUuids) { + collapse(uuids); } - }); + }; - // collapse any related uuids - if (newUuids) { - collapse(uuids); - } - }; + // collapse the specified uuids + collapse(eventUuids); - // collapse the specified uuids - collapse(eventUuids); + // update the layout + refresh(provenanceTableCtrl); + }); + }; - // update the layout - refresh(provenanceTableCtrl); + // add menu items + menuItems.push({ + 'class': 'lineage-view-parents', + 'text': 'Find parents', + 'click': function () { + expandLineage({ + lineageRequestType: 'PARENTS', + eventId: d.id, + clusterNodeId: clusterNodeId + }); + } + }, { + 'class': 'lineage-view-children', + 'text': 'Expand', + 'click': function () { + expandLineage({ + lineageRequestType: 'CHILDREN', + eventId: d.id, + clusterNodeId: clusterNodeId + }); + } + }, { + 'class': 'lineage-collapse-children', + 'text': 'Collapse', + 'click': function () { + // collapse the children lineage + collapseLineage(d.id, provenanceTableCtrl); + } }); - }; + } - // add menu items - menuItems.push({ - 'class': 'lineage-view-parents', - 'text': 'Find parents', - 'click': function () { - expandLineage({ - lineageRequestType: 'PARENTS', - eventId: d.id, - clusterNodeId: clusterNodeId - }); - } - }, { - 'class': 'lineage-view-children', - 'text': 'Expand', - 'click': function () { - expandLineage({ - lin
<TRUNCATED>
