Implement adding Interface to VFC 76/127976/28
authorvasraz <vasyl.razinkov@est.tech>
Thu, 24 Mar 2022 18:31:14 +0000 (18:31 +0000)
committerVasyl Razinkov <vasyl.razinkov@est.tech>
Mon, 4 Apr 2022 16:56:40 +0000 (16:56 +0000)
Change-Id: I7cd8b82c306294d897d37d486aa3eeff7ca4206d
Signed-off-by: Vasyl Razinkov <vasyl.razinkov@est.tech>
Issue-ID: SDC-3893
Signed-off-by: andre.schmid <andre.schmid@est.tech>
23 files changed:
catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ComponentInterfaceOperationBusinessLogic.java
catalog-be/src/main/java/org/openecomp/sdc/be/servlets/ComponentInterfaceOperationServlet.java
catalog-be/src/main/java/org/openecomp/sdc/be/servlets/ComponentNodeFilterServlet.java
catalog-model/src/main/java/org/openecomp/sdc/be/model/jsonjanusgraph/operations/ToscaOperationFacade.java
catalog-ui/src/app/ng2/app.module.ts
catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/create-interface-operation/create-interface-operation.component.less
catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.ts
catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.html
catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.ts
catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.module.ts
catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.html
catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.ts
catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.html [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.less [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.component.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/operation-creator-interface-definition.module.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/param-row/param-row.component.html [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/param-row/param-row.component.less [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/interface-definition/operation-creator/param-row/param-row.component.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/pages/interface-operation/interface-operation.page.component.ts
catalog-ui/src/app/ng2/services/component-services/component.service.ts
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/execute/sanity/ImportVfcUiTest.java
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/execute/sanity/ServiceTemplateDesignUiTests.java

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