Displaying service coarse graph

Change-Id: Iee05e9114255a20ebf600337768d180514239299
diff --git a/views/ngXosViews/serviceGrid/bower.json b/views/ngXosViews/serviceGrid/bower.json
index 18bd401..941c8db 100644
--- a/views/ngXosViews/serviceGrid/bower.json
+++ b/views/ngXosViews/serviceGrid/bower.json
@@ -17,7 +17,8 @@
   "dependencies": {
     "js-yaml": "~3.6.1",
     "jszip": "Stuk/jszip#~3.0.0",
-    "file-saver": "eligrey/FileSaver.js#~1.3.2"
+    "file-saver": "eligrey/FileSaver.js#~1.3.2",
+    "graphlib": "~2.1.0"
   },
   "devDependencies": {
     "jquery": "2.1.4",
@@ -32,5 +33,8 @@
     "angular-chart.js": "~0.10.2",
     "d3": "~3.5.17",
     "angular-recursion": "~1.0.5"
+  },
+  "resolutions": {
+    "lodash": "~4.11.1"
   }
 }
diff --git a/views/ngXosViews/serviceGrid/src/css/main.css b/views/ngXosViews/serviceGrid/src/css/main.css
index ae13614..c36e56d 100644
--- a/views/ngXosViews/serviceGrid/src/css/main.css
+++ b/views/ngXosViews/serviceGrid/src/css/main.css
@@ -1,3 +1,37 @@
+xos-side-panel .xos-side-panel-content {
+  position: fixed;
+  width: 30%;
+  height: 100%;
+  padding: 25px;
+  background: #fff;
+  overflow: scroll; }
+  xos-side-panel .xos-side-panel-content.right {
+    border-left: 1px solid #555555;
+    top: 0;
+    right: 0;
+    visibility: hidden;
+    box-shadow: -10px 0px 20px -8px rgba(0, 0, 0, 0.75); }
+  xos-side-panel .xos-side-panel-content.right.out {
+    animation: 0.5s slideOutRight ease-in-out;
+    visibility: visible; }
+  xos-side-panel .xos-side-panel-content.right.in {
+    animation: 0.5s slideInRight ease-in-out;
+    visibility: visible; }
+
+@keyframes slideInRight {
+  from {
+    transform: translate3d(100%, 0, 0);
+    visibility: visible; }
+  to {
+    transform: translate3d(0, 0, 0); } }
+
+@keyframes slideOutRight {
+  from {
+    transform: translate3d(0, 0, 0); }
+  to {
+    visibility: hidden;
+    transform: translate3d(100%, 0, 0); } }
+
 #xosServiceGrid service-graph {
   display: block;
   width: 100%;
diff --git a/views/ngXosViews/serviceGrid/src/css/side-panel.css b/views/ngXosViews/serviceGrid/src/css/side-panel.css
new file mode 100644
index 0000000..3cde922
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/css/side-panel.css
@@ -0,0 +1,33 @@
+xos-side-panel .xos-side-panel-content {
+  position: fixed;
+  width: 30%;
+  height: 100%;
+  padding: 25px;
+  background: #fff;
+  overflow: scroll; }
+  xos-side-panel .xos-side-panel-content.right {
+    border-left: 1px solid #555555;
+    top: 0;
+    right: 0;
+    visibility: hidden;
+    box-shadow: -10px 0px 20px -8px rgba(0, 0, 0, 0.75); }
+  xos-side-panel .xos-side-panel-content.right.out {
+    animation: 0.5s slideOutRight ease-in-out;
+    visibility: visible; }
+  xos-side-panel .xos-side-panel-content.right.in {
+    animation: 0.5s slideInRight ease-in-out;
+    visibility: visible; }
+
+@keyframes slideInRight {
+  from {
+    transform: translate3d(100%, 0, 0);
+    visibility: visible; }
+  to {
+    transform: translate3d(0, 0, 0); } }
+
+@keyframes slideOutRight {
+  from {
+    transform: translate3d(0, 0, 0); }
+  to {
+    visibility: hidden;
+    transform: translate3d(100%, 0, 0); } }
diff --git a/views/ngXosViews/serviceGrid/src/index.html b/views/ngXosViews/serviceGrid/src/index.html
index 8f95e59..0e3fa40 100644
--- a/views/ngXosViews/serviceGrid/src/index.html
+++ b/views/ngXosViews/serviceGrid/src/index.html
@@ -6,6 +6,7 @@
 <!-- endcss -->
 <!-- inject:css -->
 <link rel="stylesheet" href="/css/main.css">
+<link rel="stylesheet" href="/css/side-panel.css">
 <link rel="stylesheet" href="/../../../xos/core/static/xosNgLib.css">
 <!-- endinject -->
 
@@ -17,6 +18,8 @@
 <script src="vendor/js-yaml/dist/js-yaml.js"></script>
 <script src="vendor/jszip/dist/jszip.js"></script>
 <script src="vendor/file-saver/FileSaver.js"></script>
+<script src="vendor/lodash/lodash.js"></script>
+<script src="vendor/graphlib/dist/graphlib.core.js"></script>
 <script src="vendor/jquery/dist/jquery.js"></script>
 <script src="vendor/angular/angular.js"></script>
 <script src="vendor/angular-mocks/angular-mocks.js"></script>
@@ -24,7 +27,6 @@
 <script src="vendor/angular-cookies/angular-cookies.js"></script>
 <script src="vendor/angular-animate/angular-animate.js"></script>
 <script src="vendor/angular-resource/angular-resource.js"></script>
-<script src="vendor/lodash/lodash.js"></script>
 <script src="vendor/bootstrap-css/js/bootstrap.min.js"></script>
 <script src="vendor/Chart.js/Chart.js"></script>
 <script src="vendor/angular-chart.js/dist/angular-chart.js"></script>
@@ -38,6 +40,7 @@
 <script src="/.tmp/tosca_encoder.service.js"></script>
 <script src="/.tmp/slices_encorder.service.js"></script>
 <script src="/.tmp/site_encode.service.js"></script>
+<script src="/.tmp/sidePanel.component.js"></script>
 <script src="/.tmp/service_encorder.service.js"></script>
 <script src="/.tmp/service-graph.js"></script>
 <script src="/.tmp/networks_encoder.service.js"></script>
diff --git a/views/ngXosViews/serviceGrid/src/js/service-graph.js b/views/ngXosViews/serviceGrid/src/js/service-graph.js
index 51a711a..904466c 100644
--- a/views/ngXosViews/serviceGrid/src/js/service-graph.js
+++ b/views/ngXosViews/serviceGrid/src/js/service-graph.js
@@ -2,6 +2,64 @@
   'use strict';
 
   angular.module('xos.serviceGrid')
+  .service('Graph', function($q, Tenants, Services, Subscribers){
+
+    let tenancyGraph = new graphlib.Graph();
+    let cached = false;
+
+    const buildGraph = () => {
+
+      let deferred = $q.defer();
+
+      $q.all([
+        Tenants.query().$promise,
+        Services.query().$promise,
+        Subscribers.query().$promise
+      ])
+      .then((res) => {
+        let [tenants, services, subscribers] = res;
+        // adding service nodes
+        services.forEach(s => tenancyGraph.setNode(s.id, angular.extend(s, {type: 'service'})));
+
+
+        // coarse tenant
+        tenants.filter(t => t.subscriber_service && t.provider_service)
+          .forEach(t => tenancyGraph.setEdge(t.subscriber_service, t.provider_service, t, t.name));
+
+        // fine grain tenant
+        // adding subscribers as nodes (to build fine grain graph)
+        // subscribers.forEach(s => tenancyGraph.setNode(`sub-${s.id}`, angular.extend(s, {type: 'subscriber'})));
+        // TODO
+        // - Find tenant that start from a subscriber
+        // - Follow the chain: from the first tenant follow where subscriber_tenant = tenant_id untill we cannot find any more tenant
+        // tenants.filter(t => t.subscriber_root && t.provider_service)
+        // .forEach(t => tenancyGraph.setEdge(`sub-${t.subscriber_root}`, t.provider_service, t, t.name));
+        
+        deferred.resolve(tenancyGraph);
+      });
+
+      return deferred.promise;
+    };
+
+    this.getGraph = () => {
+      let deferred = $q.defer();
+
+      if(cached){
+        deferred.resolve(tenancyGraph);
+      }
+      else {
+        buildGraph()
+        .then((res) => {
+          cached = true;
+          deferred.resolve(res);
+        })
+        .catch(console.log);
+      }
+
+      return {$promise: deferred.promise};
+    };
+
+  })
   .directive('serviceGraph', function(){
     return {
       restrict: 'E',
@@ -9,54 +67,61 @@
       bindToController: true,
       controllerAs: 'vm',
       templateUrl: 'templates/service-graph.tpl.html',
-      controller: function($element, GraphService){
+      controller: function($scope, $element, GraphService, Graph, ToscaEncoder){
 
         let svg;
         let el = $element[0];
         let node;
         let link;
+        const _this = this;
 
+        this.panelConfig = {
+          position: 'right'
+        };
+
+        // animate node and links in the correct place
         const tick = (e) => {
-          // Push different nodes in different directions for clustering.
-          
           node
-            // .attr('cx', d => d.x)
-            //   .attr('cy', d => d.y)
-              .attr({
-                transform: d => `translate(${d.x}, ${d.y})`
-              });
+          .attr('cx', d => d.x)
+          .attr('cy', d => d.y)
+          .attr({
+            transform: d => `translate(${d.x}, ${d.y})`
+          });
           
-          link.attr('x1', d => d.source.x)
-              .attr('y1', d => d.source.y)
-              .attr('x2', d => d.target.x)
-              .attr('y2', d => d.target.y);
-        }
+          link
+          .attr('x1', d => d.source.x)
+          .attr('y1', d => d.source.y)
+          .attr('x2', d => getTargetNodeCircumferencePoint(d)[0])
+          .attr('y2', d => getTargetNodeCircumferencePoint(d)[1]);
+        };
 
-        GraphService.loadCoarseData()
-        .then((res) => {
+        Graph.getGraph().$promise
+        .then((graph) => {
 
           // build links
-          res.tenants = res.tenants.map(t => {
+          let links = graph.edges().map(e => {
             return {
-              source: t.provider_service,
-              target: t.subscriber_service
+              source: graph.node(e.v),
+              target: graph.node(e.w)
             }
           });
+          let nodes = graph.nodes().map(n => graph.node(n));
 
-          // add xos as a node
-          res.services.push({
-            name: 'XOS',
-            class: 'xos',
-            x: el.clientWidth / 2,
-            y: el.clientHeight / 2,
-            fixed: true
-          })
+           //add xos as a node
+          nodes.push({
+             name: 'XOS',
+             type: 'xos',
+             x: el.clientWidth / 2,
+             y: el.clientHeight / 2,
+             fixed: true
+           });
 
           handleSvg(el);
+          defineArrows();
 
           var force = d3.layout.force()
-            .nodes(res.services)
-            .links(res.tenants)
+            .nodes(nodes)
+            .links(links)
             .charge(-1060)
             .gravity(0.1)
             .linkDistance(200)
@@ -65,38 +130,66 @@
             .start();
 
           link = svg.selectAll('.link')
-          .data(res.tenants).enter().insert('line')
-                .attr('class', 'link');
+            .data(links).enter().insert('line')
+            .attr('class', 'link')
+            .attr('marker-end', 'url(#arrow)');
 
           node = svg.selectAll('.node')
-            .data(res.services)
+            .data(nodes)
             .enter().append('g')
             .call(force.drag)
-            .on("mousedown", function() { d3.event.stopPropagation(); });
+            .on('mousedown', function(d) {
+              $scope.$apply(() => {
+                if(d.name === 'XOS'){
+                  return;
+                }
+                _this.panelShow = true;
+                let status = parseInt(d.backend_status.match(/^[0-9]/)[0]);
+                console.log(status);
+                switch(status){
+                  case 0:
+                    d.icon = 'time';
+                    break;
+                  case 1:
+                    d.icon = 'ok';
+                    break;
+                  case 2:
+                    d.icon = 'remove';
+                    break;
+                }
+                _this.selectedNode = d;
+              });
+              d3.event.stopPropagation();
+            });
 
           node.append('circle')
             .attr({
-              class: d => `node ${d.class || ''}`,
+              class: d => `node ${d.type || ''}`,
               r: 10
             });
 
           node.append('text')
             .attr({
-              'text-anchor': 'middle'
+              'text-anchor': 'middle',
+              'alignment-baseline': 'middle'
             })
-            .text(d => d.name)
+            .text(d => d.humanReadableName || d.name);
 
+          // scale the node to fit the contained text
           node.select('circle')
             .attr({
               r: function(d){
-                let parent = d3.select(this).node().parentNode;
-                let sib = d3.select(parent).select('text').node().getBBox()
-                return (sib.width / 2) + 10
-                
+                const parent = d3.select(this).node().parentNode;
+                const sib = d3.select(parent).select('text').node().getBBox();
+                const radius = (sib.width / 2) + 10;
+
+                // add radius as node attribute
+                d.nodeWidth = radius * 2;
+                return radius;
               }
             })
 
-        })
+        });
 
         const handleSvg = (el) => {
           d3.select(el).select('svg').remove();
@@ -105,76 +198,39 @@
           .append('svg')
           .style('width', `${el.clientWidth}px`)
           .style('height', `${el.clientHeight}px`);
+        };
+
+        const defineArrows = () => {
+          svg.append('svg:defs').selectAll('marker')
+          .data(['arrow'])      // Different link/path types can be defined here
+          .enter().append('svg:marker')    // This section adds in the arrows
+          .attr('id', String)
+          .attr('viewBox', '0 -5 10 10')
+          .attr('refX', 10)
+          .attr('refY', 0)
+          .attr('markerWidth', 6)
+          .attr('markerHeight', 6)
+          .attr('orient', 'auto')
+          .append('svg:path')
+          .attr('d', 'M0,-5L10,0L0,5');
+        };
+
+        const getTargetNodeCircumferencePoint = d => {
+          const radius = d.target.nodeWidth / 2; // nodeWidth is just a custom attribute I calculate during the creation of the nodes depending on the node width
+          const dx = d.target.x - d.source.x;
+          const dy = d.target.y - d.source.y;
+          const gamma = Math.atan2(dy, dx); // Math.atan2 returns the angle in the correct quadrant as opposed to Math.atan
+
+          const tx = d.target.x - (Math.cos(gamma) * radius);
+          const ty = d.target.y - (Math.sin(gamma) * radius);
+
+          return [tx, ty];
+        };
+
+        this.exportToTosca = (service) => {
+          ToscaEncoder.serviceToTosca(service);
         }
       }
     };
   })
-})();
-
-// Draw services around xos and calculate coarse tenant as links
-
-// var width = 960, height = 500;
-
-// var fill = d3.scale.category10();
-
-// var nodes = [
-//   {id: 1},
-//   {id: 2},
-//   {id: 3},
-//   {id: 4},
-//   {id: 5}
-// ];
-
-// var links = [
-//   {source: 1, target: 2},
-//   {source: 2, target: 3}
-// ];
-
-// var svg = d3.select("body").append("svg")
-//     .attr("width", width)
-//     .attr("height", height);
-
-// var force = d3.layout.force()
-//     .nodes(nodes)
-//     .links(links)
-//     .charge(-8*12)
-//     .gravity(0.1)
-//     .size([width, height])
-//     .on("tick", tick)
-//     .start();
-
-// svg.append('circle')
-// .attr({
-//   "class": "xos",
-//   r: 20,
-//   cx: () => width / 2,
-//   cy: () => height / 2,
-// })
-
-// var node = svg.selectAll(".node")
-//     .data(nodes)
-//   .enter().append("circle")
-//     .attr("class", "node")
-//     .attr("cx", ({ x }) => x)
-//     .attr("cy", ({ y }) => y)
-//     .attr("r", 8)
-//     .style("fill", ({}, index) => fill(index & 3))
-//     .style("stroke", ({}, index) => d3.rgb(fill(index & 3)).darker(2))
-//     .call(force.drag)
-//     .on("mousedown", ({}) => d3.event.stopPropagation());
-
-// var link = svg.selectAll(".link")
-// .data(links).enter().insert("line")
-//       .attr("class", "link");
-
-// function tick(e) {
-//   // Push different nodes in different directions for clustering.
-  
-//   node.attr("cx", function(d) { return d.x; })
-//       .attr("cy", function(d) { return d.y; });
-  
-//   link.attr("x1", function(d) { return d.source.x; })
-//       .attr("y1", function(d) { return d.source.y; })
-//       .attr("x2", function(d) { return d.target.x; })
-//       .attr("y2", function(d) { return d.target.y; });
-// }
+})();
\ No newline at end of file
diff --git a/views/ngXosViews/serviceGrid/src/js/sidePanel.component.js b/views/ngXosViews/serviceGrid/src/js/sidePanel.component.js
new file mode 100644
index 0000000..4421ee7
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/js/sidePanel.component.js
@@ -0,0 +1,74 @@
+/**
+ * © OpenCORD
+ *
+ * Visit http://guide.xosproject.org/devguide/addview/ for more information
+ *
+ * Created by teone on 7/18/16.
+ */
+
+(function () {
+  'use strict';
+
+  angular.module('xos.uiComponents')
+  .directive('xosSidePanel', function(){
+    return {
+      restrict: 'E',
+      scope: {
+        config: '=',
+        show: '='
+      },
+      template: `
+        <div class="xos-side-panel-content {{vm.classes.join(' ')}}">
+          <div class="row">
+            <div class="col-xs-12">
+              <button type="button" class="close" ng-click="vm.dismiss()">
+                <span aria-hidden="true">&times;</span>
+              </button>
+            </div>
+          </div>
+          <div class="row">
+            <div class="col-xs-12" ng-transclude></div>
+          </div>
+        </div>
+      `,
+      transclude: true,
+      bindToController: true,
+      controllerAs: 'vm',
+      controller: function($scope, $timeout, _){
+        console.log(this.show);
+
+        this.classes = [];
+
+        this.classes.push(this.config.position);
+
+        this.dismiss = () => {
+          this.show = false;
+          this.classes = this.toggleClass(this.classes);
+          $timeout(() => {
+            return _.remove(this.classes, c => c === 'out');
+          }, 500);
+        };
+
+        this.toggleClass = (classes) => {
+          if(classes.indexOf('in') > -1){
+            _.remove(this.classes, c => c === 'in');
+            this.classes.push('out');
+            return classes;
+          }
+          _.remove(this.classes, c => c === 'out');
+          this.classes.push('in');
+          return classes;
+        };
+
+        $scope.$watch(() => this.show, val => {
+          if (angular.isDefined(val)){
+            if (val && val === true){
+              this.classes = this.toggleClass(this.classes);
+            }
+          }
+        })
+      }
+    }
+  });
+})();
+
diff --git a/views/ngXosViews/serviceGrid/src/sass/main.scss b/views/ngXosViews/serviceGrid/src/sass/main.scss
index bc8d14a..e892746 100644
--- a/views/ngXosViews/serviceGrid/src/sass/main.scss
+++ b/views/ngXosViews/serviceGrid/src/sass/main.scss
@@ -1,4 +1,5 @@
 @import '../../../../style/sass/lib/_variables.scss';
+@import './side-panel.scss';
 
 #xosServiceGrid {
   service-graph {
diff --git a/views/ngXosViews/serviceGrid/src/sass/side-panel.scss b/views/ngXosViews/serviceGrid/src/sass/side-panel.scss
new file mode 100644
index 0000000..0d5d508
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/sass/side-panel.scss
@@ -0,0 +1,54 @@
+@import '../../../../style/sass/lib/_variables.scss';
+
+xos-side-panel{
+
+  .xos-side-panel-content{
+    position: fixed;
+    width: 30%;
+    height: 100%;
+    padding: 25px;
+    background: #fff;
+    overflow: scroll;
+
+    &.right {
+      border-left: 1px solid $gray;
+      top: 0;
+      //right: -35%;
+      right: 0;
+      visibility: hidden;
+      box-shadow: -10px 0px 20px -8px rgba(0,0,0,0.75);
+    }
+
+    &.right.out {
+      animation:  0.5s slideOutRight ease-in-out;
+      visibility: visible;
+    }
+
+    &.right.in {
+      animation:  0.5s slideInRight ease-in-out;
+      visibility: visible;
+    }
+  }
+}
+
+@keyframes slideInRight {
+  from {
+    transform: translate3d(100%, 0, 0);
+    visibility: visible;
+  }
+
+  to {
+    transform: translate3d(0, 0, 0);
+  }
+}
+
+@keyframes slideOutRight {
+  from {
+    transform: translate3d(0, 0, 0);
+  }
+
+  to {
+    visibility: hidden;
+    transform: translate3d(100%, 0, 0);
+  }
+}
\ No newline at end of file
diff --git a/views/ngXosViews/serviceGrid/src/templates/service-graph.tpl.html b/views/ngXosViews/serviceGrid/src/templates/service-graph.tpl.html
index c61da91..328ea7d 100644
--- a/views/ngXosViews/serviceGrid/src/templates/service-graph.tpl.html
+++ b/views/ngXosViews/serviceGrid/src/templates/service-graph.tpl.html
@@ -1,9 +1,6 @@
 <div class="row">
   <div class="col-sm-10">
     <h1>Graph</h1>
-    <ul>
-      <li>Use D3 to create a service chart based on coarse services?</li>
-    </ul>
   </div>
   <div class="col-sm-2">
     <a href="/admin/core/service/add" class="btn btn-success btn-block">
@@ -14,4 +11,30 @@
       Service List
     </a>
   </div>
-</div>
\ No newline at end of file
+</div>
+<xos-side-panel config="vm.panelConfig" show="vm.panelShow">
+  <h1>
+    {{vm.selectedNode.name}}
+    <small>
+      <i class="glyphicon glyphicon-{{vm.selectedNode.icon}}"></i>
+    </small>
+  </h1>
+  <table class="table">
+    <tr>
+      <td>Kind:</td>
+      <td>{{vm.selectedNode.kind}}</td>
+    </tr>
+    <tr>
+      <td>Enabled:</td>
+      <td>{{vm.selectedNode.enabled}}</td>
+    </tr>
+    <tr>
+      <td>Status:</td>
+      <td>{{vm.selectedNode.backend_status}}</td>
+    </tr>
+  </table>
+  <a ng-click="vm.exportToTosca(vm.selectedNode)" class="btn btn-primary">
+    Export to TOSCA
+    <i class="glyphicon glyphicon-export"></i>
+  </a>
+</xos-side-panel>
\ No newline at end of file
diff --git a/views/ngXosViews/serviceGrid/src/templates/service-grid.tpl.html b/views/ngXosViews/serviceGrid/src/templates/service-grid.tpl.html
index 8e88e38..37402d6 100644
--- a/views/ngXosViews/serviceGrid/src/templates/service-grid.tpl.html
+++ b/views/ngXosViews/serviceGrid/src/templates/service-grid.tpl.html
@@ -7,9 +7,9 @@
       <i class="glyphicon glyphicon-plus"></i>
       Add Service
     </a>
-    <!-- <a href="#/graph" class="btn btn-default btn-block">
+    <a href="#/graph" class="btn btn-default btn-block">
       Tenancy Graph
-    </a> -->
+    </a>
   </div>
 </div>