aboutsummaryrefslogtreecommitdiffstats
path: root/catalog-ui/src/app/ng2/pages/composition/graph/utils
diff options
context:
space:
mode:
Diffstat (limited to 'catalog-ui/src/app/ng2/pages/composition/graph/utils')
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-general-utils.ts268
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-links-utils.ts342
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-nodes-utils.spec.ts158
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-nodes-utils.ts202
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-palette-utils.ts233
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-service-path-utils.ts148
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-zone-utils.ts204
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/utils/index.ts29
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requierment-utils.spec.ts342
-rw-r--r--catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requierment-utils.ts196
10 files changed, 2122 insertions, 0 deletions
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-general-utils.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-general-utils.ts
new file mode 100644
index 0000000000..bc8bd691c9
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-general-utils.ts
@@ -0,0 +1,268 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import * as _ from "lodash";
+import {ComponentInstance, Match, CompositionCiLinkBase, CompositionCiNodeUcpeCp} from "app/models";
+import {Dictionary, GraphUIObjects} from "app/utils";
+import {MatchCapabilitiesRequirementsUtils} from "./match-capability-requierment-utils";
+import {CommonGraphUtils} from "../common/common-graph-utils";
+import {Injectable} from "@angular/core";
+import {QueueServiceUtils} from "app/ng2/utils/queue-service-utils";
+import {ComponentServiceNg2} from "app/ng2/services/component-services/component.service";
+import {RequirementsGroup} from "app/models/requirement";
+import {CapabilitiesGroup} from "app/models/capability";
+import {TopologyTemplateService} from "app/ng2/services/component-services/topology-template.service";
+import {CompositionService} from "../../composition.service";
+import {WorkspaceService} from "app/ng2/pages/workspace/workspace.service";
+import {NotificationsService} from "onap-ui-angular/dist/notifications/services/notifications.service";
+import {NotificationSettings} from "onap-ui-angular/dist/notifications/utilities/notification.config";
+
+export interface RequirementAndCapabilities {
+ capabilities: CapabilitiesGroup;
+ requirements: RequirementsGroup;
+}
+
+@Injectable()
+export class CompositionGraphGeneralUtils {
+
+ public componentRequirementsAndCapabilitiesCaching = new Dictionary<string, RequirementAndCapabilities>();
+
+ constructor(private commonGraphUtils: CommonGraphUtils,
+ private matchCapabilitiesRequirementsUtils: MatchCapabilitiesRequirementsUtils,
+ private queueServiceUtils: QueueServiceUtils,
+ private componentService: ComponentServiceNg2,
+ private topologyTemplateService: TopologyTemplateService,
+ private compositionService: CompositionService,
+ private workspaceService: WorkspaceService) {
+ }
+
+ /**
+ * 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;
+ };
+
+ public zoomGraphTo = (cy: Cy.Instance, zoomLevel: number): void => {
+ let zy = cy.height() / 2;
+ let zx = cy.width() / 2;
+ cy.zoom({
+ level: zoomLevel,
+ renderedPosition: {x: zx, y: zy}
+ });
+ }
+
+ //saves the current zoom, and then sets a temporary maximum zoom for zoomAll, and then reverts to old value
+ public zoomAllWithMax = (cy: Cy.Instance, maxZoom: number): void => {
+
+ let oldMaxZoom: number = cy.maxZoom();
+
+ cy.maxZoom(maxZoom);
+ this.zoomAll(cy);
+ cy.maxZoom(oldMaxZoom);
+
+ };
+
+ //Zooms to fit all of the nodes in the collection passed in. If no nodes are passed in, will zoom to fit all nodes on graph
+ public zoomAll = (cy: Cy.Instance, nodes?: Cy.CollectionNodes): void => {
+
+ if (!nodes || !nodes.length) {
+ nodes = cy.nodes();
+ }
+
+ cy.resize();
+ cy.animate({
+ fit: {eles: nodes, padding: 20},
+ center: {eles: nodes}
+ }, {duration: 400});
+ };
+
+ /**
+ * 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 topologyTemplate instance can be hosted on the UCPE instance
+ * @param cy - Cytoscape instance
+ * @param fromUcpeInstance
+ * @param toComponentInstance
+ * @returns {Match}
+ */
+ public canBeHostedOn(cy: Cy.Instance, fromUcpeInstance: ComponentInstance, toComponentInstance: ComponentInstance): Match {
+
+ let matches: Array<Match> = this.matchCapabilitiesRequirementsUtils.getMatchedRequirementsCapabilities(fromUcpeInstance, toComponentInstance, this.getAllCompositionCiLinks(cy));
+ let hostedOnMatch: Match = _.find(matches, (match: Match) => {
+ return match.requirement.capability.toLowerCase() === 'tosca.capabilities.container';
+ });
+
+ return 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: Match = 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) {
+
+ let illegalOverlappingNodes = _.filter(cy.nodes("[isSdcElement]"), (graphNode: Cy.CollectionFirstNode) => {
+ if (this.isBBoxOverlapping(pseudoNodeBBox, graphNode.renderedBoundingBox())) {
+ 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();
+ });
+ };
+
+ /**
+ *
+ * @param blockAction - true/false if this is a block action
+ * @param instances
+ * @param component
+ */
+ public pushMultipleUpdateComponentInstancesRequestToQueue = (instances: Array<ComponentInstance>): void => {
+ this.queueServiceUtils.addNonBlockingUIAction(() => {
+ return new Promise<boolean>((resolve, reject) => {
+ let uniqueId = this.workspaceService.metadata.uniqueId;
+ let topologyType = this.workspaceService.metadata.componentType;
+ this.topologyTemplateService.updateMultipleComponentInstances(uniqueId, topologyType, instances).subscribe(instancesResult => {
+ this.compositionService.updateComponentInstances(instancesResult);
+ resolve(true);
+ });
+ });
+ });
+ }
+}
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-links-utils.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-links-utils.ts
new file mode 100644
index 0000000000..6035d05b7f
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-links-utils.ts
@@ -0,0 +1,342 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+/**
+ * Created by obarda on 6/28/2016.
+ */
+import * as _ from "lodash";
+import {GraphUIObjects} from "app/utils";
+import {
+ Match,
+ CompositionCiNodeBase,
+ RelationshipModel,
+ ConnectRelationModel,
+ LinksFactory,
+ Component,
+ LinkMenu,
+ Point,
+ CompositionCiLinkBase,
+ Requirement,
+ Capability,
+ Relationship,
+ ComponentInstance
+} from "app/models";
+import {CommonGraphUtils} from "../common/common-graph-utils";
+import {CompositionGraphGeneralUtils} from "./composition-graph-general-utils";
+import {MatchCapabilitiesRequirementsUtils} from "./match-capability-requierment-utils";
+import {CompositionCiServicePathLink} from "app/models/graph/graph-links/composition-graph-links/composition-ci-service-path-link";
+import {Injectable} from "@angular/core";
+import {QueueServiceUtils} from "app/ng2/utils/queue-service-utils";
+import {TopologyTemplateService} from "app/ng2/services/component-services/topology-template.service";
+import {SdcUiServices} from "onap-ui-angular";
+import {CompositionService} from "../../composition.service";
+import {WorkspaceService} from "app/ng2/pages/workspace/workspace.service";
+
+@Injectable()
+export class CompositionGraphLinkUtils {
+
+ constructor(private linksFactory: LinksFactory,
+ private generalGraphUtils: CompositionGraphGeneralUtils,
+ private commonGraphUtils: CommonGraphUtils,
+ private queueServiceUtils: QueueServiceUtils,
+ private matchCapabilitiesRequirementsUtils: MatchCapabilitiesRequirementsUtils,
+ private topologyTemplateService: TopologyTemplateService,
+ private loaderService: SdcUiServices.LoaderService,
+ private compositionService: CompositionService,
+ private workspaceService: WorkspaceService) {
+
+
+ }
+
+ /**
+ * 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.activate();
+ this.queueServiceUtils.addBlockingUIAction(() => {
+ this.topologyTemplateService.deleteRelation(this.workspaceService.metadata.uniqueId, this.workspaceService.metadata.componentType, link.data().relation).subscribe((deletedRelation) => {
+ this.compositionService.deleteRelation(deletedRelation);
+ cy.remove(link);
+ this.loaderService.deactivate();
+ }, (error) => {this.loaderService.deactivate()});
+ });
+ };
+
+ /**
+ * 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): void => {
+
+ this.loaderService.activate();
+ link.updateLinkDirection();
+
+ this.queueServiceUtils.addBlockingUIAction(() => {
+ this.topologyTemplateService.createRelation(this.workspaceService.metadata.uniqueId, this.workspaceService.metadata.componentType, link.relation).subscribe((relation) => {
+ link.setRelation(relation);
+ this.insertLinkToGraph(cy, link);
+ this.compositionService.addRelation(relation);
+ this.loaderService.deactivate();
+ }, (error) => {this.loaderService.deactivate()})
+ });
+ };
+
+ private createSimpleLink = (match: Match, cy: Cy.Instance): void => {
+ let newRelation: RelationshipModel = match.matchToRelationModel();
+ let linkObg: CompositionCiLinkBase = this.linksFactory.createGraphLink(cy, newRelation, newRelation.relationships[0]);
+ this.createLink(linkObg, cy);
+ };
+
+ public createLinkFromMenu = (cy: Cy.Instance, chosenMatch: Match): void => {
+
+ if (chosenMatch) {
+ if (chosenMatch && chosenMatch instanceof Match) {
+ this.createSimpleLink(chosenMatch, cy);
+ }
+ }
+ }
+
+ /**
+ * 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): ConnectRelationModel {
+
+ let linkModel: Array<CompositionCiLinkBase> = this.generalGraphUtils.getAllCompositionCiLinks(cy);
+
+ let possibleRelations: Array<Match> = this.matchCapabilitiesRequirementsUtils.getMatchedRequirementsCapabilities(fromNode.data().componentInstance,
+ toNode.data().componentInstance, linkModel);
+
+ //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 ConnectRelationModel(fromNode.data(), toNode.data(), possibleRelations);
+ }
+ return null;
+ };
+
+ private handlePathLink(cy: Cy.Instance, event: Cy.EventObject) {
+ let linkData = event.cyTarget.data();
+ let selectedPathId = linkData.pathId;
+ let pathEdges = cy.collection(`[pathId='${selectedPathId}']`);
+ if (pathEdges.length > 1) {
+ setTimeout(() => {
+ pathEdges.select();
+ }, 0);
+ }
+ }
+
+ private handleVLLink(event: Cy.EventObject) {
+ let vl: Cy.CollectionNodes = event.cyTarget[0].target('.vl-node');
+ let connectedEdges: Cy.CollectionEdges = vl.connectedEdges(`[type!="${CompositionCiServicePathLink.LINK_TYPE}"]`);
+ if (vl.length && connectedEdges.length > 1) {
+ setTimeout(() => {
+ vl.select();
+ connectedEdges.select();
+ }, 0);
+ }
+ }
+
+
+ /**
+ * Handles click event on links.
+ * If one edge selected: do nothing.
+ * Two 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 > 1 && event.cyTarget[0].selected()) {
+ cy.$(':selected').unselect();
+ } else {
+ if (event.cyTarget[0].data().type === CompositionCiServicePathLink.LINK_TYPE) {
+ this.handlePathLink(cy, event);
+ }
+ else {
+ this.handleVLLink(event);
+ }
+ }
+ }
+
+
+ /**
+ * 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.clientX, event.originalEvent.clientY);
+ 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;
+ };
+
+ /**
+ * Returns relation source and target nodes.
+ * @param nodes - all nodes in graph in order to find the edge connecting the two nodes
+ * @param fromNodeId
+ * @param toNodeId
+ * @returns [source, target] array of source node and target node.
+ */
+ public getRelationNodes(nodes: Cy.CollectionNodes, fromNodeId: string, toNodeId: string) {
+ return [
+ _.find(nodes, (node: Cy.CollectionFirst) => node.data().id === fromNodeId),
+ _.find(nodes, (node: Cy.CollectionFirst) => node.data().id === toNodeId)
+ ];
+ }
+
+
+ /**
+ * go over the relations and draw links on the graph
+ * @param cy
+ * @param getRelationRequirementCapability - function to get requirement and capability of a relation
+ */
+ public initGraphLinks(cy: Cy.Instance, relations: RelationshipModel[]) {
+ if (relations) {
+ _.forEach(relations, (relationshipModel: RelationshipModel) => {
+ _.forEach(relationshipModel.relationships, (relationship: Relationship) => {
+ let linkToCreate = this.linksFactory.createGraphLink(cy, relationshipModel, relationship);
+ this.insertLinkToGraph(cy, linkToCreate);
+ });
+ });
+ }
+ }
+
+ /**
+ * Add link to graph - only draw the link
+ * @param cy
+ * @param link
+ * @param getRelationRequirementCapability
+ */
+ public insertLinkToGraph = (cy: Cy.Instance, link: CompositionCiLinkBase) => {
+ const relationNodes = this.getRelationNodes(cy.nodes(), link.source, link.target);
+ const sourceNode: CompositionCiNodeBase = relationNodes[0] && relationNodes[0].data();
+ const targetNode: CompositionCiNodeBase = relationNodes[1] && relationNodes[1].data();
+ if ((sourceNode && !sourceNode.certified) || (targetNode && !targetNode.certified)) {
+ link.classes = 'not-certified-link';
+ }
+ let linkElement = cy.add({
+ group: 'edges',
+ data: link,
+ classes: link.classes
+ });
+
+ const getLinkRequirementCapability = () =>
+ this.getRelationRequirementCapability(link.relation.relationships[0], sourceNode.componentInstance, targetNode.componentInstance);
+ this.commonGraphUtils.initLinkTooltip(linkElement, link.relation.relationships[0], getLinkRequirementCapability);
+ };
+
+ public syncComponentByRelation(relation: RelationshipModel) {
+ let componentInstances = this.compositionService.getComponentInstances();
+ relation.relationships.forEach((rel) => {
+ if (rel.capability) {
+ const toComponentInstance: ComponentInstance = componentInstances.find((inst) => inst.uniqueId === relation.toNode);
+ const toComponentInstanceCapability: Capability = toComponentInstance.findCapability(
+ rel.capability.type, rel.capability.uniqueId, rel.capability.ownerId, rel.capability.name);
+ const isCapabilityFulfilled: boolean = rel.capability.isFulfilled();
+ if (isCapabilityFulfilled && toComponentInstanceCapability) {
+ // if capability is fulfilled and in component, then remove it
+ console.log('Capability is fulfilled', rel.capability.getFullTitle(), rel.capability.leftOccurrences);
+ toComponentInstance.capabilities[rel.capability.type].splice(
+ toComponentInstance.capabilities[rel.capability.type].findIndex((cap) => cap === toComponentInstanceCapability), 1
+ )
+ } else if (!isCapabilityFulfilled && !toComponentInstanceCapability) {
+ // if capability is unfulfilled and not in component, then add it
+ console.log('Capability is unfulfilled', rel.capability.getFullTitle(), rel.capability.leftOccurrences);
+ toComponentInstance.capabilities[rel.capability.type].push(rel.capability);
+ }
+ }
+ if (rel.requirement) {
+ const fromComponentInstance: ComponentInstance = componentInstances.find((inst) => inst.uniqueId === relation.fromNode);
+ const fromComponentInstanceRequirement: Requirement = fromComponentInstance.findRequirement(
+ rel.requirement.capability, rel.requirement.uniqueId, rel.requirement.ownerId, rel.requirement.name);
+ const isRequirementFulfilled: boolean = rel.requirement.isFulfilled();
+ if (isRequirementFulfilled && fromComponentInstanceRequirement) {
+ // if requirement is fulfilled and in component, then remove it
+ console.log('Requirement is fulfilled', rel.requirement.getFullTitle(), rel.requirement.leftOccurrences);
+ fromComponentInstance.requirements[rel.requirement.capability].splice(
+ fromComponentInstance.requirements[rel.requirement.capability].findIndex((req) => req === fromComponentInstanceRequirement), 1
+ )
+ } else if (!isRequirementFulfilled && !fromComponentInstanceRequirement) {
+ // if requirement is unfulfilled and not in component, then add it
+ console.log('Requirement is unfulfilled', rel.requirement.getFullTitle(), rel.requirement.leftOccurrences);
+ fromComponentInstance.requirements[rel.requirement.capability].push(rel.requirement);
+ }
+ }
+ });
+ }
+
+ public getRelationRequirementCapability(relationship: Relationship, sourceNode: ComponentInstance, targetNode: ComponentInstance): Promise<{ requirement: Requirement, capability: Capability }> {
+ // try find the requirement and capability in the source and target component instances:
+ let capability: Capability = targetNode.findCapability(undefined,
+ relationship.relation.capabilityUid,
+ relationship.relation.capabilityOwnerId,
+ relationship.relation.capability);
+ let requirement: Requirement = sourceNode.findRequirement(undefined,
+ relationship.relation.requirementUid,
+ relationship.relation.requirementOwnerId,
+ relationship.relation.requirement);
+
+ return new Promise<{ requirement: Requirement, capability: Capability }>((resolve, reject) => {
+ if (capability && requirement) {
+ resolve({capability, requirement});
+ }
+ else {
+ // if requirement and/or capability is missing, then fetch the full relation with its requirement and capability:
+ this.topologyTemplateService.fetchRelation(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, relationship.relation.id).subscribe((fetchedRelation) => {
+ this.syncComponentByRelation(fetchedRelation);
+ resolve({
+ capability: capability || fetchedRelation.relationships[0].capability,
+ requirement: requirement || fetchedRelation.relationships[0].requirement
+ });
+ }, reject);
+ }
+ });
+ }
+}
+
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-nodes-utils.spec.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-nodes-utils.spec.ts
new file mode 100644
index 0000000000..9dcc47f7cc
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-nodes-utils.spec.ts
@@ -0,0 +1,158 @@
+import { TestBed } from '@angular/core/testing';
+import { SdcUiServices } from 'onap-ui-angular';
+import { Observable } from 'rxjs/Rx';
+import CollectionNodes = Cy.CollectionNodes;
+import { Mock } from 'ts-mockery';
+import { ComponentInstance } from '../../../../../models';
+import { ComponentMetadata } from '../../../../../models/component-metadata';
+import { Resource } from '../../../../../models/components/resource';
+import { CompositionCiNodeCp } from '../../../../../models/graph/nodes/composition-graph-nodes/composition-ci-node-cp';
+import { CompositionCiNodeVl } from '../../../../../models/graph/nodes/composition-graph-nodes/composition-ci-node-vl';
+import { EventListenerService } from '../../../../../services';
+import CollectionEdges = Cy.CollectionEdges;
+import { GRAPH_EVENTS } from '../../../../../utils/constants';
+import { ServiceServiceNg2 } from '../../../../services/component-services/service.service';
+import { TopologyTemplateService } from '../../../../services/component-services/topology-template.service';
+import { ComponentGenericResponse } from '../../../../services/responses/component-generic-response';
+import { QueueServiceUtils } from '../../../../utils/queue-service-utils';
+import { WorkspaceService } from '../../../workspace/workspace.service';
+import { CompositionService } from '../../composition.service';
+import { CommonGraphUtils } from '../common/common-graph-utils';
+import { CompositionGraphGeneralUtils } from './composition-graph-general-utils';
+import { CompositionGraphNodesUtils } from './composition-graph-nodes-utils';
+
+describe('composition graph nodes utils', () => {
+
+ const CP_TO_DELETE_ID = 'cp1';
+ const VL_TO_DELETE_ID = 'vl';
+ const CP2_ID = 'cp2';
+
+ let loaderServiceMock: Partial<SdcUiServices.LoaderService>;
+ let service: CompositionGraphNodesUtils;
+ let topologyServiceMock: TopologyTemplateService;
+ let queueServiceMock: QueueServiceUtils;
+ let workspaceServiceMock: WorkspaceService;
+ let compositionServiceMock: CompositionService;
+ let eventListenerServiceMock: EventListenerService;
+ const cpInstanceMock: ComponentInstance = Mock.of<ComponentInstance>({
+ uniqueId: CP_TO_DELETE_ID,
+ isVl: () => false
+ });
+ const vlInstanceMock: ComponentInstance = Mock.of<ComponentInstance>({
+ uniqueId: VL_TO_DELETE_ID,
+ isVl: () => true
+ });
+ const cp2InstanceMock: ComponentInstance = Mock.of<ComponentInstance>({
+ uniqueId: CP2_ID,
+ isVl: () => false
+ });
+
+ const cyMock = Mock.of<Cy.Instance>({
+ remove: jest.fn(),
+ collection: jest.fn()
+ });
+
+ const serviceServiceMock = Mock.of<ServiceServiceNg2>({
+ getComponentCompositionData : () => Observable.of(Mock.of<ComponentGenericResponse>())
+ });
+
+ // Instances on the graph cp, vl, cp2
+ const cp = Mock.from<CompositionCiNodeCp>({ id: CP_TO_DELETE_ID, componentInstance: cpInstanceMock });
+ const vl = Mock.from<CompositionCiNodeVl>({ id: VL_TO_DELETE_ID, componentInstance: vlInstanceMock });
+ const cp2 = Mock.from<CompositionCiNodeCp>({ id: CP2_ID, componentInstance: cp2InstanceMock });
+
+ beforeEach(() => {
+
+ loaderServiceMock = {
+ activate: jest.fn(),
+ deactivate: jest.fn()
+ };
+
+ topologyServiceMock = Mock.of<TopologyTemplateService>({
+ deleteComponentInstance : () => Observable.of(cpInstanceMock)
+ });
+
+ queueServiceMock = Mock.of<QueueServiceUtils>({
+ addBlockingUIAction : ( (f) => f() )
+ });
+
+ workspaceServiceMock = Mock.of<WorkspaceService>({
+ metadata: Mock.of<ComponentMetadata>( { uniqueId: 'topologyTemplateUniqueId' } )
+ });
+
+ compositionServiceMock = Mock.of<CompositionService>({
+ deleteComponentInstance : jest.fn()
+ });
+
+ eventListenerServiceMock = Mock.of<EventListenerService>({
+ notifyObservers : jest.fn()
+ });
+
+ TestBed.configureTestingModule({
+ imports: [],
+ providers: [
+ CompositionGraphNodesUtils,
+ {provide: WorkspaceService, useValue: workspaceServiceMock},
+ {provide: TopologyTemplateService, useValue: topologyServiceMock},
+ {provide: CompositionService, useValue: compositionServiceMock},
+ {provide: CompositionGraphGeneralUtils, useValue: {}},
+ {provide: CommonGraphUtils, useValue: {}},
+ {provide: EventListenerService, useValue: eventListenerServiceMock},
+ {provide: QueueServiceUtils, useValue: queueServiceMock},
+ {provide: ServiceServiceNg2, useValue: serviceServiceMock},
+ {provide: SdcUiServices.LoaderService, useValue: loaderServiceMock}
+ ]
+ });
+ service = TestBed.get(CompositionGraphNodesUtils);
+ });
+
+ it('When a CP is deleted which is connected to a VL that has another leg to another CP, the VL is deleted as well', () => {
+ // Prepare a VL that is connected to both CP and CP2
+ const vlToDelete = Mock.of<CollectionNodes>({
+ data: () => vl,
+ connectedEdges: () => Mock.of<CollectionEdges>({
+ length: 2,
+ connectedNodes: () => [cp, cp2] as CollectionNodes
+ })
+ });
+
+ // Prepare a CP which is connected to a VL
+ const cpToDelete = Mock.of<CollectionNodes>({
+ data: () => cp,
+ connectedEdges: () => Mock.of<CollectionEdges>({
+ length: 1,
+ connectedNodes: () => [vlToDelete] as CollectionNodes
+ })
+ });
+ service.deleteNode(cyMock, Mock.of<Resource>(), cpToDelete);
+ expect(compositionServiceMock.deleteComponentInstance).toHaveBeenCalledWith(CP_TO_DELETE_ID);
+ expect(eventListenerServiceMock.notifyObservers).toHaveBeenCalledWith(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, VL_TO_DELETE_ID);
+ expect(eventListenerServiceMock.notifyObservers).toHaveBeenLastCalledWith(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE_SUCCESS, CP_TO_DELETE_ID);
+ expect(cyMock.remove).toHaveBeenCalled();
+ });
+
+ it('When a CP is deleted which is solely connected to another VL the VL is not deleted', () => {
+ // Prepare a VL that is connected only to 1 CP
+ const vlToDelete = Mock.of<CollectionNodes>({
+ data: () => vl,
+ connectedEdges: () => Mock.of<CollectionEdges>({
+ length: 1,
+ connectedNodes: () => [cp] as CollectionNodes
+ })
+ });
+
+ // Prepare a CP which is connected to a VL
+ const cpToDelete = Mock.of<CollectionNodes>({
+ data: () => cp,
+ connectedEdges: () => Mock.of<CollectionEdges>({
+ length: 1,
+ connectedNodes: () => [vlToDelete] as CollectionNodes
+ })
+ });
+ service.deleteNode(cyMock, Mock.of<Resource>(), cpToDelete);
+ expect(compositionServiceMock.deleteComponentInstance).toHaveBeenCalledWith(CP_TO_DELETE_ID);
+ expect(eventListenerServiceMock.notifyObservers).toHaveBeenLastCalledWith(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE_SUCCESS, CP_TO_DELETE_ID);
+ expect(eventListenerServiceMock.notifyObservers).toHaveBeenCalledTimes(1);
+ expect(cyMock.remove).toHaveBeenCalled();
+ });
+});
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-nodes-utils.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-nodes-utils.ts
new file mode 100644
index 0000000000..ea876c6d1a
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-nodes-utils.ts
@@ -0,0 +1,202 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import { Injectable } from '@angular/core';
+import { Component as TopologyTemplate } from 'app/models';
+import {
+ ComponentInstance,
+ CompositionCiNodeVl, Service
+} from 'app/models';
+import { CompositionCiServicePathLink } from 'app/models/graph/graph-links/composition-graph-links/composition-ci-service-path-link';
+import { WorkspaceService } from 'app/ng2/pages/workspace/workspace.service';
+import { ServiceServiceNg2 } from 'app/ng2/services/component-services/service.service';
+import { TopologyTemplateService } from 'app/ng2/services/component-services/topology-template.service';
+import { ServiceGenericResponse } from 'app/ng2/services/responses/service-generic-response';
+import { QueueServiceUtils } from 'app/ng2/utils/queue-service-utils';
+import { EventListenerService } from 'app/services';
+import { GRAPH_EVENTS } from 'app/utils';
+import * as _ from 'lodash';
+import { SdcUiServices } from 'onap-ui-angular';
+import { CompositionService } from '../../composition.service';
+import { CommonGraphUtils } from '../common/common-graph-utils';
+import { CompositionGraphGeneralUtils } from './composition-graph-general-utils';
+
+/**
+ * Created by obarda on 11/9/2016.
+ */
+@Injectable()
+export class CompositionGraphNodesUtils {
+ constructor(private generalGraphUtils: CompositionGraphGeneralUtils,
+ private commonGraphUtils: CommonGraphUtils,
+ private eventListenerService: EventListenerService,
+ private queueServiceUtils: QueueServiceUtils,
+ private serviceService: ServiceServiceNg2,
+ private loaderService: SdcUiServices.LoaderService,
+ private compositionService: CompositionService,
+ private topologyTemplateService: TopologyTemplateService,
+ private workspaceService: WorkspaceService) {
+ }
+
+ /**
+ * 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();
+ });
+ }
+
+ public highlightMatchingNodesByName = (cy: Cy.Instance, nameToMatch: string) => {
+
+ cy.batch(() => {
+ cy.nodes("[name !@^= '" + nameToMatch + "']").style({'background-image-opacity': 0.4});
+ cy.nodes("[name @^= '" + nameToMatch + "']").style({'background-image-opacity': 1});
+ });
+
+ }
+
+ // Returns all nodes whose name starts with searchTerm
+ public getMatchingNodesByName = (cy: Cy.Instance, nameToMatch: string): Cy.CollectionNodes => {
+ return cy.nodes("[name @^= '" + nameToMatch + "']");
+ }
+
+ /**
+ * 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: TopologyTemplate, nodeToDelete: Cy.CollectionNodes): void {
+
+ this.loaderService.activate();
+ const onSuccess: (response: ComponentInstance) => void = (response: ComponentInstance) => {
+ // check whether the node is connected to any VLs that only have one other connection. If so, delete that VL as well
+ this.loaderService.deactivate();
+ this.compositionService.deleteComponentInstance(response.uniqueId);
+
+ const nodeToDeleteIsNotVl = nodeToDelete.data().componentInstance && !(nodeToDelete.data().componentInstance.isVl());
+ if (nodeToDeleteIsNotVl) {
+ const connectedVls: Cy.CollectionFirstNode[] = this.getConnectedVlToNode(nodeToDelete);
+ this.handleConnectedVlsToDelete(connectedVls);
+ }
+
+ // check whether there is a service path going through this node, and if so clean it from the graph.
+ const nodeId = nodeToDelete.data().id;
+ const connectedPathLinks = cy.collection(`[type="${CompositionCiServicePathLink.LINK_TYPE}"][source="${nodeId}"], [type="${CompositionCiServicePathLink.LINK_TYPE}"][target="${nodeId}"]`);
+ _.forEach(connectedPathLinks, (link, key) => {
+ cy.remove(`[pathId="${link.data().pathId}"]`);
+ });
+
+ // update service path list
+ this.serviceService.getComponentCompositionData(component).subscribe((serviceResponse: ServiceGenericResponse) => {
+ (component as Service).forwardingPaths = serviceResponse.forwardingPaths;
+ });
+
+ this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE_SUCCESS, nodeId);
+
+ // update UI
+ cy.remove(nodeToDelete);
+ };
+
+ const onFailed: (response: any) => void = (response: any) => {
+ this.loaderService.deactivate();
+ };
+
+ this.queueServiceUtils.addBlockingUIAction(
+ () => {
+ const uniqueId = this.workspaceService.metadata.uniqueId;
+ const componentType = this.workspaceService.metadata.componentType;
+ this.topologyTemplateService.deleteComponentInstance(componentType, uniqueId, nodeToDelete.data().componentInstance.uniqueId).subscribe(onSuccess, onFailed);
+ }
+ );
+ }
+
+ /**
+ * Finds all VLs connected to a single node
+ * @param node
+ * @returns {Array<Cy.CollectionFirstNode>}
+ */
+ public getConnectedVlToNode = (node: Cy.CollectionNodes): Cy.CollectionFirstNode[] => {
+ const connectedVls: Cy.CollectionFirstNode[] = new Array<Cy.CollectionFirstNode>();
+ _.forEach(node.connectedEdges().connectedNodes(), (connectedNode: Cy.CollectionFirstNode) => {
+ const connectedNodeIsVl = connectedNode.data().componentInstance.isVl();
+ if (connectedNodeIsVl) {
+ connectedVls.push(connectedNode);
+ }
+ });
+ return connectedVls;
+ }
+
+ /**
+ * Delete all VLs that have only two connected nodes (this function is called when deleting a node)
+ * @param connectedVls
+ */
+ public handleConnectedVlsToDelete = (connectedVls: 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.uniqueId);
+ }
+ });
+ }
+
+ /**
+ * This function will update nodes position.
+ * @param cy
+ * @param component
+ * @param nodesMoved - the node/multiple nodes now moved by the user
+ */
+ public onNodesPositionChanged = (cy: Cy.Instance, component: TopologyTemplate, nodesMoved: Cy.CollectionNodes): void => {
+
+ if (nodesMoved.length === 0) {
+ return;
+ }
+
+ const isValidMove: boolean = this.generalGraphUtils.isGroupValidDrop(cy, nodesMoved);
+ if (isValidMove) {
+
+ const instancesToUpdate: ComponentInstance[] = new Array<ComponentInstance>();
+
+ _.each(nodesMoved, (node: Cy.CollectionFirstNode) => { // update all nodes new position
+
+ // update position
+ const newPosition: Cy.Position = this.commonGraphUtils.getNodePosition(node);
+ node.data().componentInstance.updatePosition(newPosition.x, newPosition.y);
+ instancesToUpdate.push(node.data().componentInstance);
+
+ });
+
+ if (instancesToUpdate.length > 0) {
+ this.generalGraphUtils.pushMultipleUpdateComponentInstancesRequestToQueue(instancesToUpdate);
+ }
+ } else {
+ // reset nodes position
+ nodesMoved.positions((i, node) => {
+ return {
+ x: +node.data().componentInstance.posX,
+ y: +node.data().componentInstance.posY
+ };
+ });
+ }
+ }
+
+}
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-palette-utils.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-palette-utils.ts
new file mode 100644
index 0000000000..1776c2f9b9
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-palette-utils.ts
@@ -0,0 +1,233 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {Injectable} from "@angular/core";
+import {CompositionGraphGeneralUtils, RequirementAndCapabilities} from "./composition-graph-general-utils";
+import {CommonGraphUtils} from "../common/common-graph-utils";
+import {EventListenerService} from "../../../../../services/event-listener-service";
+import {ResourceNamePipe} from "app/ng2/pipes/resource-name.pipe";
+import {ComponentInstanceFactory} from "app/utils/component-instance-factory";
+import {GRAPH_EVENTS, GraphUIObjects} from "app/utils/constants";
+import {TopologyTemplateService} from "app/ng2/services/component-services/topology-template.service";
+import {DndDropEvent} from "ngx-drag-drop/ngx-drag-drop";
+import {SdcUiServices} from "onap-ui-angular"
+import { Component as TopologyTemplate, NodesFactory, CapabilitiesGroup, RequirementsGroup,
+ CompositionCiNodeBase, ComponentInstance, LeftPaletteComponent, Point } from "app/models";
+import {CompositionService} from "../../composition.service";
+import {WorkspaceService} from "app/ng2/pages/workspace/workspace.service";
+import { QueueServiceUtils } from "app/ng2/utils/queue-service-utils";
+import {ComponentGenericResponse} from "../../../../services/responses/component-generic-response";
+import {MatchCapabilitiesRequirementsUtils} from "./match-capability-requierment-utils";
+import {CompositionGraphNodesUtils} from "./index";
+
+@Injectable()
+export class CompositionGraphPaletteUtils {
+
+ constructor(private generalGraphUtils:CompositionGraphGeneralUtils,
+ private nodesFactory:NodesFactory,
+ private commonGraphUtils:CommonGraphUtils,
+ private queueServiceUtils:QueueServiceUtils,
+ private eventListenerService:EventListenerService,
+ private topologyTemplateService: TopologyTemplateService,
+ private loaderService: SdcUiServices.LoaderService,
+ private compositionService: CompositionService,
+ private workspaceService: WorkspaceService,
+ private matchCapabilitiesRequirementsUtils: MatchCapabilitiesRequirementsUtils,
+ private nodesGraphUtils: CompositionGraphNodesUtils) {
+ }
+
+ /**
+ *
+ * @param Calculate matching nodes, highlight the matching nodes and fade the non matching nodes
+ * @param leftPaletteComponent
+ * @param _cy
+ * @returns void
+ * @private
+ */
+
+ public onComponentHoverIn = (leftPaletteComponent: LeftPaletteComponent, _cy: Cy.Instance) => {
+ const nodesData = this.nodesGraphUtils.getAllNodesData(_cy.nodes());
+ const nodesLinks = this.generalGraphUtils.getAllCompositionCiLinks(_cy);
+
+ if (this.generalGraphUtils.componentRequirementsAndCapabilitiesCaching.containsKey(leftPaletteComponent.uniqueId)) {
+ const reqAndCap: RequirementAndCapabilities = this.generalGraphUtils.componentRequirementsAndCapabilitiesCaching.getValue(leftPaletteComponent.uniqueId);
+ const filteredNodesData = this.matchCapabilitiesRequirementsUtils.findMatchingNodesToComponentInstance(
+ { uniqueId: leftPaletteComponent.uniqueId, requirements: reqAndCap.requirements, capabilities: reqAndCap.capabilities} as ComponentInstance, nodesData, nodesLinks);
+
+ this.matchCapabilitiesRequirementsUtils.highlightMatchingComponents(filteredNodesData, _cy);
+ this.matchCapabilitiesRequirementsUtils.fadeNonMachingComponents(filteredNodesData, nodesData, _cy);
+ } else {
+
+ this.topologyTemplateService.getCapabilitiesAndRequirements(leftPaletteComponent.componentType, leftPaletteComponent.uniqueId).subscribe((response: ComponentGenericResponse) => {
+ let reqAndCap: RequirementAndCapabilities = {
+ capabilities: response.capabilities,
+ requirements: response.requirements
+ }
+ this.generalGraphUtils.componentRequirementsAndCapabilitiesCaching.setValue(leftPaletteComponent.uniqueId, reqAndCap);
+ });
+ }
+ }
+
+ /**
+ * 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:DragEvent, position?:Cy.Position, eventPosition?: Point) {
+ let bbox = <Cy.BoundingBox>{};
+ if (!position) {
+ position = event ? this.commonGraphUtils.getCytoscapeNodePosition(cy, event) : eventPosition;
+ }
+ 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:DragEvent) {
+
+ let componentInstanceToCreate:ComponentInstance = 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.deactivate();
+ };
+
+ //on success - update node data
+ let onSuccessCreatingInstance = (createInstance:ComponentInstance):void => {
+
+ this.loaderService.deactivate();
+ this.compositionService.addComponentInstance(createInstance);
+ createInstance.name = ResourceNamePipe.getDisplayName(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);
+ this.commonGraphUtils.addComponentInstanceNodeToGraph(cy, newNode);
+ this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_CREATE_COMPONENT_INSTANCE);
+ };
+
+ this.queueServiceUtils.addBlockingUIAction(() => {
+ let uniqueId = this.workspaceService.metadata.uniqueId;
+ let componentType = this.workspaceService.metadata.componentType;
+ this.topologyTemplateService.createComponentInstance(componentType, uniqueId, componentInstanceToCreate).subscribe(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) {
+ // let draggedElement = document.getElementById("draggable_element");
+ // // event.dataTransfer.setDragImage(draggableElement, 0, 0);
+ // if (event.clientX < GraphUIObjects.DIAGRAM_PALETTE_WIDTH_OFFSET || event.clientY < GraphUIObjects.DIAGRAM_HEADER_OFFSET) { //hovering over palette. Dont bother computing validity of drop
+ // draggedElement.className = 'invalid-drag';
+ // event.dataTransfer.setDragImage(draggedElement.cloneNode(true), 0, 0);
+ // 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)) {
+ // draggedElement.className = 'valid-drag';
+ // event.dataTransfer.setDragImage(draggedElement.cloneNode(true), 0, 0);
+ // // event.dataTransfer.setDragImage(draggedElement, 0, 0);
+ // // event.dataTransfer.setDragImage(draggedElement, 0, 0);
+ //
+ // } else {
+ // draggedElement.className = 'invalid-drag';
+ // event.dataTransfer.setDragImage(draggedElement.cloneNode(true), 0, 0);
+ // }
+ // }
+
+ public isDragValid(cy:Cy.Instance, position: Point):boolean {
+ if (position.x < GraphUIObjects.DIAGRAM_PALETTE_WIDTH_OFFSET || position.y < GraphUIObjects.DIAGRAM_HEADER_OFFSET) { //hovering over palette. Dont bother computing validity of drop
+ return false;
+ }
+
+ let offsetPosition = {
+ x: position.x - GraphUIObjects.DIAGRAM_PALETTE_WIDTH_OFFSET,
+ y: position.y - GraphUIObjects.DIAGRAM_HEADER_OFFSET
+ };
+ let bbox = this._getNodeBBox(cy, null, offsetPosition, position);
+
+ if (this.generalGraphUtils.isPaletteDropValid(cy, bbox)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ /**
+ * 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 dragEvent
+ * @param component
+ */
+ public addNodeFromPalette(cy:Cy.Instance, dragEvent:DndDropEvent) {
+ this.loaderService.activate();
+
+ let draggedComponent:LeftPaletteComponent = dragEvent.data;
+
+ 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, dragEvent.event);
+
+ } else {
+
+ this.topologyTemplateService.getFullComponent(draggedComponent.componentType, draggedComponent.uniqueId).subscribe((topologyTemplate:TopologyTemplate) => {
+ draggedComponent.capabilities = topologyTemplate.capabilities;
+ draggedComponent.requirements = topologyTemplate.requirements;
+ this._createComponentInstanceOnGraphFromPaletteComponent(cy, draggedComponent, dragEvent.event);
+ });
+ }
+ }
+}
+
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-service-path-utils.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-service-path-utils.ts
new file mode 100644
index 0000000000..bc124fe9d1
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-service-path-utils.ts
@@ -0,0 +1,148 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import * as _ from "lodash";
+import {CompositionGraphGeneralUtils} from "./composition-graph-general-utils";
+import {ServiceServiceNg2} from 'app/ng2/services/component-services/service.service';
+import {Service} from "app/models/components/service";
+import {ForwardingPath} from "app/models/forwarding-path";
+import {ForwardingPathLink} from "app/models/forwarding-path-link";
+import {ComponentRef, Injectable} from "@angular/core";
+import {CompositionCiServicePathLink} from "app/models/graph/graph-links/composition-graph-links/composition-ci-service-path-link";
+import {SdcUiServices} from "onap-ui-angular";
+import {QueueServiceUtils} from "app/ng2/utils/queue-service-utils";
+import {ServicePathsListComponent} from "app/ng2/pages/composition/graph/service-paths-list/service-paths-list.component";
+import {ButtonModel, ModalModel} from "app/models";
+import {ServicePathCreatorComponent} from "app/ng2/pages/composition/graph/service-path-creator/service-path-creator.component";
+import {ModalService} from "app/ng2/services/modal.service";
+import {ModalComponent} from "app/ng2/components/ui/modal/modal.component";
+import {Select, Store} from "@ngxs/store";
+import {WorkspaceState} from "app/ng2/store/states/workspace.state";
+import {WorkspaceService} from "app/ng2/pages/workspace/workspace.service";
+import {CompositionService} from "../../composition.service";
+import {CommonGraphUtils} from "../common/common-graph-utils";
+import {GRAPH_EVENTS} from "app/utils/constants";
+import {EventListenerService} from "app/services/event-listener-service";
+
+@Injectable()
+export class ServicePathGraphUtils {
+
+ constructor(
+ private generalGraphUtils: CompositionGraphGeneralUtils,
+ private serviceService: ServiceServiceNg2,
+ private commonGraphUtils: CommonGraphUtils,
+ private loaderService: SdcUiServices.LoaderService,
+ private queueServiceUtils: QueueServiceUtils,
+ private modalService: ModalService,
+ private workspaceService: WorkspaceService,
+ private compositionService: CompositionService,
+ private store:Store,
+ private eventListenerService: EventListenerService
+ ) {
+ }
+
+ private isViewOnly = (): boolean => {
+ return this.store.selectSnapshot(state => state.workspace.isViewOnly);
+ }
+ private modalInstance: ComponentRef<ModalComponent>;
+
+ public deletePathsFromGraph(cy: Cy.Instance) {
+ cy.remove(`[type="${CompositionCiServicePathLink.LINK_TYPE}"]`);
+ }
+
+ public drawPath(cy: Cy.Instance, forwardingPath: ForwardingPath) {
+ let pathElements = forwardingPath.pathElements.listToscaDataDefinition;
+
+ _.forEach(pathElements, (link: ForwardingPathLink) => {
+ let data: CompositionCiServicePathLink = new CompositionCiServicePathLink(link);
+ data.source = _.find(
+ this.compositionService.componentInstances,
+ instance => instance.name === data.forwardingPathLink.fromNode
+ ).uniqueId;
+ data.target = _.find(
+ this.compositionService.componentInstances,
+ instance => instance.name === data.forwardingPathLink.toNode
+ ).uniqueId;
+ data.pathId = forwardingPath.uniqueId;
+ data.pathName = forwardingPath.name;
+ this.commonGraphUtils.insertServicePathLinkToGraph(cy, data);
+ });
+ }
+
+ public createOrUpdateServicePath = (path: any): void => {
+ this.loaderService.activate();
+
+ let onSuccess: (response: ForwardingPath) => void = (response: ForwardingPath) => {
+ this.loaderService.deactivate();
+ this.compositionService.forwardingPaths[response.uniqueId] = response;
+ this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_SERVICE_PATH_CREATED, response.uniqueId)
+ };
+
+ this.queueServiceUtils.addBlockingUIAction(
+ () => this.serviceService.createOrUpdateServicePath(this.workspaceService.metadata.uniqueId, path).subscribe(onSuccess
+ , (error) => {this.loaderService.deactivate()})
+ );
+ };
+
+ public onCreateServicePath = (): void => {
+ // this.showServicePathMenu = false;
+ let cancelButton: ButtonModel = new ButtonModel('Cancel', 'outline white', this.modalService.closeCurrentModal);
+ let saveButton: ButtonModel = new ButtonModel('Create', 'blue', this.createPath, this.getDisabled);
+ let modalModel: ModalModel = new ModalModel('l', 'Create Service Flow', '', [saveButton, cancelButton], 'standard', true);
+ this.modalInstance = this.modalService.createCustomModal(modalModel);
+ this.modalService.addDynamicContentToModal(this.modalInstance, ServicePathCreatorComponent, {serviceId: this.workspaceService.metadata.uniqueId});
+ this.modalInstance.instance.open();
+ };
+
+ public onListServicePath = (): void => {
+ // this.showServicePathMenu = false;
+ let cancelButton: ButtonModel = new ButtonModel('Close', 'outline white', this.modalService.closeCurrentModal);
+ let modalModel: ModalModel = new ModalModel('md', 'Service Flows List', '', [cancelButton], 'standard', true);
+ this.modalInstance = this.modalService.createCustomModal(modalModel);
+ this.modalService.addDynamicContentToModal(this.modalInstance, ServicePathsListComponent, {
+ serviceId: this.workspaceService.metadata.uniqueId,
+ onCreateServicePath: this.onCreateServicePath,
+ onEditServicePath: this.onEditServicePath,
+ isViewOnly: this.isViewOnly()
+ });
+ this.modalInstance.instance.open();
+ };
+
+ public onEditServicePath = (id: string): void => {
+ let cancelButton: ButtonModel = new ButtonModel('Cancel', 'outline white', this.modalService.closeCurrentModal);
+ let saveButton: ButtonModel = new ButtonModel('Save', 'blue', this.createPath, this.getDisabled);
+ let modalModel: ModalModel = new ModalModel('l', 'Edit Path', '', [saveButton, cancelButton], 'standard', true);
+ this.modalInstance = this.modalService.createCustomModal(modalModel);
+ this.modalService.addDynamicContentToModal(this.modalInstance, ServicePathCreatorComponent, {
+ serviceId: this.workspaceService.metadata.uniqueId,
+ pathId: id
+ });
+ this.modalInstance.instance.open();
+ };
+
+ public getDisabled = (): boolean => {
+ return this.isViewOnly() || !this.modalInstance.instance.dynamicContent.instance.checkFormValidForSubmit();
+ };
+
+ public createPath = (): void => {
+ this.createOrUpdateServicePath(this.modalInstance.instance.dynamicContent.instance.createServicePathData());
+ this.modalService.closeCurrentModal();
+ };
+}
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-zone-utils.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-zone-utils.ts
new file mode 100644
index 0000000000..9e97ec0f00
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-zone-utils.ts
@@ -0,0 +1,204 @@
+import {
+ Point,
+ PolicyInstance,
+ Zone,
+ LeftPaletteMetadataTypes,
+ ZoneInstance,
+ ZoneInstanceType,
+ ZoneInstanceAssignmentType
+} from "app/models";
+import {CanvasHandleTypes} from "app/utils";
+import {Observable} from "rxjs";
+import {GroupInstance} from "app/models/graph/zones/group-instance";
+import {Injectable} from "@angular/core";
+import {DynamicComponentService} from "app/ng2/services/dynamic-component.service";
+import {PoliciesService} from "app/ng2/services/policies.service";
+import {GroupsService} from "app/ng2/services/groups.service";
+import {Store} from "@ngxs/store";
+import {CompositionService} from "../../composition.service";
+import {WorkspaceService} from "app/ng2/pages/workspace/workspace.service";
+import { PaletteAnimationComponent } from "app/ng2/pages/composition/palette/palette-animation/palette-animation.component";
+
+@Injectable()
+export class CompositionGraphZoneUtils {
+
+ constructor(private dynamicComponentService: DynamicComponentService,
+ private policiesService: PoliciesService,
+ private groupsService: GroupsService,
+ private workspaceService: WorkspaceService,
+ private compositionService: CompositionService) {
+ }
+
+
+ public createCompositionZones = (): Array<Zone> => {
+ let zones: Array<Zone> = [];
+
+ zones[ZoneInstanceType.POLICY] = new Zone('Policies', 'P', ZoneInstanceType.POLICY);
+ zones[ZoneInstanceType.GROUP] = new Zone('Groups', 'G', ZoneInstanceType.GROUP);
+
+ return zones;
+ }
+
+ public showZone = (zone: Zone): void => {
+ zone.visible = true;
+ zone.minimized = false;
+ }
+
+ public getZoneTypeForPaletteComponent = (componentCategory: LeftPaletteMetadataTypes) => {
+ if (componentCategory == LeftPaletteMetadataTypes.Group) {
+ return ZoneInstanceType.GROUP;
+ } else if (componentCategory == LeftPaletteMetadataTypes.Policy) {
+ return ZoneInstanceType.POLICY;
+ }
+ };
+
+ public initZoneInstances(zones: Array<Zone>) {
+
+ if (this.compositionService.groupInstances && this.compositionService.groupInstances.length) {
+ this.showZone(zones[ZoneInstanceType.GROUP]);
+ zones[ZoneInstanceType.GROUP].instances = [];
+ _.forEach(this.compositionService.groupInstances, (group: GroupInstance) => {
+ let newInstance = new ZoneInstance(group, this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId);
+ this.addInstanceToZone(zones[ZoneInstanceType.GROUP], newInstance);
+ });
+ }
+
+ if (this.compositionService.policies && this.compositionService.policies.length) {
+ this.showZone(zones[ZoneInstanceType.POLICY]);
+ zones[ZoneInstanceType.POLICY].instances = [];
+ _.forEach(this.compositionService.policies, (policy: PolicyInstance) => {
+ let newInstance = new ZoneInstance(policy, this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId);
+ this.addInstanceToZone(zones[ZoneInstanceType.POLICY], newInstance);
+
+ });
+ }
+ }
+
+ public findAndUpdateZoneInstanceData(zones: Array<Zone>, instanceData: PolicyInstance | GroupInstance) {
+ _.forEach(zones, (zone: Zone) => {
+ _.forEach(zone.instances, (zoneInstance: ZoneInstance) => {
+ if (zoneInstance.instanceData.uniqueId === instanceData.uniqueId) {
+ zoneInstance.updateInstanceData(instanceData);
+ }
+ });
+ });
+ }
+
+ public updateTargetsOrMembersOnCanvasDelete = (canvasNodeID: string, zones: Array<Zone>, type: ZoneInstanceAssignmentType): void => {
+ _.forEach(zones, (zone) => {
+ _.forEach(zone.instances, (zoneInstance: ZoneInstance) => {
+ if (zoneInstance.isAlreadyAssigned(canvasNodeID)) {
+ zoneInstance.addOrRemoveAssignment(canvasNodeID, type);
+ //remove it from our list of BE targets and members as well (so that it will not be sent in future calls to BE).
+ zoneInstance.instanceData.setSavedAssignments(zoneInstance.assignments);
+ }
+ });
+ });
+ };
+
+ public createZoneInstanceFromLeftPalette = (zoneType: ZoneInstanceType, paletteComponentType: string): Observable<ZoneInstance> => {
+
+ if (zoneType === ZoneInstanceType.POLICY) {
+ return this.policiesService.createPolicyInstance(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, paletteComponentType).map(response => {
+ let newInstance = new PolicyInstance(response);
+ this.compositionService.addPolicyInstance(newInstance);
+ return new ZoneInstance(newInstance, this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId);
+ });
+ } else if (zoneType === ZoneInstanceType.GROUP) {
+ return this.groupsService.createGroupInstance(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, paletteComponentType).map(response => {
+ let newInstance = new GroupInstance(response);
+ this.compositionService.addGroupInstance(newInstance);
+ return new ZoneInstance(newInstance, this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId);
+ });
+ }
+ }
+
+ public addInstanceToZone(zone: Zone, instance: ZoneInstance, hide?: boolean) {
+ if (hide) {
+ instance.hidden = true;
+ }
+ zone.instances.push(instance);
+
+ };
+
+ private findZoneCoordinates(zoneType): Point {
+ let point: Point = new Point(0, 0);
+ let zone = angular.element(document.querySelector('.' + zoneType + '-zone'));
+ let wrapperZone = zone.offsetParent();
+ point.x = zone.prop('offsetLeft') + wrapperZone.prop('offsetLeft');
+ point.y = zone.prop('offsetTop') + wrapperZone.prop('offsetTop');
+ return point;
+ }
+
+ public createPaletteToZoneAnimation = (startPoint: Point, zoneType: ZoneInstanceType, newInstance: ZoneInstance) => {
+ let zoneTypeName = ZoneInstanceType[zoneType].toLowerCase();
+ let paletteToZoneAnimation = this.dynamicComponentService.createDynamicComponent(PaletteAnimationComponent);
+ paletteToZoneAnimation.instance.from = startPoint;
+ paletteToZoneAnimation.instance.type = zoneType;
+ paletteToZoneAnimation.instance.to = this.findZoneCoordinates(zoneTypeName);
+ paletteToZoneAnimation.instance.zoneInstance = newInstance;
+ paletteToZoneAnimation.instance.iconName = zoneTypeName;
+ paletteToZoneAnimation.instance.runAnimation();
+ }
+
+ public startCyTagMode = (cy: Cy.Instance) => {
+ cy.autolock(true);
+ cy.nodes().unselectify();
+ cy.emit('tagstart'); //dont need to show handles because they're already visible bcz of hover event
+
+ };
+
+ public endCyTagMode = (cy: Cy.Instance) => {
+ cy.emit('tagend');
+ cy.nodes().selectify();
+ cy.autolock(false);
+ };
+
+ public handleTagClick = (cy: Cy.Instance, zoneInstance: ZoneInstance, nodeId: string) => {
+ zoneInstance.addOrRemoveAssignment(nodeId, ZoneInstanceAssignmentType.COMPONENT_INSTANCES);
+ this.showZoneTagIndicationForNode(nodeId, zoneInstance, cy);
+ };
+
+ public showGroupZoneIndications = (groupInstances: Array<ZoneInstance>, policyInstance: ZoneInstance) => {
+ groupInstances.forEach((groupInstance: ZoneInstance) => {
+ let handle: string = this.getCorrectHandleForNode(groupInstance.instanceData.uniqueId, policyInstance);
+ groupInstance.showHandle(handle);
+ })
+ };
+
+ public hideGroupZoneIndications = (instances: Array<ZoneInstance>) => {
+ instances.forEach((instance) => {
+ instance.hideHandle();
+ })
+ }
+
+ public showZoneTagIndications = (cy: Cy.Instance, zoneInstance: ZoneInstance) => {
+
+ cy.nodes().forEach(node => {
+ let handleType: string = this.getCorrectHandleForNode(node.id(), zoneInstance);
+ cy.emit('showhandle', [node, handleType]);
+ });
+ };
+
+ public showZoneTagIndicationForNode = (nodeId: string, zoneInstance: ZoneInstance, cy: Cy.Instance) => {
+ let node = cy.getElementById(nodeId);
+ let handleType: string = this.getCorrectHandleForNode(nodeId, zoneInstance);
+ cy.emit('showhandle', [node, handleType]);
+ }
+
+ public hideZoneTagIndications = (cy: Cy.Instance) => {
+ cy.emit('hidehandles');
+ };
+
+ public getCorrectHandleForNode = (nodeId: string, zoneInstance: ZoneInstance): string => {
+ if (zoneInstance.isAlreadyAssigned(nodeId)) {
+ if (zoneInstance.type == ZoneInstanceType.POLICY) {
+ return CanvasHandleTypes.TAGGED_POLICY;
+ } else {
+ return CanvasHandleTypes.TAGGED_GROUP;
+ }
+ } else {
+ return CanvasHandleTypes.TAG_AVAILABLE;
+ }
+ };
+}
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/index.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/index.ts
new file mode 100644
index 0000000000..e7f11af248
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/index.ts
@@ -0,0 +1,29 @@
+/**
+ * Created by ob0695 on 6/3/2018.
+ */
+// export * from './composition-graph-general-utils';
+// export * from './composition-graph-links-utils';
+// export * from './composition-graph-nodes-utils';
+// export * from './composition-graph-palette-utils';
+// export * from './composition-graph-service-path-utils';
+// export * from './composition-graph-zone-utils';
+
+
+import {CompositionGraphGeneralUtils} from './composition-graph-general-utils';
+import {CompositionGraphNodesUtils} from './composition-graph-nodes-utils';
+import {MatchCapabilitiesRequirementsUtils} from './match-capability-requierment-utils'
+import {CompositionGraphPaletteUtils} from './composition-graph-palette-utils';
+import {CompositionGraphZoneUtils} from './composition-graph-zone-utils';
+import {ServicePathGraphUtils} from './composition-graph-service-path-utils';
+import {CompositionGraphLinkUtils} from "./composition-graph-links-utils";
+
+
+export {
+ CompositionGraphGeneralUtils,
+ CompositionGraphLinkUtils,
+ CompositionGraphNodesUtils,
+ MatchCapabilitiesRequirementsUtils,
+ CompositionGraphPaletteUtils,
+ CompositionGraphZoneUtils,
+ ServicePathGraphUtils
+}; \ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requierment-utils.spec.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requierment-utils.spec.ts
new file mode 100644
index 0000000000..dbfc3e7219
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requierment-utils.spec.ts
@@ -0,0 +1,342 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { Mock } from 'ts-mockery';
+import {
+ CapabilitiesGroup,
+ Capability, ComponentInstance, CompositionCiLinkBase, CompositionCiNodeBase, CompositionCiNodeCp,
+ CompositionCiNodeVf, CompositionCiNodeVl,
+ Requirement, RequirementsGroup
+} from '../../../../../models';
+import { MatchCapabilitiesRequirementsUtils } from './match-capability-requierment-utils';
+
+describe('match capability requirements utils service ', () => {
+
+ const bindableReq = Mock.of<Requirement>({
+ capability : 'tosca.capabilities.network.Bindable',
+ name: 'virtualBinding',
+ relationship: 'tosca.relationships.network.BindsTo',
+ uniqueId: 'eef99154-8039-4227-ba68-62a32e6b0d98.virtualBinding',
+ ownerId : 'extcp0',
+ ownerName : 's'
+ });
+
+ const virtualLinkReq = Mock.of<Requirement>({
+ capability: 'tosca.capabilities.network.Linkable',
+ name: 'virtualLink',
+ relationship: 'tosca.relationships.network.LinksTo',
+ uniqueId: 'eef99154-8039-4227-ba68-62a32e6b0d98.virtualLink',
+ ownerId : '',
+ ownerName : 's'
+ });
+
+ const storeAttachmentReq = Mock.of<Requirement>({
+ capability: 'tosca.capabilities.Attachment',
+ name: 'local_storage',
+ relationship: 'tosca.relationships.AttachesTo',
+ uniqueId: 'eef99154-8039-4227-ba68-62a32e6b0d98.local_storage',
+ node: 'tosca.nodes.BlockStorage',
+ ownerId : '',
+ ownerName : 's'
+ });
+
+ const vlAttachmentReq = Mock.of<Requirement>({
+ capability: 'tosca.capabilities.Attachment',
+ name: 'local_storage',
+ relationship: 'tosca.relationships.AttachesTo',
+ uniqueId: 'eef99154-8039-4227-ba68-62a32e6b0d98.local_storage',
+ node: 'tosca.nodes.BlockStorage',
+ ownerId : '',
+ ownerName : 's'
+ });
+
+ const extVirtualLinkReq = Mock.of<Requirement>({
+ capability: 'tosca.capabilities.network.Linkable',
+ name: 'external_virtualLink',
+ relationship: 'tosca.relationships.network.LinksTo',
+ uniqueId: 'eef99154-8039-4227-ba68-62a32e6b0d98.external_virtualLink'
+ });
+
+ const dependencyReq = Mock.of<Requirement>({
+ capability: 'tosca.capabilities.Node',
+ name: 'dependency',
+ relationship: 'tosca.relationships.DependsOn',
+ uniqueId: 'eef99154-8039-4227-ba68-62a32e6b0d98.dependency'
+ });
+
+ const featureCap = Mock.of<Capability>({
+ type: 'tosca.capabilities.Node',
+ name: 'feature',
+ uniqueId: 'capability.ddf1301e-866b-4fa3-bc4f-edbd81e532cd.feature',
+ maxOccurrences: 'UNBOUNDED',
+ minOccurrences: '1'
+ });
+
+ const internalConnPointCap = Mock.of<Capability>({
+ type: 'tosca.capabilities.Node',
+ name: 'internal_connectionPoint',
+ capabilitySources : ['org.openecomp.resource.cp.extCP'],
+ uniqueId: 'capability.ddf1301e-866b-4fa3-bc4f-edbd81e532cd.internal_connectionPoint',
+ maxOccurrences: 'UNBOUNDED',
+ minOccurrences: '1'
+ });
+
+ const blockStoreAttachmentCap = Mock.of<Capability>({
+ type: 'tosca.capabilities.Attachment',
+ name: 'attachment',
+ capabilitySources: ['tosca.nodes.BlockStorage'],
+ uniqueId: 'capability.ddf1301e-866b-4fa3-bc4f-edbd81e532cd.attachment',
+ maxOccurrences: 'UNBOUNDED',
+ minOccurrences: '1'
+ });
+
+ const bindingCap = Mock.of<Capability>({
+ type: 'tosca.capabilities.network.Bindable',
+ name: 'binding',
+ capabilitySources: ['tosca.nodes.Compute'],
+ uniqueId: 'capability.ddf1301e-866b-4fa3-bc4f-edbd81e532cd.binding',
+ maxOccurrences: 'UNBOUNDED',
+ minOccurrences: '1',
+ });
+
+ const linkableCap = Mock.of<Capability>({
+ type: 'tosca.capabilities.network.Linkable',
+ capabilitySources: ['org.openecomp.resource.vl.extVL'],
+ uniqueId: 'capability.ddf1301e-866b-4fa3-bc4f-edbd81e532cd.virtual_linkable',
+ maxOccurrences: 'UNBOUNDED',
+ minOccurrences: '1'
+ });
+
+ const nodeCompute = Mock.of<CompositionCiNodeVf>({
+ name: 'Compute 0',
+ componentInstance: Mock.of<ComponentInstance>({
+ componentName: 'Compute',
+ uniqueId : 'compute0',
+ requirements: Mock.of<RequirementsGroup>({
+ 'tosca.capabilities.Node' : [ dependencyReq ],
+ 'tosca.capabilities.Attachment' : [ storeAttachmentReq ]
+ }),
+ capabilities: Mock.of<CapabilitiesGroup>({
+ 'tosca.capabilities.network.Bindable' : [ bindingCap ],
+ 'tosca.capabilities.Node' : [ featureCap ]
+ })
+ })
+ });
+
+ const nodeBlockStorage = Mock.of<CompositionCiNodeVf>({
+ name: 'BlockStorage 0',
+ componentInstance: Mock.of<ComponentInstance>({
+ componentName: 'BlockStorage',
+ uniqueId : 'blockstorage0',
+ requirements: Mock.of<RequirementsGroup>({
+ 'tosca.capabilities.Node' : [ dependencyReq ]
+ }),
+ capabilities: Mock.of<CapabilitiesGroup>({
+ 'tosca.capabilities.Attachment' : [ blockStoreAttachmentCap ],
+ 'tosca.capabilities.Node' : [ featureCap ]
+ })
+ })
+ });
+
+ const nodeVl = Mock.of<CompositionCiNodeVl>({
+ name: 'ExtVL 0',
+ componentInstance: Mock.of<ComponentInstance>({
+ componentName: 'BlockStorage',
+ uniqueId : 'extvl0',
+ requirements: Mock.of<RequirementsGroup>({
+ 'tosca.capabilities.Node' : [ dependencyReq ]
+ }),
+ capabilities: Mock.of<CapabilitiesGroup>({
+ 'tosca.capabilities.network.Linkable' : [ linkableCap ],
+ 'tosca.capabilities.Node' : [ featureCap ]
+ })
+ })
+ });
+
+ const nodeCp = Mock.of<CompositionCiNodeCp>({
+ name: 'ExtCP 0',
+ componentInstance: Mock.of<ComponentInstance>({
+ componentName: 'ExtCP',
+ uniqueId : 'extcp0',
+ requirements: Mock.of<RequirementsGroup>({
+ 'tosca.capabilities.network.Linkable' : [ virtualLinkReq ],
+ 'tosca.capabilities.network.Bindable' : [ bindableReq ]
+ }),
+ capabilities: Mock.of<CapabilitiesGroup>({
+ 'tosca.capabilities.Node' : [ featureCap ]
+ })
+ })
+ });
+
+ let service: MatchCapabilitiesRequirementsUtils;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [],
+ providers: [MatchCapabilitiesRequirementsUtils]
+ });
+
+ service = TestBed.get(MatchCapabilitiesRequirementsUtils);
+ });
+
+ it('match capability requirements utils should be defined', () => {
+ console.log(JSON.stringify(service));
+ expect(service).toBeDefined();
+ });
+
+ describe('isMatch function ', () => {
+
+ it('capability type not equal to requirement capability, match is false', () => {
+ const requirement = Mock.of<Requirement>({capability: 'tosca.capabilities.network.Linkable11'});
+ const capability = Mock.of<Capability>({type: 'tosca.capabilities.network.Linkable'});
+ expect(service.isMatch(requirement, capability)).toBeFalsy();
+ });
+
+ it('capability type equal to requirement capability and requirement node not exist, match is true', () => {
+ const requirement = Mock.of<Requirement>({capability: 'tosca.capabilities.network.Linkable'});
+ const capability = Mock.of<Capability>({type: 'tosca.capabilities.network.Linkable'});
+ expect(service.isMatch(requirement, capability)).toBeTruthy();
+ });
+
+ it('is match - capability type equal to requirement capability and requirement node exist and includes in capability sources, match is true', () => {
+ const requirement = Mock.of<Requirement>({capability: 'tosca.capabilities.network.Linkable', node: 'node1'});
+ const capability = Mock.of<Capability>({
+ type: 'tosca.capabilities.network.Linkable',
+ capabilitySources: ['node1', 'node2', 'node3']
+ });
+ expect(service.isMatch(requirement, capability)).toBeTruthy();
+ });
+
+ it('no match - capability type equal to requirement capability and requirement node but not includes in capability sources, match is false', () => {
+ const requirement = Mock.of<Requirement>({capability: 'tosca.capabilities.network.Linkable', node: 'node4'});
+ const capability = Mock.of<Capability>({
+ type: 'tosca.capabilities.network.Linkable',
+ capabilitySources: ['node1', 'node2', 'node3']
+ });
+ expect(service.isMatch(requirement, capability)).toBeFalsy();
+ });
+ });
+
+ describe('hasUnfulfilledRequirementContainingMatch function ', () => {
+
+ it('node have no componentInstance, return false', () => {
+ const node = Mock.of<CompositionCiNodeVf>({componentInstance: undefined});
+ expect(service.hasUnfulfilledRequirementContainingMatch(node, [], {}, [])).toBeFalsy();
+ });
+
+ it('node have componentInstance data but no unfulfilled requirements, return false', () => {
+ const node = Mock.of<CompositionCiNodeVf>({componentInstance: Mock.of<ComponentInstance>()});
+ jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([]);
+ expect(service.hasUnfulfilledRequirementContainingMatch(node, [], {}, [])).toBeFalsy();
+ });
+
+ it('node have componentInstance data and unfulfilled requirements but no match found, return false', () => {
+ const node = Mock.of<CompositionCiNodeVf>({componentInstance: Mock.of<ComponentInstance>()});
+ jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([Mock.of<Requirement>(), Mock.of<Requirement>()]);
+ jest.spyOn(service, 'containsMatch').mockReturnValue(false);
+ expect(service.hasUnfulfilledRequirementContainingMatch(node, [], {}, [])).toBeFalsy();
+ });
+
+ it('node have componentInstance data with unfulfilled requirements and match found, return true', () => {
+ const node = Mock.of<CompositionCiNodeVf>({componentInstance: Mock.of<ComponentInstance>()});
+ jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([Mock.of<Requirement>(), Mock.of<Requirement>()]);
+ jest.spyOn(service, 'containsMatch').mockReturnValue(true);
+ expect(service.hasUnfulfilledRequirementContainingMatch(node, [], {}, [])).toBeTruthy();
+ });
+ });
+
+ describe('getMatches function ', () => {
+ let fromId: string;
+ let toId: string;
+
+ beforeEach(() => {
+ fromId = 'from_id';
+ toId = 'to_id';
+ });
+
+ it('node have no unfulfilled requirements, return empty match array', () => {
+ jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([]);
+ expect(service.getMatches({}, {}, [], fromId, toId, true)).toHaveLength(0);
+ });
+
+ it('node have unfulfilled requirements but no capabilities, return empty match array', () => {
+ jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([Mock.of<Requirement>(), Mock.of<Requirement>()]);
+ expect(service.getMatches({}, {}, [], fromId, toId, true)).toHaveLength(0);
+ });
+
+ it('node have unfulfilled requirements and capabilities but no match found, return empty match array', () => {
+ jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([Mock.of<Requirement>(), Mock.of<Requirement>()]);
+ jest.spyOn(service, 'isMatch').mockReturnValue(false);
+ expect(service.getMatches({}, {}, [], fromId, toId, true)).toHaveLength(0);
+ });
+
+ it('node have 2 unfulfilled requirements and 2 capabilities and match found, return 4 matches', () => {
+ jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([Mock.of<Requirement>(), Mock.of<Requirement>()]);
+ const capabilities = {aaa: Mock.of<Capability>(), bbb: Mock.of<Capability>()};
+ jest.spyOn(service, 'isMatch').mockReturnValue(true);
+ expect(service.getMatches({}, capabilities, [], fromId, toId, true)).toHaveLength(4);
+ });
+ });
+
+ describe('Find matching nodes ===>', () => {
+
+ it('should find matching nodes with component instance', () => {
+ const nodes = [ nodeBlockStorage, nodeCompute, nodeVl ];
+ let matchingNodes: any;
+
+ // Compute can connect to Block Store
+ matchingNodes = service.findMatchingNodesToComponentInstance(nodeCompute.componentInstance, nodes, []);
+ expect(matchingNodes).toHaveLength(1);
+ expect(matchingNodes).toContain(nodeBlockStorage);
+
+ // Block Storage can connect to Compute
+ matchingNodes = service.findMatchingNodesToComponentInstance(nodeBlockStorage.componentInstance, nodes, []);
+ expect(matchingNodes).toHaveLength(1);
+ expect(matchingNodes).toContain(nodeCompute);
+
+ // Vl has no matches
+ matchingNodes = service.findMatchingNodesToComponentInstance(nodeVl.componentInstance, nodes, []);
+ expect(matchingNodes).toHaveLength(0);
+
+ // CP should be able to connect to VL and Compute
+ matchingNodes = service.findMatchingNodesToComponentInstance(nodeCp.componentInstance, nodes, []);
+ expect(matchingNodes).toHaveLength(2);
+ expect(matchingNodes).toContain(nodeCompute);
+ expect(matchingNodes).toContain(nodeVl);
+ });
+
+ it('try with empty list of nodes', () => {
+ const nodes = [ ];
+ let matchingNodes: any;
+
+ // Compute can connect to Block Store
+ matchingNodes = service.findMatchingNodesToComponentInstance(nodeCompute.componentInstance, nodes, []);
+ expect(matchingNodes).toHaveLength(0);
+ });
+
+ it('should detect fulfilled connection with compute node', () => {
+ const nodes = [ nodeBlockStorage, nodeCompute, nodeVl ];
+ let matchingNodes: any;
+ const link = {
+ relation: {
+ fromNode: 'extcp0',
+ toNode: 'compute0',
+ relationships: [{
+ relation: {
+ requirementOwnerId: 'extcp0',
+ requirement: 'virtualBinding',
+ relationship: {
+ type: 'tosca.relationships.network.BindsTo'
+ }
+
+ }
+ }]
+ }
+ };
+
+ const links = [link];
+ // CP should be able to connect to VL only since it already has a link with compute
+ matchingNodes = service.findMatchingNodesToComponentInstance(nodeCp.componentInstance, nodes, links as CompositionCiLinkBase[]);
+ expect(matchingNodes).toHaveLength(1);
+ expect(matchingNodes).toContain(nodeVl);
+ });
+ });
+});
diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requierment-utils.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requierment-utils.ts
new file mode 100644
index 0000000000..c3a1286a97
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requierment-utils.ts
@@ -0,0 +1,196 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+import { Injectable } from '@angular/core';
+import {
+ CapabilitiesGroup, Capability, ComponentInstance, CompositionCiLinkBase,
+ CompositionCiNodeBase, Match, Requirement, RequirementsGroup
+} from 'app/models';
+import * as _ from 'lodash';
+
+/**
+ * Created by obarda on 1/1/2017.
+ */
+@Injectable()
+export class MatchCapabilitiesRequirementsUtils {
+
+ /**
+ * Shows + icon in corner of each node passed in
+ * @param filteredNodesData
+ * @param cy
+ */
+ public highlightMatchingComponents(filteredNodesData, cy: Cy.Instance) {
+ _.each(filteredNodesData, (data: any) => {
+ const 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?) {
+ const 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});
+ });
+ }
+
+ public getMatchedRequirementsCapabilities(fromComponentInstance: ComponentInstance,
+ toComponentInstance: ComponentInstance,
+ links: CompositionCiLinkBase[]): Match[] {
+ const fromToMatches: Match[] = this.getMatches(fromComponentInstance.requirements,
+ toComponentInstance.capabilities,
+ links,
+ fromComponentInstance.uniqueId,
+ toComponentInstance.uniqueId, true);
+ const toFromMatches: Match[] = this.getMatches(toComponentInstance.requirements,
+ fromComponentInstance.capabilities,
+ links,
+ toComponentInstance.uniqueId,
+ fromComponentInstance.uniqueId, false);
+
+ return fromToMatches.concat(toFromMatches);
+ }
+
+ /***** REFACTORED FUNCTIONS START HERE *****/
+
+ public getMatches(requirements: RequirementsGroup, capabilities: CapabilitiesGroup, links: CompositionCiLinkBase[],
+ fromId: string, toId: string, isFromTo: boolean): Match[] {
+ const matches: Match[] = [];
+ const unfulfilledReqs = this.getUnfulfilledRequirements(fromId, requirements, links);
+ _.forEach(unfulfilledReqs, (req) => {
+ _.forEach(_.flatten(_.values(capabilities)), (capability: Capability) => {
+ if (this.isMatch(req, capability)) {
+ if (isFromTo) {
+ matches.push(new Match(req, capability, isFromTo, fromId, toId));
+ } else {
+ matches.push(new Match(req, capability, isFromTo, toId, fromId));
+ }
+ }
+ });
+ });
+ return matches;
+ }
+
+ public getUnfulfilledRequirements = (fromNodeId: string, requirements: RequirementsGroup, links: CompositionCiLinkBase[]): Requirement[] => {
+ const requirementArray: Requirement[] = [];
+ _.forEach(_.flatten(_.values(requirements)), (requirement: Requirement) => {
+ const reqFulfilled = this.isRequirementFulfilled(fromNodeId, requirement, links);
+ if (requirement.name !== 'dependency' && requirement.parentName !== 'dependency' && !reqFulfilled) {
+ requirementArray.push(requirement);
+ }
+ });
+ return requirementArray;
+ }
+
+ /**
+ * Returns true if there is a match between the capabilities and requirements that are passed in
+ * @param requirements
+ * @param capabilities
+ * @returns {boolean}
+ */
+ public containsMatch = (requirements: Requirement[], capabilities: CapabilitiesGroup): boolean => {
+ return _.some(requirements, (req: Requirement) => {
+ return _.some(_.flatten(_.values(capabilities)), (capability: Capability) => {
+ return this.isMatch(req, capability);
+ });
+ });
+ }
+
+ public hasUnfulfilledRequirementContainingMatch = (node: CompositionCiNodeBase, componentRequirements: Requirement[], capabilities: CapabilitiesGroup, links: CompositionCiLinkBase[]) => {
+ if (node && node.componentInstance) {
+ // Check if node has unfulfilled requirement that can be filled by component (#2)
+ const nodeRequirements: Requirement[] = this.getUnfulfilledRequirements(node.componentInstance.uniqueId, node.componentInstance.requirements, links);
+ if (!nodeRequirements.length) {
+ return false;
+ }
+ if (this.containsMatch(nodeRequirements, capabilities)) {
+ return true;
+ }
+ }
+ }
+
+ /**
+ * Returns array of nodes that can connect to the component.
+ * In order to connect, one of the following conditions must be met:
+ * 1. component has an unfulfilled requirement that matches a node's capabilities
+ * 2. node has an unfulfilled requirement that matches the component's capabilities
+ * 3. vl is passed in which has the capability to fulfill requirement from component and requirement on node.
+ */
+ public findMatchingNodesToComponentInstance(componentInstance: ComponentInstance, nodeDataArray: CompositionCiNodeBase[], links: CompositionCiLinkBase[]): any[] {
+ return _.filter(nodeDataArray, (node: CompositionCiNodeBase) => {
+ const matchedRequirementsCapabilities = this.getMatchedRequirementsCapabilities(node.componentInstance, componentInstance, links);
+ return matchedRequirementsCapabilities && matchedRequirementsCapabilities.length > 0;
+ });
+ }
+
+ public 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 isRequirementFulfilled(fromNodeId: string, requirement: any, links: CompositionCiLinkBase[]): boolean {
+ return _.some(links, {
+ relation: {
+ fromNode: fromNodeId,
+ relationships: [{
+ relation: {
+ requirementOwnerId: requirement.ownerId,
+ requirement: requirement.name,
+ relationship: {
+ type: requirement.relationship
+ }
+
+ }
+ }]
+ }
+ });
+ }
+
+}