Integrate VNF Repository in Beijing release 81/40681/6
authorMurali-P <murali.p@huawei.com>
Thu, 29 Mar 2018 12:16:39 +0000 (17:46 +0530)
committerVitaly Emporopulo <Vitaliy.Emporopulo@amdocs.com>
Mon, 30 Apr 2018 13:51:11 +0000 (13:51 +0000)
Migrate the code

Change-Id: Ifccacf83634af32b034fd9c413e68f894f06d2f7
Issue-ID: VNFSDK-155
Signed-off-by: Murali-P <murali.p@huawei.com>
26 files changed:
openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/pom.xml
openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/src/main/webapp/WEB-INF/beans-services.xml
openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/pom.xml
openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vnf-repository-rest-services/pom.xml [new file with mode: 0644]
openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vnf-repository-rest-services/src/main/java/org/openecomp/sdcrests/vsp/rest/VnfPackageRepository.java [new file with mode: 0644]
openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vnf-repository-rest-services/src/main/java/org/openecomp/sdcrests/vsp/rest/services/VnfPackageRepositoryImpl.java [new file with mode: 0644]
openecomp-be/dist/sdc-onboard-backend-docker/artifacts/chef-repo/cookbooks/sdc-onboard-backend/recipes/ON_5_setup_configuration.rb
openecomp-be/dist/sdc-onboard-backend-docker/artifacts/chef-repo/cookbooks/sdc-onboard-backend/templates/default/vnfrepo-configuration.yaml.erb [new file with mode: 0644]
openecomp-be/dist/sdc-onboard-backend-docker/artifacts/startup.sh
openecomp-ui/resources/scss/_components.scss
openecomp-ui/resources/scss/components/_vnfBrowse.scss [new file with mode: 0644]
openecomp-ui/resources/scss/modules/_softwareProductLandingPage.scss
openecomp-ui/src/nfvo-components/vnfMarketPlace/VnfRepositorySearchBox.jsx [new file with mode: 0644]
openecomp-ui/src/nfvo-utils/RestAPIUtil.js
openecomp-ui/src/nfvo-utils/i18n/en.json
openecomp-ui/src/sdc-app/common/modal/ModalContentMapper.js
openecomp-ui/src/sdc-app/config/config.json
openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js
openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductReducer.js
openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPage.js
openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageView.jsx
openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImport.js [new file with mode: 0644]
openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportActionHelper.js [new file with mode: 0644]
openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportConstants.js [new file with mode: 0644]
openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportReducer.js [new file with mode: 0644]
openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportView.jsx [new file with mode: 0644]

index c8b2383..f2913ff 100644 (file)
             <artifactId>openecomp-sdc-notification-core</artifactId>
             <version>${project.version}</version>
         </dependency>
+       <dependency>
+            <groupId>org.openecomp.sdc.onboarding</groupId>
+            <artifactId>vnf-repository-rest-services</artifactId>
+            <version>${project.version}</version>
+        </dependency>
         <dependency>
             <groupId>org.openecomp.sdc.onboarding</groupId>
             <artifactId>vendor-software-products-rest-services</artifactId>
index ef1e724..6b812cc 100644 (file)
@@ -70,6 +70,7 @@
             <ref bean="deploymentFlavors"/>
                <ref bean="images"/>
             <ref bean="orchestrationTemplateCandidate"/>
+           <ref bean="vnfPackageRepository"/>
             <ref bean="componentDependencies"/>
             <ref bean="healthCheck"/>
             <ref bean="itemPermissions"/>
index cef2e71..a88733e 100644 (file)
@@ -17,5 +17,6 @@
     <modules>
         <module>/vendor-software-products-rest-services</module>
         <module>/vendor-software-products-rest-types</module>
+       <module>/vnf-repository-rest-services</module>
     </modules>
 </project>
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vnf-repository-rest-services/pom.xml b/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vnf-repository-rest-services/pom.xml
new file mode 100644 (file)
index 0000000..1829ceb
--- /dev/null
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>vnf-repository-rest-services</artifactId>
+
+    <parent>
+        <groupId>org.openecomp.sdc.onboarding</groupId>
+        <artifactId>vendor-software-products-rest</artifactId>
+        <version>1.2.0-SNAPSHOT</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-core</artifactId>
+            <version>${spring.framework.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+            <version>${spring.framework.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context-support</artifactId>
+            <version>${spring.framework.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+            <version>${spring.framework.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-beans</artifactId>
+            <version>${spring.framework.version}</version>
+        </dependency>
+
+        <!-- CXF -->
+        <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
+            <version>${cxf.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>${http.client.version}</version>
+        </dependency>
+
+
+        <!-- Java Stuff -->
+        <dependency>
+            <groupId>javax.inject</groupId>
+            <artifactId>javax.inject</artifactId>
+            <version>${javax.inject.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.openecomp.sdc</groupId>
+            <artifactId>openecomp-sdc-common-rest</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.openecomp.sdc</groupId>
+            <artifactId>common-app-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.openecomp.sdc.common</groupId>
+            <artifactId>openecomp-configuration-management-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.openecomp.sdc.common</groupId>
+            <artifactId>openecomp-configuration-management-core</artifactId>
+            <version>${project.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.openecomp.sdc.onboarding</groupId>
+            <artifactId>vendor-software-products-rest-services</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vnf-repository-rest-services/src/main/java/org/openecomp/sdcrests/vsp/rest/VnfPackageRepository.java b/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vnf-repository-rest-services/src/main/java/org/openecomp/sdcrests/vsp/rest/VnfPackageRepository.java
new file mode 100644 (file)
index 0000000..1a16e1f
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2017 Huawei Technologies Co., Ltd.
+ *
+ * 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.
+ */
+
+package org.openecomp.sdcrests.vsp.rest;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.openecomp.sdcrests.vendorsoftwareproducts.types.UploadFileResponseDto;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.File;
+import static org.openecomp.sdcrests.common.RestConstants.USER_ID_HEADER_PARAM;
+import static org.openecomp.sdcrests.common.RestConstants.USER_MISSING_ERROR_MSG;
+
+@Path("/v1.0/vendor-software-products/{vspId}/versions/{versionId}/vnfrepository")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+@Api(value = "VNF Repository packages")
+@Validated
+public interface VnfPackageRepository extends VspEntities {
+
+    @GET
+    @Path("/vnfpackages")
+    @Produces(MediaType.APPLICATION_OCTET_STREAM)
+    @ApiOperation(value = "Get VNF packages from VNF Repository", notes = "Call VNF Repostory to get VNF package details", response = File.class)
+    Response getVnfPackages(@PathParam("vspId") String vspId,
+            @ApiParam(value = "Version Id") @PathParam("versionId") String versionId,
+            @NotNull(message = USER_MISSING_ERROR_MSG) @HeaderParam(USER_ID_HEADER_PARAM) String user) throws Exception;
+
+    @GET
+    @Path("/vnfpackage/{csarId}/download")
+    @Produces(MediaType.APPLICATION_OCTET_STREAM)
+    @ApiOperation(value = "Download VNF package from VNF Repository", notes = "Download VNF package from VNF repository and send to client", response = File.class)
+    Response downloadVnfPackage(@PathParam("vspId") String vspId,
+            @ApiParam(value = "Version Id") @PathParam("versionId") String versionId,
+            @PathParam("csarId") String csarId,
+            @NotNull(message = USER_MISSING_ERROR_MSG) @HeaderParam(USER_ID_HEADER_PARAM) String user) throws Exception;
+
+    @POST
+    @Path("/vnfpackage/{csarId}/import")
+    @Produces(MediaType.APPLICATION_JSON)
+    @ApiOperation(value = "Import VNF package from VNF Repository", notes = "Call VNF Repostory to download VNF package, validate it and send the response", response = UploadFileResponseDto.class)
+    Response importVnfPackage(@PathParam("vspId") String vspId,
+            @ApiParam(value = "Version Id") @PathParam("versionId") String versionId,
+            @PathParam("csarId") String csarId,
+            @NotNull(message = USER_MISSING_ERROR_MSG) @HeaderParam(USER_ID_HEADER_PARAM) String user) throws Exception;
+
+}
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vnf-repository-rest-services/src/main/java/org/openecomp/sdcrests/vsp/rest/services/VnfPackageRepositoryImpl.java b/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vnf-repository-rest-services/src/main/java/org/openecomp/sdcrests/vsp/rest/services/VnfPackageRepositoryImpl.java
new file mode 100644 (file)
index 0000000..439fd14
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2017 Huawei Technologies Co., Ltd.
+ *
+ * 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.
+ */
+
+package org.openecomp.sdcrests.vsp.rest.services;
+
+import static javax.ws.rs.core.HttpHeaders.CONTENT_DISPOSITION;
+import static org.openecomp.core.utilities.file.FileUtils.getFileExtension;
+import static org.openecomp.core.utilities.file.FileUtils.getNetworkPackageName;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import javax.inject.Named;
+import javax.ws.rs.core.Response;
+
+import org.apache.http.HttpStatus;
+import org.openecomp.config.api.Configuration;
+import org.openecomp.config.api.ConfigurationManager;
+import org.openecomp.sdc.common.http.client.api.HttpRequest;
+import org.openecomp.sdc.common.http.client.api.HttpResponse;
+import org.openecomp.sdc.logging.api.Logger;
+import org.openecomp.sdc.logging.api.LoggerFactory;
+import org.openecomp.sdc.vendorsoftwareproduct.OrchestrationTemplateCandidateManager;
+import org.openecomp.sdc.vendorsoftwareproduct.OrchestrationTemplateCandidateManagerFactory;
+import org.openecomp.sdc.vendorsoftwareproduct.types.UploadFileResponse;
+import org.openecomp.sdc.versioning.VersioningManager;
+import org.openecomp.sdc.versioning.VersioningManagerFactory;
+import org.openecomp.sdc.versioning.dao.types.Version;
+import org.openecomp.sdcrests.vendorsoftwareproducts.types.UploadFileResponseDto;
+import org.openecomp.sdcrests.vsp.rest.VnfPackageRepository;
+import org.openecomp.sdcrests.vsp.rest.mapping.MapUploadFileResponseToUploadFileResponseDto;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Service;
+
+/**
+ * The class implements the API interface with VNF Repository (VNFSDK) such as
+ * i) Get all the VNF Package Meta-data ii) Download the VNF Package iii) Import
+ * VNF package to SDC catalog (Download & validate)
+ * 
+ * @version Amsterdam release (ONAP 1.0)
+ */
+@Named
+@Service("vnfPackageRepository")
+@Scope(value = "prototype")
+public class VnfPackageRepositoryImpl implements VnfPackageRepository {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(VnfPackageRepositoryImpl.class);
+
+    private static boolean initFlag = false;
+
+    // Default VNF Repository configuration
+    private static final String CONFIG_NAMESPACE = "vnfrepo";
+
+    // Default address for VNF repository docker
+    private static final String DEF_DOCKER_COMPOSE_ADDR = "127.0.0.1";
+
+    private static String ipAddress = DEF_DOCKER_COMPOSE_ADDR;
+
+    // Default Download package URI and Get VNF package meta-data URI -
+    // configurable
+    private static String getVnfPkgUri = "/onapapi/vnfsdk-marketplace/v1/PackageResource/csars";
+
+    private static String downldPkgUri = "/onapapi/vnfsdk-marketplace/v1/PackageResource/csars/%s/files";
+
+    // Default port for VNF Repository
+    private static String port = "8702";
+
+    @Override
+    public Response getVnfPackages(String vspId, String versionId, String user) throws Exception {
+
+        LOGGER.debug("Get VNF Packages from Repository:{}", vspId);
+
+        // Step 1: Create REST client and configuration and prepare URI
+        init();
+
+        // Step 2: Build URI based on the IP address and port allocated
+        HttpResponse<String> rsp = HttpRequest.get(getVnfPkgUri);
+        if(HttpStatus.SC_OK != rsp.getStatusCode()) {
+            LOGGER.error("Failed to query VNF package metadata:uri={}, Response={}", getVnfPkgUri, rsp);
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+        }
+
+        // Step 3: Send the response to the client
+        LOGGER.debug("Response from VNF Repository: {}", rsp.getResponse());
+
+        return Response.ok(rsp.getResponse()).build();
+    }
+
+    @Override
+    public Response importVnfPackage(String vspId, String versionId, String csarId, String user) throws Exception {
+
+        LOGGER.debug("Import VNF Packages from Repository:{}", csarId);
+
+        // Step 1: Create REST client and configuration and prepare URI
+        init();
+
+        // Step 2: Build URI based on the IP address and port allocated
+        String uri = String.format(downldPkgUri, csarId);
+        HttpResponse<String> rsp = HttpRequest.get(uri);
+        if(HttpStatus.SC_OK != rsp.getStatusCode()) {
+            LOGGER.error("Failed to download package from VNF Repository:uri={}, Response={}", uri, rsp);
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+        }
+        LOGGER.debug("Response from VNF Repository for download package is success ");
+
+        // Step 3: Import the file to SDC and validate and send the response
+        try (InputStream fileStream = new BufferedInputStream(
+                new ByteArrayInputStream(rsp.getResponse().getBytes(StandardCharsets.ISO_8859_1)))) {
+
+            String filename = "temp_" + csarId + ".csar";
+            OrchestrationTemplateCandidateManager candidateManager =
+                    OrchestrationTemplateCandidateManagerFactory.getInstance().createInterface();
+            UploadFileResponse uploadFileResponse = candidateManager.upload(vspId, getVersion(vspId, versionId),
+                    fileStream, getFileExtension(filename), getNetworkPackageName(filename));
+
+            UploadFileResponseDto uploadFileResponseDto = new MapUploadFileResponseToUploadFileResponseDto()
+                    .applyMapping(uploadFileResponse, UploadFileResponseDto.class);
+
+            return Response.ok(uploadFileResponseDto).build();
+        } catch(Exception e) {
+            // Exception while uploading file
+
+            LOGGER.error("Exception while uploading VNF package received from VNF Repository:", e);
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+        }
+    }
+
+    @Override
+    public Response downloadVnfPackage(String vspId, String versionId, String csarId, String user) throws Exception {
+
+        LOGGER.debug("Download VNF Packages from Repository:csarId={}", csarId);
+
+        // Step 1: Create REST client and configuration and prepare URI
+        init();
+
+        // Step 2: Build URI based on the IP address and port allocated
+        String uri = String.format(downldPkgUri, csarId);
+        HttpResponse<String> rsp = HttpRequest.get(uri);
+        if(HttpStatus.SC_OK != rsp.getStatusCode()) {
+            LOGGER.error("Failed to download package from VNF Repository:uri={}, Response={}", uri, rsp);
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+        }
+
+        // Step 3:Send response to the client
+        String filename = "temp_" + csarId + ".csar";
+        Response.ResponseBuilder response = Response.ok(rsp.getResponse().getBytes(StandardCharsets.ISO_8859_1));
+        response.header(CONTENT_DISPOSITION, "attachment; filename=" + filename);
+
+        LOGGER.debug("Response from VNF Repository for download package is success ");
+
+        return response.build();
+    }
+
+    private Version getVersion(String vspId, String versionId) {
+        // Get list of Versions from the rest call
+        VersioningManager versioningManager = VersioningManagerFactory.getInstance().createInterface();
+
+        // Find the corresponding version from versionId
+        return versioningManager.list(vspId).stream().filter(ver -> ver.getId() != versionId).findAny()
+                .orElse(new Version(versionId));
+    }
+
+    private static void setVnfRepoConfig() {
+
+        try {
+            // Step 1: Fetch the on-boarding configuration
+            Configuration config = ConfigurationManager.lookup();
+
+            String vnfRepoHost = config.getAsString(CONFIG_NAMESPACE, "vnfRepoHost");
+            if(null != vnfRepoHost) {
+                ipAddress = vnfRepoHost;
+            }
+
+            String vnfRepoPort = config.getAsString(CONFIG_NAMESPACE, "vnfRepoPort");
+            if(null != vnfRepoPort) {
+                port = vnfRepoPort;
+            }
+
+            String getVnfUri = config.getAsString(CONFIG_NAMESPACE, "getVnfUri");
+            if(null != getVnfUri) {
+                getVnfPkgUri = getVnfUri;
+            }
+
+            String downloadVnfUri = config.getAsString(CONFIG_NAMESPACE, "downloadVnfUri");
+            if(null != downloadVnfUri) {
+                downldPkgUri = downloadVnfUri;
+            }
+
+        } catch(Exception e) {
+            LOGGER.error("Failed to load configuration, Exception caught, using default configuration", e);
+        }
+
+        getVnfPkgUri =
+                new StringBuilder("http://").append(ipAddress).append(":").append(port).append(getVnfPkgUri).toString();
+
+        downldPkgUri =
+                new StringBuilder("http://").append(ipAddress).append(":").append(port).append(downldPkgUri).toString();
+    }
+
+    private static synchronized void init() throws Exception {
+        if(!initFlag) {
+            // Step 1: Initialize configuration
+            setVnfRepoConfig();
+
+            initFlag = true;
+        }
+    }
+}
index 62fe126..747c735 100644 (file)
@@ -17,4 +17,18 @@ template "onboard-be-config" do
       :cassandra_truststore_password => node['cassandra'][:truststore_password],
       :cassandra_ssl_enabled => "#{ENV['cassandra_ssl_enabled']}"
    })
+end
+
+
+
+template "VnfrepoConfiguration" do
+   path "#{ENV['JETTY_BASE']}/config/onboarding-be/config-vnfrepo.yaml"
+   source "vnfrepo-configuration.yaml.erb"
+   owner "jetty"
+   group "jetty"
+   mode "0755"
+   variables({
+      :VNFREPO_IP   => node['VnfRepo']['vnfRepoHost'],
+      :VNFREPO_PORT => node['VnfRepo']['vnfRepoPort']
+   })
 end
\ No newline at end of file
diff --git a/openecomp-be/dist/sdc-onboard-backend-docker/artifacts/chef-repo/cookbooks/sdc-onboard-backend/templates/default/vnfrepo-configuration.yaml.erb b/openecomp-be/dist/sdc-onboard-backend-docker/artifacts/chef-repo/cookbooks/sdc-onboard-backend/templates/default/vnfrepo-configuration.yaml.erb
new file mode 100644 (file)
index 0000000..07e2662
--- /dev/null
@@ -0,0 +1,4 @@
+vnfRepoPort: <%= @VNFREPO_PORT %>
+vnfRepoHost: <%= @VNFREPO_IP %>
+getVnfUri: /onapapi/vnfsdk-marketplace/v1/PackageResource/csars
+downloadVnfUri: /onapapi/vnfsdk-marketplace/v1/PackageResource/csars/%s/files
\ No newline at end of file
index 1517f72..413fdb3 100644 (file)
@@ -14,7 +14,8 @@ JAVA_OPTIONS=" ${JAVA_OPTIONS} \
             -Dconfig.home=${JETTY_BASE}/config \
             -Dlog.home=${JETTY_BASE}/logs \
             -Dlogback.configurationFile=${JETTY_BASE}/config/onboarding-be/logback.xml \
-            -Dconfiguration.yaml=${JETTY_BASE}/config/onboarding-be/onboarding_configuration.yaml"
+            -Dconfiguration.yaml=${JETTY_BASE}/config/onboarding-be/onboarding_configuration.yaml \
+            -Dconfig.location=${JETTY_BASE}/config/onboarding-be/."
 
 cd /var/lib/jetty
 
index e18b260..7bd9010 100644 (file)
@@ -22,6 +22,7 @@
 @import "components/userNotifications";
 @import "components/overlay";
 @import "components/vspDetailsVendorSelect";
+@import "components/vnfBrowse";
 
 %noselect {
   -webkit-touch-callout: none;
diff --git a/openecomp-ui/resources/scss/components/_vnfBrowse.scss b/openecomp-ui/resources/scss/components/_vnfBrowse.scss
new file mode 100644 (file)
index 0000000..7e0085a
--- /dev/null
@@ -0,0 +1,109 @@
+$message-info-icon-size: 16px;
+
+.vnf-creation-page {
+       .list-editor-view-header {
+               border-bottom: none;
+       }
+       .vnfBrowse-list-item {
+               display: flex;
+               height: 36px;
+               @extend .body-1;
+               &.header {
+                       @extend .body-1-semibold;
+                       background-color: $tlv-light-gray;
+                       color: $text-black;
+               }
+               &.selectedRow {
+                       background-color: $blue;
+                       color: $white;
+                       .svg-icon-wrapper {
+                               &.__positive {
+                                       fill: $white;
+                                       color: $white;
+                               }
+                       }
+               }
+               .svg-icon-wrapper {
+                       &.__positive {
+                               fill: $dark-gray;
+                               color: $dark-gray;
+                       }
+               }
+       }
+
+       .activity-action {
+               .svg-icon-wrapper {
+                       float: left;
+               }
+       }
+
+       .message-further-info-icon {
+               background-color: $gray;
+       }
+
+       .table-cell {
+       border-right: 1px solid $light-gray;
+       border-bottom: 1px solid $light-gray;
+       &:last-child {
+               border-right: none;
+       }
+       flex-basis: 22%;
+       display: flex;
+       padding: 0 20px;
+       justify-content: center;
+       flex-direction: column;
+
+       &.vnftable-action {
+               flex-basis: 12%;
+               span {
+                       margin: auto;
+               }
+       }
+}
+
+  .vnf-table-header {
+       cursor: pointer;
+       display: flex;
+       align-items: center;
+       .header-sort-arrow {
+         width: 0;
+         height: 0;
+         border-left: 5px solid transparent;
+         border-right: 5px solid transparent;
+         margin-left: 9px;
+         &.up {
+               border-bottom: 5px solid $black;
+         }
+         &.down {
+               border-top: 5px solid $black;
+         }
+
+       }
+  }
+
+  .vnf-table-cell {
+       display: flex;
+       justify-content: space-between;
+       span {
+               overflow: hidden;
+               text-overflow: ellipsis;
+       }
+  } 
+  .vnftable-name {
+       max-width: 22%;
+  }
+
+  .vnf-grid-section {
+       margin: 20px 20px 20px 50px;
+  }
+
+  .vnf-modal {
+       text-align: right;
+       margin-top: 22px;
+  }
+  .vnf-submit {
+       margin-right: 15px;
+  }
+
+}
\ No newline at end of file
index 99027d6..8d124c3 100644 (file)
               color: $light-blue;
             }
           }
+        }
 
         .software-product-landing-view-top-block-col-upl {
           @extend .flex;
+          height: 215px;
+          text-align: center;
+          flex-direction: column;
+          justify-content: center;
+          border: 2px dashed $light-gray;
           margin-bottom: 20px;
+          @extend .body-1;
+          align-items: center;
+          .upload-btn {
+            padding: 15px 55px;
 
           }
+          .drag-text {
+            color: $blue;
+            @extend .body-1-semibold;
+          }
+          .or-text {
+            margin-top: 10px;
+            margin-bottom: 10px;
+            color: $light-gray;
+          }
+          .upload {
+            width: 50%;
+            border : 0px !important;
+          }
+          .vnfRepo {
+            width: 50%;
+            cursor: pointer;
+            .searchRepo-text {
+              color: $blue;
+              @extend .body-1-semibold;
+              width: 72px;
+              line-height: 24px;
+              margin-left: auto;
+              margin-right: auto;
+            }
+            .svg-icon-wrapper {
+              .svg-icon.__search {
+                width: 34px;
+                height: 34px;
+                margin-top: 10px;
+              }
+              &.__positive {
+                fill: $blue;
+                color: $blue;
+              }
+            }
+          }
+          .verticalLine {
+            height: 90%;
+            border-left: 1px solid $light-gray;
+          }
+        }
+        .showVnf {
+          flex-direction: row;
         }
       }
     }
diff --git a/openecomp-ui/src/nfvo-components/vnfMarketPlace/VnfRepositorySearchBox.jsx b/openecomp-ui/src/nfvo-components/vnfMarketPlace/VnfRepositorySearchBox.jsx
new file mode 100644 (file)
index 0000000..ab8a18b
--- /dev/null
@@ -0,0 +1,73 @@
+/*!
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { Component } from 'react';
+import DraggableUploadFileBox from 'nfvo-components/fileupload/DraggableUploadFileBox.jsx';
+import Configuration from 'sdc-app/config/Configuration.js';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js';
+
+function VNFBrowse({ onBrowseVNF, isReadOnlyMode }) {
+    if (!Configuration.get('showBrowseVNF')) {
+        return <div />;
+    } else {
+        return (
+            <div
+                className={`${'vnfRepo'}${isReadOnlyMode ? ' disabled' : ''}`}
+                onClick={onBrowseVNF}>
+                <div className={`${'searchRepo-text'}`}>
+                    {i18n('Search in Repository')}
+                </div>
+                <SVGIcon
+                    name="search"
+                    color="positive"
+                    iconClassName="searchIcon"
+                />
+            </div>
+        );
+    }
+}
+
+class VnfRepositorySearchBox extends Component {
+    render() {
+        let {
+            className,
+            onClick,
+            onBrowseVNF,
+            dataTestId,
+            isReadOnlyMode
+        } = this.props;
+        let showVNF = Configuration.get('showBrowseVNF');
+        return (
+            <div className={`${className}${isReadOnlyMode ? ' disabled' : ''}`}>
+                <DraggableUploadFileBox
+                    dataTestId={dataTestId}
+                    isReadOnlyMode={isReadOnlyMode}
+                    className={'upload'}
+                    onClick={onClick}
+                />
+
+                <div className={`${'verticalLine'}${showVNF ? '' : ' hide'}`} />
+
+                <VNFBrowse
+                    onBrowseVNF={onBrowseVNF}
+                    isReadOnlyMode={isReadOnlyMode}
+                />
+            </div>
+        );
+    }
+}
+export default VnfRepositorySearchBox;
index 1a5817d..6be5db7 100644 (file)
@@ -41,6 +41,9 @@ const CONTENT_MD5_HEADER = 'Content-MD5';
 
 function applySecurity(options, data) {
     let headers = options.headers || (options.headers = {});
+    if (options.isAnonymous) {
+        return;
+    }
 
     let authToken = localStorage.getItem(STORAGE_AUTH_KEY);
     if (authToken) {
index cbc2031..10ddb42 100644 (file)
   "VSPQuestionnaire/general/storageDataReplication/storageReplicationFrequency" : "Storage Replication Frequency",
   "VSPQuestionnaire/general/storageDataReplication/storageReplicationDestination" : "Storage Replication Destination",
 
+  "VNF List Title": "VNF List",
+  "VNF import failed title" : "VNF import failed",
+  "VNF import failed msg" : "VNF Repository Server is not responding or not reachable. Please check server address in configuration file.",
+  "VNF Header Name" : "Name",
+  "VNF Header Version" : "Version",
+  "VNF Header Vendor" : "Vendor",
+  "VNF Header Desc" : "Description",
+  "VNF Header Action" : "Action",
+  
   "GENERIC_ERROR": "An error has occurred. Please contact your System Administrator for further assistance."
 }
index 5b28c5d..745f01d 100644 (file)
@@ -24,6 +24,7 @@ import NICCreation from 'sdc-app/onboarding/softwareProduct/components/network/N
 import SoftwareProductComponentsNICEditor from 'sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICEditor.js';
 import ComponentCreation from 'sdc-app/onboarding/softwareProduct/components/creation/SoftwareProductComponentCreation.js';
 import SoftwareProductDeploymentEditor from 'sdc-app/onboarding/softwareProduct/deployment/editor/SoftwareProductDeploymentEditor.js';
+import VNFImport from 'sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImport.js';
 import PermissionsManager from 'sdc-app/onboarding/permissions/PermissionsManager.js';
 import CommitCommentModal from 'nfvo-components/panel/versionController/components/CommitCommentModal.jsx';
 import Tree from 'nfvo-components/tree/Tree.jsx';
@@ -48,7 +49,8 @@ export const modalContentMapper = {
     VERSION_TREE: 'VERSION_TREE',
     MERGE_EDITOR: 'MERGE_EDITOR',
     REVISIONS_LIST: 'REVISIONS_LIST',
-    VENDOR_SELECTOR: 'VENDOR_SELECTOR'
+    VENDOR_SELECTOR: 'VENDOR_SELECTOR',
+    VNF_IMPORT: 'VNF_IMPORT'
 };
 
 export const modalContentComponents = {
@@ -67,5 +69,6 @@ export const modalContentComponents = {
     VERSION_TREE: Tree,
     MERGE_EDITOR: MergeEditor,
     REVISIONS_LIST: Revisions,
-    VENDOR_SELECTOR: VendorSelector
+    VENDOR_SELECTOR: VendorSelector,
+    VNF_IMPORT: VNFImport
 };
index fbfaf1d..e9e0b55 100644 (file)
@@ -7,5 +7,6 @@
        "defaultRestCatalogPrefix": "/sdc1/feProxy/rest",
        "defaultWebsocketPort" : "8181",
        "defaultDebugWebsocketPort" : "9000",
-       "defaultWebsocketPath" : "notification-api/ws/notificationHandler"
+       "defaultWebsocketPath" : "notification-api/ws/notificationHandler",
+       "showBrowseVNF" : true
 }
index 25bd32e..877c786 100644 (file)
@@ -81,6 +81,12 @@ function uploadFile(vspId, formData, version) {
     );
 }
 
+function uploadVNFFile(csarId, softwareProductId, version) {
+    let verId = typeof version === 'object' ? version.id : version;
+    return RestAPIUtil.post(
+        `${baseUrl()}${softwareProductId}/versions/${verId}/vnfrepository/vnfpackage/${csarId}/import`
+    );
+}
 function putSoftwareProduct({ softwareProduct, version }) {
     return RestAPIUtil.put(
         `${baseUrl()}${softwareProduct.id}/versions/${version.id}`,
@@ -421,6 +427,54 @@ const SoftwareProductActionHelper = {
             });
     },
 
+    uploadVNFFile(
+        dispatch,
+        { csarId, failedNotificationTitle, softwareProductId, version }
+    ) {
+        dispatch({
+            type: HeatSetupActions.FILL_HEAT_SETUP_CACHE,
+            payload: {}
+        });
+
+        Promise.resolve()
+            .then(() => uploadVNFFile(csarId, softwareProductId, version))
+            .then(response => {
+                if (response.status === 'Success') {
+                    dispatch({
+                        type: commonActionTypes.DATA_CHANGED,
+                        deltaData: {
+                            onboardingOrigin: response.onboardingOrigin
+                        },
+                        formName: forms.VENDOR_SOFTWARE_PRODUCT_DETAILS
+                    });
+                    switch (response.onboardingOrigin) {
+                        case onboardingOriginTypes.ZIP:
+                            OnboardingActionHelper.navigateToSoftwareProductAttachmentsSetupTab(
+                                dispatch,
+                                { softwareProductId, version }
+                            );
+                            break;
+                        case onboardingOriginTypes.CSAR:
+                            OnboardingActionHelper.navigateToSoftwareProductAttachmentsValidationTab(
+                                dispatch,
+                                { softwareProductId, version }
+                            );
+                            break;
+                    }
+                } else {
+                    throw new Error(parseUploadErrorMsg(response.errors));
+                }
+            })
+            .catch(error => {
+                dispatch({
+                    type: modalActionTypes.GLOBAL_MODAL_ERROR,
+                    data: {
+                        title: failedNotificationTitle,
+                        msg: error.message
+                    }
+                });
+            });
+    },
     downloadHeatFile(
         dispatch,
         { softwareProductId, heatCandidate, isReadOnlyMode, version }
index f3de517..fd4f02c 100644 (file)
@@ -56,6 +56,8 @@ import {
 import { NIC_QUESTIONNAIRE } from 'sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkConstants.js';
 import { IMAGE_QUESTIONNAIRE } from 'sdc-app/onboarding/softwareProduct/components/images/SoftwareProductComponentsImageConstants.js';
 
+import VNFImportReducer from './vnfMarketPlace/VNFImportReducer.js';
+
 export default combineReducers({
     softwareProductAttachments: combineReducers({
         attachmentsDetails: SoftwareProductAttachmentsReducer,
@@ -150,5 +152,8 @@ export default combineReducers({
         }
         return state;
     },
-    softwareProductQuestionnaire: createJSONSchemaReducer(PRODUCT_QUESTIONNAIRE)
+    softwareProductQuestionnaire: createJSONSchemaReducer(
+        PRODUCT_QUESTIONNAIRE
+    ),
+    VNFMarketPlaceImport: VNFImportReducer
 });
index 34bfcee..f5f3b7e 100644 (file)
@@ -21,6 +21,7 @@ import { actionTypes as modalActionTypes } from 'nfvo-components/modal/GlobalMod
 import { onboardingMethod } from '../SoftwareProductConstants.js';
 import ScreensHelper from 'sdc-app/common/helpers/ScreensHelper.js';
 import { enums, screenTypes } from 'sdc-app/onboarding/OnboardingConstants.js';
+import VNFImportActionHelper from '../vnfMarketPlace/VNFImportActionHelper.js';
 
 export const mapStateToProps = ({
     softwareProduct,
@@ -137,7 +138,12 @@ const mapActionsToProps = (dispatch, { version }) => {
                 props: { softwareProductId, version, componentId }
             }),
         /** for the next version */
-        onAddComponent: () => SoftwareProductActionHelper.addComponent(dispatch)
+        onAddComponent: () =>
+            SoftwareProductActionHelper.addComponent(dispatch),
+
+        onBrowseVNF: currentSoftwareProduct => {
+            VNFImportActionHelper.open(dispatch, currentSoftwareProduct);
+        }
     };
 };
 
index bc8a2be..00f0c2a 100644 (file)
@@ -19,7 +19,9 @@ import classnames from 'classnames';
 import Dropzone from 'react-dropzone';
 
 import i18n from 'nfvo-utils/i18n/i18n.js';
+import Configuration from 'sdc-app/config/Configuration.js';
 import DraggableUploadFileBox from 'nfvo-components/fileupload/DraggableUploadFileBox.jsx';
+import VnfRepositorySearchBox from 'nfvo-components/vnfMarketPlace/VnfRepositorySearchBox.jsx';
 
 import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js';
 import SoftwareProductComponentsList from 'sdc-app/onboarding/softwareProduct/components/SoftwareProductComponents.js';
@@ -122,26 +124,55 @@ class SoftwareProductLandingPageView extends React.Component {
     }
 
     renderProductDetails(isManual, isReadOnlyMode) {
-        return (
-            <div className="details-panel">
-                {!isManual && (
-                    <div>
-                        <div className="software-product-landing-view-heading-title">
-                            {i18n('Software Product Attachments')}
+        let { onBrowseVNF, currentSoftwareProduct } = this.props;
+
+        if (Configuration.get('showBrowseVNF')) {
+            return (
+                <div className="details-panel">
+                    {!isManual && (
+                        <div>
+                            <div className="software-product-landing-view-heading-title">
+                                {i18n('Software Product Attachments')}
+                            </div>
+                            <VnfRepositorySearchBox
+                                dataTestId="upload-btn"
+                                isReadOnlyMode={isReadOnlyMode}
+                                className={classnames(
+                                    'software-product-landing-view-top-block-col-upl showVnf',
+                                    { disabled: isReadOnlyMode }
+                                )}
+                                onClick={() => this.refs.fileInput.open()}
+                                onBrowseVNF={() =>
+                                    onBrowseVNF(currentSoftwareProduct)
+                                }
+                            />
                         </div>
-                        <DraggableUploadFileBox
-                            dataTestId="upload-btn"
-                            isReadOnlyMode={isReadOnlyMode}
-                            className={classnames(
-                                'software-product-landing-view-top-block-col-upl',
-                                { disabled: isReadOnlyMode }
-                            )}
-                            onClick={() => this.refs.fileInput.open()}
-                        />
-                    </div>
-                )}
-            </div>
-        );
+                    )}
+                </div>
+            );
+        } else {
+            return (
+                <div className="details-panel">
+                    {!isManual && (
+                        <div>
+                            <div className="software-product-landing-view-heading-title">
+                                {i18n('Software Product Attachments')}
+                            </div>
+                            <DraggableUploadFileBox
+                                dataTestId="upload-btn"
+                                isReadOnlyMode={isReadOnlyMode}
+                                className={classnames(
+                                    'software-product-landing-view-top-block-col-upl',
+                                    { disabled: isReadOnlyMode }
+                                )}
+                                onClick={() => this.refs.fileInput.open()}
+                                onBrowseVNF={() => onBrowseVNF()}
+                            />
+                        </div>
+                    )}
+                </div>
+            );
+        }
     }
 
     handleImportSubmit(files, isReadOnlyMode, isManual) {
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImport.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImport.js
new file mode 100644 (file)
index 0000000..19efab6
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 Huawei Technologies Co., Ltd.
+ *
+ * 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.
+ */
+
+import { connect } from 'react-redux';
+import VNFImportView from './VNFImportView.jsx';
+import VNFImportActionHelper from './VNFImportActionHelper.js';
+
+export const mapStateToProps = response => {
+    const {
+        softwareProduct: { VNFMarketPlaceImport: { vnfItems } }
+    } = response;
+    return {
+        vnfItems: vnfItems
+    };
+};
+
+export const mapActionsToProps = dispatch => {
+    return {
+        onCancel: () => VNFImportActionHelper.resetData(dispatch),
+        onSubmit: (csarId, selectedVendor) => {
+            VNFImportActionHelper.uploadData(selectedVendor, csarId, dispatch);
+        }
+    };
+};
+
+export default connect(mapStateToProps, mapActionsToProps, null, {
+    withRef: true
+})(VNFImportView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportActionHelper.js
new file mode 100644 (file)
index 0000000..3843330
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2017 Huawei Technologies Co., Ltd.
+ *
+ * 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.
+ */
+
+import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
+import Configuration from 'sdc-app/config/Configuration.js';
+import {
+    actionTypes as modalActionTypes,
+    modalSizes
+} from 'nfvo-components/modal/GlobalModalConstants.js';
+import { modalContentMapper } from 'sdc-app/common/modal/ModalContentMapper.js';
+import SoftwareProductActionHelper from 'sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js';
+import { actionTypes } from './VNFImportConstants.js';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+function baseUrl(selectedVendor) {
+    const restPrefix = Configuration.get('restPrefix');
+    let vspId = selectedVendor.id;
+    let version = selectedVendor.version;
+    return `${restPrefix}/v1.0/vendor-software-products/${vspId}/versions/${
+        version.id
+    }/vnfrepository`;
+}
+
+function getVNFMarketplace(dispatch, currentSoftwareProduct) {
+    return RestAPIUtil.fetch(`${baseUrl(currentSoftwareProduct)}/vnfpackages`, {
+        isAnonymous: false
+    })
+        .then(response => {
+            dispatch({
+                type: actionTypes.OPEN,
+                response
+            });
+            dispatch({
+                type: modalActionTypes.GLOBAL_MODAL_SHOW,
+                data: {
+                    modalComponentName: modalContentMapper.VNF_IMPORT,
+                    title: i18n('Browse VNF'),
+                    modalComponentProps: {
+                        currentSoftwareProduct,
+                        size: modalSizes.LARGE
+                    }
+                }
+            });
+        })
+        .catch(error => {
+            let errMessage = error.responseJSON
+                ? error.responseJSON.message
+                : i18n('VNF import failed msg');
+
+            dispatch({
+                type: modalActionTypes.GLOBAL_MODAL_ERROR,
+                data: {
+                    title: i18n('VNF import failed title'),
+                    msg: errMessage,
+                    cancelButtonText: i18n('Ok')
+                }
+            });
+        });
+}
+
+function downloadCSARFile(csarId, currSoftwareProduct) {
+    let url = `${baseUrl(currSoftwareProduct)}/vnfpackage/${csarId}/download`;
+    return RestAPIUtil.fetch(url, {
+        dataType: 'binary',
+        isAnonymous: false
+    });
+}
+
+function getFileName(xhr, defaultFilename) {
+    let filename = '';
+    let contentDisposition =
+        xhr && xhr.getResponseHeader('Content-Disposition')
+            ? xhr.getResponseHeader('Content-Disposition')
+            : '';
+    let match = contentDisposition.match(/filename=(.*?)(;|$)/);
+    if (match) {
+        filename = match[1].replace(/['"]/g, '');
+    } else {
+        filename = defaultFilename;
+    }
+    return filename;
+}
+
+function uploadVNFData(csarId, currSoftwareProduct, dispatch) {
+    let softwareProductId = currSoftwareProduct.id;
+    let version = { id: currSoftwareProduct.version };
+
+    SoftwareProductActionHelper.uploadVNFFile(dispatch, {
+        csarId,
+        currSoftwareProduct,
+        failedNotificationTitle: i18n('Upload validation failed'),
+        softwareProductId,
+        version
+    });
+}
+
+function getTimestampString() {
+    let date = new Date();
+    let z = n => (n < 10 ? '0' + n : n);
+    return `${date.getFullYear()}-${z(date.getMonth())}-${z(
+        date.getDate()
+    )}_${z(date.getHours())}-${z(date.getMinutes())}`;
+}
+
+function showFileSaveDialog({ blob, xhr, defaultFilename, addTimestamp }) {
+    let filename = getFileName(xhr, defaultFilename);
+
+    if (addTimestamp) {
+        filename = filename.replace(
+            /(^.*?)\.([^.]+$)/,
+            `$1_${getTimestampString()}.$2`
+        );
+    }
+
+    let link = document.createElement('a');
+
+    let url = URL.createObjectURL(blob.blob);
+
+    link.href = url;
+    link.download = filename;
+    link.style.display = 'none';
+    document.body.appendChild(link);
+    link.click();
+    setTimeout(function() {
+        document.body.removeChild(link);
+        URL.revokeObjectURL(url);
+    }, 0);
+}
+
+const VNFImportActionHelper = {
+    open(dispatch, currentSoftwareProduct) {
+        getVNFMarketplace(dispatch, currentSoftwareProduct);
+    },
+
+    download(csarId, currSoftwareProduct) {
+        downloadCSARFile(csarId, currSoftwareProduct).then(
+            (blob, statusText, xhr) =>
+                showFileSaveDialog({
+                    blob,
+                    xhr,
+                    defaultFilename: 'MyNewCSAR.csar',
+                    addTimestamp: true
+                })
+        );
+    },
+
+    resetData(dispatch) {
+        dispatch({
+            type: modalActionTypes.GLOBAL_MODAL_CLOSE
+        });
+
+        dispatch({
+            type: actionTypes.RESET_DATA
+        });
+    },
+
+    getVNFMarketplace(dispatch) {
+        return getVNFMarketplace(dispatch);
+    },
+
+    uploadData(currSoftwareProduct, csarId, dispatch) {
+        this.resetData(dispatch);
+        uploadVNFData(csarId, currSoftwareProduct, dispatch);
+    }
+};
+
+export default VNFImportActionHelper;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportConstants.js
new file mode 100644 (file)
index 0000000..e4540dd
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2017 Huawei Technologies Co., Ltd.
+ *
+ * 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.
+ */
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export const actionTypes = keyMirror({
+    OPEN: null,
+    RESET_DATA: null
+});
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportReducer.js
new file mode 100644 (file)
index 0000000..0f2638f
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2017 Huawei Technologies Co., Ltd.
+ *
+ * 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.
+ */
+import { actionTypes } from './VNFImportConstants.js';
+
+export default (state = {}, action) => {
+    switch (action.type) {
+        case actionTypes.OPEN:
+            return {
+                ...state,
+                showModal: true,
+                vnfItems: action.response
+            };
+        case actionTypes.RESET_DATA:
+            return {};
+        default:
+            return state;
+    }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportView.jsx
new file mode 100644 (file)
index 0000000..3a90c80
--- /dev/null
@@ -0,0 +1,309 @@
+/*
+ * Copyright 2017 Huawei Technologies Co., Ltd.
+ *
+ * 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.
+ */
+
+import React from 'react';
+import GridSection from 'nfvo-components/grid/GridSection.jsx';
+import GridItem from 'nfvo-components/grid/GridItem.jsx';
+import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js';
+import Button from 'sdc-ui/lib/react/Button.js';
+import VNFImportActionHelper from '../vnfMarketPlace/VNFImportActionHelper.js';
+
+function VNFAction({
+    action,
+    isHeader,
+    downloadCSAR,
+    id,
+    currSoftwareProduct
+}) {
+    if (isHeader) {
+        return <span>{action}</span>;
+    }
+    return (
+        <span>
+            <SVGIcon
+                name="download"
+                color="positive"
+                onClick={() => {
+                    downloadCSAR(id, currSoftwareProduct);
+                }}
+            />
+        </span>
+    );
+}
+
+function VNFSortableCellHeader({
+    isHeader,
+    data,
+    isDes,
+    onSort,
+    activeSortColumn
+}) {
+    //TODO check icon sdc-ui
+    if (isHeader) {
+        if (activeSortColumn === data) {
+            return (
+                <span
+                    className="vnf-table-header"
+                    onClick={() => {
+                        onSort(activeSortColumn);
+                    }}>
+                    <span>{data}</span>
+                    <span
+                        className={`header-sort-arrow ${isDes ? 'up' : 'down'}`}
+                    />
+                </span>
+            );
+        } else {
+            return (
+                <span
+                    className="vnf-table-header"
+                    onClick={() => {
+                        activeSortColumn = data;
+                        onSort(activeSortColumn);
+                    }}>
+                    <span>{data}</span>
+                </span>
+            );
+        }
+    }
+    return (
+        <span className="vnf-table-cell">
+            <span>{data}</span>
+        </span>
+    );
+}
+
+export function VNFItemList({
+    vnf,
+    isHeader,
+    isDes,
+    onSort,
+    activeSortColumn,
+    downloadCSAR,
+    selectTableRow,
+    selectedRow,
+    currentSoftwareProduct
+}) {
+    let { csarId, name, version, provider, shortDesc, action } = vnf;
+    return (
+        <li
+            className={`vnfBrowse-list-item ${isHeader ? 'header' : ''} ${
+                csarId === selectedRow ? 'selectedRow' : ''
+            }`}
+            data-test-id="vnfBrowse-list-item"
+            onClick={() => {
+                selectTableRow(csarId);
+            }}>
+            <div
+                className="table-cell vnftable-name"
+                data-test-id="vnftable-name">
+                <VNFSortableCellHeader
+                    isHeader={isHeader}
+                    data={name}
+                    isDes={isDes}
+                    onSort={activeSort => {
+                        onSort('name', activeSort);
+                    }}
+                    activeSortColumn={activeSortColumn}
+                />
+            </div>
+            <div
+                className="table-cell vnftable-version"
+                data-test-id="vnftable-version">
+                <VNFSortableCellHeader
+                    isHeader={isHeader}
+                    data={version}
+                    isDes={isDes}
+                    onSort={activeSort => {
+                        onSort('version', activeSort);
+                    }}
+                    activeSortColumn={activeSortColumn}
+                />
+            </div>
+            <div
+                className="table-cell vnftable-provider"
+                data-test-id="vnftable-provider">
+                <VNFSortableCellHeader
+                    isHeader={isHeader}
+                    data={provider}
+                    isDes={isDes}
+                    onSort={activeSort => {
+                        onSort('provider', activeSort);
+                    }}
+                    activeSortColumn={activeSortColumn}
+                />
+            </div>
+            <div
+                className="table-cell vnftable-shortDesc"
+                data-test-id="vnftable-shortDesc">
+                <VNFSortableCellHeader
+                    isHeader={isHeader}
+                    data={shortDesc}
+                    isDes={isDes}
+                    onSort={activeSort => {
+                        onSort('shortDesc', activeSort);
+                    }}
+                    activeSortColumn={activeSortColumn}
+                />
+            </div>
+            <div
+                className="table-cell vnftable-action"
+                data-test-id="vnftable-action">
+                <VNFAction
+                    isHeader={isHeader}
+                    action={action}
+                    downloadCSAR={downloadCSAR}
+                    id={csarId}
+                    currSoftwareProduct={currentSoftwareProduct}
+                />
+            </div>
+        </li>
+    );
+}
+
+class VNFImportView extends React.Component {
+    state = {
+        localFilter: '',
+        sortDescending: true,
+        sortCrit: 'name',
+        activeSortColumn: 'Name',
+        selectedRow: ''
+    };
+
+    render() {
+        let { onCancel, onSubmit, currentSoftwareProduct } = this.props;
+
+        return (
+            <div className="vnf-creation-page">
+                <GridSection className="vnf-grid-section">
+                    <GridItem colSpan="4">
+                        <ListEditorView
+                            title={i18n('VNF List Title')}
+                            filterValue={this.state.localFilter}
+                            onFilter={filter =>
+                                this.setState({ localFilter: filter })
+                            }>
+                            <VNFItemList
+                                isHeader={true}
+                                vnf={{
+                                    csarId: 0,
+                                    name: i18n('VNF Header Name'),
+                                    version: i18n('VNF Header Version'),
+                                    provider: i18n('VNF Header Vendor'),
+                                    shortDesc: i18n('VNF Header Desc'),
+                                    action: i18n('VNF Header Action')
+                                }}
+                                isDes={this.state.sortDescending}
+                                onSort={(sortCriteria, activeSortCol) =>
+                                    this.setState({
+                                        sortDescending: !this.state
+                                            .sortDescending,
+                                        sortCrit: sortCriteria,
+                                        activeSortColumn: activeSortCol
+                                    })
+                                }
+                                activeSortColumn={this.state.activeSortColumn}
+                            />
+                            {this.sortVNFItems(
+                                this.filterVNFItems(),
+                                this.state.sortDescending,
+                                this.state.sortCrit
+                            ).map(vnf => (
+                                <VNFItemList
+                                    key={vnf.id}
+                                    vnf={vnf}
+                                    downloadCSAR={this.downloadCSAR}
+                                    selectTableRow={selID => {
+                                        this.setState({ selectedRow: selID });
+                                        this.selectTableRow(selID);
+                                    }}
+                                    selectedRow={this.state.selectedRow}
+                                    currentSoftwareProduct={
+                                        currentSoftwareProduct
+                                    }
+                                />
+                            ))}
+                        </ListEditorView>
+                    </GridItem>
+                    <GridItem colSpan="4">
+                        <div className="vnf-modal">
+                            <Button
+                                className="vnf-submit"
+                                type="button"
+                                btnType="default"
+                                onClick={() =>
+                                    onSubmit(
+                                        this.state.selectedRow,
+                                        currentSoftwareProduct
+                                    )
+                                }>
+                                {i18n('OK')}
+                            </Button>
+                            <Button
+                                className="Cancel"
+                                type="button"
+                                btnType="outline"
+                                onClick={onCancel}>
+                                {i18n('Cancel')}
+                            </Button>
+                        </div>
+                    </GridItem>
+                </GridSection>
+            </div>
+        );
+    }
+
+    filterVNFItems() {
+        let { vnfItems } = this.props;
+        let { localFilter } = this.state;
+        if (localFilter.trim()) {
+            const filter = new RegExp(escape(localFilter), 'i');
+            return vnfItems.filter(
+                ({ name = '', provider = '', version = '', shortDesc = '' }) =>
+                    escape(name).match(filter) ||
+                    escape(provider).match(filter) ||
+                    escape(version).match(filter) ||
+                    escape(shortDesc).match(filter)
+            );
+        } else {
+            return vnfItems;
+        }
+    }
+
+    sortVNFItems(vnfItems, sortDesc, sortCrit) {
+        if (sortDesc) {
+            return vnfItems.sort((a, b) => {
+                if (a[sortCrit] < b[sortCrit]) {
+                    return -1;
+                } else if (a[sortCrit] > b[sortCrit]) {
+                    return 1;
+                } else {
+                    return 0;
+                }
+            });
+        } else {
+            return vnfItems.reverse();
+        }
+    }
+
+    downloadCSAR(id, currSoftwareProduct) {
+        VNFImportActionHelper.download(id, currSoftwareProduct);
+    }
+}
+
+export default VNFImportView;