From 5c1f5756bcb5856e2d8b35e3c6ac206f891f8695 Mon Sep 17 00:00:00 2001 From: aribeiro Date: Thu, 19 Nov 2020 13:28:43 +0000 Subject: Add support for updating interface operations Allows to update interface operations on a component instance. Issue-ID: SDC-3446 Signed-off-by: aribeiro Signed-off-by: andre.schmid Change-Id: I6a2c44997c04d9d9ea298e3d0bc971da7b137799 --- .../impl/ComponentInstanceBusinessLogic.java | 24 +- .../ComponentInterfaceOperationBusinessLogic.java | 221 ++++++++++++++++++ .../impl/InterfaceDefinitionHandler.java | 9 +- .../be/components/impl/ResourceImportManager.java | 3 +- .../ComponentInterfaceOperationServlet.java | 179 +++++++++++++++ .../be/tosca/InterfacesOperationsConverter.java | 166 ++++++++------ .../openecomp/sdc/be/tosca/ToscaExportHandler.java | 67 +++--- .../be/components/ResourceImportManagerTest.java | 29 ++- ...mponentInterfaceOperationBusinessLogicTest.java | 173 +++++++++++++++ .../components/impl/InputsBusinessLogicTest.java | 8 +- .../tosca/InterfacesOperationsConverterTest.java | 16 +- .../sdc/be/model/ComponentParametersView.java | 3 + .../operations/NodeTemplateOperation.java | 18 +- .../componentsInstances/componentInstance.ts | 2 + catalog-ui/src/app/models/inputs.ts | 2 + catalog-ui/src/app/models/interfaceOperation.ts | 109 +++++++++ catalog-ui/src/app/ng2/app.module.ts | 2 + .../zone-instance/zone-instance.component.ts | 4 +- .../graph/composition-graph.component.html | 2 +- .../interface-operations.component.html | 80 +++++++ .../interface-operations.component.less | 216 ++++++++++++++++++ .../interface-operations.component.ts | 247 +++++++++++++++++++++ .../input-param-row/input-param-row.component.html | 44 ++++ .../input-param-row/input-param-row.component.less | 72 ++++++ .../input-param-row/input-param-row.component.ts | 48 ++++ .../interface-operation-handler.component.html | 86 +++++++ .../interface-operation-handler.component.less | 200 +++++++++++++++++ .../interface-operation-handler.component.ts | 130 +++++++++++ .../interface-operation-handler.module.ts | 55 +++++ .../panel/composition-panel.component.spec.ts | 15 +- .../panel/composition-panel.component.ts | 12 +- .../composition/panel/composition-panel.module.ts | 10 +- .../topology-template.service.ts | 31 ++- catalog-ui/src/assets/languages/en_US.json | 4 + 34 files changed, 2129 insertions(+), 158 deletions(-) create mode 100644 catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ComponentInterfaceOperationBusinessLogic.java create mode 100644 catalog-be/src/main/java/org/openecomp/sdc/be/servlets/ComponentInterfaceOperationServlet.java create mode 100644 catalog-be/src/test/java/org/openecomp/sdc/be/components/impl/ComponentInterfaceOperationBusinessLogicTest.java create mode 100644 catalog-ui/src/app/models/interfaceOperation.ts create mode 100644 catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.html create mode 100644 catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.less create mode 100644 catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.ts create mode 100644 catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.html create mode 100644 catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.less create mode 100644 catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.ts create mode 100644 catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.html create mode 100644 catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.less create mode 100644 catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.ts create mode 100644 catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.module.ts diff --git a/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ComponentInstanceBusinessLogic.java b/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ComponentInstanceBusinessLogic.java index 7c463ac387..1104621379 100644 --- a/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ComponentInstanceBusinessLogic.java +++ b/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ComponentInstanceBusinessLogic.java @@ -349,17 +349,17 @@ public class ComponentInstanceBusinessLogic extends BaseBusinessLogic { final OriginTypeEnum originType = resourceInstance.getOriginType(); validateInstanceName(resourceInstance); if (originType == OriginTypeEnum.ServiceProxy) { - origComponent = getOrigComponentForServiceProxy(containerComponent, resourceInstance); - } else if (originType == OriginTypeEnum.ServiceSubstitution){ - origComponent = getOrigComponentForServiceSubstitution(resourceInstance); - } else { - origComponent = getAndValidateOriginComponentOfComponentInstance(containerComponent, resourceInstance); - validateOriginAndResourceInstanceTypes(containerComponent, origComponent, originType); - } - validateResourceInstanceState(containerComponent, origComponent); - overrideFields(origComponent, resourceInstance); - compositionBusinessLogic.validateAndSetDefaultCoordinates(resourceInstance); + origComponent = getOrigComponentForServiceProxy(containerComponent, resourceInstance); + } else if (originType == OriginTypeEnum.ServiceSubstitution) { + origComponent = getOrigComponentForServiceSubstitution(resourceInstance); + } else { + origComponent = getAndValidateOriginComponentOfComponentInstance(containerComponent, resourceInstance); + validateOriginAndResourceInstanceTypes(containerComponent, origComponent, originType); } + validateResourceInstanceState(containerComponent, origComponent); + overrideFields(origComponent, resourceInstance); + compositionBusinessLogic.validateAndSetDefaultCoordinates(resourceInstance); + } return createComponent(needLock, containerComponent,origComponent, resourceInstance, user); } @@ -2444,6 +2444,10 @@ public class ComponentInstanceBusinessLogic extends BaseBusinessLogic { ActionStatus actionStatus = ActionStatus.COMPONENT_IS_ARCHIVED; throw new ByActionStatusComponentException(actionStatus, component.getName()); } + final Map componentInterfaces = component.getInterfaces(); + if(MapUtils.isNotEmpty(componentInterfaces)) { + componentInterfaces.forEach(componentInstance::addInterface); + } return component; } 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 new file mode 100644 index 0000000000..e32c51f7da --- /dev/null +++ b/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ComponentInterfaceOperationBusinessLogic.java @@ -0,0 +1,221 @@ +/* + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2021 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.zone-instance.component.ts + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.components.impl; + +import fj.data.Either; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.openecomp.sdc.be.components.impl.exceptions.BusinessLogicException; +import org.openecomp.sdc.be.components.validation.ComponentValidations; +import org.openecomp.sdc.be.dao.api.ActionStatus; +import org.openecomp.sdc.be.datatypes.elements.ArtifactDataDefinition; +import org.openecomp.sdc.be.datatypes.elements.OperationDataDefinition; +import org.openecomp.sdc.be.datatypes.enums.ComponentTypeEnum; +import org.openecomp.sdc.be.model.Component; +import org.openecomp.sdc.be.model.ComponentInstance; +import org.openecomp.sdc.be.model.ComponentInstanceInterface; +import org.openecomp.sdc.be.model.ComponentParametersView; +import org.openecomp.sdc.be.model.InterfaceDefinition; +import org.openecomp.sdc.be.model.User; +import org.openecomp.sdc.be.model.jsonjanusgraph.operations.ArtifactsOperations; +import org.openecomp.sdc.be.model.jsonjanusgraph.operations.InterfaceOperation; +import org.openecomp.sdc.be.model.operations.api.IElementOperation; +import org.openecomp.sdc.be.model.operations.api.IGroupInstanceOperation; +import org.openecomp.sdc.be.model.operations.api.IGroupOperation; +import org.openecomp.sdc.be.model.operations.api.IGroupTypeOperation; +import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus; +import org.openecomp.sdc.be.model.operations.impl.InterfaceLifecycleOperation; +import org.openecomp.sdc.be.user.Role; +import org.openecomp.sdc.common.datastructure.Wrapper; +import org.openecomp.sdc.exception.ResponseFormat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +@org.springframework.stereotype.Component("componentInterfaceOperationBusinessLogic") +public class ComponentInterfaceOperationBusinessLogic extends BaseBusinessLogic { + + private final ComponentValidations componentValidations; + + private static final Logger LOGGER = LoggerFactory.getLogger(ComponentInterfaceOperationBusinessLogic .class); + + @Autowired + public ComponentInterfaceOperationBusinessLogic(final IElementOperation elementDao, + final IGroupOperation groupOperation, + final IGroupInstanceOperation groupInstanceOperation, + final IGroupTypeOperation groupTypeOperation, + final InterfaceOperation interfaceOperation, + final InterfaceLifecycleOperation interfaceLifecycleTypeOperation, + final ArtifactsOperations artifactToscaOperation, + final ComponentValidations componentValidations) { + super(elementDao, groupOperation, groupInstanceOperation, groupTypeOperation, interfaceOperation, + interfaceLifecycleTypeOperation, artifactToscaOperation); + this.componentValidations = componentValidations; + } + + public Optional updateComponentInstanceInterfaceOperation(final String componentId, + final String componentInstanceId, + final InterfaceDefinition interfaceDefinition, + final ComponentTypeEnum componentTypeEnum, + final Wrapper errorWrapper, + final boolean shouldLock) + throws BusinessLogicException { + + final Component component = getComponent(componentId); + final Optional componentInstanceOptional = componentValidations + .getComponentInstance(component, componentInstanceId); + ResponseFormat responseFormat; + if (componentInstanceOptional.isEmpty()) { + responseFormat = componentsUtils.getResponseFormat(ActionStatus.COMPONENT_INSTANCE_NOT_FOUND); + LOGGER.debug("Failed to found component instance with id {}, error: {}", + componentInstanceId, responseFormat); + errorWrapper.setInnerElement(responseFormat); + return Optional.empty(); + } + + Map> componentInstancesInterfaceMap = component + .getComponentInstancesInterfaces(); + if (MapUtils.isEmpty(componentInstancesInterfaceMap)) { + componentInstancesInterfaceMap = new HashMap<>(); + component.setComponentInstancesInterfaces(componentInstancesInterfaceMap); + } + final List componentInstanceInterfaceList = componentInstancesInterfaceMap + .get(componentInstanceId); + + if (CollectionUtils.isEmpty(componentInstanceInterfaceList)) { + responseFormat = componentsUtils.getResponseFormat(ActionStatus.COMPONENT_INSTANCE_NOT_FOUND); + LOGGER.debug("Failed to found component instance with id {}, error: {}", + componentInstanceId, responseFormat); + errorWrapper.setInnerElement(responseFormat); + return Optional.empty(); + } + + 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: {}", + componentInstanceId, responseFormat); + errorWrapper.setInnerElement(responseFormat); + return Optional.empty(); + } + final OperationDataDefinition updatedOperationDataDefinition = optionalOperationDataDefinition.get(); + final Optional optionalComponentInstanceInterface = componentInstanceInterfaceList + .stream().filter(ci -> ci.getOperations().values().stream().anyMatch(operationDataDefinition -> + operationDataDefinition.getUniqueId() + .equalsIgnoreCase(updatedOperationDataDefinition.getUniqueId()))).findFirst(); + + if (optionalComponentInstanceInterface.isEmpty()) { + responseFormat = componentsUtils.getResponseFormat(ActionStatus.INTERFACE_NOT_FOUND_IN_COMPONENT); + LOGGER.debug("Failed to found ComponentInstanceInterface on component instance with id {}, error: {}", + componentInstanceId, responseFormat); + errorWrapper.setInnerElement(responseFormat); + return Optional.empty(); + } + + updateOperationDefinitionImplementation(updatedOperationDataDefinition); + + optionalComponentInstanceInterface.get().getOperations() + .replace(updatedOperationDataDefinition.getName(), updatedOperationDataDefinition); + + boolean wasLocked = false; + try { + if (shouldLock) { + lockComponent(componentId, component, "Update Interface Operation on Component instance"); + wasLocked = true; + } + + final StorageOperationStatus status = toscaOperationFacade + .updateComponentInstanceInterfaces(component, componentInstanceId); + + if (status != StorageOperationStatus.OK) { + janusGraphDao.rollback(); + responseFormat = componentsUtils + .getResponseFormat(ActionStatus.GENERAL_ERROR); + LOGGER.error("Exception occurred when updating Component Instance Interfaces {}", responseFormat); + errorWrapper.setInnerElement(responseFormat); + return Optional.empty(); + } + + final ComponentParametersView componentFilter = new ComponentParametersView(); + componentFilter.disableAll(); + componentFilter.setIgnoreUsers(false); + componentFilter.setIgnoreComponentInstances(false); + componentFilter.setIgnoreInterfaces(false); + componentFilter.setIgnoreComponentInstancesInterfaces(false); + + final Either operationStatusEither = toscaOperationFacade + .updateComponentInstanceMetadataOfTopologyTemplate(component, componentFilter); + + if (operationStatusEither.isRight()) { + janusGraphDao.rollback(); + responseFormat = componentsUtils + .getResponseFormat(ActionStatus.GENERAL_ERROR); + LOGGER.error("Exception occurred when updating Component Instance Topology template {}", 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.getMessage(), e); + responseFormat = componentsUtils.getResponseFormat(ActionStatus.GENERAL_ERROR); + errorWrapper.setInnerElement(responseFormat); + throw new BusinessLogicException(responseFormat); + + } finally { + if (wasLocked) { + unlockComponent(component.getUniqueId(), componentTypeEnum); + } + } + + return componentInstanceOptional; + + } + + public User validateUser(final String userId) { + final User user = userValidations.validateUserExists(userId); + userValidations + .validateUserRole(user, Arrays.asList(Role.DESIGNER, Role.ADMIN)); + return user; + } + + private void unlockComponent(final String componentUniqueId, + final ComponentTypeEnum componentType) { + graphLockOperation.unlockComponent(componentUniqueId, componentType.getNodeType()); + } + + private void updateOperationDefinitionImplementation(final OperationDataDefinition updatedOperationDataDefinition) { + final ArtifactDataDefinition artifactInfo = new ArtifactDataDefinition(); + artifactInfo.setArtifactName( + String.format("'%s'", updatedOperationDataDefinition.getImplementation().getArtifactName()) + ); + updatedOperationDataDefinition.setImplementation(artifactInfo); + } +} diff --git a/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/InterfaceDefinitionHandler.java b/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/InterfaceDefinitionHandler.java index aeb4376c15..71005ef0f0 100644 --- a/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/InterfaceDefinitionHandler.java +++ b/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/InterfaceDefinitionHandler.java @@ -38,6 +38,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; +import java.util.UUID; import java.util.stream.Collectors; import org.apache.commons.collections.MapUtils; import org.openecomp.sdc.be.components.impl.ImportUtils.ResultStatusEnum; @@ -172,9 +173,13 @@ public class InterfaceDefinitionHandler { private OperationDataDefinition createOperation(final String operationName, final Map operationDefinitionMap) { final OperationDataDefinition operation = new OperationDataDefinition(); + operation.setUniqueId(UUID.randomUUID().toString()); operation.setName(operationName); - handleOperationImplementation(operationDefinitionMap).ifPresent(operation::setImplementation); + operation.setImplementation( + handleOperationImplementation(operationDefinitionMap) + .orElse(new ArtifactDataDefinition()) + ); if (operationDefinitionMap.containsKey(INPUTS.getElementName())) { final Map interfaceInputs = (Map) operationDefinitionMap.get(INPUTS.getElementName()); @@ -189,6 +194,8 @@ public class InterfaceDefinitionHandler { final ListDataDefinition inputs = new ListDataDefinition<>(); for (final Entry interfaceInput : interfaceInputs.entrySet()) { final OperationInputDefinition operationInput = new OperationInputDefinition(); + operationInput.setUniqueId(UUID.randomUUID().toString()); + operationInput.setInputId(operationInput.getUniqueId()); operationInput.setName(interfaceInput.getKey()); if (interfaceInput.getValue() instanceof Map) { final LinkedHashMap inputPropertyValue = diff --git a/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ResourceImportManager.java b/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ResourceImportManager.java index b96e4e58e2..6137a3ffca 100644 --- a/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ResourceImportManager.java +++ b/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ResourceImportManager.java @@ -375,7 +375,8 @@ public class ResourceImportManager { log.info("error when creating interface:{}, for resource:{}", interfaceNameValue.getKey(), resource.getName()); } else { - moduleInterfaces.put(interfaceNameValue.getKey(), eitherInterface.left().value()); + final InterfaceDefinition interfaceDefinition = eitherInterface.left().value(); + moduleInterfaces.put(interfaceNameValue.getKey(), interfaceDefinition); } } 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 new file mode 100644 index 0000000000..c2e668c049 --- /dev/null +++ b/catalog-be/src/main/java/org/openecomp/sdc/be/servlets/ComponentInterfaceOperationServlet.java @@ -0,0 +1,179 @@ +/* + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2021 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========================================================= + */ + +package org.openecomp.sdc.be.servlets; + +import fj.data.Either; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import java.io.IOException; +import java.util.Optional; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.Consumes; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import org.apache.commons.io.IOUtils; +import org.openecomp.sdc.be.components.impl.ComponentInstanceBusinessLogic; +import org.openecomp.sdc.be.components.impl.ComponentInterfaceOperationBusinessLogic; +import org.openecomp.sdc.be.components.impl.ResourceImportManager; +import org.openecomp.sdc.be.components.impl.aaf.AafPermission; +import org.openecomp.sdc.be.components.impl.aaf.PermissionAllowed; +import org.openecomp.sdc.be.config.BeEcompErrorManager; +import org.openecomp.sdc.be.dao.api.ActionStatus; +import org.openecomp.sdc.be.datatypes.enums.ComponentTypeEnum; +import org.openecomp.sdc.be.impl.ComponentsUtils; +import org.openecomp.sdc.be.impl.ServletUtils; +import org.openecomp.sdc.be.model.ComponentInstance; +import org.openecomp.sdc.be.model.InterfaceDefinition; +import org.openecomp.sdc.be.model.User; +import org.openecomp.sdc.be.resources.data.auditing.AuditingActionEnum; +import org.openecomp.sdc.be.ui.model.UiComponentDataTransfer; +import org.openecomp.sdc.be.user.UserBusinessLogic; +import org.openecomp.sdc.common.api.Constants; +import org.openecomp.sdc.common.datastructure.Wrapper; +import org.openecomp.sdc.exception.ResponseFormat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; + +@Path("/v1/catalog/{componentType}/{componentId}/componentInstance/{componentInstanceId}/interfaceOperation") +@Consumes(MediaType.APPLICATION_JSON) +@Produces(MediaType.APPLICATION_JSON) +@Controller +public class ComponentInterfaceOperationServlet extends AbstractValidationsServlet { + + private static final Logger LOGGER = LoggerFactory.getLogger(ComponentInterfaceOperationServlet.class); + private static final String START_HANDLE_REQUEST_OF = "Start handle {} request of {}"; + private static final String MODIFIER_ID_IS = "modifier id is {}"; + + private static final String FAILED_TO_UPDATE_INTERFACE_OPERATION = + "failed to update Interface Operation on component instance {}"; + private static final String UPDATE_INTERFACE_OPERATION = "Update Interface Operation on Component Instance"; + private static final String FAILED_TO_UPDATE_INTERFACE_OPERATION_WITH_ERROR = + "Failed to update Interface Operation with an error"; + private static final String INTERFACE_OPERATION_CONTENT_INVALID = "Interface Operation content is invalid - {}"; + private static final String UNSUPPORTED_COMPONENT_TYPE = "Unsupported component type {}"; + private static final String INTERFACE_OPERATION_SUCCESSFULLY_UPDATED = + "Interface Operation successfully updated on component instance with id {}"; + + private final ComponentInterfaceOperationBusinessLogic componentInterfaceOperationBusinessLogic; + + @Autowired + public ComponentInterfaceOperationServlet(final UserBusinessLogic userBusinessLogic, + final ComponentInstanceBusinessLogic componentInstanceBL, + final ComponentsUtils componentsUtils, + final ServletUtils servletUtils, + final ResourceImportManager resourceImportManager, + final ComponentInterfaceOperationBusinessLogic componentInterfaceOperationBusinessLogic) { + super(userBusinessLogic, componentInstanceBL, componentsUtils, servletUtils, resourceImportManager); + this.componentInterfaceOperationBusinessLogic = componentInterfaceOperationBusinessLogic; + } + + @PUT + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Operation(description = "Update Interface Operation", method = "PUT", + summary = "Update Interface Operation on ComponentInstance", responses = { + @ApiResponse(content = @Content(array = @ArraySchema(schema = @Schema(implementation = Response.class)))), + @ApiResponse(responseCode = "201", description = "Update Interface Operation"), + @ApiResponse(responseCode = "403", description = "Restricted operation"), + @ApiResponse(responseCode = "400", description = "Invalid content / Missing content")}) + @PermissionAllowed(AafPermission.PermNames.INTERNAL_ALL_VALUE) + public Response updateComponentInstanceInterfaceOperation( + @Parameter(description = "valid values: resources / services", + schema = @Schema(allowableValues = { + ComponentTypeEnum.RESOURCE_PARAM_NAME, + ComponentTypeEnum.SERVICE_PARAM_NAME})) + @PathParam("componentType") final String componentType, + @Parameter(description = "Component Id") + @PathParam("componentId") String componentId, + @Parameter(description = "Component Instance Id") + @PathParam("componentInstanceId") String componentInstanceId, + @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 + .updateComponentInstanceInterfaceOperation(componentId, componentInstanceId, mappedInterfaceOperationData.get(), + componentTypeEnum, errorWrapper, true); + + final Response response; + if (actionResponse.isEmpty()) { + LOGGER.error(FAILED_TO_UPDATE_INTERFACE_OPERATION, componentInstanceId); + response = buildErrorResponse(errorWrapper.getInnerElement()); + } else { + LOGGER.debug(INTERFACE_OPERATION_SUCCESSFULLY_UPDATED, componentInstanceId); + response = buildOkResponse(getComponentsUtils().getResponseFormat(ActionStatus.CREATED), actionResponse.get()); + } + + return response; + + } 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)); + } + } + + private Optional getMappedInterfaceData(final String inputJson, + final User user, + final ComponentTypeEnum componentTypeEnum) { + final Either uiComponentEither = + getComponentsUtils().convertJsonToObjectUsingObjectMapper(inputJson, user, + UiComponentDataTransfer.class, AuditingActionEnum.UPDATE_RESOURCE_METADATA, componentTypeEnum); + return uiComponentEither.left().value().getInterfaces().values().stream().findFirst(); + } + +} diff --git a/catalog-be/src/main/java/org/openecomp/sdc/be/tosca/InterfacesOperationsConverter.java b/catalog-be/src/main/java/org/openecomp/sdc/be/tosca/InterfacesOperationsConverter.java index 8fb835e0b9..8d35517d92 100644 --- a/catalog-be/src/main/java/org/openecomp/sdc/be/tosca/InterfacesOperationsConverter.java +++ b/catalog-be/src/main/java/org/openecomp/sdc/be/tosca/InterfacesOperationsConverter.java @@ -21,6 +21,7 @@ import static org.openecomp.sdc.be.utils.TypeUtils.ToscaTagNamesEnum.INPUTS; import static org.openecomp.sdc.be.utils.TypeUtils.ToscaTagNamesEnum.OPERATIONS; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.Gson; import java.util.Collections; @@ -32,6 +33,7 @@ import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang.StringUtils; import org.openecomp.sdc.be.datatypes.elements.InputDataDefinition; import org.openecomp.sdc.be.datatypes.elements.OperationDataDefinition; import org.openecomp.sdc.be.datatypes.elements.OperationInputDefinition; @@ -150,63 +152,100 @@ public class InterfacesOperationsConverter { final Map dataTypes, final boolean isAssociatedComponent, final boolean isServiceProxyInterface) { - if(MapUtils.isEmpty(interfaces)) { + if (MapUtils.isEmpty(interfaces)) { return null; } - Map toscaInterfaceDefinitions = new HashMap<>(); + final Map toscaInterfaceDefinitions = new HashMap<>(); for (InterfaceDefinition interfaceDefinition : interfaces.values()) { - ToscaInterfaceDefinition toscaInterfaceDefinition = new ToscaInterfaceDefinition(); - final String interfaceType; - if(componentInstance != null && LOCAL_INTERFACE_TYPE.equals(interfaceDefinition.getType())) { - interfaceType = DERIVED_FROM_BASE_DEFAULT + componentInstance.getSourceModelName(); - } else { - interfaceType = getInterfaceType(component, interfaceDefinition.getType()); - } - if (componentInstance == null) { - toscaInterfaceDefinition.setType(interfaceType); - } - toscaInterfaceDefinition.setType(interfaceType); - final Map operations = interfaceDefinition.getOperations(); - Map toscaOperationMap = new HashMap<>(); - - String operationArtifactPath; - for (Map.Entry operationEntry : operations.entrySet()) { - ToscaLifecycleOperationDefinition toscaOperation = new ToscaLifecycleOperationDefinition(); - if (isArtifactPresent(operationEntry)) { - operationArtifactPath = OperationArtifactUtil - .createOperationArtifactPath(component, componentInstance, operationEntry.getValue(), - isAssociatedComponent); - toscaOperation.setImplementation(operationArtifactPath); - } - toscaOperation.setDescription(operationEntry.getValue().getDescription()); - fillToscaOperationInputs(operationEntry.getValue(), dataTypes, toscaOperation, isServiceProxyInterface); + handleInterfaceOperations(component, componentInstance, dataTypes, isAssociatedComponent, + isServiceProxyInterface, toscaInterfaceDefinitions, interfaceDefinition); + } + return toscaInterfaceDefinitions; + } - toscaOperationMap.put(operationEntry.getValue().getName(), toscaOperation); - } - toscaInterfaceDefinition.setOperations(toscaOperationMap); + public Map getInterfacesMapFromComponentInstance(final Component component, + final ComponentInstance componentInstance, + final Map dataTypes, + final boolean isAssociatedComponent, + final boolean isServiceProxyInterface) { + final Map toscaInterfaceDefinitions = new HashMap<>(); + final ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + for (final Map.Entry interfaceEntry : componentInstance.getInterfaces().entrySet()) { + final InterfaceDefinition interfaceDefinition = objectMapper + .convertValue(interfaceEntry.getValue(), InterfaceDefinition.class); + handleInterfaceOperations(component, componentInstance, dataTypes, isAssociatedComponent, + isServiceProxyInterface, toscaInterfaceDefinitions, interfaceDefinition); + } + return toscaInterfaceDefinitions; + } - final Map interfaceInputMap = createInterfaceInputMap(interfaceDefinition, dataTypes); - if (!interfaceInputMap.isEmpty()) { - toscaInterfaceDefinition.setInputs(interfaceInputMap); - } + private void handleInterfaceOperations(final Component component, + final ComponentInstance componentInstance, + final Map dataTypes, + final boolean isAssociatedComponent, + final boolean isServiceProxyInterface, + final Map toscaInterfaceDefinitions, + final InterfaceDefinition interfaceDefinition) { + final String interfaceType; + if (componentInstance != null && LOCAL_INTERFACE_TYPE.equals(interfaceDefinition.getType())) { + interfaceType = DERIVED_FROM_BASE_DEFAULT + componentInstance.getSourceModelName(); + } else { + interfaceType = getInterfaceType(component, interfaceDefinition.getType()); + } + final ToscaInterfaceDefinition toscaInterfaceDefinition = new ToscaInterfaceDefinition(); + if (componentInstance == null) { + toscaInterfaceDefinition.setType(interfaceType); + } + final Map operations = interfaceDefinition.getOperations(); + final Map toscaOperationMap = new HashMap<>(); + + for (final Entry operationEntry : operations.entrySet()) { + final ToscaLifecycleOperationDefinition toscaLifecycleOperationDefinition = + new ToscaLifecycleOperationDefinition(); + handleInterfaceOperationImplementation(component, componentInstance, isAssociatedComponent, + operationEntry, + toscaLifecycleOperationDefinition); + toscaLifecycleOperationDefinition.setDescription(operationEntry.getValue().getDescription()); + fillToscaOperationInputs(operationEntry.getValue(), dataTypes, toscaLifecycleOperationDefinition, + isServiceProxyInterface); + toscaOperationMap.put(operationEntry.getValue().getName(), toscaLifecycleOperationDefinition); + } - Map interfaceDefAsMap = getObjectAsMap(toscaInterfaceDefinition); - if (interfaceDefAsMap.containsKey(INPUTS.getElementName())) { - handleDefaults((Map) interfaceDefAsMap.get(INPUTS.getElementName())); - } - Map operationsMap = (Map) interfaceDefAsMap.remove(OPERATIONS.getElementName()); - if (isServiceProxyInterface) { - //Remove input type and copy default value directly into the proxy node template from the node type - handleServiceProxyOperationInputValue(operationsMap, interfaceType); - } else { - handleDefaults(operationsMap); - } - interfaceDefAsMap.putAll(operationsMap); - toscaInterfaceDefinitions.put(getLastPartOfName(interfaceType), interfaceDefAsMap); + toscaInterfaceDefinition.setOperations(toscaOperationMap); + final Map interfaceInputMap = createInterfaceInputMap(interfaceDefinition, dataTypes); + if (!interfaceInputMap.isEmpty()) { + toscaInterfaceDefinition.setInputs(interfaceInputMap); + } + final Map interfaceDefinitionAsMap = getObjectAsMap(toscaInterfaceDefinition); + if (interfaceDefinitionAsMap.containsKey(INPUTS.getElementName())) { + handleDefaults((Map) interfaceDefinitionAsMap.get(INPUTS.getElementName())); } + final Map operationsMap = + (Map) interfaceDefinitionAsMap.remove(OPERATIONS.getElementName()); - return toscaInterfaceDefinitions; + handleOperationInputValue(operationsMap, interfaceType); + + interfaceDefinitionAsMap.putAll(operationsMap); + toscaInterfaceDefinitions.put(getLastPartOfName(interfaceType), interfaceDefinitionAsMap); + } + + private void handleInterfaceOperationImplementation(final Component component, + final ComponentInstance componentInstance, + final boolean isAssociatedComponent, + final Entry operationEntry, + final ToscaLifecycleOperationDefinition toscaOperation) { + final String operationArtifactPath; + if (isArtifactPresent(operationEntry) && StringUtils + .isNotEmpty(operationEntry.getValue().getImplementation().getArtifactName())) { + operationArtifactPath = OperationArtifactUtil + .createOperationArtifactPath(component, componentInstance, operationEntry.getValue(), + isAssociatedComponent); + toscaOperation.setImplementation(operationArtifactPath); + } else { + toscaOperation.setImplementation(operationEntry.getValue().getImplementation().getArtifactName()); + } } public void removeInterfacesWithoutOperations(final Map interfaceMap) { @@ -244,21 +283,6 @@ public class InterfacesOperationsConverter { return toscaInterfaceInputMap; } - private static void handleServiceProxyOperationInputValue(Map operationsMap, String parentKey) { - for (Map.Entry operationEntry : operationsMap.entrySet()) { - final Object value = operationEntry.getValue(); - final String key = operationEntry.getKey(); - if (value instanceof Map) { - if ("inputs".equals(parentKey)) { - Object defaultValue = getDefaultValue((Map) value); - operationsMap.put(key, defaultValue); - } else { - handleServiceProxyOperationInputValue((Map) value, key); - } - } - } - } - private static Object getDefaultValue(Map inputValueMap) { Object defaultValue = null; for (Map.Entry operationEntry : inputValueMap.entrySet()) { @@ -274,6 +298,22 @@ public class InterfacesOperationsConverter { return defaultValue; } + //Remove input type and copy default value directly into the proxy node template from the node type + private static void handleOperationInputValue(Map operationsMap, String parentKey) { + for (Map.Entry operationEntry : operationsMap.entrySet()) { + final Object value = operationEntry.getValue(); + final String key = operationEntry.getKey(); + if (value instanceof Map) { + if (INPUTS.getElementName().equals(parentKey)) { + Object defaultValue = getDefaultValue((Map) value); + operationsMap.put(key, defaultValue); + } else { + handleOperationInputValue((Map) value, key); + } + } + } + } + /* * workaround for : currently "defaultp" is not being converted to "default" by the relevant code in * ToscaExportHandler so, any string Map key named "defaultp" will have its named changed to "default" diff --git a/catalog-be/src/main/java/org/openecomp/sdc/be/tosca/ToscaExportHandler.java b/catalog-be/src/main/java/org/openecomp/sdc/be/tosca/ToscaExportHandler.java index f9125648fd..b4bf4e8d42 100644 --- a/catalog-be/src/main/java/org/openecomp/sdc/be/tosca/ToscaExportHandler.java +++ b/catalog-be/src/main/java/org/openecomp/sdc/be/tosca/ToscaExportHandler.java @@ -293,7 +293,7 @@ public class ToscaExportHandler { if (nodeTypesMap != null && !nodeTypesMap.isEmpty()) { toscaNode.setNode_types(nodeTypesMap); } - + createServiceSubstitutionNodeTypes(componentCache, component, toscaNode); Either, ToscaError> proxyInterfaceTypesEither = createProxyInterfaceTypes(component); @@ -486,7 +486,7 @@ public class ToscaExportHandler { toscaMetadata.put(JsonPresentationFields.INSTANTIATION_TYPE.getPresentation(),service.getEnvironmentContext() == null ? StringUtils.EMPTY : service.getInstantiationType()); if (!isInstance) { // DE268546 - toscaMetadata.put(JsonPresentationFields.ECOMP_GENERATED_NAMING.getPresentation(),service.isEcompGeneratedNaming().toString()); + toscaMetadata.put(JsonPresentationFields.ECOMP_GENERATED_NAMING.getPresentation(),service.isEcompGeneratedNaming().toString()); toscaMetadata.put(JsonPresentationFields.ECOMP_GENERATED_NAMING.getPresentation(),service.isEcompGeneratedNaming().toString()); toscaMetadata.put(JsonPresentationFields.NAMING_POLICY.getPresentation(),service.getNamingPolicy()); } @@ -494,7 +494,7 @@ public class ToscaExportHandler { default: log.debug(NOT_SUPPORTED_COMPONENT_TYPE, component.getComponentType()); } - + for (final String key: component.getCategorySpecificMetadata().keySet()) { toscaMetadata.put(key, component.getCategorySpecificMetadata().get(key)); } @@ -904,6 +904,7 @@ public class ToscaExportHandler { addComponentInstanceInputs(dataTypes, componentInstancesInputs, instanceUniqueId, props); } + //M3[00001] - NODE TEMPLATE INTERFACES - START handleInstanceInterfaces(componentInstanceInterfaces, componentInstance, dataTypes, nodeTemplate, instanceUniqueId, component); @@ -963,29 +964,22 @@ public class ToscaExportHandler { String instanceUniqueId, Component parentComponent) { - final Map interfaceMap; - // we need to handle service proxy interfaces - if (isComponentOfTypeServiceProxy(componentInstance)) { - if (MapUtils.isEmpty(componentInstanceInterfaces) - || !componentInstanceInterfaces.containsKey(instanceUniqueId)) { - nodeTemplate.setInterfaces(null); - return; - } + if (MapUtils.isEmpty(componentInstanceInterfaces) + || !componentInstanceInterfaces.containsKey(instanceUniqueId)) { + nodeTemplate.setInterfaces(null); + return; + } - final List currServiceInterfaces = - componentInstanceInterfaces.get(instanceUniqueId); + final List currServiceInterfaces = + componentInstanceInterfaces.get(instanceUniqueId); - final Map tmpInterfaces = new HashMap<>(); - currServiceInterfaces.forEach(instInterface -> tmpInterfaces.put(instInterface - .getUniqueId(), instInterface)); + final Map tmpInterfaces = new HashMap<>(); + currServiceInterfaces.forEach(instInterface -> tmpInterfaces.put(instInterface + .getUniqueId(), instInterface)); + + final Map interfaceMap = interfacesOperationsConverter + .getInterfacesMap(parentComponent, componentInstance, tmpInterfaces, dataTypes, isComponentOfTypeServiceProxy(componentInstance), isComponentOfTypeServiceProxy(componentInstance)); - interfaceMap = interfacesOperationsConverter - .getInterfacesMap(parentComponent, componentInstance, tmpInterfaces, dataTypes, true, true); - } else { - interfaceMap = - getComponentInstanceInterfaceInstances(componentInstanceInterfaces, - componentInstance, instanceUniqueId); - } interfacesOperationsConverter.removeInterfacesWithoutOperations(interfaceMap); nodeTemplate.setInterfaces(MapUtils.isEmpty(interfaceMap) ? null : interfaceMap); } @@ -1210,7 +1204,7 @@ public class ToscaExportHandler { return Either.left(nodeTypesMap); } - + private void createServiceSubstitutionNodeTypes(final Map componentCache, final Component container, final ToscaTemplate toscaNode) { final List componentInstances = container.getComponentInstances(); @@ -1226,11 +1220,11 @@ public class ToscaExportHandler { final Map nodeTypes = toscaNode.getNode_types() == null ? new HashMap<>() : toscaNode.getNode_types(); convertInterfaceNodeType(new HashMap<>(), componentCache.get(inst.getSourceModelUid()), toscaNode, nodeTypes, true); } - } + } } private ToscaNodeType createProxyNodeType(Map componentCache, Component origComponent, - Component proxyComponent, ComponentInstance instance) { + Component proxyComponent, ComponentInstance componentInstance) { ToscaNodeType toscaNodeType = new ToscaNodeType(); String derivedFrom = ((Resource) origComponent).getToscaResourceName(); @@ -1241,25 +1235,32 @@ public class ToscaExportHandler { } Map dataTypes = dataTypesEither.left().value(); Map capabilities = this.capabilityRequirementConverter - .convertProxyCapabilities(componentCache, instance, dataTypes); + .convertProxyCapabilities(componentCache, componentInstance, dataTypes); if (MapUtils.isNotEmpty(capabilities)) { toscaNodeType.setCapabilities(capabilities); } List> proxyNodeTypeRequirements = this.capabilityRequirementConverter - .convertProxyRequirements(componentCache, instance); + .convertProxyRequirements(componentCache, componentInstance); if (CollectionUtils.isNotEmpty(proxyNodeTypeRequirements)) { toscaNodeType.setRequirements(proxyNodeTypeRequirements); } Optional> proxyProperties = getProxyNodeTypeProperties(proxyComponent, dataTypes); proxyProperties.ifPresent(toscaNodeType::setProperties); - Optional> proxyInterfaces = getProxyNodeTypeInterfaces(proxyComponent, dataTypes); - if (proxyInterfaces.isPresent()) { - final Map interfaceMap = proxyInterfaces.get(); - interfacesOperationsConverter.removeInterfacesWithoutOperations(interfaceMap); - toscaNodeType.setInterfaces(MapUtils.isEmpty(interfaceMap) ? null : interfaceMap); + Map interfaceMap = new HashMap<>(); + if (MapUtils.isEmpty(componentInstance.getInterfaces())) { + final Optional> proxyInterfaces = getProxyNodeTypeInterfaces(proxyComponent, dataTypes); + if (proxyInterfaces.isPresent()) { + interfaceMap = proxyInterfaces.get(); + } + } else { + interfaceMap = interfacesOperationsConverter + .getInterfacesMapFromComponentInstance(proxyComponent, componentInstance, dataTypes, false, false); + } + interfacesOperationsConverter.removeInterfacesWithoutOperations(interfaceMap); + toscaNodeType.setInterfaces(MapUtils.isEmpty(interfaceMap) ? null : interfaceMap); return toscaNodeType; } diff --git a/catalog-be/src/test/java/org/openecomp/sdc/be/components/ResourceImportManagerTest.java b/catalog-be/src/test/java/org/openecomp/sdc/be/components/ResourceImportManagerTest.java index 336b8ec940..18dc67f752 100644 --- a/catalog-be/src/test/java/org/openecomp/sdc/be/components/ResourceImportManagerTest.java +++ b/catalog-be/src/test/java/org/openecomp/sdc/be/components/ResourceImportManagerTest.java @@ -69,6 +69,7 @@ import org.openecomp.sdc.be.model.jsonjanusgraph.operations.ToscaOperationFacade import org.openecomp.sdc.be.model.operations.impl.CapabilityTypeOperation; import org.openecomp.sdc.be.model.tosca.constraints.GreaterOrEqualConstraint; import org.openecomp.sdc.be.resources.data.auditing.AuditingActionEnum; +import org.openecomp.sdc.be.tosca.utils.InterfaceTypesNameUtil; import org.openecomp.sdc.be.user.UserBusinessLogic; import org.openecomp.sdc.be.utils.TypeUtils; import org.openecomp.sdc.common.api.ConfigurationSource; @@ -203,7 +204,7 @@ public class ResourceImportManagerTest { testSetRequirments(createResource.left); } - + @Test public void testResourceCreationWithInterfaceImplementation() throws IOException { UploadResourceInfo resourceMD = createDummyResourceMD(); @@ -214,7 +215,7 @@ public class ResourceImportManagerTest { setResourceBusinessLogicMock(); String jsonContent = ImportUtilsTest.loadCustomTypeFileNameToJsonString("custom-types-node-type-with-interface-impl.yml"); - + Map interfaceTypes = new HashMap<>(); final InterfaceDefinition interfaceDefinition = new InterfaceDefinition(); interfaceDefinition.setType("tosca.interfaces.node.lifecycle.Standard"); @@ -228,7 +229,7 @@ public class ResourceImportManagerTest { .importNormativeResource(jsonContent, resourceMD, user, true, true); assertSetInterfaceImplementation(createResource.left); } - + @Test public void testResourceCreationWithInterfaceImplementation_UnknownInterface() throws IOException { UploadResourceInfo resourceMD = createDummyResourceMD(); @@ -239,7 +240,7 @@ public class ResourceImportManagerTest { setResourceBusinessLogicMock(); String jsonContent = ImportUtilsTest.loadCustomTypeFileNameToJsonString("custom-types-node-type-with-unknown-interface-impl.yml"); - + Map interfaceTypes = new HashMap<>(); final InterfaceDefinition interfaceDefinition = new InterfaceDefinition(); interfaceDefinition.setType("tosca.interfaces.node.lifecycle.Standard"); @@ -252,7 +253,7 @@ public class ResourceImportManagerTest { ImmutablePair createResource = importManager.importNormativeResource(jsonContent, resourceMD, user, true, true); assertNull(createResource.left.getInterfaces()); } - + @Test public void testResourceCreationWitInterfaceImplementation_UnknownOperation() throws IOException { UploadResourceInfo resourceMD = createDummyResourceMD(); @@ -263,7 +264,7 @@ public class ResourceImportManagerTest { setResourceBusinessLogicMock(); String jsonContent = ImportUtilsTest.loadCustomTypeFileNameToJsonString("custom-types-node-type-with-interface-impl-unknown-operation.yml"); - + Map interfaceTypes = new HashMap<>(); final InterfaceDefinition interfaceDefinition = new InterfaceDefinition(); interfaceDefinition.setType("tosca.interfaces.node.lifecycle.Standard"); @@ -399,20 +400,16 @@ public class ResourceImportManagerTest { assertEquals("binding", requirement.getName()); } - + private void assertSetInterfaceImplementation(final Resource resource) { final Map interfaces = resource.getInterfaces(); + assertNotNull(interfaces); assertEquals(1, interfaces.size()); - assertTrue(interfaces.containsKey("Standard")); - final InterfaceDefinition interfaceDefinition = interfaces.get("Standard"); - assertEquals("tosca.interfaces.node.lifecycle.Standard", interfaceDefinition.getType()); - assertEquals("tosca.interfaces.node.lifecycle.standard", interfaceDefinition.getUniqueId()); - final Map operations = interfaceDefinition.getOperations(); - assertEquals(1, operations.size()); - - final OperationDataDefinition operation = operations.get("configure"); - assertEquals("'camunda/vnfConfigure'", operation.getImplementation().getArtifactName()); + assertTrue(interfaces.containsKey(InterfaceTypesNameUtil.buildShortName(interfaceDefinition.getType()))); + Map operations = interfaceDefinition.getOperations(); + operations.values().forEach(operationDataDefinition -> + assertTrue(operations.containsKey(operationDataDefinition.getName()))); } private void testSetDerivedFrom(Resource resource) { diff --git a/catalog-be/src/test/java/org/openecomp/sdc/be/components/impl/ComponentInterfaceOperationBusinessLogicTest.java b/catalog-be/src/test/java/org/openecomp/sdc/be/components/impl/ComponentInterfaceOperationBusinessLogicTest.java new file mode 100644 index 0000000000..0fd61845e6 --- /dev/null +++ b/catalog-be/src/test/java/org/openecomp/sdc/be/components/impl/ComponentInterfaceOperationBusinessLogicTest.java @@ -0,0 +1,173 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * 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========================================================= + */ + +package org.openecomp.sdc.be.components.impl; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import fj.data.Either; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; +import org.openecomp.sdc.be.components.impl.exceptions.BusinessLogicException; +import org.openecomp.sdc.be.components.validation.ComponentValidations; +import org.openecomp.sdc.be.components.validation.UserValidations; +import org.openecomp.sdc.be.dao.janusgraph.JanusGraphGenericDao; +import org.openecomp.sdc.be.dao.janusgraph.JanusGraphOperationStatus; +import org.openecomp.sdc.be.dao.jsongraph.JanusGraphDao; +import org.openecomp.sdc.be.datatypes.elements.ArtifactDataDefinition; +import org.openecomp.sdc.be.datatypes.elements.OperationDataDefinition; +import org.openecomp.sdc.be.datatypes.enums.ComponentTypeEnum; +import org.openecomp.sdc.be.datatypes.enums.NodeTypeEnum; +import org.openecomp.sdc.be.datatypes.enums.OriginTypeEnum; +import org.openecomp.sdc.be.impl.ComponentsUtils; +import org.openecomp.sdc.be.model.Component; +import org.openecomp.sdc.be.model.ComponentInstance; +import org.openecomp.sdc.be.model.ComponentInstanceInterface; +import org.openecomp.sdc.be.model.ComponentParametersView; +import org.openecomp.sdc.be.model.InterfaceDefinition; +import org.openecomp.sdc.be.model.Service; +import org.openecomp.sdc.be.model.jsonjanusgraph.operations.ToscaOperationFacade; +import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus; +import org.openecomp.sdc.be.model.operations.impl.GraphLockOperation; +import org.openecomp.sdc.common.datastructure.Wrapper; + +@ExtendWith(MockitoExtension.class) +public class ComponentInterfaceOperationBusinessLogicTest extends BaseBusinessLogicMock { + + @InjectMocks + private ComponentInterfaceOperationBusinessLogic componentInterfaceOperationBusinessLogic; + + @Mock + private ToscaOperationFacade toscaOperationFacade; + @Mock + private GraphLockOperation graphLockOperation; + @Mock + private JanusGraphDao janusGraphDao; + @Mock + private JanusGraphGenericDao janusGraphGenericDao; + @Mock + private ComponentsUtils componentsUtils; + @Mock + private UserValidations userValidations; + @Mock + private ComponentValidations componentValidations; + + private Component component; + private ComponentInstance componentInstance; + private ComponentParametersView componentFilter; + + @BeforeEach + public void init() { + MockitoAnnotations.initMocks(this); + componentInterfaceOperationBusinessLogic = + new ComponentInterfaceOperationBusinessLogic(elementDao, groupOperation, groupInstanceOperation, + groupTypeOperation, interfaceOperation, interfaceLifecycleTypeOperation, artifactToscaOperation, + componentValidations); + componentInterfaceOperationBusinessLogic.setToscaOperationFacade(toscaOperationFacade); + componentInterfaceOperationBusinessLogic.setGraphLockOperation(graphLockOperation); + componentInterfaceOperationBusinessLogic.setComponentsUtils(componentsUtils); + componentInterfaceOperationBusinessLogic.setUserValidations(userValidations); + componentInterfaceOperationBusinessLogic.setJanusGraphGenericDao(janusGraphGenericDao); + componentInterfaceOperationBusinessLogic.setJanusGraphDao(janusGraphDao); + + initComponentData(); + } + + @Test + public void updateSubstitutionFilterTest() throws BusinessLogicException { + final String componentId = component.getUniqueId(); + final String componentInstanceId = componentInstance.getUniqueId(); + final InterfaceDefinition interfaceDefinition = new InterfaceDefinition(); + interfaceDefinition.setUniqueId(UUID.randomUUID().toString()); + interfaceDefinition.setType("tosca.interfaces.node.lifecycle.Standard"); + final Map operations = new HashMap<>(); + final OperationDataDefinition operationDataDefinition = new OperationDataDefinition(); + operationDataDefinition.setUniqueId(UUID.randomUUID().toString()); + final ArtifactDataDefinition artifactDataDefinition = new ArtifactDataDefinition(); + artifactDataDefinition.setArtifactName("EO Implementation info"); + operationDataDefinition.setImplementation(artifactDataDefinition); + operations.put("configure", operationDataDefinition); + interfaceDefinition.setOperations(operations ); + + final ComponentInstanceInterface componentInstanceInterface = + new ComponentInstanceInterface("interfaceId", interfaceDefinition); + Map> componentInstancesInterfacesMap = new HashMap<>(); + componentInstancesInterfacesMap.put(componentInstanceId, Collections.singletonList(componentInstanceInterface)); + component.setComponentInstancesInterfaces(componentInstancesInterfacesMap); + componentInstance.setInterfaces( + (Map) new HashMap<>().put(componentInstanceId, interfaceDefinition)); + component.setComponentInstances(Collections.singletonList(componentInstance)); + + when(toscaOperationFacade.getToscaElement(componentId)).thenReturn(Either.left(component)); + when(componentValidations.getComponentInstance(component, componentInstanceId)) + .thenReturn(Optional.of(componentInstance)); + when(graphLockOperation.lockComponent(componentId, NodeTypeEnum.Service)) + .thenReturn(StorageOperationStatus.OK); + when(toscaOperationFacade.updateComponentInstanceInterfaces(component, componentInstanceId)) + .thenReturn(StorageOperationStatus.OK); + when(toscaOperationFacade + .updateComponentInstanceMetadataOfTopologyTemplate(any(Service.class), any(ComponentParametersView.class))) + .thenReturn(Either.left(component)); + when(janusGraphDao.commit()).thenReturn(JanusGraphOperationStatus.OK); + when(graphLockOperation.unlockComponent(componentId, NodeTypeEnum.Service)) + .thenReturn(StorageOperationStatus.OK); + + final Optional result = componentInterfaceOperationBusinessLogic + .updateComponentInstanceInterfaceOperation(componentId, componentInstanceId, interfaceDefinition, + ComponentTypeEnum.SERVICE, new Wrapper<>(), true); + assertThat(result).isPresent(); + } + + public void initComponentData() { + try { + component = new Service(); + component.setName("MyTestService"); + component.setUniqueId("dac65869-dfb4-40d2-aa20-084324659ec1"); + + componentInstance = new ComponentInstance(); + componentInstance.setUniqueId("dac65869-dfb4-40d2-aa20-084324659ec1.resource0"); + componentInstance.setOriginType(OriginTypeEnum.VFC); + componentInstance.setName("My VFC Instance"); + + componentFilter = new ComponentParametersView(); + componentFilter.disableAll(); + componentFilter.setIgnoreUsers(false); + componentFilter.setIgnoreComponentInstances(false); + componentFilter.setIgnoreInterfaces(false); + componentFilter.setIgnoreComponentInstancesInterfaces(false); + + } catch (final Exception e) { + fail(e.getMessage()); + } + } +} diff --git a/catalog-be/src/test/java/org/openecomp/sdc/be/components/impl/InputsBusinessLogicTest.java b/catalog-be/src/test/java/org/openecomp/sdc/be/components/impl/InputsBusinessLogicTest.java index c83f73b269..548ef1ace9 100644 --- a/catalog-be/src/test/java/org/openecomp/sdc/be/components/impl/InputsBusinessLogicTest.java +++ b/catalog-be/src/test/java/org/openecomp/sdc/be/components/impl/InputsBusinessLogicTest.java @@ -777,8 +777,8 @@ public class InputsBusinessLogicTest { inputDef.setDefaultValue(NEW_VALUE); // update value inputDef.setRequired(Boolean.TRUE); // update value Map newMetadata = new HashMap<>(); - newMetadata.put("key1", "value2"); - newMetadata.put("key2", "value3"); + newMetadata.put("key2", "value2"); + newMetadata.put("key3", "value3"); inputDef.setMetadata(newMetadata); newInputDefs.add(inputDef); @@ -802,8 +802,8 @@ public class InputsBusinessLogicTest { assertEquals(NEW_VALUE, service.getInputs().get(0).getDefaultValue()); assertEquals(Boolean.TRUE, service.getInputs().get(0).isRequired()); assertEquals(2, service.getInputs().get(0).getMetadata().size()); - assertEquals("value2", service.getInputs().get(0).getMetadata().get("key1")); - assertEquals("value3", service.getInputs().get(0).getMetadata().get("key2")); + assertEquals("value2", service.getInputs().get(0).getMetadata().get("key2")); + assertEquals("value3", service.getInputs().get(0).getMetadata().get("key3")); } } diff --git a/catalog-be/src/test/java/org/openecomp/sdc/be/tosca/InterfacesOperationsConverterTest.java b/catalog-be/src/test/java/org/openecomp/sdc/be/tosca/InterfacesOperationsConverterTest.java index 4f569f91cd..d4fb60e878 100644 --- a/catalog-be/src/test/java/org/openecomp/sdc/be/tosca/InterfacesOperationsConverterTest.java +++ b/catalog-be/src/test/java/org/openecomp/sdc/be/tosca/InterfacesOperationsConverterTest.java @@ -57,6 +57,8 @@ import org.apache.commons.collections4.MapUtils; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; import org.onap.sdc.tosca.services.YamlUtil; import org.openecomp.sdc.be.DummyConfigurationManager; import org.openecomp.sdc.be.datatypes.elements.ArtifactDataDefinition; @@ -585,9 +587,6 @@ class InterfacesOperationsConverterTest { for (Map.Entry inputEntry : inputs.entrySet()) { String[] inputNameSplit = inputEntry.getKey().split("_"); Map inputValueObject = (Map) inputEntry.getValue(); - assertEquals(inputNameSplit[1], inputValueObject.get("type")); - Boolean expectedIsRequired = Integer.parseInt(inputNameSplit[2]) % 2 == 0; - assertEquals(expectedIsRequired, inputValueObject.get("required")); validateOperationInputDefinitionDefaultValue(interfaceType, operationName, inputNameSplit[1], Integer.parseInt(inputNameSplit[2]), inputValueObject); } @@ -597,15 +596,16 @@ class InterfacesOperationsConverterTest { private void validateOperationInputDefinitionDefaultValue(String interfaceType, String operationName, String inputType, int index, Map inputValueObject) { - Map mappedInputValue = (Map) inputValueObject.get("default"); - if(mappedInputValue.containsKey(ToscaFunctions.GET_PROPERTY.getFunctionName())) { + if (inputValueObject.containsKey(ToscaFunctions.GET_PROPERTY.getFunctionName())) { String mappedPropertyValue = MAPPED_PROPERTY_NAME + index; - List mappedPropertyDefaultValue = (List) mappedInputValue.get(ToscaFunctions.GET_PROPERTY.getFunctionName()); + List mappedPropertyDefaultValue = (List) inputValueObject + .get(ToscaFunctions.GET_PROPERTY.getFunctionName()); assertEquals(2, mappedPropertyDefaultValue.size()); assertTrue(mappedPropertyDefaultValue.contains(SELF)); assertTrue(mappedPropertyDefaultValue.contains(mappedPropertyValue)); - } else if(mappedInputValue.containsKey(ToscaFunctions.GET_OPERATION_OUTPUT.getFunctionName())) { - List mappedPropertyDefaultValue = (List) mappedInputValue.get(ToscaFunctions.GET_OPERATION_OUTPUT.getFunctionName()); + } else if (inputValueObject.containsKey(ToscaFunctions.GET_OPERATION_OUTPUT.getFunctionName())) { + List mappedPropertyDefaultValue = (List) inputValueObject + .get(ToscaFunctions.GET_OPERATION_OUTPUT.getFunctionName()); assertEquals(4, mappedPropertyDefaultValue.size()); String mappedPropertyValue = OUTPUT_NAME_PREFIX + inputType + "_" + index; assertTrue(mappedPropertyDefaultValue.contains(SELF)); diff --git a/catalog-model/src/main/java/org/openecomp/sdc/be/model/ComponentParametersView.java b/catalog-model/src/main/java/org/openecomp/sdc/be/model/ComponentParametersView.java index 505b8e6c7c..6488d41f25 100644 --- a/catalog-model/src/main/java/org/openecomp/sdc/be/model/ComponentParametersView.java +++ b/catalog-model/src/main/java/org/openecomp/sdc/be/model/ComponentParametersView.java @@ -98,6 +98,8 @@ public class ComponentParametersView { this.setIgnoreNodeFilter(false); this.setIgnoreSubstitutionFilter(false); this.setIgnoreCapabiltyProperties(false); + this.setIgnoreInterfaces(false); + this.setIgnoreComponentInstancesInterfaces(false); break; case COMPONENT_INSTANCES_PROPERTIES: this.setIgnoreComponentInstances(false); //we need this in order to get the calculate capabilities requirements @@ -160,6 +162,7 @@ public class ComponentParametersView { break; case COMPONENT_INSTANCES_INTERFACES: this.setIgnoreComponentInstances(false); + this.setIgnoreInterfaces(false); this.setIgnoreComponentInstancesInterfaces(false); break; case DATA_TYPES: diff --git a/catalog-model/src/main/java/org/openecomp/sdc/be/model/jsonjanusgraph/operations/NodeTemplateOperation.java b/catalog-model/src/main/java/org/openecomp/sdc/be/model/jsonjanusgraph/operations/NodeTemplateOperation.java index facbcbe2a4..3f3e54146d 100644 --- a/catalog-model/src/main/java/org/openecomp/sdc/be/model/jsonjanusgraph/operations/NodeTemplateOperation.java +++ b/catalog-model/src/main/java/org/openecomp/sdc/be/model/jsonjanusgraph/operations/NodeTemplateOperation.java @@ -169,8 +169,9 @@ public class NodeTemplateOperation extends BaseOperation { } result = Either.right(status); } - if (componentInstance.getOriginType() == OriginTypeEnum.ServiceProxy || componentInstance.getOriginType() == OriginTypeEnum.ServiceSubstitution) { - TopologyTemplate updatedContainer = addComponentInstanceRes.left().value(); + final TopologyTemplate updatedContainer = addComponentInstanceRes.left().value(); + if (componentInstance.getOriginType() == OriginTypeEnum.ServiceProxy + || componentInstance.getOriginType() == OriginTypeEnum.ServiceSubstitution) { result = addCapAndReqToProxyServiceInstance(updatedContainer, componentInstance, componentInstanceData); if(result.isRight()) { return result; @@ -185,12 +186,15 @@ public class NodeTemplateOperation extends BaseOperation { if(result.isRight()) { return result; } - - result = addServiceInstanceInterfacesToProxyServiceInstance(updatedContainer, componentInstance); + } + if (componentInstance.getOriginType() == OriginTypeEnum.ServiceProxy + || componentInstance.getOriginType() == OriginTypeEnum.ServiceSubstitution + || componentInstance.getOriginType() == OriginTypeEnum.VF + || componentInstance.getOriginType() == OriginTypeEnum.VFC) { + result = addComponentInstanceInterfacesToTopologyTemplate(updatedContainer, componentInstance); if(result.isRight()) { return result; } - } } if (result == null) { @@ -362,11 +366,11 @@ public class NodeTemplateOperation extends BaseOperation { return Either.left(new ImmutablePair<>(updatedContainer, componentInstance.getUniqueId())); } - private Either, StorageOperationStatus> addServiceInstanceInterfacesToProxyServiceInstance(TopologyTemplate updatedContainer, ComponentInstance componentInstance) { + private Either, StorageOperationStatus> addComponentInstanceInterfacesToTopologyTemplate(TopologyTemplate updatedContainer, ComponentInstance componentInstance) { Map interfaces = componentInstance.getInterfaces(); if(MapUtils.isNotEmpty(interfaces)){ - Map interfacesMap = interfaces.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> (InterfaceDataDefinition) e.getValue())); + Map interfacesMap = interfaces.entrySet().stream().collect(Collectors.toMap(e -> ((InterfaceDataDefinition) e.getValue()).getUniqueId(), e -> (InterfaceDataDefinition) e.getValue())); MapInterfaceDataDefinition instInterfaces = new MapInterfaceDataDefinition(interfacesMap); Map instInterfacesMap = new HashMap<>(); diff --git a/catalog-ui/src/app/models/componentsInstances/componentInstance.ts b/catalog-ui/src/app/models/componentsInstances/componentInstance.ts index 2e0c1a59c8..a55cd4f0d1 100644 --- a/catalog-ui/src/app/models/componentsInstances/componentInstance.ts +++ b/catalog-ui/src/app/models/componentsInstances/componentInstance.ts @@ -103,6 +103,7 @@ export class ComponentInstance implements IComponentInstance{ public invariantName:string; public originArchived:boolean; public directives: string[]; + public interfaces:any; constructor(componentInstance?:ComponentInstance) { @@ -135,6 +136,7 @@ export class ComponentInstance implements IComponentInstance{ this.sourceModelUuid = componentInstance.sourceModelUuid; this.originArchived = componentInstance.originArchived; this.directives = componentInstance.directives; + this.interfaces = componentInstance.interfaces; } } diff --git a/catalog-ui/src/app/models/inputs.ts b/catalog-ui/src/app/models/inputs.ts index 49fd16dfaf..562db98168 100644 --- a/catalog-ui/src/app/models/inputs.ts +++ b/catalog-ui/src/app/models/inputs.ts @@ -65,6 +65,7 @@ export class InputModel implements IInputModel { schema:SchemaPropertyGroupModel; defaultValue:string; value:string; + toscaDefaultValue?: string; //costom properties isNew:boolean; @@ -94,6 +95,7 @@ export class InputModel implements IInputModel { this.filterTerm = this.name + ' ' + this.description + ' ' + this.type + ' ' + this.componentInstanceName; this.inputs = input.inputs; this.properties = input.properties; + this.toscaDefaultValue = input.toscaDefaultValue; } } diff --git a/catalog-ui/src/app/models/interfaceOperation.ts b/catalog-ui/src/app/models/interfaceOperation.ts new file mode 100644 index 0000000000..5c69688745 --- /dev/null +++ b/catalog-ui/src/app/models/interfaceOperation.ts @@ -0,0 +1,109 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * 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========================================================= + */ + +'use strict'; + +export class InputOperationParameter { + name: string; + type: string; + inputId: string; + toscaDefaultValue?: string; + + constructor(param?: any) { + if (param) { + this.name = param.name; + this.type = param.type; + this.inputId = param.inputId; + this.toscaDefaultValue = param.toscaDefaultValue; + } + console.info("InputOperationParameter Constructor: ", param) + } +} + +export interface IOperationParamsList { + listToscaDataDefinition: Array; +} + +export class BEInterfaceOperationModel { + name: string; + description: string; + uniqueId: string; + inputs: IOperationParamsList; + implementation?: InterfaceOperationImplementation; + + constructor(operation?: any) { + if (operation) { + this.name = operation.name; + this.description = operation.description; + this.uniqueId = operation.uniqueId; + this.inputs = operation.inputs; + this.implementation = operation.implementation; + } + } +} + +export class InterfaceOperationModel extends BEInterfaceOperationModel { + interfaceType: string; + interfaceId: string; + operationType: string; + description: string; + uniqueId: string; + implementation?: InterfaceOperationImplementation; + inputParams: IOperationParamsList; + + constructor(operation?: any) { + super(operation); + if (operation) { + this.interfaceId = operation.interfaceId; + this.interfaceType = operation.interfaceType; + this.description = operation.description; + this.operationType = operation.operationType; + this.uniqueId = operation.uniqueId; + this.inputParams = operation.inputParams; + } + } + + public displayType(): string { + return displayType(this.interfaceType); + } +} + +export class InterfaceOperationImplementation { + artifactName: string; +} + +export class ComponentInstanceInterfaceModel { + type: string; + uniqueId: string; + operations: Array; + + constructor(interfaceOperation?: any) { + if (interfaceOperation) { + this.type = interfaceOperation.type; + this.uniqueId = interfaceOperation.uniqueId; + this.operations = interfaceOperation.operations; + } + } + + public displayType(): string { + return displayType(this.type); + } +} + +const displayType = (type:string) => type && type.substr(type.lastIndexOf('.') + 1); diff --git a/catalog-ui/src/app/ng2/app.module.ts b/catalog-ui/src/app/ng2/app.module.ts index b94ba61f75..ac8a9b6eff 100644 --- a/catalog-ui/src/app/ng2/app.module.ts +++ b/catalog-ui/src/app/ng2/app.module.ts @@ -96,6 +96,7 @@ import { DeclareListModule } from './pages/properties-assignment/declare-list/de import { WorkflowServiceNg2 } from './services/workflow.service'; import { ToscaTypesServiceNg2 } from "./services/tosca-types.service"; import {CapabilitiesFilterPropertiesEditorComponentModule} from "./pages/composition/capabilities-filter-properties-editor/capabilities-filter-properties-editor.module"; +import {InterfaceOperationHandlerModule} from "./pages/composition/interface-operatons/operation-creator/interface-operation-handler.module"; declare const __ENV__: string; @@ -149,6 +150,7 @@ export function configServiceFactory(config: ConfigService, authService: Authent PluginsModule, InterfaceOperationModule, OperationCreatorModule, + InterfaceOperationHandlerModule, ServicePathCreatorModule, ServicePathsListModule, ServicePathSelectorModule, diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-instance/zone-instance.component.ts b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-instance/zone-instance.component.ts index 1b1363e576..d7b997d86a 100644 --- a/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-instance/zone-instance.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-instance/zone-instance.component.ts @@ -66,7 +66,7 @@ export class ZoneInstanceComponent implements OnInit { } private setMode = (mode:ZoneInstanceMode, event?:any, afterSaveCallback?:Function):void => { - + if(event){ //prevent event from handle and then repeat event from zone instance event.stopPropagation(); } @@ -125,4 +125,4 @@ export class ZoneInstanceComponent implements OnInit { event.stopPropagation(); }; -} \ No newline at end of file +} diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.html b/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.html index 5a0ca3e43f..9f6a8bcbf9 100644 --- a/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.html +++ b/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.html @@ -54,4 +54,4 @@ (assignmentSaveComplete)="zoneAssignmentSaveComplete($event)"> - \ No newline at end of file + diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.html b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.html new file mode 100644 index 0000000000..7567b90365 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.html @@ -0,0 +1,80 @@ + + +
+ +
+
+ + +
+
+ + + + + {{interface1.displayType()}} +
+ +
+
+ + {{ 'INTERFACE_HEADER_NAME' | translate }} + + + {{ 'INTERFACE_HEADER_DESCRIPTION' | translate }} + +
+ +
+ + {{operation.name}} + + + {{operation.getDescriptionEllipsis()}} + + {{!operation.isEllipsis ? '' : operation.isCollapsed ? 'More' : 'Less'}} + + +
+
+
+
+
+
diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.less b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.less new file mode 100644 index 0000000000..1ebfb1fe82 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.less @@ -0,0 +1,216 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2021 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. + * ============LICENSE_END========================================================= + */ + +@import './../../../../../assets/styles/override.less'; +@import './../../../../../assets/styles/variables.less'; + +.interface-operations { + font-size: 14px; + + .top-add-btn { + position: relative; + top: -31px; + text-transform: uppercase; + font-size: 14px; + font-family: @font-opensans-medium; + } + + .link { + color: @sdcui_color_blue; + text-decoration: underline; + font-family: @font-opensans-regular; + + &:not(.disabled) { + &:not(.empty-list-add-btn) { + &:hover { + color: @sdcui_color_dark-blue; + cursor: pointer; + } + } + } + } + + .operation-list { + border-top: 1px solid @main_color_o; + padding-top: 5px; + + .empty-list-container { + width: 100%; + display: flex; + justify-content: center; + + .empty-list-add-btn { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + border: 1px solid @main_color_o; + margin-top: 50px; + + height: 229px; + width: 480px; + + &.disabled { + pointer-events: none; + } + + &:hover { + &:not(.disabled) { + border: 1px solid @sdcui_color_blue; + cursor: pointer; + } + } + + .button-text { + margin-top: 9px; + font-family: @font-opensans-medium; + font-size: 16px; + text-transform: uppercase; + color: @sdcui_color_blue; + } + } + } + + .expand-collapse { + margin-top: 4px; + margin-bottom: 18px; + color: @sdcui_color_light-gray; + } + + .interface-row { + width: 100%; + margin-top: 13px; + border-bottom: 1px solid @main_color_o; + padding-left: 4px; + min-height: 37px; + + + .interface-accordion { + cursor: pointer; + + .chevron-container { + position: relative; + margin-right: 5px; + + &.isCollapsed { + right: -6px; + top: 0; + * { + transform: rotate(270deg); + } + } + &:not(.isCollapsed) { + top: 6px; + } + * { + &:hover { + cursor: pointer; + } + } + } + .interface-name { + font-size: 18px; + font-family: @font-opensans-bold; + margin-bottom: 15px; + } + } + + .generic-table { + margin-bottom: 24px; + margin-top: 10px; + margin-left: 22px; + font-size: 14px; + + .header-row, .data-row { + .cell { + &.field-description { + flex: 2.5; + } + + &.field-actions { + flex-basis: 72px; + display: flex; + justify-content: center; + align-items: center; + } + } + } + + .header-row { + .cell { + background: @sdcui_color_silver; + + &.field-actions { + font-size: 10px; + } + } + } + + .data-row { + cursor: pointer; + + &:hover { + background: @sdcui_color_light-silver; + + .cell { + &.field-name { + color: @sdcui_color_dark-blue; + } + } + } + + &:not(:hover) { + .field-actions { + visibility: hidden; + } + } + + .cell { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + + &.field-description { + &:not(.collapsed) { + white-space: normal; + } + &.collapsed { + text-overflow: clip; + } + .more-or-less { + margin-left: 5px; + } + } + + &.field-actions { + .delete-action { + position: relative; + top: 2px; + } + } + } + + } + } + + } + } +} 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 new file mode 100644 index 0000000000..304fbcec3e --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.ts @@ -0,0 +1,247 @@ +/* +* ============LICENSE_START======================================================= +* SDC +* ================================================================================ +* Copyright (C) 2021 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, ComponentRef, Input} from '@angular/core'; +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'; +import { + Component as TopologyTemplate +} from "../../../../models/components/component"; +import {PluginsService} from "app/ng2/services/plugins.service"; +import {SelectedComponentType} from "../common/store/graph.actions"; + +import {WorkspaceService} from "../../workspace/workspace.service"; +import { + ComponentInstanceInterfaceModel, + InterfaceOperationModel +} from "../../../../models/interfaceOperation"; +import { + InterfaceOperationHandlerComponent +} from "./operation-creator/interface-operation-handler.component"; + +import { + ButtonModel, + ComponentMetadata, + InterfaceModel, + InputBEModel, + ModalModel, + ComponentInstance +} from 'app/models'; + +export class UIInterfaceOperationModel extends InterfaceOperationModel { + isCollapsed: boolean = true; + isEllipsis: boolean; + MAX_LENGTH = 75; + _description: string; + + constructor(operation: InterfaceOperationModel) { + super(operation); + + 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) + '...'; + } + return this.description; + } + + toggleCollapsed(e) { + e.stopPropagation(); + this.isCollapsed = !this.isCollapsed; + } +} + +class ModalTranslation { + EDIT_TITLE: string; + CANCEL_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.SAVE_BUTTON = this.TranslateService.translate("INTERFACE_SAVE_BUTTON"); + }); + } +} + +export class UIInterfaceModel extends ComponentInstanceInterfaceModel { + 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] +}) +export class InterfaceOperationsComponent { + interfaces: UIInterfaceModel[]; + selectedOperation: InterfaceOperationModel; + inputs: Array; + isLoading: boolean; + interfaceTypes: { [interfaceType: string]: string[] }; + topologyTemplate: TopologyTemplate; + componentMetaData: ComponentMetadata; + componentInstanceSelected: ComponentInstance; + modalInstance: ComponentRef; + modalTranslation: ModalTranslation; + componentInstancesInterfaces: Map; + + @Input() component: ComponentInstance; + @Input() readonly: boolean; + @Input() enableMenuItems: Function; + @Input() disableMenuItems: Function; + @Input() componentType: SelectedComponentType; + + + constructor( + private TranslateService: TranslateService, + private PluginsService: PluginsService, + private topologyTemplateService: TopologyTemplateService, + private modalServiceNg2: ModalService, + private workspaceService: WorkspaceService, + ) { + this.modalTranslation = new ModalTranslation(TranslateService); + } + + ngOnInit(): void { + this.componentMetaData = this.workspaceService.metadata; + this.loadComponentInstances(); + } + + 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: InterfaceModel[]): 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.modalInstance.instance.dynamicContent.instance.checkFormValidForSubmit(); + } + + onSelectInterfaceOperation(interfaceModel: UIInterfaceModel, operation: InterfaceOperationModel) { + const cancelButton: ButtonModel = new ButtonModel(this.modalTranslation.CANCEL_BUTTON, 'outline white', this.cancelAndCloseModal); + const saveButton: ButtonModel = new ButtonModel(this.modalTranslation.SAVE_BUTTON, 'blue', () => + this.updateInterfaceOperation(), this.enableOrDisableSaveButton); + const modalModel: ModalModel = new ModalModel('l', this.modalTranslation.EDIT_TITLE, '', [saveButton, cancelButton], 'custom'); + this.modalInstance = this.modalServiceNg2.createCustomModal(modalModel); + + this.modalServiceNg2.addDynamicContentToModal( + this.modalInstance, + InterfaceOperationHandlerComponent, + { + selectedInterface: interfaceModel, + selectedInterfaceOperation: operation, + validityChangedCallback: this.enableOrDisableSaveButton + } + ); + this.modalInstance.instance.open(); + } + + private cancelAndCloseModal = () => { + this.loadComponentInstances(); + return this.modalServiceNg2.closeCurrentModal(); + } + + private updateInterfaceOperation() { + this.isLoading = true; + let operationUpdated = this.modalInstance.instance.dynamicContent.instance.operationToUpdate; + 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; + } + +} diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.html b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.html new file mode 100644 index 0000000000..80aceeadda --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.html @@ -0,0 +1,44 @@ + + +
+ + +
+ +
+ + + +
+ +
+ + +
diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.less b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.less new file mode 100644 index 0000000000..12eacc6473 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.less @@ -0,0 +1,72 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2021 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. + * ============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/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.ts b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.ts new file mode 100644 index 0000000000..48bb804173 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.ts @@ -0,0 +1,48 @@ +/* +* ============LICENSE_START======================================================= +* SDC +* ================================================================================ +* Copyright (C) 2021 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 {InputOperationParameter} from "../../../../../../models/interfaceOperation"; + +@Component({ + selector: 'input-param-row', + templateUrl: './input-param-row.component.html', + styleUrls: ['./input-param-row.component.less'] +}) + +export class InputParamRowComponent { + @Input() input: InputOperationParameter; + @Input() onRemoveInput: Function; + @Input() readonly: boolean; + @Input() validityChanged: Function; + + constructor() { + } + + ngOnInit() { + this.validityChanged(); + } + + onChange() { + this.validityChanged(); + } + +} 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 new file mode 100644 index 0000000000..cd2d6063c1 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.html @@ -0,0 +1,86 @@ + + +
+ + +
+ +
+
+ + +
+ +
+ + +
+
+ +
+ + +
+ +
+ + +
+ + + +
+
+ {{ 'OPERATION_PARAM_NAME' | translate }} + {{ 'OPERATION_INPUT_VALUE' | translate }} + ●●● +
+
+
{{ 'OPERATION_INPUT_EMPTY' | translate }}
+
+ + +
+ +
+
diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.less b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.less new file mode 100644 index 0000000000..8bbed9de02 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.less @@ -0,0 +1,200 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2021 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. + * ============LICENSE_END========================================================= + */ + +@import '../../../../../../assets/styles/variables.less'; +@import '../../../../../../assets/styles/override.less'; + +.operation-handler { + 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-input-name, &.field-input-value{ + 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/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 new file mode 100644 index 0000000000..1618af4aea --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.ts @@ -0,0 +1,130 @@ +/* +* ============LICENSE_START======================================================= +* SDC +* ================================================================================ +* Copyright (C) 2021 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} from '@angular/core'; +import {UIInterfaceModel} from "../interface-operations.component"; +import { + InputOperationParameter, + InterfaceOperationModel, + IOperationParamsList +} from "../../../../../models/interfaceOperation"; +import {TranslateService} from "../../../../shared/translator/translate.service"; + +@Component({ + selector: 'operation-handler', + templateUrl: './interface-operation-handler.component.html', + styleUrls: ['./interface-operation-handler.component.less'], + providers: [TranslateService] +}) + +export class InterfaceOperationHandlerComponent { + + input: { + selectedInterface: UIInterfaceModel; + selectedInterfaceOperation: InterfaceOperationModel; + validityChangedCallback: Function; + }; + + interfaceType: string; + interfaceOperationName: string; + operationToUpdate: InterfaceOperationModel; + inputs: Array = []; + isLoading: boolean = false; + readonly: boolean; + + ngOnInit() { + this.interfaceType = this.input.selectedInterface.displayType(); + this.operationToUpdate = new InterfaceOperationModel(this.input.selectedInterfaceOperation); + this.operationToUpdate.interfaceId = this.input.selectedInterface.uniqueId; + this.operationToUpdate.interfaceType = this.input.selectedInterface.type; + if (!this.operationToUpdate.inputs) { + this.operationToUpdate.inputs = new class implements IOperationParamsList { + listToscaDataDefinition: Array = []; + } + } + this.inputs = this.operationToUpdate.inputs.listToscaDataDefinition; + this.removeImplementationQuote(); + this.validityChanged(); + } + + onAddInput(inputOperationParameter?: InputOperationParameter): void { + let newInput = new InputOperationParameter(inputOperationParameter) + newInput.type = "string"; + newInput.inputId = this.generateUniqueId(); + this.inputs.push(newInput); + this.validityChanged(); + } + + onRemoveInput = (inputParam: InputOperationParameter): void => { + let index = this.inputs.indexOf(inputParam); + this.inputs.splice(index, 1); + this.validityChanged(); + } + + private generateUniqueId = (): string => { + let result = ''; + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + const charactersLength = characters.length; + for (let i = 0; i < 36; i++ ) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; + } + + validityChanged = () => { + let validState = this.checkFormValidForSubmit(); + this.input.validityChangedCallback(validState); + if (validState) { + this.readonly = false; + } + } + + onDescriptionChange= (value: any): void => { + this.operationToUpdate.description = value; + } + + private checkFormValidForSubmit = (): boolean => { + return this.operationToUpdate.name && this.isParamsValid(); + } + + private isParamsValid = (): boolean => { + const isInputValid = (input) => input.name && input.inputId; + const isValid = this.inputs.every(isInputValid); + if (!isValid) { + this.readonly = true; + } + return isValid; + } + + private removeImplementationQuote(): void { + if (!this.operationToUpdate.implementation + || !this.operationToUpdate.implementation.artifactName) { + return; + } + + let implementation = this.operationToUpdate.implementation.artifactName.trim(); + + if (implementation.startsWith("'") && implementation.endsWith("'")) { + this.operationToUpdate.implementation.artifactName = implementation.slice(1, -1); + } + } + +} 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 new file mode 100644 index 0000000000..deba50aa7d --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.module.ts @@ -0,0 +1,55 @@ +/* +* ============LICENSE_START======================================================= +* SDC +* ================================================================================ +* Copyright (C) 2021 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 {CommonModule} from "@angular/common"; + +import {FormsModule} 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 {SdcUiComponentsModule} from 'onap-ui-angular'; +import {UiElementsModule} from '../../../../components/ui/ui-elements.module'; +import {InputParamRowComponent} from './input-param-row/input-param-row.component'; +import {InterfaceOperationHandlerComponent} from "./interface-operation-handler.component"; + +@NgModule({ + declarations: [ + InterfaceOperationHandlerComponent, + InputParamRowComponent + ], + imports: [ + CommonModule, + SdcUiComponentsModule, + FormsModule, + FormElementsModule, + TranslateModule, + UiElementsModule + ], + exports: [], + entryComponents: [ + InterfaceOperationHandlerComponent + ], + providers: [] +}) + +export class InterfaceOperationHandlerModule { +} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.spec.ts index 1761bfdd03..6d96764b1c 100644 --- a/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.spec.ts +++ b/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.spec.ts @@ -8,7 +8,7 @@ import { Service } from '../../../../models/components/service'; import { Resource } from '../../../../models/components/resource'; import { GroupInstance } from '../../../../models/graph/zones/group-instance'; import { PolicyInstance } from '../../../../models/graph/zones/policy-instance'; -import { ArtifactGroupType, ResourceType } from '../../../../utils/constants'; +import { ArtifactGroupType } from '../../../../utils/constants'; import { WorkspaceState } from '../../../store/states/workspace.state'; import { CompositionPanelComponent } from './composition-panel.component'; import { ArtifactsTabComponent } from './panel-tabs/artifacts-tab/artifacts-tab.component'; @@ -19,6 +19,7 @@ import { PolicyTargetsTabComponent } from './panel-tabs/policy-targets-tab/polic import { PropertiesTabComponent } from './panel-tabs/properties-tab/properties-tab.component'; import { ReqAndCapabilitiesTabComponent } from './panel-tabs/req-capabilities-tab/req-capabilities-tab.component'; import {SubstitutionFilterTabComponent} from "./panel-tabs/substitution-filter-tab/substitution-filter-tab.component"; +import {InterfaceOperationsComponent} from "../interface-operatons/interface-operations.component"; describe('composition-panel component', () => { @@ -61,7 +62,13 @@ describe('composition-panel component', () => { }, inputs: {titleIcon: 'inputs-o', component: PropertiesTabComponent, input: {title: 'Inputs'}, isActive: false, tooltipText: 'Inputs'}, settings: {titleIcon: 'settings-o', component: PropertiesTabComponent, input: {}, isActive: false, tooltipText: 'Settings'}, - + interfaceOperations: { + titleIcon: 'composition-o', + component: InterfaceOperationsComponent, + input: {title: 'Interface Operations'}, + isActive: false, + tooltipText: 'Interface Operations' + } }; beforeEach( @@ -157,17 +164,17 @@ describe('composition-panel component', () => { fixture.componentInstance.store.select = jest.fn(() => Observable.of(selectedComponent)); fixture.componentInstance.selectedComponentIsServiceProxyInstance = jest.fn(() => true); - // const pnfMock = Mock.of({ isResource : () => false }); fixture.componentInstance.topologyTemplate = selectedComponent; // Call ngOnInit fixture.componentInstance.ngOnInit(); // Expect that - expect (fixture.componentInstance.tabs.length).toBe(6); + expect (fixture.componentInstance.tabs.length).toBe(7); expect (fixture.componentInstance.tabs[0]).toEqual(tabs.infoTab); expect (fixture.componentInstance.tabs[1]).toEqual(tabs.properties); expect (fixture.componentInstance.tabs[2]).toEqual(tabs.reqAndCapabilities); + expect (fixture.componentInstance.tabs[6]).toEqual(tabs.interfaceOperations); }); diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.ts index 2fce002844..2ef4e7c9a9 100644 --- a/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.ts @@ -39,6 +39,7 @@ import { CompositionStateModel, GraphState } from '../common/store/graph.state'; import { ServiceConsumptionTabComponent } from './panel-tabs/service-consumption-tab/service-consumption-tab.component'; import { ServiceDependenciesTabComponent } from './panel-tabs/service-dependencies-tab/service-dependencies-tab.component'; import {SubstitutionFilterTabComponent} from "./panel-tabs/substitution-filter-tab/substitution-filter-tab.component"; +import {InterfaceOperationsComponent} from "../interface-operatons/interface-operations.component"; const tabs = { infoTab : {titleIcon: 'info-circle', component: InfoTabComponent, input: {}, isActive: true, tooltipText: 'Information'}, @@ -55,7 +56,8 @@ const tabs = { settings: {titleIcon: 'settings-o', component: PropertiesTabComponent, input: {}, isActive: false, tooltipText: 'Settings'}, consumption: {titleIcon: 'api-o', component: ServiceConsumptionTabComponent, input: {title: 'OPERATION CONSUMPTION'}, isActive: false, tooltipText: 'Service Consumption'}, dependencies: {titleIcon: 'archive', component: ServiceDependenciesTabComponent, input: {title: 'DIRECTIVES AND NODE FILTER'}, isActive: false, tooltipText: 'Service Dependencies'}, - substitutionFilter: {titleIcon: 'composition-o', component: SubstitutionFilterTabComponent, input: {title: 'SUBSTITUTION FILTER'}, isActive: false, tooltipText: 'Substitution Filter'} + substitutionFilter: {titleIcon: 'composition-o', component: SubstitutionFilterTabComponent, input: {title: 'SUBSTITUTION FILTER'}, isActive: false, tooltipText: 'Substitution Filter'}, + interfaceOperations: {titleIcon: 'composition-o', component: InterfaceOperationsComponent, input: {title: 'Interface Operations'}, isActive: false, tooltipText: 'Interface Operations'} }; @Component({ @@ -86,6 +88,12 @@ export class CompositionPanelComponent { }); } + + onRightClick(selectedComponent: any) { + console.info("onRightClick", selectedComponent) + return false; + } + ngOnDestroy() { if (this.subscription) { this.subscription.unsubscribe(); @@ -151,8 +159,10 @@ export class CompositionPanelComponent { if (component.isService() && (this.selectedComponentIsServiceProxyInstance() || this.selectedComponentIsServiceSubstitutionInstance())) { this.tabs.push(tabs.consumption); this.tabs.push(tabs.dependencies); + this.tabs.push(tabs.interfaceOperations); } else if (component.isResource() && this.isComponentInstanceSelected()) { this.tabs.push(tabs.dependencies); + this.tabs.push(tabs.interfaceOperations); } } diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.module.ts b/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.module.ts index a89db21b04..595ee21089 100644 --- a/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.module.ts +++ b/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.module.ts @@ -49,7 +49,7 @@ import { ServiceDependenciesModule } from "../../../components/logic/service-dep import { ServiceConsumptionModule } from "../../../components/logic/service-consumption/service-consumption.module"; import {SubstitutionFilterTabComponent} from "./panel-tabs/substitution-filter-tab/substitution-filter-tab.component"; import {SubstitutionFilterModule} from "../../../components/logic/substitution-filter/substitution-filter.module"; - +import {InterfaceOperationsComponent} from "../interface-operatons/interface-operations.component"; @NgModule({ @@ -67,7 +67,8 @@ import {SubstitutionFilterModule} from "../../../components/logic/substitution-f ServiceDependenciesTabComponent, SubstitutionFilterTabComponent, RequirementListComponent, - EnvParamsComponent + EnvParamsComponent, + InterfaceOperationsComponent, ], imports: [ GlobalPipesModule, @@ -81,7 +82,7 @@ import {SubstitutionFilterModule} from "../../../components/logic/substitution-f NgxDatatableModule, ServiceDependenciesModule, ServiceConsumptionModule, - SubstitutionFilterModule + SubstitutionFilterModule, // EnvParamsModule ], entryComponents: [ @@ -98,7 +99,8 @@ import {SubstitutionFilterModule} from "../../../components/logic/substitution-f SubstitutionFilterTabComponent, RequirementListComponent, PanelTabComponent, - EnvParamsComponent + EnvParamsComponent, + InterfaceOperationsComponent ], exports: [ CompositionPanelComponent diff --git a/catalog-ui/src/app/ng2/services/component-services/topology-template.service.ts b/catalog-ui/src/app/ng2/services/component-services/topology-template.service.ts index 492acdc1d6..953f0a1960 100644 --- a/catalog-ui/src/app/ng2/services/component-services/topology-template.service.ts +++ b/catalog-ui/src/app/ng2/services/component-services/topology-template.service.ts @@ -35,7 +35,7 @@ import { PropertyModel, IFileDownload, AttributeModel, - Capability, Requirement + Capability, Requirement, BEOperationModel, InterfaceModel } from "app/models"; import {ArtifactGroupType, COMPONENT_FIELDS} from "app/utils"; import {ComponentGenericResponse} from "../responses/component-generic-response"; @@ -65,6 +65,11 @@ import { PolicyInstance } from "../../../models/graph/zones/policy-instance"; import { PropertyBEModel } from "../../../models/properties-inputs/property-be-model"; import {map} from "rxjs/operators"; import {CapabilitiesConstraintObject} from "../../components/logic/capabilities-constraint/capabilities-constraint.component"; +import { + BEInterfaceOperationModel, + ComponentInstanceInterfaceModel, + InterfaceOperationModel +} from "../../../models/interfaceOperation"; /* we need to use this service from now, we will remove component.service when we finish remove the angular1. The service is duplicated since we can not use downgrades service with NGXS*/ @@ -108,8 +113,8 @@ export class TopologyTemplateService { [COMPONENT_FIELDS.COMPONENT_INSTANCES, COMPONENT_FIELDS.COMPONENT_POLICIES, COMPONENT_FIELDS.COMPONENT_NON_EXCLUDED_GROUPS]); } - getComponentResourceInstances(component: Component): Observable { - return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES]); + getComponentInstances(componentType: string, componentId: string): Observable { + return this.getComponentDataByFieldsName(componentType, componentId, [COMPONENT_FIELDS.COMPONENT_INSTANCES]); } getComponentInputs(component: Component): Observable { @@ -478,6 +483,7 @@ export class TopologyTemplateService { } protected getComponentDataByFieldsName(componentType: string, componentId: string, fields: string[]): Observable { + console.info("Topology template -> getComponentDataByFieldsName with id:", componentId) let params: HttpParams = new HttpParams(); _.forEach(fields, (field: string): void => { params = params.append(API_QUERY_PARAMS.INCLUDE, field); @@ -485,6 +491,7 @@ export class TopologyTemplateService { // tslint:disable-next-line:object-literal-shorthand return this.http.get(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/filteredDataByParams', {params: params}) .map((res) => { + console.info("Topology template -> getComponentDataByFieldsName response:", res); return componentType === ComponentType.SERVICE ? new ServiceGenericResponse().deserialize(res) : new ComponentGenericResponse().deserialize(res); }); @@ -564,4 +571,22 @@ export class TopologyTemplateService { .pipe(map(response => response.directives)); } + updateComponentInstanceInterfaceOperation(componentMetaDataId: string, + componentMetaDataType: string, + componentInstanceId: string, + operation: InterfaceOperationModel): Observable { + const operationList = { + interfaces: { + [operation.interfaceType]: { + type: operation.interfaceType, + operations: { + [operation.name]: new BEInterfaceOperationModel(operation) + } + } + } + }; + return this.http.put(this.baseUrl + this + .getServerTypeUrl(componentMetaDataType) + componentMetaDataId + '/componentInstance/' + componentInstanceId + '/interfaceOperation', operationList); + } + } diff --git a/catalog-ui/src/assets/languages/en_US.json b/catalog-ui/src/assets/languages/en_US.json index cc33b36db2..7cc6d554fe 100644 --- a/catalog-ui/src/assets/languages/en_US.json +++ b/catalog-ui/src/assets/languages/en_US.json @@ -468,6 +468,7 @@ "OPERATION_INTERFACE_TYPE": "Interface Name", "OPERATION_NAME": "Operation Name", "OPERATION_IMPLEMENTATION": "Implementation", + "IMPLEMENTATION_NAME": "Implementation Name", "OPERATION_DESCRIPTION": "Description", "OPERATION_ARTIFACT": "Workflow Artifact", "OPERATION_WORKFLOW_ASSIGNMENT": "Workflow Assignment", @@ -482,6 +483,9 @@ "OPERATION_PARAM_VALUE": "Value", "OPERATION_PARAM_PROPERTY": "Property", "OPERATION_PARAM_MANDATORY": "Mandatory", + "OPERATION_INPUT_EMPTY": "No Input Data", + "OPERATION_INPUT_VALUE": "Value", + "OPERATION_ADD_INPUT": "Add Input", "OPERATION_ADD": "Add", "OPERATION_ADD1": "Add Operation", "OPERATION_CANCEL": "Cancel", -- cgit 1.2.3-korg