CSIT Fix for SDC-2585
[sdc.git] / catalog-ui / src / app / directives / graphs-v2 / composition-graph / utils / composition-graph-general-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 {ComponentInstance, Component, Match, CompositionCiLinkBase, CompositionCiNodeUcpeCp} from "app/models";
23 import {QueueUtils, Dictionary, GraphUIObjects} from "app/utils";
24 import {LoaderService} from "app/services";
25 import {MatchCapabilitiesRequirementsUtils} from "./match-capability-requierment-utils";
26 import {CommonGraphUtils} from "../../common/common-graph-utils";
27
28
29 export class CompositionGraphGeneralUtils {
30
31     public componentRequirementsAndCapabilitiesCaching = new Dictionary<string, Component>();
32     protected static graphUtilsUpdateQueue:QueueUtils;
33
34     constructor(private $q:ng.IQService,
35                 private LoaderService:LoaderService,
36                 private commonGraphUtils:CommonGraphUtils,
37                 private matchCapabilitiesRequirementsUtils:MatchCapabilitiesRequirementsUtils, 
38                 private Notification:any) {
39         CompositionGraphGeneralUtils.graphUtilsUpdateQueue = new QueueUtils(this.$q);
40     }
41
42
43     /**
44      * Get the offset for the link creation Menu
45      * @param point
46      * @returns {Cy.Position}
47      */
48     public calcMenuOffset:Function = (point:Cy.Position):Cy.Position => {
49         point.x = point.x + 60;
50         point.y = point.y + 105;
51         return point;
52     };
53
54     /**
55      *  return the top left position of the link menu
56      * @param cy
57      * @param targetNodePosition
58      * @returns {Cy.Position}
59      */
60     public getLinkMenuPosition = (cy:Cy.Instance, targetNodePosition:Cy.Position) => {
61         let menuPosition:Cy.Position = this.calcMenuOffset(targetNodePosition); //get the link mid point
62         if ($(document.body).height() < menuPosition.y + GraphUIObjects.LINK_MENU_HEIGHT + $(document.getElementsByClassName('sdc-composition-graph-wrapper')).offset().top) { // if position menu is overflow bottom
63             menuPosition.y = $(document.body).height() - GraphUIObjects.TOP_HEADER_HEIGHT - GraphUIObjects.LINK_MENU_HEIGHT;
64         }
65         return menuPosition;
66     };
67
68
69     public zoomGraphTo = (cy:Cy.Instance, zoomLevel: number):void => {
70         let zy = cy.height() / 2;
71         let zx = cy.width() / 2;
72         cy.zoom({
73             level: zoomLevel,
74             renderedPosition: { x: zx, y: zy }
75         });
76     }
77
78
79     //saves the current zoom, and then sets a temporary maximum zoom for zoomAll, and then reverts to old value
80     public zoomAllWithMax = (cy:Cy.Instance, maxZoom:number):void => {
81
82         let oldMaxZoom:number = cy.maxZoom();
83         
84         cy.maxZoom(maxZoom);
85         this.zoomAll(cy);
86         cy.maxZoom(oldMaxZoom);
87
88     };
89
90
91     //Zooms to fit all of the nodes in the collection passed in. If no nodes are passed in, will zoom to fit all nodes on graph
92     public zoomAll = (cy:Cy.Instance, nodes?:Cy.CollectionNodes):void => {
93
94         if (!nodes || !nodes.length) {
95             nodes = cy.nodes();
96         }
97
98         cy.resize();
99         cy.animate({
100             fit: { eles: nodes, padding: 20 },
101             center: { eles: nodes }
102         }, { duration: 400 });
103     };
104
105     /**
106      * will return true/false if two nodes overlapping
107      *
108      * @param graph node
109      */
110     private isNodesOverlapping(node:Cy.CollectionFirstNode, draggedNode:Cy.CollectionFirstNode):boolean {
111
112         let nodeBoundingBox:Cy.BoundingBox = node.renderedBoundingBox();
113         let secondNodeBoundingBox:Cy.BoundingBox = draggedNode.renderedBoundingBox();
114
115         return this.isBBoxOverlapping(nodeBoundingBox, secondNodeBoundingBox);
116     }
117
118     /**
119      * Checks whether the bounding boxes of two nodes are overlapping on any side
120      * @param nodeOneBBox
121      * @param nodeTwoBBox
122      * @returns {boolean}
123      */
124     private isBBoxOverlapping(nodeOneBBox:Cy.BoundingBox, nodeTwoBBox:Cy.BoundingBox) {
125         return (((nodeOneBBox.x1 < nodeTwoBBox.x1 && nodeOneBBox.x2 > nodeTwoBBox.x1) ||
126         (nodeOneBBox.x1 < nodeTwoBBox.x2 && nodeOneBBox.x2 > nodeTwoBBox.x2) ||
127         (nodeTwoBBox.x1 < nodeOneBBox.x1 && nodeTwoBBox.x2 > nodeOneBBox.x2)) &&
128         ((nodeOneBBox.y1 < nodeTwoBBox.y1 && nodeOneBBox.y2 > nodeTwoBBox.y1) ||
129         (nodeOneBBox.y1 < nodeTwoBBox.y2 && nodeOneBBox.y2 > nodeTwoBBox.y2) ||
130         (nodeTwoBBox.y1 < nodeOneBBox.y1 && nodeTwoBBox.y2 > nodeOneBBox.y2)))
131     }
132
133
134     /**
135      * Checks whether a specific component instance can be hosted on the UCPE instance
136      * @param cy - Cytoscape instance
137      * @param fromUcpeInstance
138      * @param toComponentInstance
139      * @returns {Match}
140      */
141     public canBeHostedOn(cy:Cy.Instance, fromUcpeInstance:ComponentInstance, toComponentInstance:ComponentInstance):Match {
142
143         let matches:Array<Match> = this.matchCapabilitiesRequirementsUtils.getMatchedRequirementsCapabilities(fromUcpeInstance, toComponentInstance, this.getAllCompositionCiLinks(cy));
144         let hostedOnMatch:Match = _.find(matches, (match:Match) => {
145             return match.requirement.capability.toLowerCase() === 'tosca.capabilities.container';
146         });
147
148         return hostedOnMatch;
149     };
150
151
152     /**
153      * Checks whether node can be dropped into UCPE
154      * @param cy
155      * @param nodeToInsert
156      * @param ucpeNode
157      * @returns {boolean}
158      */
159     private isValidDropInsideUCPE(cy:Cy.Instance, nodeToInsert:ComponentInstance, ucpeNode:ComponentInstance):boolean {
160
161         let hostedOnMatch:Match = this.canBeHostedOn(cy, ucpeNode, nodeToInsert);
162         let result:boolean = !angular.isUndefined(hostedOnMatch) || nodeToInsert.isVl(); //group validation
163         return result;
164
165     };
166
167
168     /**
169      * For drops from palette, checks whether the node can be dropped. If node is being held over another node, check if capable of hosting
170      * @param cy
171      * @param pseudoNodeBBox
172      * @param paletteComponentInstance
173      * @returns {boolean}
174      */
175     public isPaletteDropValid(cy:Cy.Instance, pseudoNodeBBox:Cy.BoundingBox, paletteComponentInstance:ComponentInstance) {
176
177         let componentIsUCPE:boolean = (paletteComponentInstance.capabilities && paletteComponentInstance.capabilities['tosca.capabilities.Container'] && paletteComponentInstance.name.toLowerCase().indexOf('ucpe') > -1);
178
179         if (componentIsUCPE && cy.nodes('[?isUcpe]').length > 0) { //second UCPE not allowed
180             return false;
181         }
182
183         let illegalOverlappingNodes = _.filter(cy.nodes("[isSdcElement]"), (graphNode:Cy.CollectionFirstNode) => {
184
185             if (this.isBBoxOverlapping(pseudoNodeBBox, graphNode.renderedBoundingBox())) {
186                 if (!componentIsUCPE && graphNode.data().isUcpe) {
187                     return !this.isValidDropInsideUCPE(cy, paletteComponentInstance, graphNode.data().componentInstance); //if this is valid insert into ucpe, we return false - no illegal overlapping nodes
188                 }
189                 return true;
190             }
191
192             return false;
193         });
194
195         return illegalOverlappingNodes.length === 0;
196     }
197
198     /**
199      * will return true/false if a drop of a single node is valid
200      *
201      * @param graph node
202      */
203     public isValidDrop(cy:Cy.Instance, draggedNode:Cy.CollectionFirstNode):boolean {
204
205         let illegalOverlappingNodes = _.filter(cy.nodes("[isSdcElement]"), (graphNode:Cy.CollectionFirstNode) => { //all sdc nodes, removing child nodes (childe node allways collaps
206
207             if (draggedNode.data().isUcpe && (graphNode.isChild() || graphNode.data().isInsideGroup)) { //ucpe cps always inside ucpe, no overlapping
208                 return false;
209             }
210             if (draggedNode.data().isInsideGroup && (!draggedNode.active() || graphNode.data().isUcpe)) {
211                 return false;
212             }
213
214             if (!draggedNode.data().isUcpe && !(draggedNode.data() instanceof CompositionCiNodeUcpeCp) && graphNode.data().isUcpe) { //case we are dragging a node into UCPE
215                 let isEntirelyInUCPE:boolean = this.commonGraphUtils.isFirstBoxContainsInSecondBox(draggedNode.renderedBoundingBox(), graphNode.renderedBoundingBox());
216                 if (isEntirelyInUCPE) {
217                     if (this.isValidDropInsideUCPE(cy, draggedNode.data().componentInstance, graphNode.data().componentInstance)) { //if this is valid insert into ucpe, we return false - no illegal overlapping nodes
218                         return false;
219                     }
220                 }
221             }
222             return graphNode.data().id !== draggedNode.data().id && this.isNodesOverlapping(draggedNode, graphNode);
223
224         });
225         // return false;
226         return illegalOverlappingNodes.length === 0;
227     };
228
229     /**
230      * will return true/false if the move of the nodes is valid (no node overlapping and verifying if insert into UCPE is valid)
231      *
232      * @param  nodesArray - the selected drags nodes
233      */
234     public isGroupValidDrop(cy:Cy.Instance, nodesArray:Cy.CollectionNodes):boolean {
235         let filterDraggedNodes = nodesArray.filter('[?isDraggable]');
236         let isValidDrop = _.every(filterDraggedNodes, (node:Cy.CollectionFirstNode) => {
237             return this.isValidDrop(cy, node);
238
239         });
240         return isValidDrop;
241     };
242
243     /**
244      * get all links in diagram
245      * @param cy
246      * @returns {any[]|boolean[]}
247      */
248     public getAllCompositionCiLinks = (cy:Cy.Instance):Array<CompositionCiLinkBase> => {
249         return _.map(cy.edges("[isSdcElement]"), (edge:Cy.CollectionEdges) => {
250             return edge.data();
251         });
252     };
253
254
255     public showPolicyUpdateSuccess = () => {
256         this.Notification.success({
257             message: "Policy Updated",
258             title: "Success"
259         });
260     }
261     
262     public showGroupUpdateSuccess = () => {
263         this.Notification.success({
264             message: "Group Updated",
265             title: "Success"
266         });
267     }
268
269     public showUpdateFailure = () => {
270         this.Notification.error({
271             message: "Update Failed",
272             title: "Error"
273         });
274     };
275
276     /**
277      * Get Graph Utils server queue
278      * @returns {QueueUtils}
279      */
280     public getGraphUtilsServerUpdateQueue():QueueUtils {
281         return CompositionGraphGeneralUtils.graphUtilsUpdateQueue;
282     }
283     ;
284
285     /**
286      *
287      * @param blockAction - true/false if this is a block action
288      * @param instances
289      * @param component
290      */
291     public pushMultipleUpdateComponentInstancesRequestToQueue = (blockAction:boolean, instances:Array<ComponentInstance>, component:Component):void => {
292         if (blockAction) {
293             this.getGraphUtilsServerUpdateQueue().addBlockingUIAction(
294                 () => component.updateMultipleComponentInstances(instances)
295             );
296         } else {
297             this.getGraphUtilsServerUpdateQueue().addNonBlockingUIAction(
298                 () => component.updateMultipleComponentInstances(instances),
299                 () => this.LoaderService.hideLoader('composition-graph'));
300         }
301     };
302
303     /**
304      * this function will update component instance data
305      * @param blockAction - true/false if this is a block action
306      * @param updatedInstance
307      */
308     public pushUpdateComponentInstanceActionToQueue = (component:Component, blockAction:boolean, updatedInstance:ComponentInstance):void => {
309
310         if (blockAction) {
311             this.LoaderService.showLoader('composition-graph');
312             this.getGraphUtilsServerUpdateQueue().addBlockingUIAction(
313                 () => component.updateComponentInstance(updatedInstance)
314             );
315         } else {
316             this.getGraphUtilsServerUpdateQueue().addNonBlockingUIAction(
317                 () => component.updateComponentInstance(updatedInstance),
318                 () => this.LoaderService.hideLoader('composition-graph'));
319         }
320     };
321 }
322
323 CompositionGraphGeneralUtils.$inject = ['$q', 'LoaderService', 'CommonGraphUtils', 'MatchCapabilitiesRequirementsUtils', 'Notification'];