[CORD-1043] Registering reducers on the graph

Change-Id: I5804025f25733b5b5da1fd95725db3467a65abef
diff --git a/src/app/service-graph/components/coarse/coarse.component.ts b/src/app/service-graph/components/coarse/coarse.component.ts
index d610aa6..4e681d7 100644
--- a/src/app/service-graph/components/coarse/coarse.component.ts
+++ b/src/app/service-graph/components/coarse/coarse.component.ts
@@ -6,13 +6,15 @@
 import {XosServiceGraphConfig as config} from '../../graph.config';
 import {IXosDebouncer} from '../../../core/services/helpers/debounce.helper';
 import {Subscription} from 'rxjs';
+import {IXosGraphHelpers} from '../../services/d3-helpers/graph.helpers';
 
 class XosCoarseTenancyGraphCtrl {
 
   static $inject = [
     '$log',
     'XosServiceGraphStore',
-    'XosDebouncer'
+    'XosDebouncer',
+    'XosGraphHelpers'
   ];
 
   public graph: IXosServiceGraph;
@@ -29,7 +31,8 @@
   constructor (
     private $log: ng.ILogService,
     private XosServiceGraphStore: IXosServiceGraphStore,
-    private XosDebouncer: IXosDebouncer
+    private XosDebouncer: IXosDebouncer,
+    private XosGraphHelpers: IXosGraphHelpers
   ) {
 
   }
@@ -141,10 +144,6 @@
       .start();
   }
 
-  private getSiblingTextBBox(contex: any /* D3 this */) {
-    return d3.select(contex.parentNode).select('text').node().getBBox();
-  }
-
   private renderNodes(nodes: IXosServiceGraphNode[]) {
     const self = this;
     const node = this.nodeGroup
@@ -155,7 +154,7 @@
     const entering = node.enter()
       .append('g')
       .attr({
-        class: 'node',
+        class: n => `node ${this.XosGraphHelpers.parseElemClasses(n.d3Class)}`,
         transform: `translate(${svgDim.width / 2}, ${svgDim.heigth / 2})`
       })
       .call(this.forceLayout.drag)
@@ -184,7 +183,7 @@
 
     // resize node > rect as contained text
     existing.each(function() {
-      const textBBox = self.getSiblingTextBBox(this);
+      const textBBox = self.XosGraphHelpers.getSiblingTextBBox(this);
       const rect = d3.select(this);
       rect.attr({
         width: textBBox.width + config.node.padding,
@@ -201,15 +200,12 @@
       .data(links, l => l.id);
 
     const entering = link.enter();
-      // NOTE do we need to have groups?
-      // .append('g')
-      // .attr({
-      //   class: 'link',
-      // });
+
+    // TODO read classes from graph links
 
     entering.append('line')
       .attr({
-        class: 'link',
+        class: l => `link ${this.XosGraphHelpers.parseElemClasses(l.d3Class)}`,
         'marker-start': 'url(#arrow)'
       });
   }
diff --git a/src/app/service-graph/components/fine-grained/fine-grained.component.ts b/src/app/service-graph/components/fine-grained/fine-grained.component.ts
index 522e06f..c7efd1c 100644
--- a/src/app/service-graph/components/fine-grained/fine-grained.component.ts
+++ b/src/app/service-graph/components/fine-grained/fine-grained.component.ts
@@ -8,6 +8,7 @@
 import {IXosServiceGraph, IXosServiceGraphLink, IXosServiceGraphNode} from '../../interfaces';
 import {IXosModelDiscovererService} from '../../../datasources/helpers/model-discoverer.service';
 import {IXosSidePanelService} from '../../../core/side-panel/side-panel.service';
+import {IXosGraphHelpers} from '../../services/d3-helpers/graph.helpers';
 
 class XosFineGrainedTenancyGraphCtrl {
   static $inject = [
@@ -15,7 +16,8 @@
     'XosServiceGraphStore',
     'XosDebouncer',
     'XosModelDiscoverer',
-    'XosSidePanel'
+    'XosSidePanel',
+    'XosGraphHelpers'
   ];
 
   public graph: IXosServiceGraph;
@@ -35,7 +37,8 @@
     private XosServiceGraphStore: IXosServiceGraphStore,
     private XosDebouncer: IXosDebouncer,
     private XosModelDiscoverer: IXosModelDiscovererService,
-    private XosSidePanel: IXosSidePanelService
+    private XosSidePanel: IXosSidePanelService,
+    private XosGraphHelpers: IXosGraphHelpers
   ) {
     this.handleSvg();
     this.loadDefs();
@@ -158,10 +161,6 @@
       .start();
   }
 
-  private getSiblingTextBBox(contex: any /* D3 this */) {
-    return d3.select(contex.parentNode).select('text').node().getBBox();
-  }
-
   private renderServiceNodes(nodes: any) {
 
     const self = this;
@@ -182,7 +181,7 @@
 
     // resize node > rect as contained text
     existing.each(function() {
-      const textBBox = self.getSiblingTextBBox(this);
+      const textBBox = self.XosGraphHelpers.getSiblingTextBBox(this);
       const rect = d3.select(this);
       rect.attr({
         width: textBBox.width + config.node.padding,
@@ -237,7 +236,7 @@
 
     // resize node > rect as contained text
     existing.each(function() {
-      const textBBox = self.getSiblingTextBBox(this);
+      const textBBox = self.XosGraphHelpers.getSiblingTextBBox(this);
       const useElem = d3.select(this);
       const w = textBBox.width + config.node.padding * 2;
       const h = w;
@@ -266,7 +265,7 @@
 
     // resize node > rect as contained text
     existing.each(function() {
-      const textBBox = self.getSiblingTextBBox(this);
+      const textBBox = self.XosGraphHelpers.getSiblingTextBBox(this);
       const rect = d3.select(this);
       rect.attr({
         width: textBBox.width + config.node.padding,
@@ -289,7 +288,7 @@
     const entering = node.enter()
       .append('g')
       .attr({
-        class: n => `node ${n.type}`,
+        class: n => `node ${n.type} ${this.XosGraphHelpers.parseElemClasses(n.d3Class)}`,
         transform: (n, i) => `translate(${hStep * i}, ${vStep * i})`
       })
       .call(this.forceLayout.drag)
@@ -335,7 +334,7 @@
 
     entering.append('line')
       .attr({
-        class: 'link',
+        class: n => `link ${this.XosGraphHelpers.parseElemClasses(n.d3Class)}`,
         'marker-start': 'url(#arrow)'
       });
   }
diff --git a/src/app/service-graph/index.ts b/src/app/service-graph/index.ts
index 70b255e..00cf9e8 100644
--- a/src/app/service-graph/index.ts
+++ b/src/app/service-graph/index.ts
@@ -3,11 +3,15 @@
 import {xosCore} from '../core/index';
 import {XosCoarseTenancyGraph} from './components/coarse/coarse.component';
 import {XosFineGrainedTenancyGraph} from './components/fine-grained/fine-grained.component';
+import {XosServiceGraphExtender, IXosServiceGraphExtender} from './services/graph.extender';
+import {XosGraphHelpers} from './services/d3-helpers/graph.helpers';
 export const xosServiceGraph = 'xosServiceGraph';
 
 angular
   .module(xosServiceGraph, [xosDataSources, xosCore])
   .service('XosServiceGraphStore', XosServiceGraphStore)
+  .service('XosServiceGraphExtender', XosServiceGraphExtender)
+  .service('XosGraphHelpers', XosGraphHelpers)
   .component('xosCoarseTenancyGraph', XosCoarseTenancyGraph)
   .component('xosFineGrainedTenancyGraph', XosFineGrainedTenancyGraph)
   .config(($stateProvider) => {
@@ -17,6 +21,6 @@
         component: 'xosFineGrainedTenancyGraph',
       });
   })
-  .run(($log: ng.ILogService) => {
+  .run(($log: ng.ILogService, XosServiceGraphExtender: IXosServiceGraphExtender) => {
     $log.info(`[${xosServiceGraph}] Module Setup`);
   });
diff --git a/src/app/service-graph/interfaces.ts b/src/app/service-graph/interfaces.ts
index e08bc78..3dba9d3 100644
--- a/src/app/service-graph/interfaces.ts
+++ b/src/app/service-graph/interfaces.ts
@@ -1,13 +1,19 @@
+interface Id3Element {
+  d3Class?: string;
+  d3Id?: string;
+}
+
 export interface IXosServiceModel {
   id: number;
   d3Id?: string;
   backend_status: string;
   kind: string;
   name: string;
+  class_names: string;
   service_specific_attributes: string; // this is json stringified
 }
 
-export interface IXosTenantModel {
+export interface IXosTenantModel extends Id3Element {
   id: number;
   d3Id?: string;
   backend_status: string;
@@ -48,19 +54,20 @@
   text: string;
 }
 
-export interface IXosServiceGraphNode {
+export interface IXosServiceGraphNode extends Id3Element {
   id: number | string;
   label: string;
   x?: number;
   y?: number;
   px?: number;
   py?: number;
-  badge?: IXosServiceGraphNodeBadge;
+  fixed?: boolean;
+  badge?: IXosServiceGraphNodeBadge; // TODO implement badges
   model: IXosServiceModel;
   type: 'service' | 'tenant' | 'network' | 'subscriber';
 }
 
-export interface IXosServiceGraphLink {
+export interface IXosServiceGraphLink extends Id3Element {
   id: number | string;
   source: number;
   target: number;
diff --git a/src/app/service-graph/services/README.md b/src/app/service-graph/services/README.md
new file mode 100644
index 0000000..7058021
--- /dev/null
+++ b/src/app/service-graph/services/README.md
@@ -0,0 +1,37 @@
+# Service Graph extender example
+
+```
+XosServiceGraphExtender.register('coarse', 'test', (graph: IXosServiceGraph) => {
+  graph.nodes = graph.nodes.map(n => {
+    // do my changes
+    n.label = `reduced_${n.label}`;
+    return n;
+  });
+
+  graph.links = graph.links.map(l => {
+    // do my changes
+    return l;
+  });
+  return graph;
+});
+```
+
+Note that if you add classes you'll need to provide CSS for that
+
+## What can you change:
+
+### Nodes:
+
+You can change any of the property that are present in the `node` element, plus:
+
+property | type | effect
+-------- | ---- | -----: 
+d3Class  | space separated string |the class names get prefixed with `ext-`
+x | number | horizontal position
+y | number | vertical position
+
+### Links:
+
+property | type | effect
+-------- | ---- | -----: 
+d3Class  | space separated string |the class names get prefixed with `ext-`
diff --git a/src/app/service-graph/services/d3-helpers/graph.helpers.ts b/src/app/service-graph/services/d3-helpers/graph.helpers.ts
new file mode 100644
index 0000000..4bf4cb8
--- /dev/null
+++ b/src/app/service-graph/services/d3-helpers/graph.helpers.ts
@@ -0,0 +1,25 @@
+import * as d3 from 'd3';
+
+export interface Id3BBox {
+  x: number;
+  y: number;
+  width: number;
+  height: number;
+}
+
+export interface IXosGraphHelpers {
+  parseElemClasses (classes: string): string;
+  getSiblingTextBBox(contex: any /* D3 this */): Id3BBox;
+}
+
+export class XosGraphHelpers implements IXosGraphHelpers {
+  public parseElemClasses (classes: string): string {
+    return classes ? classes.split(' ')
+      .map(c => `ext-${c}`)
+      .join(' ') : '';
+  }
+
+  public getSiblingTextBBox(contex: any): Id3BBox {
+    return d3.select(contex.parentNode).select('text').node().getBBox();
+  }
+}
diff --git a/src/app/service-graph/services/graph.extender.spec.ts b/src/app/service-graph/services/graph.extender.spec.ts
new file mode 100644
index 0000000..28ab96e
--- /dev/null
+++ b/src/app/service-graph/services/graph.extender.spec.ts
@@ -0,0 +1,47 @@
+import * as angular from 'angular';
+import 'angular-mocks';
+import 'angular-ui-router';
+import {IXosServiceGraphExtender, XosServiceGraphExtender} from './graph.extender';
+
+let service: IXosServiceGraphExtender, registerSpy;
+
+const reducer = (graph) => {
+  return graph;
+};
+
+describe('The XosServiceGraphExtender service', () => {
+
+  beforeEach(() => {
+    angular.module('xosServiceGraphExtender', [])
+      .service('XosServiceGraphExtender', XosServiceGraphExtender);
+
+    angular.mock.module('xosServiceGraphExtender');
+  });
+
+  beforeEach(angular.mock.inject((
+    XosServiceGraphExtender: IXosServiceGraphExtender,
+  ) => {
+    service = XosServiceGraphExtender;
+
+    registerSpy = spyOn(service, 'register').and.callThrough();
+  }));
+
+  it('should register a reducer for the coarse service graph', () => {
+    service.register('coarse', 'testCoarse', reducer);
+    expect(registerSpy).toHaveBeenCalled();
+    const coarseReducers = service.getCoarse();
+    expect(coarseReducers).toHaveLength(1);
+    expect(coarseReducers[0].name).toEqual('testCoarse');
+    expect(typeof coarseReducers[0].reducer).toEqual('function');
+  });
+
+  it('should register a reducer for the fine-grained service graph', () => {
+    service.register('finegrained', 'testFinegrained', reducer);
+    expect(registerSpy).toHaveBeenCalled();
+    const coarseReducers = service.getFinegrained();
+    expect(coarseReducers).toHaveLength(1);
+    expect(coarseReducers[0].name).toEqual('testFinegrained');
+    expect(typeof coarseReducers[0].reducer).toEqual('function');
+  });
+
+});
diff --git a/src/app/service-graph/services/graph.extender.ts b/src/app/service-graph/services/graph.extender.ts
new file mode 100644
index 0000000..ba9218f
--- /dev/null
+++ b/src/app/service-graph/services/graph.extender.ts
@@ -0,0 +1,57 @@
+import {IXosServiceGraph} from '../interfaces';
+
+export interface IXosServiceGraphReducers {
+  coarse: IXosServiceGraphReducer[];
+  finegrained: IXosServiceGraphReducer[];
+}
+
+export interface IXosServiceGraphReducer {
+  name: string;
+  reducer: IXosServiceGraphReducerFn;
+}
+
+export interface IXosServiceGraphReducerFn {
+  (graph: IXosServiceGraph): IXosServiceGraph;
+}
+
+export interface IXosServiceGraphExtender {
+  register(type: 'coarse' | 'finegrained', name: string, reducer: IXosServiceGraphReducerFn): boolean;
+  getCoarse(): IXosServiceGraphReducer[];
+  getFinegrained(): IXosServiceGraphReducer[];
+}
+
+export class XosServiceGraphExtender implements IXosServiceGraphExtender {
+
+  static $inject = ['$log'];
+
+  private reducers: IXosServiceGraphReducers = {
+    coarse: [],
+    finegrained: []
+  };
+
+  constructor (
+    private $log: ng.ILogService
+  ) {
+  }
+
+  public getCoarse(): IXosServiceGraphReducer[] {
+    return this.reducers.coarse;
+  }
+
+  public getFinegrained(): IXosServiceGraphReducer[] {
+    return this.reducers.finegrained;
+  }
+
+  // NOTE
+  // as now extender support:
+  // - nodes property: x, y, d3Class (applied to the group element)
+  // - links propery: d3Class (applied to the line element, there's no group for now)
+  public register(type: 'coarse' | 'finegrained', name: string, reducer: IXosServiceGraphReducerFn): boolean {
+    this.$log.debug(`[XosServiceGraphExtender] Registering ${name} reducer in ${type} list`);
+    this.reducers[type].push({
+      name,
+      reducer
+    });
+    return false;
+  }
+}
diff --git a/src/app/service-graph/services/graph.store.spec.ts b/src/app/service-graph/services/graph.store.spec.ts
new file mode 100644
index 0000000..9073d58
--- /dev/null
+++ b/src/app/service-graph/services/graph.store.spec.ts
@@ -0,0 +1,138 @@
+import * as angular from 'angular';
+import 'angular-mocks';
+import 'angular-ui-router';
+import {IXosServiceGraphStore, XosServiceGraphStore} from './graph.store';
+import {Subject} from 'rxjs';
+import {XosDebouncer} from '../../core/services/helpers/debounce.helper';
+import {IXosServiceGraph} from '../interfaces';
+import {XosServiceGraphExtender, IXosServiceGraphExtender} from './graph.extender';
+
+let service: IXosServiceGraphStore, extender: IXosServiceGraphExtender;
+
+const subjects = {
+  service: new Subject<any>(),
+  tenant: new Subject<any>(),
+  subscriber: new Subject<any>(),
+  network: new Subject<any>(),
+};
+
+// COARSE data
+const coarseServices = [
+  {
+    id: 1,
+    name: 'Service A',
+    class_names: 'Service,PlCoreBase'
+  },
+  {
+    id: 2,
+    name: 'Service B',
+    class_names: 'Service,PlCoreBase'
+  }
+];
+
+const coarseTenants = [
+  {
+    id: 1,
+    provider_service_id: 2,
+    subscriber_service_id: 1,
+    kind: 'coarse',
+    class_names: 'Tenant,PlCoreBase'
+  }
+];
+
+const mockModelStore = {
+  query: (modelName: string) => {
+    return subjects[modelName.toLowerCase()].asObservable();
+  }
+};
+
+describe('The XosServiceGraphStore service', () => {
+
+  beforeEach(() => {
+    angular.module('xosServiceGraphStore', [])
+      .service('XosServiceGraphStore', XosServiceGraphStore)
+      .value('XosModelStore', mockModelStore)
+      .service('XosServiceGraphExtender', XosServiceGraphExtender)
+      .service('XosDebouncer', XosDebouncer);
+
+    angular.mock.module('xosServiceGraphStore');
+  });
+
+  beforeEach(angular.mock.inject((
+    XosServiceGraphStore: IXosServiceGraphStore,
+    XosServiceGraphExtender: IXosServiceGraphExtender
+  ) => {
+    service = XosServiceGraphStore;
+    extender = XosServiceGraphExtender;
+  }));
+
+  describe('when subscribing for the COARSE service graph', () => {
+    beforeEach((done) => {
+      subjects.service.next(coarseServices);
+      subjects.tenant.next(coarseTenants);
+      setTimeout(done, 500);
+    });
+
+    it('should return an observer for the Coarse Service Graph', (done) => {
+      service.getCoarse()
+        .subscribe(
+          (res: IXosServiceGraph) => {
+            expect(res.nodes.length).toBe(2);
+            expect(res.nodes[0].d3Class).toBeUndefined();
+            expect(res.links.length).toBe(1);
+            expect(res.links[0].d3Class).toBeUndefined();
+            done();
+          },
+          (err) => {
+            done(err);
+          }
+        );
+    });
+
+    describe('when a reducer is register', () => {
+
+      beforeEach((done) => {
+        extender.register('coarse', 'test', (graph: IXosServiceGraph) => {
+          graph.nodes = graph.nodes.map(n => {
+            n.d3Class = `testNode`;
+            return n;
+          });
+
+          graph.links = graph.links.map(n => {
+            n.d3Class = `testLink`;
+            return n;
+          });
+
+          return graph;
+        });
+
+        // triggering another next cycle to apply the reducer
+        subjects.service.next(coarseServices);
+        subjects.tenant.next(coarseTenants);
+        setTimeout(done, 500);
+      });
+
+      it('should transform the result', (done) => {
+        service.getCoarse()
+          .subscribe(
+            (res: IXosServiceGraph) => {
+              expect(res.nodes.length).toBe(2);
+              expect(res.nodes[0].d3Class).toEqual('testNode');
+              expect(res.links.length).toBe(1);
+              expect(res.links[0].d3Class).toEqual('testLink');
+              done();
+            },
+            (err) => {
+              done(err);
+            }
+          );
+      });
+    });
+  });
+
+  describe('when subscribing for the Fine-grained service graph', () => {
+    xit('should have a test', () => {
+      expect(true).toBeTruthy();
+    });
+  });
+});
diff --git a/src/app/service-graph/services/graph.store.ts b/src/app/service-graph/services/graph.store.ts
index 1bb5d4c..54bc083 100644
--- a/src/app/service-graph/services/graph.store.ts
+++ b/src/app/service-graph/services/graph.store.ts
@@ -6,6 +6,7 @@
   IXosServiceGraphNode, IXosServiceGraphLink, IXosFineGrainedGraphData
 } from '../interfaces';
 import {IXosDebouncer} from '../../core/services/helpers/debounce.helper';
+import {IXosServiceGraphExtender, IXosServiceGraphReducer} from './graph.extender';
 export interface IXosServiceGraphStore {
   get(): Observable<IXosServiceGraph>;
   getCoarse(): Observable<IXosServiceGraph>;
@@ -15,7 +16,8 @@
   static $inject = [
     '$log',
     'XosModelStore',
-    'XosDebouncer'
+    'XosDebouncer',
+    'XosServiceGraphExtender'
   ];
 
   // graph data store
@@ -48,7 +50,8 @@
   constructor (
     private $log: ng.ILogService,
     private XosModelStore: IXosModelStoreService,
-    private XosDebouncer: IXosDebouncer
+    private XosDebouncer: IXosDebouncer,
+    private XosServiceGraphExtender: IXosServiceGraphExtender
   ) {
 
     this.$log.info(`[XosServiceGraphStore] Setup`);
@@ -199,92 +202,112 @@
 
   private graphDataToCoarseGraph(data: IXosCoarseGraphData) {
 
-    const links: IXosServiceGraphLink[] = _.chain(data.tenants)
-      .filter((t: IXosTenantModel) => t.kind === 'coarse')
-      .map((t: IXosTenantModel) => {
+    try {
+      const links: IXosServiceGraphLink[] = _.chain(data.tenants)
+        .filter((t: IXosTenantModel) => t.kind === 'coarse')
+        .map((t: IXosTenantModel) => {
+          return {
+            id: t.id,
+            source: this.getNodeIndexById(t.provider_service_id, data.services),
+            target: this.getNodeIndexById(t.subscriber_service_id, data.services),
+            model: t
+          };
+        })
+        .value();
+
+      const nodes: IXosServiceGraphNode[] = _.map(data.services, (s: IXosServiceModel) => {
         return {
-          id: t.id,
-          source: this.getNodeIndexById(t.provider_service_id, data.services),
-          target: this.getNodeIndexById(t.subscriber_service_id, data.services),
-          model: t
+          id: s.id,
+          label: s.name,
+          model: s
         };
-      })
-      .value();
+      });
 
-    const nodes: IXosServiceGraphNode[] = _.map(data.services, (s: IXosServiceModel) => {
-      return {
-        id: s.id,
-        label: s.name,
-        model: s
+      let graph: IXosServiceGraph = {
+        nodes,
+        links
       };
-    });
 
-    this.d3CoarseGraph.next({
-      nodes: nodes,
-      links: links
-    });
+      _.forEach(this.XosServiceGraphExtender.getCoarse(), (r: IXosServiceGraphReducer) => {
+        graph = r.reducer(graph);
+      });
+
+      this.d3CoarseGraph.next(graph);
+    } catch (e) {
+      this.d3CoarseGraph.error(e);
+    }
   }
 
   private graphDataToFineGrainedGraph(data: IXosFineGrainedGraphData) {
 
-    data = this.removeUnwantedFineGrainedData(data);
+    try {
+      data = this.removeUnwantedFineGrainedData(data);
 
-    let nodes = _.reduce(Object.keys(data), (list: any[], k: string) => {
-      return list.concat(data[k]);
-    }, []);
+      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)
+      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);
+
+        if (!angular.isDefined(targetId)) {
+          // if the tenant is not pointing to anything, don't draw links
+          return links;
+        }
+
+        const tenantToProvider = {
+          id: `${sourceId}_${tenant.d3Id}`,
+          source: this.getNodeIndexById(sourceId, nodes),
+          target: this.getNodeIndexById(tenant.d3Id, nodes),
+          model: tenant
         };
-        return node;
-      })
-      .value();
 
-    const links = _.reduce(data.tenants, (links: IXosServiceGraphLink[], tenant: IXosTenantModel) => {
-      const sourceId = this.getSourceId(tenant);
-      const targetId = this.getTargetId(tenant);
+        const tenantToSubscriber = {
+          id: `${tenant.d3Id}_${targetId}`,
+          source: this.getNodeIndexById(tenant.d3Id, nodes),
+          target: this.getNodeIndexById(targetId, nodes),
+          model: tenant
+        };
 
-      if (!angular.isDefined(targetId)) {
-        // if the tenant is not pointing to anything, don't draw links
+        links.push(tenantToProvider);
+        links.push(tenantToSubscriber);
         return links;
+      }, []);
+
+      if (nodes.length === 0 || links.length === 0) {
+        return;
       }
 
-      const tenantToProvider = {
-        id: `${sourceId}_${tenant.d3Id}`,
-        source: this.getNodeIndexById(sourceId, nodes),
-        target: this.getNodeIndexById(tenant.d3Id, nodes),
-        model: tenant
+      let graph: IXosServiceGraph = {
+        nodes,
+        links
       };
 
-      const tenantToSubscriber = {
-        id: `${tenant.d3Id}_${targetId}`,
-        source: this.getNodeIndexById(tenant.d3Id, nodes),
-        target: this.getNodeIndexById(targetId, nodes),
-        model: tenant
-      };
+      _.forEach(this.XosServiceGraphExtender.getFinegrained(), (r: IXosServiceGraphReducer) => {
+        graph = r.reducer(graph);
+      });
 
-      links.push(tenantToProvider);
-      links.push(tenantToSubscriber);
-      return links;
-    }, []);
-
-    if (nodes.length === 0 || links.length === 0) {
-      return;
+      this.d3FineGrainedGraph.next(graph);
+    } catch (e) {
+     this.d3FineGrainedGraph.error(e);
     }
-
-    this.d3FineGrainedGraph.next({
-      nodes: nodes,
-      links: links
-    });
   }
 
 }