Keyboard Shortcut for copy&Paste and delete
[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
41     }
42
43     /**
44      * Returns component instances for all nodes passed in
45      * @param nodes - Cy nodes
46      * @returns {any[]}
47      */
48     public getAllNodesData(nodes: Cy.CollectionNodes) {
49         return _.map(nodes, (node: Cy.CollectionFirstNode) => {
50             return node.data();
51         })
52     };
53
54
55     public highlightMatchingNodesByName = (cy: Cy.Instance, nameToMatch: string) => {
56
57         cy.batch(() => {
58             cy.nodes("[name !@^= '" + nameToMatch + "']").style({ 'background-image-opacity': 0.4 });
59             cy.nodes("[name @^= '" + nameToMatch + "']").style({ 'background-image-opacity': 1 });
60         })
61
62     }
63
64     //Returns all nodes whose name starts with searchTerm
65     public getMatchingNodesByName = (cy: Cy.Instance, nameToMatch: string): Cy.CollectionNodes => {
66         return cy.nodes("[name @^= '" + nameToMatch + "']");
67     };
68
69     /**
70      * Deletes component instances on server and then removes it from the graph as well
71      * @param cy
72      * @param component
73      * @param nodeToDelete
74      */
75     public deleteNode(cy: Cy.Instance, component: Component, nodeToDelete: Cy.CollectionNodes): void {
76
77         this.loaderService.showLoader('composition-graph');
78         let onSuccess: (response: ComponentInstance) => void = (response: ComponentInstance) => {
79             console.info('onSuccess', response);
80
81             //if node to delete is a UCPE, remove all children (except UCPE-CPs) and remove their "hostedOn" links
82             if (nodeToDelete.data().isUcpe) {
83                 _.each(cy.nodes('[?isInsideGroup]'), (node) => {
84                     this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_REMOVE_NODE_FROM_UCPE, node, nodeToDelete);
85                 });
86             }
87
88             //check whether the node is connected to any VLs that only have one other connection. If so, delete that VL as well
89             if (!(nodeToDelete.data() instanceof CompositionCiNodeVl)) {
90                 let connectedVls: Array<Cy.CollectionFirstNode> = this.getConnectedVlToNode(nodeToDelete);
91                 this.handleConnectedVlsToDelete(connectedVls);
92             }
93
94             // check whether there is a service path going through this node, and if so clean it from the graph.
95             let nodeId = nodeToDelete.data().id;
96             let connectedPathLinks = cy.collection(`[type="${CompositionCiServicePathLink.LINK_TYPE}"][source="${nodeId}"], [type="${CompositionCiServicePathLink.LINK_TYPE}"][target="${nodeId}"]`);
97             _.forEach(connectedPathLinks, (link, key) => {
98                 cy.remove(`[pathId="${link.data().pathId}"]`);
99             });
100
101             // update service path list
102             this.serviceService.getComponentCompositionData(component).subscribe((response: ServiceGenericResponse) => {
103                 (<Service>component).forwardingPaths = response.forwardingPaths;
104             });
105
106             this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE_SUCCESS, nodeId);
107
108             //update UI
109             cy.remove(nodeToDelete);
110         };
111
112         let onFailed: (response: any) => void = (response: any) => {
113             console.info('onFailed', response);
114         };
115
116
117         this.GeneralGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIActionWithReleaseCallback(
118             () => component.deleteComponentInstance(nodeToDelete.data().componentInstance.uniqueId).then(onSuccess, onFailed),
119             () => this.loaderService.hideLoader('composition-graph')
120         );
121
122     };
123
124     /**
125      * Batch delete component instances on server and then removes them from the graph as well
126      * @param cy
127      * @param component
128      * @param nodesToDelete
129      */
130     public batchDeleteNodes(cy:Cy.Instance, component:Component, nodesToDelete:Cy.CollectionNodes):Array<string> {
131         let nodesToDeleteIds:Array<string> = new Array<string>();
132         if(nodesToDelete && nodesToDelete.size() > 0){
133             nodesToDelete.each((i:number, node:Cy.CollectionNodes) => {
134                 nodesToDeleteIds.push(node.data('id'));
135             });
136             this.loaderService.showLoader('composition-graph');
137             let componentInstances:Array<ComponentInstance> = component.componentInstances;
138             let onSuccess:(response:any) => void = (deleteFailedIds:any) => {
139                 this.removeDeletedNodesOnGraph(cy, nodesToDelete, deleteFailedIds, componentInstances);
140             };
141
142             let onFailed:(response:any) => void = (response:any) => {
143                 console.error('batchDeleteNodes failed error is', response);
144             };
145
146             this.GeneralGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIActionWithReleaseCallback(
147                 () => component.batchDeleteComponentInstance(nodesToDeleteIds).then(onSuccess, onFailed),
148                 () => this.loaderService.hideLoader('composition-graph')
149             );
150         }
151
152             return nodesToDeleteIds;
153     };
154
155     private deleteNodeSuccess(cy:Cy.Instance, component:Component, nodeToDelete:Cy.CollectionNodes):void{
156         //if node to delete is a UCPE, remove all children (except UCPE-CPs) and remove their "hostedOn" links
157         if (nodeToDelete.data().isUcpe) {
158             _.each(cy.nodes('[?isInsideGroup]'), (node)=> {
159                 this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_REMOVE_NODE_FROM_UCPE, node, nodeToDelete);
160             });
161         }
162
163         //check whether the node is connected to any VLs that only have one other connection. If so, delete that VL as well
164         if (!(nodeToDelete.data() instanceof CompositionCiNodeVl)) {
165             let connectedVls:Array<Cy.CollectionFirstNode> = this.getConnectedVlToNode(nodeToDelete);
166             this.handleConnectedVlsToDelete(connectedVls);
167         }
168
169         // check whether there is a service path going through this node, and if so clean it from the graph.
170         let nodeId = nodeToDelete.data().id;
171         let connectedPathLinks = cy.collection(`[type="${CompositionCiServicePathLink.LINK_TYPE}"][source="${nodeId}"], [type="${CompositionCiServicePathLink.LINK_TYPE}"][target="${nodeId}"]`);
172         _.forEach(connectedPathLinks, (link, key) => {
173             cy.remove(`[pathId="${link.data().pathId}"]`);
174         });
175
176         // update service path list
177         this.serviceService.getComponentCompositionData(component).subscribe((response:ServiceGenericResponse) => {
178             (<Service>component).forwardingPaths = response.forwardingPaths;
179         });
180
181         this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE_SUCCESS, nodeId);
182
183         //update UI
184         cy.remove(nodeToDelete);
185     }
186
187     private removeDeletedNodesOnGraph(cy:Cy.Instance, nodesToDelete:Cy.CollectionNodes, deleteFailedIds:Array<string>, componentInstances:Array<ComponentInstance>):void {
188         nodesToDelete.each((j:number, nodeToDelete:Cy.CollectionNodes) => {
189             if(deleteFailedIds.indexOf(nodeToDelete.data('id')) < 0) {
190                 //if node to delete is a UCPE, remove all children (except UCPE-CPs) and remove their "hostedOn" links
191                 if (nodeToDelete.data().isUcpe) {
192                     _.each(cy.nodes('[?isInsideGroup]'), (node)=> {
193                         this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_REMOVE_NODE_FROM_UCPE, node , nodeToDelete);
194                     });
195                 }
196
197                 //check whether the node is connected to any VLs that only have one other connection. If so, delete that VL as well
198                 if(!(nodeToDelete.data() instanceof CompositionCiNodeVl)) {
199                     let connectedVls:Array<Cy.CollectionFirstNode> = this.getConnectedVlToNode(nodeToDelete);
200                     this.handleConnectedVlsToDelete(connectedVls);
201                 }
202
203
204                 cy.remove(nodeToDelete);
205             }
206         });
207     }
208  
209     /**
210      * Finds all VLs connected to a single node
211      * @param node
212      * @returns {Array<Cy.CollectionFirstNode>}
213      */
214     public getConnectedVlToNode = (node: Cy.CollectionNodes): Array<Cy.CollectionFirstNode> => {
215         let connectedVls: Array<Cy.CollectionFirstNode> = new Array<Cy.CollectionFirstNode>();
216         _.forEach(node.connectedEdges().connectedNodes(), (node: Cy.CollectionFirstNode) => {
217             if (node.data() instanceof CompositionCiNodeVl) {
218                 connectedVls.push(node);
219             }
220         });
221         return connectedVls;
222     };
223
224
225     /**
226      * Delete all VLs that have only two connected nodes (this function is called when deleting a node)
227      * @param connectedVls
228      */
229     public handleConnectedVlsToDelete = (connectedVls: Array<Cy.CollectionFirstNode>) => {
230         _.forEach(connectedVls, (vlToDelete: Cy.CollectionNodes) => {
231
232             if (vlToDelete.connectedEdges().length === 2) { // if vl connected only to 2 nodes need to delete the vl
233                 this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, vlToDelete.data().componentInstance);
234             }
235         });
236     };
237
238
239     /**
240      * This function is called when moving a node in or out of UCPE.
241      * Deletes all connected VLs that have less than 2 valid connections remaining after the move
242      * Returns the collection of vls that are in the process of deletion (async) to prevent duplicate calls while deletion is in progress
243      * @param component
244      * @param cy
245      * @param node - node that was moved in/out of ucpe
246      */
247     public deleteNodeVLsUponMoveToOrFromUCPE = (component: Component, cy: Cy.Instance, node: Cy.CollectionNodes): Cy.CollectionNodes => {
248         if (node.data() instanceof CompositionCiNodeVl) {
249             return;
250         }
251
252         let connectedVLsToDelete: Cy.CollectionNodes = cy.collection();
253         _.forEach(node.neighborhood('node'), (connectedNode) => {
254
255             //Find all neighboring nodes that are VLs
256             if (connectedNode.data() instanceof CompositionCiNodeVl) {
257
258                 //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)
259                 let compatibleNodeCount = 0;
260                 let vlNeighborhood = connectedNode.neighborhood('node');
261                 _.forEach(vlNeighborhood, (vlNeighborNode) => {
262                     if (this.commonGraphUtils.nodeLocationsCompatible(cy, connectedNode, vlNeighborNode)) {
263                         compatibleNodeCount++;
264                     }
265                 });
266
267                 if (compatibleNodeCount < 2) {
268                     connectedVLsToDelete = connectedVLsToDelete.add(connectedNode);
269                 }
270             }
271         });
272
273         connectedVLsToDelete.each((i, vlToDelete: Cy.CollectionNodes) => {
274             this.deleteNode(cy, component, vlToDelete);
275         });
276         return connectedVLsToDelete;
277     };
278
279     /**
280      * This function will update nodes position. if the new position is into or out of ucpe, the node will trigger the ucpe events
281      * @param cy
282      * @param component
283      * @param nodesMoved - the node/multiple nodes now moved by the user
284      */
285     public onNodesPositionChanged = (cy: Cy.Instance, component: Component, nodesMoved: Cy.CollectionNodes): void => {
286
287         if (nodesMoved.length === 0) {
288             return;
289         }
290
291         let isValidMove: boolean = this.GeneralGraphUtils.isGroupValidDrop(cy, nodesMoved);
292         if (isValidMove) {
293
294             this.$log.debug(`composition-graph::ValidDrop:: updating node position`);
295             let instancesToUpdateInNonBlockingAction: Array<ComponentInstance> = new Array<ComponentInstance>();
296
297             _.each(nodesMoved, (node: Cy.CollectionFirstNode) => {  //update all nodes new position
298
299                 if (node.data().isUcpePart && !node.data().isUcpe) {
300                     return;
301                 }//No need to update UCPE-CPs
302
303                 //update position
304                 let newPosition: Cy.Position = this.commonGraphUtils.getNodePosition(node);
305                 node.data().componentInstance.updatePosition(newPosition.x, newPosition.y);
306
307                 //check if node moved to or from UCPE
308                 let ucpe = this.commonGraphUtils.isInUcpe(node.cy(), node.boundingbox());
309                 if (node.data().isInsideGroup || ucpe.length) {
310                     this.handleUcpeChildMove(node, ucpe, instancesToUpdateInNonBlockingAction);
311                 } else {
312                     instancesToUpdateInNonBlockingAction.push(node.data().componentInstance);
313                 }
314
315             });
316
317             if (instancesToUpdateInNonBlockingAction.length > 0) {
318                 this.GeneralGraphUtils.pushMultipleUpdateComponentInstancesRequestToQueue(false, instancesToUpdateInNonBlockingAction, component);
319             }
320         } else {
321             this.$log.debug(`composition-graph::notValidDrop:: node return to latest position`);
322             //reset nodes position
323             nodesMoved.positions((i, node) => {
324                 return {
325                     x: +node.data().componentInstance.posX,
326                     y: +node.data().componentInstance.posY
327                 };
328             })
329         }
330
331         this.GeneralGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIActionWithReleaseCallback(() => {
332         }, () => {
333             this.loaderService.hideLoader('composition-graph');
334         });
335
336     };
337
338     /**
339      * Checks whether the node has been added or removed from UCPE and triggers appropriate events
340      * @param node - node moved
341      * @param ucpeContainer - UCPE container that the node has been moved to. When moving a node out of ucpe, param will be empty
342      * @param instancesToUpdateInNonBlockingAction
343      */
344     public handleUcpeChildMove(node: Cy.CollectionFirstNode, ucpeContainer: Cy.CollectionElements, instancesToUpdateInNonBlockingAction: Array<ComponentInstance>) {
345
346         if (node.data().isInsideGroup) {
347             if (ucpeContainer.length) { //moving node within UCPE. Simply update position
348                 this.commonGraphUtils.updateUcpeChildPosition(<Cy.CollectionNodes>node, ucpeContainer);
349                 instancesToUpdateInNonBlockingAction.push(node.data().componentInstance);
350             } else { //removing node from UCPE. Notify observers
351                 this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_REMOVE_NODE_FROM_UCPE, node, ucpeContainer);
352             }
353         } else if (!node.data().isInsideGroup && ucpeContainer.length && !node.data().isUcpePart) { //adding node to UCPE
354             this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_INSERT_NODE_TO_UCPE, node, ucpeContainer, true);
355         }
356     }
357
358 }
359
360
361 CompositionGraphNodesUtils.$inject = ['NodesFactory', '$log', 'CompositionGraphGeneralUtils', 'CommonGraphUtils', 'EventListenerService', 'LoaderService', 'ServiceServiceNg2' /*, 'sdcMenu', 'ModalsHandler'*/]
362