[CORD-1043] Fine-grained service graph first draft

Change-Id: I16566b0c38dda64fa920120ce16ea699ca157279
diff --git a/src/app/service-graph/services/graph.store.ts b/src/app/service-graph/services/graph.store.ts
index 821da41..0ed0960 100644
--- a/src/app/service-graph/services/graph.store.ts
+++ b/src/app/service-graph/services/graph.store.ts
@@ -3,13 +3,12 @@
 import {IXosModelStoreService} from '../../datasources/stores/model.store';
 import {
   IXosServiceGraph, IXosServiceModel, IXosTenantModel, IXosCoarseGraphData,
-  IXosServiceGraphNode, IXosServiceGraphLink
+  IXosServiceGraphNode, IXosServiceGraphLink, IXosFineGrainedGraphData
 } from '../interfaces';
 import {IXosDebouncer} from '../../core/services/helpers/debounce.helper';
 export interface IXosServiceGraphStore {
   get(): Observable<IXosServiceGraph>;
   getCoarse(): Observable<IXosServiceGraph>;
-  dispose(): void;
 }
 
 export class XosServiceGraphStore implements IXosServiceGraphStore {
@@ -20,18 +19,22 @@
   ];
 
   // graph data store
-  private graphData: BehaviorSubject<IXosCoarseGraphData> = new BehaviorSubject({
+  private graphData: BehaviorSubject<IXosFineGrainedGraphData> = new BehaviorSubject({
     services: [],
-    tenants: []
+    tenants: [],
+    networks: [],
+    subscribers: []
   });
 
-  // reprentations of the graph as D3 requires
+  // representation of the graph as D3 requires
   private d3CoarseGraph = new BehaviorSubject({});
   private d3FineGrainedGraph = new BehaviorSubject({});
 
   // storing locally reference to the data model
   private services;
   private tenants;
+  private subscribers;
+  private networks;
 
   // debounced functions
   private handleData;
@@ -39,6 +42,8 @@
   // datastore
   private ServiceSubscription: Subscription;
   private TenantSubscription: Subscription;
+  private SubscriberSubscription: Subscription;
+  private NetworkSubscription: Subscription;
 
   constructor (
     private $log: ng.ILogService,
@@ -51,15 +56,16 @@
     // we want to have a quiet period of 500ms from the last event before doing anything
     this.handleData = this.XosDebouncer.debounce(this._handleData, 500, this, false);
 
-
     // observe models and populate graphData
+    // TODO get Nodes (model that represent compute nodes in a pod)
+    // TODO get Instances (model that represent deployed VMs)
     this.ServiceSubscription = this.XosModelStore.query('Service', '/core/services')
       .subscribe(
         (res) => {
           this.combineData(res, 'services');
         },
         (err) => {
-          this.$log.error(`[XosServiceGraphStore] graphData Observable: `, err);
+          this.$log.error(`[XosServiceGraphStore] Service Observable: `, err);
         }
       );
 
@@ -69,31 +75,41 @@
           this.combineData(res, 'tenants');
         },
         (err) => {
-          this.$log.error(`[XosServiceGraphStore] graphData Observable: `, err);
+          this.$log.error(`[XosServiceGraphStore] Tenant Observable: `, err);
         }
       );
 
-    // observe graphData and build Coarse or FineGrained graphs (based on who's subscribed)
-    this.graphData
+    this.SubscriberSubscription = this.XosModelStore.query('Subscriber', '/core/subscribers')
       .subscribe(
-        (res: IXosCoarseGraphData) => {
-          if (this.d3CoarseGraph.observers.length > 0) {
-            this.graphDataToCoarseGraph(res);
-          }
-          if (this.d3FineGrainedGraph.observers.length > 0) {
-            // TODO graphDataToFineGrainedGraph
-          }
+        (res) => {
+          this.combineData(res, 'subscribers');
+        },
+        (err) => {
+          this.$log.error(`[XosServiceGraphStore] Subscriber Observable: `, err);
+        }
+      );
+
+    this.NetworkSubscription = this.XosModelStore.query('Network', '/core/networks')
+      .subscribe(
+        (res) => {
+          this.combineData(res, 'networks');
         },
         (err) => {
           this.$log.error(`[XosServiceGraphStore] graphData Observable: `, err);
         }
       );
-  }
 
-  public dispose() {
-    // cancel subscriptions from observables
-    this.ServiceSubscription.unsubscribe();
-    this.TenantSubscription.unsubscribe();
+    // observe graphData and build Coarse and FineGrained graphs
+    this.graphData
+      .subscribe(
+        (res: IXosFineGrainedGraphData) => {
+          this.graphDataToCoarseGraph(res);
+          this.graphDataToFineGrainedGraph(res);
+        },
+        (err) => {
+          this.$log.error(`[XosServiceGraphStore] graphData Observable: `, err);
+        }
+      );
   }
 
   public get() {
@@ -104,7 +120,7 @@
     return this.d3CoarseGraph.asObservable();
   }
 
-  private combineData(data: any, type: 'services'|'tenants') {
+  private combineData(data: any, type: 'services'|'tenants'|'subscribers'|'networks') {
     switch (type) {
       case 'services':
         this.services = data;
@@ -112,6 +128,12 @@
       case 'tenants':
         this.tenants = data;
         break;
+      case 'subscribers':
+        this.subscribers = data;
+        break;
+      case 'networks':
+        this.networks = data;
+        break;
     }
     this.handleData(this.services, this.tenants);
   }
@@ -119,23 +141,71 @@
   private _handleData(services: IXosServiceModel[], tenants: IXosTenantModel[]) {
     this.graphData.next({
       services: this.services,
-      tenants: this.tenants
+      tenants: this.tenants,
+      subscribers: this.subscribers,
+      networks: this.networks
     });
   }
 
-  private getCoarseNodeIndexById(id: number, nodes: IXosServiceModel[]) {
+  private getNodeIndexById(id: number | string, nodes: IXosServiceModel[]) {
     return _.findIndex(nodes, {id: id});
   }
 
+  private d3Id(type: string, id: number) {
+    return `${type.toLowerCase()}~${id}`;
+  }
+
+  private getTargetId(tenant: IXosTenantModel) {
+
+    let targetId;
+    if (tenant.subscriber_service_id) {
+      targetId = this.d3Id('service', tenant.subscriber_service_id);
+    }
+    else if (tenant.subscriber_tenant_id) {
+      targetId = this.d3Id('tenant', tenant.subscriber_tenant_id);
+    }
+    else if (tenant.subscriber_network_id) {
+      targetId = this.d3Id('network', tenant.subscriber_network_id);
+    }
+    else if (tenant.subscriber_root_id) {
+      targetId = this.d3Id('subscriber', tenant.subscriber_root_id);
+    }
+    return targetId;
+  }
+
+  private getSourceId(tenant: IXosTenantModel) {
+    return this.d3Id('service', tenant.provider_service_id);
+  }
+
+  private getNodeType(n: any) {
+    return n.class_names.split(',')[0].toLowerCase();
+  }
+
+  private getNodeLabel(n: any) {
+    if (this.getNodeType(n) === 'tenant') {
+      return n.id;
+    }
+    return n.humanReadableName ? n.humanReadableName : n.name;
+  }
+
+  private removeUnwantedFineGrainedData(data: IXosFineGrainedGraphData): IXosFineGrainedGraphData {
+    data.tenants = _.filter(data.tenants, t => t.kind !== 'coarse');
+    data.networks = _.filter(data.networks, n => {
+      const subscriber = _.findIndex(data.tenants, {subscriber_network_id: n.id});
+      return subscriber > -1;
+    });
+    return data;
+  }
+
   private graphDataToCoarseGraph(data: IXosCoarseGraphData) {
-    // TODO find how to bind source/target by node ID and not by position in array (ask Simon?)
+
     const links: IXosServiceGraphLink[] = _.chain(data.tenants)
       .filter((t: IXosTenantModel) => t.kind === 'coarse')
       .map((t: IXosTenantModel) => {
         return {
           id: t.id,
-          source: this.getCoarseNodeIndexById(t.provider_service_id, data.services),
-          target: this.getCoarseNodeIndexById(t.subscriber_service_id, data.services),
+          source: this.getNodeIndexById(t.provider_service_id, data.services),
+          target: this.getNodeIndexById(t.subscriber_service_id, data.services),
           model: t
         };
       })
@@ -155,4 +225,61 @@
     });
   }
 
+  private graphDataToFineGrainedGraph(data: IXosFineGrainedGraphData) {
+
+    data = this.removeUnwantedFineGrainedData(data);
+
+    let nodes = _.reduce(Object.keys(data), (list: any[], k: string) => {
+      return list.concat(data[k]);
+    }, []);
+
+    nodes = _.chain(nodes)
+      .map(n => {
+        n.d3Id = this.d3Id(this.getNodeType(n), n.id);
+        return n;
+      })
+      .map(n => {
+        let node: IXosServiceGraphNode = {
+          id: n.d3Id,
+          label: this.getNodeLabel(n),
+          model: n,
+          type: this.getNodeType(n)
+        };
+        return node;
+      })
+      .value();
+
+    const links = _.reduce(data.tenants, (links: IXosServiceGraphLink[], tenant: IXosTenantModel) => {
+      const sourceId = this.getSourceId(tenant);
+      const targetId = this.getTargetId(tenant);
+
+      const tenantToProvider = {
+        id: `${sourceId}_${tenant.d3Id}`,
+        source: this.getNodeIndexById(sourceId, nodes),
+        target: this.getNodeIndexById(tenant.d3Id, nodes),
+        model: tenant
+      };
+
+      const tenantToSubscriber = {
+        id: `${tenant.d3Id}_${targetId}`,
+        source: this.getNodeIndexById(tenant.d3Id, nodes),
+        target: this.getNodeIndexById(targetId, nodes),
+        model: tenant
+      };
+
+      links.push(tenantToProvider);
+      links.push(tenantToSubscriber);
+      return links;
+    }, []);
+
+    if (nodes.length === 0 || links.length === 0) {
+      return;
+    }
+
+    this.d3FineGrainedGraph.next({
+      nodes: nodes,
+      links: links
+    });
+  }
+
 }