aboutsummaryrefslogtreecommitdiffstats
path: root/catalog-ui/src/app/directives/graphs-v2/composition-graph
diff options
context:
space:
mode:
authorMichael Lando <ml636r@att.com>2017-06-09 03:19:04 +0300
committerMichael Lando <ml636r@att.com>2017-06-09 03:19:04 +0300
commited64b5edff15e702493df21aa3230b81593e6133 (patch)
treea4cb01fdaccc34930a8db403a3097c0d1e40914b /catalog-ui/src/app/directives/graphs-v2/composition-graph
parent280f8015d06af1f41a3ef12e8300801c7a5e0d54 (diff)
[SDC-29] catalog 1707 rebase commit.
Change-Id: I43c3dc5cf44abf5da817649bc738938a3e8388c1 Signed-off-by: Michael Lando <ml636r@att.com>
Diffstat (limited to 'catalog-ui/src/app/directives/graphs-v2/composition-graph')
-rw-r--r--catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.directive.ts539
-rw-r--r--catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.html23
-rw-r--r--catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.less14
-rw-r--r--catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-general-utils.ts244
-rw-r--r--catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-links-utils.ts271
-rw-r--r--catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-nodes-utils.ts266
-rw-r--r--catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-palette-utils.ts163
-rw-r--r--catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/match-capability-requierment-utils.ts259
8 files changed, 1779 insertions, 0 deletions
diff --git a/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.directive.ts b/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.directive.ts
new file mode 100644
index 0000000000..db03aa53fb
--- /dev/null
+++ b/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.directive.ts
@@ -0,0 +1,539 @@
+import {
+ MatchBase,
+ LinkMenu,
+ ComponentInstance,
+ LeftPaletteComponent,
+ Component,
+ RelationMenuDirectiveObj,
+ CompositionCiNodeBase,
+ CompositionCiNodeVl,
+ NodesFactory/*,
+ AssetPopoverObj*/
+} from "app/models";
+import {ComponentInstanceFactory, ComponentFactory, GRAPH_EVENTS, GraphColors} from "app/utils";
+import {EventListenerService, LoaderService} from "app/services";
+import {CompositionGraphLinkUtils} from "./utils/composition-graph-links-utils";
+import {CompositionGraphGeneralUtils} from "./utils/composition-graph-general-utils";
+import {CompositionGraphNodesUtils} from "./utils/composition-graph-nodes-utils";
+import {CommonGraphUtils} from "../common/common-graph-utils";
+import {MatchCapabilitiesRequirementsUtils} from "./utils/match-capability-requierment-utils";
+import {CompositionGraphPaletteUtils} from "./utils/composition-graph-palette-utils";
+import {ComponentInstanceNodesStyle} from "../common/style/component-instances-nodes-style";
+import {CytoscapeEdgeEditation} from 'third-party/cytoscape.js-edge-editation/CytoscapeEdgeEditation.js';
+import {ComponentServiceNg2} from "../../../ng2/services/component-services/component.service";
+import {ComponentGenericResponse} from "../../../ng2/services/responses/component-generic-response";
+
+interface ICompositionGraphScope extends ng.IScope {
+
+ component:Component;
+ isLoading: boolean;
+ isViewOnly:boolean;
+ // Link menu - create link menu
+ relationMenuDirectiveObj:RelationMenuDirectiveObj;
+ isLinkMenuOpen:boolean;
+ createLinkFromMenu:(chosenMatch:MatchBase, vl:Component)=>void;
+
+ //modify link menu - for now only delete menu
+ relationMenuTimeout:ng.IPromise<any>;
+ linkMenuObject:LinkMenu;
+
+ //left palette functions callbacks
+ dropCallback(event:JQueryEventObject, ui:any):void;
+ beforeDropCallback(event:IDragDropEvent):void;
+ verifyDrop(event:JQueryEventObject, ui:any):void;
+
+ //Links menus
+ deleteRelation(link:Cy.CollectionEdges):void;
+ hideRelationMenu();
+ /*//asset popover menu
+ assetPopoverObj:AssetPopoverObj;
+ assetPopoverOpen:boolean;
+ hideAssetPopover():void;
+ deleteNode(nodeId:string):void;*/
+}
+
+export class CompositionGraph implements ng.IDirective {
+ private _cy:Cy.Instance;
+ private _currentlyCLickedNodePosition:Cy.Position;
+ // private $document:JQuery = $(document);
+ private dragElement:JQuery;
+ private dragComponent:ComponentInstance;
+
+ constructor(private $q:ng.IQService,
+ private $log:ng.ILogService,
+ private $timeout:ng.ITimeoutService,
+ private NodesFactory:NodesFactory,
+ private CompositionGraphLinkUtils:CompositionGraphLinkUtils,
+ private GeneralGraphUtils:CompositionGraphGeneralUtils,
+ private ComponentInstanceFactory:ComponentInstanceFactory,
+ private NodesGraphUtils:CompositionGraphNodesUtils,
+ private eventListenerService:EventListenerService,
+ private ComponentFactory:ComponentFactory,
+ private LoaderService:LoaderService,
+ private commonGraphUtils:CommonGraphUtils,
+ private matchCapabilitiesRequirementsUtils:MatchCapabilitiesRequirementsUtils,
+ private CompositionGraphPaletteUtils:CompositionGraphPaletteUtils,
+ private ComponentServiceNg2: ComponentServiceNg2) {
+
+ }
+
+ restrict = 'E';
+ template = require('./composition-graph.html');
+ scope = {
+ component: '=',
+ isViewOnly: '='
+ };
+
+ link = (scope:ICompositionGraphScope, el:JQuery) => {
+
+ this.loadGraph(scope, el);
+
+ if(scope.component.componentInstances && scope.component.componentInstancesRelations) {
+ this.loadGraphData(scope);
+ } else {
+ //when we don't have the data we register to on graph load event
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_COMPOSITION_GRAPH_DATA_LOADED, () => {
+ this.loadGraphData(scope);
+ });
+ }
+ scope.$on('$destroy', () => {
+ this._cy.destroy();
+ _.forEach(GRAPH_EVENTS, (event) => {
+ this.eventListenerService.unRegisterObserver(event);
+ });
+ });
+
+ };
+
+ private loadGraphData = (scope:ICompositionGraphScope) => {
+ this.initGraphNodes(scope.component.componentInstances, scope.isViewOnly);
+ this.commonGraphUtils.initGraphLinks(this._cy, scope.component.componentInstancesRelations);
+ this.commonGraphUtils.initUcpeChildren(this._cy);
+ }
+
+ private loadGraph = (scope:ICompositionGraphScope, el:JQuery) => {
+
+ let graphEl = el.find('.sdc-composition-graph-wrapper');
+ this.initGraph(graphEl, scope.isViewOnly);
+ this.initDropZone(scope);
+ this.registerCytoscapeGraphEvents(scope);
+ this.registerCustomEvents(scope, el);
+ this.initViewMode(scope.isViewOnly);
+
+ };
+
+ private initGraph(graphEl:JQuery, isViewOnly:boolean) {
+
+ this._cy = cytoscape({
+ container: graphEl,
+ style: ComponentInstanceNodesStyle.getCompositionGraphStyle(),
+ zoomingEnabled: false,
+ selectionType: 'single',
+ boxSelectionEnabled: true,
+ autolock: isViewOnly,
+ autoungrabify: isViewOnly
+ });
+ }
+
+ private initViewMode(isViewOnly:boolean) {
+
+ if (isViewOnly) {
+ //remove event listeners
+ this._cy.off('drag');
+ this._cy.off('handlemouseout');
+ this._cy.off('handlemouseover');
+ this._cy.edges().unselectify();
+ }
+ };
+
+ private registerCustomEvents(scope:ICompositionGraphScope, el:JQuery) {
+
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_HOVER_IN, (leftPaletteComponent:LeftPaletteComponent) => {
+ this.$log.info(`composition-graph::registerEventServiceEvents:: palette hover on component: ${leftPaletteComponent.uniqueId}`);
+
+ let nodesData = this.NodesGraphUtils.getAllNodesData(this._cy.nodes());
+ let nodesLinks = this.GeneralGraphUtils.getAllCompositionCiLinks(this._cy);
+
+ if (this.GeneralGraphUtils.componentRequirementsAndCapabilitiesCaching.containsKey(leftPaletteComponent.uniqueId)) {
+ let cacheComponent = this.GeneralGraphUtils.componentRequirementsAndCapabilitiesCaching.getValue(leftPaletteComponent.uniqueId);
+ let filteredNodesData = this.matchCapabilitiesRequirementsUtils.findByMatchingCapabilitiesToRequirements(cacheComponent, nodesData, nodesLinks);
+
+ this.matchCapabilitiesRequirementsUtils.highlightMatchingComponents(filteredNodesData, this._cy);
+ this.matchCapabilitiesRequirementsUtils.fadeNonMachingComponents(filteredNodesData, nodesData, this._cy);
+
+ return;
+ }
+
+ //----------------------- ORIT TO FIX------------------------//
+
+ this.ComponentServiceNg2.getCapabilitiesAndRequirements(leftPaletteComponent.componentType, leftPaletteComponent.uniqueId).subscribe((response: ComponentGenericResponse) => {
+
+ let component = this.ComponentFactory.createEmptyComponent(leftPaletteComponent.componentType);
+ component.uniqueId = component.uniqueId;
+ component.capabilities = response.capabilities;
+ component.requirements = response.requirements;
+ this.GeneralGraphUtils.componentRequirementsAndCapabilitiesCaching.setValue(leftPaletteComponent.uniqueId, component);
+ let filteredNodesData = this.matchCapabilitiesRequirementsUtils.findByMatchingCapabilitiesToRequirements(component, nodesData, nodesLinks);
+ this.matchCapabilitiesRequirementsUtils.fadeNonMachingComponents(filteredNodesData, nodesData, this._cy);
+ this.matchCapabilitiesRequirementsUtils.highlightMatchingComponents(filteredNodesData, this._cy)
+ });
+ });
+
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_HOVER_OUT, () => {
+ this._cy.emit('hidehandles');
+ this.matchCapabilitiesRequirementsUtils.resetFadedNodes(this._cy);
+ });
+
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_DRAG_START, (dragElement, dragComponent) => {
+
+ this.dragElement = dragElement;
+ this.dragComponent = this.ComponentInstanceFactory.createComponentInstanceFromComponent(dragComponent);
+ });
+
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_DRAG_ACTION, (event:IDragDropEvent) => {
+ this.CompositionGraphPaletteUtils.onComponentDrag(this._cy, event, this.dragElement, this.dragComponent);
+
+ });
+
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_COMPONENT_INSTANCE_NAME_CHANGED, (component:ComponentInstance) => {
+
+ let selectedNode = this._cy.getElementById(component.uniqueId);
+ selectedNode.data().componentInstance.name = component.name;
+ selectedNode.data('name', component.name); //used for tooltip
+ selectedNode.data('displayName', selectedNode.data().getDisplayName()); //abbreviated
+
+ });
+
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, (componentInstance:ComponentInstance) => {
+ let nodeToDelete = this._cy.getElementById(componentInstance.uniqueId);
+ this.NodesGraphUtils.deleteNode(this._cy, scope.component, nodeToDelete);
+ });
+
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_MULTIPLE_COMPONENTS, () => {
+
+ this._cy.$('node:selected').each((i:number, node:Cy.CollectionNodes) => {
+ this.NodesGraphUtils.deleteNode(this._cy, scope.component, node);
+ });
+
+ });
+
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_EDGE, (releaseLoading:boolean, linksToDelete:Cy.CollectionEdges) => {
+ this.CompositionGraphLinkUtils.deleteLink(this._cy, scope.component, releaseLoading, linksToDelete);
+ });
+
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_INSERT_NODE_TO_UCPE, (node:Cy.CollectionNodes, ucpe:Cy.CollectionNodes, updateExistingNode:boolean) => {
+
+ this.commonGraphUtils.initUcpeChildData(node, ucpe);
+ //check if item is a VL, and if so, skip adding the binding to ucpe
+ if (!(node.data() instanceof CompositionCiNodeVl)) {
+ this.CompositionGraphLinkUtils.createVfToUcpeLink(scope.component, this._cy, ucpe.data(), node.data()); //create link from the node to the ucpe
+ }
+
+ if (updateExistingNode) {
+ let vlsPendingDeletion:Cy.CollectionNodes = this.NodesGraphUtils.deleteNodeVLsUponMoveToOrFromUCPE(scope.component, node.cy(), node); //delete connected VLs that no longer have 2 links
+ this.CompositionGraphLinkUtils.deleteLinksWhenNodeMovedFromOrToUCPE(scope.component, node.cy(), node, vlsPendingDeletion); //delete all connected links if needed
+ this.GeneralGraphUtils.pushUpdateComponentInstanceActionToQueue(scope.component, true, node.data().componentInstance); //update componentInstance position
+ }
+
+ });
+
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_REMOVE_NODE_FROM_UCPE, (node:Cy.CollectionNodes, ucpe:Cy.CollectionNodes) => {
+ this.commonGraphUtils.removeUcpeChildData(node);
+ let vlsPendingDeletion:Cy.CollectionNodes = this.NodesGraphUtils.deleteNodeVLsUponMoveToOrFromUCPE(scope.component, node.cy(), node);
+ this.CompositionGraphLinkUtils.deleteLinksWhenNodeMovedFromOrToUCPE(scope.component, node.cy(), node, vlsPendingDeletion); //delete all connected links if needed
+ this.GeneralGraphUtils.pushUpdateComponentInstanceActionToQueue(scope.component, true, node.data().componentInstance); //update componentInstance position
+ });
+
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_VERSION_CHANGED, (component:Component) => {
+ scope.component = component;
+ this.loadGraphData(scope);
+ });
+
+
+ scope.createLinkFromMenu = (chosenMatch:MatchBase):void => {
+ scope.isLinkMenuOpen = false;
+ this.CompositionGraphLinkUtils.createLinkFromMenu(this._cy, chosenMatch, scope.component);
+ };
+
+ scope.hideRelationMenu = () => {
+ this.commonGraphUtils.safeApply(scope, () => {
+ scope.linkMenuObject = null;
+ this.$timeout.cancel(scope.relationMenuTimeout);
+ });
+ };
+
+
+ scope.deleteRelation = (link:Cy.CollectionEdges) => {
+ scope.hideRelationMenu();
+
+ //if multiple edges selected, delete the VL itself so edges get deleted automatically
+ if (this._cy.$('edge:selected').length > 1) {
+ this.NodesGraphUtils.deleteNode(this._cy, scope.component, this._cy.$('node:selected'));
+ } else {
+ this.CompositionGraphLinkUtils.deleteLink(this._cy, scope.component, true, link);
+ }
+ };
+
+ /*
+ scope.hideAssetPopover = ():void => {
+
+ this.commonGraphUtils.safeApply(scope, () => {
+ scope.assetPopoverOpen = false;
+ scope.assetPopoverObj = null;
+ });
+ };
+
+ scope.deleteNode = (nodeId:string):void => {
+ if (!scope.isViewOnly) {
+ this.NodesGraphUtils.confirmDeleteNode(nodeId, this._cy, scope.component);
+ //scope.hideAssetPopover();
+ }
+ };*/
+ }
+
+ private registerCytoscapeGraphEvents(scope:ICompositionGraphScope) {
+
+ this._cy.on('addedgemouseup', (event, data) => {
+ scope.relationMenuDirectiveObj = this.CompositionGraphLinkUtils.onLinkDrawn(this._cy, data.source, data.target);
+ if (scope.relationMenuDirectiveObj != null) {
+ scope.$apply(() => {
+ scope.isLinkMenuOpen = true;
+ });
+ }
+ });
+ this._cy.on('tapstart', 'node', (event:Cy.EventObject) => {
+ this._currentlyCLickedNodePosition = angular.copy(event.cyTarget[0].position()); //update node position on drag
+ if (event.cyTarget.data().isUcpe) {
+ this._cy.nodes('.ucpe-cp').unlock();
+ event.cyTarget.style('opacity', 0.5);
+ }
+ //scope.hideAssetPopover();
+ });
+
+ this._cy.on('drag', 'node', (event:Cy.EventObject) => {
+
+ if (event.cyTarget.data().isDraggable) {
+ event.cyTarget.style({'overlay-opacity': 0.24});
+ if (this.GeneralGraphUtils.isValidDrop(this._cy, event.cyTarget)) {
+ event.cyTarget.style({'overlay-color': GraphColors.NODE_BACKGROUND_COLOR});
+ } else {
+ event.cyTarget.style({'overlay-color': GraphColors.NODE_OVERLAPPING_BACKGROUND_COLOR});
+ }
+ }
+
+ if (event.cyTarget.data().isUcpe) {
+ let pos = event.cyTarget.position();
+
+ this._cy.nodes('[?isInsideGroup]').positions((i, node)=> {
+ return {
+ x: pos.x + node.data("ucpeOffset").x,
+ y: pos.y + node.data("ucpeOffset").y
+ }
+ });
+ }
+ });
+
+ /* this._cy.on('mouseover', 'node', (event:Cy.EventObject) => {
+ if (!this._cy.scratch('_edge_editation_highlights')) {
+ this.commonGraphUtils.safeApply(scope, () => {
+ this.showNodePopoverMenu(scope, event.cyTarget[0]);
+ });
+ }
+ });
+
+ this._cy.on('mouseout', 'node', (event:Cy.EventObject) => {
+ scope.hideAssetPopover();
+ });*/
+ this._cy.on('handlemouseover', (event, payload) => {
+
+ if (payload.node.grabbed() /* || this._cy.scratch('_edge_editation_highlights') === true*/) { //no need to add opacity while we are dragging and hovering othe nodes- or if opacity was already calculated for these nodes
+ return;
+ }
+ let nodesData = this.NodesGraphUtils.getAllNodesData(this._cy.nodes());
+ let nodesLinks = this.GeneralGraphUtils.getAllCompositionCiLinks(this._cy);
+
+ let linkableNodes = this.commonGraphUtils.getLinkableNodes(this._cy, payload.node);
+ let filteredNodesData = this.matchCapabilitiesRequirementsUtils.findByMatchingCapabilitiesToRequirements(payload.node.data().componentInstance, linkableNodes, nodesLinks);
+ this.matchCapabilitiesRequirementsUtils.highlightMatchingComponents(filteredNodesData, this._cy);
+ this.matchCapabilitiesRequirementsUtils.fadeNonMachingComponents(filteredNodesData, nodesData, this._cy, payload.node.data());
+ /*
+ this._cy.scratch()._edge_editation_highlights = true;
+ scope.hideAssetPopover();*/
+ });
+
+ this._cy.on('handlemouseout', () => {
+ if (this._cy.scratch('_edge_editation_highlights') === true) {
+ this._cy.removeScratch('_edge_editation_highlights');
+ this._cy.emit('hidehandles');
+ this.matchCapabilitiesRequirementsUtils.resetFadedNodes(this._cy);
+ }
+ });
+
+
+ this._cy.on('tapend', (event:Cy.EventObject) => {
+
+ if (event.cyTarget === this._cy) { //On Background clicked
+ if (this._cy.$('node:selected').length === 0) { //if the background click but not dragged
+ this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED);
+ }
+ scope.hideRelationMenu();
+ }
+
+ else if (event.cyTarget.isEdge()) { //On Edge clicked
+ if (scope.isViewOnly) return;
+ this.CompositionGraphLinkUtils.handleLinkClick(this._cy, event);
+ this.openModifyLinkMenu(scope, this.CompositionGraphLinkUtils.getModifyLinkMenu(event.cyTarget[0], event), 6000);
+ }
+
+ else { //On Node clicked
+ this._cy.nodes(':grabbed').style({'overlay-opacity': 0});
+
+ let isUcpe:boolean = event.cyTarget.data().isUcpe;
+ let newPosition = event.cyTarget[0].position();
+ //node position changed (drop after drag event) - we need to update position
+ if (this._currentlyCLickedNodePosition.x !== newPosition.x || this._currentlyCLickedNodePosition.y !== newPosition.y) {
+ let nodesMoved:Cy.CollectionNodes = this._cy.$(':grabbed');
+ if (isUcpe) {
+ nodesMoved = nodesMoved.add(this._cy.nodes('[?isInsideGroup]:free')); //'child' nodes will not be recognized as "grabbed" elements within cytoscape. manually add them to collection of nodes moved.
+ }
+ this.NodesGraphUtils.onNodesPositionChanged(this._cy, scope.component, nodesMoved);
+ } else {
+ this.$log.debug('composition-graph::onNodeSelectedEvent:: fired');
+ scope.$apply(() => {
+ this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_NODE_SELECTED, event.cyTarget.data().componentInstance);
+ //open node popover menu
+ //this.showNodePopoverMenu(scope, event.cyTarget[0]);
+ });
+ }
+
+ if (isUcpe) {
+ this._cy.nodes('.ucpe-cp').lock();
+ event.cyTarget.style('opacity', 1);
+ }
+
+ }
+ });
+
+ this._cy.on('boxselect', 'node', (event:Cy.EventObject) => {
+ this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_NODE_SELECTED, event.cyTarget.data().componentInstance);
+ });
+ }
+
+ /*
+ private showNodePopoverMenu = (scope:ICompositionGraphScope, node:Cy.CollectionNodes) => {
+
+ scope.assetPopoverObj = this.NodesGraphUtils.createAssetPopover(this._cy, node, scope.isViewOnly);
+ scope.assetPopoverOpen = true;
+
+ };*/
+ private openModifyLinkMenu = (scope:ICompositionGraphScope, linkMenuObject:LinkMenu, timeOutInMilliseconds?:number) => {
+
+ this.commonGraphUtils.safeApply(scope, () => {
+ scope.linkMenuObject = linkMenuObject;
+ });
+
+ scope.relationMenuTimeout = this.$timeout(() => {
+ scope.hideRelationMenu();
+ }, timeOutInMilliseconds ? timeOutInMilliseconds : 6000);
+ };
+
+ private initGraphNodes(componentInstances:ComponentInstance[], isViewOnly:boolean) {
+
+ if (!isViewOnly) { //Init nodes handle extension - enable dynamic links
+ setTimeout(()=> {
+ let handles = new CytoscapeEdgeEditation;
+ handles.init(this._cy, 18);
+ handles.registerHandle(ComponentInstanceNodesStyle.getBasicNodeHanlde());
+ handles.registerHandle(ComponentInstanceNodesStyle.getBasicSmallNodeHandle());
+ handles.registerHandle(ComponentInstanceNodesStyle.getUcpeCpNodeHandle());
+ }, 0);
+ }
+
+ _.each(componentInstances, (instance) => {
+ let compositionGraphNode:CompositionCiNodeBase = this.NodesFactory.createNode(instance);
+ this.commonGraphUtils.addComponentInstanceNodeToGraph(this._cy, compositionGraphNode);
+ });
+ }
+
+
+ private initDropZone(scope:ICompositionGraphScope) {
+
+ if (scope.isViewOnly) {
+ return;
+ }
+ scope.dropCallback = (event:IDragDropEvent) => {
+ this.$log.debug(`composition-graph::dropCallback:: fired`);
+ this.CompositionGraphPaletteUtils.addNodeFromPalette(this._cy, event, scope.component);
+ };
+
+ scope.verifyDrop = (event:JQueryEventObject) => {
+
+ if (this.dragElement.hasClass('red')) {
+ return false;
+ }
+ return true;
+ };
+
+ scope.beforeDropCallback = (event:IDragDropEvent):ng.IPromise<void> => {
+ let deferred:ng.IDeferred<void> = this.$q.defer<void>();
+ if (this.dragElement.hasClass('red')) {
+ deferred.reject();
+ } else {
+ deferred.resolve();
+ }
+
+ return deferred.promise;
+ }
+ }
+
+ public static factory = ($q,
+ $log,
+ $timeout,
+ NodesFactory,
+ LinksGraphUtils,
+ GeneralGraphUtils,
+ ComponentInstanceFactory,
+ NodesGraphUtils,
+ EventListenerService,
+ ComponentFactory,
+ LoaderService,
+ CommonGraphUtils,
+ MatchCapabilitiesRequirementsUtils,
+ CompositionGraphPaletteUtils,
+ ComponentServiceNg2) => {
+ return new CompositionGraph(
+ $q,
+ $log,
+ $timeout,
+ NodesFactory,
+ LinksGraphUtils,
+ GeneralGraphUtils,
+ ComponentInstanceFactory,
+ NodesGraphUtils,
+ EventListenerService,
+ ComponentFactory,
+ LoaderService,
+ CommonGraphUtils,
+ MatchCapabilitiesRequirementsUtils,
+ CompositionGraphPaletteUtils,
+ ComponentServiceNg2);
+ }
+}
+
+CompositionGraph.factory.$inject = [
+ '$q',
+ '$log',
+ '$timeout',
+ 'NodesFactory',
+ 'CompositionGraphLinkUtils',
+ 'CompositionGraphGeneralUtils',
+ 'ComponentInstanceFactory',
+ 'CompositionGraphNodesUtils',
+ 'EventListenerService',
+ 'ComponentFactory',
+ 'LoaderService',
+ 'CommonGraphUtils',
+ 'MatchCapabilitiesRequirementsUtils',
+ 'CompositionGraphPaletteUtils',
+ 'ComponentServiceNg2'
+];
diff --git a/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.html b/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.html
new file mode 100644
index 0000000000..1e69d3384a
--- /dev/null
+++ b/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.html
@@ -0,0 +1,23 @@
+<loader display="isLoading" loader-type="composition-graph"></loader>
+<div class="sdc-composition-graph-wrapper" ng-class="{'view-only':isViewOnly}"
+ data-drop="true"
+ data-jqyoui-options="{accept: verifyDrop}"
+ data-jqyoui-droppable="{onDrop:'dropCallback', beforeDrop: 'beforeDropCallback'}">
+</div>
+
+<relation-menu relation-menu-directive-obj="relationMenuDirectiveObj" is-link-menu-open="isLinkMenuOpen"
+ create-relation="createLinkFromMenu" cancel="cancelRelationMenu()"></relation-menu>
+
+
+<div class="w-sdc-canvas-menu"
+ data-ng-show="linkMenuObject" ng-style="{left: linkMenuObject.position.x, top: linkMenuObject.position.y}"
+ id="relationMenu">
+
+ <div class="w-sdc-canvas-menu-content hand" data-ng-click="deleteRelation(linkMenuObject.link)">
+ <div class="w-sdc-canvas-menu-content-delete-button"></div>
+ Delete
+ </div>
+
+</div>
+
+<!--<asset-popover ng-if="assetPopoverOpen" asset-popover-obj="assetPopoverObj" delete-asset="deleteNode(assetPopoverObj.nodeId)"></asset-popover>-->
diff --git a/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.less b/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.less
new file mode 100644
index 0000000000..56c8b5529d
--- /dev/null
+++ b/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.less
@@ -0,0 +1,14 @@
+composition-graph {
+ display: block;
+ height:100%;
+ width: 100%;
+
+ .sdc-composition-graph-wrapper{
+ height:100%;
+ width: 100%;
+ }
+
+ .view-only{
+ background-color:rgb(248, 248, 248);
+ }
+}
diff --git a/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-general-utils.ts b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-general-utils.ts
new file mode 100644
index 0000000000..1303e7a894
--- /dev/null
+++ b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-general-utils.ts
@@ -0,0 +1,244 @@
+import {ComponentInstance, Component, MatchReqToCapability, MatchBase, CompositionCiLinkBase, CompositionCiNodeUcpeCp} from "app/models";
+import {QueueUtils, Dictionary, GraphUIObjects} from "app/utils";
+import {LoaderService} from "app/services";
+import {MatchCapabilitiesRequirementsUtils} from "./match-capability-requierment-utils";
+import {CommonGraphUtils} from "../../common/common-graph-utils";
+
+
+export class CompositionGraphGeneralUtils {
+
+ public componentRequirementsAndCapabilitiesCaching = new Dictionary<string, Component>();
+ protected static graphUtilsUpdateQueue:QueueUtils;
+
+ constructor(private $q:ng.IQService,
+ private LoaderService:LoaderService,
+ private commonGraphUtils:CommonGraphUtils,
+ private matchCapabilitiesRequirementsUtils:MatchCapabilitiesRequirementsUtils) {
+ CompositionGraphGeneralUtils.graphUtilsUpdateQueue = new QueueUtils(this.$q);
+ }
+
+
+ /**
+ * Get the offset for the link creation Menu
+ * @param point
+ * @returns {Cy.Position}
+ */
+ public calcMenuOffset:Function = (point:Cy.Position):Cy.Position => {
+ point.x = point.x + 60;
+ point.y = point.y + 105;
+ return point;
+ };
+
+ /**
+ * return the top left position of the link menu
+ * @param cy
+ * @param targetNodePosition
+ * @returns {Cy.Position}
+ */
+ public getLinkMenuPosition = (cy:Cy.Instance, targetNodePosition:Cy.Position) => {
+ let menuPosition:Cy.Position = this.calcMenuOffset(targetNodePosition); //get the link mid point
+ if ($(document.body).height() < menuPosition.y + GraphUIObjects.LINK_MENU_HEIGHT + $(document.getElementsByClassName('sdc-composition-graph-wrapper')).offset().top) { // if position menu is overflow bottom
+ menuPosition.y = $(document.body).height() - GraphUIObjects.TOP_HEADER_HEIGHT - GraphUIObjects.LINK_MENU_HEIGHT;
+ }
+ return menuPosition;
+ };
+
+
+ /**
+ * will return true/false if two nodes overlapping
+ *
+ * @param graph node
+ */
+ private isNodesOverlapping(node:Cy.CollectionFirstNode, draggedNode:Cy.CollectionFirstNode):boolean {
+
+ let nodeBoundingBox:Cy.BoundingBox = node.renderedBoundingBox();
+ let secondNodeBoundingBox:Cy.BoundingBox = draggedNode.renderedBoundingBox();
+
+ return this.isBBoxOverlapping(nodeBoundingBox, secondNodeBoundingBox);
+ }
+
+ /**
+ * Checks whether the bounding boxes of two nodes are overlapping on any side
+ * @param nodeOneBBox
+ * @param nodeTwoBBox
+ * @returns {boolean}
+ */
+ private isBBoxOverlapping(nodeOneBBox:Cy.BoundingBox, nodeTwoBBox:Cy.BoundingBox) {
+ return (((nodeOneBBox.x1 < nodeTwoBBox.x1 && nodeOneBBox.x2 > nodeTwoBBox.x1) ||
+ (nodeOneBBox.x1 < nodeTwoBBox.x2 && nodeOneBBox.x2 > nodeTwoBBox.x2) ||
+ (nodeTwoBBox.x1 < nodeOneBBox.x1 && nodeTwoBBox.x2 > nodeOneBBox.x2)) &&
+ ((nodeOneBBox.y1 < nodeTwoBBox.y1 && nodeOneBBox.y2 > nodeTwoBBox.y1) ||
+ (nodeOneBBox.y1 < nodeTwoBBox.y2 && nodeOneBBox.y2 > nodeTwoBBox.y2) ||
+ (nodeTwoBBox.y1 < nodeOneBBox.y1 && nodeTwoBBox.y2 > nodeOneBBox.y2)))
+ }
+
+
+ /**
+ * Checks whether a specific component instance can be hosted on the UCPE instance
+ * @param cy - Cytoscape instance
+ * @param fromUcpeInstance
+ * @param toComponentInstance
+ * @returns {MatchReqToCapability}
+ */
+ public canBeHostedOn(cy:Cy.Instance, fromUcpeInstance:ComponentInstance, toComponentInstance:ComponentInstance):MatchReqToCapability {
+
+ let matches:Array<MatchBase> = this.matchCapabilitiesRequirementsUtils.getMatchedRequirementsCapabilities(fromUcpeInstance, toComponentInstance, this.getAllCompositionCiLinks(cy));
+ let hostedOnMatch:MatchBase = _.find(matches, (match:MatchReqToCapability) => {
+ return match.requirement.capability.toLowerCase() === 'tosca.capabilities.container';
+ });
+
+ return <MatchReqToCapability>hostedOnMatch;
+ };
+
+
+ /**
+ * Checks whether node can be dropped into UCPE
+ * @param cy
+ * @param nodeToInsert
+ * @param ucpeNode
+ * @returns {boolean}
+ */
+ private isValidDropInsideUCPE(cy:Cy.Instance, nodeToInsert:ComponentInstance, ucpeNode:ComponentInstance):boolean {
+
+ let hostedOnMatch:MatchReqToCapability = this.canBeHostedOn(cy, ucpeNode, nodeToInsert);
+ let result:boolean = !angular.isUndefined(hostedOnMatch) || nodeToInsert.isVl(); //group validation
+ return result;
+
+ };
+
+
+ /**
+ * For drops from palette, checks whether the node can be dropped. If node is being held over another node, check if capable of hosting
+ * @param cy
+ * @param pseudoNodeBBox
+ * @param paletteComponentInstance
+ * @returns {boolean}
+ */
+ public isPaletteDropValid(cy:Cy.Instance, pseudoNodeBBox:Cy.BoundingBox, paletteComponentInstance:ComponentInstance) {
+
+ let componentIsUCPE:boolean = (paletteComponentInstance.capabilities && paletteComponentInstance.capabilities['tosca.capabilities.Container'] && paletteComponentInstance.name.toLowerCase().indexOf('ucpe') > -1);
+
+ if (componentIsUCPE && cy.nodes('[?isUcpe]').length > 0) { //second UCPE not allowed
+ return false;
+ }
+
+ let illegalOverlappingNodes = _.filter(cy.nodes("[isSdcElement]"), (graphNode:Cy.CollectionFirstNode) => {
+
+ if (this.isBBoxOverlapping(pseudoNodeBBox, graphNode.renderedBoundingBox())) {
+ if (!componentIsUCPE && graphNode.data().isUcpe) {
+ return !this.isValidDropInsideUCPE(cy, paletteComponentInstance, graphNode.data().componentInstance); //if this is valid insert into ucpe, we return false - no illegal overlapping nodes
+ }
+ return true;
+ }
+
+ return false;
+ });
+
+ return illegalOverlappingNodes.length === 0;
+ }
+
+ /**
+ * will return true/false if a drop of a single node is valid
+ *
+ * @param graph node
+ */
+ public isValidDrop(cy:Cy.Instance, draggedNode:Cy.CollectionFirstNode):boolean {
+
+ let illegalOverlappingNodes = _.filter(cy.nodes("[isSdcElement]"), (graphNode:Cy.CollectionFirstNode) => { //all sdc nodes, removing child nodes (childe node allways collaps
+
+ if (draggedNode.data().isUcpe && (graphNode.isChild() || graphNode.data().isInsideGroup)) { //ucpe cps always inside ucpe, no overlapping
+ return false;
+ }
+ if (draggedNode.data().isInsideGroup && (!draggedNode.active() || graphNode.data().isUcpe)) {
+ return false;
+ }
+
+ if (!draggedNode.data().isUcpe && !(draggedNode.data() instanceof CompositionCiNodeUcpeCp) && graphNode.data().isUcpe) { //case we are dragging a node into UCPE
+ let isEntirelyInUCPE:boolean = this.commonGraphUtils.isFirstBoxContainsInSecondBox(draggedNode.renderedBoundingBox(), graphNode.renderedBoundingBox());
+ if (isEntirelyInUCPE) {
+ if (this.isValidDropInsideUCPE(cy, draggedNode.data().componentInstance, graphNode.data().componentInstance)) { //if this is valid insert into ucpe, we return false - no illegal overlapping nodes
+ return false;
+ }
+ }
+ }
+ return graphNode.data().id !== draggedNode.data().id && this.isNodesOverlapping(draggedNode, graphNode);
+
+ });
+ // return false;
+ return illegalOverlappingNodes.length === 0;
+ };
+
+ /**
+ * will return true/false if the move of the nodes is valid (no node overlapping and verifying if insert into UCPE is valid)
+ *
+ * @param nodesArray - the selected drags nodes
+ */
+ public isGroupValidDrop(cy:Cy.Instance, nodesArray:Cy.CollectionNodes):boolean {
+ let filterDraggedNodes = nodesArray.filter('[?isDraggable]');
+ let isValidDrop = _.every(filterDraggedNodes, (node:Cy.CollectionFirstNode) => {
+ return this.isValidDrop(cy, node);
+
+ });
+ return isValidDrop;
+ };
+
+ /**
+ * get all links in diagram
+ * @param cy
+ * @returns {any[]|boolean[]}
+ */
+ public getAllCompositionCiLinks = (cy:Cy.Instance):Array<CompositionCiLinkBase> => {
+ return _.map(cy.edges("[isSdcElement]"), (edge:Cy.CollectionEdges) => {
+ return edge.data();
+ });
+ };
+
+
+ /**
+ * Get Graph Utils server queue
+ * @returns {QueueUtils}
+ */
+ public getGraphUtilsServerUpdateQueue():QueueUtils {
+ return CompositionGraphGeneralUtils.graphUtilsUpdateQueue;
+ }
+ ;
+
+ /**
+ *
+ * @param blockAction - true/false if this is a block action
+ * @param instances
+ * @param component
+ */
+ public pushMultipleUpdateComponentInstancesRequestToQueue = (blockAction:boolean, instances:Array<ComponentInstance>, component:Component):void => {
+ if (blockAction) {
+ this.getGraphUtilsServerUpdateQueue().addBlockingUIAction(
+ () => component.updateMultipleComponentInstances(instances)
+ );
+ } else {
+ this.getGraphUtilsServerUpdateQueue().addNonBlockingUIAction(
+ () => component.updateMultipleComponentInstances(instances),
+ () => this.LoaderService.hideLoader('composition-graph'));
+ }
+ };
+
+ /**
+ * this function will update component instance data
+ * @param blockAction - true/false if this is a block action
+ * @param updatedInstance
+ */
+ public pushUpdateComponentInstanceActionToQueue = (component:Component, blockAction:boolean, updatedInstance:ComponentInstance):void => {
+
+ if (blockAction) {
+ this.LoaderService.showLoader('composition-graph');
+ this.getGraphUtilsServerUpdateQueue().addBlockingUIAction(
+ () => component.updateComponentInstance(updatedInstance)
+ );
+ } else {
+ this.getGraphUtilsServerUpdateQueue().addNonBlockingUIAction(
+ () => component.updateComponentInstance(updatedInstance),
+ () => this.LoaderService.hideLoader('composition-graph'));
+ }
+ };
+}
+
+CompositionGraphGeneralUtils.$inject = ['$q', 'LoaderService', 'CommonGraphUtils', 'MatchCapabilitiesRequirementsUtils'];
diff --git a/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-links-utils.ts b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-links-utils.ts
new file mode 100644
index 0000000000..314c761edd
--- /dev/null
+++ b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-links-utils.ts
@@ -0,0 +1,271 @@
+/**
+ * Created by obarda on 6/28/2016.
+ */
+import {GraphUIObjects, ComponentInstanceFactory, ResourceType} from "app/utils";
+import {LeftPaletteLoaderService, LoaderService} from "app/services";
+import {
+ NodeUcpe,
+ CompositionCiNodeVf,
+ MatchReqToCapability,
+ MatchBase,
+ MatchReqToReq,
+ ComponentInstance,
+ CompositionCiNodeBase,
+ RelationshipModel,
+ RelationMenuDirectiveObj,
+ CapabilitiesGroup,
+ LinksFactory,
+ NodesFactory,
+ RequirementsGroup,
+ Component,
+ Relationship,
+ Capability,
+ LinkMenu,
+ Point,
+ CompositionCiLinkBase
+} from "app/models";
+import {CommonGraphUtils} from "../../common/common-graph-utils";
+import {CompositionGraphGeneralUtils} from "./composition-graph-general-utils";
+import {MatchCapabilitiesRequirementsUtils} from "./match-capability-requierment-utils";
+
+export class CompositionGraphLinkUtils {
+
+ constructor(private linksFactory:LinksFactory,
+ private loaderService:LoaderService,
+ private generalGraphUtils:CompositionGraphGeneralUtils,
+ private commonGraphUtils:CommonGraphUtils,
+ private matchCapabilitiesRequirementsUtils:MatchCapabilitiesRequirementsUtils) {
+ }
+
+
+ /**
+ * Delete the link on server and then remove it from graph
+ * @param component
+ * @param releaseLoading - true/false release the loader when finished
+ * @param link - the link to delete
+ */
+ public deleteLink = (cy:Cy.Instance, component:Component, releaseLoading:boolean, link:Cy.CollectionEdges) => {
+
+ this.loaderService.showLoader('composition-graph');
+ let onSuccessDeleteRelation = (response) => {
+ cy.remove(link);
+ };
+
+ if (!releaseLoading) {
+ this.generalGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIAction(
+ () => component.deleteRelation(link.data().relation).then(onSuccessDeleteRelation)
+ );
+ } else {
+ this.generalGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIActionWithReleaseCallback(
+ () => component.deleteRelation(link.data().relation).then(onSuccessDeleteRelation),
+ () => this.loaderService.hideLoader('composition-graph'));
+ }
+ };
+
+ /**
+ * create the link on server and than draw it on graph
+ * @param link - the link to create
+ * @param cy
+ * @param component
+ */
+ public createLink = (link:CompositionCiLinkBase, cy:Cy.Instance, component:Component):void => {
+
+ this.loaderService.showLoader('composition-graph');
+
+ let onSuccess:(response:RelationshipModel) => void = (relation:RelationshipModel) => {
+ link.setRelation(relation);
+ this.commonGraphUtils.insertLinkToGraph(cy, link);
+ };
+
+ link.updateLinkDirection();
+
+ this.generalGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIActionWithReleaseCallback(
+ () => component.createRelation(link.relation).then(onSuccess),
+ () => this.loaderService.hideLoader('composition-graph')
+ );
+ };
+
+ private createSimpleLink = (match:MatchReqToCapability, cy:Cy.Instance, component:Component):void => {
+ let newRelation:RelationshipModel = match.matchToRelationModel();
+ let linkObg:CompositionCiLinkBase = this.linksFactory.createGraphLink(cy, newRelation, newRelation.relationships[0]);
+ this.createLink(linkObg, cy, component);
+ };
+
+ public createLinkFromMenu = (cy:Cy.Instance, chosenMatch:MatchBase, component:Component):void => {
+
+ if (chosenMatch) {
+ if (chosenMatch && chosenMatch instanceof MatchReqToCapability) {
+ this.createSimpleLink(chosenMatch, cy, component);
+ }
+ }
+ };
+
+
+ /**
+ * Filters the matches for UCPE links so that shown requirements and capabilites are only related to the selected ucpe-cp
+ * @param fromNode
+ * @param toNode
+ * @param matchesArray
+ * @returns {Array<MatchBase>}
+ */
+ public filterUcpeLinks(fromNode:CompositionCiNodeBase, toNode:CompositionCiNodeBase, matchesArray:Array<MatchBase>):any {
+
+ let matchLink:Array<MatchBase>;
+
+ if (fromNode.isUcpePart) {
+ matchLink = _.filter(matchesArray, (match:MatchBase) => {
+ return match.isOwner(fromNode.id);
+ });
+ }
+
+ if (toNode.isUcpePart) {
+ matchLink = _.filter(matchesArray, (match:MatchBase) => {
+ return match.isOwner(toNode.id);
+ });
+ }
+ return matchLink ? matchLink : matchesArray;
+ }
+
+
+ /**
+ * open the connect link menu if the link drawn is valid - match requirements & capabilities
+ * @param cy
+ * @param fromNode
+ * @param toNode
+ * @returns {any}
+ */
+ public onLinkDrawn(cy:Cy.Instance, fromNode:Cy.CollectionFirstNode, toNode:Cy.CollectionFirstNode):RelationMenuDirectiveObj {
+
+ if (!this.commonGraphUtils.nodeLocationsCompatible(cy, fromNode, toNode)) {
+ return null;
+ }
+ let linkModel:Array<CompositionCiLinkBase> = this.generalGraphUtils.getAllCompositionCiLinks(cy);
+
+ let possibleRelations:Array<MatchBase> = this.matchCapabilitiesRequirementsUtils.getMatchedRequirementsCapabilities(fromNode.data().componentInstance,
+ toNode.data().componentInstance, linkModel);
+
+ //filter relations found to limit to specific ucpe-cp
+ possibleRelations = this.filterUcpeLinks(fromNode.data(), toNode.data(), possibleRelations);
+
+ //if found possibleRelations between the nodes we create relation menu directive and open the link menu
+ if (possibleRelations.length) {
+ let menuPosition = this.generalGraphUtils.getLinkMenuPosition(cy, toNode.renderedPoint());
+ return new RelationMenuDirectiveObj(fromNode.data(), toNode.data(), menuPosition, possibleRelations);
+ }
+ return null;
+ };
+
+
+ /**
+ * when we drag instance in to UCPE or out of UCPE - get all links we need to delete - one node in ucpe and one node outside of ucpe
+ * @param node - the node we dragged into or out of the ucpe
+ */
+ public deleteLinksWhenNodeMovedFromOrToUCPE(component:Component, cy:Cy.Instance, nodeMoved:Cy.CollectionNodes, vlsPendingDeletion?:Cy.CollectionNodes):void {
+
+
+ let linksToDelete:Cy.CollectionElements = cy.collection();
+ _.forEach(nodeMoved.neighborhood('node'), (neighborNode)=> {
+
+ if (neighborNode.data().isUcpePart) { //existing connections to ucpe or ucpe-cp - we want to delete even though nodeLocationsCompatible will technically return true
+ linksToDelete = linksToDelete.add(nodeMoved.edgesWith(neighborNode)); // This will delete the ucpe-host-link, or the vl-ucpe-link if nodeMoved is vl
+ } else if (!this.commonGraphUtils.nodeLocationsCompatible(cy, nodeMoved, neighborNode)) { //connection to regular node or vl - check if locations are compatible
+ if (!vlsPendingDeletion || !vlsPendingDeletion.intersect(neighborNode).length) { //Check if this is a link to a VL pending deletion, to prevent double deletion of between the node moved and vl
+ linksToDelete = linksToDelete.add(nodeMoved.edgesWith(neighborNode));
+ }
+ }
+ });
+
+ linksToDelete.each((i, link)=> {
+ this.deleteLink(cy, component, false, link);
+ });
+
+ };
+
+ /**
+ * Creates a hostedOn link between a VF and UCPE
+ * @param component
+ * @param cy
+ * @param ucpeNode
+ * @param vfNode
+ */
+ public createVfToUcpeLink = (component:Component, cy:Cy.Instance, ucpeNode:NodeUcpe, vfNode:CompositionCiNodeVf):void => {
+ let hostedOnMatch:MatchReqToCapability = this.generalGraphUtils.canBeHostedOn(cy, ucpeNode.componentInstance, vfNode.componentInstance);
+ /* create relation */
+ let newRelation = new RelationshipModel();
+ newRelation.fromNode = ucpeNode.id;
+ newRelation.toNode = vfNode.id;
+
+ let link:CompositionCiLinkBase = this.linksFactory.createUcpeHostLink(newRelation);
+ link.relation = hostedOnMatch.matchToRelationModel();
+ this.createLink(link, cy, component);
+ };
+
+
+ /**
+ * Handles click event on links.
+ * If one edge selected: do nothing.
+ /*Two edges selected - always select all
+ /* Three or more edges: first click - select all, secondary click - select single.
+ * @param cy
+ * @param event
+ */
+ public handleLinkClick(cy:Cy.Instance, event:Cy.EventObject) {
+ if (cy.$('edge:selected').length > 2 && event.cyTarget[0].selected()) {
+ cy.$(':selected').unselect();
+ } else {
+
+ let vl:Cy.CollectionNodes = event.cyTarget[0].target('.vl-node');
+ let connectedEdges:Cy.CollectionEdges = vl.connectedEdges();
+ if (vl.length && connectedEdges.length > 1) {
+
+ setTimeout(() => {
+ vl.select();
+ connectedEdges.select();
+ }, 0);
+ }
+ }
+
+ }
+
+
+ /**
+ * Calculates the position for the menu that modifies an existing link
+ * @param event
+ * @param elementWidth
+ * @param elementHeight
+ * @returns {Point}
+ */
+ public calculateLinkMenuPosition(event, elementWidth, elementHeight):Point {
+ let point:Point = new Point(event.originalEvent.x, event.originalEvent.y);
+ if (event.originalEvent.view.screen.height - elementHeight < point.y) {
+ point.y = event.originalEvent.view.screen.height - elementHeight;
+ }
+ if (event.originalEvent.view.screen.width - elementWidth < point.x) {
+ point.x = event.originalEvent.view.screen.width - elementWidth;
+ }
+ return point;
+ };
+
+
+ /**
+ * Gets the menu that is displayed when you click an existing link.
+ * @param link
+ * @param event
+ * @returns {LinkMenu}
+ */
+ public getModifyLinkMenu(link:Cy.CollectionFirstEdge, event:Cy.EventObject):LinkMenu {
+ let point:Point = this.calculateLinkMenuPosition(event, GraphUIObjects.MENU_LINK_VL_WIDTH_OFFSET, GraphUIObjects.MENU_LINK_VL_HEIGHT_OFFSET);
+ let menu:LinkMenu = new LinkMenu(point, true, link);
+ return menu;
+ };
+
+}
+
+
+CompositionGraphLinkUtils.$inject = [
+ 'LinksFactory',
+ 'LoaderService',
+ 'CompositionGraphGeneralUtils',
+ 'CommonGraphUtils',
+ 'MatchCapabilitiesRequirementsUtils'
+];
diff --git a/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-nodes-utils.ts b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-nodes-utils.ts
new file mode 100644
index 0000000000..96afc8a4ea
--- /dev/null
+++ b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-nodes-utils.ts
@@ -0,0 +1,266 @@
+import {Component, NodesFactory, ComponentInstance, CompositionCiNodeVl,IAppMenu,AssetPopoverObj} from "app/models";
+import {EventListenerService, LoaderService} from "app/services";
+import {GRAPH_EVENTS,ModalsHandler,GraphUIObjects} from "app/utils";
+import {CompositionGraphGeneralUtils} from "./composition-graph-general-utils";
+import {CommonGraphUtils} from "../../common/common-graph-utils";
+/**
+ * Created by obarda on 11/9/2016.
+ */
+export class CompositionGraphNodesUtils {
+ constructor(private NodesFactory:NodesFactory, private $log:ng.ILogService,
+ private GeneralGraphUtils:CompositionGraphGeneralUtils,
+ private commonGraphUtils:CommonGraphUtils,
+ private eventListenerService:EventListenerService,
+ private loaderService:LoaderService /*,
+ private sdcMenu: IAppMenu,
+ private ModalsHandler: ModalsHandler*/) {
+
+ }
+
+ /**
+ * Returns component instances for all nodes passed in
+ * @param nodes - Cy nodes
+ * @returns {any[]}
+ */
+ public getAllNodesData(nodes:Cy.CollectionNodes) {
+ return _.map(nodes, (node:Cy.CollectionFirstNode)=> {
+ return node.data();
+ })
+ };
+
+ /**
+ * Deletes component instances on server and then removes it from the graph as well
+ * @param cy
+ * @param component
+ * @param nodeToDelete
+ */
+ public deleteNode(cy:Cy.Instance, component:Component, nodeToDelete:Cy.CollectionNodes):void {
+
+ this.loaderService.showLoader('composition-graph');
+ let onSuccess:(response:ComponentInstance) => void = (response:ComponentInstance) => {
+ console.info('onSuccess', response);
+
+ //if node to delete is a UCPE, remove all children (except UCPE-CPs) and remove their "hostedOn" links
+ if (nodeToDelete.data().isUcpe) {
+ _.each(cy.nodes('[?isInsideGroup]'), (node)=> {
+ this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_REMOVE_NODE_FROM_UCPE, node, nodeToDelete);
+ });
+ }
+
+ //check whether the node is connected to any VLs that only have one other connection. If so, delete that VL as well
+ if (!(nodeToDelete.data() instanceof CompositionCiNodeVl)) {
+ let connectedVls:Array<Cy.CollectionFirstNode> = this.getConnectedVlToNode(nodeToDelete);
+ this.handleConnectedVlsToDelete(connectedVls);
+ }
+
+ //update UI
+ cy.remove(nodeToDelete);
+
+ };
+
+ let onFailed:(response:any) => void = (response:any) => {
+ console.info('onFailed', response);
+ };
+
+
+ this.GeneralGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIActionWithReleaseCallback(
+ () => component.deleteComponentInstance(nodeToDelete.data().componentInstance.uniqueId).then(onSuccess, onFailed),
+ () => this.loaderService.hideLoader('composition-graph')
+ );
+
+ };
+
+/*
+ public confirmDeleteNode = (nodeId:string, cy:Cy.Instance, component:Component) => {
+ let node:Cy.CollectionNodes = cy.getElementById(nodeId);
+ let onOk = ():void => {
+ this.deleteNode(cy, component, node);
+ };
+
+ let componentInstance:ComponentInstance = node.data().componentInstance;
+ let state = "deleteInstance";
+ let title:string = this.sdcMenu.alertMessages[state].title;
+ let message:string = this.sdcMenu.alertMessages[state].message.format([componentInstance.name]);
+
+ this.ModalsHandler.openAlertModal(title, message).then(onOk);
+ };*/
+ /**
+ * Finds all VLs connected to a single node
+ * @param node
+ * @returns {Array<Cy.CollectionFirstNode>}
+ */
+ public getConnectedVlToNode = (node:Cy.CollectionNodes):Array<Cy.CollectionFirstNode> => {
+ let connectedVls:Array<Cy.CollectionFirstNode> = new Array<Cy.CollectionFirstNode>();
+ _.forEach(node.connectedEdges().connectedNodes(), (node:Cy.CollectionFirstNode) => {
+ if (node.data() instanceof CompositionCiNodeVl) {
+ connectedVls.push(node);
+ }
+ });
+ return connectedVls;
+ };
+
+
+ /**
+ * Delete all VLs that have only two connected nodes (this function is called when deleting a node)
+ * @param connectedVls
+ */
+ public handleConnectedVlsToDelete = (connectedVls:Array<Cy.CollectionFirstNode>) => {
+ _.forEach(connectedVls, (vlToDelete:Cy.CollectionNodes) => {
+
+ if (vlToDelete.connectedEdges().length === 2) { // if vl connected only to 2 nodes need to delete the vl
+ this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, vlToDelete.data().componentInstance);
+ }
+ });
+ };
+
+
+ /**
+ * This function is called when moving a node in or out of UCPE.
+ * Deletes all connected VLs that have less than 2 valid connections remaining after the move
+ * Returns the collection of vls that are in the process of deletion (async) to prevent duplicate calls while deletion is in progress
+ * @param component
+ * @param cy
+ * @param node - node that was moved in/out of ucpe
+ */
+ public deleteNodeVLsUponMoveToOrFromUCPE = (component:Component, cy:Cy.Instance, node:Cy.CollectionNodes):Cy.CollectionNodes => {
+ if (node.data() instanceof CompositionCiNodeVl) {
+ return;
+ }
+
+ let connectedVLsToDelete:Cy.CollectionNodes = cy.collection();
+ _.forEach(node.neighborhood('node'), (connectedNode) => {
+
+ //Find all neighboring nodes that are VLs
+ if (connectedNode.data() instanceof CompositionCiNodeVl) {
+
+ //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)
+ let compatibleNodeCount = 0;
+ let vlNeighborhood = connectedNode.neighborhood('node');
+ _.forEach(vlNeighborhood, (vlNeighborNode)=> {
+ if (this.commonGraphUtils.nodeLocationsCompatible(cy, connectedNode, vlNeighborNode)) {
+ compatibleNodeCount++;
+ }
+ });
+
+ if (compatibleNodeCount < 2) {
+ connectedVLsToDelete = connectedVLsToDelete.add(connectedNode);
+ }
+ }
+ });
+
+ connectedVLsToDelete.each((i, vlToDelete:Cy.CollectionNodes)=> {
+ this.deleteNode(cy, component, vlToDelete);
+ });
+ return connectedVLsToDelete;
+ };
+
+ /**
+ * This function will update nodes position. if the new position is into or out of ucpe, the node will trigger the ucpe events
+ * @param cy
+ * @param component
+ * @param nodesMoved - the node/multiple nodes now moved by the user
+ */
+ public onNodesPositionChanged = (cy:Cy.Instance, component:Component, nodesMoved:Cy.CollectionNodes):void => {
+
+ if (nodesMoved.length === 0) {
+ return;
+ }
+
+ let isValidMove:boolean = this.GeneralGraphUtils.isGroupValidDrop(cy, nodesMoved);
+ if (isValidMove) {
+
+ this.$log.debug(`composition-graph::ValidDrop:: updating node position`);
+ let instancesToUpdateInNonBlockingAction:Array<ComponentInstance> = new Array<ComponentInstance>();
+
+ _.each(nodesMoved, (node:Cy.CollectionFirstNode)=> { //update all nodes new position
+
+ if (node.data().isUcpePart && !node.data().isUcpe) {
+ return;
+ }//No need to update UCPE-CPs
+
+ //update position
+ let newPosition:Cy.Position = this.commonGraphUtils.getNodePosition(node);
+ node.data().componentInstance.updatePosition(newPosition.x, newPosition.y);
+
+ //check if node moved to or from UCPE
+ let ucpe = this.commonGraphUtils.isInUcpe(node.cy(), node.boundingbox());
+ if (node.data().isInsideGroup || ucpe.length) {
+ this.handleUcpeChildMove(node, ucpe, instancesToUpdateInNonBlockingAction);
+ } else {
+ instancesToUpdateInNonBlockingAction.push(node.data().componentInstance);
+ }
+
+ });
+
+ if (instancesToUpdateInNonBlockingAction.length > 0) {
+ this.GeneralGraphUtils.pushMultipleUpdateComponentInstancesRequestToQueue(false, instancesToUpdateInNonBlockingAction, component);
+ }
+ } else {
+ this.$log.debug(`composition-graph::notValidDrop:: node return to latest position`);
+ //reset nodes position
+ nodesMoved.positions((i, node) => {
+ return {
+ x: +node.data().componentInstance.posX,
+ y: +node.data().componentInstance.posY
+ };
+ })
+ }
+
+ this.GeneralGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIActionWithReleaseCallback(() => {
+ }, () => {
+ this.loaderService.hideLoader('composition-graph');
+ });
+
+ };
+
+ /**
+ * Checks whether the node has been added or removed from UCPE and triggers appropriate events
+ * @param node - node moved
+ * @param ucpeContainer - UCPE container that the node has been moved to. When moving a node out of ucpe, param will be empty
+ * @param instancesToUpdateInNonBlockingAction
+ */
+ public handleUcpeChildMove(node:Cy.CollectionFirstNode, ucpeContainer:Cy.CollectionElements, instancesToUpdateInNonBlockingAction:Array<ComponentInstance>) {
+
+ if (node.data().isInsideGroup) {
+ if (ucpeContainer.length) { //moving node within UCPE. Simply update position
+ this.commonGraphUtils.updateUcpeChildPosition(<Cy.CollectionNodes>node, ucpeContainer);
+ instancesToUpdateInNonBlockingAction.push(node.data().componentInstance);
+ } else { //removing node from UCPE. Notify observers
+ this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_REMOVE_NODE_FROM_UCPE, node, ucpeContainer);
+ }
+ } else if (!node.data().isInsideGroup && ucpeContainer.length && !node.data().isUcpePart) { //adding node to UCPE
+ this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_INSERT_NODE_TO_UCPE, node, ucpeContainer, true);
+ }
+ }
+ /**
+ * Gets the position for the asset popover menu
+ * Then, check if right edge of menu would overlap horizontal screen edge (palette offset + canvas width - right panel)
+ * Then, check if bottom edge of menu would overlap the vertical end of the canvas.
+ * @param cy
+ * @param node
+ * @returns {Cy.Position}
+
+ public createAssetPopover = (cy: Cy.Instance, node:Cy.CollectionFirstNode, isViewOnly:boolean):AssetPopoverObj => {
+
+ 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.
+ let menuPosition:Cy.Position = this.commonGraphUtils.getNodePositionWithOffset(node, menuOffset);
+ let menuSide:string = 'right';
+
+ if(menuPosition.x + GraphUIObjects.COMPOSITION_NODE_MENU_WIDTH >= cy.width() + GraphUIObjects.DIAGRAM_PALETTE_WIDTH_OFFSET - GraphUIObjects.COMPOSITION_RIGHT_PANEL_OFFSET){
+ 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
+ menuSide = 'left';
+ }
+
+ if(menuPosition.y + GraphUIObjects.COMPOSITION_NODE_MENU_HEIGHT >= cy.height()){
+ menuPosition.y = menuPosition.y - GraphUIObjects.COMPOSITION_NODE_MENU_HEIGHT - menuOffset.y * 2;
+ }
+
+ return new AssetPopoverObj(node.data().id, node.data().name, menuPosition, menuSide, isViewOnly);
+ };
+ */
+
+ }
+
+
+ CompositionGraphNodesUtils.$inject = ['NodesFactory', '$log', 'CompositionGraphGeneralUtils', 'CommonGraphUtils', 'EventListenerService', 'LoaderService' /*, 'sdcMenu', 'ModalsHandler'*/]
+
diff --git a/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-palette-utils.ts b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-palette-utils.ts
new file mode 100644
index 0000000000..83bf747501
--- /dev/null
+++ b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-palette-utils.ts
@@ -0,0 +1,163 @@
+import {EventListenerService, LoaderService} from "app/services";
+import {CapabilitiesGroup, NodesFactory, ComponentInstance, Component, CompositionCiNodeBase, RequirementsGroup} from "app/models";
+import {ComponentFactory, ComponentInstanceFactory, GRAPH_EVENTS, GraphUIObjects} from "app/utils";
+import {CompositionGraphGeneralUtils} from "./composition-graph-general-utils";
+import {CommonGraphUtils} from "../../common/common-graph-utils";
+import 'angular-dragdrop';
+import {LeftPaletteComponent} from "../../../../models/components/displayComponent";
+
+export class CompositionGraphPaletteUtils {
+
+ constructor(private ComponentFactory:ComponentFactory,
+ private $filter:ng.IFilterService,
+ private loaderService:LoaderService,
+ private generalGraphUtils:CompositionGraphGeneralUtils,
+ private componentInstanceFactory:ComponentInstanceFactory,
+ private nodesFactory:NodesFactory,
+ private commonGraphUtils:CommonGraphUtils,
+ private eventListenerService:EventListenerService) {
+ }
+
+ /**
+ * Calculate the dragged element (html element) position on canvas
+ * @param cy
+ * @param event
+ * @param position
+ * @returns {Cy.BoundingBox}
+ * @private
+ */
+ private _getNodeBBox(cy:Cy.Instance, event:IDragDropEvent, position?:Cy.Position) {
+ let bbox = <Cy.BoundingBox>{};
+ if (!position) {
+ position = this.commonGraphUtils.getCytoscapeNodePosition(cy, event);
+ }
+ let cushionWidth:number = 40;
+ let cushionHeight:number = 40;
+
+ bbox.x1 = position.x - cushionWidth / 2;
+ bbox.y1 = position.y - cushionHeight / 2;
+ bbox.x2 = position.x + cushionWidth / 2;
+ bbox.y2 = position.y + cushionHeight / 2;
+ return bbox;
+ }
+
+ /**
+ * Create the component instance, update data from parent component in the left palette and notify on_insert_to_ucpe if component was dragg into ucpe
+ * @param cy
+ * @param fullComponent
+ * @param event
+ * @param component
+ */
+ private _createComponentInstanceOnGraphFromPaletteComponent(cy:Cy.Instance, fullComponent:LeftPaletteComponent, event:IDragDropEvent, component:Component) {
+
+ let componentInstanceToCreate:ComponentInstance = this.componentInstanceFactory.createComponentInstanceFromComponent(fullComponent);
+ let cytoscapePosition:Cy.Position = this.commonGraphUtils.getCytoscapeNodePosition(cy, event);
+
+ componentInstanceToCreate.posX = cytoscapePosition.x;
+ componentInstanceToCreate.posY = cytoscapePosition.y;
+
+
+ let onFailedCreatingInstance:(error:any) => void = (error:any) => {
+ this.loaderService.hideLoader('composition-graph');
+ };
+
+ //on success - update node data
+ let onSuccessCreatingInstance = (createInstance:ComponentInstance):void => {
+
+ this.loaderService.hideLoader('composition-graph');
+
+ createInstance.name = this.$filter('resourceName')(createInstance.name);
+ createInstance.requirements = new RequirementsGroup(createInstance.requirements);
+ createInstance.capabilities = new CapabilitiesGroup(createInstance.capabilities);
+ createInstance.componentVersion = fullComponent.version;
+ createInstance.icon = fullComponent.icon;
+ createInstance.setInstanceRC();
+
+ let newNode:CompositionCiNodeBase = this.nodesFactory.createNode(createInstance);
+ let cyNode:Cy.CollectionNodes = this.commonGraphUtils.addComponentInstanceNodeToGraph(cy, newNode);
+
+ //check if node was dropped into a UCPE
+ let ucpe:Cy.CollectionElements = this.commonGraphUtils.isInUcpe(cy, cyNode.boundingbox());
+ if (ucpe.length > 0) {
+ this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_INSERT_NODE_TO_UCPE, cyNode, ucpe, false);
+ }
+ this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_CREATE_COMPONENT_INSTANCE);
+
+ };
+
+ this.loaderService.showLoader('composition-graph');
+
+ // Create the component instance on server
+ this.generalGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIAction(() => {
+ component.createComponentInstance(componentInstanceToCreate).then(onSuccessCreatingInstance, onFailedCreatingInstance);
+ });
+ }
+
+ /**
+ * Thid function applay red/green background when component dragged from palette
+ * @param cy
+ * @param event
+ * @param dragElement
+ * @param dragComponent
+ */
+ public onComponentDrag(cy:Cy.Instance, event:IDragDropEvent, dragElement:JQuery, dragComponent:ComponentInstance) {
+
+ if (event.clientX < GraphUIObjects.DIAGRAM_PALETTE_WIDTH_OFFSET || event.clientY < GraphUIObjects.DIAGRAM_HEADER_OFFSET) { //hovering over palette. Dont bother computing validity of drop
+ dragElement.removeClass('red');
+ return;
+ }
+
+ let offsetPosition = {
+ x: event.clientX - GraphUIObjects.DIAGRAM_PALETTE_WIDTH_OFFSET,
+ y: event.clientY - GraphUIObjects.DIAGRAM_HEADER_OFFSET
+ };
+ let bbox = this._getNodeBBox(cy, event, offsetPosition);
+
+ if (this.generalGraphUtils.isPaletteDropValid(cy, bbox, dragComponent)) {
+ dragElement.removeClass('red');
+ } else {
+ dragElement.addClass('red');
+ }
+ }
+
+ /**
+ * This function is called when after dropping node on canvas
+ * Check if the capability & requirements fulfilled and if not get from server
+ * @param cy
+ * @param event
+ * @param component
+ */
+ public addNodeFromPalette(cy:Cy.Instance, event:IDragDropEvent, component:Component) {
+ this.loaderService.showLoader('composition-graph');
+
+ let draggedComponent:LeftPaletteComponent = event.dataTransfer.component;
+
+ if (this.generalGraphUtils.componentRequirementsAndCapabilitiesCaching.containsKey(draggedComponent.uniqueId)) {
+ let fullComponent = this.generalGraphUtils.componentRequirementsAndCapabilitiesCaching.getValue(draggedComponent.uniqueId);
+ draggedComponent.capabilities = fullComponent.capabilities;
+ draggedComponent.requirements = fullComponent.requirements;
+ this._createComponentInstanceOnGraphFromPaletteComponent(cy, draggedComponent, event, component);
+
+ } else {
+
+ this.ComponentFactory.getComponentFromServer(draggedComponent.getComponentSubType(), draggedComponent.uniqueId)
+ .then((fullComponent:Component) => {
+ draggedComponent.capabilities = fullComponent.capabilities;
+ draggedComponent.requirements = fullComponent.requirements;
+ this._createComponentInstanceOnGraphFromPaletteComponent(cy, draggedComponent, event, component);
+ });
+ }
+ }
+}
+
+
+CompositionGraphPaletteUtils.$inject = [
+ 'ComponentFactory',
+ '$filter',
+ 'LoaderService',
+ 'CompositionGraphGeneralUtils',
+ 'ComponentInstanceFactory',
+ 'NodesFactory',
+ 'CommonGraphUtils',
+ 'EventListenerService'
+];
diff --git a/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/match-capability-requierment-utils.ts b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/match-capability-requierment-utils.ts
new file mode 100644
index 0000000000..0e21f033be
--- /dev/null
+++ b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/match-capability-requierment-utils.ts
@@ -0,0 +1,259 @@
+import {Requirement, CompositionCiLinkBase, ComponentInstance, CapabilitiesGroup, RequirementsGroup, MatchReqToCapability, MatchBase,
+ MatchReqToReq,CompositionCiNodeBase, Component, Capability} from "app/models";
+/**
+ * Created by obarda on 1/1/2017.
+ */
+
+export class MatchCapabilitiesRequirementsUtils {
+
+ constructor() {
+ }
+
+ public static linkable(requirement1:Requirement, requirement2:Requirement, vlCapability:Capability):boolean {
+ return MatchCapabilitiesRequirementsUtils.isMatch(requirement1, vlCapability) && MatchCapabilitiesRequirementsUtils.isMatch(requirement2, vlCapability);
+ };
+
+
+ /**
+ * Shows + icon in corner of each node passed in
+ * @param filteredNodesData
+ * @param cy
+ */
+ public highlightMatchingComponents(filteredNodesData, cy:Cy.Instance) {
+ _.each(filteredNodesData, (data:any) => {
+ let node = cy.getElementById(data.id);
+ cy.emit('showhandle', [node]);
+ });
+ }
+
+ /**
+ * Adds opacity to each node that cannot be linked to hovered node
+ * @param filteredNodesData
+ * @param nodesData
+ * @param cy
+ * @param hoveredNodeData
+ */
+ public fadeNonMachingComponents(filteredNodesData, nodesData, cy:Cy.Instance, hoveredNodeData?) {
+ let fadeNodes = _.xorWith(nodesData, filteredNodesData, (node1, node2) => {
+ return node1.id === node2.id;
+ });
+ if (hoveredNodeData) {
+ _.remove(fadeNodes, hoveredNodeData);
+ }
+ cy.batch(()=> {
+ _.each(fadeNodes, (node) => {
+ cy.getElementById(node.id).style({'background-image-opacity': 0.4});
+ });
+ })
+ }
+
+ /**
+ * Resets all nodes to regular opacity
+ * @param cy
+ */
+ public resetFadedNodes(cy:Cy.Instance) {
+ cy.batch(()=> {
+ cy.nodes().style({'background-image-opacity': 1});
+ })
+ }
+
+ // -------------------------------------------ALL FUNCTIONS NEED REFACTORING---------------------------------------------------------------//
+
+ private static requirementFulfilled(fromNodeId:string, requirement:any, links:Array<CompositionCiLinkBase>):boolean {
+ return _.some(links, {
+ 'relation': {
+ 'fromNode': fromNodeId,
+ 'relationships': [{
+ 'requirementOwnerId': requirement.ownerId,
+ 'requirement': requirement.name,
+ 'relationship': {
+ 'type': requirement.relationship
+ }
+ }
+ ]
+ }
+ });
+ };
+
+ private static isMatch(requirement:Requirement, capability:Capability):boolean {
+ if (capability.type === requirement.capability) {
+ if (requirement.node) {
+ if (_.includes(capability.capabilitySources, requirement.node)) {
+ return true;
+ }
+ } else {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ private getFromToMatches(requirements1:RequirementsGroup,
+ requirements2:RequirementsGroup,
+ capabilities:CapabilitiesGroup,
+ links:Array<CompositionCiLinkBase>,
+ fromId:string,
+ toId:string,
+ vlCapability?:Capability):Array<MatchBase> {
+ let matches:Array<MatchBase> = new Array<MatchBase>();
+ _.forEach(requirements1, (requirementValue:Array<Requirement>, key) => {
+ _.forEach(requirementValue, (requirement:Requirement) => {
+ if (requirement.name !== "dependency" && !MatchCapabilitiesRequirementsUtils.requirementFulfilled(fromId, requirement, links)) {
+ _.forEach(capabilities, (capabilityValue:Array<Capability>, key) => {
+ _.forEach(capabilityValue, (capability:Capability) => {
+ if (MatchCapabilitiesRequirementsUtils.isMatch(requirement, capability)) {
+ let match:MatchReqToCapability = new MatchReqToCapability(requirement, capability, true, fromId, toId);
+ matches.push(match);
+ }
+ });
+ });
+ if (vlCapability) {
+ _.forEach(requirements2, (requirement2Value:Array<Requirement>, key) => {
+ _.forEach(requirement2Value, (requirement2:Requirement) => {
+ if (!MatchCapabilitiesRequirementsUtils.requirementFulfilled(toId, requirement2, links) && MatchCapabilitiesRequirementsUtils.linkable(requirement, requirement2, vlCapability)) {
+ let match:MatchReqToReq = new MatchReqToReq(requirement, requirement2, true, fromId, toId);
+ matches.push(match);
+ }
+ });
+ });
+ }
+ }
+ });
+ });
+ return matches;
+ }
+
+ private getToFromMatches(requirements:RequirementsGroup, capabilities:CapabilitiesGroup, links:Array<CompositionCiLinkBase>, fromId:string, toId:string):Array<MatchReqToCapability> {
+ let matches:Array<MatchReqToCapability> = [];
+ _.forEach(requirements, (requirementValue:Array<Requirement>, key) => {
+ _.forEach(requirementValue, (requirement:Requirement) => {
+ if (requirement.name !== "dependency" && !MatchCapabilitiesRequirementsUtils.requirementFulfilled(toId, requirement, links)) {
+ _.forEach(capabilities, (capabilityValue:Array<Capability>, key) => {
+ _.forEach(capabilityValue, (capability:Capability) => {
+ if (MatchCapabilitiesRequirementsUtils.isMatch(requirement, capability)) {
+ let match:MatchReqToCapability = new MatchReqToCapability(requirement, capability, false, toId, fromId);
+ matches.push(match);
+ }
+ });
+ });
+ }
+ });
+ });
+ return matches;
+ }
+
+ public getMatchedRequirementsCapabilities(fromComponentInstance:ComponentInstance,
+ toComponentInstance:ComponentInstance,
+ links:Array<CompositionCiLinkBase>,
+ vl?:Component):Array<MatchBase> {//TODO allow for VL array
+ let linkCapability;
+ if (vl) {
+ let linkCapabilities:Array<Capability> = vl.capabilities.findValueByKey('linkable');
+ if (linkCapabilities) {
+ linkCapability = linkCapabilities[0];
+ }
+ }
+ let fromToMatches:Array<MatchBase> = this.getFromToMatches(fromComponentInstance.requirements,
+ toComponentInstance.requirements,
+ toComponentInstance.capabilities,
+ links,
+ fromComponentInstance.uniqueId,
+ toComponentInstance.uniqueId,
+ linkCapability);
+ let toFromMatches:Array<MatchReqToCapability> = this.getToFromMatches(toComponentInstance.requirements,
+ fromComponentInstance.capabilities,
+ links,
+ fromComponentInstance.uniqueId,
+ toComponentInstance.uniqueId);
+
+ return fromToMatches.concat(toFromMatches);
+ }
+
+
+ /**
+ * Step I: Check if capabilities of component match requirements of nodeDataArray
+ * 1. Get component capabilities and loop on each capability
+ * 2. Inside the loop, perform another loop on all nodeDataArray, and fetch the requirements for each one
+ * 3. Loop on the requirements, and verify match (see in code the rules)
+ *
+ * Step II: Check if requirements of component match capabilities of nodeDataArray
+ * 1. Get component requirements and loop on each requirement
+ * 2.
+ *
+ * @param component - this is the hovered resource of the left panel of composition screen
+ * @param nodeDataArray - Array of resource instances that are on the canvas
+ * @param links -getMatchedRequirementsCapabilities
+ * @param vl -
+ * @returns {any[]|T[]}
+ */
+ public findByMatchingCapabilitiesToRequirements(component:Component,
+ nodeDataArray:Array<CompositionCiNodeBase>,
+ links:Array<CompositionCiLinkBase>,
+ vl?:Component):Array<any> {//TODO allow for VL array
+ let res = [];
+
+ // STEP I
+ {
+ let capabilities:any = component.capabilities;
+ _.forEach(capabilities, (capabilityValue:Array<any>, capabilityKey)=> {
+ _.forEach(capabilityValue, (capability)=> {
+ _.forEach(nodeDataArray, (node:CompositionCiNodeBase)=> {
+ if (node && node.componentInstance) {
+ let requirements:any = node.componentInstance.requirements;
+ let fromNodeId:string = node.componentInstance.uniqueId;
+ _.forEach(requirements, (requirementValue:Array<any>, requirementKey)=> {
+ _.forEach(requirementValue, (requirement)=> {
+ if (requirement.name !== "dependency" && MatchCapabilitiesRequirementsUtils.isMatch(requirement, capability)
+ && !MatchCapabilitiesRequirementsUtils.requirementFulfilled(fromNodeId, requirement, links)) {
+ res.push(node);
+ }
+ });
+ });
+ }
+ });
+ });
+ });
+ }
+
+ // STEP II
+ {
+ let requirements:any = component.requirements;
+ let fromNodeId:string = component.uniqueId;
+ let linkCapability:Array<Capability> = vl ? vl.capabilities.findValueByKey('linkable') : undefined;
+
+ _.forEach(requirements, (requirementValue:Array<any>, requirementKey)=> {
+ _.forEach(requirementValue, (requirement)=> {
+ if (requirement.name !== "dependency" && !MatchCapabilitiesRequirementsUtils.requirementFulfilled(fromNodeId, requirement, links)) {
+ _.forEach(nodeDataArray, (node:any)=> {
+ if (node && node.componentInstance && node.category !== 'groupCp') {
+ let capabilities:any = node.componentInstance.capabilities;
+ _.forEach(capabilities, (capabilityValue:Array<any>, capabilityKey)=> {
+ _.forEach(capabilityValue, (capability)=> {
+ if (MatchCapabilitiesRequirementsUtils.isMatch(requirement, capability)) {
+ res.push(node);
+ }
+ });
+ });
+ if (linkCapability) {
+ let linkRequirements = node.componentInstance.requirements;
+ _.forEach(linkRequirements, (value:Array<any>, key)=> {
+ _.forEach(value, (linkRequirement)=> {
+ if (!MatchCapabilitiesRequirementsUtils.requirementFulfilled(node.componentInstance.uniqueId, linkRequirement, links)
+ && MatchCapabilitiesRequirementsUtils.linkable(requirement, linkRequirement, linkCapability[0])) {
+ res.push(node);
+ }
+ });
+ });
+ }
+ }
+ });
+ }
+ });
+ });
+ }
+
+ return _.uniq(res);
+ };
+}
+
+MatchCapabilitiesRequirementsUtils.$inject = [];