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 --- .../OrchestrationTemplateCandidateImpl.java | 5 +- .../OrchestrationTemplateCandidateImplTest.java | 6 +- .../dao/impl/VspUploadStatusRecordDaoIml.java | 2 +- .../dao/impl/VspUploadStatusRecordDaoImlTest.java | 15 ++ openecomp-ui/README.md | 10 +- openecomp-ui/package.json | 2 +- .../scss/modules/_softwareProductLandingPage.scss | 4 + .../src/nfvo-components/loader/LoaderReducer.js | 4 +- .../vnfMarketPlace/VnfRepositorySearchBox.jsx | 23 ++- .../src/nfvo-utils/ErrorResponseHandler.js | 2 +- openecomp-ui/src/nfvo-utils/RestAPIUtil.js | 74 ++++---- openecomp-ui/src/nfvo-utils/i18n/en.json | 8 +- .../softwareProduct/SoftwareProductActionHelper.js | 44 ++++- .../landingPage/SoftwareProductLandingPage.js | 53 +++++- .../landingPage/SoftwareProductLandingPageView.jsx | 195 +++++++++++++++------ .../softwareProduct/landingPage/VspUploadStatus.js | 73 ++++++++ .../landingPage/landingPage.test.js | 16 +- 17 files changed, 413 insertions(+), 123 deletions(-) create mode 100644 openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/VspUploadStatus.js diff --git a/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 b/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 index 6615447c6f..b51583980d 100644 --- a/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 +++ b/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 @@ -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); diff --git a/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 b/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 index 758505f583..6c21cc471e 100644 --- a/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 +++ b/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 @@ -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()); } diff --git a/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 b/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 index 558016c9af..7b0da51c05 100644 --- a/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 +++ b/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 @@ -91,7 +91,7 @@ public class VspUploadStatusRecordDaoIml implements VspUploadStatusRecordDao { public Optional findLatest(final String vspId, final String vspVersionId) { final List 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)); } } diff --git a/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 b/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 index cd0bc1cd1e..f17d250a32 100644 --- a/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 +++ b/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 @@ -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 resultMock = mock(Result.class); + when(resultMock.all()).thenReturn(new ArrayList<>()); + + when(accessor.findAllByVspIdAndVspVersionId(vspId, vspVersionId)).thenReturn(resultMock); + //when + final Optional vspUploadStatusOptional = packageUploadManagerDaoIml.findLatest(vspId, vspVersionId); + //then + assertTrue(vspUploadStatusOptional.isEmpty()); + } + } \ No newline at end of file diff --git a/openecomp-ui/README.md b/openecomp-ui/README.md index c270802744..a41aa27e37 100644 --- a/openecomp-ui/README.md +++ b/openecomp-ui/README.md @@ -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:
`npm config set proxy http://:`
`npm config set https-proxy http://:` -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"
`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:
`npm config set proxy http://:`
`npm config set https-proxy http://:` | +| 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"
`git config --global url."https://".insteadOf git://` | diff --git a/openecomp-ui/package.json b/openecomp-ui/package.json index e19b0b1c71..96e7c43837 100644 --- a/openecomp-ui/package.json +++ b/openecomp-ui/package.json @@ -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", diff --git a/openecomp-ui/resources/scss/modules/_softwareProductLandingPage.scss b/openecomp-ui/resources/scss/modules/_softwareProductLandingPage.scss index c1cf3cf778..a6276275d4 100644 --- a/openecomp-ui/resources/scss/modules/_softwareProductLandingPage.scss +++ b/openecomp-ui/resources/scss/modules/_softwareProductLandingPage.scss @@ -199,6 +199,10 @@ color: $blue; @extend .body-1-semibold; } + .upload-status-text { + color: $blue; + @extend .body-1-semibold; + } .or-text { margin-top: 10px; margin-bottom: 10px; diff --git a/openecomp-ui/src/nfvo-components/loader/LoaderReducer.js b/openecomp-ui/src/nfvo-components/loader/LoaderReducer.js index 3f9eb17db3..4222c3dac6 100644 --- a/openecomp-ui/src/nfvo-components/loader/LoaderReducer.js +++ b/openecomp-ui/src/nfvo-components/loader/LoaderReducer.js @@ -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; diff --git a/openecomp-ui/src/nfvo-components/vnfMarketPlace/VnfRepositorySearchBox.jsx b/openecomp-ui/src/nfvo-components/vnfMarketPlace/VnfRepositorySearchBox.jsx index 2c82c2be6c..32e3a3e3f2 100644 --- a/openecomp-ui/src/nfvo-components/vnfMarketPlace/VnfRepositorySearchBox.jsx +++ b/openecomp-ui/src/nfvo-components/vnfMarketPlace/VnfRepositorySearchBox.jsx @@ -50,22 +50,27 @@ class VnfRepositorySearchBox extends Component { dataTestId, isReadOnlyMode } = this.props; - let showVNF = Configuration.get('showBrowseVNF'); + const showVNF = Configuration.get('showBrowseVNF'); return ( -
+
- -
- - + {showVNF && ( +
+ )} + {showVNF && ( + + )}
); } diff --git a/openecomp-ui/src/nfvo-utils/ErrorResponseHandler.js b/openecomp-ui/src/nfvo-utils/ErrorResponseHandler.js index 5c55855027..e7d3f8a85c 100644 --- a/openecomp-ui/src/nfvo-utils/ErrorResponseHandler.js +++ b/openecomp-ui/src/nfvo-utils/ErrorResponseHandler.js @@ -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); diff --git a/openecomp-ui/src/nfvo-utils/RestAPIUtil.js b/openecomp-ui/src/nfvo-utils/RestAPIUtil.js index 03908d8203..97d3847350 100644 --- a/openecomp-ui/src/nfvo-utils/RestAPIUtil.js +++ b/openecomp-ui/src/nfvo-utils/RestAPIUtil.js @@ -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) { diff --git a/openecomp-ui/src/nfvo-utils/i18n/en.json b/openecomp-ui/src/nfvo-utils/i18n/en.json index 7a2e66a4b3..ada011f7c3 100644 --- a/openecomp-ui/src/nfvo-utils/i18n/en.json +++ b/openecomp-ui/src/nfvo-utils/i18n/en.json @@ -662,6 +662,10 @@ "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" } 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; + } + } +} diff --git a/openecomp-ui/test/softwareProduct/landingPage/landingPage.test.js b/openecomp-ui/test/softwareProduct/landingPage/landingPage.test.js index 2ef1d9fce8..de7db29ad7 100644 --- a/openecomp-ui/test/softwareProduct/landingPage/landingPage.test.js +++ b/openecomp-ui/test/softwareProduct/landingPage/landingPage.test.js @@ -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(); -- cgit 1.2.3-korg