[CORD-2742] Remove elements from the service graph

Change-Id: Ibcb9fac4428f0b168ff66fab3219b8357e732dc5
diff --git a/src/app/service-graph/services/graph.store.spec.ts b/src/app/service-graph/services/graph.store.spec.ts
index 53f24fc..ad67f12 100644
--- a/src/app/service-graph/services/graph.store.spec.ts
+++ b/src/app/service-graph/services/graph.store.spec.ts
@@ -22,6 +22,7 @@
 import {Subject} from 'rxjs/Subject';
 import {Graph} from 'graphlib';
 import {XosDebouncer} from '../../core/services/helpers/debounce.helper';
+import {IWSEvent} from '../../datasources/websocket/global';
 
 interface ITestXosGraphStore extends IXosGraphStore {
 
@@ -94,6 +95,7 @@
 const subject_servicedependency = new Subject();
 const subject_serviceinstances = new Subject();
 const subject_serviceinstancelinks = new Subject();
+const subject_websocket = new Subject();
 
 let MockModelStore = {
   query: jasmine.createSpy('XosModelStore.query')
@@ -113,6 +115,11 @@
     })
 };
 
+let MockWebSocket = {
+  list: jasmine.createSpy('WebSocket.list')
+    .and.returnValue(subject_websocket)
+};
+
 
 describe('The XosGraphStore service', () => {
 
@@ -120,6 +127,7 @@
     angular.module('XosGraphStore', [])
       .service('XosGraphStore', XosGraphStore)
       .value('XosModelStore', MockModelStore)
+      .value('WebSocket', MockWebSocket)
       .service('XosDebouncer', XosDebouncer);
 
     angular.mock.module('XosGraphStore');
@@ -134,25 +142,128 @@
 
   }));
 
-  it('should load services and service-dependency and add nodes to the graph', (done) => {
-    let event = 0;
-    service.get().subscribe(
-      (graph: Graph) => {
-        if (event === 1) {
-          expect(graph.nodes().length).toBe(services.length);
-          expect(graph.nodes()).toEqual(['service~1', 'service~2']);
-          expect(graph.edges().length).toBe(servicedependencies.length);
-          expect(graph.edges()).toEqual([{v: 'service~1', w: 'service~2'}]);
-          done();
+  describe('when started', () => {
+
+    let subscription;
+
+    it('should load services and service-dependency and add nodes to the graph', (done) => {
+      let event = 0;
+      subscription = service.get().subscribe(
+        (graph: Graph) => {
+          if (event === 1) {
+            expect(graph.nodes().length).toBe(services.length);
+            expect(graph.nodes()).toEqual(['service~1', 'service~2']);
+            expect(graph.edges().length).toBe(servicedependencies.length);
+            expect(graph.edges()).toEqual([{v: 'service~1', w: 'service~2'}]);
+            done();
+          }
+          else {
+            event = event + 1;
+          }
         }
-        else {
-          event = event + 1;
+      );
+      subject_services.next(services);
+      subject_servicedependency.next(servicedependencies);
+      scope.$apply();
+    });
+
+    afterEach(() => {
+      subscription.unsubscribe();
+    });
+  });
+
+  describe('when an observed model is removed', () => {
+
+    let subscription;
+
+    beforeEach(() => {
+      subject_services.next([]);
+      subject_servicedependency.next([]);
+      subject_services.next(services);
+      subject_servicedependency.next(servicedependencies);
+      service.addServiceInstances();
+      subject_serviceinstances.next([serviceInstances[0]]);
+    });
+
+    it('should be removed from the graph', (done) => {
+      let event = 0;
+      subscription = service.get().subscribe(
+        (graph: Graph) => {
+          if (event === 1) {
+            expect(graph.nodes().length).toBe(0);
+            expect(graph.nodes()).toEqual([]);
+            expect(graph.edges().length).toBe(0);
+            expect(graph.edges()).toEqual([]);
+            done();
+          }
+          else {
+            event = event + 1;
+          }
         }
-      }
-    );
-    subject_services.next(services);
-    subject_servicedependency.next(servicedependencies);
-    scope.$apply();
+      );
+
+      const removeService1: IWSEvent = {
+        model: 'Service',
+        deleted: true,
+        msg: {
+          changed_fields: [],
+          pk: 1,
+          object: {
+            id: 1,
+            class_names: 'Service, XOSBase'
+          }
+        }
+      };
+
+      const removeService2: IWSEvent = {
+        model: 'Service',
+        deleted: true,
+        msg: {
+          changed_fields: [],
+          pk: 2,
+          object: {
+            id: 2,
+            class_names: 'Service, XOSBase'
+          }
+        }
+      };
+
+      const removeServiceDependency1: IWSEvent = {
+        model: 'ServiceDependency',
+        deleted: true,
+        msg: {
+          changed_fields: [],
+          pk: 1,
+          object: {
+            id: 1,
+            class_names: 'ServiceDependency, XOSBase'
+          }
+        }
+      };
+
+      const removeServiceInstnace1: IWSEvent = {
+        model: 'VSGServiceInstance',
+        deleted: true,
+        msg: {
+          changed_fields: [],
+          pk: 1,
+          object: {
+            id: 1,
+            class_names: 'VSGServiceInstance,TenantWithContainer,ServiceInstance'
+          }
+        }
+      };
+
+      subject_websocket.next(removeService1);
+      subject_websocket.next(removeService2);
+      subject_websocket.next(removeServiceDependency1);
+      subject_websocket.next(removeServiceInstnace1);
+      scope.$apply();
+    });
+
+    afterEach(() => {
+      subscription.unsubscribe();
+    });
   });
 
   describe(`the getModelType`, () => {
diff --git a/src/app/service-graph/services/graph.store.ts b/src/app/service-graph/services/graph.store.ts
index 0dd6653..0edba0f 100644
--- a/src/app/service-graph/services/graph.store.ts
+++ b/src/app/service-graph/services/graph.store.ts
@@ -23,6 +23,7 @@
 import {BehaviorSubject} from 'rxjs/BehaviorSubject';
 import {Observable} from 'rxjs/Observable';
 import {IXosBaseModel, IXosSgLink, IXosSgNode} from '../interfaces';
+import {IWSEvent, IWSEventService} from '../../datasources/websocket/global';
 
 
 export interface IXosGraphStore {
@@ -41,7 +42,8 @@
   static $inject = [
     '$log',
     'XosModelStore',
-    'XosDebouncer'
+    'XosDebouncer',
+    'WebSocket'
   ];
 
   // graphs
@@ -64,7 +66,8 @@
   constructor (
     private $log: ng.ILogService,
     private XosModelStore: IXosModelStoreService,
-    private XosDebouncer: IXosDebouncer
+    private XosDebouncer: IXosDebouncer,
+    private webSocket: IWSEventService,
   ) {
     this.$log.info('[XosGraphStore] Setup');
 
@@ -72,6 +75,34 @@
     this.ServiceGraphSubject = new BehaviorSubject(this.serviceGraph);
 
     this.loadData();
+
+    // handle model deletion
+    this.webSocket.list()
+      .filter((e: IWSEvent) => {
+
+        const model = this.getModelType(e.msg.object);
+
+        switch (model) {
+          case 'service':
+          case 'serviceinstance':
+          case 'instance':
+          case 'network':
+            return true;
+          case 'servicedependency':
+          case 'serviceinstanceLink':
+            // NOTE ServiceDependency are considered links, they are automatically removed by the graph library
+            return false;
+          default:
+            return false;
+        }
+      })
+      .subscribe((event: IWSEvent) => {
+        if (event.deleted) {
+          const nodeId = this.getNodeId(event.msg.object);
+          this.serviceGraph.removeNode(nodeId);
+          this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
+        }
+      });
   }
 
   $onDestroy() {