re base code
[sdc.git] / catalog-ui / src / app / directives / graphs-v2 / composition-graph / utils / composition-graph-nodes-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 {Component, NodesFactory, ComponentInstance, CompositionCiNodeVl,IAppMenu,AssetPopoverObj, Service} from "app/models";
23 import {EventListenerService, LoaderService} from "app/services";
24 import {GRAPH_EVENTS,ModalsHandler,GraphUIObjects} from "app/utils";
25 import {CompositionGraphGeneralUtils} from "./composition-graph-general-utils";
26 import {CommonGraphUtils} from "../../common/common-graph-utils";
27 import {CompositionCiServicePathLink} from "app/models/graph/graph-links/composition-graph-links/composition-ci-service-path-link";
28 import {ServiceGenericResponse} from "app/ng2/services/responses/service-generic-response";
29 import {ServiceServiceNg2} from 'app/ng2/services/component-services/service.service';
30 /**
31  * Created by obarda on 11/9/2016.
32  */
33 export class CompositionGraphNodesUtils {
34     constructor(private NodesFactory:NodesFactory, private $log:ng.ILogService,
35                 private GeneralGraphUtils:CompositionGraphGeneralUtils,
36                 private commonGraphUtils:CommonGraphUtils,
37                 private eventListenerService:EventListenerService,
38                 private loaderService:LoaderService,
39                 private serviceService:ServiceServiceNg2,
40                 /*private sdcMenu: IAppMenu,
41                 private ModalsHandler: ModalsHandler*/) {
42
43     }
44
45     /**
46      * Returns component instances for all nodes passed in
47      * @param nodes - Cy nodes
48      * @returns {any[]}
49      */
50     public getAllNodesData(nodes:Cy.CollectionNodes) {
51         return _.map(nodes, (node:Cy.CollectionFirstNode)=> {
52             return node.data();
53         })
54     };
55
56
57     public highlightMatchingNodesByName = (cy: Cy.Instance, nameToMatch: string) => {
58
59         cy.batch(() => {
60             cy.nodes("[name !@^= '" + nameToMatch + "']").style({ 'background-image-opacity': 0.4 });
61             cy.nodes("[name @^= '" + nameToMatch + "']").style({ 'background-image-opacity': 1 });
62         })
63         
64     }
65
66     //Returns all nodes whose name starts with searchTerm
67     public getMatchingNodesByName = (cy: Cy.Instance, nameToMatch: string): Cy.CollectionNodes => {
68         return cy.nodes("[name @^= '" + nameToMatch + "']");
69     };
70
71     /**
72      * Deletes component instances on server and then removes it from the graph as well
73      * @param cy
74      * @param component
75      * @param nodeToDelete
76      */
77     public deleteNode(cy:Cy.Instance, component:Component, nodeToDelete:Cy.CollectionNodes):void {
78
79         this.loaderService.showLoader('composition-graph');
80         let onSuccess:(response:ComponentInstance) => void = (response:ComponentInstance) => {
81             console.info('onSuccess', response);
82
83             //if node to delete is a UCPE, remove all children (except UCPE-CPs) and remove their "hostedOn" links
84             if (nodeToDelete.data().isUcpe) {
85                 _.each(cy.nodes('[?isInsideGroup]'), (node)=> {
86                     this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_REMOVE_NODE_FROM_UCPE, node, nodeToDelete);
87                 });
88             }
89
90             //check whether the node is connected to any VLs that only have one other connection. If so, delete that VL as well
91             if (!(nodeToDelete.data() instanceof CompositionCiNodeVl)) {
92                 let connectedVls:Array<Cy.CollectionFirstNode> = this.getConnectedVlToNode(nodeToDelete);
93                 this.handleConnectedVlsToDelete(connectedVls);
94             }
95
96             // check whether there is a service path going through this node, and if so clean it from the graph.
97             let nodeId = nodeToDelete.data().id;
98             let connectedPathLinks = cy.collection(`[type="${CompositionCiServicePathLink.LINK_TYPE}"][source="${nodeId}"], [type="${CompositionCiServicePathLink.LINK_TYPE}"][target="${nodeId}"]`);
99             _.forEach(connectedPathLinks, (link, key) => {
100                 cy.remove(`[pathId="${link.data().pathId}"]`);
101             });
102
103             // update service path list
104             this.serviceService.getComponentCompositionData(component).subscribe((response:ServiceGenericResponse) => {
105                 (<Service>component).forwardingPaths = response.forwardingPaths;
106             });
107
108             this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE_SUCCESS, nodeId);
109
110             //update UI
111             cy.remove(nodeToDelete);
112         };
113
114         let onFailed:(response:any) => void = (response:any) => {
115             console.info('onFailed', response);
116         };
117
118
119         this.GeneralGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIActionWithReleaseCallback(
120             () => component.deleteComponentInstance(nodeToDelete.data().componentInstance.uniqueId).then(onSuccess, onFailed),
121             () => this.loaderService.hideLoader('composition-graph')
122         );
123
124     };
125
126 /*
127         public confirmDeleteNode = (nodeId:string, cy:Cy.Instance, component:Component) => {
128             let node:Cy.CollectionNodes = cy.getElementById(nodeId);
129             let onOk = ():void => {
130                 this.deleteNode(cy, component, node);
131             };
132
133             let componentInstance:ComponentInstance = node.data().componentInstance;
134             let state = "deleteInstance";
135             let title:string =  this.sdcMenu.alertMessages[state].title;
136             let message:string =  this.sdcMenu.alertMessages[state].message.format([componentInstance.name]);
137
138             this.ModalsHandler.openAlertModal(title, message).then(onOk);
139         };*/
140     /**
141      * Finds all VLs connected to a single node
142      * @param node
143      * @returns {Array<Cy.CollectionFirstNode>}
144      */
145     public getConnectedVlToNode = (node:Cy.CollectionNodes):Array<Cy.CollectionFirstNode> => {
146         let connectedVls:Array<Cy.CollectionFirstNode> = new Array<Cy.CollectionFirstNode>();
147         _.forEach(node.connectedEdges().connectedNodes(), (node:Cy.CollectionFirstNode) => {
148             if (node.data() instanceof CompositionCiNodeVl) {
149                 connectedVls.push(node);
150             }
151         });
152         return connectedVls;
153     };
154
155
156     /**
157      * Delete all VLs that have only two connected nodes (this function is called when deleting a node)
158      * @param connectedVls
159      */
160     public handleConnectedVlsToDelete = (connectedVls:Array<Cy.CollectionFirstNode>) => {
161         _.forEach(connectedVls, (vlToDelete:Cy.CollectionNodes) => {
162
163             if (vlToDelete.connectedEdges().length === 2) { // if vl connected only to 2 nodes need to delete the vl
164                 this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, vlToDelete.data().componentInstance);
165             }
166         });
167     };
168
169
170     /**
171      * This function is called when moving a node in or out of UCPE.
172      * Deletes all connected VLs that have less than 2 valid connections remaining after the move
173      * Returns the collection of vls that are in the process of deletion (async) to prevent duplicate calls while deletion is in progress
174      * @param component
175      * @param cy
176      * @param node - node that was moved in/out of ucpe
177      */
178     public deleteNodeVLsUponMoveToOrFromUCPE = (component:Component, cy:Cy.Instance, node:Cy.CollectionNodes):Cy.CollectionNodes => {
179         if (node.data() instanceof CompositionCiNodeVl) {
180             return;
181         }
182
183         let connectedVLsToDelete:Cy.CollectionNodes = cy.collection();
184         _.forEach(node.neighborhood('node'), (connectedNode) => {
185
186             //Find all neighboring nodes that are VLs
187             if (connectedNode.data() instanceof CompositionCiNodeVl) {
188
189                 //check VL's neighbors to see if it has 2 or more nodes whose location is compatible with VL (regardless of whether VL is in or out of UCPE)
190                 let compatibleNodeCount = 0;
191                 let vlNeighborhood = connectedNode.neighborhood('node');
192                 _.forEach(vlNeighborhood, (vlNeighborNode)=> {
193                     if (this.commonGraphUtils.nodeLocationsCompatible(cy, connectedNode, vlNeighborNode)) {
194                         compatibleNodeCount++;
195                     }
196                 });
197
198                 if (compatibleNodeCount < 2) {
199                     connectedVLsToDelete = connectedVLsToDelete.add(connectedNode);
200                 }
201             }
202         });
203
204         connectedVLsToDelete.each((i, vlToDelete:Cy.CollectionNodes)=> {
205             this.deleteNode(cy, component, vlToDelete);
206         });
207         return connectedVLsToDelete;
208     };
209
210     /**
211      * This function will update nodes position. if the new position is into or out of ucpe, the node will trigger the ucpe events
212      * @param cy
213      * @param component
214      * @param nodesMoved - the node/multiple nodes now moved by the user
215      */
216     public onNodesPositionChanged = (cy:Cy.Instance, component:Component, nodesMoved:Cy.CollectionNodes):void => {
217
218         if (nodesMoved.length === 0) {
219             return;
220         }
221
222         let isValidMove:boolean = this.GeneralGraphUtils.isGroupValidDrop(cy, nodesMoved);
223         if (isValidMove) {
224
225             this.$log.debug(`composition-graph::ValidDrop:: updating node position`);
226             let instancesToUpdateInNonBlockingAction:Array<ComponentInstance> = new Array<ComponentInstance>();
227
228             _.each(nodesMoved, (node:Cy.CollectionFirstNode)=> {  //update all nodes new position
229
230                 if (node.data().isUcpePart && !node.data().isUcpe) {
231                     return;
232                 }//No need to update UCPE-CPs
233
234                 //update position
235                 let newPosition:Cy.Position = this.commonGraphUtils.getNodePosition(node);
236                 node.data().componentInstance.updatePosition(newPosition.x, newPosition.y);
237
238                 //check if node moved to or from UCPE
239                 let ucpe = this.commonGraphUtils.isInUcpe(node.cy(), node.boundingbox());
240                 if (node.data().isInsideGroup || ucpe.length) {
241                     this.handleUcpeChildMove(node, ucpe, instancesToUpdateInNonBlockingAction);
242                 } else {
243                     instancesToUpdateInNonBlockingAction.push(node.data().componentInstance);
244                 }
245
246             });
247
248             if (instancesToUpdateInNonBlockingAction.length > 0) {
249                 this.GeneralGraphUtils.pushMultipleUpdateComponentInstancesRequestToQueue(false, instancesToUpdateInNonBlockingAction, component);
250             }
251         } else {
252             this.$log.debug(`composition-graph::notValidDrop:: node return to latest position`);
253             //reset nodes position
254             nodesMoved.positions((i, node) => {
255                 return {
256                     x: +node.data().componentInstance.posX,
257                     y: +node.data().componentInstance.posY
258                 };
259             })
260         }
261
262         this.GeneralGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIActionWithReleaseCallback(() => {
263         }, () => {
264             this.loaderService.hideLoader('composition-graph');
265         });
266
267     };
268
269     /**
270      * Checks whether the node has been added or removed from UCPE and triggers appropriate events
271      * @param node - node moved
272      * @param ucpeContainer - UCPE container that the node has been moved to. When moving a node out of ucpe, param will be empty
273      * @param instancesToUpdateInNonBlockingAction
274      */
275     public handleUcpeChildMove(node:Cy.CollectionFirstNode, ucpeContainer:Cy.CollectionElements, instancesToUpdateInNonBlockingAction:Array<ComponentInstance>) {
276
277         if (node.data().isInsideGroup) {
278             if (ucpeContainer.length) { //moving node within UCPE. Simply update position
279                 this.commonGraphUtils.updateUcpeChildPosition(<Cy.CollectionNodes>node, ucpeContainer);
280                 instancesToUpdateInNonBlockingAction.push(node.data().componentInstance);
281             } else { //removing node from UCPE. Notify observers
282                 this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_REMOVE_NODE_FROM_UCPE, node, ucpeContainer);
283             }
284         } else if (!node.data().isInsideGroup && ucpeContainer.length && !node.data().isUcpePart) { //adding node to UCPE
285             this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_INSERT_NODE_TO_UCPE, node, ucpeContainer, true);
286         }
287     }
288         /**
289          * Gets the position for the asset popover menu
290          * Then, check if right edge of menu would overlap horizontal screen edge (palette offset + canvas width - right panel)
291          * Then, check if bottom edge of menu would overlap the vertical end of the canvas.
292          * @param cy
293          * @param node
294          * @returns {Cy.Position}
295         
296         public createAssetPopover = (cy: Cy.Instance, node:Cy.CollectionFirstNode, isViewOnly:boolean):AssetPopoverObj => {
297
298             let menuOffset:Cy.Position = { x: node.renderedWidth() / 2, y: -(node.renderedWidth() / 2) };// getNodePositionWithOffset returns central point of node. First add node.renderedWidth()/2 to get its to border.
299             let menuPosition:Cy.Position = this.commonGraphUtils.getNodePositionWithOffset(node, menuOffset);
300             let menuSide:string = 'right';
301
302             if(menuPosition.x + GraphUIObjects.COMPOSITION_NODE_MENU_WIDTH >= cy.width() + GraphUIObjects.DIAGRAM_PALETTE_WIDTH_OFFSET - GraphUIObjects.COMPOSITION_RIGHT_PANEL_OFFSET){
303                 menuPosition.x -= menuOffset.x * 2 + GraphUIObjects.COMPOSITION_NODE_MENU_WIDTH; //menu position already includes offset to the right. Therefore, subtract double offset so we have same distance from node for menu on left
304                 menuSide = 'left';
305             }
306
307             if(menuPosition.y + GraphUIObjects.COMPOSITION_NODE_MENU_HEIGHT >= cy.height()){
308                 menuPosition.y = menuPosition.y - GraphUIObjects.COMPOSITION_NODE_MENU_HEIGHT - menuOffset.y * 2;
309             }
310
311             return new AssetPopoverObj(node.data().id, node.data().name, menuPosition, menuSide, isViewOnly);
312         };
313  */
314
315     }
316
317
318     CompositionGraphNodesUtils.$inject = ['NodesFactory', '$log', 'CompositionGraphGeneralUtils', 'CommonGraphUtils', 'EventListenerService', 'LoaderService', 'ServiceServiceNg2' /*, 'sdcMenu', 'ModalsHandler'*/]
319