diff options
Diffstat (limited to 'openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments')
16 files changed, 1674 insertions, 1128 deletions
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachments.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachments.js index d942172973..4d5887be6f 100644 --- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachments.js +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachments.js @@ -13,100 +13,142 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import {connect} from 'react-redux'; +import { connect } from 'react-redux'; import i18n from 'nfvo-utils/i18n/i18n.js'; -import {actionTypes as modalActionTypes} from 'nfvo-components/modal/GlobalModalConstants.js'; +import { actionTypes as modalActionTypes } from 'nfvo-components/modal/GlobalModalConstants.js'; import SoftwareProductActionHelper from 'sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js'; import HeatSetupActionHelper from './setup/HeatSetupActionHelper.js'; import SoftwareProductAttachmentsView from './SoftwareProductAttachmentsView.jsx'; -import {errorLevels} from 'sdc-app/onboarding/softwareProduct/attachments/validation/HeatValidationConstants.js'; +import { errorLevels } from 'sdc-app/onboarding/softwareProduct/attachments/validation/HeatValidationConstants.js'; import HeatSetup from './setup/HeatSetup.js'; -import {doesHeatDataExist} from './SoftwareProductAttachmentsUtils.js'; +import { doesHeatDataExist } from './SoftwareProductAttachmentsUtils.js'; import ScreensHelper from 'sdc-app/common/helpers/ScreensHelper.js'; -import {enums, screenTypes} from 'sdc-app/onboarding/OnboardingConstants.js'; +import { enums, screenTypes } from 'sdc-app/onboarding/OnboardingConstants.js'; import SoftwareProductAttachmentsActionHelper from './SoftwareProductAttachmentsActionHelper.js'; -export const mapStateToProps = (state) => { - let { - softwareProduct: { - softwareProductEditor:{data: currentSoftwareProduct = {}}, - softwareProductAttachments: {attachmentsDetails: {activeTab}, heatSetup, heatSetupCache, heatValidation : {errorList}} - } - } = state; +export const mapStateToProps = state => { + let { + softwareProduct: { + softwareProductEditor: { data: currentSoftwareProduct = {} }, + softwareProductAttachments: { + attachmentsDetails: { activeTab }, + heatSetup, + heatSetupCache, + heatValidation: { errorList } + } + } + } = state; - let {unassigned = [], modules = []} = heatSetup; - let goToOverview = true; - if (errorList) { - for (let i = 0 ; i < errorList.length ; i++) { - if (errorList[i].level === errorLevels.ERROR) { - goToOverview = false; - } - } - } - let heatDataExist = doesHeatDataExist(heatSetup); + let { unassigned = [], modules = [] } = heatSetup; + let goToOverview = true; + if (errorList) { + for (let i = 0; i < errorList.length; i++) { + if (errorList[i].level === errorLevels.ERROR) { + goToOverview = false; + } + } + } + let heatDataExist = doesHeatDataExist(heatSetup); - let {version, onboardingOrigin} = currentSoftwareProduct; - return { - isValidationAvailable: unassigned.length === 0 && modules.length > 0, - heatSetup, - heatSetupCache, - heatDataExist, - goToOverview, - HeatSetupComponent: HeatSetup, - version, - onboardingOrigin, - activeTab, - candidateInProcess: !!currentSoftwareProduct.candidateOnboardingOrigin - }; + let { version, onboardingOrigin } = currentSoftwareProduct; + return { + isValidationAvailable: unassigned.length === 0 && modules.length > 0, + heatSetup, + heatSetupCache, + heatDataExist, + goToOverview, + HeatSetupComponent: HeatSetup, + version, + onboardingOrigin, + activeTab, + candidateInProcess: !!currentSoftwareProduct.candidateOnboardingOrigin + }; }; -export const mapActionsToProps = (dispatch, {softwareProductId, version}) => { - return { - onDownload: ({heatCandidate, isReadOnlyMode}) => SoftwareProductActionHelper.downloadHeatFile(dispatch, {softwareProductId, heatCandidate, isReadOnlyMode, version}), - onUpload: (formData) => dispatch({ - type: modalActionTypes.GLOBAL_MODAL_WARNING, - data:{ - msg: i18n('Upload will erase existing data. Do you want to continue?'), - confirmationButtonText: i18n('Continue'), - title: i18n('WARNING'), +export const mapActionsToProps = (dispatch, { softwareProductId, version }) => { + return { + onDownload: ({ heatCandidate, isReadOnlyMode }) => + SoftwareProductActionHelper.downloadHeatFile(dispatch, { + softwareProductId, + heatCandidate, + isReadOnlyMode, + version + }), + onUpload: formData => + dispatch({ + type: modalActionTypes.GLOBAL_MODAL_WARNING, + data: { + msg: i18n( + 'Upload will erase existing data. Do you want to continue?' + ), + confirmationButtonText: i18n('Continue'), + title: i18n('WARNING'), - onConfirmed: ()=>SoftwareProductActionHelper.uploadFile(dispatch, { - softwareProductId, - formData, - failedNotificationTitle: i18n('Upload validation failed'), - version - }) - } - }), - onUploadAbort: () => { - SoftwareProductActionHelper.abortCandidateValidation(dispatch, {softwareProductId, version}) - .then(()=>{ - ScreensHelper.loadScreen(dispatch, { - screen: enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE, screenType: screenTypes.SOFTWARE_PRODUCT, - props: {softwareProductId, version} - }); - }); - }, - onInvalidFileUpload: () => dispatch({ - type: modalActionTypes.GLOBAL_MODAL_ERROR, - data: { - title: i18n('Upload Failed'), - confirmationButtonText: i18n('Continue'), - msg: i18n('no zip or csar file was uploaded or expected file doesn\'t exist') - } - }), - onSave: (heatCandidate) => SoftwareProductActionHelper.updateSoftwareProductHeatCandidate(dispatch, {softwareProductId, heatCandidate, version}), - onGoToOverview: () => ScreensHelper.loadScreen(dispatch, { - screen: enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE, screenType: screenTypes.SOFTWARE_PRODUCT, - props: {softwareProductId, version} - }), - onProcessAndValidate: ({heatData, heatDataCache, isReadOnlyMode}) => { - return HeatSetupActionHelper.processAndValidateHeat(dispatch, - {softwareProductId, heatData, heatDataCache, isReadOnlyMode, version}); - }, - setActiveTab: ({activeTab}) => SoftwareProductAttachmentsActionHelper.setActiveTab(dispatch, {activeTab}) - - }; + onConfirmed: () => + SoftwareProductActionHelper.uploadFile(dispatch, { + softwareProductId, + formData, + failedNotificationTitle: i18n( + 'Upload validation failed' + ), + version + }) + } + }), + onUploadAbort: () => { + SoftwareProductActionHelper.abortCandidateValidation(dispatch, { + softwareProductId, + version + }).then(() => { + ScreensHelper.loadScreen(dispatch, { + screen: enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE, + screenType: screenTypes.SOFTWARE_PRODUCT, + props: { softwareProductId, version } + }); + }); + }, + onInvalidFileUpload: () => + dispatch({ + type: modalActionTypes.GLOBAL_MODAL_ERROR, + data: { + title: i18n('Upload Failed'), + confirmationButtonText: i18n('Continue'), + msg: i18n( + "no zip or csar file was uploaded or expected file doesn't exist" + ) + } + }), + onSave: heatCandidate => + SoftwareProductActionHelper.updateSoftwareProductHeatCandidate( + dispatch, + { + softwareProductId, + heatCandidate, + version + } + ), + onGoToOverview: () => + ScreensHelper.loadScreen(dispatch, { + screen: enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE, + screenType: screenTypes.SOFTWARE_PRODUCT, + props: { softwareProductId, version } + }), + onProcessAndValidate: ({ heatData, heatDataCache, isReadOnlyMode }) => { + return HeatSetupActionHelper.processAndValidateHeat(dispatch, { + softwareProductId, + heatData, + heatDataCache, + isReadOnlyMode, + version + }); + }, + setActiveTab: ({ activeTab }) => + SoftwareProductAttachmentsActionHelper.setActiveTab(dispatch, { + activeTab + }) + }; }; -export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(SoftwareProductAttachmentsView); +export default connect(mapStateToProps, mapActionsToProps, null, { + withRef: true +})(SoftwareProductAttachmentsView); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsActionHelper.js index ae4a615a40..2d35bc27ee 100644 --- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsActionHelper.js +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsActionHelper.js @@ -14,13 +14,13 @@ * permissions and limitations under the License. */ -import {actionTypes} from './SoftwareProductAttachmentsConstants'; +import { actionTypes } from './SoftwareProductAttachmentsConstants'; export default { - setActiveTab(dispatch, {activeTab}) { - dispatch({ - type: actionTypes.SET_ACTIVE_TAB, - activeTab - }); - } + setActiveTab(dispatch, { activeTab }) { + dispatch({ + type: actionTypes.SET_ACTIVE_TAB, + activeTab + }); + } }; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsConstants.js index 67265909d7..aff0a3dbdf 100644 --- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsConstants.js +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsConstants.js @@ -16,10 +16,10 @@ import keyMirror from 'nfvo-utils/KeyMirror.js'; export const tabsMapping = { - SETUP: 1, - VALIDATION: 2 + SETUP: 1, + VALIDATION: 2 }; export const actionTypes = keyMirror({ - SET_ACTIVE_TAB: null + SET_ACTIVE_TAB: null }); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsReducer.js index 5f6538ab7e..5d9a37f98a 100644 --- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsReducer.js +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsReducer.js @@ -13,13 +13,13 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import {actionTypes} from './SoftwareProductAttachmentsConstants.js'; +import { actionTypes } from './SoftwareProductAttachmentsConstants.js'; export default (state = [], action) => { - switch (action.type) { - case actionTypes.SET_ACTIVE_TAB: - return {activeTab: action.activeTab}; - default: - return state; - } + switch (action.type) { + case actionTypes.SET_ACTIVE_TAB: + return { activeTab: action.activeTab }; + default: + return state; + } }; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsUtils.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsUtils.js index 2e76b11630..f4e77229e9 100644 --- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsUtils.js +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsUtils.js @@ -15,11 +15,11 @@ */ export function doesHeatDataExist(heatData) { - let result = false; - for (let key of Object.keys(heatData)) { - if(heatData[key].length > 0) { - result = true; - } - } - return result; + let result = false; + for (let key of Object.keys(heatData)) { + if (heatData[key].length > 0) { + result = true; + } + } + return result; } diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsView.jsx index 2a849f376e..2007493d48 100644 --- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsView.jsx +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsView.jsx @@ -13,140 +13,208 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import React, {Component} from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; import accept from 'attr-accept'; -import {SVGIcon, Tab, Tabs} from 'sdc-ui/lib/react'; -import {tabsMapping} from './SoftwareProductAttachmentsConstants.js'; +import { SVGIcon, Tab, Tabs } from 'sdc-ui/lib/react'; +import { tabsMapping } from './SoftwareProductAttachmentsConstants.js'; import i18n from 'nfvo-utils/i18n/i18n.js'; import HeatValidation from './validation/HeatValidation.js'; -import {onboardingOriginTypes} from 'sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js'; +import { onboardingOriginTypes } from 'sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js'; import Button from 'sdc-ui/lib/react/Button.js'; class HeatScreenView extends Component { + static propTypes = { + isValidationAvailable: PropTypes.bool, + goToOverview: PropTypes.bool, + setActiveTab: PropTypes.func + }; - static propTypes = { - isValidationAvailable: PropTypes.bool, - goToOverview: PropTypes.bool, - setActiveTab: PropTypes.func - }; + componentDidMount() { + if (!this.props.goToOverview && this.props.candidateInProcess) { + this.props.setActiveTab({ activeTab: tabsMapping.VALIDATION }); + } + } - componentDidMount() { - if (!this.props.goToOverview && this.props.candidateInProcess) { - this.props.setActiveTab({activeTab: tabsMapping.VALIDATION}); - } - } + render() { + let { + isValidationAvailable, + isReadOnlyMode, + heatDataExist, + onDownload, + softwareProductId, + onProcessAndValidate, + onUploadAbort, + candidateInProcess, + heatSetup, + HeatSetupComponent, + onGoToOverview, + version, + onboardingOrigin, + activeTab, + setActiveTab, + ...other + } = this.props; - render() { - let {isValidationAvailable, isReadOnlyMode, heatDataExist, onDownload, softwareProductId, onProcessAndValidate, onUploadAbort, - candidateInProcess, heatSetup, HeatSetupComponent, onGoToOverview, version, onboardingOrigin, activeTab, setActiveTab, ...other} = this.props; + return ( + <div className="vsp-attachments-view"> + <div className="attachments-view-controllers"> + {activeTab === tabsMapping.SETUP && + candidateInProcess && ( + <Button + data-test-id="proceed-to-validation-btn" + disabled={!isValidationAvailable} + className="proceed-to-validation-btn" + onClick={() => this.validate()}> + {i18n('PROCEED TO VALIDATION')} + </Button> + )} + {candidateInProcess && ( + <SVGIcon + onClick={onUploadAbort} + name="close" + className="icon-component abort-btn" + label={i18n('ABORT')} + labelPosition="right" + color="secondary" + data-test-id="abort-btn" + /> + )} + {activeTab === tabsMapping.VALIDATION && + softwareProductId && ( + <Button + btnType="outline" + data-test-id="go-to-overview" + disabled={this.props.goToOverview !== true} + className="go-to-overview-btn" + onClick={ + this.props.goToOverview + ? () => onGoToOverview({ version }) + : undefined + }> + {i18n('GO TO OVERVIEW')} + </Button> + )} + <div className="separator" /> + <SVGIcon + disabled={heatDataExist ? false : true} + name="download" + className="icon-component" + color="dark-gray" + onClick={ + heatDataExist + ? () => + onDownload({ + heatCandidate: heatSetup, + isReadOnlyMode: + isReadOnlyMode || + !candidateInProcess, + version + }) + : undefined + } + data-test-id="download-heat" + /> + <SVGIcon + name="upload" + className="icon-component" + color="dark-gray" + disabled={isReadOnlyMode || candidateInProcess} + onClick={ + isReadOnlyMode + ? undefined + : evt => + this.refs.hiddenImportFileInput.click(evt) + } + data-test-id="upload-heat" + /> + <input + ref="hiddenImportFileInput" + type="file" + name="fileInput" + accept=".zip, .csar" + onChange={evt => this.handleImport(evt)} + /> + </div> + <Tabs + className="attachments-tabs" + type="header" + activeTab={activeTab} + onTabClick={key => this.handleTabPress(key)}> + <Tab + tabId={tabsMapping.SETUP} + title="Setup" + disabled={ + onboardingOrigin === onboardingOriginTypes.CSAR + }> + <HeatSetupComponent + heatDataExist={heatDataExist} + changeAttachmentsTab={tab => + setActiveTab({ activeTab: tab }) + } + onProcessAndValidate={onProcessAndValidate} + softwareProductId={softwareProductId} + isReadOnlyMode={isReadOnlyMode} + version={version} + /> + </Tab> + <Tab + tabId={tabsMapping.VALIDATION} + title="Validation" + disabled={!isValidationAvailable || candidateInProcess}> + <HeatValidation {...other} /> + </Tab> + </Tabs> + </div> + ); + } - return ( - <div className='vsp-attachments-view'> - <div className='attachments-view-controllers'> - {(activeTab === tabsMapping.SETUP) && candidateInProcess && - <Button - data-test-id='proceed-to-validation-btn' - disabled={!isValidationAvailable} - className='proceed-to-validation-btn' - onClick={()=>this.validate()}>{i18n('PROCEED TO VALIDATION')}</Button> - } - {candidateInProcess && <SVGIcon - onClick={onUploadAbort} - name='close' - className='icon-component abort-btn' - label={i18n('ABORT')} - labelPosition='right' - color='secondary' - data-test-id='abort-btn'/> - } - {(activeTab === tabsMapping.VALIDATION && softwareProductId) && - <Button btnType='outline' - data-test-id='go-to-overview' - disabled={this.props.goToOverview !== true} - className='go-to-overview-btn' - onClick={this.props.goToOverview ? () => onGoToOverview({version}) : undefined}>{i18n('GO TO OVERVIEW')}</Button>} - <div className='separator'></div> - <SVGIcon - disabled={heatDataExist ? false : true} - name='download' - className='icon-component' - color='dark-gray' - onClick={heatDataExist ? () => onDownload({heatCandidate: heatSetup, isReadOnlyMode: isReadOnlyMode || !candidateInProcess, version}) : undefined} - data-test-id='download-heat'/> - <SVGIcon - name='upload' - className='icon-component' - color='dark-gray' - disabled={isReadOnlyMode || candidateInProcess} - onClick={isReadOnlyMode ? undefined : evt => this.refs.hiddenImportFileInput.click(evt)} - data-test-id='upload-heat'/> - <input - ref='hiddenImportFileInput' - type='file' - name='fileInput' - accept='.zip, .csar' - onChange={evt => this.handleImport(evt)}/> - </div> - <Tabs - className='attachments-tabs' - type='header' - activeTab={activeTab} - onTabClick={key => this.handleTabPress(key)}> - <Tab tabId={tabsMapping.SETUP} title='Setup' disabled={onboardingOrigin === onboardingOriginTypes.CSAR}> - <HeatSetupComponent - heatDataExist={heatDataExist} - changeAttachmentsTab={tab => setActiveTab({activeTab: tab})} - onProcessAndValidate={onProcessAndValidate} - softwareProductId={softwareProductId} - isReadOnlyMode={isReadOnlyMode} - version={version}/> - </Tab> - <Tab tabId={tabsMapping.VALIDATION} title='Validation' disabled={!isValidationAvailable || candidateInProcess}> - <HeatValidation {...other}/> - </Tab> - </Tabs> - </div> - ); - } - - handleTabPress(key) { - let {setActiveTab} = this.props; - switch (key) { - case tabsMapping.VALIDATION: - setActiveTab({activeTab: tabsMapping.VALIDATION}); - return; - case tabsMapping.SETUP: - setActiveTab({activeTab: tabsMapping.SETUP}); - return; - } - } - - handleImport(evt) { - evt.preventDefault(); - let file = this.refs.hiddenImportFileInput.files[0]; - if(! (file && file.size && accept(file, ['.zip', '.csar'])) ) { - this.props.onInvalidFileUpload(); - return; - } - let {version} = this.props; - let formData = new FormData(); - formData.append('upload', file); - this.refs.hiddenImportFileInput.value = ''; - this.props.onUpload(formData, version); - } - validate() { - let {heatSetup, heatSetupCache, onProcessAndValidate, isReadOnlyMode, version, setActiveTab} = this.props; - onProcessAndValidate({heatData: heatSetup, heatDataCache: heatSetupCache, isReadOnlyMode, version}).then( - () => setActiveTab({activeTab: tabsMapping.VALIDATION}) - ); - } - save() { - return this.props.onboardingOrigin === onboardingOriginTypes.ZIP ? - this.props.onSave(this.props.heatSetup, this.props.version) : - Promise.resolve(); - } + handleTabPress(key) { + let { setActiveTab } = this.props; + switch (key) { + case tabsMapping.VALIDATION: + setActiveTab({ activeTab: tabsMapping.VALIDATION }); + return; + case tabsMapping.SETUP: + setActiveTab({ activeTab: tabsMapping.SETUP }); + return; + } + } + handleImport(evt) { + evt.preventDefault(); + let file = this.refs.hiddenImportFileInput.files[0]; + if (!(file && file.size && accept(file, ['.zip', '.csar']))) { + this.props.onInvalidFileUpload(); + return; + } + let { version } = this.props; + let formData = new FormData(); + formData.append('upload', file); + this.refs.hiddenImportFileInput.value = ''; + this.props.onUpload(formData, version); + } + validate() { + let { + heatSetup, + heatSetupCache, + onProcessAndValidate, + isReadOnlyMode, + version, + setActiveTab + } = this.props; + onProcessAndValidate({ + heatData: heatSetup, + heatDataCache: heatSetupCache, + isReadOnlyMode, + version + }).then(() => setActiveTab({ activeTab: tabsMapping.VALIDATION })); + } + save() { + return this.props.onboardingOrigin === onboardingOriginTypes.ZIP + ? this.props.onSave(this.props.heatSetup, this.props.version) + : Promise.resolve(); + } } export default HeatScreenView; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/setup/HeatSetup.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/setup/HeatSetup.js index 4c3adc6a7d..d75d464f9e 100644 --- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/setup/HeatSetup.js +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/setup/HeatSetup.js @@ -13,50 +13,65 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import {connect} from 'react-redux'; +import { connect } from 'react-redux'; import HeatSetupView from './HeatSetupView.jsx'; import HeatSetupActionHelper from './HeatSetupActionHelper.js'; - const BASE = true; function baseExists(modules) { - for (let i in modules) { - if (modules[i].isBase) { - return true; - } - } - return false; + for (let i in modules) { + if (modules[i].isBase) { + return true; + } + } + return false; } -export const mapStateToProps = ({softwareProduct: {softwareProductAttachments: {heatSetup, heatSetupCache}}}) => { - let {modules = [], unassigned = [], artifacts = [], nested = []} = heatSetup; - let isBaseExist = baseExists(modules); +export const mapStateToProps = ({ + softwareProduct: { + softwareProductAttachments: { heatSetup, heatSetupCache } + } +}) => { + let { + modules = [], + unassigned = [], + artifacts = [], + nested = [] + } = heatSetup; + let isBaseExist = baseExists(modules); - return { - heatSetupCache, - modules, - unassigned, - artifacts, - nested, - isBaseExist - }; + return { + heatSetupCache, + modules, + unassigned, + artifacts, + nested, + isBaseExist + }; }; export const mapActionsToProps = (dispatch, {}) => { - return { - onModuleRename: (oldName, newName) => HeatSetupActionHelper.renameModule(dispatch, {oldName, newName}), - onModuleAdd: () => HeatSetupActionHelper.addModule(dispatch, !BASE), - onBaseAdd: () => HeatSetupActionHelper.addModule(dispatch, BASE), - onModuleDelete: moduleName => HeatSetupActionHelper.deleteModule(dispatch, moduleName), - onModuleFileTypeChange: ({module, value, type}) => HeatSetupActionHelper.changeModuleFileType(dispatch, { - module, - value, - type - }), - onArtifactListChange: artifacts => HeatSetupActionHelper.changeArtifactList(dispatch, artifacts), - onAddAllUnassigned: () => HeatSetupActionHelper.addAllUnassignedFilesToArtifacts(dispatch) - }; + return { + onModuleRename: (oldName, newName) => + HeatSetupActionHelper.renameModule(dispatch, { oldName, newName }), + onModuleAdd: () => HeatSetupActionHelper.addModule(dispatch, !BASE), + onBaseAdd: () => HeatSetupActionHelper.addModule(dispatch, BASE), + onModuleDelete: moduleName => + HeatSetupActionHelper.deleteModule(dispatch, moduleName), + onModuleFileTypeChange: ({ module, value, type }) => + HeatSetupActionHelper.changeModuleFileType(dispatch, { + module, + value, + type + }), + onArtifactListChange: artifacts => + HeatSetupActionHelper.changeArtifactList(dispatch, artifacts), + onAddAllUnassigned: () => + HeatSetupActionHelper.addAllUnassignedFilesToArtifacts(dispatch) + }; }; -export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(HeatSetupView); +export default connect(mapStateToProps, mapActionsToProps, null, { + withRef: true +})(HeatSetupView); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/setup/HeatSetupActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/setup/HeatSetupActionHelper.js index 87953bb8aa..05ac408fbb 100644 --- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/setup/HeatSetupActionHelper.js +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/setup/HeatSetupActionHelper.js @@ -13,7 +13,7 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import {actionTypes} from './HeatSetupConstants.js'; +import { actionTypes } from './HeatSetupConstants.js'; import isEqual from 'lodash/isEqual.js'; import cloneDeep from 'lodash/cloneDeep.js'; import SoftwareProductActionHelper from 'sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js'; @@ -21,46 +21,72 @@ import SoftwareProductActionHelper from 'sdc-app/onboarding/softwareProduct/Soft // import {actionTypes as modalActionTypes} from 'nfvo-components/modal/GlobalModalConstants.js'; export default { + addModule(dispatch, isBase) { + dispatch({ type: actionTypes.ADD_MODULE, data: { isBase } }); + }, - addModule(dispatch, isBase){ - dispatch({type: actionTypes.ADD_MODULE, data: {isBase}}); - }, + deleteModule(dispatch, moduleName) { + dispatch({ type: actionTypes.REMOVE_MODULE, data: { moduleName } }); + }, - deleteModule(dispatch, moduleName){ - dispatch({type: actionTypes.REMOVE_MODULE, data: {moduleName}}); - }, + renameModule(dispatch, { oldName, newName }) { + dispatch({ + type: actionTypes.RENAME_MODULE, + data: { oldName, newName } + }); + }, - renameModule(dispatch, {oldName, newName}){ - dispatch({type: actionTypes.RENAME_MODULE, data: {oldName, newName}}); - }, + changeModuleFileType(dispatch, { module, value, type }) { + if (!value) { + value = { value: '' }; + } + dispatch({ + type: actionTypes.FILE_ASSIGN_CHANGED, + data: { module, value, type } + }); + }, - changeModuleFileType(dispatch, {module, value, type}){ - if (!value) { - value = {value: ''}; - } - dispatch({type: actionTypes.FILE_ASSIGN_CHANGED, data: {module, value, type}}); - }, + changeArtifactList(dispatch, artifacts) { + dispatch({ + type: actionTypes.ARTIFACT_LIST_CHANGE, + data: { artifacts: artifacts.map(artifact => artifact.value) } + }); + }, - changeArtifactList(dispatch, artifacts){ - dispatch({type: actionTypes.ARTIFACT_LIST_CHANGE, data: {artifacts: artifacts.map(artifact => artifact.value)}}); - }, + processAndValidateHeat( + dispatch, + { softwareProductId, heatData, heatDataCache, isReadOnlyMode, version } + ) { + return isEqual({ ...heatData, softwareProductId }, heatDataCache) || + isReadOnlyMode + ? Promise.resolve() + : SoftwareProductActionHelper.updateSoftwareProductHeatCandidate( + dispatch, + { softwareProductId, heatCandidate: heatData, version } + ) + .then(() => + SoftwareProductActionHelper.processAndValidateHeatCandidate( + dispatch, + { softwareProductId, version } + ) + ) + .then(() => + dispatch({ + type: actionTypes.FILL_HEAT_SETUP_CACHE, + payload: { ...cloneDeep(heatData), softwareProductId } + }) + ); + }, - processAndValidateHeat(dispatch, {softwareProductId, heatData, heatDataCache, isReadOnlyMode, version}){ - return (isEqual({...heatData, softwareProductId}, heatDataCache) || isReadOnlyMode) ? Promise.resolve() : - SoftwareProductActionHelper.updateSoftwareProductHeatCandidate(dispatch, {softwareProductId, heatCandidate: heatData, version}) - .then(() => SoftwareProductActionHelper.processAndValidateHeatCandidate(dispatch, {softwareProductId, version})) - .then(() => dispatch({type: actionTypes.FILL_HEAT_SETUP_CACHE, payload: {...cloneDeep(heatData), softwareProductId}})); - }, + addAllUnassignedFilesToArtifacts(dispatch) { + dispatch({ type: actionTypes.ADD_ALL_UNASSIGNED_TO_ARTIFACTS }); + }, - addAllUnassignedFilesToArtifacts(dispatch){ - dispatch({type: actionTypes.ADD_ALL_UNASSIGNED_TO_ARTIFACTS}); - }, + heatSetupLeaveConfirmation() { + return Promise.resolve(); + } - heatSetupLeaveConfirmation() { - return Promise.resolve(); - } - - /*heatSetupLeaveConfirmation(dispatch, {softwareProductId, heatSetup, heatSetupCache}) { + /*heatSetupLeaveConfirmation(dispatch, {softwareProductId, heatSetup, heatSetupCache}) { return new Promise((resolve, reject) => { if (isEqual({...heatSetup, softwareProductId}, heatSetupCache)) { resolve(); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/setup/HeatSetupConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/setup/HeatSetupConstants.js index 2d6bd574a7..ff53fad27f 100644 --- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/setup/HeatSetupConstants.js +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/setup/HeatSetupConstants.js @@ -16,27 +16,25 @@ import keyMirror from 'nfvo-utils/KeyMirror.js'; export const actionTypes = keyMirror({ + ARTIFACT_LIST_CHANGE: null, + ADD_ALL_UNASSIGNED_TO_ARTIFACTS: null, + ADD_ALL_ARTIFACTS_TO_UNASSIGNED: null, - ARTIFACT_LIST_CHANGE: null, - ADD_ALL_UNASSIGNED_TO_ARTIFACTS: null, - ADD_ALL_ARTIFACTS_TO_UNASSIGNED: null, + ADD_MODULE: null, + REMOVE_MODULE: null, + RENAME_MODULE: null, + FILL_HEAT_SETUP_CACHE: null, + FILE_ASSIGN_CHANGED: null, - ADD_MODULE: null, - REMOVE_MODULE: null, - RENAME_MODULE: null, - FILL_HEAT_SETUP_CACHE: null, - FILE_ASSIGN_CHANGED: null, - - MANIFEST_LOADED: null, - - GO_TO_VALIDATION: null, - IN_VALIDATION: null + MANIFEST_LOADED: null, + GO_TO_VALIDATION: null, + IN_VALIDATION: null }); export const fileTypes = { - YAML: {label: 'yaml', regex: /(yaml|yml)/g}, - ENV: {label: 'env', regex: /env/g}, - VOL: {label: 'vol', regex: /(yaml|yml)/g}, - VOL_ENV: {label: 'volEnv', regex: /env/g} + YAML: { label: 'yaml', regex: /(yaml|yml)/g }, + ENV: { label: 'env', regex: /env/g }, + VOL: { label: 'vol', regex: /(yaml|yml)/g }, + VOL_ENV: { label: 'volEnv', regex: /env/g } }; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/setup/HeatSetupReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/setup/HeatSetupReducer.js index f49339ce35..8840a11c3e 100644 --- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/setup/HeatSetupReducer.js +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/setup/HeatSetupReducer.js @@ -13,112 +13,163 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import {actionTypes} from './HeatSetupConstants.js'; +import { actionTypes } from './HeatSetupConstants.js'; import differenceWith from 'lodash/differenceWith.js'; - const emptyModule = (isBase, currentLength) => ({ - name: `${isBase ? 'base_' : 'module_'}${currentLength + 1}`, - isBase: isBase + name: `${isBase ? 'base_' : 'module_'}${currentLength + 1}`, + isBase: isBase }); -function syncUnassignedFilesWithArtifactsChanges(unassigned, artifacts, oldArtifacts) { - if (artifacts.length > oldArtifacts.length) { - return differenceWith(unassigned, artifacts, (unassignedFile, artifact) => unassignedFile === artifact); - } - else { - const removedArtifact = differenceWith(oldArtifacts, artifacts, (oldArtifact, artifact) => artifact === oldArtifact); - return [...unassigned, removedArtifact[0]]; - } +function syncUnassignedFilesWithArtifactsChanges( + unassigned, + artifacts, + oldArtifacts +) { + if (artifacts.length > oldArtifacts.length) { + return differenceWith( + unassigned, + artifacts, + (unassignedFile, artifact) => unassignedFile === artifact + ); + } else { + const removedArtifact = differenceWith( + oldArtifacts, + artifacts, + (oldArtifact, artifact) => artifact === oldArtifact + ); + return [...unassigned, removedArtifact[0]]; + } } function findModuleIndexByName(modules, name) { - return modules.findIndex(module => module.name === name); + return modules.findIndex(module => module.name === name); } -function addDeletedModuleFilesToUnassigned(unassigned, deletedModule){ - let files = []; - for(let i in deletedModule){ - if (deletedModule.hasOwnProperty(i)) { - if (typeof deletedModule[i] === 'string' && deletedModule[i] && i !== 'name') { - files.push(deletedModule[i]); - } - } - } +function addDeletedModuleFilesToUnassigned(unassigned, deletedModule) { + let files = []; + for (let i in deletedModule) { + if (deletedModule.hasOwnProperty(i)) { + if ( + typeof deletedModule[i] === 'string' && + deletedModule[i] && + i !== 'name' + ) { + files.push(deletedModule[i]); + } + } + } - return unassigned.concat(files); + return unassigned.concat(files); } export default (state = {}, action) => { - switch (action.type) { - case actionTypes.MANIFEST_LOADED: - return { - ...state, - ...action.response, - modules: action.response.modules.map(module => ({...module, name: module.name || module.yaml.substring(0, module.yaml.lastIndexOf('.'))})) - }; - case actionTypes.ARTIFACT_LIST_CHANGE: - return { - ...state, - artifacts: action.data.artifacts, - unassigned: syncUnassignedFilesWithArtifactsChanges(state.unassigned, action.data.artifacts, state.artifacts) - }; - case actionTypes.ADD_ALL_UNASSIGNED_TO_ARTIFACTS: - return { - ...state, - artifacts: [...state.artifacts,...state.unassigned], - unassigned: [] - }; - case actionTypes.ADD_ALL_ARTIFACTS_TO_UNASSIGNED: - return { - ...state, - artifacts: [], - unassigned: [...state.unassigned, ...state.artifacts] - }; - case actionTypes.ADD_MODULE: - return { - ...state, - modules: state.modules.concat({...emptyModule(action.data.isBase, state.modules.length)}) - }; - case actionTypes.REMOVE_MODULE: - const moduleIndexToDelete = findModuleIndexByName(state.modules, action.data.moduleName); - let unassigned = addDeletedModuleFilesToUnassigned(state.unassigned, state.modules[moduleIndexToDelete]); - return { - ...state, - unassigned, - modules: [...state.modules.slice(0, moduleIndexToDelete), ...state.modules.slice(moduleIndexToDelete + 1)] - }; - case actionTypes.RENAME_MODULE: - const indexToRename = findModuleIndexByName(state.modules, action.data.oldName); - let moduleToRename = state.modules[indexToRename]; - moduleToRename.name = action.data.newName; - return { - ...state, - modules: [...state.modules.slice(0, indexToRename), moduleToRename, ...state.modules.slice(indexToRename + 1)] - }; - case actionTypes.FILE_ASSIGN_CHANGED: - let {module, value:{value}, type} = action.data; - const moduleIndexToModify = findModuleIndexByName(state.modules, module.name); - let moduleToModify = state.modules[moduleIndexToModify]; - let dumpedFile = moduleToModify[type]; - if (dumpedFile !== value) { - if(value) { - moduleToModify[type] = value; - } - else { - delete moduleToModify[type]; - } - const newUnassignedList = dumpedFile ? [...state.unassigned.filter(file => file !== value), dumpedFile] : state.unassigned.filter(file => file !== value); - return { - ...state, - modules: [...state.modules.slice(0, moduleIndexToModify), moduleToModify, ...state.modules.slice(moduleIndexToModify + 1)], - unassigned: newUnassignedList - }; - } - else { - return state; - } - default: - return state; - } + switch (action.type) { + case actionTypes.MANIFEST_LOADED: + return { + ...state, + ...action.response, + modules: action.response.modules.map(module => ({ + ...module, + name: + module.name || + module.yaml.substring(0, module.yaml.lastIndexOf('.')) + })) + }; + case actionTypes.ARTIFACT_LIST_CHANGE: + return { + ...state, + artifacts: action.data.artifacts, + unassigned: syncUnassignedFilesWithArtifactsChanges( + state.unassigned, + action.data.artifacts, + state.artifacts + ) + }; + case actionTypes.ADD_ALL_UNASSIGNED_TO_ARTIFACTS: + return { + ...state, + artifacts: [...state.artifacts, ...state.unassigned], + unassigned: [] + }; + case actionTypes.ADD_ALL_ARTIFACTS_TO_UNASSIGNED: + return { + ...state, + artifacts: [], + unassigned: [...state.unassigned, ...state.artifacts] + }; + case actionTypes.ADD_MODULE: + return { + ...state, + modules: state.modules.concat({ + ...emptyModule(action.data.isBase, state.modules.length) + }) + }; + case actionTypes.REMOVE_MODULE: + const moduleIndexToDelete = findModuleIndexByName( + state.modules, + action.data.moduleName + ); + let unassigned = addDeletedModuleFilesToUnassigned( + state.unassigned, + state.modules[moduleIndexToDelete] + ); + return { + ...state, + unassigned, + modules: [ + ...state.modules.slice(0, moduleIndexToDelete), + ...state.modules.slice(moduleIndexToDelete + 1) + ] + }; + case actionTypes.RENAME_MODULE: + const indexToRename = findModuleIndexByName( + state.modules, + action.data.oldName + ); + let moduleToRename = state.modules[indexToRename]; + moduleToRename.name = action.data.newName; + return { + ...state, + modules: [ + ...state.modules.slice(0, indexToRename), + moduleToRename, + ...state.modules.slice(indexToRename + 1) + ] + }; + case actionTypes.FILE_ASSIGN_CHANGED: + let { module, value: { value }, type } = action.data; + const moduleIndexToModify = findModuleIndexByName( + state.modules, + module.name + ); + let moduleToModify = state.modules[moduleIndexToModify]; + let dumpedFile = moduleToModify[type]; + if (dumpedFile !== value) { + if (value) { + moduleToModify[type] = value; + } else { + delete moduleToModify[type]; + } + const newUnassignedList = dumpedFile + ? [ + ...state.unassigned.filter(file => file !== value), + dumpedFile + ] + : state.unassigned.filter(file => file !== value); + return { + ...state, + modules: [ + ...state.modules.slice(0, moduleIndexToModify), + moduleToModify, + ...state.modules.slice(moduleIndexToModify + 1) + ], + unassigned: newUnassignedList + }; + } else { + return state; + } + default: + return state; + } }; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/setup/HeatSetupView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/setup/HeatSetupView.jsx index ce6d5260d7..1d4efd9104 100644 --- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/setup/HeatSetupView.jsx +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/setup/HeatSetupView.jsx @@ -13,7 +13,7 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import React, {Component} from 'react'; +import React, { Component } from 'react'; import Button from 'sdc-ui/lib/react/Button.js'; import Tooltip from 'react-bootstrap/lib/Tooltip.js'; import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger.js'; @@ -21,307 +21,476 @@ import FormControl from 'react-bootstrap/lib/FormControl.js'; import i18n from 'nfvo-utils/i18n/i18n.js'; import SelectInput from 'nfvo-components/input/SelectInput.jsx'; import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js'; -import {fileTypes} from './HeatSetupConstants.js'; -import {tabsMapping} from '../SoftwareProductAttachmentsConstants.js'; -import {sortable} from 'react-sortable'; +import { fileTypes } from './HeatSetupConstants.js'; +import { tabsMapping } from '../SoftwareProductAttachmentsConstants.js'; +import { sortable } from 'react-sortable'; class ListItem extends Component { - - render() { - return ( - <li {...this.props}>{this.props.children}</li> - ); - } + render() { + return <li {...this.props}>{this.props.children}</li>; + } } - const SortableListItem = sortable(ListItem); class SortableModuleFileList extends Component { - - state = { - draggingIndex: null, - data: this.props.modules - }; - - - componentWillReceiveProps(nextProps) { - this.setState({data: nextProps.modules}); - } - - render() { - - let {unassigned, onModuleRename, onModuleDelete, onModuleAdd, onBaseAdd, onModuleFileTypeChange, isBaseExist, isReadOnlyMode} = this.props; - const childProps = module => ({ - module, - onModuleRename, - onModuleDelete, - onModuleFileTypeChange: (value, type) => onModuleFileTypeChange({module, value, type}), - files: unassigned - }); - let listItems = this.state.data.map(function (item, i) { - return ( - <SortableListItem - key={i} - updateState={data => this.setState(data)} - items={this.state.data} - draggingIndex={this.state.draggingIndex} - sortId={i} - outline='list'><ModuleFile {...childProps(item)} isReadOnlyMode={this.props.isReadOnlyMode} /></SortableListItem> - ); - }, this); - - return ( - <div className={`modules-list-wrapper ${(listItems.length > 0) ? 'modules-list-wrapper-divider' : ''}`}> - <div className='modules-list-header'> - {!isBaseExist && <div><Button btnType='link' onClick={onBaseAdd} disabled={isReadOnlyMode || unassigned.length === 0}>{i18n('Add Base')}</Button></div>} - <div><Button btnType='link' onClick={onModuleAdd} disabled={isReadOnlyMode || unassigned.length === 0}>{i18n('Add Module')}</Button></div> - </div> - {(listItems.length > 0) && <ul>{listItems}</ul>} - </div> - ); - } + state = { + draggingIndex: null, + data: this.props.modules + }; + + componentWillReceiveProps(nextProps) { + this.setState({ data: nextProps.modules }); + } + + render() { + let { + unassigned, + onModuleRename, + onModuleDelete, + onModuleAdd, + onBaseAdd, + onModuleFileTypeChange, + isBaseExist, + isReadOnlyMode + } = this.props; + const childProps = module => ({ + module, + onModuleRename, + onModuleDelete, + onModuleFileTypeChange: (value, type) => + onModuleFileTypeChange({ module, value, type }), + files: unassigned + }); + let listItems = this.state.data.map(function(item, i) { + return ( + <SortableListItem + key={i} + updateState={data => this.setState(data)} + items={this.state.data} + draggingIndex={this.state.draggingIndex} + sortId={i} + outline="list"> + <ModuleFile + {...childProps(item)} + isReadOnlyMode={this.props.isReadOnlyMode} + /> + </SortableListItem> + ); + }, this); + + return ( + <div + className={`modules-list-wrapper ${ + listItems.length > 0 ? 'modules-list-wrapper-divider' : '' + }`}> + <div className="modules-list-header"> + {!isBaseExist && ( + <div> + <Button + btnType="link" + onClick={onBaseAdd} + disabled={ + isReadOnlyMode || unassigned.length === 0 + }> + {i18n('Add Base')} + </Button> + </div> + )} + <div> + <Button + btnType="link" + onClick={onModuleAdd} + disabled={ + isReadOnlyMode || unassigned.length === 0 + }> + {i18n('Add Module')} + </Button> + </div> + </div> + {listItems.length > 0 && <ul>{listItems}</ul>} + </div> + ); + } } -const tooltip = (name) => <Tooltip id='tooltip-bottom'>{name}</Tooltip>; -const UnassignedFileList = (props) => { - return ( - <div> - <div className='modules-list-header'/> - <div className='unassigned-files'> - <div className='unassigned-files-title'>{i18n('UNASSIGNED FILES')}</div> - <div className='unassigned-files-list'>{props.children}</div> - </div> - </div> - ); +const tooltip = name => <Tooltip id="tooltip-bottom">{name}</Tooltip>; +const UnassignedFileList = props => { + return ( + <div> + <div className="modules-list-header" /> + <div className="unassigned-files"> + <div className="unassigned-files-title"> + {i18n('UNASSIGNED FILES')} + </div> + <div className="unassigned-files-list">{props.children}</div> + </div> + </div> + ); }; const EmptyListContent = props => { - let {heatDataExist} = props; - let displayText = heatDataExist ? 'All Files Are Assigned' : ''; - return ( - <div className='go-to-validation-button-wrapper'> - <div className='all-files-assigned'>{i18n(displayText)}</div> - </div> - ); + let { heatDataExist } = props; + let displayText = heatDataExist ? 'All Files Are Assigned' : ''; + return ( + <div className="go-to-validation-button-wrapper"> + <div className="all-files-assigned">{i18n(displayText)}</div> + </div> + ); }; -const UnassignedFile = (props) => ( - <OverlayTrigger placement='bottom' overlay={tooltip(props.name)} delayShow={1000}> - <li data-test-id='unassigned-files' className='unassigned-files-list-item'>{props.name}</li> - </OverlayTrigger> +const UnassignedFile = props => ( + <OverlayTrigger + placement="bottom" + overlay={tooltip(props.name)} + delayShow={1000}> + <li + data-test-id="unassigned-files" + className="unassigned-files-list-item"> + {props.name} + </li> + </OverlayTrigger> ); -const AddOrDeleteVolumeFiles = ({add = true, onAdd, onDelete, isReadOnlyMode}) => { - const displayText = add ? 'Add Volume Files' : 'Delete Volume Files'; - const action = add ? onAdd : onDelete; - return ( - <Button disabled={isReadOnlyMode} onClick={action} btnType='link' className='add-or-delete-volumes' iconName={add ? 'plus' : 'close'}>{i18n(displayText)}</Button> - ); +const AddOrDeleteVolumeFiles = ({ + add = true, + onAdd, + onDelete, + isReadOnlyMode +}) => { + const displayText = add ? 'Add Volume Files' : 'Delete Volume Files'; + const action = add ? onAdd : onDelete; + return ( + <Button + disabled={isReadOnlyMode} + onClick={action} + btnType="link" + className="add-or-delete-volumes" + iconName={add ? 'plus' : 'close'}> + {i18n(displayText)} + </Button> + ); }; -const SelectWithFileType = ({type, selected, files, onChange}) => { - - let filteredFiledAccordingToType = files.filter(file => file.label.search(type.regex) > -1); - if (selected) { - filteredFiledAccordingToType = filteredFiledAccordingToType.concat({label: selected, value: selected}); - } - - return ( - <SelectInput - data-test-id={`${type.label}-list`} - label={type.label} - value={selected} - onChange={value => value !== selected && onChange(value, type.label)} - disabled={filteredFiledAccordingToType.length === 0} - placeholder={filteredFiledAccordingToType.length === 0 ? '' : undefined} - clearable={true} - options={filteredFiledAccordingToType} /> - ); +const SelectWithFileType = ({ type, selected, files, onChange }) => { + let filteredFiledAccordingToType = files.filter( + file => file.label.search(type.regex) > -1 + ); + if (selected) { + filteredFiledAccordingToType = filteredFiledAccordingToType.concat({ + label: selected, + value: selected + }); + } + + return ( + <SelectInput + data-test-id={`${type.label}-list`} + label={type.label} + value={selected} + onChange={value => + value !== selected && onChange(value, type.label) + } + disabled={filteredFiledAccordingToType.length === 0} + placeholder={ + filteredFiledAccordingToType.length === 0 ? '' : undefined + } + clearable={true} + options={filteredFiledAccordingToType} + /> + ); }; class NameEditInput extends Component { - componentDidMount() { - this.input.focus(); - } - - render() { - return ( - <FormControl {...this.props} className='name-edit' inputRef={input => this.input = input}/> - ); - } + componentDidMount() { + this.input.focus(); + } + + render() { + return ( + <FormControl + {...this.props} + className="name-edit" + inputRef={input => (this.input = input)} + /> + ); + } } class ModuleFile extends Component { - constructor(props) { - super(props); - this.state = { - isInNameEdit: false, - displayVolumes: Boolean(props.module.vol || props.module.volEnv) - }; - } - - handleSubmit(event, name) { - if (event.keyCode === 13) { - this.handleModuleRename(event, name); - } - } - - componentWillReceiveProps(nextProps) { - this.setState({displayVolumes: Boolean(nextProps.module.vol || nextProps.module.volEnv)}); - } - - handleModuleRename(event, name) { - this.setState({isInNameEdit: false}); - this.props.onModuleRename(name, event.target.value); - } - - deleteVolumeFiles() { - const { onModuleFileTypeChange} = this.props; - onModuleFileTypeChange(null, fileTypes.VOL.label); - onModuleFileTypeChange(null, fileTypes.VOL_ENV.label); - this.setState({displayVolumes: false}); - } - - renderNameAccordingToEditState() { - const {module: {name}} = this.props; - if (this.state.isInNameEdit) { - return (<NameEditInput defaultValue={name} onBlur={evt => this.handleModuleRename(evt, name)} onKeyDown={evt => this.handleSubmit(evt, name)}/>); - } - return (<span className='filename-text'>{name}</span>); - } - - render() { - const {module: {name, isBase, yaml, env, vol, volEnv}, onModuleDelete, files, onModuleFileTypeChange, isReadOnlyMode} = this.props; - const {displayVolumes} = this.state; - const moduleType = isBase ? 'BASE' : 'MODULE'; - return ( - <div className='modules-list-item' data-test-id='module-item'> - <div className='modules-list-item-controllers'> - <div className='modules-list-item-filename'> - <SVGIcon name={isBase ? 'base' : 'module'} color='primary' iconClassName='heat-setup-module-icon' /> - <span className='module-title-by-type'>{`${moduleType}: `}</span> - <div className={`text-and-icon ${this.state.isInNameEdit ? 'in-edit' : ''}`}> - {this.renderNameAccordingToEditState()} - {!this.state.isInNameEdit && <SVGIcon - name='pencil' - onClick={() => this.setState({isInNameEdit: true})} - data-test-id={isBase ? 'base-name' : 'module-name'}/>} - </div> - </div> - <SVGIcon name='trashO' onClick={() => onModuleDelete(name)} data-test-id='module-delete'/> - </div> - <div className='modules-list-item-selectors'> - <SelectWithFileType - type={fileTypes.YAML} - files={files} - selected={yaml} - onChange={onModuleFileTypeChange}/> - <SelectWithFileType - type={fileTypes.ENV} - files={files} - selected={env} - onChange={onModuleFileTypeChange}/> - {displayVolumes && <SelectWithFileType - type={fileTypes.VOL} - files={files} - selected={vol} - onChange={onModuleFileTypeChange}/>} - {displayVolumes && <SelectWithFileType - type={fileTypes.VOL_ENV} - files={files} - selected={volEnv} - onChange={onModuleFileTypeChange}/>} - <AddOrDeleteVolumeFiles isReadOnlyMode={isReadOnlyMode} onAdd={() => this.setState({displayVolumes: true})} onDelete={() => this.deleteVolumeFiles()} add={!displayVolumes}/> - </div> - </div> - ); - } + constructor(props) { + super(props); + this.state = { + isInNameEdit: false, + displayVolumes: Boolean(props.module.vol || props.module.volEnv) + }; + } + + handleSubmit(event, name) { + if (event.keyCode === 13) { + this.handleModuleRename(event, name); + } + } + + componentWillReceiveProps(nextProps) { + this.setState({ + displayVolumes: Boolean( + nextProps.module.vol || nextProps.module.volEnv + ) + }); + } + + handleModuleRename(event, name) { + this.setState({ isInNameEdit: false }); + this.props.onModuleRename(name, event.target.value); + } + + deleteVolumeFiles() { + const { onModuleFileTypeChange } = this.props; + onModuleFileTypeChange(null, fileTypes.VOL.label); + onModuleFileTypeChange(null, fileTypes.VOL_ENV.label); + this.setState({ displayVolumes: false }); + } + + renderNameAccordingToEditState() { + const { module: { name } } = this.props; + if (this.state.isInNameEdit) { + return ( + <NameEditInput + defaultValue={name} + onBlur={evt => this.handleModuleRename(evt, name)} + onKeyDown={evt => this.handleSubmit(evt, name)} + /> + ); + } + return <span className="filename-text">{name}</span>; + } + + render() { + const { + module: { name, isBase, yaml, env, vol, volEnv }, + onModuleDelete, + files, + onModuleFileTypeChange, + isReadOnlyMode + } = this.props; + const { displayVolumes } = this.state; + const moduleType = isBase ? 'BASE' : 'MODULE'; + return ( + <div className="modules-list-item" data-test-id="module-item"> + <div className="modules-list-item-controllers"> + <div className="modules-list-item-filename"> + <SVGIcon + name={isBase ? 'base' : 'module'} + color="primary" + iconClassName="heat-setup-module-icon" + /> + <span className="module-title-by-type">{`${moduleType}: `}</span> + <div + className={`text-and-icon ${ + this.state.isInNameEdit ? 'in-edit' : '' + }`}> + {this.renderNameAccordingToEditState()} + {!this.state.isInNameEdit && ( + <SVGIcon + name="pencil" + onClick={() => + this.setState({ isInNameEdit: true }) + } + data-test-id={ + isBase ? 'base-name' : 'module-name' + } + /> + )} + </div> + </div> + <SVGIcon + name="trashO" + onClick={() => onModuleDelete(name)} + data-test-id="module-delete" + /> + </div> + <div className="modules-list-item-selectors"> + <SelectWithFileType + type={fileTypes.YAML} + files={files} + selected={yaml} + onChange={onModuleFileTypeChange} + /> + <SelectWithFileType + type={fileTypes.ENV} + files={files} + selected={env} + onChange={onModuleFileTypeChange} + /> + {displayVolumes && ( + <SelectWithFileType + type={fileTypes.VOL} + files={files} + selected={vol} + onChange={onModuleFileTypeChange} + /> + )} + {displayVolumes && ( + <SelectWithFileType + type={fileTypes.VOL_ENV} + files={files} + selected={volEnv} + onChange={onModuleFileTypeChange} + /> + )} + <AddOrDeleteVolumeFiles + isReadOnlyMode={isReadOnlyMode} + onAdd={() => this.setState({ displayVolumes: true })} + onDelete={() => this.deleteVolumeFiles()} + add={!displayVolumes} + /> + </div> + </div> + ); + } } class ArtifactOrNestedFileList extends Component { - - render() { - let {type, title, selected, options, onSelectChanged, onAddAllUnassigned, isReadOnlyMode, headerClassName} = this.props; - return ( - <div className={`artifact-files ${type === 'nested' ? 'nested' : ''} ${headerClassName} `}> - <div className='artifact-files-header'> - <span> - {type === 'artifact' && (<SVGIcon color='primary' name='artifacts' iconClassName='heat-setup-module-icon' />)} - {`${title}`} - </span> - {type === 'artifact' && <Button disabled={isReadOnlyMode} btnType='link' className='add-all-unassigned' onClick={onAddAllUnassigned}>{i18n('Add All Unassigned Files')}</Button>} - </div> - {type === 'nested' ? ( - <ul className='nested-list'>{selected.map(nested => - <li key={nested} className='nested-list-item'>{nested}</li> - )}</ul>) : - (<SelectInput - options={options} - onMultiSelectChanged={onSelectChanged || (() => { - })} - value={selected} - clearable={false} - placeholder={i18n('Add Artifact')} - multi/>) - } - </div> - ); - } + render() { + let { + type, + title, + selected, + options, + onSelectChanged, + onAddAllUnassigned, + isReadOnlyMode, + headerClassName + } = this.props; + return ( + <div + className={`artifact-files ${ + type === 'nested' ? 'nested' : '' + } ${headerClassName} `}> + <div className="artifact-files-header"> + <span> + {type === 'artifact' && ( + <SVGIcon + color="primary" + name="artifacts" + iconClassName="heat-setup-module-icon" + /> + )} + {`${title}`} + </span> + {type === 'artifact' && ( + <Button + disabled={isReadOnlyMode} + btnType="link" + className="add-all-unassigned" + onClick={onAddAllUnassigned}> + {i18n('Add All Unassigned Files')} + </Button> + )} + </div> + {type === 'nested' ? ( + <ul className="nested-list"> + {selected.map(nested => ( + <li key={nested} className="nested-list-item"> + {nested} + </li> + ))} + </ul> + ) : ( + <SelectInput + options={options} + onMultiSelectChanged={onSelectChanged || (() => {})} + value={selected} + clearable={false} + placeholder={i18n('Add Artifact')} + multi + /> + )} + </div> + ); + } } -const buildLabelValueObject = str => (typeof str === 'string' ? {value: str, label: str} : str); +const buildLabelValueObject = str => + typeof str === 'string' ? { value: str, label: str } : str; class SoftwareProductHeatSetupView extends Component { - - processAndValidateHeat(heatData, heatDataCache){ - let {onProcessAndValidate, changeAttachmentsTab, version} = this.props; - onProcessAndValidate({heatData, heatDataCache, version}).then( - () => changeAttachmentsTab(tabsMapping.VALIDATION) - ); - } - - render() { - let {modules, isReadOnlyMode, heatDataExist, unassigned, artifacts, nested, onArtifactListChange, onAddAllUnassigned} = this.props; - - const formattedUnassigned = unassigned.map(buildLabelValueObject); - const formattedArtifacts = artifacts.map(buildLabelValueObject); - return ( - <div className={`heat-setup-view ${isReadOnlyMode ? 'disabled' : ''}`}> - <div className='heat-setup-view-modules-and-artifacts'> - <SortableModuleFileList - {...this.props} - isReadOnlyMode={this.props.isReadOnlyMode} - artifacts={formattedArtifacts} - unassigned={formattedUnassigned}/> - <ArtifactOrNestedFileList - type={'artifact'} - title={i18n('ARTIFACTS')} - options={formattedUnassigned} - selected={formattedArtifacts} - onSelectChanged={onArtifactListChange} - isReadOnlyMode={this.props.isReadOnlyMode} - headerClassName={(modules && modules.length > 0) ? 'with-list-items' : ''} - onAddAllUnassigned={onAddAllUnassigned}/> - <ArtifactOrNestedFileList - type={'nested'} - title={i18n('NESTED HEAT FILES')} - options={[]} - isReadOnlyMode={this.props.isReadOnlyMode} - selected={nested}/> - </div> - <UnassignedFileList> - { - formattedUnassigned.length > 0 ? - (<ul>{formattedUnassigned.map(file => <UnassignedFile key={file.label} name={file.label}/>)}</ul>) - : - (<EmptyListContent - heatDataExist={heatDataExist}/>) - } - </UnassignedFileList> - </div> - ); - } - + processAndValidateHeat(heatData, heatDataCache) { + let { + onProcessAndValidate, + changeAttachmentsTab, + version + } = this.props; + onProcessAndValidate({ heatData, heatDataCache, version }).then(() => + changeAttachmentsTab(tabsMapping.VALIDATION) + ); + } + + render() { + let { + modules, + isReadOnlyMode, + heatDataExist, + unassigned, + artifacts, + nested, + onArtifactListChange, + onAddAllUnassigned + } = this.props; + + const formattedUnassigned = unassigned.map(buildLabelValueObject); + const formattedArtifacts = artifacts.map(buildLabelValueObject); + return ( + <div + className={`heat-setup-view ${ + isReadOnlyMode ? 'disabled' : '' + }`}> + <div className="heat-setup-view-modules-and-artifacts"> + <SortableModuleFileList + {...this.props} + isReadOnlyMode={this.props.isReadOnlyMode} + artifacts={formattedArtifacts} + unassigned={formattedUnassigned} + /> + <ArtifactOrNestedFileList + type={'artifact'} + title={i18n('ARTIFACTS')} + options={formattedUnassigned} + selected={formattedArtifacts} + onSelectChanged={onArtifactListChange} + isReadOnlyMode={this.props.isReadOnlyMode} + headerClassName={ + modules && modules.length > 0 + ? 'with-list-items' + : '' + } + onAddAllUnassigned={onAddAllUnassigned} + /> + <ArtifactOrNestedFileList + type={'nested'} + title={i18n('NESTED HEAT FILES')} + options={[]} + isReadOnlyMode={this.props.isReadOnlyMode} + selected={nested} + /> + </div> + <UnassignedFileList> + {formattedUnassigned.length > 0 ? ( + <ul> + {formattedUnassigned.map(file => ( + <UnassignedFile + key={file.label} + name={file.label} + /> + ))} + </ul> + ) : ( + <EmptyListContent heatDataExist={heatDataExist} /> + )} + </UnassignedFileList> + </div> + ); + } } export default SoftwareProductHeatSetupView; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/validation/HeatValidation.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/validation/HeatValidation.js index 21f6e6c77f..00130e4bc7 100644 --- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/validation/HeatValidation.js +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/validation/HeatValidation.js @@ -13,39 +13,55 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import {connect} from 'react-redux'; +import { connect } from 'react-redux'; import HeatValidationView from './HeatValidationView.jsx'; import HeatValidationActionHelper from './HeatValidationActionHelper.js'; -import {errorLevels, nodeFilters} from './HeatValidationConstants.js'; +import { errorLevels, nodeFilters } from './HeatValidationConstants.js'; -export const mapStateToProps = ({softwareProduct: {softwareProductAttachments: {heatValidation}}}) => { - let {attachmentsTree, selectedNode, errorList} = heatValidation; - let currentErrors = [], currentWarnings = []; - if (errorList) { - for (let i = 0 ; i < errorList.length ; i++) { - if (errorList[i].level === errorLevels.ERROR && (errorList[i].name === selectedNode || selectedNode === nodeFilters.ALL)) { - currentErrors[currentErrors.length] = errorList[i]; - } - if (errorList[i].level === errorLevels.WARNING && (errorList[i].name === selectedNode || selectedNode === nodeFilters.ALL)) { - currentWarnings[currentWarnings.length] = errorList[i]; - } - } - } - return { - attachmentsTree, - selectedNode, - errorList, - currentErrors, - currentWarnings - }; +export const mapStateToProps = ({ + softwareProduct: { softwareProductAttachments: { heatValidation } } +}) => { + let { attachmentsTree, selectedNode, errorList } = heatValidation; + let currentErrors = [], + currentWarnings = []; + if (errorList) { + for (let i = 0; i < errorList.length; i++) { + if ( + errorList[i].level === errorLevels.ERROR && + (errorList[i].name === selectedNode || + selectedNode === nodeFilters.ALL) + ) { + currentErrors[currentErrors.length] = errorList[i]; + } + if ( + errorList[i].level === errorLevels.WARNING && + (errorList[i].name === selectedNode || + selectedNode === nodeFilters.ALL) + ) { + currentWarnings[currentWarnings.length] = errorList[i]; + } + } + } + return { + attachmentsTree, + selectedNode, + errorList, + currentErrors, + currentWarnings + }; }; -const mapActionsToProps = (dispatch) => { - return { - toggleExpanded: (path) => HeatValidationActionHelper.toggleExpanded(dispatch, {path}), - onSelectNode: (nodeName) => HeatValidationActionHelper.onSelectNode(dispatch, {nodeName}), - onDeselectNode: () => HeatValidationActionHelper.onDeselectNode(dispatch) - }; +const mapActionsToProps = dispatch => { + return { + toggleExpanded: path => + HeatValidationActionHelper.toggleExpanded(dispatch, { path }), + onSelectNode: nodeName => + HeatValidationActionHelper.onSelectNode(dispatch, { nodeName }), + onDeselectNode: () => + HeatValidationActionHelper.onDeselectNode(dispatch) + }; }; -export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(HeatValidationView); +export default connect(mapStateToProps, mapActionsToProps, null, { + withRef: true +})(HeatValidationView); diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/validation/HeatValidationActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/validation/HeatValidationActionHelper.js index 73366c20cc..5e8e49cf8a 100644 --- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/validation/HeatValidationActionHelper.js +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/validation/HeatValidationActionHelper.js @@ -13,27 +13,26 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import {actionTypes} from './HeatValidationConstants.js'; +import { actionTypes } from './HeatValidationConstants.js'; export default { + toggleExpanded(dispatch, { path }) { + dispatch({ + type: actionTypes.TOGGLE_EXPANDED, + path + }); + }, - toggleExpanded(dispatch, {path}) { - dispatch({ - type: actionTypes.TOGGLE_EXPANDED, - path - }); - }, + onSelectNode(dispatch, { nodeName }) { + dispatch({ + type: actionTypes.SELECTED_NODE, + nodeName + }); + }, - onSelectNode(dispatch, {nodeName}) { - dispatch({ - type: actionTypes.SELECTED_NODE, - nodeName - }); - }, - - onDeselectNode(dispatch) { - dispatch({ - type: actionTypes.UNSELECTED_NODE - }); - } + onDeselectNode(dispatch) { + dispatch({ + type: actionTypes.UNSELECTED_NODE + }); + } }; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/validation/HeatValidationConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/validation/HeatValidationConstants.js index f783fe6482..8c9f54b5ba 100644 --- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/validation/HeatValidationConstants.js +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/validation/HeatValidationConstants.js @@ -17,41 +17,48 @@ import keyMirror from 'nfvo-utils/KeyMirror.js'; import i18n from 'nfvo-utils/i18n/i18n.js'; export const actionTypes = keyMirror({ - TOGGLE_EXPANDED: null, - SELECTED_NODE: null, - UNSELECTED_NODE: null + TOGGLE_EXPANDED: null, + SELECTED_NODE: null, + UNSELECTED_NODE: null }); export const errorTypes = keyMirror({ - MISSING_FILE_IN_ZIP: i18n('missing file in zip'), - MISSING_FILE_IN_MANIFEST: i18n('missing file in manifest'), - MISSING_OR_ILLEGAL_FILE_TYPE_IN_MANIFEST: i18n('missing or illegal file type in manifest'), - FILE_IS_YML_WITHOUT_YML_EXTENSION: i18n('file is defined as a heat file but it doesn\'t have .yml or .yaml extension'), - FILE_IS_ENV_WITHOUT_ENV_EXTENSION: i18n('file is defined as an env file but it doesn\'t have .env extension'), - ILLEGAL_YAML_FILE_CONTENT: i18n('illegal yaml file content'), - ILLEGAL_HEAT_YAML_FILE_CONTENT: i18n('illegal HEAT yaml file content'), - MISSING_FILE_NAME_IN_MANIFEST: i18n('a file is written in manifest without file name'), - MISSING_ENV_FILE_IN_ZIP: i18n('missing env file in zip'), - ARTIFACT_NOT_IN_USE: i18n('artifact not in use') + MISSING_FILE_IN_ZIP: i18n('missing file in zip'), + MISSING_FILE_IN_MANIFEST: i18n('missing file in manifest'), + MISSING_OR_ILLEGAL_FILE_TYPE_IN_MANIFEST: i18n( + 'missing or illegal file type in manifest' + ), + FILE_IS_YML_WITHOUT_YML_EXTENSION: i18n( + "file is defined as a heat file but it doesn't have .yml or .yaml extension" + ), + FILE_IS_ENV_WITHOUT_ENV_EXTENSION: i18n( + "file is defined as an env file but it doesn't have .env extension" + ), + ILLEGAL_YAML_FILE_CONTENT: i18n('illegal yaml file content'), + ILLEGAL_HEAT_YAML_FILE_CONTENT: i18n('illegal HEAT yaml file content'), + MISSING_FILE_NAME_IN_MANIFEST: i18n( + 'a file is written in manifest without file name' + ), + MISSING_ENV_FILE_IN_ZIP: i18n('missing env file in zip'), + ARTIFACT_NOT_IN_USE: i18n('artifact not in use') }); export const errorLevels = keyMirror({ - WARNING: 'WARNING', - ERROR: 'ERROR' + WARNING: 'WARNING', + ERROR: 'ERROR' }); export const nodeFilters = keyMirror({ - ALL: 'All' + ALL: 'All' }); export const nodeTypes = keyMirror({ - heat: i18n('Heat'), - volume: i18n('Volume'), - network: i18n('Network'), - artifact: i18n('Artifact'), - env: i18n('Environment'), - other: i18n('') + heat: i18n('Heat'), + volume: i18n('Volume'), + network: i18n('Network'), + artifact: i18n('Artifact'), + env: i18n('Environment'), + other: i18n('') }); export const mouseActions = keyMirror({ - MOUSE_BUTTON_CLICK: 0 + MOUSE_BUTTON_CLICK: 0 }); - diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/validation/HeatValidationReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/validation/HeatValidationReducer.js index 1d11bdd6b7..67e36ca040 100644 --- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/validation/HeatValidationReducer.js +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/validation/HeatValidationReducer.js @@ -13,185 +13,242 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import {actionTypes as softwareProductsActionTypes} from 'sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js'; -import {actionTypes, nodeFilters} from './HeatValidationConstants.js'; +import { actionTypes as softwareProductsActionTypes } from 'sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js'; +import { actionTypes, nodeFilters } from './HeatValidationConstants.js'; -const mapVolumeData = ({fileName, env, errors}) => ({ - name: fileName, - expanded: true, - type: 'volume', - children: env && [{ - name: env.fileName, - errors: env.errors, - type: 'env' - }], - errors +const mapVolumeData = ({ fileName, env, errors }) => ({ + name: fileName, + expanded: true, + type: 'volume', + children: env && [ + { + name: env.fileName, + errors: env.errors, + type: 'env' + } + ], + errors }); -const mapNetworkData = ({fileName, env, errors}) => ({ - name: fileName, - expanded: true, - type: 'network', - children: env && [{ - name: env.fileName, - errors: env.errors, - type: 'env' - }], - errors +const mapNetworkData = ({ fileName, env, errors }) => ({ + name: fileName, + expanded: true, + type: 'network', + children: env && [ + { + name: env.fileName, + errors: env.errors, + type: 'env' + } + ], + errors }); -const mapArtifactsData = ({fileName, errors}) => ({ - name: fileName, - type: 'artifact', - errors +const mapArtifactsData = ({ fileName, errors }) => ({ + name: fileName, + type: 'artifact', + errors }); -const mapOtherData = ({fileName, errors}) => ({ - name: fileName, - type: 'other', - errors +const mapOtherData = ({ fileName, errors }) => ({ + name: fileName, + type: 'other', + errors }); - -const mapHeatData = ({fileName, env, nested, volume, network, artifacts, errors, other}) => ({ - name: fileName, - expanded: true, - type: 'heat', - errors, - children: [ - ...(volume ? volume.map(mapVolumeData) : []), - ...(network ? network.map(mapNetworkData) : []), - ...(env ? [{ - name: env.fileName, - errors: env.errors, - type: 'env' - }] : []), - ...(artifacts ? artifacts.map(mapArtifactsData) : []), - ...(other ? other.map(mapOtherData) : []), - ...(nested ? nested.map(mapHeatData) : []) - ] +const mapHeatData = ({ + fileName, + env, + nested, + volume, + network, + artifacts, + errors, + other +}) => ({ + name: fileName, + expanded: true, + type: 'heat', + errors, + children: [ + ...(volume ? volume.map(mapVolumeData) : []), + ...(network ? network.map(mapNetworkData) : []), + ...(env + ? [ + { + name: env.fileName, + errors: env.errors, + type: 'env' + } + ] + : []), + ...(artifacts ? artifacts.map(mapArtifactsData) : []), + ...(other ? other.map(mapOtherData) : []), + ...(nested ? nested.map(mapHeatData) : []) + ] }); function createErrorList(node, parent, deep = 0, errorList = []) { - if (node.errors) { - errorList.push(...node.errors.map((error) => ({ - level: error.level, - errorMessage: error.message, - name: node.name, - hasParent: deep > 2, - parentName: parent.name, - type: node.type, - }))); - } - if (node.children && node.children.length) { - node.children.map((child) => createErrorList(child, node, deep + 1, errorList)); - } - return errorList; + if (node.errors) { + errorList.push( + ...node.errors.map(error => ({ + level: error.level, + errorMessage: error.message, + name: node.name, + hasParent: deep > 2, + parentName: parent.name, + type: node.type + })) + ); + } + if (node.children && node.children.length) { + node.children.map(child => + createErrorList(child, node, deep + 1, errorList) + ); + } + return errorList; } const mapValidationDataToTree = (validationData, packageName) => { - let {heat, nested, volume, network, artifacts, other} = validationData.importStructure || {}; - return { - children: [ - { - name: packageName, - expanded: true, - type: 'heat', - header: true, - children: (heat ? heat.map(mapHeatData) : nested ? nested.map(mapHeatData) : []) - }, - ...(artifacts ? [{ - name: 'artifacts', - expanded: true, - type: 'artifact', - children: (artifacts ? artifacts.map(mapArtifactsData) : []) - }] : []), - ...(network ? [{ - name: 'networks', - expanded: true, - type: 'network', - children: (network ? network.map(mapNetworkData) : []), - }] : []), - ...(volume ? [{ - name: 'volume', - expanded: true, - type: 'volume', - children: (volume ? volume.map(mapVolumeData) : []), - }] : []), - ...(other ? [{ - name: 'other', - expanded: true, - type: 'other', - children: (other ? other.map(mapOtherData) : []), - }] : []) - ] - }; + let { heat, nested, volume, network, artifacts, other } = + validationData.importStructure || {}; + return { + children: [ + { + name: packageName, + expanded: true, + type: 'heat', + header: true, + children: heat + ? heat.map(mapHeatData) + : nested ? nested.map(mapHeatData) : [] + }, + ...(artifacts + ? [ + { + name: 'artifacts', + expanded: true, + type: 'artifact', + children: artifacts + ? artifacts.map(mapArtifactsData) + : [] + } + ] + : []), + ...(network + ? [ + { + name: 'networks', + expanded: true, + type: 'network', + children: network ? network.map(mapNetworkData) : [] + } + ] + : []), + ...(volume + ? [ + { + name: 'volume', + expanded: true, + type: 'volume', + children: volume ? volume.map(mapVolumeData) : [] + } + ] + : []), + ...(other + ? [ + { + name: 'other', + expanded: true, + type: 'other', + children: other ? other.map(mapOtherData) : [] + } + ] + : []) + ] + }; }; const toggleExpanded = (node, path) => { - let newNode = {...node}; - if (path.length === 0) { - newNode.expanded = !node.expanded; - } else { - let index = path[0]; - newNode.children = [ - ...node.children.slice(0, index), - toggleExpanded(node.children[index], path.slice(1)), - ...node.children.slice(index + 1) - ]; - } - return newNode; + let newNode = { ...node }; + if (path.length === 0) { + newNode.expanded = !node.expanded; + } else { + let index = path[0]; + newNode.children = [ + ...node.children.slice(0, index), + toggleExpanded(node.children[index], path.slice(1)), + ...node.children.slice(index + 1) + ]; + } + return newNode; }; const expandSelected = (node, selectedNode) => { - let shouldExpand = node.name === selectedNode; - let children = node.children && node.children.map(child => { - let {shouldExpand: shouldExpandChild, node: newChild} = expandSelected(child, selectedNode); - shouldExpand = shouldExpand || shouldExpandChild; - return newChild; - }); + let shouldExpand = node.name === selectedNode; + let children = + node.children && + node.children.map(child => { + let { + shouldExpand: shouldExpandChild, + node: newChild + } = expandSelected(child, selectedNode); + shouldExpand = shouldExpand || shouldExpandChild; + return newChild; + }); - return { - node: { - ...node, - expanded: node.expanded || shouldExpand, - children - }, - shouldExpand - }; + return { + node: { + ...node, + expanded: node.expanded || shouldExpand, + children + }, + shouldExpand + }; }; -export default (state = {attachmentsTree: {}}, action) => { - switch (action.type) { - case softwareProductsActionTypes.SOFTWARE_PRODUCT_LOADED: - let currentSoftwareProduct = action.response; - const packageName = currentSoftwareProduct.networkPackageName; - let attachmentsTree = currentSoftwareProduct.validationData ? mapValidationDataToTree(currentSoftwareProduct.validationData, packageName) : {}; - let errorList = createErrorList(attachmentsTree); - return { - ...state, - attachmentsTree, - errorList, - selectedNode: nodeFilters.ALL - }; - case actionTypes.TOGGLE_EXPANDED: - return { - ...state, - attachmentsTree: toggleExpanded(state.attachmentsTree, action.path) - }; - case actionTypes.SELECTED_NODE: - let selectedNode = action.nodeName; - return { - ...state, - attachmentsTree: expandSelected(state.attachmentsTree, selectedNode).node, - selectedNode - }; - case actionTypes.UNSELECTED_NODE: - return { - ...state, - selectedNode: nodeFilters.ALL - }; - default: - return state; - } +export default (state = { attachmentsTree: {} }, action) => { + switch (action.type) { + case softwareProductsActionTypes.SOFTWARE_PRODUCT_LOADED: + let currentSoftwareProduct = action.response; + const packageName = currentSoftwareProduct.networkPackageName; + let attachmentsTree = currentSoftwareProduct.validationData + ? mapValidationDataToTree( + currentSoftwareProduct.validationData, + packageName + ) + : {}; + let errorList = createErrorList(attachmentsTree); + return { + ...state, + attachmentsTree, + errorList, + selectedNode: nodeFilters.ALL + }; + case actionTypes.TOGGLE_EXPANDED: + return { + ...state, + attachmentsTree: toggleExpanded( + state.attachmentsTree, + action.path + ) + }; + case actionTypes.SELECTED_NODE: + let selectedNode = action.nodeName; + return { + ...state, + attachmentsTree: expandSelected( + state.attachmentsTree, + selectedNode + ).node, + selectedNode + }; + case actionTypes.UNSELECTED_NODE: + return { + ...state, + selectedNode: nodeFilters.ALL + }; + default: + return state; + } }; diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/validation/HeatValidationView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/validation/HeatValidationView.jsx index 3fdaa9c591..c6ee5efd36 100644 --- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/validation/HeatValidationView.jsx +++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/validation/HeatValidationView.jsx @@ -13,254 +13,352 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import React, {Component} from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import Collapse from 'react-bootstrap/lib/Collapse.js'; import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js'; import i18n from 'nfvo-utils/i18n/i18n.js'; -import {mouseActions, errorLevels, nodeFilters} from './HeatValidationConstants.js'; +import { + mouseActions, + errorLevels, + nodeFilters +} from './HeatValidationConstants.js'; const leftPanelWidth = 250; const typeToIcon = Object.freeze({ - heat: 'nestedHeat', - volume: 'base', - network: 'network', - artifact: 'artifacts', - env: 'env', - other: 'other' + heat: 'nestedHeat', + volume: 'base', + network: 'network', + artifact: 'artifacts', + env: 'env', + other: 'other' }); - class HeatValidationView extends Component { + static propTypes = { + attachmentsTree: PropTypes.object.isRequired, + errorList: PropTypes.array.isRequired, + currentErrors: PropTypes.array.isRequired, + currentWarnings: PropTypes.array.isRequired, + onSelectNode: PropTypes.func.isRequired, + onDeselectNode: PropTypes.func.isRequired, + toggleExpanded: PropTypes.func.isRequired, + selectedNode: PropTypes.string + }; - static propTypes = { - attachmentsTree: PropTypes.object.isRequired, - errorList: PropTypes.array.isRequired, - currentErrors: PropTypes.array.isRequired, - currentWarnings: PropTypes.array.isRequired, - onSelectNode: PropTypes.func.isRequired, - onDeselectNode: PropTypes.func.isRequired, - toggleExpanded: PropTypes.func.isRequired, - selectedNode: PropTypes.string - }; - - render() { - return (<div className='vsp-attachments-heat-validation' data-test-id='heat-validation-editor'> - <HeatFileTree errorList={this.props.errorList} attachmentsTree={this.props.attachmentsTree} - onSelectNode={this.props.onSelectNode} toggleExpanded={this.props.toggleExpanded} - selectedNode={this.props.selectedNode} onDeselectNode={this.props.onDeselectNode} /> - <HeatMessageBoard errors={this.props.currentErrors} warnings={this.props.currentWarnings} selectedNode={this.props.selectedNode} /> - </div> ); - } + render() { + return ( + <div + className="vsp-attachments-heat-validation" + data-test-id="heat-validation-editor"> + <HeatFileTree + errorList={this.props.errorList} + attachmentsTree={this.props.attachmentsTree} + onSelectNode={this.props.onSelectNode} + toggleExpanded={this.props.toggleExpanded} + selectedNode={this.props.selectedNode} + onDeselectNode={this.props.onDeselectNode} + /> + <HeatMessageBoard + errors={this.props.currentErrors} + warnings={this.props.currentWarnings} + selectedNode={this.props.selectedNode} + /> + </div> + ); + } } function HeatFileTreeRow(props) { - let {node, path, toggleExpanded, selectedNode, selectNode} = props; - let isFolder = node.children && node.children.length > 0; - return ( - <div onDoubleClick={() => toggleExpanded(path)} className={classNames({ - 'tree-node-row': true, - 'tree-node-clicked': node.name === props.selectedNode - })} data-test-id='validation-tree-node'> - <div className='name-section'> - { - isFolder && - <div onClick={() => toggleExpanded(path)} - className='tree-node-expander'> - <SVGIcon name={!node.expanded ? 'chevronUp' : 'chevronDown'} data-test-id='validation-tree-block-toggle'/> - </div> - } - { - - <span className='tree-node-icon'> - <SVGIcon name={typeToIcon[node.type]} color={selectedNode === node.name ? 'primary' : 'secondary'}/> - </span> - } - { - - <span className='tree-node-name' onClick={() => selectNode(node.name)} data-test-id='validation-tree-node-name'> - {node.name ? node.name : 'UNKNOWN'} - </span> - } - </div> - <ErrorsAndWarningsCount errorList={node.errors} onClick={() => selectNode(node.name)} /> - </div>); + let { node, path, toggleExpanded, selectedNode, selectNode } = props; + let isFolder = node.children && node.children.length > 0; + return ( + <div + onDoubleClick={() => toggleExpanded(path)} + className={classNames({ + 'tree-node-row': true, + 'tree-node-clicked': node.name === props.selectedNode + })} + data-test-id="validation-tree-node"> + <div className="name-section"> + {isFolder && ( + <div + onClick={() => toggleExpanded(path)} + className="tree-node-expander"> + <SVGIcon + name={!node.expanded ? 'chevronUp' : 'chevronDown'} + data-test-id="validation-tree-block-toggle" + /> + </div> + )} + { + <span className="tree-node-icon"> + <SVGIcon + name={typeToIcon[node.type]} + color={ + selectedNode === node.name + ? 'primary' + : 'secondary' + } + /> + </span> + } + { + <span + className="tree-node-name" + onClick={() => selectNode(node.name)} + data-test-id="validation-tree-node-name"> + {node.name ? node.name : 'UNKNOWN'} + </span> + } + </div> + <ErrorsAndWarningsCount + errorList={node.errors} + onClick={() => selectNode(node.name)} + /> + </div> + ); } function HeatFileTreeHeader(props) { - let hasErrors = props.errorList.filter(error => error.level === errorLevels.ERROR).length > 0; - return ( - <div onClick={() => props.selectNode(nodeFilters.ALL)} className={classNames({'attachments-tree-header': true, - 'header-selected' : props.selectedNode === nodeFilters.ALL})} data-test-id='validation-tree-header'> - <div className='tree-header-title' > - {/*<SVGIcon name='zip' color={props.selectedNode === nodeFilters.ALL ? 'primary' : ''} iconClassName='header-icon' />*/} - <span className={classNames({'tree-header-title-text' : true, - 'tree-header-title-selected' : props.selectedNode === nodeFilters.ALL})}>{i18n('{title} {hasErrors}', {title: props.headerTitle, hasErrors: hasErrors ? '(Draft)' : ''})}</span> - </div> - <ErrorsAndWarningsCount errorList={props.errorList} size='large' /> - </div>); + let hasErrors = + props.errorList.filter(error => error.level === errorLevels.ERROR) + .length > 0; + return ( + <div + onClick={() => props.selectNode(nodeFilters.ALL)} + className={classNames({ + 'attachments-tree-header': true, + 'header-selected': props.selectedNode === nodeFilters.ALL + })} + data-test-id="validation-tree-header"> + <div className="tree-header-title"> + {/*<SVGIcon name='zip' color={props.selectedNode === nodeFilters.ALL ? 'primary' : ''} iconClassName='header-icon' />*/} + <span + className={classNames({ + 'tree-header-title-text': true, + 'tree-header-title-selected': + props.selectedNode === nodeFilters.ALL + })}> + {i18n('{title} {hasErrors}', { + title: props.headerTitle, + hasErrors: hasErrors ? '(Draft)' : '' + })} + </span> + </div> + <ErrorsAndWarningsCount errorList={props.errorList} size="large" /> + </div> + ); } -class HeatFileTree extends React.Component { - static propTypes = { - attachmentsTree: PropTypes.object.isRequired, - errorList: PropTypes.array.isRequired, - onSelectNode: PropTypes.func.isRequired, - onDeselectNode: PropTypes.func.isRequired, - toggleExpanded: PropTypes.func.isRequired, - selectedNode: PropTypes.string - }; - state = { - treeWidth: '400' - }; - render() { - let {attachmentsTree} = this.props; - return ( - <div className='validation-tree-section' style={{'width' : this.state.treeWidth + 'px'}}> - <div className='vsp-attachments-heat-validation-tree'> - <div className='tree-wrapper'> - {attachmentsTree && attachmentsTree.children && attachmentsTree.children.map((child, ind) => this.renderNode(child, [ind]))} - </div> - </div> - <div onMouseDown={(e) => this.onChangeTreeWidth(e)} - className='vsp-attachments-heat-validation-separator' data-test-id='validation-tree-separator'></div> - </div>); - } - renderNode(node, path) { - let rand = Math.random() * (3000 - 1) + 1; - let isFolder = node.children && node.children.length > 0; - let {selectedNode} = this.props; - return ( - <div key={node.name + rand} className={classNames({'tree-block-inside' : !node.header})}> - { - node.header ? - <HeatFileTreeHeader headerTitle={node.name} selectedNode={selectedNode} errorList={this.props.errorList} selectNode={(nodeName) => this.selectNode(nodeName)} /> : - <HeatFileTreeRow toggleExpanded={this.props.toggleExpanded} node={node} path={path} selectedNode={selectedNode} selectNode={() => this.selectNode(node.name)} /> - } - { - isFolder && - <Collapse in={node.expanded}> - <div className='tree-node-children'> - { - node.children.map((child, ind) => this.renderNode(child, [...path, ind])) - } - </div> - </Collapse> - } - </div> - ); - } - - - - - - selectNode(currentSelectedNode) { - let {onDeselectNode, onSelectNode, selectedNode} = this.props; - if (currentSelectedNode !== selectedNode) { - onSelectNode(currentSelectedNode); - } else { - onDeselectNode(); - } - - +class HeatFileTree extends React.Component { + static propTypes = { + attachmentsTree: PropTypes.object.isRequired, + errorList: PropTypes.array.isRequired, + onSelectNode: PropTypes.func.isRequired, + onDeselectNode: PropTypes.func.isRequired, + toggleExpanded: PropTypes.func.isRequired, + selectedNode: PropTypes.string + }; + state = { + treeWidth: '400' + }; + render() { + let { attachmentsTree } = this.props; + return ( + <div + className="validation-tree-section" + style={{ width: this.state.treeWidth + 'px' }}> + <div className="vsp-attachments-heat-validation-tree"> + <div className="tree-wrapper"> + {attachmentsTree && + attachmentsTree.children && + attachmentsTree.children.map((child, ind) => + this.renderNode(child, [ind]) + )} + </div> + </div> + <div + onMouseDown={e => this.onChangeTreeWidth(e)} + className="vsp-attachments-heat-validation-separator" + data-test-id="validation-tree-separator" + /> + </div> + ); + } + renderNode(node, path) { + let rand = Math.random() * (3000 - 1) + 1; + let isFolder = node.children && node.children.length > 0; + let { selectedNode } = this.props; + return ( + <div + key={node.name + rand} + className={classNames({ 'tree-block-inside': !node.header })}> + {node.header ? ( + <HeatFileTreeHeader + headerTitle={node.name} + selectedNode={selectedNode} + errorList={this.props.errorList} + selectNode={nodeName => this.selectNode(nodeName)} + /> + ) : ( + <HeatFileTreeRow + toggleExpanded={this.props.toggleExpanded} + node={node} + path={path} + selectedNode={selectedNode} + selectNode={() => this.selectNode(node.name)} + /> + )} + {isFolder && ( + <Collapse in={node.expanded}> + <div className="tree-node-children"> + {node.children.map((child, ind) => + this.renderNode(child, [...path, ind]) + )} + </div> + </Collapse> + )} + </div> + ); + } - } + selectNode(currentSelectedNode) { + let { onDeselectNode, onSelectNode, selectedNode } = this.props; + if (currentSelectedNode !== selectedNode) { + onSelectNode(currentSelectedNode); + } else { + onDeselectNode(); + } + } - onChangeTreeWidth(e) { - if (e.button === mouseActions.MOUSE_BUTTON_CLICK) { - let onMouseMove = (e) => { - this.setState({treeWidth: e.clientX - leftPanelWidth}); - }; - let onMouseUp = () => { - document.removeEventListener('mousemove', onMouseMove); - document.removeEventListener('mouseup', onMouseUp); - }; - document.addEventListener('mousemove', onMouseMove); - document.addEventListener('mouseup', onMouseUp); - } - } + onChangeTreeWidth(e) { + if (e.button === mouseActions.MOUSE_BUTTON_CLICK) { + let onMouseMove = e => { + this.setState({ treeWidth: e.clientX - leftPanelWidth }); + }; + let onMouseUp = () => { + document.removeEventListener('mousemove', onMouseMove); + document.removeEventListener('mouseup', onMouseUp); + }; + document.addEventListener('mousemove', onMouseMove); + document.addEventListener('mouseup', onMouseUp); + } + } } class HeatMessageBoard extends Component { - static propTypes = { - currentErrors: PropTypes.array, - currentWarnings: PropTypes.array, - selectedNode: PropTypes.string - }; - render() { - let {errors, warnings} = this.props; - let allItems = [...errors, ...warnings]; - return ( - <div className='message-board-section'> - { allItems.map(error => this.renderError(error)) } - </div> - ); - } - renderError(error) { - let rand = Math.random() * (3000 - 1) + 1; - return ( - <div - key={error.name + error.errorMessage + error.parentName + rand} - className='error-item' data-test-id='validation-error'> - {error.level === errorLevels.WARNING ? - <SVGIcon name='exclamationTriangleLine' iconClassName='large' color='warning' /> : <SVGIcon name='error' iconClassName='large' color='negative' /> } - <span className='error-item-file-type'> - { - (this.props.selectedNode === nodeFilters.ALL) ? - <span> - <span className='error-file-name'> - {error.name} - </span> - <span> - {error.errorMessage} - </span> - </span> : - error.errorMessage - } - </span> - </div> - ); - } + static propTypes = { + currentErrors: PropTypes.array, + currentWarnings: PropTypes.array, + selectedNode: PropTypes.string + }; + render() { + let { errors, warnings } = this.props; + let allItems = [...errors, ...warnings]; + return ( + <div className="message-board-section"> + {allItems.map(error => this.renderError(error))} + </div> + ); + } + renderError(error) { + let rand = Math.random() * (3000 - 1) + 1; + return ( + <div + key={error.name + error.errorMessage + error.parentName + rand} + className="error-item" + data-test-id="validation-error"> + {error.level === errorLevels.WARNING ? ( + <SVGIcon + name="exclamationTriangleLine" + iconClassName="large" + color="warning" + /> + ) : ( + <SVGIcon + name="error" + iconClassName="large" + color="negative" + /> + )} + <span className="error-item-file-type"> + {this.props.selectedNode === nodeFilters.ALL ? ( + <span> + <span className="error-file-name"> + {error.name} + </span> + <span>{error.errorMessage}</span> + </span> + ) : ( + error.errorMessage + )} + </span> + </div> + ); + } } class ErrorsAndWarningsCount extends Component { - static propTypes = { - errorList: PropTypes.array, - size: PropTypes.string - }; - render() { - let errors = this.getErrorsAndWarningsCount(this.props.errorList); - if (!errors) { - return null; - } - let {size} = this.props; - return (<div className='counters'> - {(errors.errorCount > 0) && <div className='counter'> - <SVGIcon name='error' color='negative' iconClassName={size}/> - <div className={'error-text ' + (size ? size : '')} data-test-id='validation-error-count'>{errors.errorCount}</div> - </div>} - {(errors.warningCount > 0) && <div className='counter'> - <SVGIcon name='exclamationTriangleLine' iconClassName={size} color='warning'/> - <div className={'warning-text ' + (size ? size : '')} data-test-id='validation-warning-count'>{errors.warningCount}</div> - </div>} - </div>); - } - getErrorsAndWarningsCount(errorList) { - let errorCount = 0, warningCount = 0; - if (errorList && errorList.length > 0) { - for (let i = 0; i < errorList.length; i++) { - if (errorList[i].level === errorLevels.ERROR) { - errorCount++; - } else if (errorList[i].level === errorLevels.WARNING) { - warningCount++; - } - } - } - if (errorCount === 0 && warningCount === 0) { - return null; - } - return {errorCount, warningCount}; - } + static propTypes = { + errorList: PropTypes.array, + size: PropTypes.string + }; + render() { + let errors = this.getErrorsAndWarningsCount(this.props.errorList); + if (!errors) { + return null; + } + let { size } = this.props; + return ( + <div className="counters"> + {errors.errorCount > 0 && ( + <div className="counter"> + <SVGIcon + name="error" + color="negative" + iconClassName={size} + /> + <div + className={'error-text ' + (size ? size : '')} + data-test-id="validation-error-count"> + {errors.errorCount} + </div> + </div> + )} + {errors.warningCount > 0 && ( + <div className="counter"> + <SVGIcon + name="exclamationTriangleLine" + iconClassName={size} + color="warning" + /> + <div + className={'warning-text ' + (size ? size : '')} + data-test-id="validation-warning-count"> + {errors.warningCount} + </div> + </div> + )} + </div> + ); + } + getErrorsAndWarningsCount(errorList) { + let errorCount = 0, + warningCount = 0; + if (errorList && errorList.length > 0) { + for (let i = 0; i < errorList.length; i++) { + if (errorList[i].level === errorLevels.ERROR) { + errorCount++; + } else if (errorList[i].level === errorLevels.WARNING) { + warningCount++; + } + } + } + if (errorCount === 0 && warningCount === 0) { + return null; + } + return { errorCount, warningCount }; + } } export default HeatValidationView; |