[CORD-2424] Adding Instances and Networks to the graph

Change-Id: Ib30081f4995930d979447af59124896f1308f54d
diff --git a/src/app/service-graph/services/graph.store.ts b/src/app/service-graph/services/graph.store.ts
index 4d46c88..52100e7 100644
--- a/src/app/service-graph/services/graph.store.ts
+++ b/src/app/service-graph/services/graph.store.ts
@@ -30,6 +30,8 @@
   nodesFromGraph(graph: Graph): IXosSgNode[];
   linksFromGraph(graph: Graph): IXosSgLink[];
   toggleServiceInstances(): Graph;
+  toggleInstances(): Graph;
+  toggleNetwork(): Graph;
 }
 
 export class XosGraphStore implements IXosGraphStore {
@@ -41,16 +43,22 @@
 
   // state
   private serviceInstanceShown: boolean = false;
+  private instanceShown: boolean = false;
+  private networkShown: boolean = false;
 
   // graphs
   private serviceGraph: any;
   private ServiceGraphSubject: BehaviorSubject<any>;
 
   // datastore
+  private InstanceSubscription: Subscription;
+  private NetworkSubscription: Subscription;
+  private PortSubscription: Subscription;
   private ServiceSubscription: Subscription;
   private ServiceDependencySubscription: Subscription;
   private ServiceInstanceSubscription: Subscription;
   private ServiceInstanceLinkSubscription: Subscription;
+  private TenantWithContainerSubscription: Subscription;
 
   // debounced
   private efficientNext = this.XosDebouncer.debounce(this.callNext, 500, this, false);
@@ -76,6 +84,7 @@
   public nodesFromGraph(graph: Graph): IXosSgNode[] {
     return _.map(graph.nodes(), (n: string) => {
       const nodeData = graph.node(n);
+
       return {
         id: n,
         type: this.getModelType(nodeData),
@@ -87,16 +96,9 @@
   public linksFromGraph(graph: Graph): IXosSgLink[] {
     const nodes = this.nodesFromGraph(graph);
 
-    // NOTE we'll need some intelligence here to differentiate between:
-    // - ServiceDependency
-    // - ServiceInstanceLinks
-    // - Owners
-
     return _.map(graph.edges(), l => {
       const link = graph.edge(l);
       const linkType = this.getModelType(link);
-
-      // FIXME consider ownership links
       let sourceId, targetId;
 
       switch (linkType) {
@@ -105,17 +107,28 @@
           targetId = this.getServiceId(link.provider_service_id);
           break;
         case 'serviceinstancelink':
+          // NOTE ServiceInstanceLink can actually also connect to a service and a network
           sourceId = this.getServiceInstanceId(link.subscriber_service_instance_id);
           targetId = this.getServiceInstanceId(link.provider_service_instance_id);
           break;
         case 'ownership':
           sourceId = this.getServiceId(link.service);
           targetId = this.getServiceInstanceId(link.service_instance);
+          break;
+        case 'instance_ownership':
+          sourceId = this.getServiceInstanceId(link.id);
+          targetId = this.getInstanceId(link.instance_id);
+          break;
+        case 'port':
+          sourceId = this.getInstanceId(link.instance_id);
+          targetId = this.getNetworkId(link.network_id);
+          break;
       }
 
       // NOTE help while debugging
       if (!sourceId || !targetId) {
         this.$log.warn(`Link ${l.v}-${l.w} has missing source or target:`, l, link);
+        // TODO return null and then filter out so that we don't break the rendering
       }
 
       return {
@@ -136,6 +149,18 @@
 
       // remove nodes from the graph
       this.removeElementsFromGraph('serviceinstance'); // NOTE links are automatically removed by the graph library
+
+      if (this.instanceShown) {
+        // NOTE if we remove ServiceInstances we also need to remove Instances
+        this.removeElementsFromGraph('instance');
+        this.instanceShown = false;
+      }
+
+      if (this.networkShown) {
+        // NOTE if we remove ServiceInstances we also need to remove Networks
+        this.removeElementsFromGraph('network');
+        this.networkShown = false;
+      }
     }
     else {
       // NOTE subscribe to ServiceInstance and ServiceInstanceLink observables
@@ -146,6 +171,43 @@
     return this.serviceGraph;
   }
 
+  public toggleInstances(): Graph {
+    if (this.instanceShown) {
+
+      this.InstanceSubscription.unsubscribe();
+      this.TenantWithContainerSubscription.unsubscribe();
+
+      this.removeElementsFromGraph('instance'); // NOTE links are automatically removed by the graph library
+
+      if (this.networkShown) {
+        // NOTE if we remove Instances we also need to remove Networks
+        this.removeElementsFromGraph('network');
+        this.networkShown = false;
+      }
+    }
+    else {
+      this.loadInstances();
+      this.loadInstanceLinks();
+    }
+    this.instanceShown = !this.instanceShown;
+    return this.serviceGraph;
+  }
+
+  public toggleNetwork() {
+    if (this.networkShown) {
+      this.NetworkSubscription.unsubscribe();
+      this.PortSubscription.unsubscribe();
+      this.removeElementsFromGraph('network');
+    }
+    else {
+      this.loadNetworks();
+      this.loadPorts(); // Ports define the connection of an Instance to a Network
+    }
+
+    this.networkShown = !this.networkShown;
+    return this.serviceGraph;
+  }
+
   public get(): Observable<Graph> {
     return this.ServiceGraphSubject.asObservable();
   }
@@ -194,6 +256,22 @@
     this.serviceGraph.setEdge(sourceId, targetId, link);
   }
 
+  private addInstanceOwner(tenantWithContainer: any) {
+    // NOTE some TenantWithContainer don't have an instance
+    if (tenantWithContainer.instance_id) {
+      const sourceId = this.getServiceInstanceId(tenantWithContainer.id);
+      const targetId = this.getInstanceId(tenantWithContainer.instance_id);
+      this.serviceGraph.setEdge(sourceId, targetId, angular.merge(tenantWithContainer, {type: 'instance_ownership'}));
+    }
+  }
+
+  private addNetworkLink(port: any) {
+    // ports are connected to 1 Instance and 1 Network
+    const sourceId = this.getInstanceId(port.instance_id);
+    const targetId = this.getNetworkId(port.network_id);
+    this.serviceGraph.setEdge(sourceId, targetId, angular.merge(port, {type: 'port'}));
+  }
+
   private removeElementsFromGraph(type: string) {
     _.forEach(this.serviceGraph.nodes(), (n: string) => {
       const node = this.serviceGraph.node(n);
@@ -209,7 +287,7 @@
   // helpers
   private getModelType(node: IXosBaseModel): string {
     if (node.type) {
-      // NOTE we'll add "ownership" links
+      // NOTE handling "ownership" links
       return node.type;
     }
     return node.class_names.split(',')[0].toLowerCase();
@@ -223,6 +301,14 @@
     return `serviceinstance~${id}`;
   }
 
+  private getInstanceId(id: number): string {
+    return `instance~${id}`;
+  }
+
+  private getNetworkId(id: number): string {
+    return `network~${id}`;
+  }
+
   private getNodeId(node: IXosBaseModel): string {
 
     const nodeType = this.getModelType(node);
@@ -231,6 +317,10 @@
         return this.getServiceId(node.id);
       case 'serviceinstance':
         return this.getServiceInstanceId(node.id);
+      case 'instance':
+        return this.getInstanceId(node.id);
+      case 'network':
+        return this.getNetworkId(node.id);
     }
   }
 
@@ -303,6 +393,74 @@
       );
   }
 
+  private loadInstances() {
+    this.InstanceSubscription = this.XosModelStore.query('Instance', '/core/instances')
+      .subscribe(
+        (res) => {
+          if (res.length > 0) {
+            _.forEach(res, n => {
+              this.addNode(n);
+            });
+            this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
+          }
+        },
+        (err) => {
+          this.$log.error(`[XosServiceGraphStore] Instance Observable: `, err);
+        }
+      );
+  }
+
+  private loadInstanceLinks() {
+    this.TenantWithContainerSubscription = this.XosModelStore.query('TnantWithContainer', '/core/tenantwithcontainers')
+      .subscribe(
+        (res) => {
+          if (res.length > 0) {
+            _.forEach(res, n => {
+              this.addInstanceOwner(n);
+            });
+            this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
+          }
+        },
+        (err) => {
+          this.$log.error(`[XosServiceGraphStore] Instance Observable: `, err);
+        }
+      );
+  }
+
+  private loadNetworks() {
+    this.NetworkSubscription = this.XosModelStore.query('Network', '/core/networks')
+      .subscribe(
+        (res) => {
+          if (res.length > 0) {
+            _.forEach(res, n => {
+              this.addNode(n);
+            });
+            this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
+          }
+        },
+        (err) => {
+          this.$log.error(`[XosServiceGraphStore] Network Observable: `, err);
+        }
+      );
+  }
+
+  private loadPorts() {
+    this.PortSubscription = this.XosModelStore.query('Port', '/core/ports')
+      .subscribe(
+        (res) => {
+          if (res.length > 0) {
+            _.forEach(res, n => {
+              this.addNetworkLink(n);
+            });
+            this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
+          }
+        },
+        (err) => {
+          this.$log.error(`[XosServiceGraphStore] Network Observable: `, err);
+        }
+      );
+  }
+
   private callNext(subject: BehaviorSubject<any>, data: any) {
     subject.next(data);
   }