Added support code for JobDetails page in admin console

git-svn-id: https://hyracks.googlecode.com/svn/branches/hyracks_dev_next@1140 123451ca-8445-de46-9d55-352943316053
diff --git a/hyracks-control-cc/src/main/resources/static/javascript/adminconsole/Graphs.js b/hyracks-control-cc/src/main/resources/static/javascript/adminconsole/Graphs.js
new file mode 100644
index 0000000..bc3b95b
--- /dev/null
+++ b/hyracks-control-cc/src/main/resources/static/javascript/adminconsole/Graphs.js
@@ -0,0 +1,148 @@
+Graphs = {};
+
+Graphs.DAG = function() {
+    this.nodeMap = {};
+    this.edgeMap = {};
+}
+
+Graphs.DAG.prototype.addNode = function(key, attachment) {
+    var node = new Graphs.Node(this, key, attachment);
+    this.nodeMap[key] = node;
+    return node;
+}
+
+Graphs.DAG.prototype.addEdge = function(key, attachment, sNode, sIndex, tNode, tIndex) {
+    var edge = new Graphs.DirectedEdge(this, key, attachment, sNode, sIndex, tNode, tIndex);
+    this.edgeMap[key] = edge;
+    sNode.outEdges[sIndex] = edge;
+    tNode.inEdges[tIndex] = edge;
+    return edge;
+}
+
+Graphs.DAG.prototype.lookupNode = function(key) {
+    return this.nodeMap[key];
+}
+
+Graphs.DAG.prototype.lookupEdge = function(key) {
+    return this.edgeMap[key];
+}
+
+Graphs.DAG.prototype.walkNodes = function(callback) {
+    for ( var nKey in this.nodeMap) {
+        callback(this.nodeMap[nKey]);
+    }
+}
+
+Graphs.DAG.prototype.walkEdges = function(callback) {
+    for ( var eKey in this.edgeMap) {
+        callback(this.edgeMap[eKey]);
+    }
+}
+
+Graphs.DAG.prototype.findRoots = function() {
+    var roots = [];
+    var callback = function(node) {
+        if (node.inEdges.length == 0) {
+            roots.push(node);
+        }
+    }
+    this.walkNodes(callback);
+    return roots;
+}
+
+Graphs.DAG.prototype.findLeaves = function() {
+    var leaves = [];
+    var callback = function(node) {
+        if (node.outEdges.length == 0) {
+            leaves.push(node);
+        }
+    }
+    this.walkNodes(callback);
+    return leaves;
+}
+
+Graphs.DAG.prototype.tsort = function() {
+    var sortedNodes = [];
+    var nodeState = {};
+
+    function visit(node) {
+        if (!nodeState[node.key]) {
+            nodeState[node.key] = true;
+            for ( var i = 0; i < node.inEdges.length; ++i) {
+                visit(node.inEdges[i].sNode);
+            }
+            sortedNodes.push(node);
+        }
+    }
+
+    var roots = this.findLeaves();
+    for ( var i = 0; i < roots.length; ++i) {
+        visit(roots[i]);
+    }
+    return sortedNodes;
+}
+
+Graphs.DAG.prototype.stratify = function() {
+    var sortedNodes = this.tsort();
+    var stratumMap = {};
+    var strata = [];
+    for ( var i = 0; i < sortedNodes.length; ++i) {
+        var node = sortedNodes[i];
+        var maxParentStratum = -1;
+        for ( var j = 0; j < node.inEdges.length; ++j) {
+            var edge = node.inEdges[j];
+            maxParentStratum = Math.max(maxParentStratum, stratumMap[edge.sNode.key]);
+        }
+        var stratum = maxParentStratum + 1;
+        stratumMap[node.key] = stratum;
+        var stratumList = strata[stratum];
+        if (!stratumList) {
+            stratumList = [];
+            strata[stratum] = stratumList;
+        }
+        stratumList.push(node);
+    }
+    return strata;
+}
+
+Graphs.Node = function(dag, key, attachment) {
+    this.dag = dag;
+    this.key = key;
+    this.attachment = attachment;
+    this.inEdges = [];
+    this.outEdges = [];
+}
+
+Graphs.DirectedEdge = function(dag, key, attachment, sNode, sIndex, tNode, tIndex) {
+    this.dag = dag;
+    this.key = key;
+    this.attachment = attachment;
+    this.sNode = sNode;
+    this.sIndex = sIndex;
+    this.tNode = tNode;
+    this.tIndex = tIndex;
+}
+
+Graphs.JsPlumbRenderer = function(dag, element, options) {
+    this.dag = dag;
+    this.element = element;
+    this.options = options;
+}
+
+Graphs.JsPlumbRenderer.prototype.refresh = function() {
+    var strata = this.dag.stratify();
+
+    while (this.element.hasChildNodes()) {
+        this.element.removeChild(this.element.lastChild);
+    }
+    for ( var i = 0; i < strata.length; ++i) {
+        var stratumList = strata[i];
+        for ( var j = 0; j < stratumList.length; ++j) {
+            var node = stratumList[j];
+            var div = document.createElement('div');
+            div.id = node.key;
+            div.dagNode = node;
+            this.element.appendChild(div);
+        }
+    }
+}
\ No newline at end of file
diff --git a/hyracks-control-cc/src/main/resources/static/javascript/adminconsole/JobDetailsPage.js b/hyracks-control-cc/src/main/resources/static/javascript/adminconsole/JobDetailsPage.js
new file mode 100644
index 0000000..828e310
--- /dev/null
+++ b/hyracks-control-cc/src/main/resources/static/javascript/adminconsole/JobDetailsPage.js
@@ -0,0 +1,85 @@
+$(function() {
+    var jobSpecDAG = new Graphs.DAG();
+    var jobSpecRenderer;
+
+    function drawJobGraph() {
+        var jobGraphDiv = $('#job-graph')[0];
+        jobSpecRenderer = new Graphs.JsPlumbRenderer(jobSpecDAG, jobGraphDiv, null);
+        jobSpecRenderer.refresh();
+    }
+
+    function onJobRunDataReceived(data) {
+        var run = data.result;
+
+        if (run.status != 'TERMINATED' && run.status != 'FAILURE') {
+            setTimeout(fetchJobRun, 10000);
+        }
+    }
+
+    function fetchJobRun() {
+        $.ajax({
+            url : '/rest/jobs/' + $.getURLParam('job-id') + '/job-run',
+            method : 'GET',
+            dataType : 'json',
+            success : onJobRunDataReceived
+        });
+    }
+
+    function onJobActivityGraphDataReceived(data) {
+        var jag = data.result;
+        activityMap = new Object;
+        var activities = jag.activities;
+        for ( var i = 0; i < activities.length; ++i) {
+            var activity = activities[i];
+        }
+
+        drawJobGraph();
+
+        fetchJobRun();
+    }
+
+    function fetchJobActivityGraph() {
+        $.ajax({
+            url : '/rest/jobs/' + $.getURLParam('job-id') + '/job-activity-graph',
+            method : 'GET',
+            dataType : 'json',
+            success : onJobActivityGraphDataReceived
+        });
+    }
+
+    function onJobSpecificationDataReceived(data) {
+        var jobSpec = data.result;
+        var operators = jobSpec.operators;
+        for ( var i = 0; i < operators.length; ++i) {
+            var op = operators[i];
+            jobSpecDAG.addNode(op.id, op);
+        }
+        var connectors = jobSpec.connectors;
+        for ( var i = 0; i < connectors.length; ++i) {
+            var conn = connectors[i];
+            var sNode = jobSpecDAG.lookupNode(conn['in-operator-id']);
+            var sIndex = conn['in-operator-port'];
+            var tNode = jobSpecDAG.lookupNode(conn['out-operator-id']);
+            var tIndex = conn['out-operator-port'];
+            jobSpecDAG.addEdge(conn.id, conn, sNode, sIndex, tNode, tIndex);
+        }
+        fetchJobActivityGraph();
+    }
+
+    function fetchJobSpecification() {
+        $.ajax({
+            url : '/rest/jobs/' + $.getURLParam('job-id') + '/job-specification',
+            method : 'GET',
+            dataType : 'json',
+            success : onJobSpecificationDataReceived
+        });
+    }
+
+    function init() {
+        fetchJobSpecification();
+    }
+
+    jsPlumb.bind("ready", function() {
+        init();
+    });
+});
\ No newline at end of file