blob: 904466cdde31aff1b71130b672a878718f06fdbf [file] [log] [blame]
Matteo Scandolo70bc45f2016-05-06 14:10:11 -07001(function () {
2 'use strict';
3
4 angular.module('xos.serviceGrid')
Matteo Scandoloe6393f02016-05-23 14:27:52 -07005 .service('Graph', function($q, Tenants, Services, Subscribers){
6
7 let tenancyGraph = new graphlib.Graph();
8 let cached = false;
9
10 const buildGraph = () => {
11
12 let deferred = $q.defer();
13
14 $q.all([
15 Tenants.query().$promise,
16 Services.query().$promise,
17 Subscribers.query().$promise
18 ])
19 .then((res) => {
20 let [tenants, services, subscribers] = res;
21 // adding service nodes
22 services.forEach(s => tenancyGraph.setNode(s.id, angular.extend(s, {type: 'service'})));
23
24
25 // coarse tenant
26 tenants.filter(t => t.subscriber_service && t.provider_service)
27 .forEach(t => tenancyGraph.setEdge(t.subscriber_service, t.provider_service, t, t.name));
28
29 // fine grain tenant
30 // adding subscribers as nodes (to build fine grain graph)
31 // subscribers.forEach(s => tenancyGraph.setNode(`sub-${s.id}`, angular.extend(s, {type: 'subscriber'})));
32 // TODO
33 // - Find tenant that start from a subscriber
34 // - Follow the chain: from the first tenant follow where subscriber_tenant = tenant_id untill we cannot find any more tenant
35 // tenants.filter(t => t.subscriber_root && t.provider_service)
36 // .forEach(t => tenancyGraph.setEdge(`sub-${t.subscriber_root}`, t.provider_service, t, t.name));
37
38 deferred.resolve(tenancyGraph);
39 });
40
41 return deferred.promise;
42 };
43
44 this.getGraph = () => {
45 let deferred = $q.defer();
46
47 if(cached){
48 deferred.resolve(tenancyGraph);
49 }
50 else {
51 buildGraph()
52 .then((res) => {
53 cached = true;
54 deferred.resolve(res);
55 })
56 .catch(console.log);
57 }
58
59 return {$promise: deferred.promise};
60 };
61
62 })
Matteo Scandolo70bc45f2016-05-06 14:10:11 -070063 .directive('serviceGraph', function(){
64 return {
65 restrict: 'E',
66 scope: {},
67 bindToController: true,
68 controllerAs: 'vm',
69 templateUrl: 'templates/service-graph.tpl.html',
Matteo Scandoloe6393f02016-05-23 14:27:52 -070070 controller: function($scope, $element, GraphService, Graph, ToscaEncoder){
Matteo Scandolo70bc45f2016-05-06 14:10:11 -070071
Matteo Scandolo819d13d2016-05-06 16:52:58 -070072 let svg;
73 let el = $element[0];
74 let node;
75 let link;
Matteo Scandoloe6393f02016-05-23 14:27:52 -070076 const _this = this;
Matteo Scandolo819d13d2016-05-06 16:52:58 -070077
Matteo Scandoloe6393f02016-05-23 14:27:52 -070078 this.panelConfig = {
79 position: 'right'
80 };
81
82 // animate node and links in the correct place
Matteo Scandolo819d13d2016-05-06 16:52:58 -070083 const tick = (e) => {
Matteo Scandolo819d13d2016-05-06 16:52:58 -070084 node
Matteo Scandoloe6393f02016-05-23 14:27:52 -070085 .attr('cx', d => d.x)
86 .attr('cy', d => d.y)
87 .attr({
88 transform: d => `translate(${d.x}, ${d.y})`
89 });
Matteo Scandolo819d13d2016-05-06 16:52:58 -070090
Matteo Scandoloe6393f02016-05-23 14:27:52 -070091 link
92 .attr('x1', d => d.source.x)
93 .attr('y1', d => d.source.y)
94 .attr('x2', d => getTargetNodeCircumferencePoint(d)[0])
95 .attr('y2', d => getTargetNodeCircumferencePoint(d)[1]);
96 };
Matteo Scandolo819d13d2016-05-06 16:52:58 -070097
Matteo Scandoloe6393f02016-05-23 14:27:52 -070098 Graph.getGraph().$promise
99 .then((graph) => {
Matteo Scandolo819d13d2016-05-06 16:52:58 -0700100
101 // build links
Matteo Scandoloe6393f02016-05-23 14:27:52 -0700102 let links = graph.edges().map(e => {
Matteo Scandolo819d13d2016-05-06 16:52:58 -0700103 return {
Matteo Scandoloe6393f02016-05-23 14:27:52 -0700104 source: graph.node(e.v),
105 target: graph.node(e.w)
Matteo Scandolo819d13d2016-05-06 16:52:58 -0700106 }
107 });
Matteo Scandoloe6393f02016-05-23 14:27:52 -0700108 let nodes = graph.nodes().map(n => graph.node(n));
Matteo Scandolo819d13d2016-05-06 16:52:58 -0700109
Matteo Scandoloe6393f02016-05-23 14:27:52 -0700110 //add xos as a node
111 nodes.push({
112 name: 'XOS',
113 type: 'xos',
114 x: el.clientWidth / 2,
115 y: el.clientHeight / 2,
116 fixed: true
117 });
Matteo Scandolo819d13d2016-05-06 16:52:58 -0700118
119 handleSvg(el);
Matteo Scandoloe6393f02016-05-23 14:27:52 -0700120 defineArrows();
Matteo Scandolo819d13d2016-05-06 16:52:58 -0700121
122 var force = d3.layout.force()
Matteo Scandoloe6393f02016-05-23 14:27:52 -0700123 .nodes(nodes)
124 .links(links)
Matteo Scandolo819d13d2016-05-06 16:52:58 -0700125 .charge(-1060)
126 .gravity(0.1)
127 .linkDistance(200)
128 .size([el.clientWidth, el.clientHeight])
129 .on('tick', tick)
130 .start();
131
132 link = svg.selectAll('.link')
Matteo Scandoloe6393f02016-05-23 14:27:52 -0700133 .data(links).enter().insert('line')
134 .attr('class', 'link')
135 .attr('marker-end', 'url(#arrow)');
Matteo Scandolo819d13d2016-05-06 16:52:58 -0700136
137 node = svg.selectAll('.node')
Matteo Scandoloe6393f02016-05-23 14:27:52 -0700138 .data(nodes)
Matteo Scandolo819d13d2016-05-06 16:52:58 -0700139 .enter().append('g')
140 .call(force.drag)
Matteo Scandoloe6393f02016-05-23 14:27:52 -0700141 .on('mousedown', function(d) {
142 $scope.$apply(() => {
143 if(d.name === 'XOS'){
144 return;
145 }
146 _this.panelShow = true;
147 let status = parseInt(d.backend_status.match(/^[0-9]/)[0]);
148 console.log(status);
149 switch(status){
150 case 0:
151 d.icon = 'time';
152 break;
153 case 1:
154 d.icon = 'ok';
155 break;
156 case 2:
157 d.icon = 'remove';
158 break;
159 }
160 _this.selectedNode = d;
161 });
162 d3.event.stopPropagation();
163 });
Matteo Scandolo819d13d2016-05-06 16:52:58 -0700164
165 node.append('circle')
166 .attr({
Matteo Scandoloe6393f02016-05-23 14:27:52 -0700167 class: d => `node ${d.type || ''}`,
Matteo Scandolo819d13d2016-05-06 16:52:58 -0700168 r: 10
169 });
170
171 node.append('text')
172 .attr({
Matteo Scandoloe6393f02016-05-23 14:27:52 -0700173 'text-anchor': 'middle',
174 'alignment-baseline': 'middle'
Matteo Scandolo819d13d2016-05-06 16:52:58 -0700175 })
Matteo Scandoloe6393f02016-05-23 14:27:52 -0700176 .text(d => d.humanReadableName || d.name);
Matteo Scandolo819d13d2016-05-06 16:52:58 -0700177
Matteo Scandoloe6393f02016-05-23 14:27:52 -0700178 // scale the node to fit the contained text
Matteo Scandolo819d13d2016-05-06 16:52:58 -0700179 node.select('circle')
180 .attr({
181 r: function(d){
Matteo Scandoloe6393f02016-05-23 14:27:52 -0700182 const parent = d3.select(this).node().parentNode;
183 const sib = d3.select(parent).select('text').node().getBBox();
184 const radius = (sib.width / 2) + 10;
185
186 // add radius as node attribute
187 d.nodeWidth = radius * 2;
188 return radius;
Matteo Scandolo819d13d2016-05-06 16:52:58 -0700189 }
190 })
191
Matteo Scandoloe6393f02016-05-23 14:27:52 -0700192 });
Matteo Scandolo819d13d2016-05-06 16:52:58 -0700193
194 const handleSvg = (el) => {
195 d3.select(el).select('svg').remove();
196
197 svg = d3.select(el)
198 .append('svg')
199 .style('width', `${el.clientWidth}px`)
200 .style('height', `${el.clientHeight}px`);
Matteo Scandoloe6393f02016-05-23 14:27:52 -0700201 };
202
203 const defineArrows = () => {
204 svg.append('svg:defs').selectAll('marker')
205 .data(['arrow']) // Different link/path types can be defined here
206 .enter().append('svg:marker') // This section adds in the arrows
207 .attr('id', String)
208 .attr('viewBox', '0 -5 10 10')
209 .attr('refX', 10)
210 .attr('refY', 0)
211 .attr('markerWidth', 6)
212 .attr('markerHeight', 6)
213 .attr('orient', 'auto')
214 .append('svg:path')
215 .attr('d', 'M0,-5L10,0L0,5');
216 };
217
218 const getTargetNodeCircumferencePoint = d => {
219 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
220 const dx = d.target.x - d.source.x;
221 const dy = d.target.y - d.source.y;
222 const gamma = Math.atan2(dy, dx); // Math.atan2 returns the angle in the correct quadrant as opposed to Math.atan
223
224 const tx = d.target.x - (Math.cos(gamma) * radius);
225 const ty = d.target.y - (Math.sin(gamma) * radius);
226
227 return [tx, ty];
228 };
229
230 this.exportToTosca = (service) => {
231 ToscaEncoder.serviceToTosca(service);
Matteo Scandolo819d13d2016-05-06 16:52:58 -0700232 }
Matteo Scandolo70bc45f2016-05-06 14:10:11 -0700233 }
234 };
235 })
Matteo Scandoloe6393f02016-05-23 14:27:52 -0700236})();