From 4594cba0c53461bc1a273458e0a7a314da6bfb68 Mon Sep 17 00:00:00 2001 From: "andre.schmid" Date: Tue, 25 Jan 2022 19:38:32 +0000 Subject: Obtain and control VSP package upload status Obtain the upload status and control the upload from the frontend perspective. Change-Id: Idcc921cf592efea33df35c557afcfae827af3a39 Issue-ID: SDC-3862 Signed-off-by: andre.schmid --- .../softwareProduct/SoftwareProductActionHelper.js | 44 ++++- .../landingPage/SoftwareProductLandingPage.js | 53 +++++- .../landingPage/SoftwareProductLandingPageView.jsx | 195 +++++++++++++++------ .../softwareProduct/landingPage/VspUploadStatus.js | 73 ++++++++ 4 files changed, 299 insertions(+), 66 deletions(-) create mode 100644 openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/VspUploadStatus.js (limited to 'openecomp-ui/src/sdc-app/onboarding') diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js index 5c82c70830..3364edfe11 100644 --- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js @@ -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({ diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPage.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPage.js index fa136f2f1b..4b4c2fa86b 100644 --- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPage.js +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPage.js @@ -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, diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageView.jsx index 5f208345f7..c560a73db3 100644 --- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageView.jsx +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageView.jsx @@ -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 - )} +
+ {this.renderProductAttachments( + isReadOnlyMode + )} +
@@ -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 ( -
- {!isManual && ( -
-
- {i18n('Software Product Attachments')} -
- this.refs.fileInput.open()} - onBrowseVNF={() => - onBrowseVNF(currentSoftwareProduct) - } - /> -
- )} -
- ); - } else { + if (this.isUploadInProgress()) { return ( -
- {!isManual && ( -
-
- {i18n('Software Product Attachments')} -
- this.refs.fileInput.open()} - onBrowseVNF={() => onBrowseVNF()} - /> +
+
+ {i18n('Software Product Attachments')} +
+
+
+ {this.state.uploadStatus.statusToString()} + {this.state.showProgressBar && ( + + )}
- )} +
); } + return ( +
+
+ {i18n('Software Product Attachments')} +
+ this.refs.fileInput.open()} + onBrowseVNF={() => onBrowseVNF(currentSoftwareProduct)} + /> +
+ ); } 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 index 0000000000..abb2a98c98 --- /dev/null +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/VspUploadStatus.js @@ -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; + } + } +} -- cgit 1.2.3-korg