2 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 * or implied. See the License for the specific language governing
14 * permissions and limitations under the License.
16 import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
17 import Configuration from 'sdc-app/config/Configuration.js';
18 import i18n from 'nfvo-utils/i18n/i18n.js';
19 import LicenseModelActionHelper from 'sdc-app/onboarding/licenseModel/LicenseModelActionHelper.js';
20 import LicenseAgreementActionHelper from 'sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementActionHelper.js';
21 import FeatureGroupsActionHelper from 'sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsActionHelper.js';
23 import {actionTypes, onboardingOriginTypes, PRODUCT_QUESTIONNAIRE, forms} from 'sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js';
24 import OnboardingActionHelper from 'sdc-app/onboarding/OnboardingActionHelper.js';
25 import SoftwareProductComponentsActionHelper from './components/SoftwareProductComponentsActionHelper.js';
26 import {actionsEnum as VersionControllerActionsEnum} from 'nfvo-components/panel/versionController/VersionControllerConstants.js';
27 import {actionTypes as HeatSetupActions} from 'sdc-app/onboarding/softwareProduct/attachments/setup/HeatSetupConstants.js';
28 import {actionTypes as featureGroupsActionConstants} from 'sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsConstants.js';
29 import {actionTypes as licenseAgreementActionTypes} from 'sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementConstants.js';
30 import {actionTypes as componentActionTypes} from './components/SoftwareProductComponentsConstants.js';
31 import ValidationHelper from 'sdc-app/common/helpers/ValidationHelper.js';
32 import {actionTypes as modalActionTypes} from 'nfvo-components/modal/GlobalModalConstants.js';
33 import {modalContentMapper} from 'sdc-app/common/modal/ModalContentMapper.js';
34 import ItemsHelper from 'sdc-app/common/helpers/ItemsHelper.js';
35 import ScreensHelper from 'sdc-app/common/helpers/ScreensHelper.js';
36 import {enums, screenTypes} from 'sdc-app/onboarding/OnboardingConstants.js';
37 import MergeEditorActionHelper from 'sdc-app/common/merge/MergeEditorActionHelper.js';
38 import {CommitModalType} from 'nfvo-components/panel/versionController/components/CommitCommentModal.jsx';
39 import {actionTypes as commonActionTypes} from 'sdc-app/common/reducers/PlainDataReducerConstants.js';
40 import versionPageActionHelper from 'sdc-app/onboarding/versionsPage/VersionsPageActionHelper.js';
41 import {itemTypes} from 'sdc-app/onboarding/versionsPage/VersionsPageConstants.js';
42 import {catalogItemStatuses} from 'sdc-app/onboarding/onboard/onboardingCatalog/OnboardingCatalogConstants.js';
43 import getValue from 'nfvo-utils/getValue.js';
45 function getLicensingData(licensingData = {}) {
46 const {licenseAgreement, featureGroups} = licensingData;
47 const newlicenseAgreement = getValue(licenseAgreement);
48 const newfeatureGroups = getValue(featureGroups);
49 return newlicenseAgreement ? {
50 licenseAgreement: newlicenseAgreement,
51 featureGroups: newfeatureGroups
56 const restPrefix = Configuration.get('restPrefix');
57 return `${restPrefix}/v1.0/vendor-software-products/`;
59 function softwareProductCategoriesUrl() {
60 const restATTPrefix = Configuration.get('restATTPrefix');
61 return `${restATTPrefix}/v1/categories/resources/`;
64 function uploadFile(vspId, formData, version) {
65 return RestAPIUtil.post(`${baseUrl()}${vspId}/versions/${version.id}/orchestration-template-candidate`, formData);
69 function putSoftwareProduct({softwareProduct, version}) {
70 return RestAPIUtil.put(`${baseUrl()}${softwareProduct.id}/versions/${version.id}`, {
71 name: softwareProduct.name,
72 description: softwareProduct.description,
73 category: softwareProduct.category,
74 subCategory: softwareProduct.subCategory,
75 vendorId: softwareProduct.vendorId,
76 vendorName: softwareProduct.vendorName,
77 licensingVersion: softwareProduct.licensingVersion ? softwareProduct.licensingVersion : undefined,
78 icon: softwareProduct.icon,
79 licensingData: getLicensingData(softwareProduct.licensingData)
83 function putSoftwareProductQuestionnaire(vspId, qdata, version) {
84 return RestAPIUtil.put(`${baseUrl()}${vspId}/versions/${version.id}/questionnaire`, qdata);
87 function putSoftwareProductAction(id, action, version) {
88 return RestAPIUtil.put(`${baseUrl()}${id}/versions/${version.id}/actions`, {action: action});
91 function fetchSoftwareProductList() {
92 return RestAPIUtil.fetch(`${baseUrl()}?versionFilter=Draft`);
95 function fetchFinalizedSoftwareProductList() {
96 return RestAPIUtil.fetch(`${baseUrl()}?versionFilter=Certified`);
99 function fetchSoftwareProduct(vspId, version) {
100 return RestAPIUtil.fetch(`${baseUrl()}${vspId}/versions/${version.id}`);
103 function fetchSoftwareProductQuestionnaire(vspId, version) {
104 return RestAPIUtil.fetch(`${baseUrl()}${vspId}/versions/${version.id}/questionnaire`);
107 function updateSoftwareProductHeatCandidate(softwareProductId, heatCandidate, version) {
108 return RestAPIUtil.put(`${baseUrl()}${softwareProductId}/versions/${version.id}/orchestration-template-candidate/manifest`, heatCandidate);
110 function validateHeatCandidate(softwareProductId, version) {
111 return RestAPIUtil.put(`${baseUrl()}${softwareProductId}/versions/${version.id}/orchestration-template-candidate/process`);
114 function fetchOrchestrationTemplateCandidate(softwareProductId, version, ) {
115 return RestAPIUtil.fetch(`${baseUrl()}${softwareProductId}/versions/${version.id}/orchestration-template-candidate`, {dataType: 'binary'});
118 function objToString(obj) {
120 if (obj instanceof Array) {
121 obj.forEach((item) => {
122 str += objToString(item) + '\n';
127 if (obj.hasOwnProperty(p)) {
128 str += obj[p] + '\n';
132 return str.replace(/\n$/, '');
135 function parseUploadErrorMsg(error) {
137 for (let key in error) {
138 if (error.hasOwnProperty(key)) {
139 message += objToString(error[key]) + '\n';
142 return message.replace(/\n$/, '');
145 function fetchSoftwareProductCategories(dispatch) {
146 let handleResponse = response => dispatch({
147 type: actionTypes.SOFTWARE_PRODUCT_CATEGORIES_LOADED,
148 softwareProductCategories: response
150 return RestAPIUtil.fetch(softwareProductCategoriesUrl())
151 .then(handleResponse)
152 .catch(() => handleResponse(null));
155 function loadLicensingData(dispatch, {licenseModelId, licensingVersion}) {
156 return ItemsHelper.fetchVersion({itemId: licenseModelId, versionId: licensingVersion}).then(() => {
158 LicenseAgreementActionHelper.fetchLicenseAgreementList(dispatch, {licenseModelId, version: {id: licensingVersion}}),
159 FeatureGroupsActionHelper.fetchFeatureGroupsList(dispatch, {licenseModelId, version: {id: licensingVersion}})
164 function getExpandedItemsId(items, itemIdToToggle) {
165 for (let i = 0; i < items.length; i++) {
166 if (items[i].id === itemIdToToggle) {
167 if (items[i].expanded) {
171 return {[itemIdToToggle]: true};
174 else if (items[i].items && items[i].items.length > 0) {
175 let mapOfExpandedIds = getExpandedItemsId(items[i].items, itemIdToToggle);
176 if (mapOfExpandedIds !== false) {
177 mapOfExpandedIds[items[i].id] = true;
178 return mapOfExpandedIds;
185 function getTimestampString() {
186 let date = new Date();
187 let z = n => n < 10 ? '0' + n : n;
188 return `${date.getFullYear()}-${z(date.getMonth())}-${z(date.getDate())}_${z(date.getHours())}-${z(date.getMinutes())}`;
191 function showFileSaveDialog({blob, xhr, defaultFilename, addTimestamp}) {
193 let contentDisposition = xhr.getResponseHeader('content-disposition') ? xhr.getResponseHeader('content-disposition') : '';
194 let match = contentDisposition.match(/filename=(.*?)(;|$)/);
199 filename = defaultFilename;
203 filename = filename.replace(/(^.*?)\.([^.]+$)/, `$1_${getTimestampString()}.$2`);
206 let link = document.createElement('a');
207 let url = URL.createObjectURL(blob);
209 link.download = filename;
210 link.style.display = 'none';
211 document.body.appendChild(link);
213 setTimeout(function(){
214 document.body.removeChild(link);
215 URL.revokeObjectURL(url);
219 function migrateSoftwareProduct(vspId, version) {
220 return RestAPIUtil.put(`${baseUrl()}${vspId}/versions/${version.id}/heal`);
225 const SoftwareProductActionHelper = {
227 fetchFinalizedSoftwareProductList(dispatch) {
228 return fetchFinalizedSoftwareProductList().then(response => dispatch({
229 type: actionTypes.FINALIZED_SOFTWARE_PRODUCT_LIST_LOADED,
234 loadSoftwareProductAssociatedData(dispatch) {
235 fetchSoftwareProductCategories(dispatch);
236 LicenseModelActionHelper.fetchFinalizedLicenseModels(dispatch);
239 loadSoftwareProductDetailsData(dispatch, {licenseModelId, licensingVersion}) {
240 SoftwareProductActionHelper.loadSoftwareProductAssociatedData(dispatch);
241 if (licensingVersion) {
242 return loadLicensingData(dispatch, {licenseModelId, licensingVersion});
244 return Promise.resolve();
247 fetchSoftwareProductList(dispatch) {
248 return fetchSoftwareProductList().then(response => dispatch({
249 type: actionTypes.SOFTWARE_PRODUCT_LIST_LOADED,
254 loadSoftwareProductHeatCandidate(dispatch, {softwareProductId, version}){
255 return RestAPIUtil.fetch(`${baseUrl()}${softwareProductId}/versions/${version.id}/orchestration-template-candidate/manifest`).then(response => dispatch({
256 type: HeatSetupActions.MANIFEST_LOADED,
261 loadLicensingVersionsList(dispatch, {licenseModelId}){
262 return ItemsHelper.fetchVersions({itemId: licenseModelId}).then(response => {
264 type: actionTypes.LOAD_LICENSING_VERSIONS_LIST,
265 licensingVersionsList: response.results
269 updateSoftwareProductHeatCandidate(dispatch, {softwareProductId, heatCandidate, version}){
270 return updateSoftwareProductHeatCandidate(softwareProductId, heatCandidate, version);
273 processAndValidateHeatCandidate(dispatch, {softwareProductId, version}){
274 return validateHeatCandidate(softwareProductId, version).then(response => {
275 if (response.status === 'Success') {
276 SoftwareProductComponentsActionHelper.fetchSoftwareProductComponents(dispatch, {softwareProductId, version});
277 SoftwareProductActionHelper.fetchSoftwareProduct(dispatch, {softwareProductId, version});
282 uploadFile(dispatch, {softwareProductId, formData, failedNotificationTitle, version}) {
284 type: HeatSetupActions.FILL_HEAT_SETUP_CACHE,
289 .then(() => uploadFile(softwareProductId, formData, version))
291 if (response.status === 'Success') {
293 type: commonActionTypes.DATA_CHANGED,
294 deltaData: {onboardingOrigin: response.onboardingOrigin},
295 formName: forms.VENDOR_SOFTWARE_PRODUCT_DETAILS
297 switch(response.onboardingOrigin){
298 case onboardingOriginTypes.ZIP:
299 ScreensHelper.loadScreen(dispatch, {
300 screen: enums.SCREEN.SOFTWARE_PRODUCT_ATTACHMENTS_SETUP, screenType: screenTypes.SOFTWARE_PRODUCT,
301 props: {softwareProductId, version}
304 case onboardingOriginTypes.CSAR:
305 ScreensHelper.loadScreen(dispatch, {
306 screen: enums.SCREEN.SOFTWARE_PRODUCT_ATTACHMENTS_VALIDATION, screenType: screenTypes.SOFTWARE_PRODUCT,
307 props: {softwareProductId, version}
313 throw new Error(parseUploadErrorMsg(response.errors));
318 type: modalActionTypes.GLOBAL_MODAL_ERROR,
320 title: failedNotificationTitle,
321 msg: error.message || (error.responseJSON && error.responseJSON.message)
327 downloadHeatFile(dispatch, {softwareProductId, heatCandidate, isReadOnlyMode, version}){
328 let p = isReadOnlyMode ? Promise.resolve() : SoftwareProductActionHelper.updateSoftwareProductHeatCandidate(dispatch, {softwareProductId, heatCandidate, version});
330 fetchOrchestrationTemplateCandidate(softwareProductId, version)
331 .then((blob, statusText, xhr) => showFileSaveDialog({blob, xhr, defaultFilename: 'HEAT_file.zip', addTimestamp: true}));
332 }, null/* do not download if data was not saved correctly*/);
335 hideUploadConfirm (dispatch) {
337 type: actionTypes.softwareProductEditor.UPLOAD_CONFIRMATION
340 updateSoftwareProduct(dispatch, {softwareProduct, version, qdata}) {
342 SoftwareProductActionHelper.updateSoftwareProductData(dispatch, {softwareProduct, version}).then(
344 type: actionTypes.SOFTWARE_PRODUCT_LIST_EDIT,
345 payload: {softwareProduct}
348 SoftwareProductActionHelper.updateSoftwareProductQuestionnaire(dispatch, {
349 softwareProductId: softwareProduct.id,
356 updateSoftwareProductData(dispatch, {softwareProduct, version}) {
357 return putSoftwareProduct({softwareProduct, version});
360 updateSoftwareProductQuestionnaire(dispatch, {softwareProductId, qdata, version}) {
361 return putSoftwareProductQuestionnaire(softwareProductId, qdata, version);
364 softwareProductEditorDataChanged(dispatch, {deltaData}) {
366 type: actionTypes.softwareProductEditor.DATA_CHANGED,
371 softwareProductQuestionnaireUpdate(dispatch, {data}) {
373 type: actionTypes.SOFTWARE_PRODUCT_QUESTIONNAIRE_UPDATE,
374 payload: {qdata: data}
378 softwareProductEditorVendorChanged(dispatch, {deltaData, formName}) {
379 if (deltaData.licensingVersion){
380 return loadLicensingData(dispatch, {licenseModelId: deltaData.vendorId, licensingVersion: deltaData.licensingVersion}).then(() => {
381 ValidationHelper.dataChanged(dispatch, {deltaData, formName});
382 return Promise.resolve();
384 } else if (deltaData.vendorId) {
385 ValidationHelper.dataChanged(dispatch, {deltaData, formName});
386 return SoftwareProductActionHelper.loadLicensingVersionsList(dispatch, {
387 licenseModelId: deltaData.vendorId
389 OnboardingActionHelper.forceBreadCrumbsUpdate(dispatch)
392 ValidationHelper.dataChanged(dispatch, {deltaData, formName});
395 type: licenseAgreementActionTypes.LICENSE_AGREEMENT_LIST_LOADED,
396 response: {results: []}
400 type: featureGroupsActionConstants.FEATURE_GROUPS_LIST_LOADED,
401 response: {results: []}
407 setIsValidityData(dispatch, {isValidityData}) {
409 type: actionTypes.softwareProductEditor.IS_VALIDITY_DATA_CHANGED,
414 fetchSoftwareProduct(dispatch, {softwareProductId, version}) {
416 fetchSoftwareProduct(softwareProductId, version).then(response => {
418 type: actionTypes.SOFTWARE_PRODUCT_LOADED,
423 fetchSoftwareProductQuestionnaire(softwareProductId, version).then(response => {
424 ValidationHelper.qDataLoaded(dispatch, {response: {qdata: response.data ? JSON.parse(response.data) : {},
425 qschema: JSON.parse(response.schema)}, qName: PRODUCT_QUESTIONNAIRE});
430 manageSubmitAction(dispatch, {softwareProductId, version, isDirty}) {
432 const onCommit = comment => {
433 return this.performVCAction(dispatch, {softwareProductId, action: VersionControllerActionsEnum.COMMIT, version, comment}).then(() => {
434 return this.performSubmitAction(dispatch, {softwareProductId, version});
438 type: modalActionTypes.GLOBAL_MODAL_SHOW,
440 modalComponentName: modalContentMapper.COMMIT_COMMENT,
441 modalComponentProps: {
443 type: CommitModalType.COMMIT_SUBMIT
445 title: i18n('Commit & Submit')
448 return Promise.resolve(version);
450 return this.performSubmitAction(dispatch, {softwareProductId, version});
453 performSubmitAction(dispatch, {softwareProductId, version}) {
454 return putSoftwareProductAction(softwareProductId, VersionControllerActionsEnum.SUBMIT, version).then(() => {
455 return putSoftwareProductAction(softwareProductId, VersionControllerActionsEnum.CREATE_PACKAGE, version).then(() => {
456 return ItemsHelper.checkItemStatus(dispatch, {itemId: softwareProductId, versionId: version.id}).then(updatedVersion => {
458 type: modalActionTypes.GLOBAL_MODAL_SUCCESS,
460 title: i18n('Submit Succeeded'),
461 msg: i18n('This software product successfully submitted'),
462 cancelButtonText: i18n('OK'),
466 versionPageActionHelper.fetchVersions(dispatch, {itemType: itemTypes.SOFTWARE_PRODUCT, itemId: softwareProductId});
467 return Promise.resolve(updatedVersion);
473 type: modalActionTypes.GLOBAL_MODAL_ERROR,
475 modalComponentName: modalContentMapper.SUMBIT_ERROR_RESPONSE,
476 title: i18n('Submit Failed'),
477 modalComponentProps: {
478 validationResponse: error.responseJSON
480 cancelButtonText: i18n('OK')
483 return Promise.reject(error.responseJSON);
487 performVCAction(dispatch, {softwareProductId, action, version, comment}) {
488 return MergeEditorActionHelper.analyzeSyncResult(dispatch, {itemId: softwareProductId, version}).then(({inMerge, isDirty, updatedVersion}) => {
489 if (updatedVersion.status === catalogItemStatuses.CERTIFIED &&
490 (action === VersionControllerActionsEnum.COMMIT || action === VersionControllerActionsEnum.SYNC)) {
491 versionPageActionHelper.fetchVersions(dispatch, {itemType: itemTypes.SOFTWARE_PRODUCT, itemId: softwareProductId});
493 type: modalActionTypes.GLOBAL_MODAL_WARNING,
495 title: i18n('Commit error'),
496 msg: i18n('Item version already Certified'),
497 cancelButtonText: i18n('Cancel')
500 return Promise.resolve(updatedVersion);
503 if (action === VersionControllerActionsEnum.SUBMIT) {
504 return this.manageSubmitAction(dispatch, {softwareProductId, version, isDirty});
507 let isCallActionValid = action !== VersionControllerActionsEnum.COMMIT || isDirty;
508 if(isCallActionValid) {
509 return ItemsHelper.performVCAction({itemId: softwareProductId, action, version, comment}).then(() => {
510 versionPageActionHelper.fetchVersions(dispatch, {itemType: itemTypes.LICENSE_MODEL, itemId: softwareProductId});
511 if (action === VersionControllerActionsEnum.SYNC) {
512 return MergeEditorActionHelper.analyzeSyncResult(dispatch, {itemId: softwareProductId, version}).then(({updatedVersion}) => {
513 return Promise.resolve(updatedVersion);
516 return ItemsHelper.checkItemStatus(dispatch, {itemId: softwareProductId, versionId: version.id});
522 type: modalActionTypes.GLOBAL_MODAL_ERROR,
524 title: i18n('Commit Failed'),
525 msg: i18n('There is nothing to commit')
534 toggleNavigationItems(dispatch, {items, itemIdToExpand}) {
535 let mapOfExpandedIds = getExpandedItemsId(items, itemIdToExpand);
537 type: actionTypes.TOGGLE_NAVIGATION_ITEM,
542 /** for the next verision */
543 addComponent(dispatch, {softwareProductId, modalClassName, version}) {
544 SoftwareProductComponentsActionHelper.clearComponentCreationData(dispatch);
546 type: componentActionTypes.COMPONENT_CREATE_OPEN
549 type: modalActionTypes.GLOBAL_MODAL_SHOW,
551 modalComponentName: modalContentMapper.COMPONENT_CREATION,
552 modalComponentProps: {softwareProductId, version},
554 title: 'Create Virtual Function Component'
559 migrateSoftwareProduct(dispatch, {softwareProduct}) {
560 let {id: softwareProductId, version} = softwareProduct;
561 const newVer = version.id;
562 migrateSoftwareProduct(softwareProductId, version).then(() => ScreensHelper.loadScreen(dispatch, {
563 screen: enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE, screenType: screenTypes.SOFTWARE_PRODUCT,
564 props: {softwareProductId, version: {id: newVer, label: newVer}}
570 export default SoftwareProductActionHelper;