Add support for updating interface operations 16/117116/3
authoraribeiro <anderson.ribeiro@est.tech>
Thu, 19 Nov 2020 13:28:43 +0000 (13:28 +0000)
committerChristophe Closset <christophe.closset@intl.att.com>
Fri, 29 Jan 2021 08:22:04 +0000 (08:22 +0000)
Allows to update interface operations on a component instance.

Issue-ID: SDC-3446
Signed-off-by: aribeiro <anderson.ribeiro@est.tech>
Signed-off-by: andre.schmid <andre.schmid@est.tech>
Change-Id: I6a2c44997c04d9d9ea298e3d0bc971da7b137799

34 files changed:
catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ComponentInstanceBusinessLogic.java
catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ComponentInterfaceOperationBusinessLogic.java [new file with mode: 0644]
catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/InterfaceDefinitionHandler.java
catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ResourceImportManager.java
catalog-be/src/main/java/org/openecomp/sdc/be/servlets/ComponentInterfaceOperationServlet.java [new file with mode: 0644]
catalog-be/src/main/java/org/openecomp/sdc/be/tosca/InterfacesOperationsConverter.java
catalog-be/src/main/java/org/openecomp/sdc/be/tosca/ToscaExportHandler.java
catalog-be/src/test/java/org/openecomp/sdc/be/components/ResourceImportManagerTest.java
catalog-be/src/test/java/org/openecomp/sdc/be/components/impl/ComponentInterfaceOperationBusinessLogicTest.java [new file with mode: 0644]
catalog-be/src/test/java/org/openecomp/sdc/be/components/impl/InputsBusinessLogicTest.java
catalog-be/src/test/java/org/openecomp/sdc/be/tosca/InterfacesOperationsConverterTest.java
catalog-model/src/main/java/org/openecomp/sdc/be/model/ComponentParametersView.java
catalog-model/src/main/java/org/openecomp/sdc/be/model/jsonjanusgraph/operations/NodeTemplateOperation.java
catalog-ui/src/app/models/componentsInstances/componentInstance.ts
catalog-ui/src/app/models/inputs.ts
catalog-ui/src/app/models/interfaceOperation.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/app.module.ts
catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-instance/zone-instance.component.ts
catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.html
catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.html [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.less [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.html [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.less [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-param-row/input-param-row.component.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.html [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.less [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.module.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.spec.ts
catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.ts
catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.module.ts
catalog-ui/src/app/ng2/services/component-services/topology-template.service.ts
catalog-ui/src/assets/languages/en_US.json

index 7c463ac..1104621 100644 (file)
@@ -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<String, InterfaceDefinition> 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 (file)
index 0000000..e32c51f
--- /dev/null
@@ -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<ComponentInstance> updateComponentInstanceInterfaceOperation(final String componentId,
+                                                                                 final String componentInstanceId,
+                                                                                 final InterfaceDefinition interfaceDefinition,
+                                                                                 final ComponentTypeEnum componentTypeEnum,
+                                                                                 final Wrapper<ResponseFormat> errorWrapper,
+                                                                                 final boolean shouldLock)
+        throws BusinessLogicException {
+
+        final Component component = getComponent(componentId);
+        final Optional<ComponentInstance> 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<String, List<ComponentInstanceInterface>> componentInstancesInterfaceMap = component
+            .getComponentInstancesInterfaces();
+        if (MapUtils.isEmpty(componentInstancesInterfaceMap)) {
+            componentInstancesInterfaceMap = new HashMap<>();
+            component.setComponentInstancesInterfaces(componentInstancesInterfaceMap);
+        }
+        final List<ComponentInstanceInterface> 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<OperationDataDefinition> 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<ComponentInstanceInterface> 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<Component, StorageOperationStatus> 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);
+    }
+}
index aeb4376..71005ef 100644 (file)
@@ -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<String, Object> 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<String, Object> interfaceInputs =
                 (Map<String, Object>) operationDefinitionMap.get(INPUTS.getElementName());
@@ -189,6 +194,8 @@ public class InterfaceDefinitionHandler {
         final ListDataDefinition<OperationInputDefinition> inputs = new ListDataDefinition<>();
         for (final Entry<String, Object> 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<String, Object> inputPropertyValue =
index b96e4e5..6137a3f 100644 (file)
@@ -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 (file)
index 0000000..c2e668c
--- /dev/null
@@ -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<InterfaceDefinition> mappedInterfaceOperationData = getMappedInterfaceData(data, userModifier, componentTypeEnum);
+        if (mappedInterfaceOperationData.isEmpty()) {
+            LOGGER.error(INTERFACE_OPERATION_CONTENT_INVALID, data);
+            return buildErrorResponse(getComponentsUtils().getResponseFormat(ActionStatus.INVALID_CONTENT));
+        }
+        final Wrapper<ResponseFormat> errorWrapper = new Wrapper<>();
+        try {
+             final Optional<ComponentInstance> 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<InterfaceDefinition> getMappedInterfaceData(final String inputJson,
+                                                                 final User user,
+                                                                 final ComponentTypeEnum componentTypeEnum) {
+        final Either<UiComponentDataTransfer, ResponseFormat> uiComponentEither =
+            getComponentsUtils().convertJsonToObjectUsingObjectMapper(inputJson, user,
+                UiComponentDataTransfer.class, AuditingActionEnum.UPDATE_RESOURCE_METADATA, componentTypeEnum);
+        return uiComponentEither.left().value().getInterfaces().values().stream().findFirst();
+    }
+
+}
index 8fb835e..8d35517 100644 (file)
@@ -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<String, DataTypeDefinition> dataTypes,
                                                 final boolean isAssociatedComponent,
                                                 final boolean isServiceProxyInterface) {
-        if(MapUtils.isEmpty(interfaces)) {
+        if (MapUtils.isEmpty(interfaces)) {
             return null;
         }
 
-        Map<String, Object> toscaInterfaceDefinitions = new HashMap<>();
+        final Map<String, Object> 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<String, OperationDataDefinition> operations = interfaceDefinition.getOperations();
-            Map<String, Object> toscaOperationMap = new HashMap<>();
-
-            String operationArtifactPath;
-            for (Map.Entry<String, OperationDataDefinition> 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<String, Object> getInterfacesMapFromComponentInstance(final Component component,
+                                                                     final ComponentInstance componentInstance,
+                                                                     final Map<String, DataTypeDefinition> dataTypes,
+                                                                     final boolean isAssociatedComponent,
+                                                                     final boolean isServiceProxyInterface) {
+        final Map<String, Object> toscaInterfaceDefinitions = new HashMap<>();
+        final ObjectMapper objectMapper = new ObjectMapper();
+        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+        for (final Map.Entry<String, Object> 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<String, Object> interfaceInputMap = createInterfaceInputMap(interfaceDefinition, dataTypes);
-            if (!interfaceInputMap.isEmpty()) {
-                toscaInterfaceDefinition.setInputs(interfaceInputMap);
-            }
+    private void handleInterfaceOperations(final Component component,
+                                           final ComponentInstance componentInstance,
+                                           final Map<String, DataTypeDefinition> dataTypes,
+                                           final boolean isAssociatedComponent,
+                                           final boolean isServiceProxyInterface,
+                                           final Map<String, Object> 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<String, OperationDataDefinition> operations = interfaceDefinition.getOperations();
+        final Map<String, Object> toscaOperationMap = new HashMap<>();
+
+        for (final Entry<String, OperationDataDefinition> 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<String, Object> interfaceDefAsMap = getObjectAsMap(toscaInterfaceDefinition);
-            if (interfaceDefAsMap.containsKey(INPUTS.getElementName())) {
-                handleDefaults((Map<String, Object>) interfaceDefAsMap.get(INPUTS.getElementName()));
-            }
-            Map<String, Object> operationsMap = (Map<String, Object>) 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<String, Object> interfaceInputMap = createInterfaceInputMap(interfaceDefinition, dataTypes);
+        if (!interfaceInputMap.isEmpty()) {
+            toscaInterfaceDefinition.setInputs(interfaceInputMap);
+        }
+        final Map<String, Object> interfaceDefinitionAsMap = getObjectAsMap(toscaInterfaceDefinition);
+        if (interfaceDefinitionAsMap.containsKey(INPUTS.getElementName())) {
+            handleDefaults((Map<String, Object>) interfaceDefinitionAsMap.get(INPUTS.getElementName()));
         }
+        final Map<String, Object> operationsMap =
+            (Map<String, Object>) 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<String, OperationDataDefinition> 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<String, Object> interfaceMap) {
@@ -244,21 +283,6 @@ public class InterfacesOperationsConverter {
         return toscaInterfaceInputMap;
     }
 
-    private static void handleServiceProxyOperationInputValue(Map<String, Object> operationsMap, String parentKey) {
-        for (Map.Entry<String, Object> 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<String, Object>) value);
-                    operationsMap.put(key, defaultValue);
-                } else {
-                    handleServiceProxyOperationInputValue((Map<String, Object>) value, key);
-                }
-            }
-        }
-    }
-
     private static Object getDefaultValue(Map<String, Object> inputValueMap) {
         Object defaultValue = null;
         for (Map.Entry<String, Object> 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<String, Object> operationsMap, String parentKey) {
+        for (Map.Entry<String, Object> 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<String, Object>) value);
+                    operationsMap.put(key, defaultValue);
+                } else {
+                    handleOperationInputValue((Map<String, Object>) 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"
index f912564..b4bf4e8 100644 (file)
@@ -293,7 +293,7 @@ public class ToscaExportHandler {
         if (nodeTypesMap != null && !nodeTypesMap.isEmpty()) {
             toscaNode.setNode_types(nodeTypesMap);
         }
-        
+
         createServiceSubstitutionNodeTypes(componentCache, component, toscaNode);
 
         Either<Map<String, Object>, 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<String, Object> 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<ComponentInstanceInterface> currServiceInterfaces =
-                componentInstanceInterfaces.get(instanceUniqueId);
+        final List<ComponentInstanceInterface> currServiceInterfaces =
+            componentInstanceInterfaces.get(instanceUniqueId);
 
-            final Map<String, InterfaceDefinition> tmpInterfaces = new HashMap<>();
-            currServiceInterfaces.forEach(instInterface -> tmpInterfaces.put(instInterface
-                .getUniqueId(), instInterface));
+        final Map<String, InterfaceDefinition> tmpInterfaces = new HashMap<>();
+        currServiceInterfaces.forEach(instInterface -> tmpInterfaces.put(instInterface
+            .getUniqueId(), instInterface));
+
+        final Map<String, Object> 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<String, Component> componentCache,
             final Component container, final ToscaTemplate toscaNode) {
         final List<ComponentInstance> componentInstances = container.getComponentInstances();
@@ -1226,11 +1220,11 @@ public class ToscaExportHandler {
                 final Map<String, ToscaNodeType> nodeTypes = toscaNode.getNode_types() == null ? new HashMap<>() : toscaNode.getNode_types();
                 convertInterfaceNodeType(new HashMap<>(), componentCache.get(inst.getSourceModelUid()), toscaNode, nodeTypes, true);
             }
-        }  
+        }
     }
 
     private ToscaNodeType createProxyNodeType(Map<String, Component> 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<String, DataTypeDefinition> dataTypes = dataTypesEither.left().value();
         Map<String, ToscaCapability> capabilities = this.capabilityRequirementConverter
-            .convertProxyCapabilities(componentCache, instance, dataTypes);
+            .convertProxyCapabilities(componentCache, componentInstance, dataTypes);
 
         if (MapUtils.isNotEmpty(capabilities)) {
             toscaNodeType.setCapabilities(capabilities);
         }
         List<Map<String, ToscaRequirement>> proxyNodeTypeRequirements = this.capabilityRequirementConverter
-            .convertProxyRequirements(componentCache, instance);
+            .convertProxyRequirements(componentCache, componentInstance);
         if (CollectionUtils.isNotEmpty(proxyNodeTypeRequirements)) {
             toscaNodeType.setRequirements(proxyNodeTypeRequirements);
         }
         Optional<Map<String, ToscaProperty>> proxyProperties = getProxyNodeTypeProperties(proxyComponent, dataTypes);
         proxyProperties.ifPresent(toscaNodeType::setProperties);
 
-        Optional<Map<String, Object>> proxyInterfaces = getProxyNodeTypeInterfaces(proxyComponent, dataTypes);
-        if (proxyInterfaces.isPresent()) {
-            final Map<String, Object> interfaceMap = proxyInterfaces.get();
-            interfacesOperationsConverter.removeInterfacesWithoutOperations(interfaceMap);
-            toscaNodeType.setInterfaces(MapUtils.isEmpty(interfaceMap) ? null : interfaceMap);
+        Map<String, Object> interfaceMap = new HashMap<>();
+        if (MapUtils.isEmpty(componentInstance.getInterfaces())) {
+            final Optional<Map<String, Object>> 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;
     }
index 336b8ec..18dc67f 100644 (file)
@@ -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<String, InterfaceDefinition> 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<String, InterfaceDefinition> interfaceTypes = new HashMap<>();
         final InterfaceDefinition interfaceDefinition = new InterfaceDefinition();
         interfaceDefinition.setType("tosca.interfaces.node.lifecycle.Standard");
@@ -252,7 +253,7 @@ public class ResourceImportManagerTest {
         ImmutablePair<Resource, ActionStatus> 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<String, InterfaceDefinition> 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<String, InterfaceDefinition> 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<String, OperationDataDefinition> 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<String, OperationDataDefinition> 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 (file)
index 0000000..0fd6184
--- /dev/null
@@ -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<String, OperationDataDefinition> 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<String, List<ComponentInstanceInterface>> componentInstancesInterfacesMap = new HashMap<>();
+        componentInstancesInterfacesMap.put(componentInstanceId, Collections.singletonList(componentInstanceInterface));
+        component.setComponentInstancesInterfaces(componentInstancesInterfacesMap);
+        componentInstance.setInterfaces(
+            (Map<String, Object>) 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<ComponentInstance> 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());
+        }
+    }
+}
index c83f73b..548ef1a 100644 (file)
@@ -777,8 +777,8 @@ public class InputsBusinessLogicTest {
         inputDef.setDefaultValue(NEW_VALUE); // update value
         inputDef.setRequired(Boolean.TRUE); // update value
         Map<String, String> 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"));
     }
 
 }
index 4f569f9..d4fb60e 100644 (file)
@@ -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<String, Object> inputEntry : inputs.entrySet()) {
             String[] inputNameSplit = inputEntry.getKey().split("_");
             Map<String, Object> inputValueObject = (Map<String, Object>) 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<String, Object> inputValueObject) {
-        Map<String, Object> mappedInputValue = (Map<String, Object>) inputValueObject.get("default");
-        if(mappedInputValue.containsKey(ToscaFunctions.GET_PROPERTY.getFunctionName())) {
+        if (inputValueObject.containsKey(ToscaFunctions.GET_PROPERTY.getFunctionName())) {
             String mappedPropertyValue = MAPPED_PROPERTY_NAME + index;
-            List<String> mappedPropertyDefaultValue = (List<String>) mappedInputValue.get(ToscaFunctions.GET_PROPERTY.getFunctionName());
+            List<String> mappedPropertyDefaultValue = (List<String>) 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<String> mappedPropertyDefaultValue = (List<String>) mappedInputValue.get(ToscaFunctions.GET_OPERATION_OUTPUT.getFunctionName());
+        } else if (inputValueObject.containsKey(ToscaFunctions.GET_OPERATION_OUTPUT.getFunctionName())) {
+            List<String> mappedPropertyDefaultValue = (List<String>) inputValueObject
+                .get(ToscaFunctions.GET_OPERATION_OUTPUT.getFunctionName());
             assertEquals(4, mappedPropertyDefaultValue.size());
             String mappedPropertyValue = OUTPUT_NAME_PREFIX + inputType + "_" + index;
             assertTrue(mappedPropertyDefaultValue.contains(SELF));
index 505b8e6..6488d41 100644 (file)
@@ -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:
index facbcbe..3f3e541 100644 (file)
@@ -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<ImmutablePair<TopologyTemplate, String>, StorageOperationStatus> addServiceInstanceInterfacesToProxyServiceInstance(TopologyTemplate updatedContainer, ComponentInstance componentInstance) {
+    private Either<ImmutablePair<TopologyTemplate, String>, StorageOperationStatus> addComponentInstanceInterfacesToTopologyTemplate(TopologyTemplate updatedContainer, ComponentInstance componentInstance) {
         Map<String, Object> interfaces = componentInstance.getInterfaces();
 
         if(MapUtils.isNotEmpty(interfaces)){
-            Map<String, InterfaceDataDefinition> interfacesMap = interfaces.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> (InterfaceDataDefinition) e.getValue()));
+            Map<String, InterfaceDataDefinition> interfacesMap = interfaces.entrySet().stream().collect(Collectors.toMap(e -> ((InterfaceDataDefinition) e.getValue()).getUniqueId(), e -> (InterfaceDataDefinition) e.getValue()));
             MapInterfaceDataDefinition instInterfaces = new MapInterfaceDataDefinition(interfacesMap);
 
             Map<String, MapInterfaceDataDefinition> instInterfacesMap = new HashMap<>();
index 2e0c1a5..a55cd4f 100644 (file)
@@ -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;
         }
     }
 
index 49fd16d..562db98 100644 (file)
@@ -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 (file)
index 0000000..5c69688
--- /dev/null
@@ -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<InputOperationParameter>;
+}
+
+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<InterfaceOperationModel>;
+
+    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);
index b94ba61..ac8a9b6 100644 (file)
@@ -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,
index 1b1363e..d7b997d 100644 (file)
@@ -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
+}
index 5a0ca3e..9f6a8bc 100644 (file)
@@ -54,4 +54,4 @@
                        (assignmentSaveComplete)="zoneAssignmentSaveComplete($event)">
         </zone-instance>
     </zone-container>
-</div>
\ No newline at end of file
+</div>
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 (file)
index 0000000..7567b90
--- /dev/null
@@ -0,0 +1,80 @@
+<!--
+ * ============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=========================================================
+-->
+
+<div class="interface-operations">
+  <loader [display]="isLoading" [size]="'large'" [relative]="true"></loader>
+  <div class="operation-list">
+    <div *ngIf="!isListEmpty()">
+      <div class="expand-collapse">
+        <a class="link"
+           [ngClass]="{'disabled': isAllExpanded()}"
+           (click)="collapseAll(false)">{{ 'INTERFACE_EXPAND_ALL' | translate }}
+        </a> |
+        <a class="link"
+           [ngClass]="{'disabled': isAllCollapsed()}"
+           (click)="collapseAll()">
+          {{ 'INTERFACE_COLLAPSE_ALL' | translate }}
+        </a>
+      </div>
+
+      <div class="interface-row" *ngFor="let interface1 of interfaces">
+        <div class="interface-accordion" (click)="interface1.toggleCollapse()">
+          <span
+              class="chevron-container"
+              [ngClass]="{'isCollapsed': interface1.isCollapsed}">
+              <svg-icon
+                  name="caret1-down-o"
+                  mode="primary"
+                  size="small">
+              </svg-icon>
+          </span>
+          <span class="interface-name">{{interface1.displayType()}}</span>
+        </div>
+
+        <div class="generic-table" *ngIf="!interface1.isCollapsed">
+          <div class="header-row table-row">
+            <span
+                class="cell header-cell field-name header-name">
+                {{ 'INTERFACE_HEADER_NAME' | translate }}
+            </span>
+            <span class="cell header-cell field-description header-description">
+              {{ 'INTERFACE_HEADER_DESCRIPTION' | translate }}
+            </span>
+          </div>
+
+          <div class="data-row" *ngFor="let operation of interface1.operations"
+               (click)="onSelectInterfaceOperation(interface1, operation)">
+            <span
+                class="cell field-name">
+                {{operation.name}}
+            </span>
+            <span class="cell field-description"
+                  [ngClass]="{'collapsed': operation.isCollapsed}">
+              {{operation.getDescriptionEllipsis()}}
+              <span class="more-or-less link" (click)="operation.toggleCollapsed($event)">
+                {{!operation.isEllipsis ? '' : operation.isCollapsed ? 'More' : 'Less'}}
+              </span>
+            </span>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
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 (file)
index 0000000..1ebfb1f
--- /dev/null
@@ -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 (file)
index 0000000..304fbce
--- /dev/null
@@ -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<InputBEModel>;
+  isLoading: boolean;
+  interfaceTypes: { [interfaceType: string]: string[] };
+  topologyTemplate: TopologyTemplate;
+  componentMetaData: ComponentMetadata;
+  componentInstanceSelected: ComponentInstance;
+  modalInstance: ComponentRef<ModalComponent>;
+  modalTranslation: ModalTranslation;
+  componentInstancesInterfaces: Map<string, InterfaceModel[]>;
+
+  @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 (file)
index 0000000..80aceea
--- /dev/null
@@ -0,0 +1,44 @@
+<!--
+ * ============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=========================================================
+ -->
+
+<div class="cell field-input-name">
+    <sdc-input
+        [(value)]="input.name"
+        (valueChange)="onChange()">
+    </sdc-input>
+</div>
+
+<div class="cell field-input-value">
+    <sdc-input
+        [(value)]="input.toscaDefaultValue"
+        (valueChange)="onChange()">
+    </sdc-input>
+
+</div>
+
+<div class="cell remove" *ngIf="!readonly">
+    <svg-icon
+        name="trash-o"
+        mode="info"
+        size="small"
+        (click)="onRemoveInput(input)"
+        [clickable]="true">
+    </svg-icon>
+</div>
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 (file)
index 0000000..12eacc6
--- /dev/null
@@ -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 (file)
index 0000000..48bb804
--- /dev/null
@@ -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 (file)
index 0000000..cd2d606
--- /dev/null
@@ -0,0 +1,86 @@
+<!--
+ * ============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=========================================================
+-->
+
+<div class="operation-handler">
+  <loader [display]="isLoading" [size]="'large'" [relative]="true"></loader>
+
+  <form class="w-sdc-form">
+
+    <div class="side-by-side">
+      <div class="form-item">
+        <sdc-input
+            label="{{ 'OPERATION_INTERFACE_TYPE' | translate }}"
+            [(value)]="interfaceType"
+            [disabled]="true">
+        </sdc-input>
+      </div>
+
+      <div class="form-item">
+        <sdc-input
+            label="{{ 'OPERATION_NAME' | translate }}"
+            [(value)]="operationToUpdate.name"
+            [disabled]="true">
+        </sdc-input>
+      </div>
+    </div>
+
+    <div class="i-sdc-form-item">
+      <sdc-input
+          label="{{'OPERATION_DESCRIPTION' | translate}}"
+          [(value)]="operationToUpdate.description"
+          (valueChange)="onDescriptionChange($event)">
+      </sdc-input>
+    </div>
+
+    <div class="i-sdc-form-item">
+      <sdc-input
+          label="{{'IMPLEMENTATION_NAME' | translate}}"
+          [(value)]="operationToUpdate.implementation.artifactName">
+      </sdc-input>
+    </div>
+
+    <div class="separator-buttons">
+      <tab tabTitle="Inputs"></tab>
+      <a class="add-param-link add-btn"
+         [ngClass]="{'disabled': readonly}"
+         (click)="onAddInput()">{{'OPERATION_ADD_INPUT' | translate}}
+      </a>
+    </div>
+
+    <div class="generic-table">
+      <div class="header-row table-row">
+        <span class="cell header-cell field-input-name">{{ 'OPERATION_PARAM_NAME' | translate }}</span>
+        <span class="cell header-cell field-input-value">{{ 'OPERATION_INPUT_VALUE' | translate }}</span>
+        <span class="cell header-cell remove">●●●</span>
+      </div>
+      <div class="empty-msg data-row" *ngIf="!inputs.length">
+        <div>{{ 'OPERATION_INPUT_EMPTY' | translate }}</div>
+      </div>
+      <input-param-row
+          *ngFor="let inputParameter of inputs"
+          class="data-row"
+          [input]="inputParameter"
+          [onRemoveInput]="onRemoveInput"
+          [validityChanged]="validityChanged">
+      </input-param-row>
+    </div>
+
+  </form>
+</div>
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 (file)
index 0000000..8bbed9d
--- /dev/null
@@ -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 (file)
index 0000000..1618af4
--- /dev/null
@@ -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<InputOperationParameter> = [];
+    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<InputOperationParameter> = [];
+            }
+        }
+        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 (file)
index 0000000..deba50a
--- /dev/null
@@ -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 {
+}
index 1761bfd..6d96764 100644 (file)
@@ -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<Service>({ 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);
 
     });
 
index 2fce002..2ef4e7c 100644 (file)
@@ -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);
         }
 
     }
index a89db21..595ee21 100644 (file)
@@ -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
index 492acdc..953f0a1 100644 (file)
@@ -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<ComponentGenericResponse> {
-        return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES]);
+    getComponentInstances(componentType: string, componentId: string): Observable<ComponentGenericResponse> {
+        return this.getComponentDataByFieldsName(componentType, componentId, [COMPONENT_FIELDS.COMPONENT_INSTANCES]);
     }
 
     getComponentInputs(component: Component): Observable<ComponentGenericResponse> {
@@ -478,6 +483,7 @@ export class TopologyTemplateService {
     }
 
     protected getComponentDataByFieldsName(componentType: string, componentId: string, fields: string[]): Observable<ComponentGenericResponse> {
+        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<ComponentGenericResponse>(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<ComponentInstance> {
+        const operationList = {
+            interfaces: {
+                [operation.interfaceType]: {
+                    type: operation.interfaceType,
+                    operations: {
+                        [operation.name]: new BEInterfaceOperationModel(operation)
+                    }
+                }
+            }
+        };
+        return this.http.put<ComponentInstance>(this.baseUrl + this
+        .getServerTypeUrl(componentMetaDataType) + componentMetaDataId + '/componentInstance/' + componentInstanceId + '/interfaceOperation', operationList);
+    }
+
 }
index cc33b36..7cc6d55 100644 (file)
     "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",
     "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",