2 * Copyright © 2016-2018 European Support Limited
3 * Modifications copyright (c) 2021 Nokia
4 * Modifications Copyright (C) 2021 Nordix Foundation.
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
15 * or implied. See the License for the specific language governing
16 * permissions and limitations under the License.
18 import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
19 import showFileSaveDialog from 'nfvo-utils/ShowFileSaveDialog.js';
20 import Configuration from 'sdc-app/config/Configuration.js';
21 import i18n from 'nfvo-utils/i18n/i18n.js';
22 import LicenseModelActionHelper from 'sdc-app/onboarding/licenseModel/LicenseModelActionHelper.js';
23 import LicenseAgreementActionHelper from 'sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementActionHelper.js';
24 import FeatureGroupsActionHelper from 'sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsActionHelper.js';
28 onboardingOriginTypes,
29 PRODUCT_QUESTIONNAIRE,
31 } from 'sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js';
32 import OnboardingActionHelper from 'sdc-app/onboarding/OnboardingActionHelper.js';
33 import SoftwareProductComponentsActionHelper from './components/SoftwareProductComponentsActionHelper.js';
34 import { actionsEnum as VersionControllerActionsEnum } from 'nfvo-components/panel/versionController/VersionControllerConstants.js';
35 import { actionTypes as HeatSetupActions } from 'sdc-app/onboarding/softwareProduct/attachments/setup/HeatSetupConstants.js';
36 import { actionTypes as featureGroupsActionConstants } from 'sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsConstants.js';
37 import { actionTypes as licenseAgreementActionTypes } from 'sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementConstants.js';
38 import { actionTypes as componentActionTypes } from './components/SoftwareProductComponentsConstants.js';
39 import ValidationHelper from 'sdc-app/common/helpers/ValidationHelper.js';
40 import { actionTypes as modalActionTypes } from 'nfvo-components/modal/GlobalModalConstants.js';
41 import { modalContentMapper } from 'sdc-app/common/modal/ModalContentMapper.js';
42 import { default as ItemsHelper } from 'sdc-app/common/helpers/ItemsHelper.js';
43 import ScreensHelper from 'sdc-app/common/helpers/ScreensHelper.js';
44 import { enums, screenTypes } from 'sdc-app/onboarding/OnboardingConstants.js';
45 import MergeEditorActionHelper from 'sdc-app/common/merge/MergeEditorActionHelper.js';
46 import { CommitModalType } from 'nfvo-components/panel/versionController/components/CommitCommentModal.jsx';
47 import { actionTypes as commonActionTypes } from 'sdc-app/common/reducers/PlainDataReducerConstants.js';
48 import versionPageActionHelper from 'sdc-app/onboarding/versionsPage/VersionsPageActionHelper.js';
49 import { itemTypes } from 'sdc-app/onboarding/versionsPage/VersionsPageConstants.js';
50 import getValue from 'nfvo-utils/getValue.js';
54 } from 'sdc-app/common/helpers/ItemsHelperConstants.js';
56 let shouldDisplayTimingValidationInfo = true;
57 let alertValidationTiming = Configuration.get(
58 'displayAlertValidationAfterMilisec'
61 function getLicensingData(licensingData = {}) {
62 const { licenseAgreement, featureGroups } = licensingData;
63 const newlicenseAgreement = getValue(licenseAgreement);
64 const newfeatureGroups = getValue(featureGroups);
65 return newlicenseAgreement
67 licenseAgreement: newlicenseAgreement,
68 featureGroups: newfeatureGroups
73 function getTimingInfoWarning() {
75 type: modalActionTypes.GLOBAL_MODAL_WARNING,
77 title: 'Please be patient',
78 msg: 'Large files processing may take up to several minutes.',
79 cancelButtonText: 'OK'
84 function displayTimingValidationInfo(dispatch) {
85 shouldDisplayTimingValidationInfo = true;
87 if (shouldDisplayTimingValidationInfo) {
88 dispatch(getTimingInfoWarning());
90 }, alertValidationTiming);
93 function closeTimingValidationInfo(dispatch) {
94 shouldDisplayTimingValidationInfo = false;
96 type: modalActionTypes.GLOBAL_MODAL_CLOSE
101 const restPrefix = Configuration.get('restPrefix');
102 return `${restPrefix}/v1.0/vendor-software-products/`;
105 function softwareProductCategoriesUrl() {
106 const restCatalogPrefix = Configuration.get('restCatalogPrefix');
107 return `${restCatalogPrefix}/v1/categories/resources/`;
110 function getModelUrl() {
111 const restCatalogPrefix = Configuration.get('restCatalogPrefix');
112 return `${restCatalogPrefix}/v1/catalog/model?modelType=normative`;
115 function uploadFile(vspId, formData, version, onUploadProgress = undefined) {
119 if (onUploadProgress) {
120 options.onUploadProgress = onUploadProgress;
122 return RestAPIUtil.post(
123 `${baseUrl()}${vspId}/versions/${
125 }/orchestration-template-candidate`,
131 function uploadVNFFile(csarId, softwareProductId, version) {
132 let verId = typeof version === 'object' ? version.id : version;
133 return RestAPIUtil.post(
134 `${baseUrl()}${softwareProductId}/versions/${verId}/vnfrepository/vnfpackage/${csarId}/import`
137 function putSoftwareProduct({ softwareProduct, version }) {
138 return RestAPIUtil.put(
139 `${baseUrl()}${softwareProduct.id}/versions/${version.id}`,
141 name: softwareProduct.name,
142 description: softwareProduct.description,
143 category: softwareProduct.category,
144 subCategory: softwareProduct.subCategory,
145 vendorId: softwareProduct.vendorId,
146 vendorName: softwareProduct.vendorName,
147 licensingVersion: softwareProduct.licensingVersion
148 ? softwareProduct.licensingVersion
150 icon: softwareProduct.icon,
151 licenseType: softwareProduct.licenseType,
152 selectedModelList: softwareProduct.selectedModelList,
153 licensingData: getLicensingData(softwareProduct.licensingData)
158 function putSoftwareProductQuestionnaire(vspId, qdata, version) {
159 return RestAPIUtil.put(
160 `${baseUrl()}${vspId}/versions/${version.id}/questionnaire`,
165 function putSoftwareProductAction(id, action, version) {
166 return RestAPIUtil.put(`${baseUrl()}${id}/versions/${version.id}/actions`, {
171 function fetchSoftwareProductList() {
172 return RestAPIUtil.fetch(
173 `${baseUrl()}?versionFilter=${versionStatus.DRAFT}`
177 function fetchArchivedSoftwareProductList() {
178 return RestAPIUtil.fetch(`${baseUrl()}?Status=${itemStatus.ARCHIVED}`);
181 function fetchFinalizedSoftwareProductList() {
182 return RestAPIUtil.fetch(
183 `${baseUrl()}?versionFilter=${versionStatus.CERTIFIED}`
187 function fetchSoftwareProduct(vspId, version) {
188 return RestAPIUtil.fetch(`${baseUrl()}${vspId}/versions/${version.id}`);
191 function fetchSoftwareProductQuestionnaire(vspId, version) {
192 return RestAPIUtil.fetch(
193 `${baseUrl()}${vspId}/versions/${version.id}/questionnaire`
197 function updateSoftwareProductHeatCandidate(
202 return RestAPIUtil.put(
203 `${baseUrl()}${softwareProductId}/versions/${
205 }/orchestration-template-candidate/manifest`,
209 function validateHeatCandidate(softwareProductId, version) {
210 return RestAPIUtil.put(
211 `${baseUrl()}${softwareProductId}/versions/${
213 }/orchestration-template-candidate/process`
217 function fetchOrchestrationTemplateCandidate(softwareProductId, version) {
218 return RestAPIUtil.fetch(
219 `${baseUrl()}${softwareProductId}/versions/${
221 }/orchestration-template-candidate`,
222 { dataType: 'binary' }
226 function abortValidationProcess(softwareProductId, version) {
227 return RestAPIUtil.destroy(
228 `${baseUrl()}${softwareProductId}/versions/${
230 }/orchestration-template-candidate`
234 function objToString(obj) {
236 if (obj instanceof Array) {
237 obj.forEach(item => {
238 str += objToString(item) + '\n';
242 if (obj.hasOwnProperty(p)) {
243 str += obj[p] + '\n';
247 return str.replace(/\n$/, '');
250 function parseUploadErrorMsg(error) {
252 for (let key in error) {
253 if (error.hasOwnProperty(key)) {
254 message += objToString(error[key]) + '\n';
257 return message.replace(/\n$/, '');
260 function showWarningValidationInfo(dispatch, errors) {
262 type: modalActionTypes.GLOBAL_MODAL_WARNING,
264 title: 'Validation messages',
265 msg: parseUploadErrorMsg(errors),
266 cancelButtonText: 'OK'
271 function fetchSoftwareProductCategories(dispatch) {
272 let handleResponse = response =>
274 type: actionTypes.SOFTWARE_PRODUCT_CATEGORIES_LOADED,
275 softwareProductCategories: response
277 return RestAPIUtil.fetch(softwareProductCategoriesUrl())
278 .then(handleResponse)
279 .catch(() => handleResponse(null));
282 function fetchModelList(dispatch) {
283 let handleResponse = response =>
285 type: actionTypes.SOFTWARE_PRODUCT_MODELS_LOADED,
288 RestAPIUtil.fetch(getModelUrl())
289 .then(handleResponse)
290 .catch(() => handleResponse(null));
293 function loadLicensingData(dispatch, { licenseModelId, licensingVersion }) {
294 return ItemsHelper.fetchVersion({
295 itemId: licenseModelId,
296 versionId: licensingVersion
299 LicenseAgreementActionHelper.fetchLicenseAgreementList(dispatch, {
301 version: { id: licensingVersion }
303 FeatureGroupsActionHelper.fetchFeatureGroupsList(dispatch, {
305 version: { id: licensingVersion }
311 function getExpandedItemsId(items, itemIdToToggle) {
312 for (let i = 0; i < items.length; i++) {
313 if (items[i].id === itemIdToToggle) {
314 if (items[i].expanded) {
317 return { [itemIdToToggle]: true };
319 } else if (items[i].items && items[i].items.length > 0) {
320 let mapOfExpandedIds = getExpandedItemsId(
324 if (mapOfExpandedIds !== false) {
325 mapOfExpandedIds[items[i].id] = true;
326 return mapOfExpandedIds;
333 function migrateSoftwareProduct(vspId, version) {
334 return RestAPIUtil.put(`${baseUrl()}${vspId}/versions/${version.id}/heal`);
337 const SoftwareProductActionHelper = {
338 fetchFinalizedSoftwareProductList(dispatch) {
339 return fetchFinalizedSoftwareProductList().then(response =>
341 type: actionTypes.FINALIZED_SOFTWARE_PRODUCT_LIST_LOADED,
347 fetchArchivedSoftwareProductList(dispatch) {
348 return fetchArchivedSoftwareProductList().then(response =>
350 type: actionTypes.ARCHIVED_SOFTWARE_PRODUCT_LIST_LOADED,
356 fetchUploadStatus(vspId, versionId) {
358 validateStatus: function(status) {
359 return status < 400 || status === 404;
363 return RestAPIUtil.get(
364 `${baseUrl()}${vspId}/versions/${versionId}/orchestration-template-candidate/upload`,
369 loadSoftwareProductAssociatedData(dispatch) {
370 fetchSoftwareProductCategories(dispatch);
371 fetchModelList(dispatch);
372 LicenseModelActionHelper.fetchFinalizedLicenseModels(dispatch);
375 loadSoftwareProductDetailsData(
377 { licenseModelId, licensingVersion }
379 SoftwareProductActionHelper.loadSoftwareProductAssociatedData(dispatch);
380 if (licensingVersion) {
381 return loadLicensingData(dispatch, {
386 return Promise.resolve();
389 fetchSoftwareProductList(dispatch) {
390 return fetchSoftwareProductList().then(response =>
392 type: actionTypes.SOFTWARE_PRODUCT_LIST_LOADED,
398 loadSoftwareProductHeatCandidate(dispatch, { softwareProductId, version }) {
399 return RestAPIUtil.fetch(
400 `${baseUrl()}${softwareProductId}/versions/${
402 }/orchestration-template-candidate/manifest`
405 type: HeatSetupActions.MANIFEST_LOADED,
411 loadLicensingVersionsList(dispatch, { licenseModelId }) {
412 return ItemsHelper.fetchVersions({ itemId: licenseModelId }).then(
415 type: actionTypes.LOAD_LICENSING_VERSIONS_LIST,
416 licensingVersionsList: response.results
421 updateSoftwareProductHeatCandidate(
423 { softwareProductId, heatCandidate, version }
425 return updateSoftwareProductHeatCandidate(
432 processAndValidateHeatCandidate(dispatch, { softwareProductId, version }) {
433 displayTimingValidationInfo(dispatch);
434 return validateHeatCandidate(softwareProductId, version).then(
436 if (response.status === 'Success') {
437 SoftwareProductComponentsActionHelper.fetchSoftwareProductComponents(
439 { softwareProductId, version }
441 SoftwareProductActionHelper.fetchSoftwareProduct(dispatch, {
446 SoftwareProductActionHelper.fetchSoftwareProduct(dispatch, {
451 closeTimingValidationInfo(dispatch);
461 failedNotificationTitle,
463 onUploadProgress = undefined
467 type: HeatSetupActions.FILL_HEAT_SETUP_CACHE,
470 return Promise.resolve()
480 if (response.status === 'Success') {
482 type: commonActionTypes.DATA_CHANGED,
484 onboardingOrigin: response.onboardingOrigin
486 formName: forms.VENDOR_SOFTWARE_PRODUCT_DETAILS
488 switch (response.onboardingOrigin) {
489 case onboardingOriginTypes.ZIP:
490 ScreensHelper.loadScreen(dispatch, {
493 .SOFTWARE_PRODUCT_ATTACHMENTS_SETUP,
494 screenType: screenTypes.SOFTWARE_PRODUCT,
495 props: { softwareProductId, version }
498 type: actionTypes.CANDIDATE_IN_PROCESS,
502 case onboardingOriginTypes.CSAR:
503 ScreensHelper.loadScreen(dispatch, {
506 .SOFTWARE_PRODUCT_ATTACHMENTS_VALIDATION,
507 screenType: screenTypes.SOFTWARE_PRODUCT,
508 props: { softwareProductId, version }
512 closeTimingValidationInfo(dispatch);
514 response.errors !== null &&
515 Object.keys(response.errors).length !== 0
517 showWarningValidationInfo(dispatch, response.errors);
520 throw new Error(parseUploadErrorMsg(response.errors));
526 type: modalActionTypes.GLOBAL_MODAL_ERROR,
528 title: failedNotificationTitle,
531 (error.responseJSON &&
532 error.responseJSON.message) ||
533 parseUploadErrorMsg(error.responseJSON.errors)
536 closeTimingValidationInfo(dispatch)
543 { csarId, failedNotificationTitle, softwareProductId, version }
546 type: HeatSetupActions.FILL_HEAT_SETUP_CACHE,
551 .then(() => uploadVNFFile(csarId, softwareProductId, version))
553 if (response.status === 'Success') {
555 type: commonActionTypes.DATA_CHANGED,
557 onboardingOrigin: response.onboardingOrigin
559 formName: forms.VENDOR_SOFTWARE_PRODUCT_DETAILS
561 switch (response.onboardingOrigin) {
562 case onboardingOriginTypes.ZIP:
563 OnboardingActionHelper.navigateToSoftwareProductAttachmentsSetupTab(
565 { softwareProductId, version }
568 case onboardingOriginTypes.CSAR:
569 OnboardingActionHelper.navigateToSoftwareProductAttachmentsValidationTab(
571 { softwareProductId, version }
576 throw new Error(parseUploadErrorMsg(response.errors));
581 type: modalActionTypes.GLOBAL_MODAL_ERROR,
583 title: failedNotificationTitle,
591 { softwareProductId, heatCandidate, isReadOnlyMode, version }
593 let p = isReadOnlyMode
595 : SoftwareProductActionHelper.updateSoftwareProductHeatCandidate(
604 fetchOrchestrationTemplateCandidate(
610 headers: response.headers,
611 defaultFilename: 'HEAT_file.zip',
615 }, null /* do not download if data was not saved correctly*/);
618 hideUploadConfirm(dispatch) {
620 type: actionTypes.softwareProductEditor.UPLOAD_CONFIRMATION
623 updateSoftwareProduct(dispatch, { softwareProduct, version, qdata }) {
625 SoftwareProductActionHelper.updateSoftwareProductData(dispatch, {
630 type: actionTypes.SOFTWARE_PRODUCT_LIST_EDIT,
631 payload: { softwareProduct }
634 SoftwareProductActionHelper.updateSoftwareProductQuestionnaire(
637 softwareProductId: softwareProduct.id,
645 updateSoftwareProductData(dispatch, { softwareProduct, version }) {
646 return putSoftwareProduct({ softwareProduct, version });
649 updateSoftwareProductQuestionnaire(
651 { softwareProductId, qdata, version }
653 return putSoftwareProductQuestionnaire(
660 softwareProductEditorDataChanged(dispatch, { deltaData }) {
662 type: actionTypes.softwareProductEditor.DATA_CHANGED,
667 softwareProductQuestionnaireUpdate(dispatch, { data }) {
669 type: actionTypes.SOFTWARE_PRODUCT_QUESTIONNAIRE_UPDATE,
670 payload: { qdata: data }
674 softwareProductEditorVendorChanged(dispatch, { deltaData, formName }) {
675 if (deltaData.licensingVersion) {
676 return loadLicensingData(dispatch, {
677 licenseModelId: deltaData.vendorId,
678 licensingVersion: deltaData.licensingVersion
680 ValidationHelper.dataChanged(dispatch, { deltaData, formName });
681 return Promise.resolve();
683 } else if (deltaData.vendorId) {
684 ValidationHelper.dataChanged(dispatch, { deltaData, formName });
685 return SoftwareProductActionHelper.loadLicensingVersionsList(
688 licenseModelId: deltaData.vendorId
691 OnboardingActionHelper.forceBreadCrumbsUpdate(dispatch)
694 ValidationHelper.dataChanged(dispatch, { deltaData, formName });
697 type: licenseAgreementActionTypes.LICENSE_AGREEMENT_LIST_LOADED,
698 response: { results: [] }
702 type: featureGroupsActionConstants.FEATURE_GROUPS_LIST_LOADED,
703 response: { results: [] }
708 setIsValidityData(dispatch, { isValidityData }) {
710 type: actionTypes.softwareProductEditor.IS_VALIDITY_DATA_CHANGED,
715 fetchSoftwareProduct(dispatch, { softwareProductId, version }) {
717 fetchSoftwareProduct(softwareProductId, version).then(response => {
719 type: actionTypes.SOFTWARE_PRODUCT_LOADED,
724 fetchSoftwareProductQuestionnaire(softwareProductId, version).then(
726 ValidationHelper.qDataLoaded(dispatch, {
729 ? JSON.parse(response.data)
731 qschema: JSON.parse(response.schema)
733 qName: PRODUCT_QUESTIONNAIRE
740 manageSubmitAction(dispatch, { softwareProductId, version, isDirty }) {
742 const onCommit = comment => {
743 return this.performVCAction(dispatch, {
745 action: VersionControllerActionsEnum.COMMIT,
749 return this.performSubmitAction(dispatch, {
756 type: modalActionTypes.GLOBAL_MODAL_SHOW,
758 modalComponentName: modalContentMapper.COMMIT_COMMENT,
759 modalComponentProps: {
761 type: CommitModalType.COMMIT_SUBMIT
763 title: i18n('Commit & Submit')
766 return Promise.resolve(version);
768 return this.performSubmitAction(dispatch, {
774 performSubmitAction(dispatch, { softwareProductId, version }) {
775 return putSoftwareProductAction(
777 VersionControllerActionsEnum.SUBMIT,
781 return putSoftwareProductAction(
783 VersionControllerActionsEnum.CREATE_PACKAGE,
786 return ItemsHelper.checkItemStatus(dispatch, {
787 itemId: softwareProductId,
788 versionId: version.id
789 }).then(updatedVersion => {
791 type: modalActionTypes.GLOBAL_MODAL_SUCCESS,
793 title: i18n('Submit Succeeded'),
795 'This software product successfully submitted'
797 cancelButtonText: i18n('OK'),
801 versionPageActionHelper.fetchVersions(dispatch, {
802 itemType: itemTypes.SOFTWARE_PRODUCT,
803 itemId: softwareProductId
805 return Promise.resolve(updatedVersion);
811 type: modalActionTypes.GLOBAL_MODAL_ERROR,
814 modalContentMapper.SUMBIT_ERROR_RESPONSE,
815 title: i18n('Submit Failed'),
816 modalComponentProps: {
817 validationResponse: error.responseJSON
819 cancelButtonText: i18n('OK')
822 return Promise.reject(error.responseJSON);
827 performVCAction(dispatch, { softwareProductId, action, version, comment }) {
828 return MergeEditorActionHelper.analyzeSyncResult(dispatch, {
829 itemId: softwareProductId,
831 }).then(({ inMerge, isDirty, updatedVersion }) => {
833 (updatedVersion.status === versionStatus.CERTIFIED ||
834 updatedVersion.archivedStatus === itemStatus.ARCHIVED) &&
835 (action === VersionControllerActionsEnum.COMMIT ||
836 action === VersionControllerActionsEnum.SYNC)
838 versionPageActionHelper.fetchVersions(dispatch, {
839 itemType: itemTypes.SOFTWARE_PRODUCT,
840 itemId: softwareProductId
843 updatedVersion.archivedStatus === itemStatus.ARCHIVED
844 ? i18n('Item was Archived')
845 : i18n('Item version already Certified');
847 type: modalActionTypes.GLOBAL_MODAL_WARNING,
849 title: i18n('Commit error'),
851 cancelButtonText: i18n('Cancel')
854 return Promise.resolve(updatedVersion);
857 if (action === VersionControllerActionsEnum.SUBMIT) {
858 return this.manageSubmitAction(dispatch, {
864 let isCallActionValid =
865 action !== VersionControllerActionsEnum.COMMIT ||
867 if (isCallActionValid) {
868 return ItemsHelper.performVCAction({
869 itemId: softwareProductId,
874 versionPageActionHelper.fetchVersions(dispatch, {
875 itemType: itemTypes.LICENSE_MODEL,
876 itemId: softwareProductId
878 if (action === VersionControllerActionsEnum.SYNC) {
879 return MergeEditorActionHelper.analyzeSyncResult(
881 { itemId: softwareProductId, version }
882 ).then(({ updatedVersion }) => {
883 return Promise.resolve(updatedVersion);
886 return ItemsHelper.checkItemStatus(dispatch, {
887 itemId: softwareProductId,
888 versionId: version.id
894 type: modalActionTypes.GLOBAL_MODAL_ERROR,
896 title: i18n('Commit Failed'),
897 msg: i18n('There is nothing to commit')
906 toggleNavigationItems(dispatch, { items, itemIdToExpand }) {
907 let mapOfExpandedIds = getExpandedItemsId(items, itemIdToExpand);
909 type: actionTypes.TOGGLE_NAVIGATION_ITEM,
914 /** for the next verision */
915 addComponent(dispatch, { softwareProductId, version }) {
916 SoftwareProductComponentsActionHelper.clearComponentCreationData(
920 type: componentActionTypes.COMPONENT_CREATE_OPEN
923 type: modalActionTypes.GLOBAL_MODAL_SHOW,
925 modalComponentName: modalContentMapper.COMPONENT_CREATION,
926 modalComponentProps: { softwareProductId, version },
927 title: 'Create Virtual Function Component'
932 migrateSoftwareProduct(dispatch, { softwareProduct }) {
933 let { id: softwareProductId, version } = softwareProduct;
934 const newVer = version.id;
935 migrateSoftwareProduct(softwareProductId, version).then(() =>
936 ScreensHelper.loadScreen(dispatch, {
937 screen: enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE,
938 screenType: screenTypes.SOFTWARE_PRODUCT,
941 version: { id: newVer, label: newVer }
947 abortCandidateValidation(dispatch, { softwareProductId, version }) {
948 return abortValidationProcess(softwareProductId, version);
952 export default SoftwareProductActionHelper;