From: vasraz Date: Thu, 24 Mar 2022 18:31:14 +0000 (+0000) Subject: Implement adding Interface to VFC X-Git-Tag: 1.11.0~1 X-Git-Url: https://gerrit.onap.org/r/gitweb?a=commitdiff_plain;h=4aff8f5eafb6fbd6cc2c764fa1a5a676fa05c89c;p=sdc.git Implement adding Interface to VFC Change-Id: I7cd8b82c306294d897d37d486aa3eeff7ca4206d Signed-off-by: Vasyl Razinkov Issue-ID: SDC-3893 Signed-off-by: andre.schmid --- diff --git a/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ComponentInterfaceOperationBusinessLogic.java b/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ComponentInterfaceOperationBusinessLogic.java index 57571c0b49..4e44967dcb 100644 --- a/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ComponentInterfaceOperationBusinessLogic.java +++ b/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ComponentInterfaceOperationBusinessLogic.java @@ -26,6 +26,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.UUID; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.openecomp.sdc.be.components.impl.exceptions.BusinessLogicException; @@ -209,8 +210,7 @@ public class ComponentInterfaceOperationBusinessLogic extends BaseBusinessLogic lockComponent(componentId, component, "Update Interface Operation on Component instance"); wasLocked = true; } - final StorageOperationStatus status = - toscaOperationFacade.updateComponentInterfaces(component.getUniqueId(), component.getInterfaces(), interfaceDefinitionType); + final StorageOperationStatus status = toscaOperationFacade.updateComponentInterfaces(component, interfaceDefinitionType); if (status != StorageOperationStatus.OK) { janusGraphDao.rollback(); responseFormat = componentsUtils.getResponseFormat(ActionStatus.GENERAL_ERROR); @@ -233,6 +233,84 @@ public class ComponentInterfaceOperationBusinessLogic extends BaseBusinessLogic return Optional.of(component); } + public Optional createInterfaceOperationInResource(final String componentId, final InterfaceDefinition interfaceDefinition, + final ComponentTypeEnum componentTypeEnum, + final Wrapper errorWrapper, final boolean shouldLock) + throws BusinessLogicException { + final Component component = getComponent(componentId); + ResponseFormat responseFormat; + final String componentInterfaceUpdatedKey = interfaceDefinition.getType(); + + Map componentInterfaceMap = component.getInterfaces(); + if (MapUtils.isEmpty(componentInterfaceMap)) { + componentInterfaceMap = new HashMap<>(); + component.setInterfaces(componentInterfaceMap); + } + + interfaceDefinition.setUniqueId(componentInterfaceUpdatedKey); + interfaceDefinition.setToscaResourceName(componentInterfaceUpdatedKey); + + final Optional optionalOperationDataDefinition = interfaceDefinition.getOperations().values().stream().findFirst(); + if (optionalOperationDataDefinition.isEmpty()) { + responseFormat = componentsUtils.getResponseFormat(ActionStatus.INTERFACE_OPERATION_NOT_FOUND); + LOGGER.debug("Failed to found interface operation on component instance with id {}, error: {}", componentId, responseFormat); + errorWrapper.setInnerElement(responseFormat); + return Optional.empty(); + } + + final OperationDataDefinition updatedOperationDataDefinition = optionalOperationDataDefinition.get(); + updatedOperationDataDefinition.setUniqueId(UUID.randomUUID().toString()); + + final InterfaceDefinition interfaceDefinitionFound = componentInterfaceMap.get(componentInterfaceUpdatedKey); + if (interfaceDefinitionFound != null) { + final Map operationsFromComponent = interfaceDefinitionFound.getOperations(); + final String updatedOperationDataDefinitionName = updatedOperationDataDefinition.getName(); + final boolean find = operationsFromComponent.containsKey(updatedOperationDataDefinitionName); + if (find) { + responseFormat = componentsUtils.getResponseFormat(ActionStatus.INTERFACE_OPERATION_NAME_ALREADY_IN_USE, + updatedOperationDataDefinitionName); + LOGGER.error("Operation '{}' for Interface '{}' already exist, error: '{}'", updatedOperationDataDefinitionName, + componentInterfaceUpdatedKey, responseFormat); + errorWrapper.setInnerElement(responseFormat); + return Optional.empty(); + } else { + operationsFromComponent.put(updatedOperationDataDefinitionName, updatedOperationDataDefinition); + interfaceDefinition.setOperations(operationsFromComponent); + } + } + + componentInterfaceMap.put(componentInterfaceUpdatedKey, interfaceDefinition); + + boolean wasLocked = false; + try { + if (shouldLock) { + lockComponent(componentId, component, "Update Interface Operation on Component instance"); + wasLocked = true; + } + final Either operationStatusEither = + toscaOperationFacade.addInterfaceToComponent(componentInterfaceUpdatedKey, interfaceDefinition, component); + if (operationStatusEither.isRight()) { + janusGraphDao.rollback(); + responseFormat = componentsUtils.getResponseFormat(ActionStatus.GENERAL_ERROR); + LOGGER.error("Exception occurred when updating Component Instance Interfaces {}", responseFormat); + errorWrapper.setInnerElement(responseFormat); + return Optional.empty(); + } + janusGraphDao.commit(); + } catch (final Exception e) { + janusGraphDao.rollback(); + LOGGER.error("Exception occurred when updating Interface Operation on Component Instance: ", e); + responseFormat = componentsUtils.getResponseFormat(ActionStatus.GENERAL_ERROR); + errorWrapper.setInnerElement(responseFormat); + throw new BusinessLogicException(responseFormat); + } finally { + if (wasLocked) { + unlockComponent(component.getUniqueId(), componentTypeEnum); + } + } + return Optional.of(component); + } + public User validateUser(final String userId) { final User user = userValidations.validateUserExists(userId); userValidations.validateUserRole(user, Arrays.asList(Role.DESIGNER, Role.ADMIN)); diff --git a/catalog-be/src/main/java/org/openecomp/sdc/be/servlets/ComponentInterfaceOperationServlet.java b/catalog-be/src/main/java/org/openecomp/sdc/be/servlets/ComponentInterfaceOperationServlet.java index 83ee15240f..3b8bfe5bd7 100644 --- a/catalog-be/src/main/java/org/openecomp/sdc/be/servlets/ComponentInterfaceOperationServlet.java +++ b/catalog-be/src/main/java/org/openecomp/sdc/be/servlets/ComponentInterfaceOperationServlet.java @@ -36,6 +36,7 @@ import java.util.Optional; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @@ -138,9 +139,8 @@ public class ComponentInterfaceOperationServlet extends AbstractValidationsServl } final Wrapper errorWrapper = new Wrapper<>(); try { - final Optional actionResponse = componentInterfaceOperationBusinessLogic - .updateComponentInstanceInterfaceOperation(componentId, componentInstanceId, mappedInterfaceOperationData.get(), componentTypeEnum, - errorWrapper, true); + final Optional actionResponse = componentInterfaceOperationBusinessLogic.updateComponentInstanceInterfaceOperation( + componentId, componentInstanceId, mappedInterfaceOperationData.get(), componentTypeEnum, errorWrapper, true); if (actionResponse.isEmpty()) { LOGGER.error(FAILED_TO_UPDATE_INTERFACE_OPERATION, componentInstanceId); return buildErrorResponse(errorWrapper.getInnerElement()); @@ -186,7 +186,60 @@ public class ComponentInterfaceOperationServlet extends AbstractValidationsServl final Wrapper errorWrapper = new Wrapper<>(); try { final Optional actionResponse = componentInterfaceOperationBusinessLogic - .updateResourceInterfaceOperation(componentId, mappedInterfaceOperationData.get(), componentTypeEnum, errorWrapper, true); + .updateResourceInterfaceOperation(componentId, mappedInterfaceOperationData.get(), componentTypeEnum, + errorWrapper, true); + if (actionResponse.isEmpty()) { + LOGGER.error(FAILED_TO_UPDATE_INTERFACE_OPERATION, componentId); + return buildErrorResponse(errorWrapper.getInnerElement()); + } else { + LOGGER.debug(INTERFACE_OPERATION_SUCCESSFULLY_UPDATED, componentId); + return buildOkResponse(getComponentsUtils().getResponseFormat(ActionStatus.CREATED), actionResponse.get()); + } + } catch (final Exception e) { + BeEcompErrorManager.getInstance().logBeRestApiGeneralError(UPDATE_INTERFACE_OPERATION); + LOGGER.error(FAILED_TO_UPDATE_INTERFACE_OPERATION_WITH_ERROR, e); + return buildErrorResponse(getComponentsUtils().getResponseFormat(ActionStatus.GENERAL_ERROR)); + } + } + + @POST + @Path("/{componentType}/{componentId}/resource/interfaceOperation") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Operation(description = "Create Interface Operation", method = "POST", summary = "Create Interface Operation on ComponentInstance", responses = { + @ApiResponse(content = @Content(array = @ArraySchema(schema = @Schema(implementation = Response.class)))), + @ApiResponse(responseCode = "201", description = "Create Interface Operation"), + @ApiResponse(responseCode = "403", description = "Restricted operation"), + @ApiResponse(responseCode = "400", description = "Invalid content / Missing content")}) + @PermissionAllowed(AafPermission.PermNames.INTERNAL_ALL_VALUE) + public Response createInterfaceOperationInResource( + @Parameter(description = "valid values: resources", schema = @Schema(allowableValues = {ComponentTypeEnum.RESOURCE_PARAM_NAME})) + @PathParam("componentType") final String componentType, + @Parameter(description = "Component Id") @PathParam("componentId") String componentId, + @Context final HttpServletRequest request, @HeaderParam(value = Constants.USER_ID_HEADER) String userId) throws IOException { + LOGGER.debug(START_HANDLE_REQUEST_OF, request.getMethod(), request.getRequestURI()); + LOGGER.debug(MODIFIER_ID_IS, userId); + final User userModifier = componentInterfaceOperationBusinessLogic.validateUser(userId); + final ComponentTypeEnum componentTypeEnum = ComponentTypeEnum.findByParamName(componentType); + if (componentTypeEnum == null) { + LOGGER.debug(UNSUPPORTED_COMPONENT_TYPE, componentType); + return buildErrorResponse(getComponentsUtils().getResponseFormat(ActionStatus.UNSUPPORTED_ERROR, componentType)); + } + final byte[] bytes = IOUtils.toByteArray(request.getInputStream()); + if (bytes == null || bytes.length == 0) { + LOGGER.error(INTERFACE_OPERATION_CONTENT_INVALID); + return buildErrorResponse(getComponentsUtils().getResponseFormat(ActionStatus.INVALID_CONTENT)); + } + final String data = new String(bytes); + final Optional mappedInterfaceOperationData = getMappedInterfaceData(data, userModifier, componentTypeEnum); + if (mappedInterfaceOperationData.isEmpty()) { + LOGGER.error(INTERFACE_OPERATION_CONTENT_INVALID, data); + return buildErrorResponse(getComponentsUtils().getResponseFormat(ActionStatus.INVALID_CONTENT)); + } + final Wrapper errorWrapper = new Wrapper<>(); + try { + final Optional actionResponse = componentInterfaceOperationBusinessLogic.createInterfaceOperationInResource( + componentId, mappedInterfaceOperationData.get(), componentTypeEnum, errorWrapper, true); if (actionResponse.isEmpty()) { LOGGER.error(FAILED_TO_UPDATE_INTERFACE_OPERATION, componentId); return buildErrorResponse(errorWrapper.getInnerElement()); diff --git a/catalog-be/src/main/java/org/openecomp/sdc/be/servlets/ComponentNodeFilterServlet.java b/catalog-be/src/main/java/org/openecomp/sdc/be/servlets/ComponentNodeFilterServlet.java index 77b0998e53..c66bb8a8ec 100644 --- a/catalog-be/src/main/java/org/openecomp/sdc/be/servlets/ComponentNodeFilterServlet.java +++ b/catalog-be/src/main/java/org/openecomp/sdc/be/servlets/ComponentNodeFilterServlet.java @@ -64,7 +64,7 @@ import org.openecomp.sdc.common.api.Constants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -@Path("/v1/catalog/{componentType}/{componentId}/componentInstance/{componentInstanceId}/{constraintType}") +@Path("/v1/catalog") @Tags({@Tag(name = "SDCE-2 APIs")}) @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @@ -99,7 +99,7 @@ public class ComponentNodeFilterServlet extends AbstractValidationsServlet { @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) - @Path("/nodeFilter") + @Path("/{componentType}/{componentId}/componentInstance/{componentInstanceId}/{constraintType}/nodeFilter") @Operation(description = "Add Component Filter Constraint", method = "POST", summary = "Add Component Filter Constraint", responses = { @ApiResponse(content = @Content(array = @ArraySchema(schema = @Schema(implementation = Response.class)))), @ApiResponse(responseCode = "201", description = "Create Component Filter"), @@ -154,7 +154,7 @@ public class ComponentNodeFilterServlet extends AbstractValidationsServlet { @PUT @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) - @Path("/{constraintIndex}/nodeFilter") + @Path("/{componentType}/{componentId}/componentInstance/{componentInstanceId}/{constraintType}/{constraintIndex}/nodeFilter") @Operation(description = "Update Component Filter Constraint", method = "PUT", summary = "Update Component Filter Constraint", responses = { @ApiResponse(content = @Content(array = @ArraySchema(schema = @Schema(implementation = Response.class)))), @ApiResponse(responseCode = "201", description = "Create Component Filter"), @@ -208,7 +208,7 @@ public class ComponentNodeFilterServlet extends AbstractValidationsServlet { @DELETE @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) - @Path("{constraintIndex}/nodeFilter") + @Path("/{componentType}/{componentId}/componentInstance/{componentInstanceId}/{constraintType}/{constraintIndex}/nodeFilter") @Operation(description = "Delete Component Filter Constraint", method = "Delete", summary = "Delete Component Filter Constraint", responses = { @ApiResponse(content = @Content(array = @ArraySchema(schema = @Schema(implementation = Response.class)))), @ApiResponse(responseCode = "201", description = "Delete Component Filter Constraint"), diff --git a/catalog-model/src/main/java/org/openecomp/sdc/be/model/jsonjanusgraph/operations/ToscaOperationFacade.java b/catalog-model/src/main/java/org/openecomp/sdc/be/model/jsonjanusgraph/operations/ToscaOperationFacade.java index c07523d3cc..496fd0fe08 100644 --- a/catalog-model/src/main/java/org/openecomp/sdc/be/model/jsonjanusgraph/operations/ToscaOperationFacade.java +++ b/catalog-model/src/main/java/org/openecomp/sdc/be/model/jsonjanusgraph/operations/ToscaOperationFacade.java @@ -43,9 +43,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.BiPredicate; -import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.stream.StreamSupport; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; @@ -55,9 +53,9 @@ import org.apache.tinkerpop.gremlin.structure.Edge; import org.janusgraph.graphdb.query.JanusGraphPredicate; import org.openecomp.sdc.be.config.Configuration; import org.openecomp.sdc.be.config.ConfigurationManager; +import org.openecomp.sdc.be.dao.janusgraph.HealingJanusGraphDao; import org.openecomp.sdc.be.dao.janusgraph.JanusGraphOperationStatus; import org.openecomp.sdc.be.dao.jsongraph.GraphVertex; -import org.openecomp.sdc.be.dao.janusgraph.HealingJanusGraphDao; import org.openecomp.sdc.be.dao.jsongraph.types.EdgeLabelEnum; import org.openecomp.sdc.be.dao.jsongraph.types.JsonParseFlagEnum; import org.openecomp.sdc.be.dao.jsongraph.types.VertexTypeEnum; @@ -487,7 +485,8 @@ public class ToscaOperationFacade { }); } - public Either getByToscaResourceNameAndVersion(final String toscaResourceName, final String version, final String model) { + public Either getByToscaResourceNameAndVersion(final String toscaResourceName, + final String version, final String model) { Either result; Map hasProperties = new EnumMap<>(GraphPropertyEnum.class); @@ -904,8 +903,9 @@ public class ToscaOperationFacade { } public Either getComponentByNameAndVendorRelease(final ComponentTypeEnum componentType, - final String name, final String vendorRelease, - final JsonParseFlagEnum parseFlag, final String modelName) { + final String name, final String vendorRelease, + final JsonParseFlagEnum parseFlag, + final String modelName) { Map hasProperties = new EnumMap<>(GraphPropertyEnum.class); Map hasNotProperties = new EnumMap<>(GraphPropertyEnum.class); hasProperties.put(GraphPropertyEnum.NAME, name); @@ -1871,16 +1871,17 @@ public class ToscaOperationFacade { updateInstancesCapAndReqOnComponentFromDB(component); return storageOperationStatus; } - - public StorageOperationStatus updateCalculatedCapabilitiesRequirements(final Map>> instCapabilties, - final Map>> instReg, - final Component component) { + + public StorageOperationStatus updateCalculatedCapabilitiesRequirements( + final Map>> instCapabilties, + final Map>> instReg, + final Component component) { StorageOperationStatus storageOperationStatus = StorageOperationStatus.OK; if (instCapabilties != null) { for (Entry>> entry : instCapabilties.entrySet()) { final Map> cap = entry.getValue(); - for (List capabilityList: cap.values()) { - for (CapabilityDefinition capability: capabilityList) { + for (List capabilityList : cap.values()) { + for (CapabilityDefinition capability : capabilityList) { nodeTemplateOperation.updateComponentInstanceCapabilities(component.getUniqueId(), entry.getKey().getUniqueId(), capability); } } @@ -1889,9 +1890,10 @@ public class ToscaOperationFacade { if (instReg != null) { for (Entry>> entry : instReg.entrySet()) { final Map> req = entry.getValue(); - for (List requirementList: req.values()) { - for (RequirementDefinition requirement: requirementList) { - storageOperationStatus = nodeTemplateOperation.updateComponentInstanceRequirement(component.getUniqueId(), entry.getKey().getUniqueId(), requirement); + for (List requirementList : req.values()) { + for (RequirementDefinition requirement : requirementList) { + storageOperationStatus = nodeTemplateOperation.updateComponentInstanceRequirement(component.getUniqueId(), + entry.getKey().getUniqueId(), requirement); if (storageOperationStatus != StorageOperationStatus.OK) { return storageOperationStatus; } @@ -2103,6 +2105,7 @@ public class ToscaOperationFacade { } return result; } + public Either validateComponentNameUniqueness(String name, ResourceTypeEnum resourceType, ComponentTypeEnum componentType) { String normalizedName = ValidationUtils.normaliseComponentName(name); @@ -2130,7 +2133,8 @@ public class ToscaOperationFacade { final ComponentTypeEnum componentType) { final String normalizedName = ValidationUtils.normaliseComponentName(resourceName); final Either, JanusGraphOperationStatus> vertexEither = janusGraphDao - .getByCriteria(getVertexTypeEnum(resourceType), propertiesToMatch(normalizedName, componentType), null, null, JsonParseFlagEnum.NoParse, modelName); + .getByCriteria(getVertexTypeEnum(resourceType), propertiesToMatch(normalizedName, componentType), null, null, JsonParseFlagEnum.NoParse, + modelName); if (vertexEither.isRight() && vertexEither.right().value() != JanusGraphOperationStatus.NOT_FOUND) { log.debug("failed to get vertex from graph with property normalizedName: {} and model: {}", normalizedName, modelName); return Either.right(DaoStatusConverter.convertJanusGraphStatusToStorageStatus(vertexEither.right().value())); @@ -2210,7 +2214,8 @@ public class ToscaOperationFacade { if (ComponentTypeEnum.RESOURCE == componentTypeEnum) { internalVertexTypes.add(VertexTypeEnum.NODE_TYPE); } - if (ComponentTypeEnum.SERVICE == componentTypeEnum || SERVICE.equalsIgnoreCase(internalComponentType) || VF.equalsIgnoreCase(internalComponentType)) { + if (ComponentTypeEnum.SERVICE == componentTypeEnum || SERVICE.equalsIgnoreCase(internalComponentType) || VF.equalsIgnoreCase( + internalComponentType)) { internalVertexTypes.add(VertexTypeEnum.TOPOLOGY_TEMPLATE); } return internalVertexTypes; @@ -2471,7 +2476,8 @@ public class ToscaOperationFacade { return null; } - public Either validateToscaResourceNameExtends(String templateNameCurrent, String templateNameExtends, String model) { + public Either validateToscaResourceNameExtends(String templateNameCurrent, String templateNameExtends, + String model) { String currentTemplateNameChecked = templateNameExtends; while (currentTemplateNameChecked != null && !currentTemplateNameChecked.equalsIgnoreCase(templateNameCurrent)) { Either latestByToscaResourceName = getLatestByToscaResourceName(currentTemplateNameChecked, model); @@ -2617,27 +2623,6 @@ public class ToscaOperationFacade { return Either.left(servicesAll); } - private List getVerticesForModel(final String modelName, final List graphVertexList) { - final Predicate filterPredicate = - StringUtils.isEmpty(modelName) ? this::vertexNotConnectedToAnyModel - : graphVertex -> vertexValidForModel(graphVertex, modelName).isPresent(); - return StreamSupport.stream(graphVertexList.spliterator(), false).filter(filterPredicate).collect(Collectors.toList()); - } - - private boolean vertexNotConnectedToAnyModel(final GraphVertex vertex) { - return !vertex.getVertex().edges(Direction.OUT, EdgeLabelEnum.MODEL.name()).hasNext(); - } - - private Optional vertexValidForModel(final GraphVertex vertex, final String model) { - final Either, JanusGraphOperationStatus> nodeModelVertices = janusGraphDao - .getParentVertices(vertex, EdgeLabelEnum.MODEL, JsonParseFlagEnum.NoParse); - if (nodeModelVertices.isRight() || Objects.isNull(nodeModelVertices.left().value())) { - return Optional.empty(); - } - return nodeModelVertices.left().value().stream().filter(graphVertex -> graphVertex.getMetadataProperty(GraphPropertyEnum.MODEL).equals(model)) - .findFirst(); - } - public void rollback() { janusGraphDao.rollback(); } @@ -3107,7 +3092,7 @@ public class ToscaOperationFacade { public StorageOperationStatus updateComponentInstanceCapabilityProperties(Component containerComponent, String componentInstanceUniqueId) { return convertComponentInstanceProperties(containerComponent, componentInstanceUniqueId).map(instanceCapProps -> topologyTemplateOperation - .updateComponentInstanceCapabilityProperties(containerComponent, componentInstanceUniqueId, instanceCapProps)) + .updateComponentInstanceCapabilityProperties(containerComponent, componentInstanceUniqueId, instanceCapProps)) .orElse(StorageOperationStatus.NOT_FOUND); } @@ -3127,10 +3112,54 @@ public class ToscaOperationFacade { return topologyTemplateOperation.updateComponentInstanceInterfaces(containerComponent, componentInstanceUniqueId, mapInterfaceDataDefinition); } - public StorageOperationStatus updateComponentInterfaces(final String componentId, final Map interfaces, - final String componentInterfaceUpdatedKey) { - MapInterfaceDataDefinition mapInterfaceDataDefinition = convertComponentInterfaces(interfaces); - return topologyTemplateOperation.updateComponentInterfaces(componentId, mapInterfaceDataDefinition, componentInterfaceUpdatedKey); + public StorageOperationStatus updateComponentInterfaces(final Component component, final String componentInterfaceUpdatedKey) { + MapInterfaceDataDefinition mapInterfaceDataDefinition = convertComponentInterfaces(component.getInterfaces()); + return topologyTemplateOperation.updateComponentInterfaces(component.getUniqueId(), mapInterfaceDataDefinition, componentInterfaceUpdatedKey); + } + + public Either addInterfaceToComponent(final String interfaceName, + final InterfaceDefinition interfaceDefinition, + final Component component) { + + final boolean match = component.getInterfaces().keySet().stream().anyMatch(s -> s.equals(interfaceName)); + StorageOperationStatus status = StorageOperationStatus.OK; + final ToscaElementOperation toscaElementOperation = getToscaElementOperation(component); + if (match) { + status = toscaElementOperation.updateToscaDataOfToscaElement(component.getUniqueId(), EdgeLabelEnum.INTERFACE_ARTIFACTS, + VertexTypeEnum.INTERFACE_ARTIFACTS, interfaceDefinition, JsonPresentationFields.TYPE); + } else { + status = toscaElementOperation.addToscaDataToToscaElement(component.getUniqueId(), EdgeLabelEnum.INTERFACE_ARTIFACTS, + VertexTypeEnum.INTERFACE_ARTIFACTS, interfaceDefinition, JsonPresentationFields.TYPE); + } + + if (status != StorageOperationStatus.OK) { + CommonUtility.addRecordToLog(log, LogLevelEnum.DEBUG, "Failed to add the interface {} to the component {}. Status is {}. ", + interfaceName, component.getName(), status); + return Either.right(status); + } + final ComponentParametersView filter = new ComponentParametersView(true); + filter.setIgnoreInterfaces(false); + filter.setIgnoreInterfaceInstances(false); + final Either getUpdatedComponentRes = getToscaElement(component.getUniqueId(), filter); + if (getUpdatedComponentRes.isRight()) { + CommonUtility.addRecordToLog(log, LogLevelEnum.DEBUG, "Failed to get updated component {}. Status is {}. ", + component.getUniqueId(), getUpdatedComponentRes.right().value()); + return Either.right(getUpdatedComponentRes.right().value()); + } + InterfaceDefinition newInterfaceDefinition = null; + final Map interfaces = (getUpdatedComponentRes.left().value()).getInterfaces(); + if (MapUtils.isNotEmpty(interfaces)) { + final Optional interfaceNameOptional = interfaces.keySet().stream().filter(key -> key.equals(interfaceName)).findAny(); + if (interfaceNameOptional.isPresent()) { + newInterfaceDefinition = interfaces.get(interfaceNameOptional.get()); + } + } + if (newInterfaceDefinition == null) { + CommonUtility.addRecordToLog(log, LogLevelEnum.DEBUG, "Failed to find recently added interface {} on the component {}. Status is {}. ", + interfaceName, component.getUniqueId(), StorageOperationStatus.NOT_FOUND); + return Either.right(StorageOperationStatus.NOT_FOUND); + } + return Either.left(newInterfaceDefinition); } public StorageOperationStatus updateComponentCalculatedCapabilitiesProperties(Component containerComponent) { diff --git a/catalog-ui/src/app/ng2/app.module.ts b/catalog-ui/src/app/ng2/app.module.ts index e9ae1201af..37167b4360 100644 --- a/catalog-ui/src/app/ng2/app.module.ts +++ b/catalog-ui/src/app/ng2/app.module.ts @@ -45,6 +45,7 @@ import {UiElementsModule} from './components/ui/ui-elements.module'; import {ConnectionWizardModule} from './pages/composition/graph/connection-wizard/connection-wizard.module'; import {InterfaceOperationModule} from './pages/interface-operation/interface-operation.module'; import {OperationCreatorModule} from './pages/interface-operation/operation-creator/operation-creator.module'; +import {OperationCreatorInterfaceDefinitionModule} from './pages/interface-definition/operation-creator/operation-creator-interface-definition.module'; import {LayoutModule} from './components/layout/layout.module'; import {UserService} from './services/user.service'; import {DynamicComponentService} from './services/dynamic-component.service'; @@ -158,6 +159,7 @@ export function configServiceFactory(config: ConfigService, authService: Authent InterfaceOperationModule, InterfaceDefinitionModule, OperationCreatorModule, + OperationCreatorInterfaceDefinitionModule, InterfaceOperationHandlerModule, ServicePathCreatorModule, ServicePathsListModule, diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-interface-operation.component.less b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-interface-operation.component.less index 4c7f8aba48..ac917134f2 100644 --- a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-interface-operation.component.less +++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-interface-operation.component.less @@ -20,7 +20,7 @@ @import '../../../../../../../assets/styles/variables.less'; @import '../../../../../../../assets/styles/override.less'; -.operation-creator { +.operation-creator-interface-definition { font-family: @font-opensans-regular; user-select: none; padding-top: 12px; diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.ts b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.ts index 07d8fd6eea..c17c130d92 100644 --- a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.ts @@ -20,7 +20,9 @@ */ import {Component, ComponentRef, Inject, Input} from '@angular/core'; -import {TopologyTemplateService} from '../../../services/component-services/topology-template.service'; +import { + TopologyTemplateService +} from '../../../services/component-services/topology-template.service'; import {TranslateService} from "../../../shared/translator/translate.service"; import {ModalService} from 'app/ng2/services/modal.service'; import {ModalComponent} from 'app/ng2/components/ui/modal/modal.component'; @@ -29,265 +31,282 @@ import {PluginsService} from "app/ng2/services/plugins.service"; import {SelectedComponentType} from "../common/store/graph.actions"; import {WorkspaceService} from "../../workspace/workspace.service"; -import {ComponentInterfaceDefinitionModel, InterfaceOperationModel} from "../../../../models/interfaceOperation"; -import {InterfaceOperationHandlerComponent} from "./operation-creator/interface-operation-handler.component"; - -import {ArtifactModel, ButtonModel, ComponentInstance, ComponentMetadata, InputBEModel, InterfaceModel, ModalModel} from 'app/models'; +import { + ComponentInterfaceDefinitionModel, + InterfaceOperationModel +} from "../../../../models/interfaceOperation"; +import { + InterfaceOperationHandlerComponent +} from "./operation-creator/interface-operation-handler.component"; + +import { + ArtifactModel, + ButtonModel, + ComponentInstance, + ComponentMetadata, + InputBEModel, + InterfaceModel, + ModalModel +} from 'app/models'; import {ArtifactGroupType} from "../../../../utils/constants"; -import {DropdownValue} from "../../../components/ui/form-components/dropdown/ui-element-dropdown.component"; +import { + DropdownValue +} from "../../../components/ui/form-components/dropdown/ui-element-dropdown.component"; import {ToscaArtifactService} from "../../../services/tosca-artifact.service"; import {ToscaArtifactModel} from "../../../../models/toscaArtifact"; export class UIInterfaceOperationModel extends InterfaceOperationModel { - isCollapsed: boolean = true; - isEllipsis: boolean; - MAX_LENGTH = 75; - constructor(operation: InterfaceOperationModel) { - super(operation); - - if (!operation.description) { - this.description = ''; - } + isCollapsed: boolean = true; + isEllipsis: boolean; + MAX_LENGTH = 75; + + constructor(operation: InterfaceOperationModel) { + super(operation); - if (this.description.length > this.MAX_LENGTH) { - this.isEllipsis = true; - } else { - this.isEllipsis = false; + if (!operation.description) { + this.description = ''; + } + + if (this.description.length > this.MAX_LENGTH) { + this.isEllipsis = true; + } else { + this.isEllipsis = false; + } } - } - getDescriptionEllipsis(): string { - if (this.isCollapsed && this.description.length > this.MAX_LENGTH) { - return this.description.substr(0, this.MAX_LENGTH - 3) + '...'; + getDescriptionEllipsis(): string { + if (this.isCollapsed && this.description.length > this.MAX_LENGTH) { + return this.description.substr(0, this.MAX_LENGTH - 3) + '...'; + } + return this.description; } - return this.description; - } - toggleCollapsed(e) { - e.stopPropagation(); - this.isCollapsed = !this.isCollapsed; - } + toggleCollapsed(e) { + e.stopPropagation(); + this.isCollapsed = !this.isCollapsed; + } } class ModalTranslation { - EDIT_TITLE: string; - CANCEL_BUTTON: string; - CLOSE_BUTTON: string; - SAVE_BUTTON: string; - - constructor(private TranslateService: TranslateService) { - this.TranslateService.languageChangedObservable.subscribe(lang => { - this.EDIT_TITLE = this.TranslateService.translate('INTERFACE_EDIT_TITLE'); - this.CANCEL_BUTTON = this.TranslateService.translate("INTERFACE_CANCEL_BUTTON"); - this.CLOSE_BUTTON = this.TranslateService.translate("INTERFACE_CLOSE_BUTTON"); - this.SAVE_BUTTON = this.TranslateService.translate("INTERFACE_SAVE_BUTTON"); - }); - } + EDIT_TITLE: string; + CANCEL_BUTTON: string; + CLOSE_BUTTON: string; + SAVE_BUTTON: string; + + constructor(private TranslateService: TranslateService) { + this.TranslateService.languageChangedObservable.subscribe(lang => { + this.EDIT_TITLE = this.TranslateService.translate('INTERFACE_EDIT_TITLE'); + this.CANCEL_BUTTON = this.TranslateService.translate("INTERFACE_CANCEL_BUTTON"); + this.CLOSE_BUTTON = this.TranslateService.translate("INTERFACE_CLOSE_BUTTON"); + this.SAVE_BUTTON = this.TranslateService.translate("INTERFACE_SAVE_BUTTON"); + }); + } } export class UIInterfaceModel extends ComponentInterfaceDefinitionModel { - isCollapsed: boolean = false; - - constructor(interf?: any) { - super(interf); - this.operations = _.map( - this.operations, - (operation) => new UIInterfaceOperationModel(operation) - ); - } - - toggleCollapse() { - this.isCollapsed = !this.isCollapsed; - } + isCollapsed: boolean = false; + + constructor(interf?: any) { + super(interf); + this.operations = _.map( + this.operations, + (operation) => new UIInterfaceOperationModel(operation) + ); + } + + toggleCollapse() { + this.isCollapsed = !this.isCollapsed; + } } @Component({ - selector: 'app-interface-operations', - templateUrl: './interface-operations.component.html', - styleUrls: ['./interface-operations.component.less'], - providers: [ModalService, TranslateService] + selector: 'app-interface-operations', + templateUrl: './interface-operations.component.html', + styleUrls: ['./interface-operations.component.less'], + providers: [ModalService, TranslateService] }) export class InterfaceOperationsComponent { - interfaces: UIInterfaceModel[]; - inputs: Array; - isLoading: boolean; - interfaceTypes: { [interfaceType: string]: string[] }; - topologyTemplate: TopologyTemplate; - componentMetaData: ComponentMetadata; - componentInstanceSelected: ComponentInstance; - modalInstance: ComponentRef; - modalTranslation: ModalTranslation; - componentInstancesInterfaces: Map; - - deploymentArtifactsFilePath: Array = []; - toscaArtifactTypes: Array = []; - - @Input() component: ComponentInstance; - @Input() isViewOnly: boolean; - @Input() enableMenuItems: Function; - @Input() disableMenuItems: Function; - @Input() componentType: SelectedComponentType; - - - constructor( - private TranslateService: TranslateService, - private PluginsService: PluginsService, - private topologyTemplateService: TopologyTemplateService, - private toscaArtifactService: ToscaArtifactService, - private modalServiceNg2: ModalService, - private workspaceService: WorkspaceService, - @Inject("Notification") private Notification: any, - ) { - this.modalTranslation = new ModalTranslation(TranslateService); - } - - ngOnInit(): void { - this.componentMetaData = this.workspaceService.metadata; - this.loadComponentInstances(); - this.loadDeployedArtifacts(); - this.loadToscaArtifacts() - } - - private loadComponentInstances() { - this.isLoading = true; - this.topologyTemplateService.getComponentInstances(this.componentMetaData.componentType, this.componentMetaData.uniqueId) - .subscribe((response) => { - this.componentInstanceSelected = response.componentInstances.find(ci => ci.uniqueId === this.component.uniqueId); - this.initComponentInstanceInterfaceOperations(); - this.isLoading = false; - }); - } - - private initComponentInstanceInterfaceOperations() { - this.initInterfaces(this.componentInstanceSelected.interfaces); - this.sortInterfaces(); - } - - private initInterfaces(interfaces: ComponentInterfaceDefinitionModel[]): void { - this.interfaces = _.map(interfaces, (interfaceModel) => new UIInterfaceModel(interfaceModel)); - } - - private sortInterfaces(): void { - this.interfaces = _.filter(this.interfaces, (interf) => interf.operations && interf.operations.length > 0); // remove empty interfaces - this.interfaces.sort((a, b) => a.type.localeCompare(b.type)); // sort interfaces alphabetically - _.forEach(this.interfaces, (interf) => { - interf.operations.sort((a, b) => a.name.localeCompare(b.name)); // sort operations alphabetically - }); - } - - collapseAll(value: boolean = true): void { - _.forEach(this.interfaces, (interf) => { - interf.isCollapsed = value; - }); - } - - isAllCollapsed(): boolean { - return _.every(this.interfaces, (interf) => interf.isCollapsed); - } - - isAllExpanded(): boolean { - return _.every(this.interfaces, (interf) => !interf.isCollapsed); - } - - isListEmpty(): boolean { - return _.filter( - this.interfaces, - (interf) => interf.operations && interf.operations.length > 0 - ).length === 0; - } - - private enableOrDisableSaveButton = (): boolean => { - return this.isViewOnly; - } - - onSelectInterfaceOperation(interfaceModel: UIInterfaceModel, operation: InterfaceOperationModel) { - - const buttonList = []; - if (this.isViewOnly) { - const closeButton: ButtonModel = new ButtonModel(this.modalTranslation.CLOSE_BUTTON, 'outline white', this.cancelAndCloseModal); - buttonList.push(closeButton); - } else { - const saveButton: ButtonModel = new ButtonModel(this.modalTranslation.SAVE_BUTTON, 'blue', () => - this.updateInterfaceOperation(), this.enableOrDisableSaveButton); - const cancelButton: ButtonModel = new ButtonModel(this.modalTranslation.CANCEL_BUTTON, 'outline white', this.cancelAndCloseModal); - buttonList.push(saveButton); - buttonList.push(cancelButton); + interfaces: UIInterfaceModel[]; + inputs: Array; + isLoading: boolean; + interfaceTypes: { [interfaceType: string]: string[] }; + topologyTemplate: TopologyTemplate; + componentMetaData: ComponentMetadata; + componentInstanceSelected: ComponentInstance; + modalInstance: ComponentRef; + modalTranslation: ModalTranslation; + componentInstancesInterfaces: Map; + + deploymentArtifactsFilePath: Array = []; + toscaArtifactTypes: Array = []; + + @Input() component: ComponentInstance; + @Input() isViewOnly: boolean; + @Input() enableMenuItems: Function; + @Input() disableMenuItems: Function; + @Input() componentType: SelectedComponentType; + + + constructor( + private TranslateService: TranslateService, + private PluginsService: PluginsService, + private topologyTemplateService: TopologyTemplateService, + private toscaArtifactService: ToscaArtifactService, + private modalServiceNg2: ModalService, + private workspaceService: WorkspaceService, + @Inject("Notification") private Notification: any, + ) { + this.modalTranslation = new ModalTranslation(TranslateService); + } + + ngOnInit(): void { + this.componentMetaData = this.workspaceService.metadata; + this.loadComponentInstances(); + this.loadDeployedArtifacts(); + this.loadToscaArtifacts() + } + + private loadComponentInstances() { + this.isLoading = true; + this.topologyTemplateService.getComponentInstances(this.componentMetaData.componentType, this.componentMetaData.uniqueId) + .subscribe((response) => { + this.componentInstanceSelected = response.componentInstances.find(ci => ci.uniqueId === this.component.uniqueId); + this.initComponentInstanceInterfaceOperations(); + this.isLoading = false; + }); + } + + private initComponentInstanceInterfaceOperations() { + this.initInterfaces(this.componentInstanceSelected.interfaces); + this.sortInterfaces(); + } + + private initInterfaces(interfaces: ComponentInterfaceDefinitionModel[]): void { + this.interfaces = _.map(interfaces, (interfaceModel) => new UIInterfaceModel(interfaceModel)); + } + + private sortInterfaces(): void { + this.interfaces = _.filter(this.interfaces, (interf) => interf.operations && interf.operations.length > 0); // remove empty interfaces + this.interfaces.sort((a, b) => a.type.localeCompare(b.type)); // sort interfaces alphabetically + _.forEach(this.interfaces, (interf) => { + interf.operations.sort((a, b) => a.name.localeCompare(b.name)); // sort operations alphabetically + }); + } + + collapseAll(value: boolean = true): void { + _.forEach(this.interfaces, (interf) => { + interf.isCollapsed = value; + }); + } + + isAllCollapsed(): boolean { + return _.every(this.interfaces, (interf) => interf.isCollapsed); + } + + isAllExpanded(): boolean { + return _.every(this.interfaces, (interf) => !interf.isCollapsed); + } + + isListEmpty(): boolean { + return _.filter( + this.interfaces, + (interf) => interf.operations && interf.operations.length > 0 + ).length === 0; + } + + private enableOrDisableSaveButton = (): boolean => { + return this.isViewOnly; } - const modalModel: ModalModel = new ModalModel('l', this.modalTranslation.EDIT_TITLE, '', buttonList, 'custom'); - this.modalInstance = this.modalServiceNg2.createCustomModal(modalModel); - - this.modalServiceNg2.addDynamicContentToModal( - this.modalInstance, - InterfaceOperationHandlerComponent, - { - deploymentArtifactsFilePath: this.deploymentArtifactsFilePath, - toscaArtifactTypes: this.toscaArtifactTypes, - selectedInterface: interfaceModel, - selectedInterfaceOperation: operation, - validityChangedCallback: this.enableOrDisableSaveButton, - isViewOnly: this.isViewOnly + + onSelectInterfaceOperation(interfaceModel: UIInterfaceModel, operation: InterfaceOperationModel) { + + const buttonList = []; + if (this.isViewOnly) { + const closeButton: ButtonModel = new ButtonModel(this.modalTranslation.CLOSE_BUTTON, 'outline white', this.cancelAndCloseModal); + buttonList.push(closeButton); + } else { + const saveButton: ButtonModel = new ButtonModel(this.modalTranslation.SAVE_BUTTON, 'blue', () => + this.updateInterfaceOperation(), this.enableOrDisableSaveButton); + const cancelButton: ButtonModel = new ButtonModel(this.modalTranslation.CANCEL_BUTTON, 'outline white', this.cancelAndCloseModal); + buttonList.push(saveButton); + buttonList.push(cancelButton); } - ); - this.modalInstance.instance.open(); - } - - private cancelAndCloseModal = () => { - this.loadComponentInstances(); - return this.modalServiceNg2.closeCurrentModal(); - } - - private updateInterfaceOperation() { - this.isLoading = true; - const interfaceOperationHandlerComponentInstance: InterfaceOperationHandlerComponent = this.modalInstance.instance.dynamicContent.instance; - const operationUpdated: InterfaceOperationModel = interfaceOperationHandlerComponentInstance.operationToUpdate; - const isArtifactChecked = interfaceOperationHandlerComponentInstance.enableAddArtifactImplementation; - if (!isArtifactChecked) { - let artifactName = interfaceOperationHandlerComponentInstance.artifactName; - artifactName = artifactName === undefined ? '' : artifactName; - operationUpdated.implementation = new ArtifactModel({'artifactName': artifactName} as ArtifactModel); + const modalModel: ModalModel = new ModalModel('l', this.modalTranslation.EDIT_TITLE, '', buttonList, 'custom'); + this.modalInstance = this.modalServiceNg2.createCustomModal(modalModel); + + this.modalServiceNg2.addDynamicContentToModal( + this.modalInstance, + InterfaceOperationHandlerComponent, + { + deploymentArtifactsFilePath: this.deploymentArtifactsFilePath, + toscaArtifactTypes: this.toscaArtifactTypes, + selectedInterface: interfaceModel ? interfaceModel : new UIInterfaceModel(), + selectedInterfaceOperation: operation ? operation : new InterfaceOperationModel(), + validityChangedCallback: this.enableOrDisableSaveButton, + isViewOnly: this.isViewOnly + } + ); + this.modalInstance.instance.open(); + } + + private cancelAndCloseModal = () => { + this.loadComponentInstances(); + return this.modalServiceNg2.closeCurrentModal(); } - this.topologyTemplateService.updateComponentInstanceInterfaceOperation( - this.componentMetaData.uniqueId, - this.componentMetaData.componentType, - this.componentInstanceSelected.uniqueId, - operationUpdated) + + private updateInterfaceOperation() { + this.isLoading = true; + const interfaceOperationHandlerComponentInstance: InterfaceOperationHandlerComponent = this.modalInstance.instance.dynamicContent.instance; + const operationUpdated: InterfaceOperationModel = interfaceOperationHandlerComponentInstance.operationToUpdate; + const isArtifactChecked = interfaceOperationHandlerComponentInstance.enableAddArtifactImplementation; + if (!isArtifactChecked) { + let artifactName = interfaceOperationHandlerComponentInstance.artifactName; + artifactName = artifactName === undefined ? '' : artifactName; + operationUpdated.implementation = new ArtifactModel({'artifactName': artifactName} as ArtifactModel); + } + this.topologyTemplateService.updateComponentInstanceInterfaceOperation( + this.componentMetaData.uniqueId, + this.componentMetaData.componentType, + this.componentInstanceSelected.uniqueId, + operationUpdated) .subscribe((updatedComponentInstance: ComponentInstance) => { this.componentInstanceSelected = new ComponentInstance(updatedComponentInstance); this.initComponentInstanceInterfaceOperations(); }); - this.modalServiceNg2.closeCurrentModal(); - this.isLoading = false; - } - - loadDeployedArtifacts() { - this.topologyTemplateService.getArtifactsByType(this.componentMetaData.componentType, this.componentMetaData.uniqueId, ArtifactGroupType.DEPLOYMENT) - .subscribe(response => { - let artifactsDeployment = response.deploymentArtifacts; - if (artifactsDeployment) { - let deploymentArtifactsFound = _.values(artifactsDeployment) - deploymentArtifactsFound.forEach(value => { - this.deploymentArtifactsFilePath.push(new DropdownValue(value, value.artifactType.concat('->').concat(value.artifactName))); + this.modalServiceNg2.closeCurrentModal(); + this.isLoading = false; + } + + loadDeployedArtifacts() { + this.topologyTemplateService.getArtifactsByType(this.componentMetaData.componentType, this.componentMetaData.uniqueId, ArtifactGroupType.DEPLOYMENT) + .subscribe(response => { + let artifactsDeployment = response.deploymentArtifacts; + if (artifactsDeployment) { + let deploymentArtifactsFound = _.values(artifactsDeployment) + deploymentArtifactsFound.forEach(value => { + this.deploymentArtifactsFilePath.push(new DropdownValue(value, value.artifactType.concat('->').concat(value.artifactName))); + }); + } + }, error => { + this.Notification.error({ + message: 'Failed to Load the Deployed Artifacts:' + error, + title: 'Failure' + }); + }); + } + + loadToscaArtifacts() { + this.toscaArtifactService.getToscaArtifacts(this.componentMetaData.model).subscribe(response => { + if (response) { + let toscaArtifactsFound = _.values(response); + toscaArtifactsFound.forEach(value => this.toscaArtifactTypes.push(new DropdownValue(value, value.type))); + } + }, error => { + this.Notification.error({ + message: 'Failed to Load Tosca Artifacts:' + error, + title: 'Failure' + }); }); - }}, error => { - this.Notification.error({ - message: 'Failed to Load the Deployed Artifacts:' + error, - title: 'Failure' - }); - }); - } - - loadToscaArtifacts() { - this.toscaArtifactService.getToscaArtifacts(this.componentMetaData.model).subscribe(response => { - if (response) { - let toscaArtifactsFound = _.values(response); - toscaArtifactsFound.forEach(value => this.toscaArtifactTypes.push(new DropdownValue(value, value.type))); - } - }, error => { - this.Notification.error({ - message: 'Failed to Load Tosca Artifacts:' + error, - title: 'Failure' - }); - }); - } + } } diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.html b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.html index 7a73a5babc..5f02bc2bf7 100644 --- a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.html +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.html @@ -25,19 +25,30 @@
- - + [required]="true" + [testId]="'interface-name'" + [selectedOption]="selectedInterfaceType" + [placeHolder]="'Select...'" + [disabled]="isViewOnly || isEdit" + (changed)="onSelectInterface($event)" + [options]="interfaceTypeOptions"> +
- - + [required]="true" + [testId]="'operation-name'" + [selectedOption]="selectedInterfaceOperation" + [placeHolder]="'Select...'" + [disabled]="isViewOnly || isEdit" + (changed)="onSelectOperation($event)" + [options]="interfaceOperationOptions"> +
diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.ts b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.ts index 059708592e..5cc7f691aa 100644 --- a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.ts @@ -18,17 +18,18 @@ * SPDX-License-Identifier: Apache-2.0 * ============LICENSE_END========================================================= */ - -import {Component, EventEmitter, Input, Output} from '@angular/core'; +import {Component, EventEmitter, Input, Output, ViewChild} from '@angular/core'; import {UIInterfaceModel} from "../interface-operations.component"; import {InputOperationParameter, InterfaceOperationModel, IOperationParamsList} from "../../../../../models/interfaceOperation"; import {TranslateService} from "../../../../shared/translator/translate.service"; -import {IDropDownOption} from "onap-ui-angular/dist/form-elements/dropdown/dropdown-models"; import {DropdownValue} from "../../../../components/ui/form-components/dropdown/ui-element-dropdown.component"; import {ArtifactModel} from "../../../../../models/artifacts"; import {PropertyBEModel} from "../../../../../models/properties-inputs/property-be-model"; import {PropertyParamRowComponent} from "./property-param-row/property-param-row.component"; import {PropertyFEModel} from "../../../../../models/properties-inputs/property-fe-model"; +import {IDropDownOption} from 'onap-ui-angular'; +import {ComponentServiceNg2} from "../../../../services/component-services/component.service"; +import {DropDownComponent} from "onap-ui-angular/dist/form-elements/dropdown/dropdown.component"; import {DataTypeService} from "../../../../services/data-type.service"; import {Observable} from "rxjs/Observable"; import {DataTypeModel} from "../../../../../models/data-types"; @@ -43,13 +44,15 @@ export class InterfaceOperationHandlerComponent { @Input() private modelName: string; @Output('propertyChanged') emitter: EventEmitter = new EventEmitter(); + @ViewChild('interfaceOperationDropDown') interfaceOperationDropDown: DropDownComponent; + input: { toscaArtifactTypes: Array; selectedInterface: UIInterfaceModel; selectedInterfaceOperation: InterfaceOperationModel; validityChangedCallback: Function; isViewOnly: boolean; - interfaceTypesMap: Map; + isEdit: boolean; }; dataTypeMap$: Observable>; @@ -64,10 +67,13 @@ export class InterfaceOperationHandlerComponent { isLoading: boolean = false; readonly: boolean; isViewOnly: boolean; + isEdit: boolean; interfaceTypes: Array = []; - interfaceOperations: Array = []; - - interfaceTypesMap: Map; + interfaceTypeOptions: Array = []; + selectedInterfaceType: DropDownOption = undefined; + interfaceOperationMap: Map> = new Map>(); + interfaceOperationOptions: Array = []; + selectedInterfaceOperation: DropDownOption = undefined; toscaArtifactTypeSelected: string; toscaArtifactTypeProperties: Array = []; @@ -80,7 +86,7 @@ export class InterfaceOperationHandlerComponent { propertyValueValid: boolean = true; inputTypeOptions: any[]; - constructor(private dataTypeService: DataTypeService) { + constructor(private dataTypeService: DataTypeService, private componentServiceNg2: ComponentServiceNg2) { this.dataTypeMap$ = new Observable>(subscriber => { this.dataTypeService.findAllDataTypesByModel(this.modelName) .then((dataTypesMap: Map) => { @@ -95,6 +101,7 @@ export class InterfaceOperationHandlerComponent { ngOnInit() { this.isViewOnly = this.input.isViewOnly; + this.isEdit = this.input.isEdit; this.interfaceType = this.input.selectedInterface.type; this.operationToUpdate = this.input.selectedInterfaceOperation; this.operationToUpdate.interfaceId = this.input.selectedInterface.uniqueId; @@ -113,18 +120,56 @@ export class InterfaceOperationHandlerComponent { } this.inputs = Array.from(this.operationToUpdate.inputs.listToscaDataDefinition); - this.interfaceTypesMap = this.input.interfaceTypesMap; - this.loadInterfaceTypesAndOperations(); this.removeImplementationQuote(); this.validityChanged(); this.loadInterfaceOperationImplementation(); + this.loadInterfaceType(); + } + + private loadInterfaceType() { + this.componentServiceNg2.getInterfaceTypesByModel(undefined) + .subscribe(response => { + if (response) { + this.interfaceOperationMap = new Map>(); + for (const interfaceType of Object.keys(response).sort()) { + const operationList = response[interfaceType]; + operationList.sort(); + this.interfaceOperationMap.set(interfaceType, operationList); + const operationDropDownOption: DropDownOption = new DropDownOption(interfaceType); + this.interfaceTypeOptions.push(operationDropDownOption); + if (this.interfaceType == interfaceType) { + this.selectedInterfaceType = operationDropDownOption; + } + } + this.loadInterfaceTypeOperations(); + } + }); + } + + loadInterfaceTypeOperations() { + this.interfaceOperationOptions = new Array(); + const interfaceOperationList = this.interfaceOperationMap.get(this.interfaceType); + + if (interfaceOperationList) { + interfaceOperationList.forEach(operationName => { + const operationOption = new DropDownOption(operationName, operationName); + this.interfaceOperationOptions.push(operationOption); + if (this.operationToUpdate.name == operationName) { + this.selectedInterfaceOperation = operationOption + } + }); + } + + this.interfaceOperationDropDown.allOptions = this.interfaceOperationOptions; } private loadInterfaceOperationImplementation() { this.toscaArtifactTypes = this.input.toscaArtifactTypes; - this.artifactVersion = this.operationToUpdate.implementation.artifactVersion; - this.artifactName = this.operationToUpdate.implementation.artifactName; - this.toscaArtifactTypeProperties = this.operationToUpdate.implementation.properties; + if (this.operationToUpdate.implementation) { + this.artifactVersion = this.operationToUpdate.implementation.artifactVersion; + this.artifactName = this.operationToUpdate.implementation.artifactName; + this.toscaArtifactTypeProperties = this.operationToUpdate.implementation.properties; + } this.artifactTypeProperties = this.convertArtifactsPropertiesToInput(); this.getArtifactTypesSelected(); } @@ -348,11 +393,43 @@ export class InterfaceOperationHandlerComponent { return inputList; } - private loadInterfaceTypesAndOperations() { - console.log("loadInterfaceTypesAndOperations ", this.interfaceTypesMap.keys()); + onSelectInterface(dropDownOption: DropDownOption) { + if (dropDownOption) { + this.setInterfaceType(dropDownOption); + } else { + this.setInterfaceType(undefined); + } + this.setInterfaceOperation(undefined); + this.interfaceOperationDropDown.selectOption({} as IDropDownOption); + this.loadInterfaceTypeOperations(); + } - Array.from(this.interfaceTypesMap.keys()).forEach(value => this.interfaceTypes.push(new DropdownValue(value, value))); - console.log("loadInterfaceTypesAndOperations interfaceType ", this.interfaceTypes); + onSelectOperation(dropDownOption: DropDownOption) { + if (this.selectedInterfaceType && dropDownOption) { + this.setInterfaceOperation(dropDownOption); + } } + private setInterfaceType(dropDownOption: DropDownOption) { + this.selectedInterfaceType = dropDownOption ? dropDownOption : undefined; + this.interfaceType = dropDownOption ? dropDownOption.value : undefined; + this.operationToUpdate.interfaceType = dropDownOption ? dropDownOption.value : undefined; + this.operationToUpdate.interfaceId = dropDownOption ? dropDownOption.value : undefined; + } + + private setInterfaceOperation(dropDownOption: DropDownOption) { + this.operationToUpdate.name = dropDownOption ? dropDownOption.value : undefined; + this.operationToUpdate.operationType = dropDownOption ? dropDownOption.value : undefined; + this.selectedInterfaceOperation = dropDownOption ? dropDownOption : undefined; + } } + +class DropDownOption implements IDropDownOption { + value: string; + label: string; + + constructor(value: string, label?: string) { + this.value = value; + this.label = label || value; + } +} \ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.module.ts b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.module.ts index b212eec034..bcc797c64c 100644 --- a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.module.ts +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.module.ts @@ -25,16 +25,14 @@ import {CommonModule} from "@angular/common"; import {FormsModule, ReactiveFormsModule} from "@angular/forms"; import {FormElementsModule} from "app/ng2/components/ui/form-components/form-elements.module"; import {TranslateModule} from "app/ng2/shared/translator/translate.module"; - - +import {AddInputComponent} from './add-input/add-input.component'; +import {InputListComponent} from './input-list/input-list.component'; +import {InputListItemComponent} from './input-list/input-list-item/input-list-item.component'; +import {PropertyParamRowComponent} from "./property-param-row/property-param-row.component"; +import {InterfaceOperationHandlerComponent} from "./interface-operation-handler.component"; import {SdcUiComponentsModule} from "onap-ui-angular/dist"; -import { InterfaceOperationHandlerComponent } from "app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component"; -import { PropertyParamRowComponent } from "app/ng2/pages/composition/interface-operatons/operation-creator/property-param-row/property-param-row.component"; -import { UiElementsModule } from "app/ng2/components/ui/ui-elements.module"; -import { PropertyTableModule } from "app/ng2/components/logic/properties-table/property-table.module"; -import { AddInputComponent } from './add-input/add-input.component'; -import { InputListComponent } from './input-list/input-list.component'; -import { InputListItemComponent } from './input-list/input-list-item/input-list-item.component'; +import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module"; +import {PropertyTableModule} from "app/ng2/components/logic/properties-table/property-table.module"; @NgModule({ declarations: [ diff --git a/catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.html b/catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.html index 25ccf111a1..f3043ffebd 100644 --- a/catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.html +++ b/catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.html @@ -24,6 +24,13 @@
{{ 'INTERFACE_DATA_EMPTY' | translate }}
+
+ {{ 'INTERFACE_ADD_OPERATION' | translate }} +
@@ -38,11 +45,11 @@
-
-
+
+
- {{interface.type}} + {{interface1.type}}
-
+
@@ -63,20 +70,14 @@ {{ 'INTERFACE_HEADER_DESCRIPTION' | translate }}
- -
- - {{operation.name}} - - - {{operation.getDescriptionEllipsis()}} +
+ {{operation.name}} + + {{operation.getDescriptionEllipsis()}} - {{!operation.isEllipsis ? '' : operation.isCollapsed ? 'More' : 'Less'}} + {{!operation.isEllipsis ? '' : operation.isCollapsed ? 'More' : 'Less'}} + -
diff --git a/catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.ts b/catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.ts index 8dd17f60e2..c9a6d07057 100644 --- a/catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.ts +++ b/catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.ts @@ -20,26 +20,27 @@ */ import {Component, ComponentRef, Inject, Input} from '@angular/core'; import {Component as IComponent} from 'app/models/components/component'; +import {WorkflowServiceNg2} from 'app/ng2/services/workflow.service'; import {ISdcConfig, SdcConfigToken} from "app/ng2/config/sdc-config.config"; import {TranslateService} from "app/ng2/shared/translator/translate.service"; - +import {IModalButtonComponent, SdcUiServices} from 'onap-ui-angular'; import {ModalComponent} from 'app/ng2/components/ui/modal/modal.component'; + import {ModalService} from 'app/ng2/services/modal.service'; -import {ButtonModel, CapabilitiesGroup, ModalModel, OperationModel} from 'app/models'; +import { + ButtonModel, + CapabilitiesGroup, + InputBEModel, + InterfaceModel, + ModalModel, + OperationModel, + WORKFLOW_ASSOCIATION_OPTIONS +} from 'app/models'; import {ComponentServiceNg2} from 'app/ng2/services/component-services/component.service'; - -import {SdcUiServices} from 'onap-ui-angular'; import {TopologyTemplateService} from "../../services/component-services/topology-template.service"; -import { - ComponentInterfaceDefinitionModel, - InputOperationParameter, - InterfaceOperationModel -} from "../../../models/interfaceOperation"; -import { - PropertyParamRowComponent -} from "../composition/interface-operatons/operation-creator/property-param-row/property-param-row.component"; +import {InterfaceOperationModel} from "../../../models/interfaceOperation"; import { InterfaceOperationHandlerComponent } from "../composition/interface-operatons/operation-creator/interface-operation-handler.component"; @@ -49,15 +50,17 @@ import { import {ToscaArtifactModel} from "../../../models/toscaArtifact"; import {ToscaArtifactService} from "../../services/tosca-artifact.service"; import { - UIInterfaceOperationModel -} from "../composition/interface-operatons/interface-operations.component"; + InterfaceOperationComponent +} from "../interface-operation/interface-operation.page.component"; +import {Observable} from "rxjs/Observable"; +import {PluginsService} from 'app/ng2/services/plugins.service'; export class UIOperationModel extends OperationModel { isCollapsed: boolean = true; isEllipsis: boolean; MAX_LENGTH = 75; - constructor(operation: UIOperationModel) { + constructor(operation: OperationModel) { super(operation); if (!operation.description) { @@ -109,15 +112,14 @@ class ModalTranslation { } } -export class UIInterfaceModel extends ComponentInterfaceDefinitionModel { +export class UIInterfaceModel extends InterfaceModel { isCollapsed: boolean = false; constructor(interf?: any) { super(interf); - this.operations = _.map( - this.operations, - (operation) => new UIInterfaceOperationModel(operation) - ); + if (this.operations) { + this.operations = this.operations.map((operation) => new UIOperationModel(operation)); + } } toggleCollapse() { @@ -130,16 +132,14 @@ export class UIInterfaceModel extends ComponentInterfaceDefinitionModel { selector: 'interface-definition', templateUrl: './interface-definition.page.component.html', styleUrls: ['interface-definition.page.component.less'], - providers: [ModalService, TranslateService] + providers: [ModalService, TranslateService, InterfaceOperationComponent] }) - export class InterfaceDefinitionComponent { modalInstance: ComponentRef; interfaces: UIInterfaceModel[]; - inputs: Array = []; + inputs: InputBEModel[]; - properties: Array = []; deploymentArtifactsFilePath: Array = []; toscaArtifactTypes: Array = []; @@ -153,6 +153,10 @@ export class InterfaceDefinitionComponent { capabilities: CapabilitiesGroup; isViewOnly: boolean; + openOperation: OperationModel; + enableWorkflowAssociation: boolean; + workflowIsOnline: boolean; + @Input() component: IComponent; @Input() readonly: boolean; @Input() enableMenuItems: Function; @@ -167,19 +171,52 @@ export class InterfaceDefinitionComponent { private modalServiceNg2: ModalService, private modalServiceSdcUI: SdcUiServices.ModalService, private topologyTemplateService: TopologyTemplateService, - private toscaArtifactService: ToscaArtifactService + private toscaArtifactService: ToscaArtifactService, + private ComponentServiceNg2: ComponentServiceNg2, + private WorkflowServiceNg2: WorkflowServiceNg2, + private ModalServiceSdcUI: SdcUiServices.ModalService, + private PluginsService: PluginsService ) { this.modalTranslation = new ModalTranslation(translateService); this.interfaceTypesMap = new Map(); } ngOnInit(): void { - console.info("this.component.lifecycleState ", this.component.lifecycleState); - if (this.component) { - this.isViewOnly = this.component.componentMetadata.isComponentDataEditable(); - this.initInterfaceDefinition(); - this.loadInterfaceTypes(); - this.loadToscaArtifacts(); + this.isLoading = true; + this.interfaces = []; + this.workflowIsOnline = !_.isUndefined(this.PluginsService.getPluginByStateUrl('workflowDesigner')); + Observable.forkJoin( + this.ComponentServiceNg2.getInterfaceOperations(this.component), + this.ComponentServiceNg2.getComponentInputs(this.component), + this.ComponentServiceNg2.getInterfaceTypes(this.component), + this.ComponentServiceNg2.getCapabilitiesAndRequirements(this.component.componentType, this.component.uniqueId) + ).subscribe((response: any[]) => { + const callback = (workflows) => { + this.isLoading = false; + this.initInterfaces(response[0].interfaces); + this.sortInterfaces(); + this.inputs = response[1].inputs; + this.interfaceTypes = response[2]; + this.workflows = (workflows.items) ? workflows.items : workflows; + this.capabilities = response[3].capabilities; + }; + if (this.enableWorkflowAssociation && this.workflowIsOnline) { + this.WorkflowServiceNg2.getWorkflows().subscribe( + callback, + (err) => { + this.workflowIsOnline = false; + callback([]); + } + ); + } else { + callback([]); + } + }); + } + + initInterfaces(interfaces: InterfaceModel[]): void { + if (interfaces) { + this.interfaces = interfaces.map((interf) => new UIInterfaceModel(interf)); } } @@ -190,15 +227,18 @@ export class InterfaceDefinitionComponent { private disableSaveButton = (): boolean => { return this.isViewOnly || (this.isEnableAddArtifactImplementation() - && (!this.modalInstance.instance.dynamicContent.instance.toscaArtifactTypeSelected || - !this.modalInstance.instance.dynamicContent.instance.artifactName) + && (!this.modalInstance.instance.dynamicContent.toscaArtifactTypeSelected || + !this.modalInstance.instance.dynamicContent.artifactName) ); } onSelectInterfaceOperation(interfaceModel: UIInterfaceModel, operation: InterfaceOperationModel) { + const isEdit = operation !== undefined; const cancelButton: ButtonModel = new ButtonModel(this.modalTranslation.CANCEL_BUTTON, 'outline white', this.cancelAndCloseModal); - const saveButton: ButtonModel = new ButtonModel(this.modalTranslation.SAVE_BUTTON, 'blue', () => - this.updateOperation(), this.disableSaveButton); + const saveButton: ButtonModel = new ButtonModel(this.modalTranslation.SAVE_BUTTON, 'blue', + () => isEdit ? this.updateOperation() : this.createOperationCallback(), + this.disableSaveButton + ); const interfaceDataModal: ModalModel = new ModalModel('l', this.modalTranslation.EDIT_TITLE, '', [saveButton, cancelButton], 'custom'); this.modalInstance = this.modalServiceNg2.createCustomModal(interfaceDataModal); @@ -209,12 +249,14 @@ export class InterfaceDefinitionComponent { { deploymentArtifactsFilePath: this.deploymentArtifactsFilePath, toscaArtifactTypes: this.toscaArtifactTypes, - selectedInterface: interfaceModel, - selectedInterfaceOperation: operation, + selectedInterface: interfaceModel ? interfaceModel : new UIInterfaceModel(), + selectedInterfaceOperation: operation ? operation : new InterfaceOperationModel(), validityChangedCallback: this.disableSaveButton, isViewOnly: this.isViewOnly, + isEdit: isEdit, interfaceTypesMap: this.interfaceTypesMap, - }); + } + ); this.modalInstance.instance.open(); } @@ -239,6 +281,27 @@ export class InterfaceDefinitionComponent { this.modalServiceNg2.closeCurrentModal(); } + private createOperationCallback(): void { + const operationToUpdate = this.modalInstance.instance.dynamicContent.instance.operationToUpdate; + console.log('createOperationCallback', operationToUpdate); + console.log('this.component', this.component); + this.componentServiceNg2.createComponentInterfaceOperation(this.component.uniqueId, this.component.getTypeUrl(), operationToUpdate) + .subscribe((newOperation: InterfaceOperationModel) => { + const foundInterface = this.interfaces.find(value => value.type === newOperation.interfaceType); + if (foundInterface) { + foundInterface.operations.push(new UIOperationModel(new OperationModel(newOperation))); + } else { + const uiInterfaceModel = new UIInterfaceModel(); + uiInterfaceModel.type = newOperation.interfaceType; + uiInterfaceModel.uniqueId = newOperation.interfaceType; + uiInterfaceModel.operations = []; + uiInterfaceModel.operations.push(new UIOperationModel(new OperationModel(newOperation))); + this.interfaces.push(uiInterfaceModel); + } + }); + this.modalServiceNg2.closeCurrentModal(); + } + private handleEnableAddArtifactImplementation = (newOperation: InterfaceOperationModel): InterfaceOperationModel => { if (!this.isEnableAddArtifactImplementation()) { newOperation.implementation.artifactType = null; @@ -248,7 +311,7 @@ export class InterfaceDefinitionComponent { } private isEnableAddArtifactImplementation = (): boolean => { - return this.modalInstance.instance.dynamicContent.instance.enableAddArtifactImplementation; + return this.modalInstance.instance.dynamicContent.enableAddArtifactImplementation; } private initInterfaceDefinition() { @@ -257,7 +320,7 @@ export class InterfaceDefinitionComponent { this.topologyTemplateService.getComponentInterfaceOperations(this.component.componentType, this.component.uniqueId) .subscribe((response) => { if (response.interfaces) { - this.interfaces = _.map(response.interfaces, (interfaceModel) => new UIInterfaceModel(interfaceModel)); + this.interfaces = response.interfaces.map((interfaceModel) => new UIInterfaceModel(interfaceModel)); } this.isLoading = false; }); @@ -301,11 +364,11 @@ export class InterfaceDefinitionComponent { } isAllCollapsed(): boolean { - return _.every(this.interfaces, (interfaceData) => interfaceData.isCollapsed); + return this.interfaces.every((interfaceData) => interfaceData.isCollapsed); } isAllExpanded(): boolean { - return _.every(this.interfaces, (interfaceData) => !interfaceData.isCollapsed); + return this.interfaces.every((interfaceData) => !interfaceData.isCollapsed); } isInterfaceListEmpty(): boolean { @@ -313,8 +376,92 @@ export class InterfaceDefinitionComponent { } isOperationListEmpty(): boolean { - return _.filter(this.interfaces, (interfaceData) => - interfaceData.operations && interfaceData.operations.length > 0).length > 0; + return this.interfaces.filter((interfaceData) => interfaceData.operations && interfaceData.operations.length > 0).length > 0; + } + + onRemoveOperation = (event: Event, operation: OperationModel): void => { + event.stopPropagation(); + + const deleteButton: IModalButtonComponent = { + id: 'deleteButton', + text: this.modalTranslation.DELETE_BUTTON, + type: 'primary', + size: 'small', + closeModal: true, + callback: () => { + this.ComponentServiceNg2 + .deleteInterfaceOperation(this.component, operation) + .subscribe(() => { + const curInterf = this.interfaces.find((interf) => interf.type === operation.interfaceType); + const index = curInterf.operations.findIndex((el) => el.uniqueId === operation.uniqueId); + curInterf.operations.splice(index, 1); + if (!curInterf.operations.length) { + const interfIndex = this.interfaces.findIndex((interf) => interf.type === operation.interfaceType); + this.interfaces.splice(interfIndex, 1); + } + }); + } + }; + + const cancelButton: IModalButtonComponent = { + id: 'cancelButton', + text: this.modalTranslation.CANCEL_BUTTON, + type: 'secondary', + size: 'small', + closeModal: true, + callback: () => { + this.openOperation = null; + }, + }; + + this.ModalServiceSdcUI.openWarningModal( + this.modalTranslation.DELETE_TITLE, + this.modalTranslation.deleteText(operation.name), + 'deleteOperationModal', + [deleteButton, cancelButton], + ); + } + + private createOperation = (operation: OperationModel): void => { + this.ComponentServiceNg2.createInterfaceOperation(this.component, operation).subscribe((response: OperationModel) => { + this.openOperation = null; + + let curInterf = this.interfaces.find((interf) => interf.type === operation.interfaceType); + + if (!curInterf) { + curInterf = new UIInterfaceModel({ + type: response.interfaceType, + uniqueId: response.uniqueId, + operations: [] + }); + this.interfaces.push(curInterf); + } + + const newOpModel = new UIOperationModel(response); + curInterf.operations.push(newOpModel); + this.sortInterfaces(); + + if (operation.workflowAssociationType === WORKFLOW_ASSOCIATION_OPTIONS.EXTERNAL && operation.artifactData) { + this.ComponentServiceNg2.uploadInterfaceOperationArtifact(this.component, newOpModel, operation).subscribe(); + } else if (response.workflowId && operation.workflowAssociationType === WORKFLOW_ASSOCIATION_OPTIONS.EXISTING) { + this.WorkflowServiceNg2.associateWorkflowArtifact(this.component, response).subscribe(); + } else if (operation.workflowAssociationType === WORKFLOW_ASSOCIATION_OPTIONS.NEW) { + this.$state.go('workspace.plugins', {path: 'workflowDesigner'}); + } + }); + } + + private enableOrDisableSaveButton = (shouldEnable: boolean): void => { + const saveButton = this.modalInstance.instance.dynamicContent.getButtonById('saveButton'); + saveButton.disabled = !shouldEnable; + } + + private sortInterfaces(): void { + this.interfaces = this.interfaces.filter((interf) => interf.operations && interf.operations.length > 0); // remove empty interfaces + this.interfaces.sort((a, b) => a.type.localeCompare(b.type)); // sort interfaces alphabetically + this.interfaces.forEach((interf) => { + interf.operations.sort((a, b) => a.name.localeCompare(b.name)); // sort operations alphabetically + }); } } diff --git a/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.html b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.html new file mode 100644 index 0000000000..687c79fe86 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.html @@ -0,0 +1,211 @@ + + +
+ + +
+ +
+
+ + +
+ +
+ + +
+
+ + +
+ +
+ +
+ {{ 'OPERATION_DESCRIPTION' | translate }} + +
+ +
+
+ + +
+ +
+ +
+ +
+ + {{ operation.artifactFileName }} + +
+ +
+
+ +
+ + + +
+ +
+ + +
+
+ + + +
+
+ {{ 'OPERATION_PARAM_NAME' | translate }} + {{ 'OPERATION_PARAM_TYPE' | translate }} + + {{ 'OPERATION_PARAM_PROPERTY' | translate }} + + + + {{ 'OPERATION_PARAM_MANDATORY' | translate }} + ●●● +
+ +
+
{{ 'EMPTY_PARAM_TABLE_HEADER' | translate }}
+
+
+ {{ 'EMPTY_PARAM_TABLE_NO_SELECTED_WORKFLOW_1' | translate }} + {{ 'EMPTY_PARAM_TABLE_NO_SELECTED_WORKFLOW_2' | translate }} +
+
+ Only certified workflow versions can be assigned to an operation +
+
+
+ + + +
+ +
+
diff --git a/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.less b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.less new file mode 100644 index 0000000000..0afaa47ca3 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.less @@ -0,0 +1,200 @@ +/* +* ============LICENSE_START======================================================= +* SDC +* ================================================================================ +* Copyright (C) 2022 Nordix Foundation. 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. +* +* SPDX-License-Identifier: Apache-2.0 +* ============LICENSE_END========================================================= +*/ +@import '../../../../../assets/styles/variables.less'; +@import '../../../../../assets/styles/override.less'; + +.operation-creator-interface-definition { + font-family: @font-opensans-regular; + user-select: none; + padding-top: 12px; + padding-bottom: 20px; + + .i-sdc-form-label { + font-size: 12px; + } + + .w-sdc-form .i-sdc-form-item { + margin-bottom: 15px; + } + + textarea { + min-height: 74px; + margin-bottom: 18px; + } + + /deep/ .sdc-dropdown__component-container { + .sdc-dropdown__header { + height: 38px; + line-height: 35px; + + svg-icon { + margin: 13px 6px; + } + } + } + + /deep/ .sdc-input { + margin-bottom: 0; + + .sdc-input__input { + height: 38px; + } + } + + .side-by-side { + display: flex; + + .form-item { + flex: 1; + + &:first-child { + margin-right: 14px; + flex-basis: 37%; + flex-grow: 0; + flex-shrink: 0; + } + + &:nth-child(3) { + margin-left: 14px; + flex: 0.4; + } + + .i-sdc-form-file-upload { + height: 37px; + margin-bottom: 0; + + .i-sdc-form-file-name { + padding: 8px 10px; + } + + .i-sdc-form-file-upload-x-btn { + top: 13px; + } + + .file-upload-browse-btn { + height: 100%; + padding: 7px 6px; + z-index: 1; + } + } + + } + } + + .archive-warning { + font-family: @font-opensans-bold; + color: @main_color_i; + } + + .no-workflow-warning { + font-family: @font-opensans-bold; + color: @sdcui_color_red; + float: right; + } + + .input-param-title { + font-size: 16px; + text-transform: uppercase; + } + + .separator-buttons { + display: flex; + justify-content: space-between; + margin-top: 10px; + + .add-param-link { + &:not(.disabled):hover { + cursor: pointer; + } + } + + .tab { + width: 84px; + text-align: center; + } + } + + .generic-table { + max-height: 244px; + min-height: 91px; + background: @main_color_p; + + .header-row .header-cell { + .info-icon { + float: right; + position: relative; + top: 2px; + } + /deep/ .tooltip-inner { + padding: 2px; + max-width: 270px; + font-size: 11px; + } + &.remove { + padding: 10px; + font-size: 10px; + } + } + + .data-row { + &.empty-msg { + .bold-message { + font-family: @font-opensans-bold; + } + + :first-child { + &:not(:only-child) { + margin: 6px 0; + } + } + + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 14px; + } + } + + /deep/ .cell { + &.field-name, &.field-type { + flex: 1; + } + + &.field-property { + &, &:last-child { + flex: 1; + } + } + + &.field-mandatory { + flex: 0.5; + text-align: center; + } + + &.remove { + min-width: 40px; + max-width: 40px; + } + } + + } +} diff --git a/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.ts b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.ts new file mode 100644 index 0000000000..1897e3190d --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.ts @@ -0,0 +1,582 @@ +/* +* ============LICENSE_START======================================================= +* SDC +* ================================================================================ +* Copyright (C) 2022 Nordix Foundation. 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. +* +* SPDX-License-Identifier: Apache-2.0 +* ============LICENSE_END========================================================= +*/ +import * as _ from "lodash"; +import {Component, ViewChild} from '@angular/core'; + +import {TranslateService} from "app/ng2/shared/translator/translate.service"; +import {WorkflowServiceNg2} from 'app/ng2/services/workflow.service'; +import { + Capability, + InputBEModel, + InterfaceModel, + OperationModel, + OperationParameter, + WORKFLOW_ASSOCIATION_OPTIONS +} from 'app/models'; + +import {Tabs} from "app/ng2/components/ui/tabs/tabs.component"; +import { + DropdownValue +} from "app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component"; +import {IDropDownOption} from 'onap-ui-angular'; +import {DropDownComponent} from "onap-ui-angular/dist/components"; +import {DROPDOWN_OPTION_TYPE} from "app/utils"; +import {Subscription} from "rxjs"; + +export class DropDownOption implements IDropDownOption { + value: string; + label: string; + + constructor(value: string, label?: string) { + this.value = value; + this.label = label || value; + } +} + +class TypedDropDownOption extends DropDownOption { + type: string; + + constructor(value: string, label?: string, type?: string) { + super(value, label); + this.type = type; + } +} + +export interface OperationCreatorInput { + allWorkflows: Array, + inputOperation: OperationModel, + interfaces: Array, + inputProperties: Array, + enableWorkflowAssociation: boolean, + readonly: boolean, + interfaceTypes: { [interfaceType: string]: Array }, + validityChangedCallback: Function, + workflowIsOnline: boolean, + capabilities: Array +} + +@Component({ + selector: 'operation-creator-interface-definition', + templateUrl: './operation-creator-interface-definition.component.html', + styleUrls: ['./operation-creator-interface-definition.component.less'], + providers: [TranslateService] +}) + +export class OperationCreatorInterfaceDefinitionComponent implements OperationCreatorInput { + + input: OperationCreatorInput; + inputOperation: OperationModel; + interfaces: Array; + operation: OperationModel; + interfaceNames: Array = []; + interfaceTypes: { [interfaceType: string]: Array }; + operationNames: Array = []; + validityChangedCallback: Function; + capabilities: Array; + + allWorkflows: Array; + workflows: Array = []; + workflowVersions: Array = []; + inputProperties: Array = []; + archivedWorkflowId: string = '&'; + + inputParameters: Array = []; + noAssignInputParameters: Array = []; + assignInputParameters: { [key: string]: { [key: string]: Array; }; } = {}; + + outputParameters: Array = []; + noAssignOutputParameters: Array = []; + assignOutputParameters: { [key: string]: { [key: string]: Array; }; } = {}; + componentCapabilities: Array = []; + + tableParameters: Array = []; + operationOutputs: Array = []; + + associationOptions: Array = []; + workflowAssociationType: string; + + enableWorkflowAssociation: boolean; + workflowIsOnline: boolean; + isEditMode: boolean = false; + isLoading: boolean = false; + readonly: boolean; + + propertyTooltipText: String; + + TYPE_INPUT = 'Inputs'; + TYPE_OUTPUT = 'Outputs'; + + INTERFACE_OTHER_HEADER = 'Local Interfaces'; + INTERFACE_OTHER = 'Local'; + + @ViewChild('propertyInputTabs') propertyInputTabs: Tabs; + @ViewChild('operationNamesDropdown') operationNamesDropdown: DropDownComponent; + @ViewChild('workflowAssignmentDropdown') workflowAssignmentDropdown: DropDownComponent; + currentTab: String; + + constructor(private workflowServiceNg2: WorkflowServiceNg2, private translateService: TranslateService) { + this.translateService.languageChangedObservable.subscribe(lang => { + this.propertyTooltipText = this.translateService.translate("OPERATION_PROPERTY_TOOLTIP_TEXT"); + + this.associationOptions = [ + new DropDownOption(WORKFLOW_ASSOCIATION_OPTIONS.EXTERNAL, this.translateService.translate("EXTERNAL_WORKFLOW_ASSOCIATION")), + new DropDownOption(WORKFLOW_ASSOCIATION_OPTIONS.EXISTING, this.translateService.translate("EXISTING_WORKFLOW_ASSOCIATION")), + ]; + + this.workflowAssociationType = this.operation.workflowAssociationType; + }); + + this.currentTab = this.TYPE_INPUT; + } + + createInterfaceDropdown(type: string) { + let label = type; + const lastDot = label.lastIndexOf('.'); + if (lastDot > -1) { + label = label.substr(lastDot + 1); + } + return new TypedDropDownOption(type, label); + } + + ngOnInit() { + this.interfaceNames = _.map( + _.keys(this.interfaceTypes), + type => this.createInterfaceDropdown(type) + ); + this.interfaceNames.unshift(new TypedDropDownOption('Existing Interfaces', 'Existing Interfaces', DROPDOWN_OPTION_TYPE.HEADER)); + this.interfaceNames = this.interfaceNames.concat([ + new TypedDropDownOption(' ', ' ', DROPDOWN_OPTION_TYPE.HORIZONTAL_LINE), + new TypedDropDownOption(this.INTERFACE_OTHER_HEADER, this.INTERFACE_OTHER_HEADER, DROPDOWN_OPTION_TYPE.HEADER), + new TypedDropDownOption(this.INTERFACE_OTHER) + ]); + const inputOperation = this.inputOperation; + this.operation = new OperationModel(inputOperation || {}); + + this.operationOutputs = _.reduce( + this.interfaces, + (acc: Array, interf) => [ + ...acc, + ..._.filter( + interf.operations, + op => op.uniqueId !== this.operation.uniqueId + ), + ], + []); + + if (this.enableWorkflowAssociation) { + if (this.workflowIsOnline) { + this.workflows = _.map( + _.filter( + this.allWorkflows, + (workflow: any) => { + if (workflow.archiving === this.workflowServiceNg2.WF_STATE_ACTIVE) { + return true; + } + if (workflow.archiving === this.workflowServiceNg2.WF_STATE_ARCHIVED && + workflow.id === this.operation.workflowId) { + this.archivedWorkflowId = workflow.id; + return true; + } + return false; + } + ), + (workflow: any) => new DropdownValue(workflow.id, workflow.name) + ); + } else { + this.workflows = []; + } + } + this.reconstructOperation(); + this.filterCapabilities(); + this.validityChanged(); + this.updateTable(); + } + + ngAfterViewInit() { + if (this.workflowAssignmentDropdown) { + this.workflowAssignmentDropdown.allOptions = this.associationOptions && this.associationOptions.length ? + this.associationOptions : + [ + new DropDownOption(WORKFLOW_ASSOCIATION_OPTIONS.EXTERNAL, this.translateService.translate("EXTERNAL_WORKFLOW_ASSOCIATION")), + new DropDownOption(WORKFLOW_ASSOCIATION_OPTIONS.EXISTING, this.translateService.translate("EXISTING_WORKFLOW_ASSOCIATION")), + ]; + } + } + + reconstructOperation = () => { + + const buildAndUpdate = () => { + this.buildParams(); + this.updateTable(); + }; + + const inputOperation = this.inputOperation; + if (inputOperation) { + this.onSelectInterface(new DropDownOption(this.operation.interfaceType)); + + if (this.enableWorkflowAssociation && inputOperation.workflowVersionId && this.isUsingExistingWF(inputOperation)) { + this.assignInputParameters[this.operation.workflowId] = {[inputOperation.workflowVersionId]: []}; + this.assignOutputParameters[this.operation.workflowId] = {[inputOperation.workflowVersionId]: []}; + this.inputParameters = this.assignInputParameters[this.operation.workflowId][this.operation.workflowVersionId]; + this.outputParameters = this.assignOutputParameters[this.operation.workflowId][this.operation.workflowVersionId]; + + const sub = this.onSelectWorkflow(new DropDownOption(inputOperation.workflowId), inputOperation.workflowVersionId); + if (sub) { + sub.add(() => { + buildAndUpdate(); + this.operation.workflowVersionId = '-1'; + setTimeout(() => this.operation.workflowVersionId = this.inputOperation.workflowVersionId); + }); + } else { + buildAndUpdate(); + } + } else { + this.inputParameters = this.noAssignInputParameters; + this.outputParameters = this.noAssignOutputParameters; + buildAndUpdate(); + } + + if (inputOperation.uniqueId) { + this.isEditMode = true; + } + } + + } + + filterCapabilities() { + this.componentCapabilities = _.filter(this.capabilities, (cap: Capability) => cap.properties); + } + + buildParams = () => { + + if (this.inputOperation.outputs) { + this.currentTab = this.TYPE_OUTPUT; + this.updateTable(); + _.forEach( + [...this.inputOperation.outputs.listToscaDataDefinition].sort((a, b) => a.name.localeCompare(b.name)), + (output: OperationParameter) => { + this.addParam({...output, required: Boolean(output.required)}); + } + ); + } + + this.currentTab = this.TYPE_INPUT; + this.updateTable(); + if (this.inputOperation.inputs) { + _.forEach( + [...this.inputOperation.inputs.listToscaDataDefinition].sort((a, b) => a.name.localeCompare(b.name)), + (input: OperationParameter) => { + this.addParam({...input, required: Boolean(input.required)}); + } + ); + } + + } + + isInterfaceOther(): boolean { + return this.operation.interfaceType === this.INTERFACE_OTHER; + } + + onSelectInterface(interf: IDropDownOption) { + if (interf && this.operation.interfaceType !== interf.value) { + this.operation.name = null; + } + this.operation.interfaceType = interf && interf.value; + this.operationNames = !this.operation.interfaceType ? [] : ( + _.map( + this.interfaceTypes[this.operation.interfaceType], + name => { + const curInterf = _.find( + this.interfaces, + interf => interf.type === this.operation.interfaceType + ); + const existingOp = _.find( + curInterf && curInterf.operations || [], + op => op.name === name + ); + const ddType = (existingOp && existingOp.uniqueId !== this.operation.uniqueId) ? DROPDOWN_OPTION_TYPE.HORIZONTAL_LINE : DROPDOWN_OPTION_TYPE.SIMPLE; + return new TypedDropDownOption(name, name, ddType); + } + ) + ); + if (this.operationNamesDropdown) { + this.operationNamesDropdown.allOptions = this.operationNames; + } + this.validityChanged(); + } + + onSelectOperationName(name: IDropDownOption) { + if (name) { + this.operation.name = name.value; + } + this.validityChanged(); + } + + onChangeName() { + this.validityChanged(); + } + + get descriptionValue() { + return this.operation.description; + } + + set descriptionValue(v) { + this.operation.description = v || null; + this.validityChanged(); + } + + onSelectWorkflow(workflowId: DropDownOption, selectedVersionId?: string): Subscription { + + if (_.isUndefined(workflowId) || !this.workflowIsOnline) { + return; + } + + if (this.operation.workflowId === workflowId.value && !selectedVersionId) { + return; + } + + this.operation.workflowId = workflowId.value; + if (!this.assignInputParameters[this.operation.workflowId]) { + this.assignInputParameters[this.operation.workflowId] = {}; + this.assignOutputParameters[this.operation.workflowId] = {}; + } + this.operation.workflowName = workflowId.label; + if (!this.assignInputParameters[this.operation.workflowName]) { + this.assignInputParameters[this.operation.workflowName] = {}; + this.assignOutputParameters[this.operation.workflowName] = {}; + } + + this.isLoading = true; + this.validityChanged(); + return this.workflowServiceNg2.getWorkflowVersions(this.operation.workflowId).subscribe((versions: Array) => { + this.isLoading = false; + + this.workflowVersions = _.map( + _.filter( + versions, version => version.state === this.workflowServiceNg2.VERSION_STATE_CERTIFIED + ).sort((a, b) => a.name.localeCompare(b.name)), + (version: any) => { + if (!this.assignInputParameters[this.operation.workflowId][version.id] && version.id !== selectedVersionId) { + this.assignInputParameters[this.operation.workflowId][version.id] = _.map(version.inputs, (input: any) => { + return new OperationParameter({ + ...input, + type: input.type.toLowerCase(), + required: Boolean(input.mandatory) + }); + }) + .sort((a, b) => a.name.localeCompare(b.name)); + + this.assignOutputParameters[this.operation.workflowId][version.id] = _.map(version.outputs, (output: any) => { + return new OperationParameter({ + ...output, + type: output.type.toLowerCase(), + required: Boolean(output.mandatory) + }); + }) + .sort((a, b) => a.name.localeCompare(b.name)); + } + return new DropdownValue(version.id, `V ${version.name}`); + } + ); + if (!selectedVersionId && this.workflowVersions.length) { + this.operation.workflowVersionId = _.last(this.workflowVersions).value; + this.operation.workflowVersion = _.last(this.workflowVersions).label; + } + + this.changeWorkflowVersion(new DropDownOption(this.operation.workflowVersionId)); + this.validityChanged(); + }); + + } + + changeWorkflowVersion(versionId: DropDownOption) { + + if (_.isUndefined(versionId) || !this.workflowIsOnline) { + return; + } + + this.operation.workflowVersionId = versionId.value; + this.inputParameters = this.assignInputParameters[this.operation.workflowId][this.operation.workflowVersionId]; + this.outputParameters = this.assignOutputParameters[this.operation.workflowId][this.operation.workflowVersionId]; + this.updateTable(); + this.validityChanged(); + + } + + toggleAssociateWorkflow(type: DropDownOption) { + + if (_.isUndefined(type)) { + return; + } + + this.operation.workflowAssociationType = type.value; + this.workflowAssociationType = this.operation.workflowAssociationType; + + if (!this.isUsingExistingWF()) { + this.inputParameters = this.noAssignInputParameters; + this.outputParameters = this.noAssignOutputParameters; + } else { + if (!this.operation.workflowId || !this.operation.workflowVersionId) { + this.inputParameters = []; + this.outputParameters = []; + } else { + this.inputParameters = this.assignInputParameters[this.operation.workflowId][this.operation.workflowVersionId]; + this.outputParameters = this.assignOutputParameters[this.operation.workflowId][this.operation.workflowVersionId]; + } + } + + this.updateTable(); + this.validityChanged(); + + } + + onChangeArtifactFile(e: any) { + const file = e.target.files && e.target.files[0]; + this.operation.artifactFileName = file && file.name; + + if (!this.operation.artifactFileName) { + this.operation.artifactData = null; + this.validityChanged(); + return; + } + + const reader = new FileReader(); + reader.onloadend = () => { + this.isLoading = false; + const result = reader.result; + this.operation.artifactData = result.substring(result.indexOf(',') + 1); + this.validityChanged(); + } + + this.isLoading = true; + reader.readAsDataURL(file); + } + + tabChanged = (event) => { + + this.currentTab = event.title; + this.updateTable(); + + } + + updateTable() { + + switch (this.currentTab) { + case this.TYPE_INPUT: + this.tableParameters = this.inputParameters; + break; + case this.TYPE_OUTPUT: + this.tableParameters = this.outputParameters; + break; + } + + } + + addParam(param?: OperationParameter): void { + this.tableParameters.push(new OperationParameter(param || {required: false})); + this.validityChanged(); + } + + canAdd = (): boolean => { + + let valid = true; + if (this.currentTab === this.TYPE_INPUT) { + _.forEach(this.inputParameters, param => { + if (!param.name || !param.inputId) { + valid = false; + } + }); + } else { + _.forEach(this.outputParameters, param => { + if (!param.name || !param.type) { + valid = false; + } + }); + } + + return valid; + + } + + isParamsValid = (): boolean => { + + let valid = true; + _.forEach(this.inputParameters, param => { + if (!param.name || !param.inputId) { + valid = false; + } + }); + _.forEach(this.outputParameters, param => { + if (!param.name || !param.type) { + valid = false; + } + }); + + return valid; + + } + + onRemoveParam = (param: OperationParameter): void => { + let index = _.indexOf(this.tableParameters, param); + this.tableParameters.splice(index, 1); + this.validityChanged(); + } + + createParamLists = () => { + this.operation.createInputsList(this.inputParameters); + this.operation.createOutputsList(this.outputParameters); + } + + isUsingExistingWF = (operation?: OperationModel): boolean => { + operation = operation || this.operation; + return operation.workflowAssociationType === WORKFLOW_ASSOCIATION_OPTIONS.EXISTING; + } + + isUsingExternalWF = (operation?: OperationModel): boolean => { + operation = operation || this.operation; + return operation.workflowAssociationType === WORKFLOW_ASSOCIATION_OPTIONS.EXTERNAL; + } + + shouldCreateWF = (operation?: OperationModel): boolean => { + operation = operation || this.operation; + return operation.workflowAssociationType === WORKFLOW_ASSOCIATION_OPTIONS.NEW; + } + + checkFormValidForSubmit = (): boolean => { + return this.operation.name && + (!this.isUsingExistingWF() || this.operation.workflowVersionId) && + this.isParamsValid(); + } + + validityChanged = () => { + let validState = this.checkFormValidForSubmit(); + this.validityChangedCallback(validState); + } + + getSelectedDropdown(options: DropdownValue[], selectedValue: string): DropdownValue { + const selectedDropdown = _.find(options, (option) => option.value === selectedValue); + return selectedDropdown || this.toDropDownOption(null); + } + + toDropDownOption(val: string) { + return {value: val, label: val}; + } +} diff --git a/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.module.ts b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.module.ts new file mode 100644 index 0000000000..7f75240735 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.module.ts @@ -0,0 +1,52 @@ +/* +* ============LICENSE_START======================================================= +* SDC +* ================================================================================ +* Copyright (C) 2022 Nordix Foundation. 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. +* +* SPDX-License-Identifier: Apache-2.0 +* ============LICENSE_END========================================================= +*/ +import {NgModule} from "@angular/core"; +import {ParamRowComponent} from "./param-row/param-row.component"; +import {OperationCreatorInterfaceDefinitionComponent} from "./operation-creator-interface-definition.component"; +import {UiElementsModule} from "../../../components/ui/ui-elements.module"; +import {TranslateModule} from "../../../shared/translator/translate.module"; +import {SdcUiComponentsModule} from "onap-ui-angular"; +import {CommonModule} from "@angular/common"; +import {FormsModule} from "@angular/forms"; +import {FormElementsModule} from "../../../components/ui/form-components/form-elements.module"; + +@NgModule({ + declarations: [ + OperationCreatorInterfaceDefinitionComponent, + ParamRowComponent + ], + imports: [ + CommonModule, + SdcUiComponentsModule, + FormsModule, + FormElementsModule, + TranslateModule, + UiElementsModule + ], + exports: [], + entryComponents: [ + OperationCreatorInterfaceDefinitionComponent + ], + providers: [] +}) + +export class OperationCreatorInterfaceDefinitionModule { +} diff --git a/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/param-row/param-row.component.html b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/param-row/param-row.component.html new file mode 100644 index 0000000000..1ed0375a16 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/param-row/param-row.component.html @@ -0,0 +1,100 @@ + + +
+ + + {{param.name}} +
+ +
+ + + {{param.type}} +
+ +
+ + + {{ 'PARAM_NONE_OF_TYPE' | translate }} + +
+ +
+ + +
+ +
+ + +
diff --git a/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/param-row/param-row.component.less b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/param-row/param-row.component.less new file mode 100644 index 0000000000..d616bad1f9 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/param-row/param-row.component.less @@ -0,0 +1,73 @@ +/* +* ============LICENSE_START======================================================= +* SDC +* ================================================================================ +* Copyright (C) 2022 Nordix Foundation. 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. +* +* SPDX-License-Identifier: Apache-2.0 +* ============LICENSE_END========================================================= +*/ +@import '../../../../../../assets/styles/variables.less'; + +.remove { + display: flex; + align-items: center; + justify-content: center; + + svg-icon { + position: relative; + right: -3px; + + &:hover { + cursor: pointer; + } + } +} + + +.cell { + min-height: 50px; + padding: 10px; + display: flex; + align-items: center; + + > * { + flex-basis: 100%; + } + + /deep/ select { + height: 30px; + } + + input { + height: 30px; + border: none; + padding-left: 10px; + } + + select { + width: 100%; + } + + &.field-property { + &:last-child { + flex: 1; + } + + .no-properties-error { + color: @func_color_q; + font-style: italic; + } + } +} diff --git a/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/param-row/param-row.component.ts b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/param-row/param-row.component.ts new file mode 100644 index 0000000000..43760ba117 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/param-row/param-row.component.ts @@ -0,0 +1,257 @@ +/* +* ============LICENSE_START======================================================= +* SDC +* ================================================================================ +* Copyright (C) 2022 Nordix Foundation. 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. +* +* SPDX-License-Identifier: Apache-2.0 +* ============LICENSE_END========================================================= +*/ +import {Component, Input} from '@angular/core'; +import {DataTypeService} from "app/ng2/services/data-type.service"; +import { + Capability, + DataTypeModel, + InputBEModel, + OperationModel, + OperationParameter +} from 'app/models'; +import { + DropdownValue +} from "app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component"; +import {WorkspaceService} from "../../../workspace/workspace.service"; + +class DropdownValueType extends DropdownValue { + type: string; + + constructor(value: string, label: string, type?: string) { + super(value, label); + if (type) { + this.type = type; + } + } +} + +@Component({ + selector: 'param-row', + templateUrl: './param-row.component.html', + styleUrls: ['./param-row.component.less'] +}) + +export class ParamRowComponent { + @Input() param: OperationParameter; + @Input() inputProps: Array; + @Input() operationOutputs: Array; + @Input() capabilitiesProps: Array; + @Input() onRemoveParam: Function; + @Input() isAssociateWorkflow: boolean; + @Input() readonly: boolean; + @Input() isInputParam: boolean; + @Input() validityChanged: Function; + + propTypeEnum: Array = []; + operationOutputCats: Array<{ operationName: string, outputs: Array }> = []; + filteredInputProps: Array = []; + filteredCapabilitiesProps: Array<{ capabilityName: string, properties: Array }> = []; + + constructor(private dataTypeService: DataTypeService, protected workspaceService: WorkspaceService) { + } + + ngOnInit() { + if (this.isInputParam) { + this.propTypeEnum = _.uniq( + _.map( + _.concat( + this.getPrimitiveSubtypes(), + _.reduce( + this.operationOutputs, + (acc, op) => [...acc, ...op.outputs.listToscaDataDefinition], + []), + _.reduce( + this.capabilitiesProps, + (acc, capab) => [...acc, ...capab.properties], + []) + ), + prop => prop.type + ) + ); + } else { + const dataTypes: Array = _.toArray(this.dataTypeService.getDataTypeByModel(this.workspaceService.metadata.model)); + this.propTypeEnum = _.concat( + _.map( + _.filter( + dataTypes, + type => this.isTypePrimitive(type.name) + ), + type => type.name + ).sort(), + _.map( + _.filter( + dataTypes, + type => !this.isTypePrimitive(type.name) + ), + type => type.name + ).sort() + ); + } + + this.onChangeType(); + this.validityChanged(); + } + + onChangeName() { + this.validityChanged(); + } + + onChangeType() { + if (!this.isInputParam) { + this.validityChanged(); + return; + } + + this.filteredInputProps = _.map( + _.filter( + this.getPrimitiveSubtypes(), + prop => !this.param.type || prop.type === this.param.type + ), + prop => new DropdownValue(prop.uniqueId, prop.name) + ); + this.filteredInputProps.unshift(new DropdownValue("", "")); + + this.operationOutputCats = _.filter( + _.map( + this.operationOutputs, + op => { + return { + operationName: `${op.displayType()}.${op.name}`, + outputs: _.map( + _.filter(op.outputs.listToscaDataDefinition, output => !this.param.type || output.type === this.param.type), + output => new DropdownValueType( + `${op.interfaceType}.${op.name}.${output.name}`, + output.name, + output.type + ) + ) + }; + } + ), + category => category.outputs.length > 0 + ); + + this.filteredCapabilitiesProps = _.filter( + _.map( + this.capabilitiesProps, + cap => { + return { + capabilityName: cap.name, + properties: _.map( + _.filter(cap.properties, prop => !this.param.type || prop.type === this.param.type), + prop => new DropdownValueType( + prop.uniqueId, + prop.name, + prop.type + ) + ) + }; + } + ), + capability => capability.properties.length > 0 + ); + + if (this.param.inputId) { + const selProp = this.getSelectedProp(); + if (selProp && selProp.type === this.param.type) { + this.param.inputId = '-1'; + setTimeout(() => this.param.inputId = selProp.uniqueId || selProp.value); + } else { + this.param.inputId = null; + } + } + + this.validityChanged(); + } + + onChangeProperty() { + const newProp = this.getSelectedProp(); + + if (!this.param.type) { + this.param.type = newProp.type; + this.onChangeType(); + } + + if (!this.param.name) { + this.param.name = newProp.name || newProp.label; + } + + this.validityChanged(); + } + + getPrimitiveSubtypes(): Array { + const flattenedProps: Array = []; + const dataTypes = this.dataTypeService.getDataTypeByModel(this.workspaceService.metadata.model); + + _.forEach(this.inputProps, prop => { + const type: DataTypeModel = _.find( + _.toArray(dataTypes), + (type: DataTypeModel) => type.name === prop.type + ); + flattenedProps.push(prop); + if (!type) { + console.error('Could not find prop in dataTypes: ', prop); + } else { + if (type.properties) { + _.forEach(type.properties, subType => { + if (this.isTypePrimitive(subType.type)) { + flattenedProps.push({ + type: subType.type, + name: `${prop.name}.${subType.name}`, + uniqueId: `${prop.uniqueId}.${subType.name}` + }); + } + }); + } + } + }); + + return flattenedProps; + } + + getSelectedProp() { + return _.find( + this.getPrimitiveSubtypes(), + prop => this.param.inputId === prop.uniqueId + ) || _.find( + _.reduce( + this.operationOutputCats, + (acc, cat) => [...acc, ...cat.outputs], + []), + (out: DropdownValueType) => this.param.inputId === out.value + ) || _.find( + _.reduce( + this.filteredCapabilitiesProps, + (acc, cap) => [...acc, ...cap.properties], + []), + (prop: DropdownValueType) => this.param.inputId === prop.value + ); + } + + isTypePrimitive(type: string): boolean { + return ( + type === 'string' || + type === 'integer' || + type === 'float' || + type === 'boolean' + ); + } +} diff --git a/catalog-ui/src/app/ng2/pages/interface-operation/interface-operation.page.component.ts b/catalog-ui/src/app/ng2/pages/interface-operation/interface-operation.page.component.ts index 4f93e727ec..75a31b96c0 100644 --- a/catalog-ui/src/app/ng2/pages/interface-operation/interface-operation.page.component.ts +++ b/catalog-ui/src/app/ng2/pages/interface-operation/interface-operation.page.component.ts @@ -1,36 +1,52 @@ +/* +* ============LICENSE_START======================================================= +* SDC +* ================================================================================ +* Copyright (C) 2022 Nordix Foundation. 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. +* +* SPDX-License-Identifier: Apache-2.0 +* ============LICENSE_END========================================================= +*/ import * as _ from "lodash"; -import { Component, Input, Inject } from '@angular/core'; -import {Component as IComponent } from 'app/models/components/component'; +import {Component, Inject, Input} from '@angular/core'; +import {Component as IComponent} from 'app/models/components/component'; -import { SdcConfigToken, ISdcConfig } from "app/ng2/config/sdc-config.config"; -import {TranslateService } from "app/ng2/shared/translator/translate.service"; +import {ISdcConfig, SdcConfigToken} from "app/ng2/config/sdc-config.config"; +import {TranslateService} from "app/ng2/shared/translator/translate.service"; -import {Observable } from "rxjs/Observable"; +import {Observable} from "rxjs/Observable"; -import { ModalComponent } from 'onap-ui-angular/dist/modals/modal.component'; -import {ModalService } from 'app/ng2/services/modal.service'; +import {ModalComponent} from 'onap-ui-angular/dist/modals/modal.component'; +import {ModalService} from 'app/ng2/services/modal.service'; import { + CapabilitiesGroup, + Capability, InputBEModel, - OperationModel, InterfaceModel, - WORKFLOW_ASSOCIATION_OPTIONS, - CapabilitiesGroup, - Capability + OperationModel, + WORKFLOW_ASSOCIATION_OPTIONS } from 'app/models'; -// import {SdcUiComponents } from 'sdc-ui/lib/angular'; -// import {ModalButtonComponent } from 'sdc-ui/lib/angular/components'; -// import { IModalButtonComponent, IModalConfig } from 'sdc-ui/lib/angular/modals/models/modal-config'; +import {ComponentServiceNg2} from 'app/ng2/services/component-services/component.service'; +import {PluginsService} from 'app/ng2/services/plugins.service'; +import {WorkflowServiceNg2} from 'app/ng2/services/workflow.service'; -import {ComponentServiceNg2 } from 'app/ng2/services/component-services/component.service'; -import {PluginsService } from 'app/ng2/services/plugins.service'; -import {WorkflowServiceNg2 } from 'app/ng2/services/workflow.service'; - -import { OperationCreatorComponent, OperationCreatorInput } from 'app/ng2/pages/interface-operation/operation-creator/operation-creator.component'; -import { IModalButtonComponent } from 'onap-ui-angular'; -import { ModalButtonComponent } from 'onap-ui-angular'; -import { IModalConfig } from 'onap-ui-angular'; -import { SdcUiServices } from 'onap-ui-angular'; +import { + OperationCreatorComponent, + OperationCreatorInput +} from 'app/ng2/pages/interface-operation/operation-creator/operation-creator.component'; +import {IModalButtonComponent, IModalConfig, SdcUiServices} from 'onap-ui-angular'; export class UIOperationModel extends OperationModel { isCollapsed: boolean = true; @@ -143,7 +159,6 @@ export class InterfaceOperationComponent { private WorkflowServiceNg2: WorkflowServiceNg2, private ModalServiceNg2: ModalService, private ModalServiceSdcUI: SdcUiServices.ModalService - ) { this.enableWorkflowAssociation = sdcConfig.enableWorkflowAssociation; this.modalTranslation = new ModalTranslation(TranslateService); @@ -164,7 +179,7 @@ export class InterfaceOperationComponent { this.sortInterfaces(); this.inputs = response[1].inputs; this.interfaceTypes = response[2]; - this.workflows = (workflows.items) ? workflows.items: workflows; + this.workflows = (workflows.items) ? workflows.items : workflows; this.capabilities = response[3].capabilities; }; if (this.enableWorkflowAssociation && this.workflowIsOnline) { @@ -308,16 +323,16 @@ export class InterfaceOperationComponent { closeModal: true, callback: () => { this.ComponentServiceNg2 - .deleteInterfaceOperation(this.component, operation) - .subscribe(() => { - const curInterf = _.find(this.interfaces, (interf) => interf.type === operation.interfaceType); - const index = _.findIndex(curInterf.operations, (el) => el.uniqueId === operation.uniqueId); - curInterf.operations.splice(index, 1); - if (!curInterf.operations.length) { - const interfIndex = _.findIndex(this.interfaces, (interf) => interf.type === operation.interfaceType); - this.interfaces.splice(interfIndex, 1); - } - }); + .deleteInterfaceOperation(this.component, operation) + .subscribe(() => { + const curInterf = _.find(this.interfaces, (interf) => interf.type === operation.interfaceType); + const index = _.findIndex(curInterf.operations, (el) => el.uniqueId === operation.uniqueId); + curInterf.operations.splice(index, 1); + if (!curInterf.operations.length) { + const interfIndex = _.findIndex(this.interfaces, (interf) => interf.type === operation.interfaceType); + this.interfaces.splice(interfIndex, 1); + } + }); } }; @@ -372,7 +387,7 @@ export class InterfaceOperationComponent { } else if (response.workflowId && operation.workflowAssociationType === WORKFLOW_ASSOCIATION_OPTIONS.EXISTING) { this.WorkflowServiceNg2.associateWorkflowArtifact(this.component, response).subscribe(); } else if (operation.workflowAssociationType === WORKFLOW_ASSOCIATION_OPTIONS.NEW) { - this.$state.go('workspace.plugins', { path: 'workflowDesigner' }); + this.$state.go('workspace.plugins', {path: 'workflowDesigner'}); } }); } diff --git a/catalog-ui/src/app/ng2/services/component-services/component.service.ts b/catalog-ui/src/app/ng2/services/component-services/component.service.ts index 8d91eede84..3889b73f49 100644 --- a/catalog-ui/src/app/ng2/services/component-services/component.service.ts +++ b/catalog-ui/src/app/ng2/services/component-services/component.service.ts @@ -238,12 +238,44 @@ export class ComponentServiceNg2 { }); } + createComponentInterfaceOperation(componentMetaDataId: string, + componentMetaDataType: string, + operation: InterfaceOperationModel): Observable { + const operationList = { + interfaces: { + [operation.interfaceType]: { + type: operation.interfaceType, + operations: { + [operation.name]: new BEInterfaceOperationModel(operation) + } + } + } + }; + console.log(operationList); + console.log(this.baseUrl + componentMetaDataType + componentMetaDataId + '/resource/interfaceOperation') + return this.http.post(this.baseUrl + componentMetaDataType + componentMetaDataId + '/resource/interfaceOperation', operationList) + .map((res: any) => { + const interf: InterfaceModel = _.find(res.interfaces, interf => interf.type === operation.interfaceType); + const newOperation: OperationModel = _.find(interf.operations, op => op.name === operation.name); + + return new InterfaceOperationModel({ + ...newOperation, + interfaceType: interf.type, + interfaceId: interf.uniqueId, + }); + }); + } + deleteInterfaceOperation(component: Component, operation: OperationModel): Observable { return this.http.delete(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/interfaces/' + operation.interfaceId + '/operations/' + operation.uniqueId); } getInterfaceTypes(component: Component): Observable<{ [id: string]: Array }> { - return this.http.get(this.baseUrl + 'interfaceLifecycleTypes' + ((component && component.model) ? '?model=' + component.model : '')) + return this.getInterfaceTypesByModel(component && component.model); + } + + getInterfaceTypesByModel(model: string): Observable<{ [id: string]: Array }> { + return this.http.get(this.baseUrl + 'interfaceLifecycleTypes' + ((model) ? '?model=' + model : '')) .map((res: any) => { const interfaceMap = {}; if (!res) { diff --git a/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/execute/sanity/ImportVfcUiTest.java b/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/execute/sanity/ImportVfcUiTest.java index 62d8be8a33..e1705a2255 100644 --- a/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/execute/sanity/ImportVfcUiTest.java +++ b/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/execute/sanity/ImportVfcUiTest.java @@ -68,11 +68,9 @@ import org.onap.sdc.frontend.ci.tests.utilities.FileHandling; import org.openecomp.sdc.be.model.ComponentInstance; import org.openqa.selenium.WebDriver; import org.testng.annotations.BeforeClass; -import org.testng.annotations.Ignore; import org.testng.annotations.Test; import org.yaml.snakeyaml.Yaml; -@Ignore // solved in https://gerrit.onap.org/r/c/sdc/+/127976 public class ImportVfcUiTest extends SetupCDTest { private String filePath; diff --git a/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/execute/sanity/ServiceTemplateDesignUiTests.java b/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/execute/sanity/ServiceTemplateDesignUiTests.java index d6e04d6e13..96193f69a2 100644 --- a/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/execute/sanity/ServiceTemplateDesignUiTests.java +++ b/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/execute/sanity/ServiceTemplateDesignUiTests.java @@ -91,7 +91,6 @@ import org.openqa.selenium.WebDriver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Ignore; import org.testng.annotations.Test; import org.yaml.snakeyaml.Yaml; @@ -241,7 +240,6 @@ public class ServiceTemplateDesignUiTests extends SetupCDTest { } @Test(dependsOnMethods = "addRelationshipTemplate") - @Ignore // solved in https://gerrit.onap.org/r/c/sdc/+/127976 public void updateInterfaceOperation() throws Exception { homePage.isLoaded(); componentPage = (ComponentPage) homePage.clickOnComponent(vfResourceCreateData.getName());