CSIT Fix for SDC-2585
[sdc.git] / catalog-ui / src / app / directives / graphs-v2 / common / common-graph-utils.ts
1 /*-
2  * ============LICENSE_START=======================================================
3  * SDC
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 import * as _ from "lodash";
22 import {CommonNodeBase, CompositionCiLinkBase, RelationshipModel, Relationship, CompositionCiNodeBase, NodesFactory, LinksFactory} from "app/models";
23 import {GraphUIObjects} from "app/utils";
24 import {CompositionCiServicePathLink} from "app/models/graph/graph-links/composition-graph-links/composition-ci-service-path-link";
25 import {Requirement, Capability} from "app/models";
26 /**
27  * Created by obarda on 12/21/2016.
28  */
29 export class CommonGraphUtils {
30
31     constructor(private NodesFactory:NodesFactory, private LinksFactory:LinksFactory) {
32
33     }
34
35     public safeApply = (scope:ng.IScope, fn:any) => { //todo remove to general utils
36         let phase = scope.$root.$$phase;
37         if (phase == '$apply' || phase == '$digest') {
38             if (fn && (typeof(fn) === 'function')) {
39                 fn();
40             }
41         } else {
42             scope.$apply(fn);
43         }
44     };
45
46     /**
47      * Draw node on the graph
48      * @param cy
49      * @param compositionGraphNode
50      * @param position
51      * @returns {CollectionElements}
52      */
53     public addNodeToGraph(cy:Cy.Instance, compositionGraphNode:CommonNodeBase, position?:Cy.Position):Cy.CollectionElements {
54
55         let node = cy.add(<Cy.ElementDefinition> {
56             group: 'nodes',
57             position: position,
58             data: compositionGraphNode,
59             classes: compositionGraphNode.classes
60         });
61
62         if(!node.data().isUcpe) { //ucpe should not have tooltip
63             this.initNodeTooltip(node);
64         }
65         return node;
66     };
67
68     /**
69      * The function will create a component instance node by the componentInstance position.
70      * If the node is UCPE the function will create all cp lan&wan for the ucpe
71      * @param cy
72      * @param compositionGraphNode
73      * @returns {Cy.CollectionElements}
74      */
75     public addComponentInstanceNodeToGraph(cy:Cy.Instance, compositionGraphNode:CompositionCiNodeBase):Cy.CollectionElements {
76
77         let nodePosition = {
78             x: +compositionGraphNode.componentInstance.posX,
79             y: +compositionGraphNode.componentInstance.posY
80         };
81
82         let node = this.addNodeToGraph(cy, compositionGraphNode, nodePosition);
83         if (compositionGraphNode.isUcpe) {
84             this.createUcpeCpNodes(cy, node);
85         }
86         return node;
87     };
88
89     /**
90      * This function will create CP_WAN & CP_LAN  for the UCPE. this is a special node on the group that will behave like ports on the ucpe
91      * @param cy
92      * @param ucpeGraphNode
93      */
94     private createUcpeCpNodes(cy:Cy.Instance, ucpeGraphNode:Cy.CollectionNodes):void {
95
96         let requirementsArray:Array<any> = ucpeGraphNode.data().componentInstance.requirements["tosca.capabilities.Node"];
97         //show only LAN or WAN requirements
98         requirementsArray = _.reject(requirementsArray, (requirement:any) => {
99             let name:string = requirement.ownerName.toLowerCase();
100             return name.indexOf('lan') === -1 && name.indexOf('wan') === -1;
101         });
102         requirementsArray.sort(function (a, b) {
103             let nameA = a.ownerName.toLowerCase().match(/[^ ]+/)[0];
104             let nameB = b.ownerName.toLowerCase().match(/[^ ]+/)[0];
105             let numA = _.last(a.ownerName.toLowerCase().split(' '));
106             let numB = _.last(b.ownerName.toLowerCase().split(' '));
107
108             if (nameA === nameB) return numA > numB ? 1 : -1;
109             return nameA < nameB ? 1 : -1;
110         });
111         let position = angular.copy(ucpeGraphNode.boundingbox());
112         //add CP nodes to group
113         let topCps:number = 0;
114         for (let i = 0; i < requirementsArray.length; i++) {
115
116             let cpNode = this.NodesFactory.createUcpeCpNode(angular.copy(ucpeGraphNode.data().componentInstance));
117             cpNode.componentInstance.capabilities = requirementsArray[i];
118             cpNode.id = requirementsArray[i].ownerId;
119             cpNode.group = ucpeGraphNode.data().componentInstance.uniqueId;
120             cpNode.name = requirementsArray[i].ownerName; //for tooltip
121             cpNode.displayName = requirementsArray[i].ownerName;
122             cpNode.displayName = cpNode.displayName.length > 5 ? cpNode.displayName.substring(0, 5) + '...' : cpNode.displayName;
123
124
125             if (cpNode.name.toLowerCase().indexOf('lan') > -1) {
126                 cpNode.textPosition = "top";
127                 cpNode.componentInstance.posX = position.x1 + (i * 90) - (topCps * 90) + 53;
128                 cpNode.componentInstance.posY = position.y1 + 400 + 27;
129             } else {
130                 cpNode.textPosition = "bottom";
131                 cpNode.componentInstance.posX = position.x1 + (topCps * 90) + 53;
132                 cpNode.componentInstance.posY = position.y1 + 27;
133                 topCps++;
134             }
135             let cyCpNode = this.addComponentInstanceNodeToGraph(cy, cpNode);
136             cyCpNode.lock();
137         }
138     };
139
140     /**
141      * Returns relation source and target nodes.
142      * @param nodes - all nodes in graph in order to find the edge connecting the two nodes
143      * @param fromNodeId
144      * @param toNodeId
145      * @returns [source, target] array of source node and target node.
146      */
147     public getRelationNodes(nodes:Cy.CollectionNodes, fromNodeId:string, toNodeId:string) {
148         return [
149             _.find(nodes, (node:Cy.CollectionFirst) => node.data().id === fromNodeId),
150             _.find(nodes, (node:Cy.CollectionFirst) => node.data().id === toNodeId)
151         ];
152     }
153
154     /**
155      * Add link to graph - only draw the link
156      * @param cy
157      * @param link
158      * @param getRelationRequirementCapability
159      */
160     public insertLinkToGraph = (cy:Cy.Instance, link:CompositionCiLinkBase, getRelationRequirementCapability:Function) => {
161         const relationNodes = this.getRelationNodes(cy.nodes(), link.source, link.target);
162         const sourceNode:CompositionCiNodeBase = relationNodes[0] && relationNodes[0].data();
163         const targetNode:CompositionCiNodeBase = relationNodes[1] && relationNodes[1].data();
164         if ((sourceNode && !sourceNode.certified) || (targetNode && !targetNode.certified)) {
165             link.classes = 'not-certified-link';
166         }
167         let linkElement = cy.add({
168             group: 'edges',
169             data: link,
170             classes: link.classes
171         });
172         const getLinkRequirementCapability = () =>
173             getRelationRequirementCapability(link.relation.relationships[0], sourceNode.componentInstance, targetNode.componentInstance);
174         this.initLinkTooltip(linkElement, link.relation.relationships[0], getLinkRequirementCapability);
175     };
176
177     /**
178      * Add service path link to graph - only draw the link
179      * @param cy
180      * @param link
181      */
182     public insertServicePathLinkToGraph = (cy:Cy.Instance, link:CompositionCiServicePathLink) => {
183         let linkElement = cy.add({
184             group: 'edges',
185             data: link,
186             classes: link.classes
187         });
188         this.initServicePathTooltip(linkElement, link);
189     };
190
191     /**
192      * Returns function for the link tooltip content
193      * @param {Relationship} linkRelation
194      * @param {Requirement} requirement
195      * @param {Capability} capability
196      * @returns {() => string}
197      * @private
198      */
199     private _getLinkTooltipContent(linkRelation:Relationship, requirement?:Requirement, capability?:Capability):string {
200         return '<div class="line">' +
201             '<span class="req-cap-label">R: </span>' +
202             '<span>' + (requirement ? requirement.getTitle() : linkRelation.relation.requirement) + '</span>' +
203             '</div>' +
204             '<div class="line">' +
205             '<div class="sprite-new link-tooltip-arrow"></div>' +
206             '<span class="req-cap-label">C: </span>' +
207             '<span>' + (capability ? capability.getTitle() : linkRelation.relation.capability) + '</span>' +
208             '</div>';
209     }
210
211     /**
212      * This function will init qtip tooltip on the link
213      * @param linkElement - the link we want the tooltip to apply on,
214      * @param link
215      * @param getLinkRequirementCapability
216      * link - the link obj
217      */
218     public initLinkTooltip(linkElement:Cy.CollectionElements, link:Relationship, getLinkRequirementCapability:Function) {
219         const content = () => this._getLinkTooltipContent(link);  // base tooltip content without owner names
220         const render = (event, api) => {
221             // on render (called once at first show), get the link requirement and capability and change to full tooltip content (with owner names)
222             getLinkRequirementCapability().then((linkReqCap) => {
223                 const fullContent = () => this._getLinkTooltipContent(link, linkReqCap.requirement, linkReqCap.capability);
224                 api.set('content.text', fullContent);
225             });
226         };
227         linkElement.qtip(this.prepareInitTooltipData({content, events: {render}}));
228     };
229
230     /**
231      *
232      * @param linkElement
233      * @param link
234      */
235     public initServicePathTooltip(linkElement:Cy.CollectionElements, link:CompositionCiServicePathLink) {
236         let content = function () {
237             return '<div class="line">' +
238                 '<div>'+link.pathName+'</div>' +
239                 '</div>';
240         };
241         linkElement.qtip(this.prepareInitTooltipData({content}));
242     };
243
244     private prepareInitTooltipData(options?:Object) {
245         return _.merge({
246             position: {
247                 my: 'top center',
248                 at: 'bottom center',
249                 adjust: {x:0, y:0},
250                 effect: false
251             },
252             style: {
253                 classes: 'qtip-dark qtip-rounded qtip-custom link-qtip',
254                 tip: {
255                     width: 16,
256                     height: 8
257                 }
258             },
259             show: {
260                 event: 'mouseover',
261                 delay: 1000
262             },
263             hide: {event: 'mouseout mousedown'},
264             includeLabels: true,
265             events: {}
266         }, options);
267     }
268
269     /**
270      *  go over the relations and draw links on the graph
271      * @param cy
272      * @param instancesRelations
273      * @param getRelationRequirementCapability - function to get requirement and capability of a relation
274      */
275     public initGraphLinks(cy:Cy.Instance, instancesRelations:Array<RelationshipModel>, getRelationRequirementCapability:Function) {
276
277         if (instancesRelations) {
278             _.forEach(instancesRelations, (relationshipModel:RelationshipModel) => {
279                 _.forEach(relationshipModel.relationships, (relationship:Relationship) => {
280                     let linkToCreate = this.LinksFactory.createGraphLink(cy, relationshipModel, relationship);
281                     this.insertLinkToGraph(cy, linkToCreate, getRelationRequirementCapability);
282                 });
283             });
284         }
285     }
286
287     /**
288      *  Determine which nodes are in the UCPE and set child data for them.
289      * @param cy
290      */
291     public initUcpeChildren(cy:Cy.Instance) {
292         let ucpe:Cy.CollectionNodes = cy.nodes('[?isUcpe]'); // Get ucpe on graph if exist
293         _.each(cy.edges('.ucpe-host-link'), (link)=> {
294
295             let ucpeChild:Cy.CollectionNodes = (link.source().id() == ucpe.id()) ? link.target() : link.source();
296             this.initUcpeChildData(ucpeChild, ucpe);
297
298             //vls dont have ucpe-host-link connection, so need to find them and iterate separately
299             let connectedVLs = ucpeChild.connectedEdges().connectedNodes('.vl-node');
300             _.forEach(connectedVLs, (vl)=> { //all connected vls must be UCPE children because not allowed to connect to a VL outside of the UCPE
301                 this.initUcpeChildData(vl, ucpe);
302             });
303         });
304     }
305
306     /**
307      *  Set properties for nodes contained by the UCPE
308      * @param childNode- node contained in UCPE
309      * @param ucpe- ucpe container node
310      */
311     public initUcpeChildData(childNode:Cy.CollectionNodes, ucpe:Cy.CollectionNodes) {
312
313         if (!childNode.data('isInsideGroup')) {
314             this.updateUcpeChildPosition(childNode, ucpe);
315             childNode.data({isInsideGroup: true});
316         }
317
318     }
319
320     /**
321      *  Updates UCPE child node offset, which allows child nodes to be dragged in synchronization with ucpe
322      * @param childNode- node contained in UCPE
323      * @param ucpe- ucpe container node
324      */
325     public updateUcpeChildPosition(childNode:Cy.CollectionNodes, ucpe:Cy.CollectionNodes) {
326         let childPos:Cy.Position = childNode.relativePosition();
327         let ucpePos:Cy.Position = ucpe.relativePosition();
328         let offset:Cy.Position = {
329             x: childPos.x - ucpePos.x,
330             y: childPos.y - ucpePos.y
331         };
332         childNode.data("ucpeOffset", offset);
333     }
334
335     /**
336      *  Removes ucpe-child properties from the node
337      * @param childNode- node being removed from UCPE
338      */
339     public removeUcpeChildData(childNode:Cy.CollectionNodes) {
340         childNode.removeData("ucpeOffset");
341         childNode.data({isInsideGroup: false});
342
343     }
344
345
346     public HTMLCoordsToCytoscapeCoords(cytoscapeBoundingBox:Cy.Extent, mousePos:Cy.Position):Cy.Position {
347         return {x: mousePos.x + cytoscapeBoundingBox.x1, y: mousePos.y + cytoscapeBoundingBox.y1}
348     };
349
350
351     public getCytoscapeNodePosition = (cy:Cy.Instance, event:IDragDropEvent):Cy.Position => {
352         let targetOffset = $(event.target).offset();
353         let x = (event.pageX - targetOffset.left) / cy.zoom();
354         let y = (event.pageY - targetOffset.top) / cy.zoom();
355
356         return this.HTMLCoordsToCytoscapeCoords(cy.extent(), {
357             x: x,
358             y: y
359         });
360     };
361
362
363     public getNodePosition(node:Cy.CollectionFirstNode):Cy.Position {
364         let nodePosition = node.relativePoint();
365         if (node.data().isUcpe) { //UCPEs use bounding box and not relative point.
366             nodePosition = {x: node.boundingbox().x1, y: node.boundingbox().y1};
367         }
368
369         return nodePosition;
370     }
371
372     /**
373      * Generic function that can be used for any html elements overlaid on canvas
374      * Returns the html position of a node on canvas, including left palette and header offsets. Option to pass in additional offset to add to return position.
375      * @param node
376      * @param additionalOffset
377      * @returns {Cy.Position}
378
379      public getNodePositionWithOffset = (node:Cy.CollectionFirstNode, additionalOffset?:Cy.Position): Cy.Position => {
380             if(!additionalOffset) additionalOffset = {x: 0, y:0};
381
382             let nodePosition = node.renderedPosition();
383             let posWithOffset:Cy.Position = {
384                 x: nodePosition.x + GraphUIObjects.DIAGRAM_PALETTE_WIDTH_OFFSET + additionalOffset.x,
385                 y: nodePosition.y + GraphUIObjects.COMPOSITION_HEADER_OFFSET + additionalOffset.y
386             };
387             return posWithOffset;
388         };*/
389
390     /**
391      *  return true/false if first node contains in second - this used in order to verify is node is entirely inside ucpe
392      * @param firstBox
393      * @param secondBox
394      * @returns {boolean}
395      */
396     public isFirstBoxContainsInSecondBox(firstBox:Cy.BoundingBox, secondBox:Cy.BoundingBox) {
397
398         return firstBox.x1 > secondBox.x1 && firstBox.x2 < secondBox.x2 && firstBox.y1 > secondBox.y1 && firstBox.y2 < secondBox.y2;
399
400     };
401
402
403     /**
404      * Check if node node bounds position is inside any ucpe on graph, and return the ucpe
405      * @param {diagram} the diagram.
406      * @param {nodeActualBounds} the actual bound position of the node.
407      * @return the ucpe if found else return null
408      */
409     public isInUcpe = (cy:Cy.Instance, nodeBounds:Cy.BoundingBox):Cy.CollectionElements => {
410
411         let ucpeNodes = cy.nodes('[?isUcpe]').filterFn((ucpeNode) => {
412             return this.isFirstBoxContainsInSecondBox(nodeBounds, ucpeNode.boundingbox());
413         });
414         return ucpeNodes;
415     };
416
417     /**
418      *
419      * @param cy
420      * @param node
421      * @returns {Array}
422      */
423     public getLinkableNodes(cy:Cy.Instance, node:Cy.CollectionFirstNode):Array<CompositionCiNodeBase> {
424         let compatibleNodes = [];
425         _.each(cy.nodes(), (tempNode)=> {
426             if (this.nodeLocationsCompatible(cy, node, tempNode)) {
427                 compatibleNodes.push(tempNode.data());
428             }
429         });
430         return compatibleNodes;
431     }
432
433     /**
434      * Checks whether node locations are compatible in reference to UCPEs.
435      * Returns true if both nodes are in UCPE or both nodes out, or one node is UCPEpart.
436      * @param node1
437      * @param node2
438      */
439     public nodeLocationsCompatible(cy:Cy.Instance, node1:Cy.CollectionFirstNode, node2:Cy.CollectionFirstNode) {
440
441         let ucpe = cy.nodes('[?isUcpe]');
442         if(!ucpe.length){ return true; }
443         if(node1.data().isUcpePart || node2.data().isUcpePart) { return true; }
444
445         return (this.isFirstBoxContainsInSecondBox(node1.boundingbox(), ucpe.boundingbox()) == this.isFirstBoxContainsInSecondBox(node2.boundingbox(), ucpe.boundingbox()));
446
447     }
448
449     /**
450      * This function will init qtip tooltip on the node
451      * @param node - the node we want the tooltip to apply on
452      */
453     public initNodeTooltip(node:Cy.CollectionNodes) {
454
455         let opts = {
456             content: function () {
457                 return this.data('name');
458             },
459             position: {
460                 my: 'top center',
461                 at: 'bottom center',
462                 adjust: {x:0, y:-5}
463             },
464             style: {
465                 classes: 'qtip-dark qtip-rounded qtip-custom',
466                 tip: {
467                     width: 16,
468                     height: 8
469                 }
470             },
471             show: {
472                 event: 'mouseover',
473                 delay: 1000
474             },
475             hide: {event: 'mouseout mousedown'},
476             includeLabels: true
477         };
478
479         if (node.data().isUcpePart){ //fix tooltip positioning for UCPE-cps
480             opts.position.adjust = {x:0, y:20};
481         }
482
483         node.qtip(opts);
484     };
485 }
486
487 CommonGraphUtils.$inject = ['NodesFactory', 'LinksFactory'];