Obtain and control VSP package upload status 34/126834/3
authorandre.schmid <andre.schmid@est.tech>
Tue, 25 Jan 2022 19:38:32 +0000 (19:38 +0000)
committerMichael Morris <michael.morris@est.tech>
Mon, 31 Jan 2022 15:23:21 +0000 (15:23 +0000)
Obtain the upload status and control the upload from the frontend
perspective.

Change-Id: Idcc921cf592efea33df35c557afcfae827af3a39
Issue-ID: SDC-3862
Signed-off-by: andre.schmid <andre.schmid@est.tech>
17 files changed:
openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vendor-software-products-rest-services/src/main/java/org/openecomp/sdcrests/vsp/rest/services/OrchestrationTemplateCandidateImpl.java
openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vendor-software-products-rest-services/src/test/java/org/openecomp/sdcrests/vsp/rest/services/OrchestrationTemplateCandidateImplTest.java
openecomp-be/lib/openecomp-sdc-vendor-software-product-lib/openecomp-sdc-vendor-software-product-core/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/dao/impl/VspUploadStatusRecordDaoIml.java
openecomp-be/lib/openecomp-sdc-vendor-software-product-lib/openecomp-sdc-vendor-software-product-core/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/dao/impl/VspUploadStatusRecordDaoImlTest.java
openecomp-ui/README.md
openecomp-ui/package.json
openecomp-ui/resources/scss/modules/_softwareProductLandingPage.scss
openecomp-ui/src/nfvo-components/loader/LoaderReducer.js
openecomp-ui/src/nfvo-components/vnfMarketPlace/VnfRepositorySearchBox.jsx
openecomp-ui/src/nfvo-utils/ErrorResponseHandler.js
openecomp-ui/src/nfvo-utils/RestAPIUtil.js
openecomp-ui/src/nfvo-utils/i18n/en.json
openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.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/landingPage/VspUploadStatus.js [new file with mode: 0644]
openecomp-ui/test/softwareProduct/landingPage/landingPage.test.js

index 6615447..b515839 100644 (file)
@@ -152,18 +152,21 @@ public class OrchestrationTemplateCandidateImpl implements OrchestrationTemplate
                 fileToUploadBytes = fileToUpload.getObject(byte[].class);
             }
 
-
             vspUploadStatus = orchestrationTemplateCandidateUploadManager.putUploadInValidation(vspId, versionId, user);
             final var onboardingPackageProcessor =
                 new OnboardingPackageProcessor(filename, fileToUploadBytes, new CnfPackageValidator(), artifactInfo);
             final ErrorMessage[] errorMessages = onboardingPackageProcessor.getErrorMessages().toArray(new ErrorMessage[0]);
             if (onboardingPackageProcessor.hasErrors()) {
+                orchestrationTemplateCandidateUploadManager
+                    .putUploadAsFinished(vspId, versionId, vspUploadStatus.getLockId(), VspUploadStatus.ERROR, user);
                 return Response.status(NOT_ACCEPTABLE).entity(buildUploadResponseWithError(errorMessages)).build();
             }
             final var onboardPackageInfo = onboardingPackageProcessor.getOnboardPackageInfo().orElse(null);
             if (onboardPackageInfo == null) {
                 final UploadFileResponseDto uploadFileResponseDto = buildUploadResponseWithError(
                     new ErrorMessage(ErrorLevel.ERROR, PACKAGE_PROCESS_ERROR.formatMessage(filename)));
+                orchestrationTemplateCandidateUploadManager
+                    .putUploadAsFinished(vspId, versionId, vspUploadStatus.getLockId(), VspUploadStatus.ERROR, user);
                 return Response.ok(uploadFileResponseDto).build();
             }
             final var version = new Version(versionId);
index 758505f..6c21cc4 100644 (file)
@@ -223,8 +223,12 @@ class OrchestrationTemplateCandidateImplTest {
 
     @Test
     void uploadSignNotValidTest() throws IOException {
+        //given
+        when(orchestrationTemplateCandidateUploadManager.putUploadInValidation(candidateId, versionId, user)).thenReturn(new VspUploadStatusDto());
+        //when
         Response response = orchestrationTemplateCandidate
-            .upload("1", "1", mockAttachment("filename.zip", null), user);
+            .upload(candidateId, versionId, mockAttachment("filename.zip", null), user);
+        //then
         assertEquals(Status.NOT_ACCEPTABLE.getStatusCode(), response.getStatus());
         assertFalse(((UploadFileResponseDto) response.getEntity()).getErrors().isEmpty());
     }
index 558016c..7b0da51 100644 (file)
@@ -91,7 +91,7 @@ public class VspUploadStatusRecordDaoIml implements VspUploadStatusRecordDao {
     public Optional<VspUploadStatusRecord> findLatest(final String vspId, final String vspVersionId) {
         final List<VspUploadStatusRecord> vspUploadStatusRecordList = accessor.findAllByVspIdAndVspVersionId(vspId, vspVersionId).all();
         vspUploadStatusRecordList.sort(Comparator.comparing(VspUploadStatusRecord::getCreated).reversed());
-        return Optional.ofNullable(vspUploadStatusRecordList.get(0));
+        return vspUploadStatusRecordList.isEmpty() ? Optional.empty() : Optional.ofNullable(vspUploadStatusRecordList.get(0));
     }
 
 }
index cd0bc1c..f17d250 100644 (file)
@@ -130,4 +130,19 @@ class VspUploadStatusRecordDaoImlTest {
         assertEquals(mostRecentVspUploadStatus, vspUploadStatusOptional.get());
     }
 
+    @Test
+    void findLatest_noEntryFoundTest() {
+        //given
+        final String vspId = "vspId";
+        final String vspVersionId = "vspVersionId";
+        final Result<VspUploadStatusRecord> resultMock = mock(Result.class);
+        when(resultMock.all()).thenReturn(new ArrayList<>());
+
+        when(accessor.findAllByVspIdAndVspVersionId(vspId, vspVersionId)).thenReturn(resultMock);
+        //when
+        final Optional<VspUploadStatusRecord> vspUploadStatusOptional = packageUploadManagerDaoIml.findLatest(vspId, vspVersionId);
+        //then
+        assertTrue(vspUploadStatusOptional.isEmpty());
+    }
+
 }
\ No newline at end of file
index c270802..a41aa27 100644 (file)
@@ -35,8 +35,8 @@ install gulp by running the following command `npm install --global gulp-cli`
 * your favorite UI will wait for you at: `http://localhost:9000/sdc1/#!/onboardVendor`
 
 ## Troubleshooting
-Problem | Why is this happening | Solution
-------- | --------------------- | --------
-Build (npm install) error | npm/node_modules cache | If having problems with the compilation of  dox-sequence-diagram-ui and openecomp-ui, delete the node_modules and package-lock.json in each respective projects folder.
-npm cannot reach destination | proxy | When within managed network, you should set your proxy to NPM as the following: <br> `npm config set proxy http://<host>:<port>` <br> `npm config set https-proxy http://<host>:<port>`
-git protocol is blocked and cannot connect | managed network rules for protocols       | When within managed network, you should set globally that when git protocol is used, then it will be replaced with "https" <br> `git config --global url."https://".insteadOf git://`
+| Problem                                    | Why is this happening                | Solution                                                                                                                                                                                |
+|--------------------------------------------|--------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| Build (npm install) error                  | npm/node_modules cache               | If having problems with the compilation of  dox-sequence-diagram-ui and openecomp-ui, delete the node_modules and package-lock.json in each respective projects folder.                 |
+| npm cannot reach destination               | proxy                                | When within managed network, you should set your proxy to NPM as the following: <br> `npm config set proxy http://<host>:<port>` <br> `npm config set https-proxy http://<host>:<port>` |
+| git protocol is blocked and cannot connect | managed network rules for protocols      | When within managed network, you should set globally that when git protocol is used, then it will be replaced with "https" <br> `git config --global url."https://".insteadOf git://`   |
index e19b0b1..96e7c43 100644 (file)
@@ -18,7 +18,7 @@
     },
     "dependencies": {
         "attr-accept": "^1.1.0",
-        "axios": "^0.16.2",
+        "axios": "^0.25.0",
         "classnames": "^2.2.5",
         "core-js": "^2.4.0",
         "d3": "^4.10.0",
index c1cf3cf..a627627 100644 (file)
                         color: $blue;
                         @extend .body-1-semibold;
                     }
+                    .upload-status-text {
+                        color: $blue;
+                        @extend .body-1-semibold;
+                    }
                     .or-text {
                         margin-top: 10px;
                         margin-bottom: 10px;
index 3f9eb17..4222c3d 100644 (file)
@@ -32,7 +32,9 @@ export default (
                 isLoading: true
             };
         case actionTypes.RECEIVE_RESPONSE:
-            fetchingRequests--;
+            if (fetchingRequests > 0) {
+                fetchingRequests--;
+            }
 
             newArray = state.currentlyFetching.filter(item => {
                 return item !== action.url;
index 2c82c2b..32e3a3e 100644 (file)
@@ -50,22 +50,27 @@ class VnfRepositorySearchBox extends Component {
             dataTestId,
             isReadOnlyMode
         } = this.props;
-        let showVNF = Configuration.get('showBrowseVNF');
+        const showVNF = Configuration.get('showBrowseVNF');
         return (
-            <div className={`${className}${isReadOnlyMode ? ' disabled' : ''}`}>
+            <div
+                className={`${className}${isReadOnlyMode ? ' disabled' : ''}${
+                    showVNF ? ' showVnf' : ''
+                }`}>
                 <DraggableUploadFileBox
                     dataTestId={dataTestId}
                     isReadOnlyMode={isReadOnlyMode}
                     className={'upload'}
                     onClick={onClick}
                 />
-
-                <div className={`${'verticalLine'}${showVNF ? '' : ' hide'}`} />
-
-                <VNFBrowse
-                    onBrowseVNF={onBrowseVNF}
-                    isReadOnlyMode={isReadOnlyMode}
-                />
+                {showVNF && (
+                    <div className={`verticalLine${showVNF ? '' : ' hide'}`} />
+                )}
+                {showVNF && (
+                    <VNFBrowse
+                        onBrowseVNF={onBrowseVNF}
+                        isReadOnlyMode={isReadOnlyMode}
+                    />
+                )}
             </div>
         );
     }
index 5c55855..e7d3f8a 100644 (file)
@@ -64,7 +64,7 @@ function parseCatalogExceptionObject(responseJSON) {
     return { title, msg };
 }
 
-var errorResponseHandler = error => {
+const errorResponseHandler = error => {
     let errorData;
     if (error.data) {
         errorData = parseCatalogExceptionObject(error.data);
index 03908d8..97d3847 100644 (file)
@@ -77,55 +77,69 @@ class RestAPIUtil {
     handleRequest(url, type, options = {}, data = {}) {
         applySecurity(options, data);
 
-        let config = {
+        const config = {
             method: type,
             url: url,
             headers: options.headers,
             data: data
         };
 
-        store.dispatch({ type: LoaderConstants.SEND_REQUEST, url: url });
+        if (options.validateStatus) {
+            config.validateStatus = options.validateStatus;
+        }
+
+        if (options.onUploadProgress) {
+            config.onUploadProgress = options.onUploadProgress;
+        }
+
+        if (!options.noLoading) {
+            store.dispatch({ type: LoaderConstants.SEND_REQUEST, url: url });
+        }
         if (options.dataType === BINARY) {
             config.responseType = 'arraybuffer';
             return axios(config)
                 .then(result => {
-                    store.dispatch({
-                        type: LoaderConstants.RECEIVE_RESPONSE,
-                        url: result.config.url
-                    });
+                    if (!options.noLoading) {
+                        store.dispatch({
+                            type: LoaderConstants.RECEIVE_RESPONSE,
+                            url: result.config.url
+                        });
+                    }
+
                     return {
                         blob: new Blob([result.data]),
                         headers: result.headers
                     };
                 })
                 .catch(error => {
-                    store.dispatch({
-                        type: LoaderConstants.RECEIVE_RESPONSE,
-                        url: error.config.url
-                    });
-                    errorResponseHandler(error.response);
-                });
-        } else {
-            return axios(config)
-                .then(result => {
-                    store.dispatch({
-                        type: LoaderConstants.RECEIVE_RESPONSE,
-                        url: result.config.url
-                    });
-                    handleSuccess(result.headers, result.config.headers);
-                    return result.data;
-                })
-                .catch(error => {
-                    store.dispatch({
-                        type: LoaderConstants.RECEIVE_RESPONSE,
-                        url: error.config.url
-                    });
+                    if (!options.noLoading) {
+                        store.dispatch({
+                            type: LoaderConstants.RECEIVE_RESPONSE,
+                            url: error.config.url
+                        });
+                    }
                     errorResponseHandler(error.response);
-                    return Promise.reject({
-                        responseJSON: error.response.data
-                    });
                 });
         }
+        return axios(config)
+            .then(result => {
+                store.dispatch({
+                    type: LoaderConstants.RECEIVE_RESPONSE,
+                    url: result.config.url
+                });
+                handleSuccess(result.headers, result.config.headers);
+                return result.data;
+            })
+            .catch(error => {
+                store.dispatch({
+                    type: LoaderConstants.RECEIVE_RESPONSE,
+                    url: error.config.url
+                });
+                errorResponseHandler(error.response);
+                return Promise.reject({
+                    responseJSON: error.response.data
+                });
+            });
     }
 
     fetch(url, options) {
index 7a2e66a..ada011f 100644 (file)
   "Unknown": "Unknown",
   "No Test Result Available": "No Test Result Available",
   "Test is In-progress": "Test is In-progress",
-  "has passed all checks": "has passed all checks"
-
+  "has passed all checks": "has passed all checks",
+  "upload.status.uploading": "Package upload is in progress",
+  "upload.status.validating": "Package is in validation",
+  "upload.status.processing": "Package is being processed",
+  "upload.status.success": "Package was successfully uploaded",
+  "upload.status.error": "Package upload resulted in error"
 }
index 5c82c70..3364edf 100644 (file)
@@ -112,12 +112,19 @@ function getModelUrl() {
     return `${restCatalogPrefix}/v1/catalog/model?modelType=normative`;
 }
 
-function uploadFile(vspId, formData, version) {
+function uploadFile(vspId, formData, version, onUploadProgress = undefined) {
+    const options = {
+        noLoading: true
+    };
+    if (onUploadProgress) {
+        options.onUploadProgress = onUploadProgress;
+    }
     return RestAPIUtil.post(
         `${baseUrl()}${vspId}/versions/${
             version.id
         }/orchestration-template-candidate`,
-        formData
+        formData,
+        options
     );
 }
 
@@ -346,6 +353,19 @@ const SoftwareProductActionHelper = {
         );
     },
 
+    fetchUploadStatus(vspId, versionId) {
+        const options = {
+            validateStatus: function(status) {
+                return status < 400 || status === 404;
+            },
+            noLoading: true
+        };
+        return RestAPIUtil.get(
+            `${baseUrl()}${vspId}/versions/${versionId}/orchestration-template-candidate/upload`,
+            options
+        );
+    },
+
     loadSoftwareProductAssociatedData(dispatch) {
         fetchSoftwareProductCategories(dispatch);
         fetchModelList(dispatch);
@@ -435,15 +455,27 @@ const SoftwareProductActionHelper = {
 
     uploadFile(
         dispatch,
-        { softwareProductId, formData, failedNotificationTitle, version }
+        {
+            softwareProductId,
+            formData,
+            failedNotificationTitle,
+            version,
+            onUploadProgress = undefined
+        }
     ) {
         dispatch({
             type: HeatSetupActions.FILL_HEAT_SETUP_CACHE,
             payload: {}
         });
-        displayTimingValidationInfo(dispatch);
-        Promise.resolve()
-            .then(() => uploadFile(softwareProductId, formData, version))
+        return Promise.resolve()
+            .then(() =>
+                uploadFile(
+                    softwareProductId,
+                    formData,
+                    version,
+                    onUploadProgress
+                )
+            )
             .then(response => {
                 if (response.status === 'Success') {
                     dispatch({
index fa136f2..4b4c2fa 100644 (file)
@@ -109,15 +109,40 @@ const mapActionsToProps = (dispatch, { version }) => {
                 screenType: screenTypes.SOFTWARE_PRODUCT,
                 props: { softwareProductId, version }
             }),
-        onUpload: (softwareProductId, formData) =>
+        onUpload: (
+            softwareProductId,
+            formData,
+            onUploadStart = () => {
+                // do nothing by default
+            },
+            onUploadProgress = undefined,
+            onUploadFinished = () => {
+                // do nothing by default
+            }
+        ) => {
             SoftwareProductActionHelper.uploadFile(dispatch, {
                 softwareProductId,
                 formData,
                 failedNotificationTitle: i18n('Upload validation failed'),
-                version
-            }),
+                version,
+                onUploadProgress
+            }).finally(() => {
+                onUploadFinished();
+            });
+            onUploadStart();
+        },
 
-        onUploadConfirmation: (softwareProductId, formData) =>
+        onUploadConfirmation: (
+            softwareProductId,
+            formData,
+            onUploadStart = () => {
+                // do nothing by default
+            },
+            onUploadProgress = undefined,
+            onUploadFinished = () => {
+                // do nothing by default
+            }
+        ) =>
             dispatch({
                 type: modalActionTypes.GLOBAL_MODAL_WARNING,
                 data: {
@@ -126,15 +151,21 @@ const mapActionsToProps = (dispatch, { version }) => {
                     ),
                     confirmationButtonText: i18n('Continue'),
                     title: i18n('Warning'),
-                    onConfirmed: () =>
+                    onConfirmed: () => {
                         SoftwareProductActionHelper.uploadFile(dispatch, {
                             softwareProductId,
                             formData,
                             failedNotificationTitle: i18n(
                                 'Upload validation failed'
                             ),
-                            version
-                        }),
+                            version,
+                            onUploadProgress
+                        }).finally(value => {
+                            console.log('upload finished', value);
+                            onUploadFinished();
+                        });
+                        onUploadStart();
+                    },
                     onDeclined: () =>
                         dispatch({
                             type: modalActionTypes.GLOBAL_MODAL_CLOSE
@@ -153,6 +184,14 @@ const mapActionsToProps = (dispatch, { version }) => {
                     )
                 }
             }),
+
+        fetchUploadStatus: softwareProductId => {
+            return SoftwareProductActionHelper.fetchUploadStatus(
+                softwareProductId,
+                version.id
+            );
+        },
+
         onComponentSelect: ({ id: softwareProductId, componentId }) =>
             ScreensHelper.loadScreen(dispatch, {
                 screen: screenTypes.SOFTWARE_PRODUCT_COMPONENT_DEFAULT_GENERAL,
index 5f20834..c560a73 100644 (file)
@@ -20,12 +20,12 @@ 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 'onap-ui-react';
 import SoftwareProductComponentsList from 'sdc-app/onboarding/softwareProduct/components/SoftwareProductComponents.js';
+import VspUploadStatus from 'sdc-app/onboarding/softwareProduct/landingPage/VspUploadStatus';
+import ProgressBar from 'react-bootstrap/lib/ProgressBar';
 
 const SoftwareProductPropType = PropTypes.shape({
     name: PropTypes.string,
@@ -52,7 +52,10 @@ class SoftwareProductLandingPageView extends React.Component {
     state = {
         fileName: '',
         dragging: false,
-        files: []
+        uploadStatus: {},
+        files: [],
+        uploadProgress: 0,
+        showProgressBar: false
     };
 
     constructor(props) {
@@ -69,11 +72,13 @@ class SoftwareProductLandingPageView extends React.Component {
         version: PropTypes.object,
         onLicenseChange: PropTypes.func,
         onUpload: PropTypes.func,
+        fetchUploadStatus: PropTypes.func,
         onUploadConfirmation: PropTypes.func,
         onInvalidFileSizeUpload: PropTypes.func,
         onComponentSelect: PropTypes.func,
         onAddComponent: PropTypes.func
     };
+
     componentDidMount() {
         const {
             onCandidateInProcess,
@@ -83,6 +88,41 @@ class SoftwareProductLandingPageView extends React.Component {
         if (currentSoftwareProduct.candidateOnboardingOrigin && !isCertified) {
             onCandidateInProcess(currentSoftwareProduct.id);
         }
+        this.keepCheckingUploadStatus();
+    }
+
+    componentWillUnmount() {
+        this.stopUploadStatusChecking();
+    }
+
+    keepCheckingUploadStatus(initialDelayInMs = 0, updatePeriodInMs = 10000) {
+        this.stopUploadStatusChecking();
+        setTimeout(() => this.updateUploadStatus(), initialDelayInMs);
+        this.uploadStatusInterval = setInterval(
+            () => this.updateUploadStatus(),
+            updatePeriodInMs
+        );
+    }
+
+    stopUploadStatusChecking() {
+        clearInterval(this.uploadStatusInterval);
+    }
+
+    updateUploadStatus() {
+        const currentVspId = this.props.currentSoftwareProduct.id;
+        this.props
+            .fetchUploadStatus(currentVspId)
+            .then(uploadStatusResponse => {
+                const vspUploadStatus = new VspUploadStatus(
+                    uploadStatusResponse
+                );
+                this.setState({
+                    uploadStatus: vspUploadStatus
+                });
+            })
+            .catch(error =>
+                console.error('Could not retrieve upload status', error)
+            );
     }
 
     licenceChange = (e, currentSoftwareProduct, onLicenseChange) => {
@@ -93,7 +133,7 @@ class SoftwareProductLandingPageView extends React.Component {
     };
 
     getExternalLicenceFeatureState() {
-        var licenseFeature = this.props.features.find(
+        const licenseFeature = this.props.features.find(
             feature => feature.name === 'EXTERNAL_LICENSE'
         );
         return licenseFeature ? licenseFeature.active : true;
@@ -136,10 +176,11 @@ class SoftwareProductLandingPageView extends React.Component {
                                     onLicenseChange={onLicenseChange}
                                     externalLicenceEnabled={this.getExternalLicenceFeatureState()}
                                 />
-                                {this.renderProductDetails(
-                                    isManual,
-                                    isReadOnlyMode
-                                )}
+                                <div className="details-panel">
+                                    {this.renderProductAttachments(
+                                        isReadOnlyMode
+                                    )}
+                                </div>
                             </div>
                         </div>
                     </div>
@@ -155,56 +196,50 @@ class SoftwareProductLandingPageView extends React.Component {
         }
     }
 
-    renderProductDetails(isManual, isReadOnlyMode) {
+    isUploadInProgress() {
+        return (
+            this.state.uploadStatus.complete !== undefined &&
+            !this.state.uploadStatus.complete
+        );
+    }
+
+    renderProductAttachments(isReadOnlyMode) {
         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>
-                    )}
-                </div>
-            );
-        } else {
+        if (this.isUploadInProgress()) {
             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 className="software-product-landing-view-heading-title">
+                        {i18n('Software Product Attachments')}
+                    </div>
+                    <div className="software-product-landing-view-top-block-col-upl ">
+                        <div className="upload-status-text">
+                            {this.state.uploadStatus.statusToString()}
+                            {this.state.showProgressBar && (
+                                <ProgressBar now={this.state.uploadProgress} />
+                            )}
                         </div>
-                    )}
+                    </div>
                 </div>
             );
         }
+        return (
+            <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',
+                        { disabled: isReadOnlyMode }
+                    )}
+                    onClick={() => this.refs.fileInput.open()}
+                    onBrowseVNF={() => onBrowseVNF(currentSoftwareProduct)}
+                />
+            </div>
+        );
     }
 
     handleImportSubmit(files, isReadOnlyMode, isManual) {
@@ -226,6 +261,48 @@ class SoftwareProductLandingPageView extends React.Component {
         }
     }
 
+    onUploadStart = () => {
+        this.stopUploadStatusChecking();
+        this.showProgressBar();
+    };
+
+    onUploadProgress = progressEvent => {
+        const vspUploadStatus = new VspUploadStatus({
+            status: VspUploadStatus.UPLOADING,
+            complete: false
+        });
+        this.setState({
+            uploadStatus: vspUploadStatus
+        });
+        const percentCompleted = Math.round(
+            progressEvent.loaded * 100 / progressEvent.total
+        );
+        if (percentCompleted === 100) {
+            this.keepCheckingUploadStatus(5000);
+            this.resetUploadProgress(2000);
+        }
+        this.setState({ uploadProgress: percentCompleted });
+    };
+
+    onUploadFinished = () => {
+        this.updateUploadStatus();
+    };
+
+    showProgressBar() {
+        this.setState({ showProgressBar: true });
+    }
+
+    hideProgressBar() {
+        this.setState({ showProgressBar: false });
+    }
+
+    resetUploadProgress(milliseconds) {
+        setTimeout(() => {
+            this.setState({ uploadProgress: 0 });
+            this.hideProgressBar();
+        }, milliseconds);
+    }
+
     startUploading(files) {
         let {
             onUpload,
@@ -244,9 +321,21 @@ class SoftwareProductLandingPageView extends React.Component {
         this.refs.fileInput.value = '';
 
         if (validationData) {
-            onUploadConfirmation(currentSoftwareProduct.id, formData);
+            onUploadConfirmation(
+                currentSoftwareProduct.id,
+                formData,
+                () => this.onUploadStart(),
+                this.onUploadProgress,
+                this.onUploadFinished
+            );
         } else {
-            onUpload(currentSoftwareProduct.id, formData);
+            onUpload(
+                currentSoftwareProduct.id,
+                formData,
+                () => this.onUploadStart(),
+                this.onUploadProgress,
+                this.onUploadFinished
+            );
         }
     }
 }
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/VspUploadStatus.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/VspUploadStatus.js
new file mode 100644 (file)
index 0000000..abb2a98
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * -
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022 Nordix Foundation.
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+import i18n from 'nfvo-utils/i18n/i18n';
+
+export default class VspUploadStatus {
+    static UPLOADING = 'UPLOADING';
+    static VALIDATING = 'VALIDATING';
+    static PROCESSING = 'PROCESSING';
+    static SUCCESS = 'SUCCESS';
+    static ERROR = 'ERROR';
+
+    complete;
+    created;
+    lockId;
+    status;
+    updated;
+    vspId;
+    vspVersionId;
+
+    constructor(vspUploadStatusResponse) {
+        this.status = vspUploadStatusResponse.status;
+        this.complete = vspUploadStatusResponse.complete;
+        this.created = vspUploadStatusResponse.created;
+        this.lockId = vspUploadStatusResponse.lockId;
+        this.updated = vspUploadStatusResponse.updated;
+        this.vspId = vspUploadStatusResponse.vspId;
+        this.vspVersionId = vspUploadStatusResponse.vspVersionId;
+    }
+
+    statusToString() {
+        if (!this.status) {
+            return '';
+        }
+        switch (this.status) {
+            case VspUploadStatus.UPLOADING: {
+                return i18n('upload.status.uploading');
+            }
+            case VspUploadStatus.VALIDATING: {
+                return i18n('upload.status.validating');
+            }
+            case VspUploadStatus.PROCESSING: {
+                return i18n('upload.status.processing');
+            }
+            case VspUploadStatus.SUCCESS: {
+                return i18n('upload.status.success');
+            }
+            case VspUploadStatus.ERROR: {
+                return i18n('upload.status.error');
+            }
+            default:
+                return this.status;
+        }
+    }
+}
index 2ef1d9f..de7db29 100644 (file)
@@ -39,6 +39,7 @@ describe('Software Product Landing Page: ', function () {
        let currentSoftwareProduct = {}, softwareProductCategories = [], currentScreen = {},
                finalizedLicenseModelList, licenseAgreementList, featureGroupsList, qschema, qdata = {};
        const dummyFunc = () => {};
+       const fetchUploadStatusMock = () => { return Promise.resolve({ data: {} }) };
 
        beforeAll(function() {
                finalizedLicenseModelList = FinalizedLicenseModelFactory.buildList(2);
@@ -96,7 +97,8 @@ describe('Software Product Landing Page: ', function () {
                        ...currentScreen.props,
                        currentSoftwareProduct,
                        componentsList: VSPComponentsFactory.buildList(2),
-                       features: [{name:'EXTERNAL_LICENSE', active: true}]
+                       features: [{name:'EXTERNAL_LICENSE', active: true}],
+                       fetchUploadStatus: fetchUploadStatusMock
                };
 
                const store = storeCreator();
@@ -113,7 +115,8 @@ describe('Software Product Landing Page: ', function () {
                        ...currentScreen.props,
                        currentSoftwareProduct,
                        componentsList: VSPComponentsFactory.buildList(2),
-                       features: [{name:'EXTERNAL_LICENSE', active: true}]
+                       features: [{name:'EXTERNAL_LICENSE', active: true}],
+                       fetchUploadStatus: fetchUploadStatusMock
                };
                const e = { target: {
                        value: 'INTERNAL'
@@ -143,7 +146,8 @@ describe('Software Product Landing Page: ', function () {
                        currentSoftwareProduct,
                        ...currentScreen.props,
                        componentsList: VSPComponentsFactory.buildList(2),
-                       features: [{name:'EXTERNAL_LICENSE', active: true}]
+                       features: [{name:'EXTERNAL_LICENSE', active: true}],
+                       fetchUploadStatus: fetchUploadStatusMock
                };
 
                const store = storeCreator();
@@ -171,7 +175,8 @@ describe('Software Product Landing Page: ', function () {
                        onUploadConfirmation:  dummyFunc,
                        onUpload: dummyFunc,
                        onInvalidFileSizeUpload: dummyFunc,
-                       features: [{name:'EXTERNAL_LICENSE', active: true}]
+                       features: [{name:'EXTERNAL_LICENSE', active: true}],
+                       fetchUploadStatus: fetchUploadStatusMock
                };
 
                const files = [
@@ -215,7 +220,8 @@ describe('Software Product Landing Page: ', function () {
                        onUploadConfirmation:  dummyFunc,
                        onUpload: dummyFunc,
                        onInvalidFileSizeUpload: dummyFunc,
-                       features: [{name:'EXTERNAL_LICENSE', active: true}]
+                       features: [{name:'EXTERNAL_LICENSE', active: true}],
+                       fetchUploadStatus: fetchUploadStatusMock
                };
 
                const store = storeCreator();