Add new code new version
Change-Id: Ic02a76313503b526f17c3df29eb387a29fe6a42a
Signed-off-by: Michael Lando <ml636r@att.com>
diff --git a/openecomp-ui/src/heat.html b/openecomp-ui/src/heat.html
new file mode 100644
index 0000000..3334074
--- /dev/null
+++ b/openecomp-ui/src/heat.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Heat Validation</title>
+</head>
+
+<body>
+<div id="heat-validation-app"></div>
+</body>
+
+<script src="heat-validation_en.js"></script>
+</html>
diff --git a/openecomp-ui/src/index.html b/openecomp-ui/src/index.html
new file mode 100644
index 0000000..7f4fe04
--- /dev/null
+++ b/openecomp-ui/src/index.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta http-equiv="Content-type" content="text/html"/>
+ <link rel="SHORTCUT ICON" href="images/icons/favicon.png"/>
+ <title>Service Design & Creation</title>
+</head>
+<body>
+<div id="sdc-app" class="sdc-app"></div>
+
+<script>
+
+(function(){
+
+ /**
+ * Bundle Import script( By Language)!
+ */
+
+ var DEFAULT_LANG = 'en';
+ var lang = localStorage.getItem('user_locale') || ((navigator && (navigator.language || navigator.userLanguage)) || DEFAULT_LANG).toLowerCase();
+
+ function writeAppBundle() {
+ var supportedLangs = [
+ //<!--prod:supported-langs--><!--/prod:supported-langs-->
+ ];
+ if(-1 === supportedLangs.indexOf(lang)) {
+ lang = DEFAULT_LANG;
+ }
+
+ var bundleScript = document.createElement('script');
+ bundleScript.src = 'bundle_' + lang + '.js';
+ document.write(bundleScript.outerHTML);
+ }
+
+ writeAppBundle();
+
+})()
+</script>
+
+</body>
+</html>
diff --git a/openecomp-ui/src/nfvo-components/SubmitErrorResponse.jsx b/openecomp-ui/src/nfvo-components/SubmitErrorResponse.jsx
new file mode 100644
index 0000000..f2ec158
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/SubmitErrorResponse.jsx
@@ -0,0 +1,133 @@
+import React, {Component} from 'react';
+import ListGroupItem from 'react-bootstrap/lib/ListGroupItem.js';
+import ListGroup from 'react-bootstrap/lib/ListGroup.js';
+import Panel from 'react-bootstrap/lib/Panel.js';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+/**
+ * parsing and showing the following Java Response object
+ *
+ * public class ValidationResponse {
+ private boolean valid = true;
+ private Collection<ErrorCode> vspErrors;
+ private Collection<ErrorCode> licensingDataErrors;
+ private Map<String, List<ErrorMessage>> uploadDataErrors;
+ private Map<String, List<ErrorMessage>> compilationErrors;
+ private QuestionnaireValidationResult questionnaireValidationResult;
+ }
+
+ * public class ErrorCode {
+ private String id;
+ private String message;
+ private ErrorCategory category;
+ }
+
+ * public class ErrorMessage {
+ private final ErrorLevel level;
+ private final String message;
+ }
+ */
+class SubmitErrorResponse extends Component {
+
+
+ render() {
+ let {validationResponse} = this.props;
+ return (
+ <div className='submit-error-response-view'>
+ {validationResponse.vspErrors && this.renderVspErrors(validationResponse.vspErrors)}
+ {validationResponse.licensingDataErrors && this.renderVspErrors(validationResponse.licensingDataErrors)}
+ {validationResponse.compilationErrors && this.renderCompilationErrors(validationResponse.compilationErrors)}
+ {validationResponse.uploadDataErrors && this.renderUploadDataErrors(validationResponse.uploadDataErrors)}
+ {validationResponse.questionnaireValidationResult && this.renderQuestionnaireValidationResult(validationResponse.questionnaireValidationResult)}
+ </div>
+ );
+ }
+
+ renderVspErrors(vspErrors) {
+ return (
+ <Panel header={i18n('VSP Errors')} collapsible>{this.parseErrorCodeCollection(vspErrors)}</Panel>
+ );
+ }
+
+ renderLicensingDataErrors(licensingDataErrors) {
+ return (
+ <Panel
+ header={i18n('Licensing Data Errors')}
+ collapsible>{this.parseErrorCodeCollection(licensingDataErrors)}
+ </Panel>
+ );
+ }
+
+ renderUploadDataErrors(uploadDataErrors) {
+ return (
+ <Panel
+ header={i18n('Upload Data Errors')}
+ collapsible>{this.parseMapOfErrorMessagesList(uploadDataErrors)}
+ </Panel>
+ );
+ }
+
+ renderCompilationErrors(compilationErrors) {
+ return (
+ <Panel
+ header={i18n('Compilation Errors')}
+ collapsible>{this.parseMapOfErrorMessagesList(compilationErrors)}
+ </Panel>
+ );
+ }
+
+ parseErrorCodeCollection(errors) {
+ return (
+ <ListGroup>{errors.map(error =>
+ <ListGroupItem className='error-code-list-item'>
+ <div><span>{i18n('Category: ')}</span>{error.category}</div>
+ <div><span>{i18n('Message: ')}</span>{error.message}</div>
+ </ListGroupItem>
+ )}</ListGroup>
+ );
+ }
+
+ parseMapOfErrorMessagesList(errorMap) {
+ return (
+ <ListGroup>
+ {Object.keys(errorMap).map(errorStringKey =>
+ <Panel header={errorStringKey} collapsible>
+ <ListGroup>{errorMap[errorStringKey].map(error =>
+ <ListGroupItem className='error-code-list-item'>
+ <div><span>{i18n('Level: ')}</span>{error.level}</div>
+ <div><span>{i18n('Message: ')}</span>{error.message}</div>
+ </ListGroupItem>
+ )}</ListGroup>
+ </Panel>
+ )}
+ </ListGroup>
+ );
+ }
+
+
+ renderQuestionnaireValidationResult(questionnaireValidationResult) {
+ if (!questionnaireValidationResult.valid) {
+ return this.parseAndRenderCompositionEntityValidationData(questionnaireValidationResult.validationData);
+ }
+ }
+
+ parseAndRenderCompositionEntityValidationData(validationData) {
+ let {entityType, entityId, errors = [], subEntitiesValidationData = []} = validationData;
+ return (
+ <ListGroup>
+ <Panel header={`${entityType}: ${entityId}`} collapsible>
+ <ListGroup>{errors.map(error =>
+ <ListGroupItem className='error-code-list-item'>
+ <div>{error}</div>
+ </ListGroupItem>
+ )}</ListGroup>
+ {subEntitiesValidationData.map(subValidationData => this.parseAndRenderCompositionEntityValidationData(subValidationData))}
+ </Panel>
+ </ListGroup>
+ );
+ }
+
+
+}
+
+export default SubmitErrorResponse;
diff --git a/openecomp-ui/src/nfvo-components/confirmations/ConfirmationModalView.jsx b/openecomp-ui/src/nfvo-components/confirmations/ConfirmationModalView.jsx
new file mode 100644
index 0000000..cc971c6
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/confirmations/ConfirmationModalView.jsx
@@ -0,0 +1,53 @@
+import React from 'react';
+import Button from 'react-bootstrap/lib/Button.js';
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import Modal from 'nfvo-components/modal/Modal.jsx';
+
+let typeClass = {
+ 'default': 'primary',
+ error: 'danger',
+ warning: 'warning',
+ success: 'success'
+};
+
+
+class ConfirmationModalView extends React.Component {
+
+ static propTypes = {
+ show: React.PropTypes.bool,
+ type: React.PropTypes.oneOf(['default', 'error', 'warning', 'success']),
+ msg: React.PropTypes.node,
+ title: React.PropTypes.string,
+ confirmationDetails: React.PropTypes.object,
+ confirmationButtonText: React.PropTypes.string,
+
+ };
+
+ static defaultProps = {
+ show: false,
+ type: 'warning',
+ title: 'Warning',
+ msg: '',
+ confirmationButtonText: i18n('Delete')
+ };
+
+ render() {
+ let {title, type, msg, show, confirmationButtonText} = this.props;
+
+ return(
+ <Modal show={show} className={`notification-modal ${typeClass[type]}`}>
+ <Modal.Header>
+ <Modal.Title>{title}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>{msg}</Modal.Body>
+ <Modal.Footer>
+ <Button bsStyle={typeClass[type]} onClick={() => this.props.onDeclined(this.props.confirmationDetails)}>{i18n('Cancel')}</Button>
+ <Button bsStyle={typeClass[type]} onClick={() => this.props.onConfirmed(this.props.confirmationDetails)}>{confirmationButtonText}</Button>
+ </Modal.Footer>
+ </Modal>
+ );
+ };
+}
+
+export default ConfirmationModalView;
diff --git a/openecomp-ui/src/nfvo-components/editor/TabulatedEditor.jsx b/openecomp-ui/src/nfvo-components/editor/TabulatedEditor.jsx
new file mode 100644
index 0000000..4a106b5
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/editor/TabulatedEditor.jsx
@@ -0,0 +1,71 @@
+import React from 'react';
+import classnames from 'classnames';
+
+import VersionController from 'nfvo-components/panel/versionController/VersionController.jsx';
+import NavigationSideBar from 'nfvo-components/panel/NavigationSideBar.jsx';
+
+export default class TabulatedEditor extends React.Component {
+
+ render() {
+ const {versionControllerProps, navigationBarProps, onToggle, onVersionSwitching, onCreate, onSave, onClose, onVersionControllerAction, onNavigate, children} = this.props;
+ const {className = ''} = React.Children.only(children).props;
+ const child = this.prepareChild();
+
+ return (
+ <div className='software-product-view'>
+ <div className='software-product-navigation-side-bar'>
+ <NavigationSideBar {...navigationBarProps} onSelect={onNavigate} onToggle={onToggle}/>
+ </div>
+ <div className='software-product-landing-view-right-side flex-column'>
+ <VersionController
+ {...versionControllerProps}
+ onVersionSwitching={version => onVersionSwitching(version)}
+ callVCAction={onVersionControllerAction}
+ onCreate={onCreate && this.handleCreate}
+ onSave={onSave && this.handleSave}
+ onClose={() => onClose(versionControllerProps)}/>
+ <div className={classnames('content-area', `${className}`)}>
+ {
+ child
+ }
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ prepareChild() {
+ const {onSave, onCreate, children} = this.props;
+
+ const additionalChildProps = {ref: 'editor'};
+ if (onSave) {
+ additionalChildProps.onSave = onSave;
+ }
+ if (onCreate) {
+ additionalChildProps.onCreate = onCreate;
+ }
+
+ const child = React.cloneElement(React.Children.only(children), additionalChildProps);
+ return child;
+ }
+
+
+
+ handleSave = () => {
+ const childInstance = this.refs.editor.getWrappedInstance();
+ if (childInstance.save) {
+ return childInstance.save();
+ } else {
+ return this.props.onSave();
+ }
+ };
+
+ handleCreate = () => {
+ const childInstance = this.refs.editor.getWrappedInstance();
+ if (childInstance.create) {
+ childInstance.create();
+ } else {
+ this.props.onCreate();
+ }
+ }
+}
diff --git a/openecomp-ui/src/nfvo-components/input/ExpandableInput.jsx b/openecomp-ui/src/nfvo-components/input/ExpandableInput.jsx
new file mode 100644
index 0000000..3ac3fca
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/input/ExpandableInput.jsx
@@ -0,0 +1,77 @@
+import React from 'react';
+import FontAwesome from 'react-fontawesome';
+import classnames from 'classnames';
+import Input from 'react-bootstrap/lib/Input';
+
+
+class ExpandableInput extends React.Component {
+ constructor(props){
+ super(props);
+ this.state = {showInput: false, value: ''};
+ this.toggleInput = this.toggleInput.bind(this);
+ this.handleFocus = this.handleFocus.bind(this);
+ this.handleInput = this.handleInput.bind(this);
+ this.handleClose = this.handleClose.bind(this);
+ }
+
+ toggleInput(){
+ if (!this.state.showInput){
+ this.searchInputNode.refs.input.focus();
+ } else {
+ this.setState({showInput: false});
+ }
+ }
+
+ handleInput(e){
+ let {onChange} = this.props;
+
+ this.setState({value: e.target.value});
+ onChange(e);
+ }
+
+ handleClose(){
+ this.handleInput({target: {value: ''}});
+ this.searchInputNode.refs.input.focus();
+ }
+
+ handleFocus(){
+ if (!this.state.showInput){
+ this.setState({showInput: true});
+ }
+ }
+
+ getValue(){
+ return this.state.value;
+ }
+
+ render(){
+ let {iconType} = this.props;
+
+ let inputClasses = classnames({
+ 'expandable-active': this.state.showInput,
+ 'expandable-not-active': !this.state.showInput
+ });
+
+ let iconClasses = classnames(
+ 'expandable-icon',
+ {'expandable-icon-active': this.state.showInput}
+ );
+
+ return (
+ <div className='expandable-input-wrapper'>
+ <Input
+ type='text'
+ value={this.state.value}
+ ref={(input) => this.searchInputNode = input}
+ className={inputClasses}
+ groupClassName='expandable-input-control'
+ onChange={e => this.handleInput(e)}
+ onFocus={this.handleFocus}/>
+ {this.state.showInput && this.state.value && <FontAwesome onClick={this.handleClose} name='close' className='expandable-close-button'/>}
+ {!this.state.value && <FontAwesome onClick={this.toggleInput} name={iconType} className={iconClasses}/>}
+ </div>
+ );
+ }
+}
+
+export default ExpandableInput;
diff --git a/openecomp-ui/src/nfvo-components/input/SelectInput.jsx b/openecomp-ui/src/nfvo-components/input/SelectInput.jsx
new file mode 100644
index 0000000..1036ac4
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/input/SelectInput.jsx
@@ -0,0 +1,52 @@
+/**
+ * The HTML structure here is aligned with bootstrap HTML structure for form elements.
+ * In this way we have proper styling and it is aligned with other form elements on screen.
+ *
+ * Select and MultiSelect options:
+ *
+ * label - the label to be shown which paired with the input
+ *
+ * all other "react-select" props - as documented on
+ * http://jedwatson.github.io/react-select/
+ * or
+ * https://github.com/JedWatson/react-select
+ */
+import React, {Component} from 'react';
+import Select from 'react-select';
+
+class SelectInput extends Component {
+
+ inputValue = [];
+
+ render() {
+ let {label, value, ...other} = this.props;
+ return (
+ <div className='validation-input-wrapper dropdown-multi-select'>
+ <div className='form-group'>
+ {label && <label className='control-label'>{label}</label>}
+ <Select ref='_myInput' onChange={value => this.onSelectChanged(value)} {...other} value={value} />
+ </div>
+ </div>
+ );
+ }
+
+ getValue() {
+ return this.inputValue && this.inputValue.length ? this.inputValue : '';
+ }
+
+ onSelectChanged(value) {
+ this.props.onMultiSelectChanged(value);
+ }
+
+ componentDidMount() {
+ let {value} = this.props;
+ this.inputValue = value ? value : [];
+ }
+ componentDidUpdate() {
+ if (this.inputValue !== this.props.value) {
+ this.inputValue = this.props.value;
+ }
+ }
+}
+
+export default SelectInput;
diff --git a/openecomp-ui/src/nfvo-components/input/ToggleInput.jsx b/openecomp-ui/src/nfvo-components/input/ToggleInput.jsx
new file mode 100644
index 0000000..873d3de
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/input/ToggleInput.jsx
@@ -0,0 +1,53 @@
+import React from 'react';
+
+export default
+class ToggleInput extends React.Component {
+
+ static propTypes = {
+ label: React.PropTypes.node,
+ value: React.PropTypes.bool,
+ onChange: React.PropTypes.func,
+ disabled: React.PropTypes.bool
+ }
+
+ static defaultProps = {
+ value: false,
+ label: ''
+ }
+
+ state = {
+ value: this.props.value
+ }
+
+ status() {
+ return this.state.value ? 'on' : 'off';
+ }
+
+ render() {
+ let {label, disabled} = this.props;
+ let checked = this.status() === 'on';
+ return (
+ <div className='toggle-input-wrapper form-group' onClick={!disabled && this.click}>
+ <div className='toggle-input-label'>{label}</div>
+ <div className='toggle-switch'>
+ <input className='toggle toggle-round-flat' type='checkbox' checked={checked} readOnly/>
+ <label></label>
+ </div>
+ </div>
+ );
+ }
+
+ click = () => {
+ let value = !this.state.value;
+ this.setState({value});
+
+ let onChange = this.props.onChange;
+ if (onChange) {
+ onChange(value);
+ }
+ }
+
+ getValue() {
+ return this.state.value;
+ }
+}
diff --git a/openecomp-ui/src/nfvo-components/input/dualListbox/DualListboxView.jsx b/openecomp-ui/src/nfvo-components/input/dualListbox/DualListboxView.jsx
new file mode 100644
index 0000000..171bead
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/input/dualListbox/DualListboxView.jsx
@@ -0,0 +1,132 @@
+import React from 'react';
+import FontAwesome from 'react-fontawesome';
+import Input from 'react-bootstrap/lib/Input.js';
+
+class DualListboxView extends React.Component {
+
+ static propTypes = {
+
+ availableList: React.PropTypes.arrayOf(React.PropTypes.shape({
+ id: React.PropTypes.string.isRequired,
+ name: React.PropTypes.string.isRequired
+ })),
+ filterTitle: React.PropTypes.shape({
+ left: React.PropTypes.string,
+ right: React.PropTypes.string
+ }),
+ selectedValuesList: React.PropTypes.arrayOf(React.PropTypes.string),
+
+ onChange: React.PropTypes.func.isRequired
+ };
+
+ static defaultProps = {
+ selectedValuesList: [],
+ availableList: [],
+ filterTitle: {
+ left: '',
+ right: ''
+ }
+ };
+
+ state = {
+ availableListFilter: '',
+ selectedValuesListFilter: ''
+ };
+
+ static contextTypes = {
+ isReadOnlyMode: React.PropTypes.bool
+ };
+
+ render() {
+ let {availableList, selectedValuesList, filterTitle} = this.props;
+ let {availableListFilter, selectedValuesListFilter} = this.state;
+ let isReadOnlyMode = this.context.isReadOnlyMode;
+
+ let unselectedList = availableList.filter(availableItem => !selectedValuesList.find(value => value === availableItem.id));
+ let selectedList = availableList.filter(availableItem => selectedValuesList.find(value => value === availableItem.id));
+ selectedList = selectedList.sort((a, b) => selectedValuesList.indexOf(a.id) - selectedValuesList.indexOf(b.id));
+
+ return (
+ <div className='dual-list-box'>
+ {this.renderListbox(filterTitle.left, unselectedList, {
+ value: availableListFilter,
+ ref: 'availableListFilter',
+ disabled: isReadOnlyMode,
+ onChange: () => this.setState({availableListFilter: this.refs.availableListFilter.getValue()})
+ }, {ref: 'availableValues', disabled: isReadOnlyMode})}
+ {this.renderOperationsBar(isReadOnlyMode)}
+ {this.renderListbox(filterTitle.right, selectedList, {
+ value: selectedValuesListFilter,
+ ref: 'selectedValuesListFilter',
+ disabled: isReadOnlyMode,
+ onChange: () => this.setState({selectedValuesListFilter: this.refs.selectedValuesListFilter.getValue()})
+ }, {ref: 'selectedValues', disabled: isReadOnlyMode})}
+ </div>
+ );
+ }
+
+ renderListbox(filterTitle, list, filterProps, props) {
+ let regExFilter = new RegExp(escape(filterProps.value), 'i');
+ let matchedItems = list.filter(item => item.name.match(regExFilter));
+ let unMatchedItems = list.filter(item => !item.name.match(regExFilter));
+
+
+ return (
+ <div className='dual-search-multi-select-section'>
+ <p>{filterTitle}</p>
+ <div className='dual-text-box-search search-wrapper'>
+ <Input name='search-input-control' type='text' groupClassName='search-input-control' {...filterProps}/>
+ <FontAwesome name='search' className='search-icon'/>
+ </div>
+ <Input
+ multiple
+ groupClassName='dual-list-box-multi-select'
+ type='select'
+ name='dual-list-box-multi-select'
+ {...props}>
+ {matchedItems.map(item => this.renderOption(item.id, item.name))}
+ {matchedItems.length && unMatchedItems.length && <option style={{pointerEvents: 'none'}}>--------------------</option>}
+ {unMatchedItems.map(item => this.renderOption(item.id, item.name))}
+ </Input>
+ </div>
+ );
+ }
+
+ renderOption(value, name) {
+ return (<option className='dual-list-box-multi-select-text' key={value} value={value}>{name}</option>);
+ }
+
+ renderOperationsBar(isReadOnlyMode) {
+ return (
+ <div className={`dual-list-options-bar${isReadOnlyMode ? ' disabled' : ''}`}>
+ {this.renderOperationBarButton(() => this.addToSelectedList(), 'angle-right')}
+ {this.renderOperationBarButton(() => this.removeFromSelectedList(), 'angle-left')}
+ {this.renderOperationBarButton(() => this.addAllToSelectedList(), 'angle-double-right')}
+ {this.renderOperationBarButton(() => this.removeAllFromSelectedList(), 'angle-double-left')}
+ </div>
+ );
+ }
+
+ renderOperationBarButton(onClick, fontAwesomeIconName){
+ return (<div className='dual-list-option' onClick={onClick}><FontAwesome name={fontAwesomeIconName}/></div>);
+ }
+
+ addToSelectedList() {
+ this.props.onChange(this.props.selectedValuesList.concat(this.refs.availableValues.getValue()));
+ }
+
+ removeFromSelectedList() {
+ const selectedValues = this.refs.selectedValues.getValue();
+ this.props.onChange(this.props.selectedValuesList.filter(value => !selectedValues.find(selectedValue => selectedValue === value)));
+ }
+
+ addAllToSelectedList() {
+ this.props.onChange(this.props.availableList.map(item => item.id));
+ }
+
+ removeAllFromSelectedList() {
+ this.props.onChange([]);
+ }
+}
+
+export default DualListboxView;
diff --git a/openecomp-ui/src/nfvo-components/input/inputOptions/InputOptions.jsx b/openecomp-ui/src/nfvo-components/input/inputOptions/InputOptions.jsx
new file mode 100644
index 0000000..5daaffe
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/input/inputOptions/InputOptions.jsx
@@ -0,0 +1,221 @@
+import React from 'react';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import classNames from 'classnames';
+import Select from 'nfvo-components/input/SelectInput.jsx';
+
+export const other = {OTHER: 'Other'};
+
+class InputOptions extends React.Component {
+
+ static propTypes = {
+ values: React.PropTypes.arrayOf(React.PropTypes.shape({
+ enum: React.PropTypes.string,
+ title: React.PropTypes.string
+ })),
+ isEnabledOther: React.PropTypes.bool,
+ title: React.PropTypes.string,
+ selectedValue: React.PropTypes.string,
+ multiSelectedEnum: React.PropTypes.array,
+ selectedEnum: React.PropTypes.string,
+ otherValue: React.PropTypes.string,
+ onEnumChange: React.PropTypes.func,
+ onOtherChange: React.PropTypes.func,
+ isRequired: React.PropTypes.bool,
+ isMultiSelect: React.PropTypes.bool
+ };
+
+
+ static contextTypes = {
+ isReadOnlyMode: React.PropTypes.bool
+ };
+
+ state = {
+ otherInputDisabled: !this.props.otherValue
+ };
+
+ oldProps = {
+ selectedEnum: '',
+ otherValue: '',
+ multiSelectedEnum: []
+ };
+
+ render() {
+ let {label, isRequired, values, otherValue, onOtherChange, isMultiSelect, onBlur, multiSelectedEnum, selectedEnum, hasError, validations, children} = this.props;
+
+ let currentMultiSelectedEnum = [];
+ let currentSelectedEnum = '';
+ let {otherInputDisabled} = this.state;
+ if (isMultiSelect) {
+ currentMultiSelectedEnum = multiSelectedEnum;
+ if(!otherInputDisabled) {
+ currentSelectedEnum = multiSelectedEnum ? multiSelectedEnum.toString() : undefined;
+ }
+ }
+ else if(selectedEnum){
+ currentSelectedEnum = selectedEnum;
+ }
+
+ let isReadOnlyMode = this.context.isReadOnlyMode;
+
+ return(
+ <div className={classNames('form-group', {'required' : validations.required , 'has-error' : hasError})}>
+ {label && <label className='control-label'>{label}</label>}
+ {isMultiSelect && otherInputDisabled ?
+ <Select
+ ref='_myInput'
+ value={currentMultiSelectedEnum}
+ className='options-input'
+ clearable={false}
+ required={isRequired}
+ disabled={isReadOnlyMode || Boolean(this.props.disabled)}
+ onBlur={() => onBlur()}
+ onMultiSelectChanged={value => this.multiSelectEnumChanged(value)}
+ options={this.renderMultiSelectOptions(values)}
+ multi/> :
+ <div className={classNames('input-options',{'has-error' : hasError})}>
+ <select
+ ref={'_myInput'}
+ label={label}
+ className='form-control input-options-select'
+ value={currentSelectedEnum}
+ style={{'width' : otherInputDisabled ? '100%' : '95px'}}
+ onBlur={() => onBlur()}
+ disabled={isReadOnlyMode || Boolean(this.props.disabled)}
+ onChange={ value => this.enumChanged(value)}
+ type='select'>
+ {values && values.length && values.map(val => this.renderOptions(val))}
+ {onOtherChange && <option key='other' value={other.OTHER}>{i18n(other.OTHER)}</option>}
+ {children}
+ </select>
+
+ {!otherInputDisabled && <div className='input-options-separator'/>}
+ <input
+ className='form-control input-options-other'
+ placeholder={i18n('other')}
+ ref='_otherValue'
+ style={{'display' : otherInputDisabled ? 'none' : 'block'}}
+ disabled={isReadOnlyMode || Boolean(this.props.disabled)}
+ value={otherValue || ''}
+ onBlur={() => onBlur()}
+ onChange={() => this.changedOtherInput()}/>
+ </div>
+ }
+ </div>
+ );
+ }
+
+ renderOptions(val){
+ return(
+ <option key={val.enum} value={val.enum}>{val.title}</option>
+ );
+ }
+
+
+ renderMultiSelectOptions(values) {
+ let {onOtherChange} = this.props;
+ let optionsList = [];
+ if (onOtherChange) {
+ optionsList = values.map(option => {
+ return {
+ label: option.title,
+ value: option.enum,
+ };
+ }).concat([{
+ label: i18n(other.OTHER),
+ value: i18n(other.OTHER),
+ }]);
+ }
+ else {
+ optionsList = values.map(option => {
+ return {
+ label: option.title,
+ value: option.enum,
+ };
+ });
+ }
+ if (optionsList.length > 0 && optionsList[0].value === '') {
+ optionsList.shift();
+ }
+ return optionsList;
+ }
+
+ getValue() {
+ let res = '';
+ let {isMultiSelect} = this.props;
+ let {otherInputDisabled} = this.state;
+
+ if (otherInputDisabled) {
+ res = isMultiSelect ? this.refs._myInput.getValue() : this.refs._myInput.value;
+ } else {
+ res = this.refs._otherValue.value;
+ }
+ return res;
+ }
+
+ enumChanged() {
+ let enumValue = this.refs._myInput.value;
+ let {onEnumChange, isMultiSelect, onChange} = this.props;
+ this.setState({
+ otherInputDisabled: enumValue !== other.OTHER
+ });
+
+ let value = isMultiSelect ? [enumValue] : enumValue;
+ if (onEnumChange) {
+ onEnumChange(value);
+ }
+ if (onChange) {
+ onChange(value);
+ }
+ }
+
+ multiSelectEnumChanged(enumValue) {
+ let {onEnumChange} = this.props;
+ let selectedValues = enumValue.map(enumVal => {
+ return enumVal.value;
+ });
+
+ if (this.state.otherInputDisabled === false) {
+ selectedValues.shift();
+ }
+ else if (selectedValues.includes(i18n(other.OTHER))) {
+ selectedValues = [i18n(other.OTHER)];
+ }
+
+ this.setState({
+ otherInputDisabled: !selectedValues.includes(i18n(other.OTHER))
+ });
+ onEnumChange(selectedValues);
+ }
+
+ changedOtherInput() {
+ let {onOtherChange} = this.props;
+ onOtherChange(this.refs._otherValue.value);
+ }
+
+ componentDidUpdate() {
+ let {otherValue, selectedEnum, onInputChange, multiSelectedEnum} = this.props;
+ if (this.oldProps.otherValue !== otherValue
+ || this.oldProps.selectedEnum !== selectedEnum
+ || this.oldProps.multiSelectedEnum !== multiSelectedEnum) {
+ this.oldProps = {
+ otherValue,
+ selectedEnum,
+ multiSelectedEnum
+ };
+ onInputChange();
+ }
+ }
+
+ static getTitleByName(values, name) {
+ for (let key of Object.keys(values)) {
+ let option = values[key].find(option => option.enum === name);
+ if (option) {
+ return option.title;
+ }
+ }
+ return name;
+ }
+
+}
+
+export default InputOptions;
diff --git a/openecomp-ui/src/nfvo-components/input/validation/ValidationButtons.jsx b/openecomp-ui/src/nfvo-components/input/validation/ValidationButtons.jsx
new file mode 100644
index 0000000..a87c8d6
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/input/validation/ValidationButtons.jsx
@@ -0,0 +1,40 @@
+/**
+ * Holds the buttons for save/reset for forms.
+ * Used by the ValidationForm that changes the state of the buttons according to its own state.
+ *
+ * properties:
+ * labledButtons - whether or not to use labeled buttons or icons only
+ */
+import React from 'react';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import Button from 'react-bootstrap/lib/Button.js';
+import FontAwesome from 'react-fontawesome';
+
+class ValidationButtons extends React.Component {
+
+ static propTypes = {
+ labledButtons: React.PropTypes.bool.isRequired,
+ isReadOnlyMode: React.PropTypes.bool
+ };
+
+ state = {
+ isValid: this.props.formValid
+ };
+
+ render() {
+ var submitBtn = this.props.labledButtons ? i18n('Save') : <FontAwesome className='check' name='check'/>;
+ var closeBtn = this.props.labledButtons ? i18n('Cancel') : <FontAwesome className='close' name='close'/>;
+ return (
+ <div className='validation-buttons'>
+ {!this.props.isReadOnlyMode ?
+ <div>
+ <Button bsStyle='primary' ref='submitbutton' type='submit' disabled={!this.state.isValid}>{submitBtn}</Button>
+ <Button type='reset'>{closeBtn}</Button>
+ </div>
+ : <Button type='reset'>{i18n('Close')}</Button>
+ }
+ </div>
+ );
+ }
+}
+export default ValidationButtons;
diff --git a/openecomp-ui/src/nfvo-components/input/validation/ValidationForm.jsx b/openecomp-ui/src/nfvo-components/input/validation/ValidationForm.jsx
new file mode 100644
index 0000000..098ccf1
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/input/validation/ValidationForm.jsx
@@ -0,0 +1,200 @@
+/**
+ * ValidationForm should be used in order to have a form that handles it's internal validation state.
+ * All ValidationInputs inside the form are checked for validity and the styling and submit buttons
+ * are updated accordingly.
+ *
+ * The properties that ahould be given to the form:
+ * labledButtons - whether or not use icons only as the form default buttons or use buttons with labels
+ * onSubmit - function for click on the submit button
+ * onReset - function for click on the reset button
+ */
+import React from 'react';
+import JSONSchema from 'nfvo-utils/json/JSONSchema.js';
+import JSONPointer from 'nfvo-utils/json/JSONPointer.js';
+import ValidationButtons from './ValidationButtons.jsx';
+
+class ValidationForm extends React.Component {
+
+ static childContextTypes = {
+ validationParent: React.PropTypes.any,
+ isReadOnlyMode: React.PropTypes.bool,
+ validationSchema: React.PropTypes.instanceOf(JSONSchema),
+ validationData: React.PropTypes.object
+ };
+
+ static defaultProps = {
+ hasButtons : true,
+ onSubmit : null,
+ onReset : null,
+ labledButtons: true,
+ onValidChange : null,
+ isValid: true
+ };
+
+ static propTypes = {
+ isValid : React.PropTypes.bool,
+ isReadOnlyMode : React.PropTypes.bool,
+ hasButtons : React.PropTypes.bool,
+ onSubmit : React.PropTypes.func,
+ onReset : React.PropTypes.func,
+ labledButtons: React.PropTypes.bool,
+ onValidChange : React.PropTypes.func,
+ onValidityChanged: React.PropTypes.func,
+ schema: React.PropTypes.object,
+ data: React.PropTypes.object
+ };
+
+ state = {
+ isValid: this.props.isValid
+ };
+
+ constructor(props) {
+ super(props);
+ this.validationComponents = [];
+ }
+
+ componentWillMount() {
+ let {schema, data} = this.props;
+ if (schema) {
+ this.processSchema(schema, data);
+ }
+ }
+
+ componentWillReceiveProps(nextProps) {
+ let {schema, data} = this.props;
+ let {schema: nextSchema, data: nextData} = nextProps;
+
+ if (schema !== nextSchema || data !== nextData) {
+ if (!schema || !nextSchema) {
+ throw new Error('ValidationForm: dynamically adding/removing schema is not supported');
+ }
+
+ if (schema !== nextSchema) {
+ this.processSchema(nextSchema, nextData);
+ } else {
+ this.setState({data: nextData});
+ }
+ }
+ }
+
+ processSchema(rawSchema, rawData) {
+ let schema = new JSONSchema();
+ schema.setSchema(rawSchema);
+ let data = schema.processData(rawData);
+ this.setState({
+ schema,
+ data
+ });
+ }
+
+ render() {
+ // eslint-disable-next-line no-unused-vars
+ let {isValid, isReadOnlyMode, hasButtons, onSubmit, labledButtons, onValidChange, onValidityChanged, schema, data, children, ...formProps} = this.props;
+ return (
+ <form {...formProps} onSubmit={event => this.handleFormSubmit(event)}>
+ <div className='validation-form-content'>{children}</div>
+ {hasButtons && <ValidationButtons labledButtons={labledButtons} ref='buttons' isReadOnlyMode={isReadOnlyMode}/>}
+ </form>
+ );
+ }
+
+ handleFormSubmit(event) {
+ event.preventDefault();
+ let isFormValid = true;
+ this.validationComponents.forEach(validationComponent => {
+ const isInputValid = validationComponent.validate().isValid;
+ isFormValid = isInputValid && isFormValid;
+ });
+ if(isFormValid && this.props.onSubmit) {
+ return this.props.onSubmit(event);
+ } else if(!isFormValid) {
+ this.setState({isValid: false});
+ }
+ }
+
+ componentWillUpdate(nextProps, nextState) {
+ if(this.state.isValid !== nextState.isValid && this.props.onValidityChanged) {
+ this.props.onValidityChanged(nextState.isValid);
+ }
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ // only handling this programatically if the validation of the form is done outside of the view
+ // (example with a form that is dependent on the state of other forms)
+ if (prevProps.isValid !== this.props.isValid) {
+ if (this.props.hasButtons) {
+ this.refs.buttons.setState({isValid: this.state.isValid});
+ }
+ } else if(this.state.isValid !== prevState.isValid) {
+ if (this.props.hasButtons) {
+ this.refs.buttons.setState({isValid: this.state.isValid});
+ }
+ // callback in case form is part of bigger picture in view
+ if (this.props.onValidChange) {
+ this.props.onValidChange(this.state.isValid);
+ }
+ }
+ }
+
+ componentDidMount() {
+ if (this.props.hasButtons) {
+ this.refs.buttons.setState({isValid: this.state.isValid});
+ }
+ }
+
+
+ getChildContext() {
+ return {
+ validationParent: this,
+ isReadOnlyMode: this.props.isReadOnlyMode,
+ validationSchema: this.state.schema,
+ validationData: this.state.data
+ };
+ }
+
+
+ /***
+ * Used by ValidationInput in order to let the (parent) form know
+ * the valid state. If there is a change in the state of the form,
+ * the buttons will be updated.
+ *
+ * @param validationComponent
+ * @param isValid
+ */
+ childValidStateChanged(validationComponent, isValid) {
+ if (isValid !== this.state.isValid) {
+ let oldState = this.state.isValid;
+ let newState = isValid && this.validationComponents.filter(otherValidationComponent => validationComponent !== otherValidationComponent).every(otherValidationComponent => {
+ return otherValidationComponent.isValid();
+ });
+
+ if (oldState !== newState) {
+ this.setState({isValid: newState});
+ }
+ }
+ }
+
+ register(validationComponent) {
+ if (this.state.schema) {
+ // TODO: register
+ } else {
+ this.validationComponents.push(validationComponent);
+ }
+ }
+
+ unregister(validationComponent) {
+ this.childValidStateChanged(validationComponent, true);
+ this.validationComponents = this.validationComponents.filter(otherValidationComponent => validationComponent !== otherValidationComponent);
+ }
+
+ onValueChanged(pointer, value, isValid, error) {
+ this.props.onDataChanged({
+ data: JSONPointer.setValue(this.props.data, pointer, value),
+ isValid,
+ error
+ });
+ }
+}
+
+
+export default ValidationForm;
diff --git a/openecomp-ui/src/nfvo-components/input/validation/ValidationInput.jsx b/openecomp-ui/src/nfvo-components/input/validation/ValidationInput.jsx
new file mode 100644
index 0000000..0f14307
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/input/validation/ValidationInput.jsx
@@ -0,0 +1,509 @@
+/**
+ * Used for inputs on a validation form.
+ * All properties will be passed on to the input element.
+ *
+ * The following properties can be set for OOB validations and callbacks:
+ - required: Boolean: Should be set to true if the input must have a value
+ - numeric: Boolean : Should be set to true id the input should be an integer
+ - onChange : Function : Will be called to validate the value if the default validations are not sufficient, should return a boolean value
+ indicating whether the value is valid
+ - didUpdateCallback :Function: Will be called after the state has been updated and the component has rerendered. This can be used if
+ there are dependencies between inputs in a form.
+ *
+ * The following properties of the state can be set to determine
+ * the state of the input from outside components:
+ - isValid : Boolean - whether the value is valid
+ - value : value for the input field,
+ - disabled : Boolean,
+ - required : Boolean - whether the input value must be filled out.
+ */
+import React from 'react';
+import ReactDOM from 'react-dom';
+import Validator from 'validator';
+import FormGroup from 'react-bootstrap/lib/FormGroup.js';
+import Input from 'react-bootstrap/lib/Input.js';
+import Overlay from 'react-bootstrap/lib/Overlay.js';
+import Tooltip from 'react-bootstrap/lib/Tooltip.js';
+import isEqual from 'lodash/isEqual.js';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import JSONSchema from 'nfvo-utils/json/JSONSchema.js';
+import JSONPointer from 'nfvo-utils/json/JSONPointer.js';
+
+
+import InputOptions from '../inputOptions/InputOptions.jsx';
+
+const globalValidationFunctions = {
+ required: value => value !== '',
+ maxLength: (value, length) => Validator.isLength(value, {max: length}),
+ minLength: (value, length) => Validator.isLength(value, {min: length}),
+ pattern: (value, pattern) => Validator.matches(value, pattern),
+ numeric: value => {
+ if (value === '') {
+ // to allow empty value which is not zero
+ return true;
+ }
+ return Validator.isNumeric(value);
+ },
+ maxValue: (value, maxValue) => value < maxValue,
+ minValue: (value, minValue) => value >= minValue,
+ alphanumeric: value => Validator.isAlphanumeric(value),
+ alphanumericWithSpaces: value => Validator.isAlphanumeric(value.replace(/ /g, '')),
+ validateName: value => Validator.isAlphanumeric(value.replace(/\s|\.|\_|\-/g, ''), 'en-US'),
+ validateVendorName: value => Validator.isAlphanumeric(value.replace(/[\x7F-\xFF]|\s/g, ''), 'en-US'),
+ freeEnglishText: value => Validator.isAlphanumeric(value.replace(/\s|\.|\_|\-|\,|\(|\)|\?/g, ''), 'en-US'),
+ email: value => Validator.isEmail(value),
+ ip: value => Validator.isIP(value),
+ url: value => Validator.isURL(value)
+};
+
+const globalValidationMessagingFunctions = {
+ required: () => i18n('Field is required'),
+ maxLength: (value, maxLength) => i18n('Field value has exceeded it\'s limit, {maxLength}. current length: {length}', {
+ length: value.length,
+ maxLength
+ }),
+ minLength: (value, minLength) => i18n('Field value should contain at least {minLength} characters.', {minLength}),
+ pattern: (value, pattern) => i18n('Field value should match the pattern: {pattern}.', {pattern}),
+ numeric: () => i18n('Field value should contain numbers only.'),
+ maxValue: (value, maxValue) => i18n('Field value should be less than: {maxValue}.', {maxValue}),
+ minValue: (value, minValue) => i18n('Field value should be at least: {minValue}.', {minValue}),
+ alphanumeric: () => i18n('Field value should contain letters or digits only.'),
+ alphanumericWithSpaces: () => i18n('Field value should contain letters, digits or spaces only.'),
+ validateName: ()=> i18n('Field value should contain English letters, digits , spaces, underscores, dashes and dots only.'),
+ validateVendorName: ()=> i18n('Field value should contain English letters digits and spaces only.'),
+ freeEnglishText: ()=> i18n('Field value should contain English letters, digits , spaces, underscores, dashes and dots only.'),
+ email: () => i18n('Field value should be a valid email address.'),
+ ip: () => i18n('Field value should be a valid ip address.'),
+ url: () => i18n('Field value should be a valid url address.'),
+ general: () => i18n('Field value is invalid.')
+};
+
+class ValidationInput extends React.Component {
+
+ static contextTypes = {
+ validationParent: React.PropTypes.any,
+ isReadOnlyMode: React.PropTypes.bool,
+ validationSchema: React.PropTypes.instanceOf(JSONSchema),
+ validationData: React.PropTypes.object
+ };
+
+ static defaultProps = {
+ onChange: null,
+ disabled: null,
+ didUpdateCallback: null,
+ validations: {},
+ value: ''
+ };
+
+ static propTypes = {
+ type: React.PropTypes.string.isRequired,
+ onChange: React.PropTypes.func,
+ disabled: React.PropTypes.bool,
+ didUpdateCallback: React.PropTypes.func,
+ validations: React.PropTypes.object,
+ isMultiSelect: React.PropTypes.bool,
+ onOtherChange: React.PropTypes.func,
+ pointer: React.PropTypes.string
+ };
+
+
+ state = {
+ isValid: true,
+ style: null,
+ value: this.props.value,
+ error: {},
+ previousErrorMessage: '',
+ wasInvalid: false,
+ validations: this.props.validations,
+ isMultiSelect: this.props.isMultiSelect
+ };
+
+ componentWillMount() {
+ if (this.context.validationSchema) {
+ let {validationSchema: schema, validationData: data} = this.context,
+ {pointer} = this.props;
+
+ if (!schema.exists(pointer)) {
+ console.error(`Field doesn't exists in the schema ${pointer}`);
+ }
+
+ let value = JSONPointer.getValue(data, pointer);
+ if (value === undefined) {
+ value = schema.getDefault(pointer);
+ if (value === undefined) {
+ value = '';
+ }
+ }
+ this.setState({value});
+
+ let enums = schema.getEnum(pointer);
+ if (enums) {
+ let values = enums.map(value => ({enum: value, title: value, groupName: pointer})),
+ isMultiSelect = schema.isArray(pointer);
+
+ if (!isMultiSelect && this.props.type !== 'radiogroup') {
+ values = [{enum: '', title: i18n('Select...')}, ...values];
+ }
+ if (isMultiSelect && Array.isArray(value) && value.length === 0) {
+ value = '';
+ }
+
+ this.setState({
+ isMultiSelect,
+ values,
+ onEnumChange: value => this.changedInputOptions(value),
+ value
+ });
+ }
+
+ this.setState({validations: this.extractValidationsFromSchema(schema, pointer, this.props)});
+ }
+ }
+
+ extractValidationsFromSchema(schema, pointer, props) {
+ /* props are here to get precedence over the scheme definitions */
+ let validations = {};
+
+ if (schema.isRequired(pointer)) {
+ validations.required = true;
+ }
+
+ if (schema.isNumber(pointer)) {
+ validations.numeric = true;
+
+ const maxValue = props.validations.maxValue || schema.getMaxValue(pointer);
+ if (maxValue !== undefined) {
+ validations.maxValue = maxValue;
+ }
+
+ const minValue = props.validations.minValue || schema.getMinValue(pointer);
+ if (minValue !== undefined) {
+ validations.minValue = minValue;
+ }
+ }
+
+
+ if (schema.isString(pointer)) {
+
+ const pattern = schema.getPattern(pointer);
+ if (pattern) {
+ validations.pattern = pattern;
+ }
+
+ const maxLength = schema.getMaxLength(pointer);
+ if (maxLength !== undefined) {
+ validations.maxLength = maxLength;
+ }
+
+ const minLength = schema.getMinLength(pointer);
+ if (minLength !== undefined) {
+ validations.minLength = minLength;
+ }
+ }
+
+ return validations;
+ }
+
+ componentWillReceiveProps({value: nextValue, validations: nextValidations, pointer: nextPointer}, nextContext) {
+ const {validations, value} = this.props;
+ const validationsChanged = !isEqual(validations, nextValidations);
+ if (nextContext.validationSchema) {
+ if (this.props.pointer !== nextPointer ||
+ this.context.validationData !== nextContext.validationData) {
+ let currentValue = JSONPointer.getValue(this.context.validationData, this.props.pointer),
+ nextValue = JSONPointer.getValue(nextContext.validationData, nextPointer);
+ if(nextValue === undefined) {
+ nextValue = '';
+ }
+ if (this.state.isMultiSelect && Array.isArray(nextValue) && nextValue.length === 0) {
+ nextValue = '';
+ }
+ if (currentValue !== nextValue) {
+ this.setState({value: nextValue});
+ }
+ if (validationsChanged) {
+ this.setState({
+ validations: this.extractValidationsFromSchema(nextContext.validationSchema, nextPointer, {validations: nextValidations})
+ });
+ }
+ }
+ } else {
+ if (validationsChanged) {
+ this.setState({validations: nextValidations});
+ }
+ if (this.state.wasInvalid && (value !== nextValue || validationsChanged)) {
+ this.validate(nextValue, nextValidations);
+ } else if (value !== nextValue) {
+ this.setState({value: nextValue});
+ }
+ }
+ }
+
+ shouldTypeBeNumberBySchemeDefinition(pointer) {
+ return this.context.validationSchema &&
+ this.context.validationSchema.isNumber(pointer);
+ }
+
+ hasEnum(pointer) {
+ return this.context.validationSchema &&
+ this.context.validationSchema.getEnum(pointer);
+ }
+
+ render() {
+ let {value, isMultiSelect, values, onEnumChange, style, isValid, validations} = this.state;
+ let {onOtherChange, type, pointer} = this.props;
+ if (this.shouldTypeBeNumberBySchemeDefinition(pointer) && !this.hasEnum(pointer)) {
+ type = 'number';
+ }
+ let props = {...this.props};
+
+ let groupClasses = this.props.groupClassName || '';
+ if (validations.required) {
+ groupClasses += ' required';
+ }
+ let isReadOnlyMode = this.context.isReadOnlyMode;
+
+ if (value === true && (type === 'checkbox' || type === 'radio')) {
+ props.checked = true;
+ }
+ return (
+ <div className='validation-input-wrapper'>
+ {
+ !isMultiSelect && !onOtherChange && type !== 'select' && type !== 'radiogroup'
+ && <Input
+ {...props}
+ type={type}
+ groupClassName={groupClasses}
+ ref={'_myInput'}
+ value={value}
+ disabled={isReadOnlyMode || Boolean(this.props.disabled)}
+ bsStyle={style}
+ onChange={() => this.changedInput()}
+ onBlur={() => this.blurInput()}>
+ {this.props.children}
+ </Input>
+ }
+ {
+ type === 'radiogroup'
+ && <FormGroup>
+ {
+ values.map(val =>
+ <Input disabled={isReadOnlyMode || Boolean(this.props.disabled)}
+ inline={true}
+ ref={'_myInput' + (typeof val.enum === 'string' ? val.enum.replace(/\W/g, '_') : val.enum)}
+ value={val.enum} checked={value === val.enum}
+ type='radio' label={val.title}
+ name={val.groupName}
+ onChange={() => this.changedInput()}/>
+ )
+ }
+ </FormGroup>
+ }
+ {
+ (isMultiSelect || onOtherChange || type === 'select')
+ && <InputOptions
+ onInputChange={() => this.changedInput()}
+ onBlur={() => this.blurInput()}
+ hasError={!isValid}
+ ref={'_myInput'}
+ isMultiSelect={isMultiSelect}
+ values={values}
+ onEnumChange={onEnumChange}
+ selectedEnum={value}
+ multiSelectedEnum={value}
+ {...props} />
+ }
+ {this.renderOverlay()}
+ </div>
+ );
+ }
+
+ renderOverlay() {
+ let position = 'right';
+ if (this.props.type === 'text'
+ || this.props.type === 'email'
+ || this.props.type === 'number'
+ || this.props.type === 'password'
+
+ ) {
+ position = 'bottom';
+ }
+
+ let validationMessage = this.state.error.message || this.state.previousErrorMessage;
+ return (
+ <Overlay
+ show={!this.state.isValid}
+ placement={position}
+ target={() => {
+ let target = ReactDOM.findDOMNode(this.refs._myInput);
+ return target.offsetParent ? target : undefined;
+ }}
+ container={this}>
+ <Tooltip
+ id={`error-${validationMessage.replace(' ', '-')}`}
+ className='validation-error-message'>
+ {validationMessage}
+ </Tooltip>
+ </Overlay>
+ );
+ }
+
+ componentDidMount() {
+ if (this.context.validationParent) {
+ this.context.validationParent.register(this);
+ }
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ if (this.context.validationParent) {
+ if (prevState.isValid !== this.state.isValid) {
+ this.context.validationParent.childValidStateChanged(this, this.state.isValid);
+ }
+ }
+ if (this.props.didUpdateCallback) {
+ this.props.didUpdateCallback();
+ }
+
+ }
+
+ componentWillUnmount() {
+ if (this.context.validationParent) {
+ this.context.validationParent.unregister(this);
+ }
+ }
+
+ isNumberInputElement() {
+ return this.props.type === 'number' || this.refs._myInput.props.type === 'number';
+ }
+
+ /***
+ * Adding same method as the actual input component
+ * @returns {*}
+ */
+ getValue() {
+ if (this.props.type === 'checkbox') {
+ return this.refs._myInput.getChecked();
+ }
+ if (this.props.type === 'radiogroup') {
+ for (let key in this.refs) { // finding the value of the radio button that was checked
+ if (this.refs[key].getChecked()) {
+ return this.refs[key].getValue();
+ }
+ }
+ }
+ if (this.isNumberInputElement()) {
+ return Number(this.refs._myInput.getValue());
+ }
+
+ return this.refs._myInput.getValue();
+ }
+
+ resetValue() {
+ this.setState({value: this.props.value});
+ }
+
+
+ /***
+ * internal method that validated the value. includes callback to the onChange method
+ * @param value
+ * @param validations - map containing validation id and the limitation describing the validation.
+ * @returns {object}
+ */
+ validateValue = (value, validations) => {
+ let {customValidationFunction} = validations;
+ let error = {};
+ let isValid = true;
+ for (let validation in validations) {
+ if ('customValidationFunction' !== validation) {
+ if (validations[validation]) {
+ if (!globalValidationFunctions[validation](value, validations[validation])) {
+ error.id = validation;
+ error.message = globalValidationMessagingFunctions[validation](value, validations[validation]);
+ isValid = false;
+ break;
+ }
+ }
+ } else {
+ let customValidationResult = customValidationFunction(value);
+
+ if (customValidationResult !== true) {
+ error.id = 'custom';
+ isValid = false;
+ if (typeof customValidationResult === 'string') {//custom validation error message supplied.
+ error.message = customValidationResult;
+ } else {
+ error.message = globalValidationMessagingFunctions.general();
+ }
+ break;
+ }
+
+
+ }
+ }
+
+ return {
+ isValid,
+ error
+ };
+ };
+
+ /***
+ * Internal method that handles the change event of the input. validates and updates the state.
+ */
+ changedInput() {
+
+ let {isValid, error} = this.state.wasInvalid ? this.validate() : this.state;
+ let onChange = this.props.onChange;
+ if (onChange) {
+ onChange(this.getValue(), isValid, error);
+ }
+ if (this.context.validationSchema) {
+ let value = this.getValue();
+ if (this.state.isMultiSelect && value === '') {
+ value = [];
+ }
+ if (this.shouldTypeBeNumberBySchemeDefinition(this.props.pointer)) {
+ value = Number(value);
+ }
+ this.context.validationParent.onValueChanged(this.props.pointer, value, isValid, error);
+ }
+ }
+
+ changedInputOptions(value) {
+ this.context.validationParent.onValueChanged(this.props.pointer, value, true);
+ }
+
+ blurInput() {
+ if (!this.state.wasInvalid) {
+ this.setState({wasInvalid: true});
+ }
+
+ let {isValid, error} = !this.state.wasInvalid ? this.validate() : this.state;
+ let onBlur = this.props.onBlur;
+ if (onBlur) {
+ onBlur(this.getValue(), isValid, error);
+ }
+ }
+
+ validate(value = this.getValue(), validations = this.state.validations) {
+ let validationStatus = this.validateValue(value, validations);
+ let {isValid, error} = validationStatus;
+ let _style = isValid ? null : 'error';
+ this.setState({
+ isValid,
+ error,
+ value,
+ previousErrorMessage: this.state.error.message || '',
+ style: _style,
+ wasInvalid: !isValid || this.state.wasInvalid
+ });
+
+ return validationStatus;
+ }
+
+ isValid() {
+ return this.state.isValid;
+ }
+
+}
+export default ValidationInput;
diff --git a/openecomp-ui/src/nfvo-components/input/validation/ValidationTab.jsx b/openecomp-ui/src/nfvo-components/input/validation/ValidationTab.jsx
new file mode 100644
index 0000000..6036518
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/input/validation/ValidationTab.jsx
@@ -0,0 +1,107 @@
+import React from 'react';
+import Tab from 'react-bootstrap/lib/Tab.js';
+
+export default
+class ValidationTab extends React.Component {
+
+ static propTypes = {
+ children: React.PropTypes.node,
+ eventKey: React.PropTypes.any.isRequired,
+ onValidationStateChange: React.PropTypes.func //This property is assigned dynamically via React.cloneElement. lookup ValidationTabs.jsx. therefore it cannot be stated as required!
+ };
+
+ constructor(props) {
+ super(props);
+ this.validationComponents = [];
+ }
+
+ static childContextTypes = {
+ validationParent: React.PropTypes.any
+ };
+
+ static contextTypes = {
+ validationParent: React.PropTypes.any
+ };
+
+ getChildContext() {
+ return {validationParent: this};
+ }
+
+ state = {
+ isValid: true,
+ notifyParent: false
+ };
+
+ componentDidMount() {
+ let validationParent = this.context.validationParent;
+ if (validationParent) {
+ validationParent.register(this);
+ }
+ }
+
+ componentWillUnmount() {
+ let validationParent = this.context.validationParent;
+ if (validationParent) {
+ validationParent.unregister(this);
+ }
+ }
+
+ register(validationComponent) {
+ this.validationComponents.push(validationComponent);
+ }
+
+ unregister(validationComponent) {
+ this.childValidStateChanged(validationComponent, true);
+ this.validationComponents = this.validationComponents.filter(otherValidationComponent => validationComponent !== otherValidationComponent);
+ }
+
+ notifyValidStateChangedToParent(isValid) {
+
+ let validationParent = this.context.validationParent;
+ if (validationParent) {
+ validationParent.childValidStateChanged(this, isValid);
+ }
+ }
+
+ childValidStateChanged(validationComponent, isValid) {
+
+ const currentValidState = this.state.isValid;
+ if (isValid !== currentValidState) {
+ let filteredValidationComponents = this.validationComponents.filter(otherValidationComponent => validationComponent !== otherValidationComponent);
+ let newValidState = isValid && filteredValidationComponents.every(otherValidationComponent => {
+ return otherValidationComponent.isValid();
+ });
+ this.setState({isValid: newValidState, notifyParent: true});
+ }
+ }
+
+ validate() {
+ let isValid = true;
+ this.validationComponents.forEach(validationComponent => {
+ const isValidationComponentValid = validationComponent.validate().isValid;
+ isValid = isValidationComponentValid && isValid;
+ });
+ this.setState({isValid, notifyParent: false});
+ return {isValid};
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ if(prevState.isValid !== this.state.isValid) {
+ if(this.state.notifyParent) {
+ this.notifyValidStateChangedToParent(this.state.isValid);
+ }
+ this.props.onValidationStateChange(this.props.eventKey, this.state.isValid);
+ }
+ }
+
+ isValid() {
+ return this.state.isValid;
+ }
+
+ render() {
+ let {children, ...tabProps} = this.props;
+ return (
+ <Tab {...tabProps}>{children}</Tab>
+ );
+ }
+}
diff --git a/openecomp-ui/src/nfvo-components/input/validation/ValidationTabs.jsx b/openecomp-ui/src/nfvo-components/input/validation/ValidationTabs.jsx
new file mode 100644
index 0000000..6eda4b9
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/input/validation/ValidationTabs.jsx
@@ -0,0 +1,72 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import Tabs from 'react-bootstrap/lib/Tabs.js';
+import Overlay from 'react-bootstrap/lib/Overlay.js';
+import Tooltip from 'react-bootstrap/lib/Tooltip.js';
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+export default
+class ValidationTab extends React.Component {
+
+ static propTypes = {
+ children: React.PropTypes.node
+ };
+
+ state = {
+ invalidTabs: []
+ };
+
+ cloneTab(element) {
+ const {invalidTabs} = this.state;
+ return React.cloneElement(
+ element,
+ {
+ key: element.props.eventKey,
+ tabClassName: invalidTabs.indexOf(element.props.eventKey) > -1 ? 'invalid-tab' : 'valid-tab',
+ onValidationStateChange: (eventKey, isValid) => this.validTabStateChanged(eventKey, isValid)
+ }
+ );
+ }
+
+ validTabStateChanged(eventKey, isValid) {
+ let {invalidTabs} = this.state;
+ let invalidTabIndex = invalidTabs.indexOf(eventKey);
+ if (isValid && invalidTabIndex > -1) {
+ this.setState({invalidTabs: invalidTabs.filter(otherEventKey => eventKey !== otherEventKey)});
+ } else if (!isValid && invalidTabIndex === -1) {
+ this.setState({invalidTabs: [...invalidTabs, eventKey]});
+ }
+ }
+
+ showTabsError() {
+ const {invalidTabs} = this.state;
+ return invalidTabs.length > 0 && (invalidTabs.length > 1 || invalidTabs[0] !== this.props.activeKey);
+ }
+
+ render() {
+ return (
+ <div>
+ <Tabs {...this.props} ref='tabsList'>
+ {this.props.children.map(element => this.cloneTab(element))}
+ </Tabs>
+ <Overlay
+ animation={false}
+ show={this.showTabsError()}
+ placement='bottom'
+ target={() => {
+ let target = ReactDOM.findDOMNode(this.refs.tabsList).querySelector('ul > li.invalid-tab:not(.active):nth-of-type(n)');
+ return target && target.offsetParent ? target : undefined;
+ }
+ }
+ container={this}>
+ <Tooltip
+ id='error-some-tabs-contain-errors'
+ className='validation-error-message'>
+ {i18n('One or more tabs are invalid')}
+ </Tooltip>
+ </Overlay>
+ </div>
+ );
+ }
+}
diff --git a/openecomp-ui/src/nfvo-components/listEditor/ListEditorItemView.jsx b/openecomp-ui/src/nfvo-components/listEditor/ListEditorItemView.jsx
new file mode 100644
index 0000000..e8d0fc2
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/listEditor/ListEditorItemView.jsx
@@ -0,0 +1,47 @@
+import React from 'react';
+import FontAwesome from 'react-fontawesome';
+import store from 'sdc-app/AppStore.js';
+import NotificationConstants from 'nfvo-components/notifications/NotificationConstants.js';
+
+class ListEditorItem extends React.Component {
+ static propTypes = {
+ onSelect: React.PropTypes.func,
+ onDelete: React.PropTypes.func,
+ onEdit: React.PropTypes.func,
+ children: React.PropTypes.node,
+ isReadOnlyMode: React.PropTypes.bool
+ }
+
+ render() {
+ let {onDelete, onSelect, onEdit, children, isReadOnlyMode} = this.props;
+ let isAbilityToDelete = isReadOnlyMode === undefined ? true : !isReadOnlyMode;
+ return (
+ <div className='list-editor-item-view'>
+ <div className='list-editor-item-view-content' onClick={onSelect}>
+ {children}
+ </div>
+ <div className='list-editor-item-view-controller'>
+ {onEdit && <FontAwesome name='sliders' onClick={() => this.onClickedItem(onEdit)}/>}
+ {onDelete && isAbilityToDelete && <FontAwesome name='trash-o' onClick={() => this.onClickedItem(onDelete)}/>}
+ </div>
+ </div>
+ );
+ }
+
+ onClickedItem(callBackFunc) {
+ if(typeof callBackFunc === 'function') {
+ let {isCheckedOut} = this.props;
+ if (isCheckedOut === false) {
+ store.dispatch({
+ type: NotificationConstants.NOTIFY_ERROR,
+ data: {title: 'Error', msg: 'This item is checkedin/submitted, Click Check Out to continue'}
+ });
+ }
+ else {
+ callBackFunc();
+ }
+ }
+ }
+}
+
+export default ListEditorItem;
diff --git a/openecomp-ui/src/nfvo-components/listEditor/ListEditorView.jsx b/openecomp-ui/src/nfvo-components/listEditor/ListEditorView.jsx
new file mode 100644
index 0000000..1ee91f3
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/listEditor/ListEditorView.jsx
@@ -0,0 +1,64 @@
+import React from 'react';
+import FontAwesome from 'react-fontawesome';
+import Input from 'react-bootstrap/lib/Input.js';
+
+
+class ListEditorView extends React.Component {
+
+ static defaultProps = {
+ className: ''
+ };
+
+ static propTypes = {
+ title: React.PropTypes.string,
+ plusButtonTitle: React.PropTypes.string,
+ children: React.PropTypes.node,
+ filterValue: React.PropTypes.string,
+ onFilter: React.PropTypes.func,
+ className: React.PropTypes.string,
+ isReadOnlyMode: React.PropTypes.bool,
+ placeholder: React.PropTypes.string
+ };
+
+ render() {
+ let {title, plusButtonTitle, onAdd, children, filterValue, onFilter, className, placeholder, isReadOnlyMode} = this.props;
+ return (
+ <div className={`list-editor-view ${className}`}>
+ {title && onAdd && <div className='list-editor-view-title'>{title}</div>}
+ <div className='list-editor-view-actions'>
+ {title && !onAdd && <div className='list-editor-view-title-inline'>{title}</div>}
+ <div className={`list-editor-view-add-controller${isReadOnlyMode ? ' disabled' : ''}`} >
+ { onAdd &&
+ <div onClick={onAdd}>
+ <span className='plus-icon-button pull-left'/>
+ <span>{plusButtonTitle}</span>
+ </div>
+ }
+ </div>
+
+ {
+ onFilter &&
+ <div className='list-editor-view-search search-wrapper'>
+ <Input
+ ref='filter'
+ type='text'
+ value={filterValue}
+ name='list-editor-view-search'
+ placeholder={placeholder}
+ groupClassName='search-input-control'
+ onChange={() => onFilter(this.refs.filter.getValue())}/>
+ <FontAwesome name='filter' className='filter-icon'/>
+ </div>
+ }
+ </div>
+ <div className='list-editor-view-list-scroller'>
+ <div className='list-editor-view-list'>
+ {children}
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+}
+export default ListEditorView;
diff --git a/openecomp-ui/src/nfvo-components/loader/Loader.jsx b/openecomp-ui/src/nfvo-components/loader/Loader.jsx
new file mode 100644
index 0000000..cc1ffdb
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/loader/Loader.jsx
@@ -0,0 +1,35 @@
+import React from 'react';
+import {connect} from 'react-redux';
+
+const mapStateToProps = ({loader}) => {
+ return {
+ isLoading: loader.isLoading
+ };
+};
+
+class Loader extends React.Component {
+
+ static propTypes = {
+ isLoading: React.PropTypes.bool.isRequired
+ };
+
+ static defaultProps = {
+ isLoading: false
+ };
+
+ render() {
+ let {isLoading} = this.props;
+
+ return (
+ <div className='onboarding-loader'>
+ {
+ isLoading && <div className='onboarding-loader-backdrop'>
+ <div className='tlv-loader large'></div>
+ </div>
+ }
+ </div>
+ );
+ }
+}
+
+export default connect(mapStateToProps) (Loader);
diff --git a/openecomp-ui/src/nfvo-components/loader/LoaderConstants.js b/openecomp-ui/src/nfvo-components/loader/LoaderConstants.js
new file mode 100644
index 0000000..e8e4953
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/loader/LoaderConstants.js
@@ -0,0 +1,26 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export const actionTypes = keyMirror({
+ SHOW: null,
+ HIDE: null
+});
diff --git a/openecomp-ui/src/nfvo-components/loader/LoaderReducer.js b/openecomp-ui/src/nfvo-components/loader/LoaderReducer.js
new file mode 100644
index 0000000..582eff3
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/loader/LoaderReducer.js
@@ -0,0 +1,32 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './LoaderConstants.js';
+
+export default (state = {}, action) => {
+ switch (action.type) {
+ case actionTypes.SHOW:
+ return {isLoading: true};
+ case actionTypes.HIDE:
+ return {isLoading: false};
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/nfvo-components/modal/Modal.jsx b/openecomp-ui/src/nfvo-components/modal/Modal.jsx
new file mode 100644
index 0000000..be4963e
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/modal/Modal.jsx
@@ -0,0 +1,69 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import BootstrapModal from 'react-bootstrap/lib/Modal.js';
+
+let nextModalId = 0;
+
+export default class Modal extends React.Component {
+
+ static Header = BootstrapModal.Header;
+
+ static Title = BootstrapModal.Title;
+
+ static Footer = BootstrapModal.Footer;
+
+ static Body = class ModalBody extends React.Component {
+
+ render() {
+ let {children, ...props} = this.props;
+ return (
+ <BootstrapModal.Body {...props}>
+ {children}
+ </BootstrapModal.Body>
+ );
+ }
+
+ componentDidMount() {
+ let element = ReactDOM.findDOMNode(this);
+ element.addEventListener('click', event => {
+ if (event.target.tagName === 'A') {
+ event.preventDefault();
+ }
+ });
+ ['wheel', 'mousewheel', 'DOMMouseScroll'].forEach(eventType =>
+ element.addEventListener(eventType, event => event.stopPropagation())
+ );
+ }
+ };
+
+ componentWillMount() {
+ this.modalId = `dox-ui-modal-${nextModalId++}`;
+ }
+
+ componentDidMount() {
+ this.ensureRootClass();
+ }
+
+ componentDidUpdate() {
+ this.ensureRootClass();
+ }
+
+ ensureRootClass() {
+ let element = document.getElementById(this.modalId);
+ while(element && !element.hasAttribute('data-reactroot')) {
+ element = element.parentElement;
+ }
+ if (element && !element.classList.contains('dox-ui')) {
+ element.classList.add('dox-ui');
+ }
+ }
+
+ render() {
+ let {children, ...props} = this.props;
+ return (
+ <BootstrapModal {...props} id={this.modalId}>
+ {children}
+ </BootstrapModal>
+ );
+ }
+}
diff --git a/openecomp-ui/src/nfvo-components/notifications/NotificationConstants.js b/openecomp-ui/src/nfvo-components/notifications/NotificationConstants.js
new file mode 100644
index 0000000..1a53f4c
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/notifications/NotificationConstants.js
@@ -0,0 +1,29 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export default keyMirror({
+ NOTIFY_ERROR: null,
+ NOTIFY_SUCCESS: null,
+ NOTIFY_WARNING: null,
+ NOTIFY_INFO: null,
+ NOTIFY_CLOSE: null
+});
diff --git a/openecomp-ui/src/nfvo-components/notifications/NotificationModal.jsx b/openecomp-ui/src/nfvo-components/notifications/NotificationModal.jsx
new file mode 100644
index 0000000..7179309
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/notifications/NotificationModal.jsx
@@ -0,0 +1,100 @@
+/**
+ * NotificationModal options:
+ *
+ * show: whether to show notification or not,
+ * type: the type of the notification. valid values are: 'default', 'error', 'warning', 'success'
+ * msg: the notification content. could be a string or node (React component)
+ * title: the notification title
+ * timeout: timeout for the notification to fade out. if timeout == 0 then the notification is rendered until the user closes it
+ *
+ */
+import React, {Component, PropTypes} from 'react';
+import {connect} from 'react-redux';
+import Button from 'react-bootstrap/lib/Button.js';
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import Modal from 'nfvo-components/modal/Modal.jsx';
+import SubmitErrorResponse from 'nfvo-components/SubmitErrorResponse.jsx';
+import NotificationConstants from './NotificationConstants.js';
+
+let typeClass = {
+ 'default': 'primary',
+ error: 'danger',
+ warning: 'warning',
+ success: 'success'
+};
+
+const mapActionsToProps = (dispatch) => {
+ return {onCloseClick: () => dispatch({type: NotificationConstants.NOTIFY_CLOSE})};
+};
+
+const mapStateToProps = ({notification}) => {
+
+ let show = notification !== null && notification.title !== 'Conflict';
+ let mapResult = {show};
+ if (show) {
+ mapResult = {show, ...notification};
+ }
+
+ return mapResult;
+};
+
+export class NotificationModal extends Component {
+
+ static propTypes = {
+ show: PropTypes.bool,
+ type: PropTypes.oneOf(['default', 'error', 'warning', 'success']),
+ title: PropTypes.string,
+ msg: PropTypes.node,
+ validationResponse: PropTypes.object,
+ timeout: PropTypes.number
+ };
+
+ static defaultProps = {
+ show: false,
+ type: 'default',
+ title: '',
+ msg: '',
+ timeout: 0
+ };
+
+ state = {type: undefined};
+
+ componentWillReceiveProps(nextProps) {
+ if (this.props.show !== nextProps.show && nextProps.show === false) {
+ this.setState({type: this.props.type});
+ }
+ else {
+ this.setState({type: undefined});
+ }
+ }
+
+ componentDidUpdate() {
+ if (this.props.timeout) {
+ setTimeout(this.props.onCloseClick, this.props.timeout);
+ }
+ }
+
+ render() {
+ let {title, type, msg, show, validationResponse, onCloseClick} = this.props;
+ if (!show) {
+ type = this.state.type;
+ }
+ if (validationResponse) {
+ msg = (<SubmitErrorResponse validationResponse={validationResponse}/>);
+ }
+ return (
+ <Modal show={show} className={`notification-modal ${typeClass[type]}`}>
+ <Modal.Header>
+ <Modal.Title>{title}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>{msg}</Modal.Body>
+ <Modal.Footer>
+ <Button bsStyle={typeClass[type]} onClick={onCloseClick}>{i18n('OK')}</Button>
+ </Modal.Footer>
+ </Modal>
+ );
+ }
+}
+
+export default connect(mapStateToProps, mapActionsToProps)(NotificationModal);
diff --git a/openecomp-ui/src/nfvo-components/notifications/NotificationReducer.js b/openecomp-ui/src/nfvo-components/notifications/NotificationReducer.js
new file mode 100644
index 0000000..c8b30d6
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/notifications/NotificationReducer.js
@@ -0,0 +1,51 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import NotificationConstants from './NotificationConstants.js';
+
+export default (state = null, action) => {
+ switch (action.type) {
+ case NotificationConstants.NOTIFY_INFO:
+ return {type: 'default', title: action.data.title, msg: action.data.msg, timeout: action.data.timeout};
+
+ case NotificationConstants.NOTIFY_ERROR:
+ return {
+ type: 'error',
+ title: action.data.title,
+ msg: action.data.msg,
+ validationResponse: action.data.validationResponse,
+ timeout: action.data.timeout
+ };
+
+ case NotificationConstants.NOTIFY_WARNING:
+ return {type: 'warning', title: action.data.title, msg: action.data.msg, timeout: action.data.timeout};
+
+ case NotificationConstants.NOTIFY_SUCCESS:
+ return {
+ type: 'success', title: action.data.title, msg: action.data.msg, timeout: action.data.timeout
+ };
+ case NotificationConstants.NOTIFY_CLOSE:
+ return null;
+
+ default:
+ return state;
+ }
+
+};
diff --git a/openecomp-ui/src/nfvo-components/panel/NavigationSideBar.jsx b/openecomp-ui/src/nfvo-components/panel/NavigationSideBar.jsx
new file mode 100644
index 0000000..feb0f81
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/panel/NavigationSideBar.jsx
@@ -0,0 +1,73 @@
+import React from 'react';
+import classnames from 'classnames';
+import Collapse from 'react-bootstrap/lib/Collapse.js';
+
+class NavigationSideBar extends React.Component {
+
+ static PropTypes = {
+ activeItemId: React.PropTypes.string.isRequired,
+ onSelect: React.PropTypes.func,
+ onToggle: React.PropTypes.func,
+ groups: React.PropTypes.array
+ };
+
+ render() {
+ let {groups, activeItemId} = this.props;
+
+ return (
+ <div className='navigation-side-content'>
+ {groups.map(group => (
+ <div className='navigation-group' key={group.id}>
+ <div className='group-name'>{group.name}</div>
+ <div className='navigation-group-items'>
+ {
+ group.items && group.items.map(item => this.renderGroupItem(item, activeItemId))
+ }
+ </div>
+ </div>
+ ))}
+ </div>
+ );
+ }
+
+ renderGroupItem(item, activeItemId) {
+ let isGroup = item.items && item.items.length > 0;
+ return (
+ <div className={classnames('navigation-group-item', {'selected-item': item.id === activeItemId})}>
+ <div
+ key={item.id}
+ className={classnames('navigation-group-item-name', {
+ 'selected': item.id === activeItemId,
+ 'disabled': item.disabled,
+ 'bold-name': item.expanded,
+ 'hidden': item.hidden
+ })}
+ onClick={(event) => this.handleItemClicked(event, item)}>
+ {item.name}
+ </div>
+ {isGroup &&
+ <Collapse in={item.expanded}>
+ <div>
+ {item.items.map(item => this.renderGroupItem(item, activeItemId))}
+ </div>
+ </Collapse>
+ }
+ </div>
+ );
+ }
+
+ handleItemClicked(event, item) {
+ event.stopPropagation();
+ if(this.props.onToggle) {
+ this.props.onToggle(this.props.groups, item.id);
+ }
+ if(item.onSelect) {
+ item.onSelect();
+ }
+ if(this.props.onSelect) {
+ this.props.onSelect(item);
+ }
+ }
+}
+
+export default NavigationSideBar;
diff --git a/openecomp-ui/src/nfvo-components/panel/SlidePanel.jsx b/openecomp-ui/src/nfvo-components/panel/SlidePanel.jsx
new file mode 100644
index 0000000..10c5326
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/panel/SlidePanel.jsx
@@ -0,0 +1,109 @@
+import React from 'react';
+import FontAwesome from 'react-fontawesome';
+import ReactDOM from 'react-dom';
+
+class SlidePanel extends React.Component {
+
+ static PropTypes = {
+ direction: React.PropTypes.string.isRequired,
+ className: React.PropTypes.string,
+ title: React.PropTypes.string,
+ isOpen: React.PropTypes.bool
+ };
+
+ static defaultProps = {
+ title: '',
+ className: '',
+ isOpen: true
+ };
+
+ state = {
+ isOpen: this.props.isOpen,
+ direction: this.props.direction,
+ width: 0,
+ arrowWidth: 0
+ };
+
+ componentDidMount() {
+ this.setSliderPosition();
+ }
+
+ componentDidUpdate() {
+ this.setSliderPosition();
+ }
+
+ render() {
+
+ let {children, className} = this.props;
+ let {isOpen} = this.state;
+
+ return (
+ <div className={ `slide-panel ${className}`}>
+ {this.renderHeader(isOpen)}
+ <div className={'slide-panel-content ' + (isOpen ? 'opened' : 'closed')}>{children}</div>
+ </div>
+ );
+ }
+
+ renderHeader(isOpen) {
+ let {direction: initialDirection, title} = this.props;
+ let {direction: currentDirection} = this.state;
+
+ let iconName = currentDirection === 'right' ? 'angle-double-right collapse-double-icon' : 'angle-double-left collapse-double-icon';
+
+ let awestyle = {padding: '5px'};
+
+ if (!isOpen && initialDirection === 'right') {
+ awestyle.marginLeft = '-1px';
+ }
+ return (
+ <div className='slide-panel-header'>
+ { initialDirection === 'left' && <span className='slide-panel-header-title'>{title}</span>}
+ <FontAwesome
+ ref='arrowIcon'
+ style={awestyle}
+ onClick={this.handleClick}
+ className='pull-right'
+ name={iconName}
+ size='2x'/>
+ { initialDirection === 'right' && <span className='slide-panel-header-title'>{title}</span>}
+ </div>
+ );
+ }
+
+ handleClick = () => {
+ this.setState({
+ isOpen: !this.state.isOpen,
+ direction: this.state.direction === 'left' ? 'right' : 'left'
+ });
+ }
+
+ setSliderPosition = () => {
+
+ let el = ReactDOM.findDOMNode(this);
+ let {style} = el;
+
+ let {direction: initialDirection} = this.props;
+ let arrowIconSize = Math.floor(ReactDOM.findDOMNode(this.refs.arrowIcon).getBoundingClientRect().width) * 2;
+ if (!this.state.isOpen) {
+ if (this.props.direction === 'left') {
+ style.left = arrowIconSize - el.getBoundingClientRect().width + 'px';
+ }
+ if (initialDirection === 'right') {
+ style.right = arrowIconSize - el.getBoundingClientRect().width + 'px';
+ }
+ }
+ else {
+ if (initialDirection === 'left') {
+ style.left = '0px';
+ }
+
+ if (this.props.direction === 'right') {
+ style.right = '0px';
+ }
+ }
+ }
+
+}
+
+export default SlidePanel;
\ No newline at end of file
diff --git a/openecomp-ui/src/nfvo-components/panel/versionController/VersionController.jsx b/openecomp-ui/src/nfvo-components/panel/versionController/VersionController.jsx
new file mode 100644
index 0000000..78525f8
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/panel/versionController/VersionController.jsx
@@ -0,0 +1,165 @@
+import React from 'react';
+import classnames from 'classnames';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+import Navbar from 'react-bootstrap/lib/Navbar.js';
+import Nav from 'react-bootstrap/lib/Nav.js';
+import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx';
+import {actionsEnum, statusEnum} from './VersionControllerConstants.js';
+
+
+class VersionController extends React.Component {
+
+ static propTypes = {
+ version: React.PropTypes.string,
+ viewableVersions: React.PropTypes.array,
+ onVersionSwitching: React.PropTypes.func,
+ isCheckedOut: React.PropTypes.bool.isRequired,
+ status: React.PropTypes.string.isRequired,
+ callVCAction: React.PropTypes.func,
+ onSave: React.PropTypes.func,
+ onClose: React.PropTypes.func,
+ isFormDataValid: React.PropTypes.bool
+ };
+
+ render() {
+ let {status, isCheckedOut, version = '', viewableVersions = [], onVersionSwitching, callVCAction, onSave, isFormDataValid, onClose} = this.props;
+ let isCheckedIn = Boolean(status === statusEnum.CHECK_IN_STATUS);
+ let isLatestVersion = Boolean(version === viewableVersions[viewableVersions.length - 1]);
+ if (!isLatestVersion) {
+ status = statusEnum.PREVIOUS_VERSION;
+ }
+
+ return (
+ <div className='version-controller-bar'>
+ <Navbar inverse className='navbar'>
+ <Navbar.Collapse>
+ <Nav className='items-in-left'>
+ <div className='version-section'>
+ <ValidationInput
+ type='select'
+ selectedEnum={version}
+ onEnumChange={value => onVersionSwitching && onVersionSwitching(value)}>
+ {viewableVersions && viewableVersions.map(viewVersion => {
+ return (
+ <option key={viewVersion} value={viewVersion}>{`V ${viewVersion}`}</option>
+ );
+ })
+ }
+ {!viewableVersions.includes(version) &&
+ <option key={version} value={version}>{`V ${version}`}</option>}
+ </ValidationInput>
+ </div>
+ <div className='vc-status'>
+ <div className='onboarding-status-icon'></div>
+ <div className='status-text'> {i18n('ONBOARDING')}
+ <div className='status-text-dash'> -</div>
+ </div>
+ {this.renderStatus(status)}
+ </div>
+ </Nav>
+ <Nav pullRight>
+ <div className='items-in-right'>
+ <div className='action-buttons'>
+ {callVCAction &&
+ <div className='version-control-buttons'>
+ <div
+ className={classnames('vc-nav-item-button button-submit', {'disabled': !isCheckedIn || !isLatestVersion})}
+ onClick={() => this.submit(callVCAction)}>
+ {i18n('Submit')}
+ </div>
+ <div
+ className={classnames('vc-nav-item-button button-checkin-checkout', {'disabled': status === statusEnum.LOCK_STATUS || !isLatestVersion})}
+ onClick={() => this.checkinCheckoutVersion(callVCAction)}>
+ {`${isCheckedOut ? i18n('Check In') : i18n('Check Out')}`}
+ </div>
+ <div
+ className={classnames('sprite-new revert-btn ng-scope ng-isolate-scope', {'disabled': !isCheckedOut || version === '0.1' || !isLatestVersion})}
+ onClick={() => this.revertCheckout(callVCAction)}>
+ </div>
+ </div>
+ }
+ {onSave &&
+ <div
+ className={classnames('sprite-new save-btn ng-scope ng-isolate-scope', {'disabled': !isCheckedOut || !isFormDataValid || !isLatestVersion})}
+ onClick={() => onSave()}>
+ </div>
+ }
+ </div>
+ <div className='vc-nav-item-close' onClick={() => onClose && onClose()}> X</div>
+ </div>
+ </Nav>
+ </Navbar.Collapse>
+ </Navbar>
+ </div>
+ );
+ }
+
+ renderStatus(status) {
+ switch (status) {
+ case statusEnum.CHECK_OUT_STATUS:
+ return (
+ <div className='checkout-status-icon'>
+ <div className='catalog-tile-check-in-status sprite-new checkout-editable-status-icon'></div>
+ <div className='status-text'> {i18n('CHECKED OUT')} </div>
+ </div>
+ );
+ case statusEnum.LOCK_STATUS:
+ return (
+ <div className='status-text'> {i18n('LOCKED')} </div>
+ );
+ case statusEnum.CHECK_IN_STATUS:
+ return (
+ <div className='status-text'> {i18n('CHECKED IN')} </div>
+ );
+ case statusEnum.SUBMIT_STATUS:
+ return (
+ <div className='status-text'> {i18n('SUBMITTED')} </div>
+ );
+ default:
+ return (
+ <div className='status-text'> {i18n(status)} </div>
+ );
+ }
+ }
+
+ checkinCheckoutVersion(callVCAction) {
+ if (this.props.isCheckedOut) {
+ this.checkin(callVCAction);
+ }
+ else {
+ this.checkout(callVCAction);
+ }
+ }
+
+ checkin(callVCAction) {
+
+ const action = actionsEnum.CHECK_IN;
+
+ if (this.props.onSave) {
+ this.props.onSave().then(()=>{
+ callVCAction(action);
+ });
+ }else{
+ callVCAction(action);
+ }
+
+ }
+
+ checkout(callVCAction) {
+ const action = actionsEnum.CHECK_OUT;
+ callVCAction(action);
+ }
+
+ submit(callVCAction) {
+ const action = actionsEnum.SUBMIT;
+ callVCAction(action);
+ }
+
+ revertCheckout(callVCAction) {
+ const action = actionsEnum.UNDO_CHECK_OUT;
+ callVCAction(action);
+ }
+}
+
+export default VersionController;
diff --git a/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerConstants.js b/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerConstants.js
new file mode 100644
index 0000000..9251fd1
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerConstants.js
@@ -0,0 +1,38 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export const actionsEnum = keyMirror({
+ CHECK_IN: 'Checkin',
+ CHECK_OUT: 'Checkout',
+ UNDO_CHECK_OUT: 'Undo_Checkout',
+ SUBMIT: 'Submit',
+ CREATE_PACKAGE: 'Create_Package'
+});
+
+export const statusEnum = keyMirror({
+ CHECK_OUT_STATUS: 'Locked',
+ CHECK_IN_STATUS: 'Available',
+ SUBMIT_STATUS: 'Final',
+ LOCK_STATUS: 'LockedByUser',
+ PREVIOUS_VERSION: 'READ ONLY'
+});
+
diff --git a/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerUtils.js b/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerUtils.js
new file mode 100644
index 0000000..de99144
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerUtils.js
@@ -0,0 +1,49 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import Configuration from 'sdc-app/config/Configuration.js';
+import {statusEnum} from './VersionControllerConstants.js';
+
+
+const VersionControllerUtils = {
+
+ getCheckOutStatusKindByUserID(status, lockingUser) {
+ let currentLoginUserID = Configuration.get('ATTUserID');
+ let isCheckedOut = currentLoginUserID === lockingUser;
+
+ return {
+ status: isCheckedOut ? status : statusEnum.LOCK_STATUS,
+ isCheckedOut
+ };
+ },
+
+ isCheckedOutByCurrentUser(resource) {
+ let currentLoginUserID = Configuration.get('ATTUserID');
+ return resource.lockingUser !== undefined && resource.lockingUser === currentLoginUserID;
+ },
+
+ isReadOnly(resource) {
+ const {version, viewableVersions = []} = resource;
+ const latestVersion = viewableVersions[viewableVersions.length - 1];
+ return version !== latestVersion || !VersionControllerUtils.isCheckedOutByCurrentUser(resource);
+ }
+};
+
+export default VersionControllerUtils;
diff --git a/openecomp-ui/src/nfvo-components/progressBar/ProgressBar.jsx b/openecomp-ui/src/nfvo-components/progressBar/ProgressBar.jsx
new file mode 100644
index 0000000..d786aee
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/progressBar/ProgressBar.jsx
@@ -0,0 +1,22 @@
+import React from 'react';
+
+class ProgressBar extends React.Component {
+ static propTypes = {
+ label: React.PropTypes.string,
+ now: React.PropTypes.string.isRequired
+ }
+ render() {
+ let {label, now} = this.props;
+
+ return(
+ <div className='progress-bar-view'>
+ <div className='progress-bar-outside'>
+ <div style={{width: now + '%'}} className='progress-bar-inside'></div>
+ </div>
+ <div className='progress-bar-view-label'>{label}</div>
+ </div>
+ );
+ }
+}
+
+export default ProgressBar;
diff --git a/openecomp-ui/src/nfvo-utils/ErrorResponseHandler.js b/openecomp-ui/src/nfvo-utils/ErrorResponseHandler.js
new file mode 100644
index 0000000..0d27204
--- /dev/null
+++ b/openecomp-ui/src/nfvo-utils/ErrorResponseHandler.js
@@ -0,0 +1,72 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import store from 'sdc-app/AppStore.js';
+import NotificationConstants from 'nfvo-components/notifications/NotificationConstants.js';
+
+function showVariablesInMessage(variables, msg) {
+ let regex;
+ variables.forEach((value, index) => {
+ value = value.replace(';', ',');
+ regex = new RegExp('\'\%' + (index + 1) + '\'');
+ msg = msg.replace(regex, value);
+ });
+ return msg;
+}
+
+function parseATTExceptionObject(responseJSON) {
+ let title, msg;
+ if (responseJSON.requestError && responseJSON.requestError.policyException) {
+ title = 'Error: ' + responseJSON.requestError.policyException.messageId;
+ msg = responseJSON.requestError.policyException.text;
+ }
+ else if (responseJSON.requestError && responseJSON.requestError.serviceException) {
+ title = 'Error: ' + responseJSON.requestError.serviceException.messageId;
+ msg = responseJSON.requestError.serviceException.text;
+ let {variables} = responseJSON.requestError.serviceException;
+ if (variables) {
+ msg = showVariablesInMessage(variables, msg);
+ }
+ }
+ else {
+ title = responseJSON.status;
+ msg = responseJSON.message;
+ }
+ return {title, msg};
+}
+
+var errorResponseHandler = (xhr/*, textStatus, errorThrown*/) => {
+ let errorData;
+ if (xhr.responseJSON) {
+ errorData = parseATTExceptionObject(xhr.responseJSON);
+ }
+ else {
+ errorData = {
+ title: xhr.statusText,
+ msg: xhr.responseText
+ };
+ }
+ store.dispatch({
+ type: NotificationConstants.NOTIFY_ERROR,
+ data: {...errorData}
+ });
+};
+
+export default errorResponseHandler;
diff --git a/openecomp-ui/src/nfvo-utils/KeyMirror.js b/openecomp-ui/src/nfvo-utils/KeyMirror.js
new file mode 100644
index 0000000..eb50d31
--- /dev/null
+++ b/openecomp-ui/src/nfvo-utils/KeyMirror.js
@@ -0,0 +1,44 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+var keyMirror = function (obj) {
+ var ret = {};
+ var key;
+ var val;
+ if (!(obj instanceof Object && !Array.isArray(obj))) {
+ throw new Error('keyMirror(...): Argument must be an object.');
+ }
+ for (key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ val = obj[key];
+ if (val instanceof Object) {
+ ret[key] = keyMirror(obj[key]);
+ } else if(val !== null && val !== undefined){
+ ret[key] = val;
+ }
+ else {
+ ret[key] = Symbol(key);
+ }
+ }
+ }
+ return Object.freeze(ret);
+};
+
+export default keyMirror;
diff --git a/openecomp-ui/src/nfvo-utils/RestAPIUtil.js b/openecomp-ui/src/nfvo-utils/RestAPIUtil.js
new file mode 100644
index 0000000..2473473
--- /dev/null
+++ b/openecomp-ui/src/nfvo-utils/RestAPIUtil.js
@@ -0,0 +1,288 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import _extend from 'lodash/extend.js';
+import _clone from 'lodash/clone.js';
+import _defaults from 'lodash/defaults.js';
+import $ from 'jquery';
+import uuid from 'uuid-js';
+import md5 from 'md5';
+
+import store from 'sdc-app/AppStore.js';
+import {actionTypes as LoaderConstants} from 'nfvo-components/loader/LoaderConstants.js';
+import Configuration from 'sdc-app/config/Configuration.js';
+import errorResponseHandler from './ErrorResponseHandler.js';
+
+const methodMap = {
+ 'create': 'POST',
+ 'update': 'PUT',
+ 'delete': 'DELETE',
+ 'read': 'GET'
+};
+const AUTHORIZATION_HEADER = 'X-AUTH-TOKEN';
+const STORAGE_AUTH_KEY = 'sdc-auth-token';
+const REQUEST_ID_HEADER = 'X-ECOMP-RequestID';
+const CONTENT_MD5_HEADER = 'Content-MD5';
+const namedParam = /{(\w+)}/g;
+const queryParamsNames = {
+ pageStart: 'pageStart',
+ pageSize: 'pageSize',
+ sortField: 'sortField',
+ sortDir: 'sortDir',
+ filtering: 'filter'
+};
+
+
+// jQuery binary transport to download files through XHR
+// http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/
+// https://github.com/henrya/js-jquery/tree/master/BinaryTransport
+$.ajaxTransport('+binary', function (options/*, originalOptions , jqXHR*/) {
+ // check for conditions and support for blob / arraybuffer response type
+ if (window.FormData && ((options.dataType && (options.dataType === 'binary')) ||
+ (options.data && ((window.ArrayBuffer && options.data instanceof ArrayBuffer) ||
+ (window.Blob && options.data instanceof Blob))))
+ ) {
+ return {
+ // create new XMLHttpRequest
+ send: function (headers, callback) {
+ // setup all variables
+ var xhr = new XMLHttpRequest(),
+ url = options.url,
+ type = options.type,
+ async = options.async || true,
+ // blob or arraybuffer. Default is blob
+ dataType = options.responseType || 'blob',
+ data = options.data || null,
+ username = options.username || null,
+ password = options.password || null;
+
+ xhr.addEventListener('load', function () {
+ var data = {};
+ data[options.dataType] = xhr.response;
+ // make callback and send data
+ callback(xhr.status, xhr.statusText, data, xhr.getAllResponseHeaders());
+ });
+
+ xhr.open(type, url, async, username, password);
+
+ // setup custom headers
+ for (var i in headers) {
+ xhr.setRequestHeader(i, headers[i]);
+ }
+
+ xhr.responseType = dataType;
+ xhr.send(data);
+ },
+ abort: function () {
+ }
+ };
+ }
+});
+
+$(document).ajaxStart(()=> store.dispatch({type: LoaderConstants.SHOW}));
+$(document).ajaxStop(()=> store.dispatch({type: LoaderConstants.HIDE}));
+
+function urlError() {
+ throw new Error('A "url" property or function must be specified');
+};
+
+export function makeQueryParams(options) {
+ var qParams = {};
+ if (options.pagination) {
+ qParams[queryParamsNames.pageStart] = options.pagination.pageStart;
+ qParams[queryParamsNames.pageSize] = options.pagination.pageSize;
+ }
+ if (options.sorting) {
+ qParams[queryParamsNames.sortField] = options.sorting.sortField;
+ qParams[queryParamsNames.sortDir] = options.sorting.sortDir;
+ }
+ if (options.filtering) {
+ qParams[queryParamsNames.filtering] = JSON.stringify(options.filtering);
+ }
+
+ return _defaults(qParams, options.qParams);
+}
+
+function appendQueryParam(p, value) {
+ var str = '';
+
+ if (value instanceof Array) {
+ if (value.length === 1) {
+ str = appendQueryParam(p, value[0]);
+ } else if (value.length > 1) {
+ str = appendQueryParam(p, value.shift()) + '&' + appendQueryParam(p, value);
+ }
+ } else {
+ str = p + '=' + encodeURIComponent(value);
+ }
+
+ return str;
+}
+
+function appendQueryString(url, qParams) {
+ var str = '';
+ for (var param in qParams) {
+ str += (str ? '&' : '') + appendQueryParam(param, qParams[param]);
+ }
+ return url + (str ? '?' : '') + str;
+}
+
+function composeURL(baseUrl, options) {
+ var url = baseUrl || urlError();
+ if (options.url) {
+ delete options.url;
+ }
+
+ var qParams = makeQueryParams(options);
+ url = appendQueryString(url, qParams);
+
+ var matches = url.match(namedParam);
+ if (matches) {
+ for (var i = 0; i < matches.length; i++) {
+ var param = matches[i].substring(1, matches[i].length - 1);
+ var value = (options.params && options.params[param]);
+
+ if (value === undefined) {
+ value = options[param];
+ }
+ url = url.replace(matches[i], encodeURIComponent(value));
+ }
+ }
+
+ return url;
+}
+
+function applyMD5Header(options, data) {
+ if (options.md5) {
+ let headers = options.headers;
+ headers[CONTENT_MD5_HEADER] = window.btoa(md5(JSON.stringify(data)).toLowerCase());
+ }
+}
+
+function applySecurity(options, data) {
+ var headers = options.headers || (options.headers = {});
+
+ var authToken = localStorage.getItem(STORAGE_AUTH_KEY);
+ if (authToken) {
+ headers[AUTHORIZATION_HEADER] = authToken;
+ }
+
+ var attApiHeaders = Configuration.get('ATTApiHeaders'),
+ attUidHeader = attApiHeaders && attApiHeaders.userId;
+ if (attUidHeader) {
+ headers[attUidHeader.name] = attUidHeader.value;
+ }
+
+ headers[REQUEST_ID_HEADER] = uuid.create().toString();
+
+ applyMD5Header(options, data);
+}
+
+function handleResponse(options) {
+ var authToken = options.xhr.getResponseHeader(AUTHORIZATION_HEADER);
+ var prevToken = options.headers && options.headers[AUTHORIZATION_HEADER];
+ if (authToken && authToken !== prevToken) {
+ if (authToken === 'null') {
+ localStorage.removeItem(STORAGE_AUTH_KEY);
+ } else {
+ localStorage.setItem(STORAGE_AUTH_KEY, authToken);
+ }
+ }
+}
+
+function sync(baseUrl, method, options, data) {
+
+ options = options ? _clone(options) : {};
+
+ var type = methodMap[method];
+ _defaults(options || (options = {}));
+ var params = {
+ type: type,
+ dataType: 'json'
+ };
+ params.url = composeURL(baseUrl, options);
+
+ if ((method === 'create' || method === 'update') && data instanceof FormData) {
+ params.contentType = 'multipart/form-data';
+ params.data = data;
+ }
+ else if (method === 'create' || method === 'update') {
+ params.contentType = 'application/json';
+ params.data = JSON.stringify(data);
+ }
+
+ if (params.type !== 'GET') {
+ params.processData = false;
+ }
+ var success = options.success;
+ options.success = function (resp) {
+ if (success) {
+ handleResponse(options);
+ success.call(options.context, _clone(resp), resp, options);
+ }
+ };
+
+ options.error = options.error || errorResponseHandler;
+
+ if (typeof options.progressCallback === 'function' && options.fileSize) {
+ const {fileSize} = options;
+ options.xhrFields = {
+ // add listener to XMLHTTPRequest object directly for progress (jquery doesn't have this yet)
+ onprogress: function (progress) {
+ // calculate upload progress
+ let percentage = Math.floor((progress.loaded / fileSize) * 100);
+ // log upload progress to console
+ //console.log('progress', percentage);
+ options.progressCallback(percentage);
+ if (percentage === 100) {
+ console.log('DONE!');
+ }
+ }
+ };
+ }
+
+ applySecurity(options, data);
+
+ if (DEBUG) {
+ console.log('--> Making REST call (' + type + '): ' + params.url);
+ }
+ var xhr = options.xhr = $.ajax(_extend(params, options));
+ return xhr;
+}
+
+export default {
+
+ fetch(baseUrl, options) {
+ return sync(baseUrl, 'read', options);
+ },
+
+ save(baseUrl, data, options) {
+ return sync(baseUrl, 'update', options, data);
+ },
+
+ create(baseUrl, data, options) {
+ return sync(baseUrl, 'create', options, data);
+ },
+
+ destroy(baseUrl, options) {
+ return sync(baseUrl, 'delete', options);
+ }
+
+};
diff --git a/openecomp-ui/src/nfvo-utils/UUID.js b/openecomp-ui/src/nfvo-utils/UUID.js
new file mode 100644
index 0000000..314c98b
--- /dev/null
+++ b/openecomp-ui/src/nfvo-utils/UUID.js
@@ -0,0 +1,58 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import UUID from 'uuid-js';
+
+let toCustomUUID = (uuid) => {
+ return 'U' + uuid.replace(/-/g, '');
+};
+
+let getUUID = function(num, isSync) {
+ if (isSync) {
+ let uuid;
+ if (!num) {
+ uuid = toCustomUUID(UUID.create().toString());
+ } else {
+ uuid = [];
+ for (var i = 0; i < num; i++) {
+ uuid[i] = toCustomUUID(UUID.create().toString());
+ }
+ }
+ if (num === 1) {
+ return uuid[0];
+ } else {
+ return uuid;
+ }
+ }
+ return new Promise(resolve => {
+ let uuid;
+ if (!num) {
+ uuid = toCustomUUID(UUID.create().toString());
+ } else {
+ uuid = [];
+ for (var i = 0; i < num; i++) {
+ uuid[i] = toCustomUUID(UUID.create().toString());
+ }
+ }
+ setTimeout(() => resolve(uuid), 100);
+ });
+};
+
+export default getUUID;
diff --git a/openecomp-ui/src/nfvo-utils/i18n/i18n.js b/openecomp-ui/src/nfvo-utils/i18n/i18n.js
new file mode 100644
index 0000000..6458771
--- /dev/null
+++ b/openecomp-ui/src/nfvo-utils/i18n/i18n.js
@@ -0,0 +1,122 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import IntlObj from 'intl';
+import IntlMessageFormatObj from 'intl-messageformat';
+import IntlRelativeFormatObj from 'intl-relativeformat';
+import createFormatCacheObj from 'intl-format-cache';
+import i18nJson from 'i18nJson';
+
+/*
+ Intl libs are using out dated transpailer from ecmascript6.
+* TODO: As soon as they fix it, remove this assignments!!!
+* */
+var Intl = window.Intl || IntlObj.default,
+ IntlMessageFormat = IntlMessageFormatObj.default,
+ IntlRelativeFormat = IntlRelativeFormatObj.default,
+ createFormatCache = createFormatCacheObj.default;
+
+var i18nData;
+
+if(i18nJson) {
+ i18nData = i18nJson.dataWrapperArr[i18nJson.i18nDataIdx];
+}
+
+
+/*extract locale*/
+var _locale = window.localStorage && localStorage.getItem('user_locale');
+if(!_locale) {
+ if(window.navigator) {
+ _locale = navigator.language || navigator.userLanguage;
+
+ //For now removing the dashes from the language.
+ let indexOfDash = _locale.indexOf('-');
+ if(-1 !== indexOfDash) {
+ _locale = _locale.substr(0, indexOfDash);
+ }
+ }
+ if(!_locale) {
+ _locale = 'en';
+ }
+}
+
+var _localeUpper = _locale.toUpperCase();
+
+var i18n = {
+
+ _locale: _locale,
+ _localeUpper: _localeUpper,
+ _i18nData: i18nData || {},
+
+ number(num) {
+ return createFormatCache(Intl.NumberFormat)(this._locale).format(num);
+ },
+
+ date(date, options, relativeDates) {
+ if (undefined === relativeDates || relativeDates) {
+ return this.dateRelative(date, options);
+ } else {
+ return this.dateNormal(date, options);
+ }
+ },
+
+ dateNormal(date, options) {
+ return createFormatCache(Intl.DateTimeFormat)(this._locale, options).format(date);
+ },
+
+ dateRelative(date, options) {
+ return createFormatCache(IntlRelativeFormat)(this._locale, options).format(date);
+ },
+
+ message(messageId, options) {
+ return createFormatCache(IntlMessageFormat)(this._i18nData[messageId] || String(messageId), this._locale).format(options);
+ },
+
+ getLocale() {
+ return this._locale;
+ },
+
+ getLocaleUpper() {
+ return this._localeUpper;
+ },
+
+ setLocale(locale) {
+ localStorage.setItem('user_locale', locale);
+ window.location.reload();
+ }
+
+};
+
+function i18nWrapper() {
+ return i18nWrapper.message.apply(i18nWrapper, arguments);
+}
+
+/*replace with some kind of extend method*/
+var prop, propKey;
+for (propKey in i18n) {
+ prop = i18n[propKey];
+ if (typeof prop === 'function') {
+ prop = prop.bind(i18nWrapper);
+ }
+ i18nWrapper[propKey] = prop;
+}
+
+
+export default i18nWrapper;
diff --git a/openecomp-ui/src/nfvo-utils/i18n/locale.json b/openecomp-ui/src/nfvo-utils/i18n/locale.json
new file mode 100644
index 0000000..d9047ba
--- /dev/null
+++ b/openecomp-ui/src/nfvo-utils/i18n/locale.json
@@ -0,0 +1 @@
+{"dataWrapperArr":["I18N_IDENTIFIER_START",{},"I18N_IDENTIFIER_END"],"i18nDataIdx":1}
\ No newline at end of file
diff --git a/openecomp-ui/src/nfvo-utils/json/JSONPointer.js b/openecomp-ui/src/nfvo-utils/json/JSONPointer.js
new file mode 100644
index 0000000..a6e8198
--- /dev/null
+++ b/openecomp-ui/src/nfvo-utils/json/JSONPointer.js
@@ -0,0 +1,62 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+const JSONPointer = {
+
+ extractParentPointer(pointer) {
+ return pointer.replace(/\/[^\/]+$/, '');
+ },
+
+ extractLastPart(pointer) {
+ const [,lastPart] = pointer.match(/\/([^\/]+)$/) || [];
+ return lastPart;
+ },
+
+ extractParts(pointer) {
+ return pointer.split('/').slice(1)
+ .map(part => part.replace(/~1/g, '/'))
+ .map(part => part.replace(/~0/g, '~'));
+ },
+
+ getValue(object, pointer) {
+ let parts = JSONPointer.extractParts(pointer);
+ return parts.reduce((object, part) => object && object[part], object);
+ },
+
+ setValue(object, pointer, value) {
+ let clone = obj => Array.isArray(obj) ? [...obj] : {...obj};
+
+ let parts = JSONPointer.extractParts(pointer),
+ newObject = clone(object),
+ subObject = object,
+ subNewObject = newObject;
+
+ for(let i = 0, n = parts.length - 1; i < n; ++i) {
+ let nextSubObject = subObject && subObject[parts[i]];
+ subNewObject = subNewObject[parts[i]] = nextSubObject ? clone(nextSubObject) : {};
+ subObject = nextSubObject;
+ }
+ subNewObject[parts[parts.length - 1]] = value;
+
+ return newObject;
+ }
+};
+
+export default JSONPointer;
diff --git a/openecomp-ui/src/nfvo-utils/json/JSONSchema.js b/openecomp-ui/src/nfvo-utils/json/JSONSchema.js
new file mode 100644
index 0000000..8c7d8cf
--- /dev/null
+++ b/openecomp-ui/src/nfvo-utils/json/JSONSchema.js
@@ -0,0 +1,155 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+// import Ajv from 'ajv';
+import cloneDeep from 'lodash/cloneDeep.js';
+import JSONPointer from './JSONPointer.js';
+
+export default class JSONSchema {
+
+ setSchema(schema) {
+ this._schema = schema;
+ this._fragmentsCache = new Map();
+ // this._ajv = new Ajv({
+ // useDefaults: true,
+ // coerceTypes: true
+ // });
+ // this._validate = this._ajv.compile(schema);
+ }
+
+ processData(data) {
+ data = cloneDeep(data);
+ // this._validate(data);
+ return data;
+ }
+
+ getTitle(pointer) {
+ return this._getSchemaFragment(pointer).title;
+ }
+
+ exists(pointer) {
+ const fragment = this._getSchemaFragment(pointer);
+ return !!fragment;
+ }
+
+ getDefault(pointer) {
+ const fragment = this._getSchemaFragment(pointer);
+ return fragment && fragment.default;
+ }
+
+ getEnum(pointer) {
+ const fragment = this._getSchemaFragment(pointer);
+ return fragment && (fragment.type === 'array' ? fragment.items.enum : fragment.enum);
+ }
+
+ isRequired(pointer) {
+ const parentPointer = JSONPointer.extractParentPointer(pointer);
+ const lastPart = JSONPointer.extractLastPart(pointer);
+ let parentFragment = this._getSchemaFragment(parentPointer);
+ return parentFragment && parentFragment.required && parentFragment.required.includes(lastPart);
+ }
+
+ isNumber(pointer) {
+ const fragment = this._getSchemaFragment(pointer);
+ return fragment && fragment.type === 'number';
+ }
+
+ getMaxValue(pointer) {
+ const fragment = this._getSchemaFragment(pointer);
+ return fragment && fragment.maximum;
+ }
+
+ getMinValue(pointer) {
+ const fragment = this._getSchemaFragment(pointer);
+ return fragment && fragment.minimum;
+ }
+
+ isString(pointer) {
+ const fragment = this._getSchemaFragment(pointer);
+ return fragment && fragment.type === 'string';
+ }
+
+ getPattern(pointer) {
+ const fragment = this._getSchemaFragment(pointer);
+ return fragment && fragment.pattern;
+ }
+
+ getMaxLength(pointer) {
+ const fragment = this._getSchemaFragment(pointer);
+ return fragment && fragment.maxLength;
+ }
+
+ getMinLength(pointer) {
+ const fragment = this._getSchemaFragment(pointer);
+ return fragment && fragment.minLength;
+ }
+
+ isArray(pointer) {
+ const fragment = this._getSchemaFragment(pointer);
+ return fragment && fragment.type === 'array';
+ }
+
+ _getSchemaFragment(pointer) {
+ if (this._fragmentsCache.has(pointer)) {
+ return this._fragmentsCache.get(pointer);
+ }
+
+ let parts = JSONPointer.extractParts(pointer);
+
+ let fragment = parts.reduce((fragment, part) => {
+ if (fragment === undefined) {
+ return undefined;
+ }
+
+ if (fragment.$ref) {
+ fragment = this._getSchemaFragmentByRef(fragment.$ref);
+ }
+
+ switch (fragment.type) {
+ case 'object':
+ return fragment.properties && fragment.properties[part];
+
+ case 'array':
+ return fragment.enum && fragment.enum[part];
+
+ default:
+ // throw new Error(`Incorrect/unsupported JSONPointer "${pointer}" from "${part}"`);
+ return undefined;
+ }
+ }, this._schema);
+
+ while(fragment && fragment.$ref) {
+ fragment = this._getSchemaFragmentByRef(fragment.$ref);
+ }
+
+ this._fragmentsCache.set(pointer, fragment);
+ return fragment;
+ }
+
+ _getSchemaFragmentByRef($ref) {
+ let pointer = $ref.substr(1);
+ return JSONPointer.getValue(this._schema, pointer);
+ // let fragmentAjv = new Ajv();
+ // fragmentAjv.addSchema(this._schema);
+ // let compiledFragment = fragmentAjv.compile({$ref});
+ // let fragment = compiledFragment.refVal[compiledFragment.refs[$ref]];
+ // return fragment;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/AppStore.js b/openecomp-ui/src/sdc-app/AppStore.js
new file mode 100644
index 0000000..0abcaac
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/AppStore.js
@@ -0,0 +1,47 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {combineReducers, createStore} from 'redux';
+import onBoardingReducersMap from './onboarding/OnboardingReducersMap.js';
+import flowsReducersMap from './flows/FlowsReducersMap.js';
+import notificationReducer from 'nfvo-components/notifications/NotificationReducer.js';
+import loaderReducer from 'nfvo-components/loader/LoaderReducer.js';
+import uploadScreenReducer from 'sdc-app/heatvalidation/UploadScreenReducer.js';
+import SoftwareProductAttachmentsReducer from 'sdc-app/heatvalidation/attachments/AttachmentsReducer';
+
+export const storeCreator = (initialState) => createStore(combineReducers({
+ // on-boarding reducers
+ ...onBoardingReducersMap,
+
+ // flows reducers
+ ...flowsReducersMap,
+
+ // heat validation stand-alone app
+ uploadScreen: combineReducers({
+ upload: uploadScreenReducer,
+ attachments: SoftwareProductAttachmentsReducer
+ }),
+ notification: notificationReducer,
+ loader: loaderReducer
+}), initialState, window.devToolsExtension ? window.devToolsExtension() : undefined);
+
+const store = storeCreator();
+
+export default store;
diff --git a/openecomp-ui/src/sdc-app/Application.jsx b/openecomp-ui/src/sdc-app/Application.jsx
new file mode 100644
index 0000000..5cb385b
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/Application.jsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import {Provider} from 'react-redux';
+import NotificationModal from 'nfvo-components/notifications/NotificationModal.jsx';
+import Loader from 'nfvo-components/loader/Loader.jsx';
+import store from './AppStore.js';
+
+
+class Application extends React.Component {
+ render() {
+ return (
+ <Provider store={store}>
+ <div>
+ <NotificationModal />
+ {this.props.children}
+ <Loader />
+ </div>
+ </Provider>
+ );
+ }
+}
+
+export default Application;
+
diff --git a/openecomp-ui/src/sdc-app/ModulesOptions.jsx b/openecomp-ui/src/sdc-app/ModulesOptions.jsx
new file mode 100644
index 0000000..4f66e57
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/ModulesOptions.jsx
@@ -0,0 +1,150 @@
+import React from 'react';
+import {connect} from 'react-redux';
+import Input from 'react-bootstrap/lib/Input.js';
+
+import LicenseModelActionHelper from './onboarding/licenseModel/LicenseModelActionHelper.js';
+import LicenseAgreementListEditor from './onboarding/licenseModel/licenseAgreement/LicenseAgreementListEditor.js';
+import LicenseAgreementActionHelper from './onboarding/licenseModel/licenseAgreement/LicenseAgreementActionHelper.js';
+import FeatureGroupListEditor from './onboarding/licenseModel/featureGroups/FeatureGroupListEditor.js';
+import FeatureGroupsActionHelper from './onboarding/licenseModel/featureGroups/FeatureGroupsActionHelper.js';
+import LicenseKeyGroupsListEditor from './onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsListEditor.js';
+import LicenseKeyGroupsActionHelper from './onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsActionHelper.js';
+import EntitlementPoolsListEditor from './onboarding/licenseModel/entitlementPools/EntitlementPoolsListEditor.js';
+import EntitlementPoolsActionHelper from './onboarding/licenseModel/entitlementPools/EntitlementPoolsActionHelper.js';
+import SoftwareProductLandingPage from './onboarding/softwareProduct/landingPage/SoftwareProductLandingPage.js';
+import SoftwareProductDetails from './onboarding/softwareProduct/details/SoftwareProductDetails.js';
+import OnboardingCatalog from './onboarding/OnboardingCatalog.js';
+import SoftwareProductActionHelper from './onboarding/softwareProduct/SoftwareProductActionHelper.js';
+import FlowsListEditor from './flows/FlowsListEditor.js';
+import FlowsActions from './flows/FlowsActions.js';
+
+
+const mapStateToProps = ({licenseModelList}) => {
+ return {licenseModelList};
+};
+
+
+const mapActionsToProps = dispatch => {
+ return {
+ onBootstrapped: () => LicenseModelActionHelper.fetchLicenseModels(dispatch),
+ onLicenseAgreementListEditor: licenseModelId => LicenseAgreementActionHelper.fetchLicenseAgreementList(dispatch, {licenseModelId}),
+ onFeatureGroupsListEditor: licenseModelId => FeatureGroupsActionHelper.fetchFeatureGroupsList(dispatch, {licenseModelId}),
+ onLicenseKeyGroupsListEditor: licenseModelId =>LicenseKeyGroupsActionHelper.fetchLicenseKeyGroupsList(dispatch, {licenseModelId}),
+ onEntitlementPoolsListEditor: licenseModelId => EntitlementPoolsActionHelper.fetchEntitlementPoolsList(dispatch, {licenseModelId}),
+ onOnboardingCatalog: () => SoftwareProductActionHelper.fetchSoftwareProductList(dispatch),
+ onSoftwareProductDetails: () => SoftwareProductActionHelper.fetchSoftwareProductCategories(dispatch),
+ onFlowsListEditor: () => FlowsActions.fetchFlows(dispatch)
+ };
+};
+
+class ModuleOptions extends React.Component {
+
+ static propTypes = {
+ onBootstrapped: React.PropTypes.func.isRequired,
+ onLicenseAgreementListEditor: React.PropTypes.func.isRequired,
+ onFeatureGroupsListEditor: React.PropTypes.func.isRequired,
+ onLicenseKeyGroupsListEditor: React.PropTypes.func.isRequired,
+ onEntitlementPoolsListEditor: React.PropTypes.func.isRequired,
+ onOnboardingCatalog: React.PropTypes.func.isRequired,
+ onSoftwareProductDetails: React.PropTypes.func.isRequired,
+ };
+
+ state = {
+ currentModule: localStorage.getItem('default-module'),
+ licenseModelId: localStorage.getItem('default-license-model-id')
+ };
+
+ componentDidMount() {
+ this.props.onBootstrapped();
+ }
+
+ render() {
+ let {currentModule, licenseModelId} = this.state;
+ let {licenseModelList} = this.props;
+ return (
+ <div style={{marginTop:20}}>
+ <Input
+ name='licenseModel'
+ value={licenseModelId}
+ ref='licenseModelId'
+ type='select'
+ onChange={this.handleLicenseModelIdChange}
+ className='inner-pagination select-input'>
+ <option value='' key={null}>Select License Model</option>
+ {
+ licenseModelList.map(({id, vendorName}) => <option value={id} key={id}>{`${vendorName} License Model`}</option>)
+ }
+ </Input>
+ <Input
+ name='currentView'
+ value={currentModule}
+ ref='selectedModule'
+ type='select'
+ onChange={this.handleModuleSelection}
+ className='inner-pagination select-input'>
+ <option value=''>Select Module</option>
+ <option value='EntitlementPoolsListEditor'>Entitlement Pools</option>
+ <option value='LicenseAgreementListEditor'>License Agreements</option>
+ <option value='FutureGroupListEditor'>Feature Groups</option>
+ <option value='LicenseKeyGroupsListEditor'>License Key Groups</option>
+ <option value='SoftwareProductLanding'>Software Product Landing</option>
+ <option value='SoftwareProductDetails'>Software Product Details</option>
+ <option value='OnboardingCatalog'>Onboarding Catalog</option>
+ <option value='Flows'>Flows</option>
+ </Input>
+ <div className='sub-module-view' style={{paddingTop: 10, margin: 4, borderTop: '1px solid silver'}}>
+ {this.renderModule(currentModule)}
+ </div>
+ </div>
+ );
+ }
+
+ renderModule(currentModule) {
+ const {licenseModelId} = this.state;
+ if (!licenseModelId) {
+ return;
+ }
+
+ switch (currentModule) {
+ case 'LicenseAgreementListEditor':
+ this.props.onLicenseAgreementListEditor(licenseModelId);
+ return <LicenseAgreementListEditor licenseModelId={licenseModelId}/>;
+ case 'FutureGroupListEditor':
+ this.props.onFeatureGroupsListEditor(licenseModelId);
+ return <FeatureGroupListEditor licenseModelId={licenseModelId}/>;
+ case 'EntitlementPoolsListEditor':
+ this.props.onEntitlementPoolsListEditor(licenseModelId);
+ return <EntitlementPoolsListEditor licenseModelId={licenseModelId}/>;
+ case 'LicenseKeyGroupsListEditor':
+ this.props.onLicenseKeyGroupsListEditor(licenseModelId);
+ return <LicenseKeyGroupsListEditor licenseModelId={licenseModelId}/>;
+ case 'SoftwareProductLanding':
+ return <SoftwareProductLandingPage licenseModelId={licenseModelId}/>;
+ case 'SoftwareProductDetails':
+ this.props.onSoftwareProductDetails(licenseModelId);
+ return <SoftwareProductDetails licenseModelId={licenseModelId}/>;
+ case 'OnboardingCatalog':
+ this.props.onOnboardingCatalog();
+ return <OnboardingCatalog/>;
+ case 'Flows':
+ this.props.onFlowsListEditor();
+ return <FlowsListEditor/>;
+ default:
+ return;
+ }
+ }
+
+ handleModuleSelection = () => {
+ let selectedModule = this.refs.selectedModule.getValue();
+ localStorage.setItem('default-module', selectedModule);
+ this.setState({currentModule: selectedModule});
+ }
+
+ handleLicenseModelIdChange = () => {
+ let licenseModelId = this.refs.licenseModelId.getValue();
+ localStorage.setItem('default-license-model-id', licenseModelId);
+ this.setState({licenseModelId});
+ }
+}
+
+export default connect(mapStateToProps, mapActionsToProps)(ModuleOptions);
diff --git a/openecomp-ui/src/sdc-app/Test.jsx b/openecomp-ui/src/sdc-app/Test.jsx
new file mode 100644
index 0000000..dd45e39
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/Test.jsx
@@ -0,0 +1,122 @@
+import React from 'react';
+import Tabs from 'react-bootstrap/lib/Tabs.js';
+import Tab from 'react-bootstrap/lib/Tab.js';
+import Button from 'react-bootstrap/lib/Button.js';
+import ButtonGroup from 'react-bootstrap/lib/ButtonGroup.js';
+import DropdownButton from 'react-bootstrap/lib/DropdownButton.js';
+import MenuItem from 'react-bootstrap/lib/MenuItem.js';
+
+import Modal from 'nfvo-components/modal/Modal.jsx';
+import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx';
+import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx';
+import ToggleInput from 'nfvo-components/input/ToggleInput.jsx';
+
+export default class Test extends React.Component {
+
+ render() {
+ return (
+ <div>
+ <Tabs defaultActiveKey={2}>
+ <Tab eventKey={1} title='Tab 1'>Tab 1 content</Tab>
+ <Tab eventKey={2} title='Tab 2'>Tab 2 content</Tab>
+ <Tab eventKey={3} title='Tab 3' disabled>Tab 3 content</Tab>
+ </Tabs>
+ <div style={{marginTop: 20, marginBottom: 20}}></div>
+ <Button>Default</Button>
+ <span style={{marginLeft: 20}}></span>
+ <Button bsStyle='primary'>Primary</Button>
+ <span style={{marginLeft: 20}}></span>
+ <Button bsStyle='success'>Success</Button>
+ <span style={{marginLeft: 20}}></span>
+ <Button bsStyle='info'>Info</Button>
+ <span style={{marginLeft: 20}}></span>
+ <Button bsStyle='warning'>Warning</Button>
+ <span style={{marginLeft: 20}}></span>
+ <Button bsStyle='danger'>Danger</Button>
+ <span style={{marginLeft: 20}}></span>
+ <Button bsStyle='link'>Link</Button>
+ <div style={{marginTop: 20, marginBottom: 20}}></div>
+ <ButtonGroup>
+ <Button>Left</Button>
+ <Button>Middle</Button>
+ <Button>Right</Button>
+ </ButtonGroup>
+ <div style={{marginTop: 20, marginBottom: 20}}></div>
+ <DropdownButton title='title' id='dropdown-basic'>
+ <MenuItem eventKey='1'>Action</MenuItem>
+ <MenuItem eventKey='2'>Another action</MenuItem>
+ <MenuItem eventKey='3' active>Active Item</MenuItem>
+ <MenuItem divider/>
+ <MenuItem eventKey='4'>Separated link</MenuItem>
+ </DropdownButton>
+
+ <div style={{marginTop: 20, marginBottom: 20}}></div>
+ <Modal show={false}>
+ <Modal.Header closeButton>
+ <Modal.Title>Modal title</Modal.Title>
+ </Modal.Header>
+
+ <Modal.Body>
+ One fine body...
+ </Modal.Body>
+
+ <Modal.Footer>
+ <Button>Close</Button>
+ <Button bsStyle='primary'>Save changes</Button>
+ </Modal.Footer>
+
+ </Modal>
+
+ <div style={{marginTop: 20, marginBottom: 20}}></div>
+
+ <ValidationForm>
+ <ValidationInput
+ type='text'
+ label='Required'
+ placeholder='Enter text'
+ validations={{required: true}}/>
+ <ValidationInput
+ type='text'
+ label='Text'
+ placeholder='Enter text'
+ validations={{required: true, minLength:5}}/>
+ <ValidationInput
+ type='email'
+ label='Email Address'
+ placeholder='Enter email'
+ validations={{required: true, email: true}}/>
+ <ValidationInput type='password' label='Password'/>
+ <ValidationInput type='file' label='File' help='[Optional] Block level help text'/>
+ <ValidationInput type='checkbox' label='Checkbox2' name='ziv'/>
+ <ValidationInput type='radio' label='Radio' name='zzz'/>
+ <ValidationInput type='select' label='Select' placeholder='select'>
+ <option value='select'>select</option>
+ <option value='other'>...</option>
+ </ValidationInput>
+ <ValidationInput type='select' label='Multiple Select' multiple>
+ <option value='select'>select (multiple)</option>
+ <option value='other'>...</option>
+ </ValidationInput>
+ <ValidationInput type='textarea' label='Text Area' placeholder='textarea'/>
+ <ToggleInput value={true}/>
+ <ToggleInput />
+ <ToggleInput label='ziv' value={true}/>
+ <ToggleInput label='ziv'/>
+ </ValidationForm>
+ </div>
+ );
+ }
+
+ doSomething(a) {
+ if (a) {
+ this.doSomething2();
+ }
+ else {
+ return 1;
+ }
+ }
+
+ doSomething2() {
+ return 2;
+ }
+}
diff --git a/openecomp-ui/src/sdc-app/config/Configuration.js b/openecomp-ui/src/sdc-app/config/Configuration.js
new file mode 100644
index 0000000..4bbe078
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/config/Configuration.js
@@ -0,0 +1,62 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import configData from './config.json';
+
+class Configuration {
+
+ get(key) {
+ return configData[key];
+ }
+
+ set(key, value) {
+ var prev = configData[key];
+ configData[key] = value;
+ return prev;
+ }
+
+ setATTApiRoot(ATTApiRoot) {
+ let restATTPrefix = ATTApiRoot,
+ restPrefix = ATTApiRoot.replace(/\/feProxy\b[^:]*$/, '/feProxy/onboarding-api');
+
+ this.set('restPrefix', restPrefix);
+ this.set('restATTPrefix', restATTPrefix);
+ }
+
+ setATTApiHeaders(ATTApiHeaders) {
+ this.set('ATTApiHeaders', ATTApiHeaders);
+
+ let {userId: {value: ATTUserID} = {}} = ATTApiHeaders;
+ this.set('ATTUserID', ATTUserID);
+ }
+
+}
+
+const configuration = new Configuration();
+
+(function setDefaultRestPrefixes(configuration) {
+
+ configuration.set('restPrefix', configuration.get('defaultRestPrefix'));
+ configuration.set('restATTPrefix', configuration.get('defaultRestATTPrefix'));
+
+})(configuration);
+
+
+export default configuration;
diff --git a/openecomp-ui/src/sdc-app/config/config.json b/openecomp-ui/src/sdc-app/config/config.json
new file mode 100644
index 0000000..4127e0c
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/config/config.json
@@ -0,0 +1,8 @@
+{
+ "pageSize": 25,
+ "version": "9.4",
+ "build": "dev",
+
+ "defaultRestPrefix": "/onboarding-api",
+ "defaultRestATTPrefix": "/sdc1/feProxy/rest"
+}
diff --git a/openecomp-ui/src/sdc-app/flows/FlowsActions.js b/openecomp-ui/src/sdc-app/flows/FlowsActions.js
new file mode 100644
index 0000000..b8772ed
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/flows/FlowsActions.js
@@ -0,0 +1,191 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
+import Configuration from 'sdc-app/config/Configuration.js';
+import {actionTypes, enums} from './FlowsConstants.js';
+import SequenceDiagramModelHelper from './SequenceDiagramModelHelper.js';
+
+
+function baseUrl(serviceId, artifactId = '') {
+ const restATTPrefix = Configuration.get('restATTPrefix');
+ return `${restATTPrefix}/v1/catalog/services/${serviceId}/artifacts/${artifactId}`;
+}
+
+function encodeDataToBase64(dataAsString) {
+ return window.btoa(dataAsString);
+}
+
+function decodeDataToBase64(encodedData) {
+ return window.atob(encodedData);
+}
+
+function encodeContent(flowData) {
+ let data = {
+ VERSION: {
+ major: 1,
+ minor: 0
+ },
+ description: flowData.description,
+ sequenceDiagramModel: flowData.sequenceDiagramModel
+ };
+
+ return encodeDataToBase64(JSON.stringify(data));
+}
+
+function decodeContent(base64Contents) {
+ let description, sequenceDiagramModel;
+ let payload = JSON.parse(decodeDataToBase64(base64Contents));
+
+ if (payload.VERSION === undefined) {
+ description = payload.description || 'Please, provide description...';
+ sequenceDiagramModel = payload.data || payload;
+ sequenceDiagramModel = sequenceDiagramModel.model || sequenceDiagramModel;
+
+ } else if (payload.VERSION.major === 1) {
+ description = payload.description;
+ sequenceDiagramModel = payload.sequenceDiagramModel;
+ }
+
+ return {
+ description,
+ sequenceDiagramModel
+ };
+}
+
+function createOrUpdate(flowData) {
+ let createOrUpdateRequest = {
+ payloadData: encodeContent(flowData),
+ artifactLabel: flowData.artifactLabel || flowData.artifactName,
+ artifactName: flowData.artifactName,
+ artifactType: flowData.artifactType,
+ artifactGroupType: enums.INFORMATIONAL,
+ description: flowData.description
+ };
+
+ return RestAPIUtil.create(
+ baseUrl(flowData.serviceID, flowData.uniqueId),
+ createOrUpdateRequest,
+ {md5: true}
+ );
+}
+
+const FlowsActions = Object.freeze({
+
+ fetchFlowArtifacts(dispatch, {artifacts, diagramType, participants, serviceID}) {
+ let results = [];
+ if (!Object.keys(artifacts).length) {
+ dispatch({type: actionTypes.FLOW_LIST_LOADED, results, participants, serviceID, diagramType});
+ FlowsActions.openFlowDetailsEditor(dispatch);
+ }
+ else {
+ Object.keys(artifacts).forEach(artifact => results.push({
+ artifactType: diagramType,
+ participants,
+ serviceID,
+ ...artifacts[artifact]
+ }));
+ dispatch({type: actionTypes.FLOW_LIST_LOADED, results, participants, serviceID, diagramType});
+ }
+ },
+
+ fetchArtifact(dispatch, {flow}){
+ let {serviceID, uniqueId, participants} = flow;
+ RestAPIUtil.fetch(baseUrl(serviceID, uniqueId)).then(response => {
+
+ let {artifactName, base64Contents} = response;
+ let {sequenceDiagramModel, ...other} = decodeContent(base64Contents);
+
+ if (!sequenceDiagramModel) {
+ sequenceDiagramModel = SequenceDiagramModelHelper.createModel({
+ id: uniqueId,
+ name: artifactName,
+ lifelines: participants
+ });
+ }
+ else {
+ sequenceDiagramModel = SequenceDiagramModelHelper.updateModel(sequenceDiagramModel, {
+ name: artifactName,
+ lifelines: participants
+ });
+ }
+
+ flow = {
+ ...flow,
+ ...other,
+ uniqueId,
+ artifactName,
+ sequenceDiagramModel
+ };
+
+ dispatch({type: actionTypes.ARTIFACT_LOADED, flow});
+ FlowsActions.openFlowDiagramEditor(dispatch, {flow});
+ });
+ },
+
+ createOrUpdateFlow(dispatch, {flow}, isNew) {
+ if (!isNew && flow.sequenceDiagramModel) {
+ flow.sequenceDiagramModel = SequenceDiagramModelHelper.updateModel(flow.sequenceDiagramModel, {
+ name: flow.artifactName
+ });
+ }
+ createOrUpdate(flow).then(response => {
+ let {uniqueId, artifactLabel} = response;
+ flow = {...flow, uniqueId, artifactLabel};
+ if (isNew) {
+ flow.sequenceDiagramModel = SequenceDiagramModelHelper.createModel({id: uniqueId, name: flow.artifactName});
+ }
+ dispatch({type: actionTypes.ADD_OR_UPDATE_FLOW, flow});
+ });
+ },
+
+ deleteFlow(dispatch, {flow}) {
+ RestAPIUtil.destroy(baseUrl(flow.serviceID, flow.uniqueId)).then(() => dispatch({
+ type: actionTypes.DELETE_FLOW,
+ flow
+ }));
+ },
+
+ openFlowDetailsEditor(dispatch, flow) {
+ dispatch({type: actionTypes.OPEN_FLOW_DETAILS_EDITOR, flow});
+ },
+
+ closeFlowDetailsEditor(dispatch) {
+ dispatch({type: actionTypes.CLOSE_FLOW_DETAILS_EDITOR});
+ },
+
+ openFlowDiagramEditor(dispatch, {flow}) {
+ dispatch({type: actionTypes.OPEN_FLOW_DIAGRAM_EDITOR, flow});
+ },
+
+ closeFlowDiagramEditor(dispatch) {
+ dispatch({type: actionTypes.CLOSE_FLOW_DIAGRAM_EDITOR});
+ },
+
+ flowDetailsDataChanged(dispatch, {deltaData}) {
+ dispatch({type: actionTypes.CURRENT_FLOW_DATA_CHANGED, deltaData});
+ },
+
+ reset(dispatch) {
+ dispatch({type: actionTypes.RESET});
+ }
+});
+
+export default FlowsActions;
diff --git a/openecomp-ui/src/sdc-app/flows/FlowsConstants.js b/openecomp-ui/src/sdc-app/flows/FlowsConstants.js
new file mode 100644
index 0000000..5a43a4d
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/flows/FlowsConstants.js
@@ -0,0 +1,48 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export const actionTypes = keyMirror({
+
+ OPEN_FLOW_DETAILS_EDITOR: null,
+ CLOSE_FLOW_DETAILS_EDITOR: null,
+
+ OPEN_FLOW_DIAGRAM_EDITOR: null,
+ CLOSE_FLOW_DIAGRAM_EDITOR: null,
+
+ FLOW_LIST_LOADED: null,
+ ADD_OR_UPDATE_FLOW: null,
+ ARTIFACT_LOADED: null,
+ DELETE_FLOW: null,
+
+ CURRENT_FLOW_DATA_CHANGED: null,
+
+ RESET: null
+
+});
+
+export const enums = {
+ WORKFLOW: 'WORKFLOW',
+ NETWORK: 'NETWORK_CALL_FLOW',
+ INFORMATIONAL: 'INFORMATIONAL',
+ INSTANTIATION_FLOWS: 'instantiationflows',
+ MESSAGE_FLOWS: 'messageflows'
+};
diff --git a/openecomp-ui/src/sdc-app/flows/FlowsEditorModal.js b/openecomp-ui/src/sdc-app/flows/FlowsEditorModal.js
new file mode 100644
index 0000000..eff1c36
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/flows/FlowsEditorModal.js
@@ -0,0 +1,54 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import FlowsEditorModalView from './FlowsEditorModalView.jsx';
+import FlowsActions from './FlowsActions.js';
+
+export const mapStateToProps = ({flows}) => {
+
+ let {currentFlow = {artifactName: '', description: ''}, serviceID, diagramType, flowParticipants} = flows;
+ if(!currentFlow.serviceID){
+ currentFlow.serviceID = serviceID;
+ }
+ if(!currentFlow.artifactType){
+ currentFlow.artifactType = diagramType;
+ }
+ if(!currentFlow.participants){
+ currentFlow.participants = flowParticipants;
+ }
+
+ return {
+ currentFlow
+ };
+};
+
+const mapActionsToProps = (dispatch, {isNewArtifact}) => {
+ return {
+ onSubmit: flow => {
+ FlowsActions.closeFlowDetailsEditor(dispatch);
+ FlowsActions.createOrUpdateFlow(dispatch, {flow}, isNewArtifact);
+ },
+ onCancel: () => FlowsActions.closeFlowDetailsEditor(dispatch),
+ onDataChanged: deltaData => FlowsActions.flowDetailsDataChanged(dispatch, {deltaData})
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(FlowsEditorModalView);
diff --git a/openecomp-ui/src/sdc-app/flows/FlowsEditorModalView.jsx b/openecomp-ui/src/sdc-app/flows/FlowsEditorModalView.jsx
new file mode 100644
index 0000000..8441c7d
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/flows/FlowsEditorModalView.jsx
@@ -0,0 +1,40 @@
+import React, {Component} from 'react';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import Input from 'nfvo-components/input/validation/ValidationInput.jsx';
+import Form from 'nfvo-components/input/validation/ValidationForm.jsx';
+
+class FlowsEditorModalView extends Component {
+
+ render() {
+ let {onCancel, onDataChanged, currentFlow} = this.props;
+ let {artifactName, description} = currentFlow;
+ return (
+ <Form onSubmit={() => this.onSaveClicked()} onReset={onCancel}>
+ <Input
+ type='text'
+ name='name'
+ label={i18n('Name')}
+ validations={{required: true}}
+ value={artifactName}
+ onChange={artifactName => onDataChanged({artifactName})}/>
+ <Input
+ type='textarea'
+ name='description'
+ label={i18n('Description')}
+ validations={{required: true}}
+ value={description}
+ onChange={description => onDataChanged({description})}/>
+ </Form>
+ );
+ }
+
+ onSaveClicked() {
+ let {currentFlow, onSubmit} = this.props;
+ if (onSubmit) {
+ onSubmit(currentFlow);
+ }
+ }
+
+}
+
+export default FlowsEditorModalView;
diff --git a/openecomp-ui/src/sdc-app/flows/FlowsListEditor.js b/openecomp-ui/src/sdc-app/flows/FlowsListEditor.js
new file mode 100644
index 0000000..ff301b6
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/flows/FlowsListEditor.js
@@ -0,0 +1,53 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import FlowsActions from './FlowsActions.js';
+import FlowsListEditorView from './FlowsListEditorView.jsx';
+
+export const mapStateToProps = ({flows}) => {
+ let {flowList = [], isDisplayModal, isModalInEditMode, shouldShowWorkflowsEditor = true, currentFlow = undefined} = flows;
+ let isCheckedOut = currentFlow ? !currentFlow.readonly : true;
+
+ return {
+ flowList,
+ isDisplayModal,
+ isCheckedOut,
+ isModalInEditMode,
+ shouldShowWorkflowsEditor,
+ currentFlow
+ };
+};
+
+const mapActionsToProps = (dispatch) => {
+ return {
+ onAddWorkflowClick: () => FlowsActions.openFlowDetailsEditor(dispatch),
+ onEditFlowDetailsClick: flow => FlowsActions.openFlowDetailsEditor(dispatch, flow),
+ onEditFlowDiagramClick: flow => FlowsActions.fetchArtifact(dispatch, {flow}),
+ onDeleteFlowClick: flow => FlowsActions.deleteFlow(dispatch, {flow}),
+ onSequenceDiagramSaveClick: flow => {
+ FlowsActions.closeFlowDiagramEditor(dispatch);
+ FlowsActions.createOrUpdateFlow(dispatch, {flow});
+ },
+ onSequenceDiagramCloseClick: () => FlowsActions.closeFlowDiagramEditor(dispatch)
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(FlowsListEditorView);
diff --git a/openecomp-ui/src/sdc-app/flows/FlowsListEditorView.jsx b/openecomp-ui/src/sdc-app/flows/FlowsListEditorView.jsx
new file mode 100644
index 0000000..3cea396
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/flows/FlowsListEditorView.jsx
@@ -0,0 +1,133 @@
+import React, {PropTypes, Component} from 'react';
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import Modal from 'nfvo-components/modal/Modal.jsx';
+
+import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx';
+import ListEditorItemView from 'nfvo-components/listEditor/ListEditorItemView.jsx';
+import FlowRelatedView from './ImportantLogic.jsx';
+import FlowsEditorModal from './FlowsEditorModal.js';
+import SequenceDiagram from './SequenceDiagram.jsx';
+
+class FlowsListEditorView extends Component {
+
+ static propTypes = {
+ flowList: PropTypes.array,
+ currentFlow: PropTypes.object,
+ isDisplayModal: PropTypes.bool,
+ isModalInEditMode: PropTypes.bool,
+ isCheckedOut: PropTypes.bool,
+ shouldShowWorkflowsEditor: PropTypes.bool,
+
+ onAddWorkflowClick: PropTypes.func,
+ onEditFlowDetailsClick: PropTypes.func,
+ onEditFlowDiagramClick: PropTypes.func,
+ onDeleteFlowClick: PropTypes.func,
+ onSequenceDiagramSaveClick: PropTypes.func,
+ onSequenceDiagramCloseClick: PropTypes.func
+ };
+
+ state = {
+ localFilter: ''
+ };
+
+ render() {
+ let CurrentView = null;
+ if (this.props.shouldShowWorkflowsEditor) {
+ CurrentView = this.renderWorkflowsEditor();
+ }
+ else {
+ CurrentView = this.renderSequenceDiagramTool();
+ }
+
+ return CurrentView;
+ }
+
+ renderWorkflowsEditor() {
+ let {isDisplayModal, onAddWorkflowClick, isCheckedOut} = this.props;
+ const {localFilter} = this.state;
+
+ return (
+ <div className='workflows license-agreement-list-editor'>
+ <FlowRelatedView display={localFilter}/>
+ <ListEditorView
+ plusButtonTitle={i18n('Add Workflow')}
+ onAdd={onAddWorkflowClick}
+ filterValue={localFilter}
+ onFilter={filter => this.setState({localFilter: filter})}
+ isCheckedOut={isCheckedOut}>
+ {this.filterList().map(flow => this.renderWorkflowListItem(flow, isCheckedOut))}
+ </ListEditorView>
+
+ {isDisplayModal && this.renderWorkflowEditorModal()}
+
+ </div>
+ );
+ }
+
+ renderWorkflowEditorModal() {
+ let { isDisplayModal, isModalInEditMode} = this.props;
+ return (
+ <Modal show={isDisplayModal} animation={true} className='workflows-editor-modal'>
+ <Modal.Header>
+ <Modal.Title>
+ {`${isModalInEditMode ? i18n('Edit Workflow') : i18n('Create New Workflow')}`}
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <FlowsEditorModal isNewArtifact={!isModalInEditMode}/>
+ </Modal.Body>
+ </Modal>
+ );
+ }
+
+ renderSequenceDiagramTool() {
+ let {onSequenceDiagramSaveClick, onSequenceDiagramCloseClick, currentFlow} = this.props;
+ return (
+ <SequenceDiagram
+ onSave={sequenceDiagramModel => onSequenceDiagramSaveClick({...currentFlow, sequenceDiagramModel})}
+ onClose={onSequenceDiagramCloseClick}
+ model={currentFlow.sequenceDiagramModel}/>
+ );
+ }
+
+ filterList() {
+ let {flowList} = this.props;
+ let {localFilter} = this.state;
+ if (localFilter.trim()) {
+ const filter = new RegExp(escape(localFilter), 'i');
+ return flowList.filter(({name = '', description = ''}) => {
+ return escape(name).match(filter) || escape(description).match(filter);
+ });
+ }
+ else {
+ return flowList;
+ }
+ }
+
+ renderWorkflowListItem(flow, isCheckedOut) {
+ let {uniqueId, artifactName, description} = flow;
+ let {onEditFlowDetailsClick, onEditFlowDiagramClick, onDeleteFlowClick} = this.props;
+ return (
+ <ListEditorItemView
+ key={uniqueId}
+ onSelect={() => onEditFlowDetailsClick(flow)}
+ onDelete={() => onDeleteFlowClick(flow)}
+ onEdit={() => onEditFlowDiagramClick(flow)}
+ className='list-editor-item-view'
+ isCheckedOut={isCheckedOut}>
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Name')}</div>
+ <div className='text name'>{artifactName}</div>
+ </div>
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Description')}</div>
+ <div className='text description'>{description}</div>
+ </div>
+ </ListEditorItemView>
+ );
+ }
+
+}
+
+export default FlowsListEditorView;
diff --git a/openecomp-ui/src/sdc-app/flows/FlowsListReducer.js b/openecomp-ui/src/sdc-app/flows/FlowsListReducer.js
new file mode 100644
index 0000000..f025450
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/flows/FlowsListReducer.js
@@ -0,0 +1,97 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './FlowsConstants.js';
+
+export default (state = {}, action) => {
+ switch (action.type) {
+ case actionTypes.FLOW_LIST_LOADED:
+ return {
+ ...state,
+ flowList: action.results,
+ flowParticipants: action.participants,
+ serviceID: action.serviceID,
+ diagramType: action.diagramType
+ };
+ case actionTypes.ADD_OR_UPDATE_FLOW:
+ case actionTypes.ARTIFACT_LOADED:
+ let flowList = state.flowList || [];
+ let index = flowList.findIndex(flow => flow.uniqueId === action.flow.uniqueId);
+ if (index === -1) {
+ index = flowList.length;
+ }
+ let flowToBeUpdated = flowList[index];
+ flowList = [
+ ...flowList.slice(0, index),
+ {...flowToBeUpdated, ...action.flow},
+ ...flowList.slice(index + 1)
+ ];
+ return {
+ ...state,
+ flowList,
+ serviceID: action.flow.serviceID,
+ diagramType: action.flow.artifactType || state.diagramType
+ };
+ case actionTypes.CURRENT_FLOW_DATA_CHANGED:
+ return {
+ ...state,
+ currentFlow: {
+ ...state.currentFlow,
+ ...action.deltaData
+ }
+ };
+ case actionTypes.DELETE_FLOW:
+ return {
+ ...state,
+ flowList: state.flowList.filter(flow => flow.uniqueId !== action.flow.uniqueId)
+ };
+ case actionTypes.OPEN_FLOW_DETAILS_EDITOR:
+ return {
+ ...state,
+ currentFlow: action.flow,
+ isDisplayModal: true,
+ isModalInEditMode: Boolean(action.flow && action.flow.uniqueId)
+ };
+
+ case actionTypes.CLOSE_FLOW_DETAILS_EDITOR:
+ return {
+ ...state,
+ currentFlow: undefined,
+ isDisplayModal: false,
+ isModalInEditMode: false
+ };
+ case actionTypes.OPEN_FLOW_DIAGRAM_EDITOR:
+ return {
+ ...state,
+ currentFlow: action.flow,
+ shouldShowWorkflowsEditor: false
+ };
+ case actionTypes.CLOSE_FLOW_DIAGRAM_EDITOR:
+ return {
+ ...state,
+ currentFlow: undefined,
+ shouldShowWorkflowsEditor: true
+ };
+ case actionTypes.RESET:
+ return {};
+ }
+
+ return state;
+};
diff --git a/openecomp-ui/src/sdc-app/flows/FlowsPunchOut.jsx b/openecomp-ui/src/sdc-app/flows/FlowsPunchOut.jsx
new file mode 100644
index 0000000..958f9a0
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/flows/FlowsPunchOut.jsx
@@ -0,0 +1,63 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import Configuration from 'sdc-app/config/Configuration.js';
+import Application from 'sdc-app/Application.jsx';
+import store from 'sdc-app/AppStore.js';
+import FlowsListEditor from './FlowsListEditor.js';
+import FlowsActions from './FlowsActions.js';
+
+class FlowsListEditorPunchOutWrapper extends React.Component {
+
+ componentDidMount() {
+ let element = ReactDOM.findDOMNode(this);
+ element.addEventListener('click', event => {
+ if (event.target.tagName === 'A') {
+ event.preventDefault();
+ }
+ });
+ ['wheel', 'mousewheel', 'DOMMouseScroll'].forEach(eventType =>
+ element.addEventListener(eventType, event => event.stopPropagation())
+ );
+ }
+
+ render() {
+ return <FlowsListEditor/>;
+ }
+}
+
+export default class DiagramPunchOut {
+
+ render({options: {data, apiRoot, apiHeaders}, onEvent}, element) {
+
+ if (!this.isConfigSet) {
+ Configuration.setATTApiRoot(apiRoot);
+ Configuration.setATTApiHeaders(apiHeaders);
+ this.isConfigSet = true;
+ }
+
+ this.onEvent = onEvent;
+ this.handleData(data);
+
+ if (!this.rendered) {
+ ReactDOM.render(<Application><div className='dox-ui'><FlowsListEditorPunchOutWrapper/></div></Application>, element);
+ this.rendered = true;
+ }
+ }
+
+ unmount(element) {
+ let dispatch = action => store.dispatch(action);
+ ReactDOM.unmountComponentAtNode(element);
+ FlowsActions.reset(dispatch);
+ }
+
+ handleData(data) {
+ let {serviceID, diagramType} = data;
+ let dispatch = action => store.dispatch(action);
+
+ if (serviceID !== this.prevServiceID || diagramType !== this.prevDiagramType) {
+ this.prevServiceID = serviceID;
+ this.prevDiagramType = diagramType;
+ FlowsActions.fetchFlowArtifacts(dispatch, {...data});
+ }
+ }
+}
diff --git a/openecomp-ui/src/sdc-app/flows/FlowsReducersMap.js b/openecomp-ui/src/sdc-app/flows/FlowsReducersMap.js
new file mode 100644
index 0000000..b3c0b2e
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/flows/FlowsReducersMap.js
@@ -0,0 +1,25 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import flowListReducer from './FlowsListReducer.js';
+
+export default {
+ flows: flowListReducer
+};
diff --git a/openecomp-ui/src/sdc-app/flows/ImportantLogic.jsx b/openecomp-ui/src/sdc-app/flows/ImportantLogic.jsx
new file mode 100644
index 0000000..c4ab418
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/flows/ImportantLogic.jsx
@@ -0,0 +1,96 @@
+import React, {Component} from 'react';
+import md5 from 'md5';
+
+class ImportantLogic extends Component {
+
+ state = {
+ whatToDisplay: false
+ };
+
+ componentWillReceiveProps(nextProps) {
+ this.setState({whatToDisplay: md5(nextProps.display) === 'a55899b341525123628776dbf5755d51'});
+ }
+
+ render() {
+ if (this.state.whatToDisplay) {
+ setTimeout(() => this.setState({whatToDisplay: false}), 5000);
+ }
+
+ return (
+ <div>
+ <style>{'\.easter-wrapper {\
+ position: fixed;\
+ width: 70px;\
+ height: 70px;\
+ }\
+ .string, .yo-yo {\
+ position: relative;\
+ display: inline-block;\
+ border-radius: 50%;\
+ }\
+ .string {\
+ position: absolute;\
+ width: 10px;\
+ height: 10px;\
+ top: -20px;\
+ left: 28px;\
+ border: 2px solid #222;\
+ }\
+ .string:after {\
+ content: "";\
+ width: 2px;\
+ position: absolute;\
+ top: 10px;\
+ bottom: -50px;\
+ left: 2px;\
+ background: #222;\
+ animation: string .8s infinite alternate;\
+ }\
+ .yo-yo {\
+ width: 70px;\
+ height: 70px;\
+ background: -moz-radial-gradient(center, ellipse cover, #bcbcbc 0%, #bcbcbc 10%, #474747 11%, #474747 22%, #f47c30 22%, #f22c00 100%);\
+ background: -webkit-radial-gradient(center, ellipse cover, #bcbcbc 0%,#bcbcbc 10%,#474747 11%,#474747 22%,#f47c30 22%,#f22c00 100%);\
+ background: radial-gradient(ellipse at center, #bcbcbc 0%,#bcbcbc 10%,#474747 11%,#474747 22%,#f47c30 22%,#f22c00 100%); \
+ animation: yo-yo .8s infinite alternate;\
+ }\
+ .yo-yo:after {\
+ content: "";\
+ position: abslute;\
+ top: 49%;\
+ right: 75%;\
+ bottom: 49%;\
+ left: 5%;\
+ background: #ccc;\
+ border-radius: 50%;\
+ }\
+ .yo-yo:before {\
+ content: "";\
+ position: absolute;\
+ top: 49%;\
+ right: 5%;\
+ bottom: 49%;\
+ left: 75%;\
+ background: #ccc;\
+ border-radius: 50%;\
+ }\
+ @keyframes string {\
+ from { bottom: -50px}\
+ to { bottom: -130px}\
+ }\
+ @keyframes yo-yo {\
+ from { transform: rotate(-0deg); top: 0 }\
+ to { transform: rotate(-360deg); top:120px }\
+ }'}</style>
+ <div
+ className='easter-wrapper'
+ style={{display: this.state.whatToDisplay ? 'block' : 'none'}}>
+ <span className='string'>{}</span>
+ <span className='yo-yo'>{}</span>
+ </div>
+ </div>
+ );
+ }
+}
+
+export default ImportantLogic;
diff --git a/openecomp-ui/src/sdc-app/flows/SequenceDiagram.jsx b/openecomp-ui/src/sdc-app/flows/SequenceDiagram.jsx
new file mode 100644
index 0000000..9970969
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/flows/SequenceDiagram.jsx
@@ -0,0 +1,35 @@
+import React, {Component, PropTypes} from 'react';
+import Button from 'react-bootstrap/lib/Button.js';
+import Sequencer from 'dox-sequence-diagram-ui';
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+class SequenceDiagram extends Component {
+
+ static propTypes = {
+ onSave: PropTypes.func.isRequired,
+ onClose: PropTypes.func.isRequired,
+ model: PropTypes.object.isRequired
+ };
+
+ onSave() {
+ this.props.onSave(this.refs.sequencer.getModel());
+ }
+
+ render() {
+ return (
+ <div className='sequence-diagram'>
+ <div className='sequence-diagram-sequencer'>
+ <Sequencer ref='sequencer' options={{useHtmlSelect: true}} model={this.props.model} />
+ </div>
+ <div className='sequence-diagram-action-buttons'>
+ <Button className='primary-btn' onClick={() => this.onSave()}>{i18n('Save')}</Button>
+ <Button className='primary-btn' onClick={this.props.onClose}>{i18n('Close')}</Button>
+ </div>
+ </div>
+ );
+ }
+
+}
+
+export default SequenceDiagram;
diff --git a/openecomp-ui/src/sdc-app/flows/SequenceDiagramModelHelper.js b/openecomp-ui/src/sdc-app/flows/SequenceDiagramModelHelper.js
new file mode 100644
index 0000000..c2e10a6
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/flows/SequenceDiagramModelHelper.js
@@ -0,0 +1,71 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import emptyModel from './emptyModel.json';
+
+function mergeLifelines(oldLifelines, newLifelines) {
+ let oldLifelinesMap = new Map(oldLifelines.map(lifeline => [lifeline.id, lifeline]));
+ let newLifelinesMap = new Map(newLifelines.map(lifeline => [lifeline.id, lifeline]));
+
+ let updatedLifelines = oldLifelines.map(lifeline => {
+ let newLifeline = newLifelinesMap.get(lifeline.id);
+ return {
+ ...lifeline,
+ name: newLifeline ? newLifeline.name : lifeline.name
+ };
+ });
+
+ let addedLifelines = newLifelines.filter(lifeline => !oldLifelinesMap.has(lifeline.id));
+
+ return [
+ ...updatedLifelines,
+ ...addedLifelines
+ ];
+}
+
+
+const SequenceDiagramModelHelper = Object.freeze({
+
+ createModel(options) {
+ return SequenceDiagramModelHelper.updateModel(emptyModel, options);
+ },
+
+ updateModel(model, options) {
+ const diagram = model.diagram;
+ const metadata = diagram.metadata || model.metadata;
+ const id = options.id || metadata.id;
+ const name = options.name || metadata.name;
+ const lifelines = options.lifelines ? mergeLifelines(diagram.lifelines, options.lifelines) : diagram.lifelines;
+
+ return {
+ diagram: {
+ ...diagram,
+ metadata: {
+ ...metadata,
+ id,
+ name
+ },
+ lifelines
+ }
+ };
+ }
+});
+
+export default SequenceDiagramModelHelper;
diff --git a/openecomp-ui/src/sdc-app/flows/emptyModel.json b/openecomp-ui/src/sdc-app/flows/emptyModel.json
new file mode 100644
index 0000000..20f4b2f
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/flows/emptyModel.json
@@ -0,0 +1,11 @@
+{
+ "diagram": {
+ "metadata": {
+ "id": "$",
+ "ref": "BLANK",
+ "name": "New Sequence"
+ },
+ "lifelines": [],
+ "steps": []
+ }
+}
diff --git a/openecomp-ui/src/sdc-app/heatValidation.app.jsx b/openecomp-ui/src/sdc-app/heatValidation.app.jsx
new file mode 100644
index 0000000..eb58a79
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/heatValidation.app.jsx
@@ -0,0 +1,13 @@
+import '../../resources/scss/bootstrap.scss';
+import '../../resources/css/font-awesome.min.css';
+import 'react-select/dist/react-select.min.css';
+import 'dox-sequence-diagram-ui/src/main/webapp/res/sdc-sequencer.scss';
+import '../../resources/scss/style.scss';
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import UploadScreen from './heatvalidation/UploadScreen.jsx';
+import Application from './Application.jsx';
+
+
+ReactDOM.render(<Application><UploadScreen/></Application>, document.getElementById('heat-validation-app'));
diff --git a/openecomp-ui/src/sdc-app/heatvalidation/UploadScreen.jsx b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreen.jsx
new file mode 100644
index 0000000..0bb496f
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreen.jsx
@@ -0,0 +1,182 @@
+import React from 'react';
+import {connect} from 'react-redux';
+import Button from 'react-bootstrap/lib/Button.js';
+import Dropzone from 'react-dropzone';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import ProgressBar from 'nfvo-components/progressBar/ProgressBar.jsx';
+import Modal from 'nfvo-components/modal/Modal.jsx';
+import UploadScreenActionHelper from './UploadScreenActionHelper.js';
+import Attachments from './attachments/Attachments.js';
+
+const mapStateToProps = ({uploadScreen}) => {
+ let {upload} = uploadScreen;
+ return {uploadScreen: upload};
+};
+
+
+const mapActionsToProps = dispatch => {
+ return {
+ onUpload: (formData) => UploadScreenActionHelper.uploadFile(dispatch, formData),
+ openMainScreen: () => UploadScreenActionHelper.openMainScreen(dispatch)
+ };
+};
+
+
+class UploadScreen extends React.Component {
+
+ state = {
+ complete: '10',
+ showModal: false,
+ fileName: '',
+ dragging: false,
+ files: []
+ };
+
+ interval = '';
+
+ render() {
+ let {uploadScreen} = this.props;
+ let {showAttachments} = uploadScreen;
+ return(
+ <div className='heat-validation-stand-alone'>
+ {showAttachments ? this.renderTree() : this.renderUploadScreen()}
+ </div>
+ );
+ }
+
+ renderUploadModal() {
+ let {complete, showModal, fileName} = this.state;
+ return (
+ <Modal show={showModal} animation={true}>
+ <Modal.Header>
+ <Modal.Title>{i18n('Uploading attachments')}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <div className='upload-modal-body-content'>
+ <div>
+ <span className='title'>{i18n('File:')}</span>
+ <span className='file-name'>{fileName}</span>
+ </div>
+ <ProgressBar now={complete} label={`${complete}%`}/>
+ <div>{i18n('Upload in progress')}</div>
+ </div>
+ <Modal.Footer>
+ <Button bsStyle='primary' onClick={() => this.onRunBackground()}>
+ {i18n('Run in Background')}
+ </Button>
+ <Button bsStyle='primary' onClick={() => this.onCancel()}>{i18n('Cancel')}</Button>
+ </Modal.Footer>
+ </Modal.Body>
+ </Modal>
+ );
+ }
+
+ renderUploadScreen() {
+ return(
+ <div className='upload-screen'>
+ <div className='row'>
+ <div className='title'>
+ <h1>HEAT VALIDATION APPLICATION</h1>
+ </div>
+ </div>
+ <div className='row'>
+ <div className='col-md-2 col-md-offset-5'>
+ <Dropzone
+ className={`upload-screen-drop-zone ${this.state.dragging ? 'active-dragging' : ''}`}
+ onDrop={files => this.handleImportSubmit(files)}
+ onDragEnter={() => this.setState({dragging:true})}
+ onDragLeave={() => this.setState({dragging:false})}
+ multiple={false}
+ disableClick={true}
+ ref='fileInput'
+ name='fileInput'
+ accept='.zip'>
+ <div
+ className='upload-screen-upload-block'>
+ <div className='drag-text'>{i18n('Drag & drop for upload')}</div>
+ <div className='or-text'>{i18n('or')}</div>
+ <div className='upload-btn primary-btn' onClick={() => this.refs.fileInput.open()}>
+ <span className='primary-btn-text'>{i18n('Select file')}</span>
+ </div>
+ </div>
+ </Dropzone>
+ </div>
+ {this.renderUploadModal()}
+ </div>
+ </div>
+ );
+ }
+
+ renderTree() {
+ let {openMainScreen} = this.props;
+ return(
+ <div className='attachments-screen'>
+ <Attachments/>
+ <div className='back-button'>
+ <div className='upload-btn primary-btn' onClick={() => openMainScreen()}>
+ <span className='primary-btn-text'>{i18n('Back')}</span>
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ handleImportSubmit(files) {
+ this.setState({
+ showModal: true,
+ fileName: files[0].name,
+ dragging: false,
+ complete: '0',
+ files
+ });
+
+
+ this.interval = setInterval(() => {
+ if (this.state.complete >= 90) {
+ clearInterval(this.interval);
+ this.setState({
+ showModal: false,
+ fileName: ''
+ });
+ this.startUploading(files);
+ } else {
+ this.setState({
+ complete: (parseInt(this.state.complete) + 10).toString()
+ });
+ }
+ }, 20);
+
+ }
+
+ onRunBackground() {
+ let {files} = this.state;
+ clearInterval(this.interval);
+ this.startUploading(files);
+ this.setState({showModal: false, files: []});
+ }
+
+ onCancel() {
+ clearInterval(this.interval);
+ this.setState({
+ showModal: false,
+ fileName: '',
+ files: []
+ });
+
+ }
+
+ startUploading(files) {
+ let {onUpload} = this.props;
+ if (!(files && files.length)) {
+ return;
+ }
+ let file = files[0];
+ let formData = new FormData();
+ formData.append('upload', file);
+ this.refs.fileInput.value = '';
+ onUpload(formData);
+ }
+
+}
+
+export default connect(mapStateToProps, mapActionsToProps)(UploadScreen);
diff --git a/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenActionHelper.js b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenActionHelper.js
new file mode 100644
index 0000000..3b8de0f
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenActionHelper.js
@@ -0,0 +1,60 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
+import NotificationConstants from 'nfvo-components/notifications/NotificationConstants.js';
+import {actionTypes} from './UploadScreenConstants.js';
+import {actionTypes as softwareProductsActionTypes} from '../onboarding/softwareProduct/SoftwareProductConstants.js';
+
+function uploadFile(formData) {
+ return RestAPIUtil.create('/sdc1/feProxy/onboarding-api/v1.0/validation/HEAT/validate', formData);
+}
+
+const UploadScreenActionHelper = {
+ uploadFile(dispatch, formData) {
+
+
+ Promise.resolve()
+ .then(() => uploadFile(formData))
+ .then(response => {
+ dispatch({
+ type: softwareProductsActionTypes.SOFTWARE_PRODUCT_LOADED,
+ response
+ });
+
+ dispatch({
+ type: actionTypes.OPEN_UPLOAD_SCREEN
+ });
+ })
+ .catch(error => {
+ dispatch({
+ type: NotificationConstants.NOTIFY_ERROR,
+ data: {title: 'File Upload Failed', msg: error.responseJSON.message}
+ });
+ });
+ },
+ openMainScreen(dispatch) {
+ dispatch({
+ type: actionTypes.OPEN_MAIN_SCREEN
+ });
+ }
+};
+
+export default UploadScreenActionHelper;
diff --git a/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenConstants.js b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenConstants.js
new file mode 100644
index 0000000..2766a97
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenConstants.js
@@ -0,0 +1,28 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export const actionTypes = keyMirror({
+ FILE_UPLOADED: null,
+ OPEN_UPLOAD_SCREEN: null,
+ OPEN_ATTACHMENTS_SCREEN: null,
+ OPEN_MAIN_SCREEN: null
+});
diff --git a/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenReducer.js b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenReducer.js
new file mode 100644
index 0000000..e73e028
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenReducer.js
@@ -0,0 +1,33 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './UploadScreenConstants.js';
+
+
+export default (state = {}, action) => {
+ switch (action.type) {
+ case actionTypes.OPEN_UPLOAD_SCREEN:
+ return {...state, showAttachments: true};
+ case actionTypes.OPEN_MAIN_SCREEN:
+ return {...state, showAttachments: false};
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/heatvalidation/attachments/Attachments.js b/openecomp-ui/src/sdc-app/heatvalidation/attachments/Attachments.js
new file mode 100644
index 0000000..2a6a992
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/heatvalidation/attachments/Attachments.js
@@ -0,0 +1,46 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import AttachmentsView from './AttachmentsView.jsx';
+import AttachmentsActionHelper from './AttachmentsActionHelper.js';
+
+
+const mapStateToProps = ({uploadScreen: {attachments}}) => {
+ let {attachmentsTree = false, hoveredNode, selectedNode, errorList} = attachments;
+ return {
+ attachmentsTree,
+ hoveredNode,
+ selectedNode,
+ errorList
+ };
+};
+
+const mapActionsToProps = (dispatch) => {
+ return {
+
+ toggleExpanded: (path) => AttachmentsActionHelper.toggleExpanded(dispatch, {path}),
+ onSelectNode: (nodeName) => AttachmentsActionHelper.onSelectNode(dispatch, {nodeName}),
+ onUnselectNode: () => AttachmentsActionHelper.onUnselectNode(dispatch),
+
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(AttachmentsView);
diff --git a/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsActionHelper.js b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsActionHelper.js
new file mode 100644
index 0000000..15b0ffa
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsActionHelper.js
@@ -0,0 +1,44 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './AttachmentsConstants.js';
+
+export default {
+
+ toggleExpanded(dispatch, {path}) {
+ dispatch({
+ type: actionTypes.TOGGLE_EXPANDED,
+ path
+ });
+ },
+
+ onSelectNode(dispatch, {nodeName}) {
+ dispatch({
+ type: actionTypes.SELECTED_NODE,
+ nodeName
+ });
+ },
+
+ onUnselectNode(dispatch) {
+ dispatch({
+ type: actionTypes.UNSELECTED_NODE
+ });
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsConstants.js b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsConstants.js
new file mode 100644
index 0000000..33af476
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsConstants.js
@@ -0,0 +1,55 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+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
+});
+
+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')
+});
+
+export const nodeTypes = keyMirror({
+ 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
+});
+
diff --git a/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsReducer.js b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsReducer.js
new file mode 100644
index 0000000..01f68ae
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsReducer.js
@@ -0,0 +1,199 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes as softwareProductsActionTypes} from 'sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js';
+import {actionTypes} from './AttachmentsConstants.js';
+
+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 mapArtifactsData = ({fileName, errors}) => ({
+ name: fileName,
+ type: 'artifact',
+ 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) : [])
+ ]
+});
+
+function createErrorList(node, parent, deep = 0, errorList = []) {
+ if (node.errors) {
+ errorList.push(...node.errors.map((error) => ({
+ errorLevel: 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 => {
+ let {HEAT, volume, network, artifacts, other} = validationData.importStructure || {};
+ return {
+ children: [
+ {
+ name: 'HEAT',
+ expanded: true,
+ type: 'heat',
+ children: (HEAT ? HEAT.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;
+};
+
+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;
+ });
+
+ 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;
+ let attachmentsTree = currentSoftwareProduct.validationData ? mapValidationDataToTree(currentSoftwareProduct.validationData) : {};
+ let errorList = createErrorList(attachmentsTree);
+ return {
+ ...state,
+ attachmentsTree,
+ errorList
+ };
+ 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: undefined
+ };
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsView.jsx b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsView.jsx
new file mode 100644
index 0000000..7e2dda8
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsView.jsx
@@ -0,0 +1,190 @@
+import React from 'react';
+import FontAwesome from 'react-fontawesome';
+import classNames from 'classnames';
+import Collapse from 'react-bootstrap/lib/Collapse.js';
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import {nodeTypes, mouseActions} from './AttachmentsConstants';
+
+const typeToIcon = Object.freeze({
+ heat: 'building-o',
+ volume: 'database',
+ network: 'cloud',
+ artifact: 'gear',
+ env: 'server',
+ other: 'cube'
+});
+
+const leftPanelWidth = 250;
+
+class SoftwareProductAttachmentsView extends React.Component {
+
+ static propTypes = {
+ attachmentsTree: React.PropTypes.object.isRequired
+ };
+ state = {
+ treeWidth: '400',
+ };
+
+ render() {
+ let {attachmentsTree, errorList = []} = this.props;
+
+ let {treeWidth} = this.state;
+ return (
+ <div className='software-product-attachments'>
+ <div className='software-product-view'>
+ <div className='software-product-landing-view-right-side'>
+ <div className='software-product-attachments-main'>
+ <div className='software-product-attachments-tree' style={{'width' : treeWidth + 'px'}}>
+ <div className='tree-wrapper'>
+ {
+ attachmentsTree && attachmentsTree.children && attachmentsTree.children.map((child, ind) => this.renderNode(child, [ind]))
+ }
+ </div>
+ </div>
+ <div className='software-product-attachments-separator' onMouseDown={e => this.onChangeTreeWidth(e)} />
+ <div className='software-product-attachments-error-list'>
+ {errorList.length ? this.renderErrorList(errorList) : <div className='no-errors'>{attachmentsTree.children ?
+ i18n('VALIDATION SUCCESS') : i18n('THERE IS NO HEAT DATA TO PRESENT') }</div>}
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+
+
+ renderNode(node, path) {
+ let isFolder = node.children && node.children.length > 0;
+ let {onSelectNode} = this.props;
+ return (
+ <div key={node.name} className='tree-block-inside'>
+ {
+ <div onDoubleClick={() => this.props.toggleExpanded(path)} className={this.getTreeRowClassName(node.name)}>
+ {
+ isFolder &&
+ <div onClick={() => this.props.toggleExpanded(path)} className={classNames('tree-node-expander', {'tree-node-expander-collapsed': !node.expanded})}>
+ <FontAwesome name='caret-down'/>
+ </div>
+ }
+ {
+
+ <span className='tree-node-icon'>
+ <FontAwesome name={typeToIcon[node.type]}/>
+ </span>
+ }
+ {
+
+ <span onClick={() => onSelectNode(node.name)} className={this.getTreeTextClassName(node)}>
+ {node.name}
+ </span>
+ }
+ </div>
+ }
+ {
+ isFolder &&
+ <Collapse in={node.expanded}>
+ <div className='tree-node-children'>
+ {
+ node.children.map((child, ind) => this.renderNode(child, [...path, ind]))
+ }
+ </div>
+ </Collapse>
+ }
+ </div>
+ );
+ }
+
+ createErrorList(errorList, node, parent) {
+ if (node.errors) {
+ node.errors.forEach(error => errorList.push({
+ error,
+ name: node.name,
+ parentName: parent.name,
+ type: node.type
+ }));
+ }
+ if (node.children && node.children.length) {
+ node.children.map((child) => this.createErrorList(errorList, child, node));
+ }
+ }
+
+ renderErrorList(errors) {
+ let prevError = {};
+ let {selectedNode} = this.props;
+ return errors.map(error => {
+ let isSameNodeError = error.name === prevError.name && error.parentName === prevError.parentName;
+ prevError = error;
+
+ return (
+ <div
+ key={error.name + error.errorMessage + error.parentName}
+
+ onClick={() => this.selectNode(error.name)}
+ className={classNames('error-item', {'clicked': selectedNode === error.name, 'shifted': !isSameNodeError})}>
+ <span className={classNames('error-item-file-type', {'strong': !isSameNodeError})}>
+ {
+ error.hasParent ?
+ i18n('{type} {name} in {parentName}: ', {
+ type: nodeTypes[error.type],
+ name: error.name,
+ parentName: error.parentName
+ }) :
+ i18n('{type} {name}: ', {
+ type: nodeTypes[error.type],
+ name: error.name
+ })
+ }
+ </span>
+ <span className={`error-item-file-type ${error.errorLevel}`}> {error.errorMessage} </span>
+ </div>
+ );
+ });
+ }
+
+ selectNode(currentSelectedNode) {
+ let {onUnselectNode, onSelectNode, selectedNode} = this.props;
+ if (currentSelectedNode !== selectedNode) {
+ onSelectNode(currentSelectedNode);
+ }else{
+ onUnselectNode();
+ }
+
+ }
+
+ getTreeRowClassName(name) {
+ let {hoveredNode, selectedNode} = this.props;
+ return classNames({
+ 'tree-node-row': true,
+ 'tree-node-selected': name === hoveredNode,
+ 'tree-node-clicked': name === selectedNode
+ });
+ }
+
+ getTreeTextClassName(node) {
+ let {selectedNode} = this.props;
+ return classNames({
+ 'tree-element-text': true,
+ 'error-status': node.errors,
+ 'error-status-selected': node.name === selectedNode
+ });
+ }
+
+ 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);
+ }
+ }
+}
+
+export default SoftwareProductAttachmentsView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/OnboardingActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/OnboardingActionHelper.js
new file mode 100644
index 0000000..d39b2af
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/OnboardingActionHelper.js
@@ -0,0 +1,188 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import LicenseModelActionHelper from './licenseModel/LicenseModelActionHelper.js';
+import LicenseAgreementActionHelper from './licenseModel/licenseAgreement/LicenseAgreementActionHelper.js';
+import FeatureGroupsActionHelper from './licenseModel/featureGroups/FeatureGroupsActionHelper.js';
+import LicenseKeyGroupsActionHelper from './licenseModel/licenseKeyGroups/LicenseKeyGroupsActionHelper.js';
+import EntitlementPoolsActionHelper from './licenseModel/entitlementPools/EntitlementPoolsActionHelper.js';
+import SoftwareProductActionHelper from './softwareProduct/SoftwareProductActionHelper.js';
+import SoftwareProductProcessesActionHelper from './softwareProduct/processes/SoftwareProductProcessesActionHelper.js';
+import SoftwareProductNetworksActionHelper from './softwareProduct/networks/SoftwareProductNetworksActionHelper.js';
+import SoftwareProductComponentsActionHelper from './softwareProduct/components/SoftwareProductComponentsActionHelper.js';
+import SoftwareProductComponentProcessesActionHelper from './softwareProduct/components/processes/SoftwareProductComponentProcessesActionHelper.js';
+import SoftwareProductComponentsNetworkActionHelper from './softwareProduct/components/network/SoftwareProductComponentsNetworkActionHelper.js';
+import SoftwareProductComponentsMonitoringAction from './softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringActionHelper.js';
+import {actionTypes, enums} from './OnboardingConstants.js';
+import {navigationItems as SoftwareProductNavigationItems, actionTypes as SoftwareProductActionTypes} from 'sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js';
+import store from 'sdc-app/AppStore.js';
+
+function setCurrentScreen(dispatch, screen, props = {}) {
+ dispatch({
+ type: actionTypes.SET_CURRENT_SCREEN,
+ currentScreen: {
+ screen,
+ props
+ }
+ });
+}
+
+function getCurrentLicenseModelVersion(licenseModelId) {
+ return store.getState().licenseModelList.find(({id}) => id === licenseModelId).version;
+}
+
+export default {
+
+ navigateToOnboardingCatalog(dispatch) {
+ LicenseModelActionHelper.fetchLicenseModels(dispatch);
+ SoftwareProductActionHelper.fetchSoftwareProductList(dispatch);
+ setCurrentScreen(dispatch, enums.SCREEN.ONBOARDING_CATALOG);
+ },
+
+ navigateToLicenseAgreements(dispatch, {licenseModelId, version}) {
+ if(!version) {
+ version = getCurrentLicenseModelVersion(licenseModelId);
+ }
+ LicenseAgreementActionHelper.fetchLicenseAgreementList(dispatch, {licenseModelId, version});
+ LicenseModelActionHelper.fetchLicenseModelById(dispatch, {licenseModelId, version}).then(() => {
+ setCurrentScreen(dispatch, enums.SCREEN.LICENSE_AGREEMENTS, {licenseModelId});
+ });
+ },
+
+ navigateToFeatureGroups(dispatch, {licenseModelId, version}) {
+ if(!version) {
+ version = getCurrentLicenseModelVersion(licenseModelId);
+ }
+ FeatureGroupsActionHelper.fetchFeatureGroupsList(dispatch, {licenseModelId, version});
+ setCurrentScreen(dispatch, enums.SCREEN.FEATURE_GROUPS, {licenseModelId});
+ },
+
+ navigateToEntitlementPools(dispatch, {licenseModelId, version}) {
+ if(!version) {
+ version = getCurrentLicenseModelVersion(licenseModelId);
+ }
+ EntitlementPoolsActionHelper.fetchEntitlementPoolsList(dispatch, {licenseModelId, version});
+ setCurrentScreen(dispatch, enums.SCREEN.ENTITLEMENT_POOLS, {licenseModelId});
+ },
+
+ navigateToLicenseKeyGroups(dispatch, {licenseModelId, version}) {
+ if(!version) {
+ version = getCurrentLicenseModelVersion(licenseModelId);
+ }
+ LicenseKeyGroupsActionHelper.fetchLicenseKeyGroupsList(dispatch, {licenseModelId, version});
+ setCurrentScreen(dispatch, enums.SCREEN.LICENSE_KEY_GROUPS, {licenseModelId});
+ },
+
+ navigateToSoftwareProductLandingPage(dispatch, {softwareProductId, licenseModelId, version, licensingVersion}) {
+ SoftwareProductActionHelper.fetchSoftwareProduct(dispatch, {softwareProductId, version}).then(response => {
+ if(!licensingVersion) {
+ licensingVersion = response[0].licensingVersion;
+ }
+ if (!licenseModelId) {
+ licenseModelId = response[0].vendorId;
+ }
+
+ SoftwareProductActionHelper.loadSoftwareProductDetailsData(dispatch, {licenseModelId, licensingVersion});
+ SoftwareProductComponentsActionHelper.fetchSoftwareProductComponents(dispatch, {softwareProductId, version});
+
+ setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE, {softwareProductId, licenseModelId, version});
+ });
+ },
+
+ navigateToSoftwareProductDetails(dispatch, {softwareProductId}) {
+ setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_DETAILS, {softwareProductId});
+ },
+
+ navigateToSoftwareProductAttachments(dispatch, {softwareProductId}) {
+ setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_ATTACHMENTS, {softwareProductId});
+ },
+
+ navigateToSoftwareProductProcesses(dispatch, {softwareProductId, version}) {
+ if (softwareProductId) {
+ SoftwareProductProcessesActionHelper.fetchProcessesList(dispatch, {softwareProductId, version});
+ }
+ setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_PROCESSES, {softwareProductId});
+ },
+
+ navigateToSoftwareProductNetworks(dispatch, {softwareProductId, version}) {
+ if (softwareProductId) {
+ SoftwareProductNetworksActionHelper.fetchNetworksList(dispatch, {softwareProductId, version});
+ }
+ setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_NETWORKS, {softwareProductId});
+ },
+
+ navigateToSoftwareProductComponents(dispatch, {softwareProductId}) {
+ setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_COMPONENTS, {softwareProductId});
+ },
+
+ navigateToSoftwareProductComponentProcesses(dispatch, {softwareProductId, componentId, version}) {
+ if (componentId && softwareProductId) {
+ SoftwareProductComponentProcessesActionHelper.fetchProcessesList(dispatch, {componentId, softwareProductId, version});
+ }
+ setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_PROCESSES, {softwareProductId, componentId});
+ },
+
+ navigateToSoftwareProductComponentMonitoring(dispatch, {softwareProductId, componentId}){
+ if (componentId && softwareProductId) {
+ SoftwareProductComponentsMonitoringAction.fetchExistingFiles(dispatch, {componentId, softwareProductId});
+ }
+ setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_MONITORING, {softwareProductId, componentId});
+ },
+
+ navigateToComponentStorage(dispatch, {softwareProductId, componentId}) {
+ setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_STORAGE, {softwareProductId, componentId});
+ },
+
+ navigateToComponentCompute(dispatch, {softwareProductId, componentId}) {
+ setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_COMPUTE, {softwareProductId, componentId});
+ },
+
+ navigateToComponentNetwork(dispatch, {softwareProductId, componentId, version}) {
+ SoftwareProductComponentsNetworkActionHelper.fetchNICsList(dispatch, {softwareProductId, componentId, version});
+ setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_NETWORK, {softwareProductId, componentId});
+ },
+
+ navigateToSoftwareProductComponentGeneral(dispatch, {softwareProductId, componentId, version}) {
+ if (componentId && softwareProductId) {
+ SoftwareProductComponentsActionHelper.fetchSoftwareProductComponent(dispatch, {softwareProductId, vspComponentId: componentId, version});
+ SoftwareProductComponentsActionHelper.fetchSoftwareProductComponentQuestionnaire(dispatch, {
+ softwareProductId,
+ vspComponentId: componentId,
+ version
+ });
+ }
+ setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_GENERAL, {softwareProductId, componentId});
+ },
+
+ navigateToSoftwareProductComponentGeneralAndUpdateLeftPanel(dispatch, {softwareProductId, componentId, version}) {
+ this.navigateToSoftwareProductComponentGeneral(dispatch, {softwareProductId, componentId, version});
+ dispatch({
+ type: SoftwareProductActionTypes.TOGGLE_NAVIGATION_ITEM,
+ mapOfExpandedIds: {
+ [SoftwareProductNavigationItems.COMPONENTS]: true,
+ [SoftwareProductNavigationItems.COMPONENTS + '|' + componentId]: true
+ }
+ });
+ },
+
+ navigateToComponentLoadBalancing(dispatch, {softwareProductId, componentId}) {
+ setCurrentScreen(dispatch, enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING, {softwareProductId, componentId});
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/OnboardingCatalog.js b/openecomp-ui/src/sdc-app/onboarding/OnboardingCatalog.js
new file mode 100644
index 0000000..4772c8d
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/OnboardingCatalog.js
@@ -0,0 +1,59 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import {default as OnboardingCatalogView, catalogItemTypes} from './OnboardingCatalogView.jsx';
+import OnboardingActionHelper from './OnboardingActionHelper.js';
+import LicenseModelCreationActionHelper from './licenseModel/creation/LicenseModelCreationActionHelper.js';
+import SoftwareProductCreationActionHelper from './softwareProduct/creation/SoftwareProductCreationActionHelper.js';
+
+
+const mapStateToProps = ({licenseModelList, softwareProductList, softwareProduct: {softwareProductCreation}, licenseModel: {licenseModelCreation}}) => {
+
+ let modalToShow;
+
+ if(licenseModelCreation.data) {
+ modalToShow = catalogItemTypes.LICENSE_MODEL;
+ } else if(softwareProductCreation.showModal && softwareProductCreation.data) {
+ modalToShow = catalogItemTypes.SOFTWARE_PRODUCT;
+ }
+
+ return {
+ licenseModelList,
+ softwareProductList,
+ modalToShow
+ };
+};
+
+const mapActionsToProps = (dispatch) => {
+ return {
+ onSelectLicenseModel({id: licenseModelId}) {
+ OnboardingActionHelper.navigateToLicenseAgreements(dispatch, {licenseModelId});
+ },
+ onSelectSoftwareProduct(softwareProduct) {
+ let {id: softwareProductId, vendorId: licenseModelId, licensingVersion} = softwareProduct;
+ OnboardingActionHelper.navigateToSoftwareProductLandingPage(dispatch, {softwareProductId, licenseModelId, licensingVersion});
+ },
+ onAddSoftwareProductClick: () => SoftwareProductCreationActionHelper.open(dispatch),
+ onAddLicenseModelClick: () => LicenseModelCreationActionHelper.open(dispatch)
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(OnboardingCatalogView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/OnboardingCatalogView.jsx b/openecomp-ui/src/sdc-app/onboarding/OnboardingCatalogView.jsx
new file mode 100644
index 0000000..f2a9db1
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/OnboardingCatalogView.jsx
@@ -0,0 +1,147 @@
+import React from 'react';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import Modal from 'nfvo-components/modal/Modal.jsx';
+import objectValues from 'lodash/values.js';
+import LicenseModelCreation from './licenseModel/creation/LicenseModelCreation.js';
+import SoftwareProductCreation from './softwareProduct/creation/SoftwareProductCreation.js';
+import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js';
+import classnames from 'classnames';
+import ExpandableInput from 'nfvo-components/input/ExpandableInput.jsx';
+
+export const catalogItemTypes = Object.freeze({
+ LICENSE_MODEL: 'license-model',
+ SOFTWARE_PRODUCT: 'software-product'
+});
+
+const catalogItemTypeClasses = {
+ LICENSE_MODEL: 'license-model-type',
+ SOFTWARE_PRODUCT: 'software-product-type'
+};
+
+class OnboardingCatalogView extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {searchValue: ''};
+ this.handleSearch = this.handleSearch.bind(this);
+ }
+
+ handleSearch(event){
+ this.setState({searchValue: event.target.value});
+ }
+
+ static propTypes = {
+ licenseModelList: React.PropTypes.array,
+ softwareProductList: React.PropTypes.array,
+ modalToShow: React.PropTypes.oneOf(objectValues(catalogItemTypes)),
+ onSelectLicenseModel: React.PropTypes.func.isRequired,
+ onSelectSoftwareProduct: React.PropTypes.func.isRequired,
+ onAddLicenseModelClick: React.PropTypes.func.isRequired,
+ onAddSoftwareProductClick: React.PropTypes.func.isRequired
+ };
+
+ getModalDetails() {
+ const {modalToShow} = this.props;
+ switch (modalToShow) {
+ case catalogItemTypes.LICENSE_MODEL:
+ return {
+ title: i18n('New License Model'),
+ element: <LicenseModelCreation/>
+ };
+ case catalogItemTypes.SOFTWARE_PRODUCT:
+ return {
+ title: i18n('New Software Product'),
+ element: <SoftwareProductCreation/>
+ };
+ }
+ }
+
+ render() {
+ const modalDetails = this.getModalDetails();
+ const {licenseModelList, softwareProductList, onSelectLicenseModel, onSelectSoftwareProduct, onAddLicenseModelClick, onAddSoftwareProductClick, modalToShow} = this.props;
+
+ return (
+ <div className='catalog-view'>
+ <div className='catalog-header'>
+ <div className='catalog-header-title'>{i18n('Onboarding Catalog')}</div>
+ <ExpandableInput
+ onChange={this.handleSearch}
+ iconType='search'/>
+ </div>
+ <div className='catalog-list'>
+
+ <div className='create-catalog-item tile'>
+ <div className='plus-section'>
+ <div className='plus-icon-button'/>
+ <span>{i18n('ADD')}</span>
+ </div>
+ <div className='primary-btn new-license-model'>
+ <span
+ className='primary-btn-text'
+ onClick={() => onAddLicenseModelClick()}>{i18n('New License Model')}</span></div>
+ <div className='primary-btn'>
+ <span
+ className='primary-btn-text'
+ onClick={() => onAddSoftwareProductClick()}>{i18n('New Vendor Software Product')}</span>
+ </div>
+ </div>
+ {licenseModelList.filter(vlm => vlm.vendorName.toLowerCase().indexOf(this.state.searchValue.toLowerCase()) > -1).map(licenseModel => this.renderTile(
+ {
+ ...licenseModel,
+ name: licenseModel.vendorName
+ },
+ catalogItemTypeClasses.LICENSE_MODEL,
+ () => onSelectLicenseModel(licenseModel))
+ )}
+ {softwareProductList.filter(vsp => vsp.name.toLowerCase().indexOf(this.state.searchValue.toLowerCase()) > -1).map(softwareProduct => this.renderTile(softwareProduct,
+ catalogItemTypeClasses.SOFTWARE_PRODUCT,
+ () => onSelectSoftwareProduct(softwareProduct))
+ )}
+ </div>
+ <Modal
+ show={Boolean(modalDetails)}
+ className={`${this.getCatalogItemTypeClassByItemType(modalToShow)}-modal`}>
+ <Modal.Header>
+ <Modal.Title>{modalDetails && modalDetails.title}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ {
+ modalDetails && modalDetails.element
+ }
+ </Modal.Body>
+ </Modal>
+ </div>
+ );
+
+ }
+
+ getCatalogItemTypeClassByItemType(catalogItemType) {
+ switch (catalogItemType) {
+ case catalogItemTypes.LICENSE_MODEL:
+ return catalogItemTypeClasses.LICENSE_MODEL;
+ case catalogItemTypes.SOFTWARE_PRODUCT:
+ return catalogItemTypeClasses.SOFTWARE_PRODUCT;
+ }
+ }
+
+ renderTile(catalogItemData, catalogItemTypeClass, onSelect) {
+ let {status: itemStatus} = VersionControllerUtils.getCheckOutStatusKindByUserID(catalogItemData.status, catalogItemData.lockingUser);
+ return (
+ <div className='catalog-tile tile' key={catalogItemData.id} onClick={() => onSelect()}>
+ <div className={`catalog-tile-type ${catalogItemTypeClass}`}/>
+ <div className='catalog-tile-icon'>
+ <div className={`icon ${catalogItemTypeClass}-icon`}></div>
+ </div>
+ <div className='catalog-tile-content'>
+ <div className='catalog-tile-item-details'>
+ <div className='catalog-tile-item-name'>{catalogItemData.name}</div>
+ <div className='catalog-tile-item-version'>V {catalogItemData.version}</div>
+ </div>
+ <div className={classnames('catalog-tile-check-in-status', {'sprite-new checkout-editable-status-icon': itemStatus === 'Locked'})}>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+export default OnboardingCatalogView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/OnboardingConstants.js b/openecomp-ui/src/sdc-app/onboarding/OnboardingConstants.js
new file mode 100644
index 0000000..d9c177f
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/OnboardingConstants.js
@@ -0,0 +1,72 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export const actionTypes = keyMirror({
+ SET_CURRENT_SCREEN: null,
+ SET_CURRENT_LICENSE_MODEL: null
+});
+
+export const enums = keyMirror({
+
+ BREADCRUMS: {
+ LICENSE_MODEL: 'LICENSE_MODEL',
+ LICENSE_AGREEMENTS: 'LICENSE_AGREEMENTS',
+ FEATURE_GROUPS: 'FEATURE_GROUPS',
+ ENTITLEMENT_POOLS: 'ENTITLEMENT_POOLS',
+ LICENSE_KEY_GROUPS: 'LICENSE_KEY_GROUPS',
+
+ SOFTWARE_PRODUCT: 'SOFTWARE_PRODUCT',
+ SOFTWARE_PRODUCT_DETAILS: 'SOFTWARE_PRODUCT_DETAILS',
+ SOFTWARE_PRODUCT_ATTACHMENTS: 'SOFTWARE_PRODUCT_ATTACHMENTS',
+ SOFTWARE_PRODUCT_PROCESSES: 'SOFTWARE_PRODUCT_PROCESSES',
+ SOFTWARE_PRODUCT_NETWORKS: 'SOFTWARE_PRODUCT_NETWORKS',
+ SOFTWARE_PRODUCT_COMPONENTS: 'SOFTWARE_PRODUCT_COMPONENTS',
+ SOFTWARE_PRODUCT_COMPONENT_PROCESSES: 'SOFTWARE_PRODUCT_COMPONENT_PROCESSES',
+ SOFTWARE_PRODUCT_COMPONENT_STORAGE: 'SOFTWARE_PRODUCT_COMPONENT_STORAGE',
+ SOFTWARE_PRODUCT_COMPONENT_GENERAL: 'SOFTWARE_PRODUCT_COMPONENT_GENERAL',
+ SOFTWARE_PRODUCT_COMPONENT_COMPUTE: 'SOFTWARE_PRODUCT_COMPONENT_COMPUTE',
+ SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING: 'SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING',
+ SOFTWARE_PRODUCT_COMPONENT_MONITORING: 'SOFTWARE_PRODUCT_COMPONENT_MONITORING'
+ },
+
+ SCREEN: {
+ ONBOARDING_CATALOG: null,
+ LICENSE_AGREEMENTS: null,
+ FEATURE_GROUPS: null,
+ ENTITLEMENT_POOLS: null,
+ LICENSE_KEY_GROUPS: null,
+
+ SOFTWARE_PRODUCT_LANDING_PAGE: null,
+ SOFTWARE_PRODUCT_DETAILS: null,
+ SOFTWARE_PRODUCT_ATTACHMENTS: null,
+ SOFTWARE_PRODUCT_PROCESSES: null,
+ SOFTWARE_PRODUCT_NETWORKS: null,
+ SOFTWARE_PRODUCT_COMPONENTS: null,
+ SOFTWARE_PRODUCT_COMPONENT_PROCESSES: null,
+ SOFTWARE_PRODUCT_COMPONENT_COMPUTE: null,
+ SOFTWARE_PRODUCT_COMPONENT_STORAGE: null,
+ SOFTWARE_PRODUCT_COMPONENT_NETWORK: null,
+ SOFTWARE_PRODUCT_COMPONENT_GENERAL: null,
+ SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING: null,
+ SOFTWARE_PRODUCT_COMPONENT_MONITORING: null
+ }
+});
diff --git a/openecomp-ui/src/sdc-app/onboarding/OnboardingPunchOut.jsx b/openecomp-ui/src/sdc-app/onboarding/OnboardingPunchOut.jsx
new file mode 100644
index 0000000..c4627b1
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/OnboardingPunchOut.jsx
@@ -0,0 +1,501 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import {connect} from 'react-redux';
+import isEqual from 'lodash/isEqual.js';
+import objectValues from 'lodash/values.js';
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import Application from 'sdc-app/Application.jsx';
+import store from 'sdc-app/AppStore.js';
+import Configuration from 'sdc-app/config/Configuration.js';
+
+import OnboardingCatalog from './OnboardingCatalog.js';
+import LicenseModel from './licenseModel/LicenseModel.js';
+import LicenseAgreementListEditor from './licenseModel/licenseAgreement/LicenseAgreementListEditor.js';
+import FeatureGroupListEditor from './licenseModel/featureGroups/FeatureGroupListEditor.js';
+import LicenseKeyGroupsListEditor from './licenseModel/licenseKeyGroups/LicenseKeyGroupsListEditor.js';
+import EntitlementPoolsListEditor from './licenseModel/entitlementPools/EntitlementPoolsListEditor.js';
+import SoftwareProduct from './softwareProduct/SoftwareProduct.js';
+import SoftwareProductLandingPage from './softwareProduct/landingPage/SoftwareProductLandingPage.js';
+import SoftwareProductDetails from './softwareProduct/details/SoftwareProductDetails.js';
+import SoftwareProductAttachments from './softwareProduct/attachments/SoftwareProductAttachments.js';
+import SoftwareProductProcesses from './softwareProduct/processes/SoftwareProductProcesses.js';
+import SoftwareProductNetworks from './softwareProduct/networks/SoftwareProductNetworks.js';
+import SoftwareProductComponentsList from './softwareProduct/components/SoftwareProductComponentsList.js';
+import SoftwareProductComponentProcessesList from './softwareProduct/components/processes/SoftwareProductComponentProcessesList.js';
+import SoftwareProductComponentStorage from './softwareProduct/components/storage/SoftwareProductComponentStorage.js';
+import SoftwareProductComponentsNetworkList from './softwareProduct/components/network/SoftwareProductComponentsNetworkList.js';
+import SoftwareProductComponentsGeneral from './softwareProduct/components/general/SoftwareProductComponentsGeneral.js';
+import SoftwareProductComponentsCompute from './softwareProduct/components/compute/SoftwareProductComponentCompute.js';
+import SoftwareProductComponentLoadBalancing from './softwareProduct/components/loadBalancing/SoftwareProductComponentLoadBalancing.js';
+import SoftwareProductComponentsMonitoring from './softwareProduct/components/monitoring/SoftwareProductComponentsMonitoring.js';
+import {navigationItems as SoftwareProductNavigationItems, actionTypes as SoftwareProductActionTypes} from 'sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js';
+
+import {enums} from './OnboardingConstants.js';
+import OnboardingActionHelper from './OnboardingActionHelper.js';
+
+
+class OnboardingView extends React.Component {
+ static propTypes = {
+ currentScreen: React.PropTypes.shape({
+ screen: React.PropTypes.oneOf(objectValues(enums.SCREEN)).isRequired,
+ props: React.PropTypes.object.isRequired
+ }).isRequired
+ };
+
+ componentDidMount() {
+ let element = ReactDOM.findDOMNode(this);
+ element.addEventListener('click', event => {
+ if (event.target.tagName === 'A') {
+ event.preventDefault();
+ }
+ });
+ ['wheel', 'mousewheel', 'DOMMouseScroll'].forEach(eventType =>
+ element.addEventListener(eventType, event => event.stopPropagation())
+ );
+ }
+
+ render() {
+ let {currentScreen} = this.props;
+ let {screen, props} = currentScreen;
+
+ return (
+ <div className='dox-ui dox-ui-punch-out dox-ui-punch-out-full-page'>
+ {(() => {
+ switch (screen) {
+ case enums.SCREEN.ONBOARDING_CATALOG:
+ return <OnboardingCatalog {...props}/>;
+
+ case enums.SCREEN.LICENSE_AGREEMENTS:
+ case enums.SCREEN.FEATURE_GROUPS:
+ case enums.SCREEN.ENTITLEMENT_POOLS:
+ case enums.SCREEN.LICENSE_KEY_GROUPS:
+ return (
+ <LicenseModel currentScreen={currentScreen}>
+ {
+ (()=>{
+ switch(screen) {
+ case enums.SCREEN.LICENSE_AGREEMENTS:
+ return <LicenseAgreementListEditor {...props}/>;
+ case enums.SCREEN.FEATURE_GROUPS:
+ return <FeatureGroupListEditor {...props}/>;
+ case enums.SCREEN.ENTITLEMENT_POOLS:
+ return <EntitlementPoolsListEditor {...props}/>;
+ case enums.SCREEN.LICENSE_KEY_GROUPS:
+ return <LicenseKeyGroupsListEditor {...props}/>;
+ }
+ })()
+ }
+ </LicenseModel>
+ );
+
+ case enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE:
+ case enums.SCREEN.SOFTWARE_PRODUCT_DETAILS:
+ case enums.SCREEN.SOFTWARE_PRODUCT_ATTACHMENTS:
+ case enums.SCREEN.SOFTWARE_PRODUCT_PROCESSES:
+ case enums.SCREEN.SOFTWARE_PRODUCT_NETWORKS:
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENTS:
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_PROCESSES:
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_STORAGE:
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_NETWORK:
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_GENERAL:
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_COMPUTE:
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING:
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_MONITORING:
+ return (
+ <SoftwareProduct currentScreen={currentScreen}>
+ {
+ (()=>{
+ switch(screen) {
+ case enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE:
+ return <SoftwareProductLandingPage {...props}/>;
+ case enums.SCREEN.SOFTWARE_PRODUCT_DETAILS:
+ return <SoftwareProductDetails {...props}/>;
+ case enums.SCREEN.SOFTWARE_PRODUCT_ATTACHMENTS:
+ return <SoftwareProductAttachments className='no-padding-content-area' {...props} />;
+ case enums.SCREEN.SOFTWARE_PRODUCT_PROCESSES:
+ return <SoftwareProductProcesses {...props}/>;
+ case enums.SCREEN.SOFTWARE_PRODUCT_NETWORKS:
+ return <SoftwareProductNetworks {...props}/>;
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENTS:
+ return <SoftwareProductComponentsList {...props}/>;
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_PROCESSES:
+ return <SoftwareProductComponentProcessesList {...props}/>;
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_STORAGE:
+ return <SoftwareProductComponentStorage {...props}/>;
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_NETWORK:
+ return <SoftwareProductComponentsNetworkList {...props}/>;
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_GENERAL:
+ return <SoftwareProductComponentsGeneral{...props}/>;
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_COMPUTE:
+ return <SoftwareProductComponentsCompute {...props}/>;
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING:
+ return <SoftwareProductComponentLoadBalancing{...props}/>;
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_MONITORING:
+ return <SoftwareProductComponentsMonitoring {...props}/>;
+ }
+ })()
+ }
+ </SoftwareProduct>
+ );
+ }
+ })()}
+ </div>
+ );
+ }
+}
+const mapStateToProps = ({currentScreen}) => ({currentScreen});
+let Onboarding = connect(mapStateToProps, null)(OnboardingView);
+
+export default class OnboardingPunchOut {
+
+ render({options: {data, apiRoot, apiHeaders}, onEvent}, element) {
+ if (!this.unsubscribeFromStore) {
+ this.unsubscribeFromStore = store.subscribe(() => this.handleStoreChange());
+ }
+
+ if (!this.isConfigSet) {
+ Configuration.setATTApiRoot(apiRoot);
+ Configuration.setATTApiHeaders(apiHeaders);
+ this.isConfigSet = true;
+ }
+
+ this.onEvent = (...args) => onEvent(...args);
+ this.handleData(data);
+
+ if (!this.rendered) {
+ ReactDOM.render(
+ <Application>
+ <Onboarding/>
+ </Application>,
+ element
+ );
+ this.rendered = true;
+ }
+ }
+
+ unmount(element) {
+ ReactDOM.unmountComponentAtNode(element);
+ this.rendered = false;
+ this.unsubscribeFromStore();
+ this.unsubscribeFromStore = null;
+ }
+
+ handleData(data) {
+ let {breadcrumbs: {selectedKeys = []} = {}} = data;
+ let dispatch = action => store.dispatch(action);
+ let {softwareProductList, softwareProduct: {softwareProductEditor: {data: {id: currentSoftwareProductId, version: currentSoftwareProductVersion} = {}}},
+ licenseModelList, licenseModel: {licenseModelEditor: {data: {id: currentLicenseModelId, version: currentLicenseModelVersion} = {}}}} = store.getState();
+
+ if (this.programmaticBreadcrumbsUpdate) {
+ this.prevSelectedKeys = selectedKeys;
+ this.programmaticBreadcrumbsUpdate = false;
+ return;
+ }
+
+ if (!isEqual(selectedKeys, this.prevSelectedKeys)) {
+ this.breadcrumbsPrefixSelected = isEqual(selectedKeys, this.prevSelectedKeys && this.prevSelectedKeys.slice(0, selectedKeys.length));
+ this.prevSelectedKeys = selectedKeys;
+
+ if (selectedKeys.length === 0) {
+ OnboardingActionHelper.navigateToOnboardingCatalog(dispatch);
+ } else if (selectedKeys.length === 1 || selectedKeys[1] === enums.BREADCRUMS.LICENSE_MODEL) {
+ let [licenseModelId, , licenseModelScreen] = selectedKeys;
+ if (!licenseModelScreen) {
+ licenseModelScreen = enums.BREADCRUMS.LICENSE_AGREEMENTS;
+ }
+ if(currentLicenseModelId !== licenseModelId) {
+ currentLicenseModelVersion = licenseModelList.find(lm => lm.id === licenseModelId).version;
+ }
+ switch (licenseModelScreen) {
+ case enums.BREADCRUMS.LICENSE_AGREEMENTS:
+ OnboardingActionHelper.navigateToLicenseAgreements(dispatch, {licenseModelId, version: currentLicenseModelVersion});
+ break;
+ case enums.BREADCRUMS.FEATURE_GROUPS:
+ OnboardingActionHelper.navigateToFeatureGroups(dispatch, {licenseModelId, version: currentLicenseModelVersion});
+ break;
+ case enums.BREADCRUMS.ENTITLEMENT_POOLS:
+ OnboardingActionHelper.navigateToEntitlementPools(dispatch, {licenseModelId, version: currentLicenseModelVersion});
+ break;
+ case enums.BREADCRUMS.LICENSE_KEY_GROUPS:
+ OnboardingActionHelper.navigateToLicenseKeyGroups(dispatch, {licenseModelId, version: currentLicenseModelVersion});
+ break;
+ }
+ } else if (selectedKeys.length <= 4 && selectedKeys[1] === enums.BREADCRUMS.SOFTWARE_PRODUCT) {
+ let [licenseModelId, , softwareProductId, softwareProductScreen] = selectedKeys;
+ let softwareProduct = softwareProductId ?
+ softwareProductList.find(({id}) => id === softwareProductId) :
+ softwareProductList.find(({vendorId}) => vendorId === licenseModelId);
+ if (!softwareProductId) {
+ softwareProductId = softwareProduct.id;
+ }
+ if(currentSoftwareProductId !== softwareProductId) {
+ currentSoftwareProductVersion = softwareProduct.version;
+ }
+ switch (softwareProductScreen) {
+ case enums.BREADCRUMS.SOFTWARE_PRODUCT_DETAILS:
+ OnboardingActionHelper.navigateToSoftwareProductDetails(dispatch, {softwareProductId});
+ break;
+ case enums.BREADCRUMS.SOFTWARE_PRODUCT_ATTACHMENTS:
+ OnboardingActionHelper.navigateToSoftwareProductAttachments(dispatch, {softwareProductId});
+ break;
+ case enums.BREADCRUMS.SOFTWARE_PRODUCT_PROCESSES:
+ OnboardingActionHelper.navigateToSoftwareProductProcesses(dispatch, {softwareProductId, version: currentSoftwareProductVersion});
+ break;
+ case enums.BREADCRUMS.SOFTWARE_PRODUCT_NETWORKS:
+ OnboardingActionHelper.navigateToSoftwareProductNetworks(dispatch, {softwareProductId, version: currentSoftwareProductVersion});
+ break;
+ case enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENTS:
+ OnboardingActionHelper.navigateToSoftwareProductComponents(dispatch, {softwareProductId, version: currentSoftwareProductVersion});
+ dispatch({
+ type: SoftwareProductActionTypes.TOGGLE_NAVIGATION_ITEM,
+ mapOfExpandedIds: {
+ [SoftwareProductNavigationItems.COMPONENTS]: true
+ }
+ });
+ break;
+ default:
+ OnboardingActionHelper.navigateToSoftwareProductLandingPage(dispatch, {softwareProductId, version: currentSoftwareProductVersion});
+ break;
+ }
+ }else if (selectedKeys.length === 5 && selectedKeys[1] === enums.BREADCRUMS.SOFTWARE_PRODUCT && selectedKeys[3] === enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENTS) {
+ let [licenseModelId, , softwareProductId, , componentId] = selectedKeys;
+ let softwareProduct = softwareProductId ?
+ softwareProductList.find(({id}) => id === softwareProductId) :
+ softwareProductList.find(({vendorId}) => vendorId === licenseModelId);
+ if (!softwareProductId) {
+ softwareProductId = softwareProduct.id;
+ }
+ if(currentSoftwareProductId !== softwareProductId) {
+ currentSoftwareProductVersion = softwareProduct.version;
+ }
+ OnboardingActionHelper.navigateToSoftwareProductComponentGeneralAndUpdateLeftPanel(dispatch, {softwareProductId, componentId, version: currentSoftwareProductVersion});
+ }else if (selectedKeys.length === 6 && selectedKeys[1] === enums.BREADCRUMS.SOFTWARE_PRODUCT && selectedKeys[3] === enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENTS) {
+ let [licenseModelId, , softwareProductId, , componentId, componentScreen] = selectedKeys;
+ let softwareProduct = softwareProductId ?
+ softwareProductList.find(({id}) => id === softwareProductId) :
+ softwareProductList.find(({vendorId}) => vendorId === licenseModelId);
+ if (!softwareProductId) {
+ softwareProductId = softwareProduct.id;
+ }
+ if(currentSoftwareProductId !== softwareProductId) {
+ currentSoftwareProductVersion = softwareProduct.version;
+ }
+ switch (componentScreen) {
+ case enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_GENERAL:
+ OnboardingActionHelper.navigateToSoftwareProductComponentGeneral(dispatch, {softwareProductId, componentId, version: currentSoftwareProductVersion});
+ break;
+ case enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_COMPUTE:
+ OnboardingActionHelper.navigateToComponentCompute(dispatch, {softwareProductId, componentId});
+ break;
+ case enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING:
+ OnboardingActionHelper.navigateToComponentLoadBalancing(dispatch, {softwareProductId, componentId});
+ break;
+ case enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_NETWORK:
+ OnboardingActionHelper.navigateToComponentNetwork(dispatch, {softwareProductId, componentId});
+ break;
+ case enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_STORAGE:
+ OnboardingActionHelper.navigateToComponentStorage(dispatch, {softwareProductId, componentId});
+ break;
+ case enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_PROCESSES:
+ OnboardingActionHelper.navigateToSoftwareProductComponentProcesses(dispatch, {softwareProductId, componentId});
+ break;
+ case enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_MONITORING:
+ OnboardingActionHelper.navigateToSoftwareProductComponentMonitoring(dispatch, {softwareProductId, componentId});
+ break;
+ }
+ } else {
+ console.error('Unknown breadcrumbs path: ', selectedKeys);
+ }
+ }
+ }
+
+ handleStoreChange() {
+ let {currentScreen, licenseModelList, softwareProductList} = store.getState();
+ let breadcrumbsData = {currentScreen, licenseModelList, softwareProductList};
+
+ if (!isEqual(breadcrumbsData, this.prevBreadcrumbsData) || this.breadcrumbsPrefixSelected) {
+ this.prevBreadcrumbsData = breadcrumbsData;
+ this.breadcrumbsPrefixSelected = false;
+ this.programmaticBreadcrumbsUpdate = true;
+
+ let breadcrumbs = this.buildBreadcrumbs(breadcrumbsData);
+ this.onEvent('breadcrumbsupdated', breadcrumbs);
+ }
+ }
+
+ buildBreadcrumbs({currentScreen: {screen, props}, licenseModelList, softwareProductList}) {
+ let componentsList;
+ if(props.componentId) {
+ componentsList = store.getState().softwareProduct.softwareProductComponents.componentsList;
+ }
+ let screenToBreadcrumb;
+ switch (screen) {
+ case enums.SCREEN.ONBOARDING_CATALOG:
+ return [];
+
+ case enums.SCREEN.LICENSE_AGREEMENTS:
+ case enums.SCREEN.FEATURE_GROUPS:
+ case enums.SCREEN.ENTITLEMENT_POOLS:
+ case enums.SCREEN.LICENSE_KEY_GROUPS:
+ screenToBreadcrumb = {
+ [enums.SCREEN.LICENSE_AGREEMENTS]: enums.BREADCRUMS.LICENSE_AGREEMENTS,
+ [enums.SCREEN.FEATURE_GROUPS]: enums.BREADCRUMS.FEATURE_GROUPS,
+ [enums.SCREEN.ENTITLEMENT_POOLS]: enums.BREADCRUMS.ENTITLEMENT_POOLS,
+ [enums.SCREEN.LICENSE_KEY_GROUPS]: enums.BREADCRUMS.LICENSE_KEY_GROUPS
+ };
+ return [
+ {
+ selectedKey: props.licenseModelId,
+ menuItems: licenseModelList.map(({id, vendorName}) => ({
+ key: id,
+ displayText: vendorName
+ }))
+ },
+ {
+ selectedKey: enums.BREADCRUMS.LICENSE_MODEL,
+ menuItems: [{
+ key: enums.BREADCRUMS.LICENSE_MODEL,
+ displayText: i18n('License Model')
+ },
+ ...(softwareProductList.findIndex(({vendorId}) => vendorId === props.licenseModelId) === -1 ? [] : [{
+ key: enums.BREADCRUMS.SOFTWARE_PRODUCT,
+ displayText: i18n('Software Products')
+ }])]
+ }, {
+ selectedKey: screenToBreadcrumb[screen],
+ menuItems: [{
+ key: enums.BREADCRUMS.LICENSE_AGREEMENTS,
+ displayText: i18n('License Agreements')
+ }, {
+ key: enums.BREADCRUMS.FEATURE_GROUPS,
+ displayText: i18n('Feature Groups')
+ }, {
+ key: enums.BREADCRUMS.ENTITLEMENT_POOLS,
+ displayText: i18n('Entitlement Pools')
+ }, {
+ key: enums.BREADCRUMS.LICENSE_KEY_GROUPS,
+ displayText: i18n('License Key Groups')
+ }]
+ }
+ ];
+
+ case enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE:
+ case enums.SCREEN.SOFTWARE_PRODUCT_DETAILS:
+ case enums.SCREEN.SOFTWARE_PRODUCT_ATTACHMENTS:
+ case enums.SCREEN.SOFTWARE_PRODUCT_PROCESSES:
+ case enums.SCREEN.SOFTWARE_PRODUCT_NETWORKS:
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENTS:
+
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_PROCESSES:
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_COMPUTE:
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_STORAGE:
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_NETWORK:
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_GENERAL:
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING:
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_MONITORING:
+ screenToBreadcrumb = {
+ [enums.SCREEN.SOFTWARE_PRODUCT_DETAILS]: enums.BREADCRUMS.SOFTWARE_PRODUCT_DETAILS,
+ [enums.SCREEN.SOFTWARE_PRODUCT_ATTACHMENTS]: enums.BREADCRUMS.SOFTWARE_PRODUCT_ATTACHMENTS,
+ [enums.SCREEN.SOFTWARE_PRODUCT_PROCESSES]: enums.BREADCRUMS.SOFTWARE_PRODUCT_PROCESSES,
+ [enums.SCREEN.SOFTWARE_PRODUCT_NETWORKS]: enums.BREADCRUMS.SOFTWARE_PRODUCT_NETWORKS,
+ [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENTS]: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENTS
+ };
+ let componentScreenToBreadcrumb = {
+ [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_PROCESSES]: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_PROCESSES,
+ [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_COMPUTE]: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_COMPUTE,
+ [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_STORAGE]: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_STORAGE,
+ [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_NETWORK]: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_NETWORK,
+ [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_GENERAL]: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_GENERAL,
+ [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING]: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING,
+ [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_MONITORING]: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_MONITORING
+ };
+ let licenseModelId = softwareProductList.find(({id}) => id === props.softwareProductId).vendorId;
+ let returnedBreadcrumb = [
+ {
+ selectedKey: licenseModelId,
+ menuItems: licenseModelList.map(({id, vendorName}) => ({
+ key: id,
+ displayText: vendorName
+ }))
+ },
+ {
+ selectedKey: enums.BREADCRUMS.SOFTWARE_PRODUCT,
+ menuItems: [{
+ key: enums.BREADCRUMS.LICENSE_MODEL,
+ displayText: i18n('License Model')
+ }, {
+ key: enums.BREADCRUMS.SOFTWARE_PRODUCT,
+ displayText: i18n('Software Products')
+ }]
+ },
+ {
+ selectedKey: props.softwareProductId,
+ menuItems: softwareProductList
+ .filter(({vendorId}) => vendorId === licenseModelId)
+ .map(({id, name}) => ({
+ key: id,
+ displayText: name
+ }))
+ },
+ ...(screen === enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE ? [] : [{
+ selectedKey: screenToBreadcrumb[screen] || enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENTS,
+ menuItems: [{
+ key: enums.BREADCRUMS.SOFTWARE_PRODUCT_DETAILS,
+ displayText: i18n('General')
+ }, {
+ key: enums.BREADCRUMS.SOFTWARE_PRODUCT_ATTACHMENTS,
+ displayText: i18n('Attachments')
+ }, {
+ key: enums.BREADCRUMS.SOFTWARE_PRODUCT_PROCESSES,
+ displayText: i18n('Process Details')
+ }, {
+ key: enums.BREADCRUMS.SOFTWARE_PRODUCT_NETWORKS,
+ displayText: i18n('Networks')
+ }, {
+ key: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENTS,
+ displayText: i18n('Components')
+ }]
+ }])
+ ];
+ if(props.componentId) {
+ returnedBreadcrumb = [
+ ...returnedBreadcrumb, {
+ selectedKey: props.componentId,
+ menuItems: componentsList
+ .map(({id, displayName}) => ({
+ key: id,
+ displayText: displayName
+ }))
+ },
+ ...[{
+ selectedKey: componentScreenToBreadcrumb[screen],
+ menuItems: [{
+ key: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_GENERAL,
+ displayText: i18n('General')
+ }, {
+ key: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_COMPUTE,
+ displayText: i18n('Compute')
+ }, {
+ key: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING,
+ displayText: i18n('High Availability & Load Balancing')
+ }, {
+ key: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_NETWORK,
+ displayText: i18n('Networks')
+ }, {
+ key: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_STORAGE,
+ displayText: i18n('Storage')
+ }, {
+ key: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_PROCESSES,
+ displayText: i18n('Process Details')
+ }, {
+ key: enums.BREADCRUMS.SOFTWARE_PRODUCT_COMPONENT_MONITORING,
+ displayText: i18n('Monitoring')
+ }]
+ }]
+ ];
+ }
+ return returnedBreadcrumb;
+ }
+ }
+}
diff --git a/openecomp-ui/src/sdc-app/onboarding/OnboardingReducers.js b/openecomp-ui/src/sdc-app/onboarding/OnboardingReducers.js
new file mode 100644
index 0000000..9af2427
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/OnboardingReducers.js
@@ -0,0 +1,29 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes, enums} from './OnboardingConstants.js';
+
+export const currentScreenReducer = (state = {screen: enums.SCREEN.ONBOARDING_CATALOG, props: {}}, action) => {
+ if (action.type === actionTypes.SET_CURRENT_SCREEN) {
+ return action.currentScreen;
+ }
+ return state;
+};
+
diff --git a/openecomp-ui/src/sdc-app/onboarding/OnboardingReducersMap.js b/openecomp-ui/src/sdc-app/onboarding/OnboardingReducersMap.js
new file mode 100644
index 0000000..92d53a3
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/OnboardingReducersMap.js
@@ -0,0 +1,36 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {currentScreenReducer} from './OnboardingReducers.js';
+import licenseModelListReducer from './licenseModel/LicenseModelListReducer.js';
+import finalizedLicenseModelListReducer from './licenseModel/FinalizedLicenseModelListReducer.js';
+import licenseModelReducer from './licenseModel/LicenseModelReducer.js';
+import softwareProductReducer from './softwareProduct/SoftwareProductReducer.js';
+import softwareProductListReducer from './softwareProduct/SoftwareProductListReducer.js';
+
+
+export default {
+ currentScreen: currentScreenReducer,
+ licenseModelList: licenseModelListReducer,
+ finalizedLicenseModelList: finalizedLicenseModelListReducer,
+ licenseModel: licenseModelReducer,
+ softwareProduct: softwareProductReducer,
+ softwareProductList: softwareProductListReducer
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/FinalizedLicenseModelListReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/FinalizedLicenseModelListReducer.js
new file mode 100644
index 0000000..a851e77
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/FinalizedLicenseModelListReducer.js
@@ -0,0 +1,30 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './LicenseModelConstants.js';
+
+export default (state = [], action) => {
+ switch (action.type) {
+ case actionTypes.FINALIZED_LICENSE_MODELS_LIST_LOADED:
+ return [...action.response.results];
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModel.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModel.js
new file mode 100644
index 0000000..ad91a0d
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModel.js
@@ -0,0 +1,147 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import {statusEnum as versionStatusEnum} from 'nfvo-components/panel/versionController/VersionControllerConstants.js';
+import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js';
+import TabulatedEditor from 'src/nfvo-components/editor/TabulatedEditor.jsx';
+
+import {enums} from 'sdc-app/onboarding/OnboardingConstants.js';
+import OnboardingActionHelper from 'sdc-app/onboarding/OnboardingActionHelper.js';
+
+import {navigationItems} from './LicenseModelConstants.js';
+import LicenseModelActionHelper from './LicenseModelActionHelper.js';
+import LicenseAgreementActionHelper from './licenseAgreement/LicenseAgreementActionHelper.js';
+import FeatureGroupsActionHelper from './featureGroups/FeatureGroupsActionHelper.js';
+import EntitlementPoolsActionHelper from './entitlementPools/EntitlementPoolsActionHelper.js';
+import LicenseKeyGroupsActionHelper from './licenseKeyGroups/LicenseKeyGroupsActionHelper.js';
+
+
+const buildNavigationBarProps = (licenseModel, screen) => {
+ const {id, vendorName, version} = licenseModel;
+ const meta = {version};
+
+ const groups = [{
+ id,
+ name: vendorName,
+ items: [
+ {
+ id: navigationItems.LICENSE_AGREEMENTS,
+ name: i18n('License Agreements'),
+ meta
+ },
+ {
+ id: navigationItems.FEATURE_GROUPS,
+ name: i18n('Feature Groups'),
+ meta
+ },
+ {
+ id: navigationItems.ENTITLEMENT_POOLS,
+ name: i18n('Entitlement Pools'),
+ meta
+ },
+ {
+ id: navigationItems.LICENSE_KEY_GROUPS,
+ name: i18n('License Key Groups'),
+ meta
+ }
+ ]
+ }];
+
+ const activeItemId = ({
+ [enums.SCREEN.LICENSE_AGREEMENTS]: navigationItems.LICENSE_AGREEMENTS,
+ [enums.SCREEN.FEATURE_GROUPS]: navigationItems.FEATURE_GROUPS,
+ [enums.SCREEN.ENTITLEMENT_POOLS]: navigationItems.ENTITLEMENT_POOLS,
+ [enums.SCREEN.LICENSE_KEY_GROUPS]: navigationItems.LICENSE_KEY_GROUPS
+ })[screen];
+
+ return {
+ activeItemId, groups
+ };
+};
+
+
+const buildVersionControllerProps = (licenseModel) => {
+ let {version, viewableVersions, status: currentStatus, lockingUser} = licenseModel;
+ let {status, isCheckedOut} = (currentStatus === versionStatusEnum.CHECK_OUT_STATUS) ?
+ VersionControllerUtils.getCheckOutStatusKindByUserID(currentStatus, lockingUser) :
+ {status: currentStatus, isCheckedOut: false};
+
+ return {
+ version,
+ viewableVersions,
+ status,
+ isCheckedOut
+ };
+};
+
+
+const mapStateToProps = ({licenseModel: {licenseModelEditor}}, {currentScreen: {screen}}) => {
+ return {
+ versionControllerProps: buildVersionControllerProps(licenseModelEditor.data),
+ navigationBarProps: buildNavigationBarProps(licenseModelEditor.data, screen)
+ };
+};
+
+
+const mapActionsToProps = (dispatch, {currentScreen: {screen, props: {licenseModelId}}}) => {
+ return {
+ onVersionControllerAction: action =>
+ LicenseModelActionHelper.performVCAction(dispatch, {licenseModelId, action}).then(() => {
+ switch(screen) {
+ case enums.SCREEN.LICENSE_AGREEMENTS:
+ LicenseAgreementActionHelper.fetchLicenseAgreementList(dispatch, {licenseModelId});
+ break;
+ case enums.SCREEN.FEATURE_GROUPS:
+ FeatureGroupsActionHelper.fetchFeatureGroupsList(dispatch, {licenseModelId});
+ break;
+ case enums.SCREEN.ENTITLEMENT_POOLS:
+ EntitlementPoolsActionHelper.fetchEntitlementPoolsList(dispatch, {licenseModelId});
+ break;
+ case enums.SCREEN.LICENSE_KEY_GROUPS:
+ LicenseKeyGroupsActionHelper.fetchLicenseKeyGroupsList(dispatch, {licenseModelId});
+ break;
+ }
+ }),
+ onVersionSwitching: version => LicenseAgreementActionHelper.switchVersion(dispatch, {licenseModelId, version}),
+ onClose: () => OnboardingActionHelper.navigateToOnboardingCatalog(dispatch),
+
+ onNavigate: ({id, meta: {version}}) => {
+ switch(id) {
+ case navigationItems.LICENSE_AGREEMENTS:
+ OnboardingActionHelper.navigateToLicenseAgreements(dispatch, {licenseModelId, version});
+ break;
+ case navigationItems.FEATURE_GROUPS:
+ OnboardingActionHelper.navigateToFeatureGroups(dispatch, {licenseModelId, version});
+ break;
+ case navigationItems.ENTITLEMENT_POOLS:
+ OnboardingActionHelper.navigateToEntitlementPools(dispatch, {licenseModelId, version});
+ break;
+ case navigationItems.LICENSE_KEY_GROUPS:
+ OnboardingActionHelper.navigateToLicenseKeyGroups(dispatch, {licenseModelId, version});
+ break;
+ }
+ }
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(TabulatedEditor);
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelActionHelper.js
new file mode 100644
index 0000000..a379a2c
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelActionHelper.js
@@ -0,0 +1,101 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
+import Configuration from 'sdc-app/config/Configuration.js';
+import {actionTypes} from './LicenseModelConstants.js';
+import {actionsEnum as vcActionsEnum} from 'nfvo-components/panel/versionController/VersionControllerConstants.js';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import NotificationConstants from 'nfvo-components/notifications/NotificationConstants.js';
+
+function baseUrl() {
+ const restPrefix = Configuration.get('restPrefix');
+ return `${restPrefix}/v1.0/vendor-license-models/`;
+}
+
+function fetchLicenseModels() {
+ return RestAPIUtil.fetch(baseUrl());
+}
+
+function fetchFinalizedLicenseModels() {
+ return RestAPIUtil.fetch(`${baseUrl()}?versionFilter=Final`);
+}
+
+function fetchLicenseModelById(licenseModelId, version) {
+ let versionQuery = version ? `?version=${version}` : '';
+ return RestAPIUtil.fetch(`${baseUrl()}${licenseModelId}${versionQuery}`);
+}
+
+function putLicenseModelAction(id, action) {
+ return RestAPIUtil.save(`${baseUrl()}${id}/actions`, {action: action});
+}
+
+const LicenseModelActionHelper = {
+
+ fetchLicenseModels(dispatch) {
+ return fetchLicenseModels().then(response => {
+ dispatch({
+ type: actionTypes.LICENSE_MODELS_LIST_LOADED,
+ response
+ });
+ });
+ },
+
+ fetchFinalizedLicenseModels(dispatch) {
+ return fetchFinalizedLicenseModels().then(response => dispatch({
+ type: actionTypes.FINALIZED_LICENSE_MODELS_LIST_LOADED,
+ response
+ }));
+
+ },
+
+ fetchLicenseModelById(dispatch, {licenseModelId, version}) {
+ return fetchLicenseModelById(licenseModelId, version).then(response => {
+ if(version) {
+ response.version = version;
+ }
+ dispatch({
+ type: actionTypes.LICENSE_MODEL_LOADED,
+ response
+ });
+ });
+ },
+
+ addLicenseModel(dispatch, {licenseModel}){
+ dispatch({
+ type: actionTypes.ADD_LICENSE_MODEL,
+ licenseModel
+ });
+ },
+
+ performVCAction(dispatch, {licenseModelId, action}) {
+ return putLicenseModelAction(licenseModelId, action).then(() => {
+ if(action === vcActionsEnum.SUBMIT){
+ dispatch({
+ type: NotificationConstants.NOTIFY_SUCCESS,
+ data: {title: i18n('Submit Succeeded'), msg: i18n('This license model successfully submitted'), timeout: 2000}
+ });
+ }
+ return LicenseModelActionHelper.fetchLicenseModelById(dispatch, {licenseModelId});
+ });
+ }
+};
+
+export default LicenseModelActionHelper;
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelConstants.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelConstants.js
new file mode 100644
index 0000000..13fa9f5
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelConstants.js
@@ -0,0 +1,36 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export const actionTypes = keyMirror({
+ LICENSE_MODEL_LOADED: null,
+ LICENSE_MODELS_LIST_LOADED: null,
+ FINALIZED_LICENSE_MODELS_LIST_LOADED: null,
+ ADD_LICENSE_MODEL: null,
+ EDIT_LICENSE_MODEL: null
+});
+
+export const navigationItems = keyMirror({
+ LICENSE_AGREEMENTS: 'License Agreements',
+ FEATURE_GROUPS: 'Feature Groups',
+ ENTITLEMENT_POOLS: 'Entitlement Pools',
+ LICENSE_KEY_GROUPS: 'License Key Groups'
+});
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelEditorReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelEditorReducer.js
new file mode 100644
index 0000000..e92e32a
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelEditorReducer.js
@@ -0,0 +1,33 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './LicenseModelConstants.js';
+
+export default (state = {}, action) => {
+ switch (action.type) {
+ case actionTypes.LICENSE_MODEL_LOADED:
+ return {
+ ...state,
+ data: action.response
+ };
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelListReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelListReducer.js
new file mode 100644
index 0000000..8874c4c
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelListReducer.js
@@ -0,0 +1,32 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './LicenseModelConstants.js';
+
+export default (state = [], action) => {
+ switch (action.type) {
+ case actionTypes.LICENSE_MODELS_LIST_LOADED:
+ return [...action.response.results];
+ case actionTypes.ADD_LICENSE_MODEL:
+ return [...state, action.licenseModel];
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelReducer.js
new file mode 100644
index 0000000..5982b9f
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/LicenseModelReducer.js
@@ -0,0 +1,66 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {combineReducers} from 'redux';
+
+import licenseModelCreationReducer from './creation/LicenseModelCreationReducer.js';
+import licenseModelEditorReducer from './LicenseModelEditorReducer.js';
+
+import licenseAgreementListReducer from './licenseAgreement/LicenseAgreementListReducer.js';
+import licenseAgreementEditorReducer from './licenseAgreement/LicenseAgreementEditorReducer.js';
+import {actionTypes as licenseAgreementActionTypes} from './licenseAgreement/LicenseAgreementConstants.js';
+
+import featureGroupsEditorReducer from './featureGroups/FeatureGroupsEditorReducer.js';
+import featureGroupsListReducer from './featureGroups/FeatureGroupsListReducer.js';
+import {actionTypes as featureGroupsActionConstants} from './featureGroups/FeatureGroupsConstants';
+
+import entitlementPoolsListReducer from './entitlementPools/EntitlementPoolsListReducer.js';
+import entitlementPoolsEditorReducer from './entitlementPools/EntitlementPoolsEditorReducer.js';
+import {actionTypes as entitlementPoolsConstants} from './entitlementPools/EntitlementPoolsConstants';
+
+import licenseKeyGroupsEditorReducer from './licenseKeyGroups/LicenseKeyGroupsEditorReducer.js';
+import licenseKeyGroupsListReducer from './licenseKeyGroups/LicenseKeyGroupsListReducer.js';
+import {actionTypes as licenseKeyGroupsConstants} from './licenseKeyGroups/LicenseKeyGroupsConstants.js';
+
+export default combineReducers({
+ licenseModelCreation: licenseModelCreationReducer,
+ licenseModelEditor: licenseModelEditorReducer,
+
+ licenseAgreement: combineReducers({
+ licenseAgreementEditor: licenseAgreementEditorReducer,
+ licenseAgreementList: licenseAgreementListReducer,
+ licenseAgreementToDelete: (state = false, action) => action.type === licenseAgreementActionTypes.LICENSE_AGREEMENT_DELETE_CONFIRM ? action.licenseAgreementToDelete : state
+ }),
+ featureGroup: combineReducers({
+ featureGroupEditor: featureGroupsEditorReducer,
+ featureGroupsList: featureGroupsListReducer,
+ featureGroupToDelete: (state = false, action) => action.type === featureGroupsActionConstants.FEATURE_GROUPS_DELETE_CONFIRM ? action.featureGroupToDelete : state
+ }),
+ entitlementPool: combineReducers({
+ entitlementPoolEditor: entitlementPoolsEditorReducer,
+ entitlementPoolsList: entitlementPoolsListReducer,
+ entitlementPoolToDelete: (state = false, action) => action.type === entitlementPoolsConstants.ENTITLEMENT_POOLS_DELETE_CONFIRM ? action.entitlementPoolToDelete : state
+ }),
+ licenseKeyGroup: combineReducers({
+ licenseKeyGroupsEditor: licenseKeyGroupsEditorReducer,
+ licenseKeyGroupsList: licenseKeyGroupsListReducer,
+ licenseKeyGroupToDelete: (state = false, action) => action.type === licenseKeyGroupsConstants.LICENSE_KEY_GROUPS_DELETE_CONFIRM ? action.licenseKeyGroupToDelete : state
+ }),
+});
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreation.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreation.js
new file mode 100644
index 0000000..63d0f27
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreation.js
@@ -0,0 +1,41 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import OnboardingActionHelper from 'sdc-app/onboarding/OnboardingActionHelper.js';
+import LicenseModelCreationActionHelper from './LicenseModelCreationActionHelper.js';
+import LicenseModelCreationView from './LicenseModelCreationView.jsx';
+
+const mapStateToProps = ({licenseModel: {licenseModelCreation}}) => licenseModelCreation;
+
+const mapActionsToProps = (dispatch) => {
+ return {
+ onDataChanged: deltaData => LicenseModelCreationActionHelper.dataChanged(dispatch, {deltaData}),
+ onCancel: () => LicenseModelCreationActionHelper.close(dispatch),
+ onSubmit: (licenseModel) => {
+ LicenseModelCreationActionHelper.close(dispatch);
+ LicenseModelCreationActionHelper.createLicenseModel(dispatch, {licenseModel}).then(licenseModelId => {
+ OnboardingActionHelper.navigateToLicenseAgreements(dispatch, {licenseModelId});
+ });
+ }
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(LicenseModelCreationView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationActionHelper.js
new file mode 100644
index 0000000..c2a0409
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationActionHelper.js
@@ -0,0 +1,72 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
+import Configuration from 'sdc-app/config/Configuration.js';
+import LicenseModelActionHelper from 'sdc-app/onboarding/licenseModel/LicenseModelActionHelper.js';
+import {actionTypes} from './LicenseModelCreationConstants.js';
+
+function baseUrl() {
+ const restPrefix = Configuration.get('restPrefix');
+ return `${restPrefix}/v1.0/vendor-license-models/`;
+}
+
+function createLicenseModel(licenseModel) {
+ return RestAPIUtil.create(baseUrl(), {
+ vendorName: licenseModel.vendorName,
+ description: licenseModel.description,
+ iconRef: 'icon'
+ });
+}
+
+
+export default {
+
+ open(dispatch) {
+ dispatch({
+ type: actionTypes.OPEN
+ });
+ },
+
+ close(dispatch){
+ dispatch({
+ type: actionTypes.CLOSE
+ });
+ },
+
+ dataChanged(dispatch, {deltaData}){
+ dispatch({
+ type: actionTypes.DATA_CHANGED,
+ deltaData
+ });
+ },
+
+ createLicenseModel(dispatch, {licenseModel}){
+ return createLicenseModel(licenseModel).then(response => {
+ LicenseModelActionHelper.addLicenseModel(dispatch, {
+ licenseModel: {
+ ...licenseModel,
+ id: response.value
+ }
+ });
+ return response.value;
+ });
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationConstants.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationConstants.js
new file mode 100644
index 0000000..603d177
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationConstants.js
@@ -0,0 +1,27 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export const actionTypes = keyMirror({
+ OPEN: null,
+ CLOSE: null,
+ DATA_CHANGED: null
+});
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationReducer.js
new file mode 100644
index 0000000..a54d1b3
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationReducer.js
@@ -0,0 +1,43 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './LicenseModelCreationConstants.js';
+
+export default (state = {}, action) => {
+ switch (action.type) {
+ case actionTypes.OPEN:
+ return {
+ ...state,
+ data: {}
+ };
+ case actionTypes.CLOSE:
+ return {};
+ case actionTypes.DATA_CHANGED:
+ return {
+ ...state,
+ data: {
+ ...state.data,
+ ...action.deltaData
+ }
+ };
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationView.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationView.jsx
new file mode 100644
index 0000000..4dccc9e
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/creation/LicenseModelCreationView.jsx
@@ -0,0 +1,60 @@
+import React from 'react';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx';
+import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx';
+
+const LicenseModelPropType = React.PropTypes.shape({
+ id: React.PropTypes.string,
+ vendorName: React.PropTypes.string,
+ description: React.PropTypes.string
+});
+
+class LicenseModelCreationView extends React.Component {
+
+ static propTypes = {
+ data: LicenseModelPropType,
+ onDataChanged: React.PropTypes.func.isRequired,
+ onSubmit: React.PropTypes.func.isRequired,
+ onCancel: React.PropTypes.func.isRequired
+ };
+
+ render() {
+ let {data = {}, onDataChanged} = this.props;
+ let {vendorName, description} = data;
+ return (
+ <div>
+ <ValidationForm
+ ref='validationForm'
+ hasButtons={true}
+ onSubmit={ () => this.submit() }
+ onReset={ () => this.props.onCancel() }
+ labledButtons={true}>
+ <ValidationInput
+ value={vendorName}
+ label={i18n('Vendor Name')}
+ ref='vendor-name'
+ onChange={vendorName => onDataChanged({vendorName})}
+ validations={{maxLength: 25, required: true}}
+ type='text'
+ className='field-section'/>
+ <ValidationInput
+ value={description}
+ label={i18n('Description')}
+ ref='description'
+ onChange={description => onDataChanged({description})}
+ validations={{maxLength: 1000, required: true}}
+ type='textarea'
+ className='field-section'/>
+ </ValidationForm>
+ </div>
+ );
+ }
+
+
+ submit() {
+ const {data:licenseModel} = this.props;
+ this.props.onSubmit(licenseModel);
+ }
+}
+
+export default LicenseModelCreationView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsActionHelper.js
new file mode 100644
index 0000000..631597a
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsActionHelper.js
@@ -0,0 +1,149 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
+import Configuration from 'sdc-app/config/Configuration.js';
+import {actionTypes as entitlementPoolsActionTypes } from './EntitlementPoolsConstants.js';
+import LicenseModelActionHelper from 'sdc-app/onboarding/licenseModel/LicenseModelActionHelper.js';
+
+function baseUrl(licenseModelId) {
+ const restPrefix = Configuration.get('restPrefix');
+ return `${restPrefix}/v1.0/vendor-license-models/${licenseModelId}/entitlement-pools`;
+}
+
+function fetchEntitlementPoolsList(licenseModelId, version) {
+ let versionQuery = version ? `?version=${version}` : '';
+ return RestAPIUtil.fetch(`${baseUrl(licenseModelId)}${versionQuery}`);
+}
+
+function postEntitlementPool(licenseModelId, entitlementPool) {
+ return RestAPIUtil.create(baseUrl(licenseModelId), {
+ name: entitlementPool.name,
+ description: entitlementPool.description,
+ thresholdValue: entitlementPool.thresholdValue,
+ thresholdUnits: entitlementPool.thresholdUnits,
+ entitlementMetric: entitlementPool.entitlementMetric,
+ increments: entitlementPool.increments,
+ aggregationFunction: entitlementPool.aggregationFunction,
+ operationalScope: entitlementPool.operationalScope,
+ time: entitlementPool.time,
+ manufacturerReferenceNumber: entitlementPool.manufacturerReferenceNumber
+ });
+}
+
+
+function putEntitlementPool(licenseModelId, previousEntitlementPool, entitlementPool) {
+ return RestAPIUtil.save(`${baseUrl(licenseModelId)}/${entitlementPool.id}`, {
+ name: entitlementPool.name,
+ description: entitlementPool.description,
+ thresholdValue: entitlementPool.thresholdValue,
+ thresholdUnits: entitlementPool.thresholdUnits,
+ entitlementMetric: entitlementPool.entitlementMetric,
+ increments: entitlementPool.increments,
+ aggregationFunction: entitlementPool.aggregationFunction,
+ operationalScope: entitlementPool.operationalScope,
+ time: entitlementPool.time,
+ manufacturerReferenceNumber: entitlementPool.manufacturerReferenceNumber
+ });
+}
+
+function deleteEntitlementPool(licenseModelId, entitlementPoolId) {
+ return RestAPIUtil.destroy(`${baseUrl(licenseModelId)}/${entitlementPoolId}`);
+}
+
+
+export default {
+ fetchEntitlementPoolsList(dispatch, {licenseModelId, version}) {
+ return fetchEntitlementPoolsList(licenseModelId, version).then(response => dispatch({
+ type: entitlementPoolsActionTypes.ENTITLEMENT_POOLS_LIST_LOADED,
+ response
+ }));
+ },
+
+ openEntitlementPoolsEditor(dispatch, {entitlementPool} = {}) {
+ dispatch({
+ type: entitlementPoolsActionTypes.entitlementPoolsEditor.OPEN,
+ entitlementPool
+ });
+ },
+
+ deleteEntitlementPool(dispatch, {licenseModelId, entitlementPoolId}) {
+ return deleteEntitlementPool(licenseModelId, entitlementPoolId).then(() => {
+ dispatch({
+ type: entitlementPoolsActionTypes.DELETE_ENTITLEMENT_POOL,
+ entitlementPoolId
+ });
+ });
+ },
+
+ entitlementPoolsEditorDataChanged(dispatch, {deltaData}) {
+ dispatch({
+ type: entitlementPoolsActionTypes.entitlementPoolsEditor.DATA_CHANGED,
+ deltaData
+ });
+ },
+
+ closeEntitlementPoolsEditor(dispatch) {
+ dispatch({
+ type: entitlementPoolsActionTypes.entitlementPoolsEditor.CLOSE
+ });
+ },
+
+ saveEntitlementPool(dispatch, {licenseModelId, previousEntitlementPool, entitlementPool}) {
+ if (previousEntitlementPool) {
+ return putEntitlementPool(licenseModelId, previousEntitlementPool, entitlementPool).then(() => {
+ dispatch({
+ type: entitlementPoolsActionTypes.EDIT_ENTITLEMENT_POOL,
+ entitlementPool
+ });
+ });
+ }
+ else {
+ return postEntitlementPool(licenseModelId, entitlementPool).then(response => {
+ dispatch({
+ type: entitlementPoolsActionTypes.ADD_ENTITLEMENT_POOL,
+ entitlementPool: {
+ ...entitlementPool,
+ id: response.value
+ }
+ });
+ });
+ }
+ },
+
+ hideDeleteConfirm(dispatch) {
+ dispatch({
+ type: entitlementPoolsActionTypes.ENTITLEMENT_POOLS_DELETE_CONFIRM,
+ entitlementPoolToDelete: false
+ });
+ },
+ openDeleteEntitlementPoolConfirm(dispatch, {entitlementPool}) {
+ dispatch({
+ type: entitlementPoolsActionTypes.ENTITLEMENT_POOLS_DELETE_CONFIRM,
+ entitlementPoolToDelete: entitlementPool
+ });
+ },
+
+ switchVersion(dispatch, {licenseModelId, version}) {
+ LicenseModelActionHelper.fetchLicenseModelById(dispatch, {licenseModelId, version}).then(() => {
+ this.fetchEntitlementPoolsList(dispatch, {licenseModelId, version});
+ });
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsConfirmationModal.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsConfirmationModal.jsx
new file mode 100644
index 0000000..04f038f
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsConfirmationModal.jsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import {connect} from 'react-redux';
+import ConfirmationModalView from 'nfvo-components/confirmations/ConfirmationModalView.jsx';
+import EntitlementPoolsActionHelper from './EntitlementPoolsActionHelper.js';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+function renderMsg(entitlementPoolToDelete) {
+ let poolName = entitlementPoolToDelete ? entitlementPoolToDelete.name : '';
+ let msg = i18n('Are you sure you want to delete "{poolName}"?', {poolName});
+ let subMsg = entitlementPoolToDelete
+ && entitlementPoolToDelete.referencingFeatureGroups
+ && entitlementPoolToDelete.referencingFeatureGroups.length > 0 ?
+ i18n('This entitlement pool is associated with one or more feature groups') :
+ '';
+ return (
+ <div>
+ <p>{msg}</p>
+ <p>{subMsg}</p>
+ </div>
+ );
+};
+
+const mapStateToProps = ({licenseModel: {entitlementPool}}, {licenseModelId}) => {
+ let {entitlementPoolToDelete} = entitlementPool;
+ const show = entitlementPoolToDelete !== false;
+ return {
+ show,
+ title: 'Warning!',
+ type: 'warning',
+ msg: renderMsg(entitlementPoolToDelete),
+ confirmationDetails: {entitlementPoolToDelete, licenseModelId}
+ };
+};
+
+const mapActionsToProps = (dispatch) => {
+ return {
+ onConfirmed: ({entitlementPoolToDelete, licenseModelId}) => {
+ EntitlementPoolsActionHelper.deleteEntitlementPool(dispatch, {
+ licenseModelId,
+ entitlementPoolId: entitlementPoolToDelete.id
+ });
+ EntitlementPoolsActionHelper.hideDeleteConfirm(dispatch);
+ },
+ onDeclined: () => {
+ EntitlementPoolsActionHelper.hideDeleteConfirm(dispatch);
+ }
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(ConfirmationModalView);
+
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsConstants.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsConstants.js
new file mode 100644
index 0000000..8a85507
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsConstants.js
@@ -0,0 +1,112 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+export const actionTypes = keyMirror({
+
+ ENTITLEMENT_POOLS_LIST_LOADED: null,
+ ADD_ENTITLEMENT_POOL: null,
+ EDIT_ENTITLEMENT_POOL: null,
+ DELETE_ENTITLEMENT_POOL: null,
+ ENTITLEMENT_POOLS_DELETE_CONFIRM: null,
+
+ entitlementPoolsEditor: {
+ OPEN: null,
+ CLOSE: null,
+ DATA_CHANGED: null,
+ }
+
+});
+
+export const enums = keyMirror({
+ SELECTED_FEATURE_GROUP_TAB: {
+ GENERAL: 1,
+ ENTITLEMENT_POOLS: 2,
+ LICENCE_KEY_GROUPS: 3
+ },
+ SELECTED_ENTITLEMENT_POOLS_BUTTONTAB: {
+ ASSOCIATED_ENTITLEMENT_POOLS: 1,
+ AVAILABLE_ENTITLEMENT_POOLS: 2
+ }
+});
+
+export const defaultState = {
+ ENTITLEMENT_POOLS_EDITOR_DATA: {
+ entitlementMetric: {choice: '', other: ''},
+ aggregationFunction: {choice: '', other: ''},
+ operationalScope: {choices: [], other: ''},
+ time: {choice: '', other: ''}
+ }
+};
+
+export const thresholdUnitType = {
+ ABSOLUTE: 'Absolute',
+ PERCENTAGE: 'Percentage'
+};
+
+export const optionsInputValues = {
+ OPERATIONAL_SCOPE: [
+ {enum: '', title: i18n('please select…')},
+ {enum: 'Network_Wide', title: 'Network Wide'},
+ {enum: 'Availability_Zone', title: 'Availability Zone'},
+ {enum: 'Data_Center', title: 'Data Center'},
+ {enum: 'Tenant', title: 'Tenant'},
+ {enum: 'VM', title: 'VM'},
+ {enum: 'CPU', title: 'CPU'},
+ {enum: 'Core', title: 'Core'}
+ ],
+ TIME: [
+ {enum: '', title: i18n('please select…')},
+ {enum: 'Hour', title: 'Hour'},
+ {enum: 'Day', title: 'Day'},
+ {enum: 'Month', title: 'Month'}
+ ],
+ AGGREGATE_FUNCTION: [
+ {enum: '', title: i18n('please select…')},
+ {enum: 'Peak', title: 'Peak'},
+ {enum: 'Average', title: 'Average'}
+ ],
+ ENTITLEMENT_METRIC: [
+ {enum: '', title: i18n('please select…')},
+ {enum: 'Software_Instances_Count', title: 'Software Instances'},
+ {enum: 'Core', title: 'Core'},
+ {enum: 'CPU', title: 'CPU'},
+ {enum: 'Trunks', title: 'Trunks'},
+ {enum: 'User', title: 'User'},
+ {enum: 'Subscribers', title: 'Subscribers'},
+ {enum: 'Tenants', title: 'Tenants'},
+ {enum: 'Tokens', title: 'Tokens'},
+ {enum: 'Seats', title: 'Seats'},
+ {enum: 'Units_TB', title: 'Units-TB'},
+ {enum: 'Units_GB', title: 'Units-GB'},
+ {enum: 'Units_MB', title: 'Units-MB'}
+ ],
+ THRESHOLD_UNITS: [
+ {enum: '', title: i18n('please select…')},
+ {enum: thresholdUnitType.ABSOLUTE, title: 'Absolute'},
+ {enum: thresholdUnitType.PERCENTAGE, title: '%'}
+ ]
+};
+
+
+
+
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsEditor.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsEditor.js
new file mode 100644
index 0000000..d5bd07e
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsEditor.js
@@ -0,0 +1,53 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import EntitlementPoolsActionHelper from './EntitlementPoolsActionHelper.js';
+import EntitlementPoolsEditorView from './EntitlementPoolsEditorView.jsx';
+
+const mapStateToProps = ({licenseModel: {entitlementPool}}) => {
+
+
+ let {data} = entitlementPool.entitlementPoolEditor;
+
+ let previousData;
+ const entitlementPoolId = data ? data.id : null;
+ if(entitlementPoolId) {
+ previousData = entitlementPool.entitlementPoolsList.find(entitlementPool => entitlementPool.id === entitlementPoolId);
+ }
+
+ return {
+ data,
+ previousData
+ };
+};
+
+const mapActionsToProps = (dispatch, {licenseModelId}) => {
+ return {
+ onDataChanged: deltaData => EntitlementPoolsActionHelper.entitlementPoolsEditorDataChanged(dispatch, {deltaData}),
+ onCancel: () => EntitlementPoolsActionHelper.closeEntitlementPoolsEditor(dispatch),
+ onSubmit: ({previousEntitlementPool, entitlementPool}) => {
+ EntitlementPoolsActionHelper.closeEntitlementPoolsEditor(dispatch);
+ EntitlementPoolsActionHelper.saveEntitlementPool(dispatch, {licenseModelId, previousEntitlementPool, entitlementPool});
+ }
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(EntitlementPoolsEditorView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsEditorReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsEditorReducer.js
new file mode 100644
index 0000000..86e97ec
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsEditorReducer.js
@@ -0,0 +1,44 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes, defaultState} from './EntitlementPoolsConstants.js';
+
+export default (state = {}, action) => {
+ switch (action.type) {
+ case actionTypes.entitlementPoolsEditor.OPEN:
+ return {
+ ...state,
+ data: action.entitlementPool ? {...action.entitlementPool} : defaultState.ENTITLEMENT_POOLS_EDITOR_DATA
+ };
+ case actionTypes.entitlementPoolsEditor.DATA_CHANGED:
+ return {
+ ...state,
+ data: {
+ ...state.data,
+ ...action.deltaData
+ }
+ };
+ case actionTypes.entitlementPoolsEditor.CLOSE:
+ return {};
+ default:
+ return state;
+ }
+
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsEditorView.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsEditorView.jsx
new file mode 100644
index 0000000..77c5a12
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsEditorView.jsx
@@ -0,0 +1,167 @@
+import React from 'react';
+
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx';
+import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx';
+import {optionsInputValues as EntitlementPoolsOptionsInputValues, thresholdUnitType} from './EntitlementPoolsConstants.js';
+import {other as optionInputOther} from 'nfvo-components/input/inputOptions/InputOptions.jsx';
+
+
+const EntitlementPoolPropType = React.PropTypes.shape({
+ id: React.PropTypes.string,
+ name: React.PropTypes.string,
+ description: React.PropTypes.string,
+ manufacturerReferenceNumber: React.PropTypes.string,
+ operationalScope: React.PropTypes.shape({
+ choices: React.PropTypes.array,
+ other: React.PropTypes.string
+ }),
+ aggregationFunction: React.PropTypes.shape({
+ choice: React.PropTypes.string,
+ other: React.PropTypes.string
+ }),
+ increments: React.PropTypes.string,
+ time: React.PropTypes.shape({
+ choice: React.PropTypes.string,
+ other: React.PropTypes.string
+ }),
+ entitlementMetric: React.PropTypes.shape({
+ choice: React.PropTypes.string,
+ other: React.PropTypes.string
+ })
+});
+
+class EntitlementPoolsEditorView extends React.Component {
+
+ static propTypes = {
+ data: EntitlementPoolPropType,
+ previousData: EntitlementPoolPropType,
+ isReadOnlyMode: React.PropTypes.bool,
+ onDataChanged: React.PropTypes.func.isRequired,
+ onSubmit: React.PropTypes.func.isRequired,
+ onCancel: React.PropTypes.func.isRequired
+ };
+
+ static defaultProps = {
+ data: {}
+ };
+
+ render() {
+ let {data = {}, onDataChanged, isReadOnlyMode} = this.props;
+ let {
+ name, description, manufacturerReferenceNumber, operationalScope, aggregationFunction, thresholdUnits, thresholdValue,
+ increments, time, entitlementMetric} = data;
+ let thresholdValueValidation = thresholdUnits === thresholdUnitType.PERCENTAGE ? {numeric: true, required: true, maxValue: 100} : {numeric: true, required: true};
+ let timeValidation = time && time.choice === optionInputOther.OTHER ? {numeric: true, required: true} : {required: true};
+
+ return (
+ <ValidationForm
+ ref='validationForm'
+ hasButtons={true}
+ onSubmit={ () => this.submit() }
+ onReset={ () => this.props.onCancel() }
+ labledButtons={true}
+ isReadOnlyMode={isReadOnlyMode}
+ className='entitlement-pools-form'>
+ <div className='entitlement-pools-form-row'>
+ <ValidationInput
+ onChange={name => onDataChanged({name})}
+ label={i18n('Name')}
+ value={name}
+ validations={{maxLength: 120, required: true}}
+ type='text'/>
+
+ <ValidationInput
+ isMultiSelect={true}
+ onEnumChange={operationalScope => onDataChanged({operationalScope:{choices: operationalScope, other: ''}})}
+ onOtherChange={operationalScope => onDataChanged({operationalScope:{choices: [optionInputOther.OTHER], other: operationalScope}})}
+ multiSelectedEnum={operationalScope && operationalScope.choices}
+ label={i18n('Operational Scope')}
+ otherValue={operationalScope && operationalScope.other}
+ validations={{required: true}}
+ values={EntitlementPoolsOptionsInputValues.OPERATIONAL_SCOPE}/>
+
+ </div>
+ <div className='entitlement-pools-form-row'>
+ <ValidationInput
+ onChange={description => onDataChanged({description})}
+ label={i18n('Description')}
+ value={description}
+ validations={{maxLength: 1000, required: true}}
+ type='textarea'/>
+ <div className='entitlement-pools-form-row-group'>
+ <div className='entitlement-pools-form-row'>
+ <ValidationInput
+ onEnumChange={thresholdUnits => onDataChanged({thresholdUnits})}
+ selectedEnum={thresholdUnits}
+ label={i18n('Threshold Value')}
+ type='select'
+ values={EntitlementPoolsOptionsInputValues.THRESHOLD_UNITS}
+ validations={{required: true}}/>
+ <ValidationInput
+ className='entitlement-pools-form-row-threshold-value'
+ onChange={thresholdValue => onDataChanged({thresholdValue})}
+ value={thresholdValue}
+ validations={thresholdValueValidation}
+ type='text'/>
+ </div>
+
+ <ValidationInput
+ onEnumChange={entitlementMetric => onDataChanged({entitlementMetric:{choice: entitlementMetric, other: ''}})}
+ onOtherChange={entitlementMetric => onDataChanged({entitlementMetric:{choice: optionInputOther.OTHER, other: entitlementMetric}})}
+ selectedEnum={entitlementMetric && entitlementMetric.choice}
+ otherValue={entitlementMetric && entitlementMetric.other}
+ label={i18n('Entitlement Metric')}
+ validations={{required: true}}
+ values={EntitlementPoolsOptionsInputValues.ENTITLEMENT_METRIC}/>
+ <ValidationInput
+ onEnumChange={aggregationFunction => onDataChanged({aggregationFunction:{choice: aggregationFunction, other: ''}})}
+ onOtherChange={aggregationFunction => onDataChanged({aggregationFunction:{choice: optionInputOther.OTHER, other: aggregationFunction}})}
+ selectedEnum={aggregationFunction && aggregationFunction.choice}
+ otherValue={aggregationFunction && aggregationFunction.other}
+ validations={{required: true}}
+ label={i18n('Aggregate Function')}
+ values={EntitlementPoolsOptionsInputValues.AGGREGATE_FUNCTION}/>
+
+ </div>
+ </div>
+ <div className='entitlement-pools-form-row'>
+
+ <ValidationInput
+ onChange={manufacturerReferenceNumber => onDataChanged({manufacturerReferenceNumber})}
+ label={i18n('Manufacturer Reference Number')}
+ value={manufacturerReferenceNumber}
+ validations={{maxLength: 100, required: true}}
+ type='text'/>
+
+ <ValidationInput
+ onEnumChange={time => onDataChanged({time:{choice: time, other: ''}})}
+ onOtherChange={time => onDataChanged({time:{choice: optionInputOther.OTHER, other: time}})}
+ selectedEnum={time && time.choice}
+ otherValue={time && time.other}
+ validations={timeValidation}
+ label={i18n('Time')}
+ values={EntitlementPoolsOptionsInputValues.TIME}/>
+ </div>
+ <div className='entitlement-pools-form-row'>
+ <ValidationInput
+ onChange={increments => onDataChanged({increments})}
+ label={i18n('Increments')}
+ value={increments}
+ validations={{maxLength: 120}}
+ type='text'/>
+
+ </div>
+ </ValidationForm>
+ );
+ }
+
+ submit() {
+ const {data: entitlementPool, previousData: previousEntitlementPool} = this.props;
+ this.props.onSubmit({entitlementPool, previousEntitlementPool});
+ }
+}
+
+export default EntitlementPoolsEditorView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsListEditor.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsListEditor.js
new file mode 100644
index 0000000..4b21a2f
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsListEditor.js
@@ -0,0 +1,53 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import EntitlementPoolsActionHelper from './EntitlementPoolsActionHelper.js';
+import EntitlementPoolsListEditorView from './EntitlementPoolsListEditorView.jsx';
+import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js';
+
+const mapStateToProps = ({licenseModel: {entitlementPool, licenseModelEditor}}) => {
+ let {entitlementPoolsList} = entitlementPool;
+ let {data} = entitlementPool.entitlementPoolEditor;
+
+ let {vendorName} = licenseModelEditor.data;
+ let isReadOnlyMode = VersionControllerUtils.isReadOnly(licenseModelEditor.data);
+
+ return {
+ vendorName,
+ entitlementPoolsList,
+ isReadOnlyMode,
+ isDisplayModal: Boolean(data),
+ isModalInEditMode: Boolean(data && data.id),
+ };
+};
+
+const mapActionsToProps = (dispatch, {licenseModelId}) => {
+ return {
+ onAddEntitlementPoolClick: () => EntitlementPoolsActionHelper.openEntitlementPoolsEditor(dispatch),
+ onEditEntitlementPoolClick: entitlementPool => EntitlementPoolsActionHelper.openEntitlementPoolsEditor(dispatch, {entitlementPool}),
+ onDeleteEntitlementPool: entitlementPool => EntitlementPoolsActionHelper.openDeleteEntitlementPoolConfirm(dispatch, {
+ licenseModelId,
+ entitlementPool
+ })
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(EntitlementPoolsListEditorView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsListEditorView.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsListEditorView.jsx
new file mode 100644
index 0000000..52df102
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsListEditorView.jsx
@@ -0,0 +1,132 @@
+import React from 'react';
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import Modal from 'nfvo-components/modal/Modal.jsx';
+import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx';
+import ListEditorItemView from 'nfvo-components/listEditor/ListEditorItemView.jsx';
+
+import EntitlementPoolsEditor from './EntitlementPoolsEditor.js';
+import InputOptions, {other as optionInputOther} from 'nfvo-components/input/inputOptions/InputOptions.jsx';
+import {optionsInputValues} from './EntitlementPoolsConstants';
+import EntitlementPoolsConfirmationModal from './EntitlementPoolsConfirmationModal.jsx';
+
+
+class EntitlementPoolsListEditorView extends React.Component {
+ static propTypes = {
+ vendorName: React.PropTypes.string,
+ licenseModelId: React.PropTypes.string.isRequired,
+ entitlementPoolsList: React.PropTypes.array,
+ isReadOnlyMode: React.PropTypes.bool.isRequired,
+ isDisplayModal: React.PropTypes.bool,
+ isModalInEditMode: React.PropTypes.bool,
+ onAddEntitlementPoolClick: React.PropTypes.func,
+ onEditEntitlementPoolClick: React.PropTypes.func,
+ onDeleteEntitlementPool: React.PropTypes.func,
+ };
+
+ static defaultProps = {
+ entitlementPoolsList: []
+ };
+
+ state = {
+ localFilter: ''
+ };
+
+ render() {
+ let {licenseModelId, vendorName, isReadOnlyMode, isDisplayModal, isModalInEditMode} = this.props;
+ let {onAddEntitlementPoolClick} = this.props;
+ const {localFilter} = this.state;
+
+ return (
+ <div className='entitlement-pools-list-editor'>
+ <ListEditorView
+ title={i18n('Entitlement Pools for {vendorName} License Model', {vendorName})}
+ plusButtonTitle={i18n('Add Entitlement Pool')}
+ onAdd={onAddEntitlementPoolClick}
+ filterValue={localFilter}
+ onFilter={filter => this.setState({localFilter: filter})}
+ isReadOnlyMode={isReadOnlyMode}>
+ {this.filterList().map(entitlementPool => this.renderEntitlementPoolListItem(entitlementPool, isReadOnlyMode))}
+ </ListEditorView>
+ <Modal show={isDisplayModal} bsSize='large' animation={true} className='entitlement-pools-modal'>
+ <Modal.Header>
+ <Modal.Title>{`${isModalInEditMode ? i18n('Edit Entitlement Pool') : i18n('Create New Entitlement Pool')}`}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ {
+ isDisplayModal && (
+ <EntitlementPoolsEditor licenseModelId={licenseModelId} isReadOnlyMode={isReadOnlyMode}/>
+ )
+ }
+ </Modal.Body>
+ </Modal>
+
+ <EntitlementPoolsConfirmationModal licenseModelId={licenseModelId}/>
+ </div>
+ );
+ }
+
+ filterList() {
+ let {entitlementPoolsList} = this.props;
+ let {localFilter} = this.state;
+ if(localFilter.trim()) {
+ const filter = new RegExp(escape(localFilter), 'i');
+ return entitlementPoolsList.filter(({name = '', description = ''}) => {
+ return escape(name).match(filter) || escape(description).match(filter);
+ });
+ }
+ else {
+ return entitlementPoolsList;
+ }
+ }
+
+ renderEntitlementPoolListItem(entitlementPool, isReadOnlyMode) {
+ let {id, name, description, thresholdValue, thresholdUnits, entitlementMetric, aggregationFunction,
+ manufacturerReferenceNumber, time} = entitlementPool;
+ let {onEditEntitlementPoolClick, onDeleteEntitlementPool} = this.props;
+ return (
+ <ListEditorItemView
+ key={id}
+ onSelect={() => onEditEntitlementPoolClick(entitlementPool)}
+ onDelete={() => onDeleteEntitlementPool(entitlementPool)}
+ className='list-editor-item-view'
+ isReadOnlyMode={isReadOnlyMode}>
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Name')}</div>
+ <div className='text name'>{name}</div>
+ </div>
+
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Entitlement')}</div>
+ <div className='entitlement-parameters'>{`${this.extractValue(aggregationFunction)} ${this.extractValue(entitlementMetric)} per ${this.extractValue(time)}`}</div>
+ <div className='entitlement-pools-count'>{`${thresholdValue ? thresholdValue : ''} ${this.extractUnits(thresholdUnits)}`}</div>
+ </div>
+
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Manufacturer Reference Number')}</div>
+ <div className='text contract-number'>{manufacturerReferenceNumber}</div>
+ </div>
+
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Description')}</div>
+ <div className='text description'>{description}</div>
+ </div>
+ </ListEditorItemView>
+ );
+ }
+
+
+
+ extractUnits(units) {
+ if (units === undefined) {return '';} //TODO fix it later
+ return units === 'Absolute' ? '' : '%';
+ }
+
+ extractValue(item) {
+ if (item === undefined) {return '';} //TODO fix it later
+
+ return item ? item.choice === optionInputOther.OTHER ? item.other : InputOptions.getTitleByName(optionsInputValues, item.choice) : '';
+ }
+}
+
+export default EntitlementPoolsListEditorView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsListReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsListReducer.js
new file mode 100644
index 0000000..63e351f
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsListReducer.js
@@ -0,0 +1,36 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './EntitlementPoolsConstants';
+export default (state = [], action) => {
+ switch (action.type) {
+ case actionTypes.ENTITLEMENT_POOLS_LIST_LOADED:
+ return [...action.response.results];
+ case actionTypes.ADD_ENTITLEMENT_POOL:
+ return [...state, action.entitlementPool];
+ case actionTypes.EDIT_ENTITLEMENT_POOL:
+ const indexForEdit = state.findIndex(entitlementPool => entitlementPool.id === action.entitlementPool.id);
+ return [...state.slice(0, indexForEdit), action.entitlementPool, ...state.slice(indexForEdit + 1)];
+ case actionTypes.DELETE_ENTITLEMENT_POOL:
+ return state.filter(entitlementPool => entitlementPool.id !== action.entitlementPoolId);
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupEditor.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupEditor.js
new file mode 100644
index 0000000..c2b269b
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupEditor.js
@@ -0,0 +1,66 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+
+import FeatureGroupsActionHelper from './FeatureGroupsActionHelper.js';
+import FeatureGroupEditorView from './FeatureGroupEditorView.jsx';
+
+const mapStateToProps = ({licenseModel: {featureGroup, entitlementPool, licenseKeyGroup}}) => {
+
+ const {featureGroupEditor} = featureGroup;
+
+ let {data, selectedTab, selectedEntitlementPoolsButtonTab, selectedLicenseKeyGroupsButtonTab} = featureGroupEditor;
+
+ let previousData;
+ const featureGroupId = data ? data.id : null;
+ if (featureGroupId) {
+ previousData = featureGroup.featureGroupsList.find(featureGroup => featureGroup.id === featureGroupId);
+ }
+ let {entitlementPoolsList = []} = entitlementPool;
+ let {licenseKeyGroupsList = []} = licenseKeyGroup;
+
+ return {
+ data,
+ previousData,
+ selectedTab,
+ selectedEntitlementPoolsButtonTab,
+ selectedLicenseKeyGroupsButtonTab,
+ entitlementPoolsList,
+ licenseKeyGroupsList
+ };
+};
+
+
+const mapActionsToProps = (dispatch, {licenseModelId}) => {
+ return {
+ onTabSelect: tab => FeatureGroupsActionHelper.selectEntitlementPoolsEditorTab(dispatch, {tab}),
+ onEntitlementPoolsButtonTabSelect: buttonTab => FeatureGroupsActionHelper.selectFeatureGroupsEditorEntitlementPoolsButtonTab(dispatch, {buttonTab}),
+ onLicenseKeyGroupsButtonTabSelect: buttonTab => FeatureGroupsActionHelper.selectFeatureGroupsEditorLicenseKeyGroupsButtonTab(dispatch, {buttonTab}),
+ onDataChanged: deltaData => FeatureGroupsActionHelper.featureGroupsEditorDataChanged(dispatch, {deltaData}),
+ onSubmit: (previousFeatureGroup, featureGroup) => {
+ FeatureGroupsActionHelper.closeFeatureGroupsEditor(dispatch);
+ FeatureGroupsActionHelper.saveFeatureGroup(dispatch, {licenseModelId, previousFeatureGroup, featureGroup});
+ }
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(FeatureGroupEditorView);
+
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupEditorView.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupEditorView.jsx
new file mode 100644
index 0000000..6fecd16
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupEditorView.jsx
@@ -0,0 +1,339 @@
+import React from 'react';
+import ValidationTabs from 'nfvo-components/input/validation/ValidationTabs.jsx';
+import ValidationTab from 'nfvo-components/input/validation/ValidationTab.jsx';
+import ButtonGroup from 'react-bootstrap/lib/ButtonGroup.js';
+import Button from 'react-bootstrap/lib/Button.js';
+
+import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx';
+import DualListboxView from 'nfvo-components/input/dualListbox/DualListboxView.jsx';
+import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx';
+import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx';
+import ListEditorViewItem from 'nfvo-components/listEditor/ListEditorItemView.jsx';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+import {state as FeatureGroupStateConstants} from './FeatureGroupsConstants.js';
+
+const FeatureGroupsPropType = React.PropTypes.shape({
+ id: React.PropTypes.string,
+ name: React.PropTypes.string,
+ description: React.PropTypes.string,
+ partNumber: React.PropTypes.string,
+ entitlementPoolsIds: React.PropTypes.array(React.PropTypes.string),
+ licenseKeyGroupsIds: React.PropTypes.array(React.PropTypes.string)
+});
+
+class FeatureGroupEditorView extends React.Component {
+
+
+ static propTypes = {
+ data: FeatureGroupsPropType,
+ previousData: FeatureGroupsPropType,
+ isReadOnlyMode: React.PropTypes.bool,
+
+ onSubmit: React.PropTypes.func,
+ onCancel: React.PropTypes.func,
+
+ selectedTab: React.PropTypes.number,
+ onTabSelect: React.PropTypes.func,
+
+ selectedEntitlementPoolsButtonTab: React.PropTypes.number,
+ selectedLicenseKeyGroupsButtonTab: React.PropTypes.number,
+ onEntitlementPoolsButtonTabSelect: React.PropTypes.func,
+ onLicenseKeyGroupsButtonTabSelect: React.PropTypes.func,
+
+ entitlementPoolsList: DualListboxView.propTypes.availableList,
+ licenseKeyGroupsList: DualListboxView.propTypes.availableList
+ };
+
+
+ static defaultProps = {
+ data: {},
+ selectedTab: FeatureGroupStateConstants.SELECTED_FEATURE_GROUP_TAB.GENERAL,
+ selectedEntitlementPoolsButtonTab: FeatureGroupStateConstants.SELECTED_ENTITLEMENT_POOLS_BUTTONTAB.ASSOCIATED_ENTITLEMENT_POOLS,
+ selectedLicenseKeyGroupsButtonTab: FeatureGroupStateConstants.SELECTED_LICENSE_KEY_GROUPS_BUTTONTAB.ASSOCIATED_LICENSE_KEY_GROUPS
+ };
+
+ state = {
+ localEntitlementPoolsListFilter: '',
+ localLicenseKeyGroupsListFilter: ''
+ };
+
+
+ render() {
+ let {selectedTab, onTabSelect, isReadOnlyMode} = this.props;
+ return (
+ <ValidationForm
+ ref='validationForm'
+ hasButtons={true}
+ onSubmit={ () => this.submit() }
+ onReset={ () => this.props.onCancel() }
+ labledButtons={true}
+ isReadOnlyMode={isReadOnlyMode}
+ name='feature-group-validation-form'
+ className='feature-group-form'>
+ <ValidationTabs activeKey={onTabSelect ? selectedTab : undefined} onSelect={onTabSelect}>
+ {this.renderGeneralTab()}
+ {this.renderEntitlementPoolsTab()}
+ {this.renderLicenseKeyGroupsTab()}
+ </ValidationTabs>
+
+ </ValidationForm>
+ );
+ }
+
+ submit() {
+ const {data: featureGroup, previousData: previousFeatureGroup} = this.props;
+ this.props.onSubmit(previousFeatureGroup, featureGroup);
+ }
+
+ renderGeneralTab() {
+ let {data = {}, onDataChanged} = this.props;
+ let {name, description, partNumber} = data;
+ return (
+ <ValidationTab eventKey={FeatureGroupStateConstants.SELECTED_FEATURE_GROUP_TAB.GENERAL} title={i18n('General')}>
+ <div>
+ <ValidationInput
+ groupClassName='field-section'
+ onChange={name => onDataChanged({name})}
+ ref='name'
+ label={i18n('Name')}
+ value={name}
+ name='feature-group-name'
+ validations={{maxLength: 120, required: true}}
+ type='text'/>
+ <ValidationInput
+ groupClassName='field-section'
+ className='description-field'
+ onChange={description => onDataChanged({description})}
+ ref='description'
+ label={i18n('Description')}
+ value={description}
+ name='feature-group-description'
+ validations={{maxLength: 1000, required: true}}
+ type='textarea'/>
+ <ValidationInput
+ groupClassName='field-section'
+ onChange={partNumber => onDataChanged({partNumber})}
+ label={i18n('Part Number')}
+ value={partNumber}
+ validations={{required: true}}
+ type='text'/>
+ </div>
+ </ValidationTab>
+ );
+ }
+
+ renderEntitlementPoolsTab() {
+ let {selectedEntitlementPoolsButtonTab, onEntitlementPoolsButtonTabSelect, entitlementPoolsList} = this.props;
+ if (entitlementPoolsList.length > 0) {
+ return (
+ <ValidationTab
+ eventKey={FeatureGroupStateConstants.SELECTED_FEATURE_GROUP_TAB.ENTITLEMENT_POOLS}
+ title={i18n('Entitlement Pools')}>
+ <ButtonGroup>
+ {
+ this.renderButtonsTab(
+ FeatureGroupStateConstants.SELECTED_ENTITLEMENT_POOLS_BUTTONTAB.ASSOCIATED_ENTITLEMENT_POOLS,
+ selectedEntitlementPoolsButtonTab,
+ i18n('Associated Entitlement Pools'),
+ onEntitlementPoolsButtonTabSelect
+ )
+ }
+ {
+ this.renderButtonsTab(
+ FeatureGroupStateConstants.SELECTED_ENTITLEMENT_POOLS_BUTTONTAB.AVAILABLE_ENTITLEMENT_POOLS,
+ selectedEntitlementPoolsButtonTab,
+ i18n('Available Entitlement Pools'),
+ onEntitlementPoolsButtonTabSelect
+ )
+ }
+ </ButtonGroup>
+ {this.renderEntitlementPoolsButtonTabContent(selectedEntitlementPoolsButtonTab)}
+ </ValidationTab>
+ );
+ } else {
+ return (
+ <ValidationTab
+ eventKey={FeatureGroupStateConstants.SELECTED_FEATURE_GROUP_TAB.ENTITLEMENT_POOLS}
+ title={i18n('Entitlement Pools')}>
+ <p>{i18n('There is no available entitlement pools.')}</p>
+ </ValidationTab>
+ );
+ }
+ }
+
+ renderLicenseKeyGroupsTab() {
+ let {selectedLicenseKeyGroupsButtonTab, onLicenseKeyGroupsButtonTabSelect, licenseKeyGroupsList} = this.props;
+ if (licenseKeyGroupsList.length > 0) {
+ return (
+ <ValidationTab
+ eventKey={FeatureGroupStateConstants.SELECTED_FEATURE_GROUP_TAB.LICENCE_KEY_GROUPS}
+ title={i18n('License Key Groups')}>
+ <ButtonGroup>
+ {
+ this.renderButtonsTab(
+ FeatureGroupStateConstants.SELECTED_LICENSE_KEY_GROUPS_BUTTONTAB.ASSOCIATED_LICENSE_KEY_GROUPS,
+ selectedLicenseKeyGroupsButtonTab,
+ i18n('Associated License Key Groups'),
+ onLicenseKeyGroupsButtonTabSelect
+ )
+ }
+ {
+ this.renderButtonsTab(
+ FeatureGroupStateConstants.SELECTED_LICENSE_KEY_GROUPS_BUTTONTAB.AVAILABLE_LICENSE_KEY_GROUPS,
+ selectedLicenseKeyGroupsButtonTab,
+ i18n('Available License Key Groups'),
+ onLicenseKeyGroupsButtonTabSelect
+ )
+ }
+ </ButtonGroup>
+ {this.renderLicenseKeyGroupsTabContent(selectedLicenseKeyGroupsButtonTab)}
+ </ValidationTab>
+ );
+ } else {
+ return (
+ <ValidationTab
+ eventKey={FeatureGroupStateConstants.SELECTED_FEATURE_GROUP_TAB.LICENCE_KEY_GROUPS}
+ title={i18n('License Key Groups')}>
+ <p>{i18n('There is no available license key groups')}</p>
+ </ValidationTab>);
+ }
+ }
+
+ renderButtonsTab(buttonTab, selectedButtonTab, title, onClick) {
+ const isSelected = buttonTab === selectedButtonTab;
+ return (
+ <Button
+ className='button-tab'
+ active={isSelected}
+ onClick={() => !isSelected && onClick(buttonTab)}>
+ { title }
+ </Button>
+ );
+ }
+
+ renderEntitlementPoolsButtonTabContent(selectedFeatureGroupsButtonTab) {
+ const {entitlementPoolsList = [], data: {entitlementPoolsIds = []}} = this.props;
+ let dualBoxTitle = {
+ left: i18n('Available Entitlement Pools'),
+ right: i18n('Selected Entitlement Pools')
+ };
+
+ if (entitlementPoolsList.length) {
+ const {localEntitlementPoolsListFilter} = this.state;
+ let selectedEntitlementPools = entitlementPoolsIds.map(entitlementPoolId => entitlementPoolsList.find(entitlementPool => entitlementPool.id === entitlementPoolId));
+
+ switch (selectedFeatureGroupsButtonTab) {
+ case FeatureGroupStateConstants.SELECTED_ENTITLEMENT_POOLS_BUTTONTAB.ASSOCIATED_ENTITLEMENT_POOLS:
+ if (selectedEntitlementPools.length) {
+ return (
+ <ListEditorView
+ className='thinner-list'
+ filterValue={localEntitlementPoolsListFilter}
+ onFilter={localEntitlementPoolsListFilter => this.setState({localEntitlementPoolsListFilter})}>
+ {this.filterAssociatedItems(selectedEntitlementPools, localEntitlementPoolsListFilter)
+ .map(entitlementPool => this.renderAssociatedListItem(entitlementPool
+ , FeatureGroupStateConstants.SELECTED_FEATURE_GROUP_TAB.ENTITLEMENT_POOLS))}
+ </ListEditorView>
+ );
+ }
+ else {
+ return (
+ <div>
+ <br/>{i18n('There are currently no entitlement pools associated with this feature group. Click "Available Entitlement Pools" to associate.')}
+ </div>
+ );
+ }
+ case FeatureGroupStateConstants.SELECTED_ENTITLEMENT_POOLS_BUTTONTAB.AVAILABLE_ENTITLEMENT_POOLS:
+ return (
+ <DualListboxView
+ filterTitle={dualBoxTitle}
+ selectedValuesList={entitlementPoolsIds}
+ availableList={entitlementPoolsList}
+ onChange={ selectedValuesList => this.props.onDataChanged( { entitlementPoolsIds: selectedValuesList } )}/>
+ );
+ }
+ }
+ }
+
+ renderLicenseKeyGroupsTabContent(selectedFeatureGroupsButtonTab) {
+ const {licenseKeyGroupsList = [], data: {licenseKeyGroupsIds = []}} = this.props;
+ let dualBoxFilterTitle = {
+ left: i18n('Available License Key Groups'),
+ right: i18n('Selected License Key Groups')
+ };
+
+ if (licenseKeyGroupsList.length) {
+ const {localLicenseKeyGroupsListFilter} = this.state;
+ let selectedLicenseKeyGroups = licenseKeyGroupsIds.map(licenseKeyGroupId => licenseKeyGroupsList.find(licenseKeyGroup => licenseKeyGroup.id === licenseKeyGroupId));
+
+ switch (selectedFeatureGroupsButtonTab) {
+ case FeatureGroupStateConstants.SELECTED_LICENSE_KEY_GROUPS_BUTTONTAB.ASSOCIATED_LICENSE_KEY_GROUPS:
+ if (selectedLicenseKeyGroups.length) {
+ return (
+ <ListEditorView
+ className='thinner-list'
+ filterValue={localLicenseKeyGroupsListFilter}
+ onFilter={localLicenseKeyGroupsListFilter => this.setState({localLicenseKeyGroupsListFilter})}>
+ {this.filterAssociatedItems(selectedLicenseKeyGroups, localLicenseKeyGroupsListFilter)
+ .map(licenseKeyGroup => this.renderAssociatedListItem(licenseKeyGroup
+ , FeatureGroupStateConstants.SELECTED_FEATURE_GROUP_TAB.LICENCE_KEY_GROUPS))}
+ </ListEditorView>
+ );
+ } else {
+ return (
+ <div className='no-items-msg'>
+ {i18n('There are currently no license key groups associated with this feature group. Click "Available License Key Groups" to associate.')}
+ </div>
+ );
+ }
+ case FeatureGroupStateConstants.SELECTED_LICENSE_KEY_GROUPS_BUTTONTAB.AVAILABLE_LICENSE_KEY_GROUPS:
+ return (
+ <DualListboxView
+ filterTitle={dualBoxFilterTitle}
+ selectedValuesList={this.props.data.licenseKeyGroupsIds}
+ availableList={this.props.licenseKeyGroupsList}
+ onChange={ selectedValuesList => this.props.onDataChanged( { licenseKeyGroupsIds: selectedValuesList } )}/>
+ );
+ }
+ }
+ }
+
+
+ renderAssociatedListItem(listItem, itemType) {
+ let {isReadOnlyMode} = this.props;
+ return (
+ <ListEditorViewItem
+ key={listItem.id}
+ onDelete={() => this.deleteAssociatedItem(listItem.id, itemType)}
+ isReadOnlyMode={isReadOnlyMode}>
+ <div className='name'>{listItem.name}</div>
+ <div className='description'>{listItem.description}</div>
+ </ListEditorViewItem>
+ );
+ }
+
+ filterAssociatedItems(list, localList) {
+ if (localList) {
+ const filter = new RegExp(escape(localList), 'i');
+ return list.filter(({name = '', description = ''}) => name.match(filter) || description.match(filter));
+ }
+ else {
+ return list;
+ }
+ }
+
+ deleteAssociatedItem(id, type) {
+ const {data: {licenseKeyGroupsIds = [], entitlementPoolsIds = []}} = this.props;
+ if (type === FeatureGroupStateConstants.SELECTED_FEATURE_GROUP_TAB.LICENCE_KEY_GROUPS) {
+ this.props.onDataChanged({licenseKeyGroupsIds: licenseKeyGroupsIds.filter(listId => listId !== id)});
+ } else {
+ this.props.onDataChanged({entitlementPoolsIds: entitlementPoolsIds.filter(listId => listId !== id)});
+ }
+
+ }
+}
+
+
+export default FeatureGroupEditorView;
+
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupListEditor.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupListEditor.js
new file mode 100644
index 0000000..9ea5a31
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupListEditor.js
@@ -0,0 +1,56 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import FeatureGroupsActionHelper from './FeatureGroupsActionHelper.js';
+import FeatureGroupListEditorView from './FeatureGroupListEditorView.jsx';
+import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js';
+
+const mapStateToProps = ({licenseModel: {featureGroup, licenseModelEditor}}) => {
+ const {featureGroupEditor: {data}, featureGroupsList} = featureGroup;
+ let {vendorName} = licenseModelEditor.data;
+ let isReadOnlyMode = VersionControllerUtils.isReadOnly(licenseModelEditor.data);
+
+ return {
+ vendorName,
+ featureGroupsModal: {
+ show: Boolean(data),
+ editMode: Boolean(data && data.id)
+ },
+ featureGroupsList,
+ isReadOnlyMode
+ };
+};
+
+
+const mapActionsToProps = (dispatch, {licenseModelId}) => {
+ return {
+ onDeleteFeatureGroupClick: (featureGroup) => FeatureGroupsActionHelper.openDeleteFeatureGroupConfirm(dispatch, {licenseModelId, featureGroup}),
+ onCancelFeatureGroupsEditor: () => FeatureGroupsActionHelper.closeFeatureGroupsEditor(dispatch),
+
+ onAddFeatureGroupClick: () => FeatureGroupsActionHelper.openFeatureGroupsEditor(dispatch, {licenseModelId}),
+ onEditFeatureGroupClick: featureGroup => FeatureGroupsActionHelper.openFeatureGroupsEditor(dispatch, {
+ featureGroup,
+ licenseModelId
+ })
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(FeatureGroupListEditorView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupListEditorView.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupListEditorView.jsx
new file mode 100644
index 0000000..d998f92
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupListEditorView.jsx
@@ -0,0 +1,136 @@
+import React from 'react';
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import Modal from 'nfvo-components/modal/Modal.jsx';
+import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx';
+import ListEditorItemView from 'nfvo-components/listEditor/ListEditorItemView.jsx';
+
+import FeatureGroupEditor from './FeatureGroupEditor.js';
+import FeatureGroupsConfirmationModal from './FeatureGroupsConfirmationModal.jsx';
+
+class FeatureGroupListEditorView extends React.Component {
+ static propTypes = {
+ vendorName: React.PropTypes.string,
+ licenseModelId: React.PropTypes.string.isRequired,
+ featureGroupsModal: React.PropTypes.shape({
+ show: React.PropTypes.bool,
+ editMode: React.PropTypes.bool
+ }),
+ isReadOnlyMode: React.PropTypes.bool.isRequired,
+ onAddFeatureGroupClick: React.PropTypes.func,
+ onEditFeatureGroupClick: React.PropTypes.func,
+ onDeleteFeatureGroupClick: React.PropTypes.func,
+ onCancelFeatureGroupsEditor: React.PropTypes.func,
+ featureGroupsList: React.PropTypes.array
+ };
+
+ static defaultProps = {
+ featureGroupsList: [],
+ featureGroupsModal: {
+ show: false,
+ editMode: false
+ }
+ };
+
+ state = {
+ localFilter: ''
+ };
+
+ render() {
+ let {vendorName, licenseModelId, featureGroupsModal, isReadOnlyMode, onAddFeatureGroupClick} = this.props;
+ const {localFilter} = this.state;
+
+ return (
+ <div className='feature-groups-list-editor'>
+ <ListEditorView
+ title={i18n('Feature Groups for {vendorName} License Model', {vendorName})}
+ plusButtonTitle={i18n('Add Feature Group')}
+ filterValue={localFilter}
+ onFilter={filter => this.setState({localFilter: filter})}
+ onAdd={() => onAddFeatureGroupClick()}
+ isReadOnlyMode={isReadOnlyMode}>
+ {this.filterList().map(listItem => this.renderFeatureGroupListItem(listItem, isReadOnlyMode))}
+ </ListEditorView>
+ <Modal show={featureGroupsModal.show} bsSize='large' animation={true} className='feature-group-modal'>
+ <Modal.Header>
+ <Modal.Title>{`${featureGroupsModal.editMode ? i18n('Edit Feature Group') : i18n('Create New Feature Group')}`}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <FeatureGroupEditor
+ onCancel={() => this.closeFeatureGroupsEditor()}
+ licenseModelId={licenseModelId}
+ isReadOnlyMode={isReadOnlyMode}/>
+ </Modal.Body>
+ </Modal>
+
+ <FeatureGroupsConfirmationModal licenseModelId={licenseModelId}/>
+
+ </div>
+ );
+ }
+
+
+ renderFeatureGroupListItem(listItem, isReadOnlyMode) {
+ let {name, description, entitlementPoolsIds = [], licenseKeyGroupsIds = []} = listItem;
+ return (
+ <ListEditorItemView
+ key={listItem.id}
+ onDelete={() => this.deleteFeatureGroupItem(listItem)}
+ onSelect={() => this.editFeatureGroupItem(listItem)}
+ className='list-editor-item-view'
+ isReadOnlyMode={isReadOnlyMode}>
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Name')}</div>
+ <div className='text name'>{name}</div>
+ </div>
+
+ <div className='list-editor-item-view-field'>
+ <div className='feature-groups-count-field'>
+ <div className='title'>{i18n('Entitlement')}</div>
+ <div className='title'>{i18n('Pools')}</div>
+ <div className='feature-groups-count-ep'>{entitlementPoolsIds.length || 0}</div>
+ </div>
+ <div className='feature-groups-count-field'>
+ <div className='title'>{i18n('License key')}</div>
+ <div className='title'>{i18n('Groups')}</div>
+ <div className='feature-groups-count-lk'>{licenseKeyGroupsIds.length || 0}</div>
+ </div>
+ </div>
+
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Description')}</div>
+ <div className='text description'>{description}</div>
+ </div>
+
+ </ListEditorItemView>
+ );
+ }
+
+ filterList() {
+ let {featureGroupsList} = this.props;
+ let {localFilter} = this.state;
+ if (localFilter.trim()) {
+ const filter = new RegExp(escape(localFilter), 'i');
+ return featureGroupsList.filter(({name = '', description = ''}) => {
+ return escape(name).match(filter) || escape(description).match(filter);
+ });
+ }
+ else {
+ return featureGroupsList;
+ }
+ }
+
+ closeFeatureGroupsEditor() {
+ this.props.onCancelFeatureGroupsEditor();
+ }
+
+ editFeatureGroupItem(featureGroup) {
+ this.props.onEditFeatureGroupClick(featureGroup);
+ }
+
+ deleteFeatureGroupItem(featureGroup) {
+ this.props.onDeleteFeatureGroupClick(featureGroup);
+ }
+}
+
+export default FeatureGroupListEditorView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsActionHelper.js
new file mode 100644
index 0000000..3776c01
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsActionHelper.js
@@ -0,0 +1,165 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
+import Configuration from 'sdc-app/config/Configuration.js';
+import {actionTypes as featureGroupsActionConstants} from './FeatureGroupsConstants.js';
+import LicenseModelActionHelper from 'sdc-app/onboarding/licenseModel/LicenseModelActionHelper.js';
+import EntitlementPoolsActionHelper from 'sdc-app/onboarding/licenseModel/entitlementPools/EntitlementPoolsActionHelper.js';
+import LicenseKeyGroupsActionHelper from 'sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsActionHelper.js';
+
+function baseUrl(licenseModelId) {
+ const restPrefix = Configuration.get('restPrefix');
+ return `${restPrefix}/v1.0/vendor-license-models/${licenseModelId}/feature-groups`;
+}
+
+function fetchFeatureGroupsList(licenseModelId, version) {
+ let versionQuery = version ? `?version=${version}` : '';
+ return RestAPIUtil.fetch(`${baseUrl(licenseModelId)}${versionQuery}`);
+}
+
+function deleteFeatureGroup(licenseModelId, featureGroupId) {
+ return RestAPIUtil.destroy(`${baseUrl(licenseModelId)}/${featureGroupId}`);
+}
+
+function addFeatureGroup(licenseModelId, featureGroup) {
+ return RestAPIUtil.create(baseUrl(licenseModelId), {
+ name: featureGroup.name,
+ description: featureGroup.description,
+ partNumber: featureGroup.partNumber,
+ addedLicenseKeyGroupsIds: featureGroup.licenseKeyGroupsIds,
+ addedEntitlementPoolsIds: featureGroup.entitlementPoolsIds
+ });
+}
+
+function updateFeatureGroup(licenseModelId, previousFeatureGroup, featureGroup) {
+
+ const {licenseKeyGroupsIds = []} = featureGroup;
+ const {licenseKeyGroupsIds: prevLicenseKeyGroupsIds = []} = previousFeatureGroup;
+ const {entitlementPoolsIds = []} = featureGroup;
+ const {entitlementPoolsIds: prevEntitlementPoolsIds = []} = previousFeatureGroup;
+ return RestAPIUtil.save(`${baseUrl(licenseModelId)}/${featureGroup.id}`, {
+ name: featureGroup.name,
+ description: featureGroup.description,
+ partNumber: featureGroup.partNumber,
+ addedLicenseKeyGroupsIds: licenseKeyGroupsIds.filter(licenseKeyGroupId => prevLicenseKeyGroupsIds.indexOf(licenseKeyGroupId) === -1),
+ removedLicenseKeyGroupsIds: prevLicenseKeyGroupsIds.filter(prevLicenseKeyGroupId => licenseKeyGroupsIds.indexOf(prevLicenseKeyGroupId) === -1),
+ addedEntitlementPoolsIds: entitlementPoolsIds.filter(entitlementPoolId => prevEntitlementPoolsIds.indexOf(entitlementPoolId) === -1),
+ removedEntitlementPoolsIds: prevEntitlementPoolsIds.filter(prevEntitlementPoolId => entitlementPoolsIds.indexOf(prevEntitlementPoolId) === -1)
+
+ });
+}
+
+export default {
+ fetchFeatureGroupsList(dispatch, {licenseModelId, version}) {
+ return fetchFeatureGroupsList(licenseModelId, version).then(response => dispatch({
+ type: featureGroupsActionConstants.FEATURE_GROUPS_LIST_LOADED,
+ response
+ }));
+ },
+
+ deleteFeatureGroup(dispatch, {licenseModelId, featureGroupId}) {
+ return deleteFeatureGroup(licenseModelId, featureGroupId).then(() => dispatch({
+ type: featureGroupsActionConstants.DELETE_FEATURE_GROUPS,
+ featureGroupId
+ }));
+ },
+
+ saveFeatureGroup(dispatch, {licenseModelId, previousFeatureGroup, featureGroup}) {
+ if (previousFeatureGroup) {
+ return updateFeatureGroup(licenseModelId, previousFeatureGroup, featureGroup).then(() => dispatch({
+ type: featureGroupsActionConstants.EDIT_FEATURE_GROUPS,
+ featureGroup
+ }));
+ }
+ else {
+ return addFeatureGroup(licenseModelId, featureGroup).then(response => dispatch({
+ type: featureGroupsActionConstants.ADD_FEATURE_GROUPS,
+ featureGroup: {
+ ...featureGroup,
+ id: response.value
+ }
+ }));
+ }
+ },
+
+ selectEntitlementPoolsEditorTab(dispatch, {tab}) {
+ dispatch({
+ type: featureGroupsActionConstants.featureGroupsEditor.SELECT_TAB,
+ tab
+ });
+ },
+
+ selectFeatureGroupsEditorEntitlementPoolsButtonTab(dispatch, {buttonTab}) {
+ dispatch({
+ type: featureGroupsActionConstants.featureGroupsEditor.SELECTED_ENTITLEMENT_POOLS_BUTTONTAB,
+ buttonTab
+ });
+ },
+
+ selectFeatureGroupsEditorLicenseKeyGroupsButtonTab(dispatch, {buttonTab}) {
+ dispatch({
+ type: featureGroupsActionConstants.featureGroupsEditor.SELECTED_LICENSE_KEY_GROUPS_BUTTONTAB,
+ buttonTab
+ });
+ },
+
+ openFeatureGroupsEditor(dispatch, {featureGroup, licenseModelId}) {
+ EntitlementPoolsActionHelper.fetchEntitlementPoolsList(dispatch, {licenseModelId});
+ LicenseKeyGroupsActionHelper.fetchLicenseKeyGroupsList(dispatch, {licenseModelId});
+ dispatch({
+ type: featureGroupsActionConstants.featureGroupsEditor.OPEN,
+ featureGroup
+ });
+ },
+
+ closeFeatureGroupsEditor(dispatch) {
+ dispatch({
+ type: featureGroupsActionConstants.featureGroupsEditor.CLOSE
+ });
+ },
+
+ featureGroupsEditorDataChanged(dispatch, {deltaData}) {
+ dispatch({
+ type: featureGroupsActionConstants.featureGroupsEditor.DATA_CHANGED,
+ deltaData
+ });
+ },
+
+ hideDeleteConfirm(dispatch) {
+ dispatch({
+ type: featureGroupsActionConstants.FEATURE_GROUPS_DELETE_CONFIRM,
+ featureGroupToDelete: false
+ });
+ },
+
+ openDeleteFeatureGroupConfirm(dispatch, {featureGroup}) {
+ dispatch({
+ type: featureGroupsActionConstants.FEATURE_GROUPS_DELETE_CONFIRM,
+ featureGroupToDelete: featureGroup
+ });
+ },
+
+ switchVersion(dispatch, {licenseModelId, version}) {
+ LicenseModelActionHelper.fetchLicenseModelById(dispatch, {licenseModelId, version}).then(() => {
+ this.fetchFeatureGroupsList(dispatch, {licenseModelId, version});
+ });
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsConfirmationModal.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsConfirmationModal.jsx
new file mode 100644
index 0000000..142ec3c
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsConfirmationModal.jsx
@@ -0,0 +1,48 @@
+import React from 'react';
+import {connect} from 'react-redux';
+import ConfirmationModalView from 'nfvo-components/confirmations/ConfirmationModalView.jsx';
+import FeatureGroupsActionHelper from './FeatureGroupsActionHelper.js';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+function renderMsg(featureGroupToDelete) {
+ let name = featureGroupToDelete ? featureGroupToDelete.name : '';
+ let msg = i18n('Are you sure you want to delete "{name}"?', {name});
+ let subMsg = featureGroupToDelete
+ && featureGroupToDelete.referencingLicenseAgreements
+ && featureGroupToDelete.referencingLicenseAgreements.length > 0 ?
+ i18n('This feature group is associated with one ore more license agreements') :
+ '';
+ return (
+ <div>
+ <p>{msg}</p>
+ <p>{subMsg}</p>
+ </div>
+ );
+};
+
+const mapStateToProps = ({licenseModel: {featureGroup}}, {licenseModelId}) => {
+ let {featureGroupToDelete} = featureGroup;
+ const show = featureGroupToDelete !== false;
+ return {
+ show,
+ title: 'Warning!',
+ type: 'warning',
+ msg: renderMsg(featureGroupToDelete),
+ confirmationDetails: {featureGroupToDelete, licenseModelId}
+ };
+};
+
+const mapActionsToProps = (dispatch) => {
+ return {
+ onConfirmed: ({featureGroupToDelete, licenseModelId}) => {
+ FeatureGroupsActionHelper.deleteFeatureGroup(dispatch, {featureGroupId: featureGroupToDelete.id, licenseModelId});
+ FeatureGroupsActionHelper.hideDeleteConfirm(dispatch);
+ },
+ onDeclined: () => {
+ FeatureGroupsActionHelper.hideDeleteConfirm(dispatch);
+ }
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(ConfirmationModalView);
+
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsConstants.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsConstants.js
new file mode 100644
index 0000000..e02c545
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsConstants.js
@@ -0,0 +1,60 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export const actionTypes = keyMirror({
+ FEATURE_GROUPS_LIST_LOADED: null,
+ ADD_FEATURE_GROUPS: null,
+ EDIT_FEATURE_GROUPS: null,
+ DELETE_FEATURE_GROUPS: null,
+ FEATURE_GROUPS_DELETE_CONFIRM: null,
+
+
+ ENTITLEMENT_POOLS_LIST_LOADED: null,
+
+ featureGroupsEditor: {
+ OPEN: null,
+ CLOSE: null,
+ DATA_CHANGED: null,
+ SELECT_TAB: null,
+ SELECTED_ENTITLEMENT_POOLS_BUTTONTAB: null,
+ SELECTED_LICENSE_KEY_GROUPS_BUTTONTAB: null
+ }
+});
+
+export const state = keyMirror({
+ SELECTED_FEATURE_GROUP_TAB: {
+ GENERAL: 1,
+ ENTITLEMENT_POOLS: 2,
+ LICENCE_KEY_GROUPS: 3
+ },
+ SELECTED_ENTITLEMENT_POOLS_BUTTONTAB: {
+ ASSOCIATED_ENTITLEMENT_POOLS: 1,
+ AVAILABLE_ENTITLEMENT_POOLS: 2
+ },
+ SELECTED_LICENSE_KEY_GROUPS_BUTTONTAB: {
+ ASSOCIATED_LICENSE_KEY_GROUPS: 1,
+ AVAILABLE_LICENSE_KEY_GROUPS: 2
+ },
+});
+
+
+
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsEditorReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsEditorReducer.js
new file mode 100644
index 0000000..576a535
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsEditorReducer.js
@@ -0,0 +1,62 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './FeatureGroupsConstants.js';
+
+
+
+export default (state = {}, action) => {
+ switch (action.type) {
+ case actionTypes.featureGroupsEditor.OPEN:
+ return {
+ ...state,
+ data: action.featureGroup || {}
+ };
+ case actionTypes.featureGroupsEditor.DATA_CHANGED:
+ return {
+ ...state,
+ data: {
+ ...state.data,
+ ...action.deltaData
+ }
+ };
+ case actionTypes.featureGroupsEditor.CLOSE:
+ return {};
+ case actionTypes.featureGroupsEditor.SELECT_TAB:
+ return {
+ ...state,
+ selectedTab: action.tab
+ };
+
+ case actionTypes.featureGroupsEditor.SELECTED_ENTITLEMENT_POOLS_BUTTONTAB:
+ return {
+ ...state,
+ selectedEntitlementPoolsButtonTab: action.buttonTab
+ };
+ case actionTypes.featureGroupsEditor.SELECTED_LICENSE_KEY_GROUPS_BUTTONTAB:
+ return {
+ ...state,
+ selectedLicenseKeyGroupsButtonTab: action.buttonTab
+ };
+ default:
+ return state;
+ }
+
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsListReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsListReducer.js
new file mode 100644
index 0000000..5cf3248
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsListReducer.js
@@ -0,0 +1,36 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './FeatureGroupsConstants.js';
+export default (state = [], action) => {
+ switch (action.type) {
+ case actionTypes.FEATURE_GROUPS_LIST_LOADED:
+ return [...action.response.results];
+ case actionTypes.ADD_FEATURE_GROUPS:
+ return [...state, action.featureGroup];
+ case actionTypes.EDIT_FEATURE_GROUPS:
+ const indexForEdit = state.findIndex(featureGroup => featureGroup.id === action.featureGroup.id);
+ return [...state.slice(0, indexForEdit), action.featureGroup, ...state.slice(indexForEdit + 1)];
+ case actionTypes.DELETE_FEATURE_GROUPS:
+ return state.filter(featureGroup => featureGroup.id !== action.featureGroupId);
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementActionHelper.js
new file mode 100644
index 0000000..9616b60
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementActionHelper.js
@@ -0,0 +1,160 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
+import Configuration from 'sdc-app/config/Configuration.js';
+import {actionTypes as licenseAgreementActionTypes} from './LicenseAgreementConstants.js';
+import FeatureGroupsActionHelper from 'sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsActionHelper.js';
+import LicenseModelActionHelper from 'sdc-app/onboarding/licenseModel/LicenseModelActionHelper.js';
+
+function baseUrl(licenseModelId) {
+ const restPrefix = Configuration.get('restPrefix');
+ return `${restPrefix}/v1.0/vendor-license-models/${licenseModelId}/license-agreements`;
+}
+
+function fetchLicenseAgreementList(licenseModelId, version) {
+ let versionQuery = version ? `?version=${version}` : '';
+ return RestAPIUtil.fetch(`${baseUrl(licenseModelId)}${versionQuery}`);
+}
+
+function postLicenseAgreement(licenseModelId, licenseAgreement) {
+ return RestAPIUtil.create(baseUrl(licenseModelId), {
+ name: licenseAgreement.name,
+ description: licenseAgreement.description,
+ licenseTerm: licenseAgreement.licenseTerm,
+ requirementsAndConstrains: licenseAgreement.requirementsAndConstrains,
+ addedFeatureGroupsIds: licenseAgreement.featureGroupsIds
+ });
+}
+
+function putLicenseAgreement(licenseModelId, previousLicenseAgreement, licenseAgreement) {
+ const {featureGroupsIds = []} = licenseAgreement;
+ const {featureGroupsIds: prevFeatureGroupsIds = []} = previousLicenseAgreement;
+ return RestAPIUtil.save(`${baseUrl(licenseModelId)}/${licenseAgreement.id}`, {
+ name: licenseAgreement.name,
+ description: licenseAgreement.description,
+ licenseTerm: licenseAgreement.licenseTerm,
+ requirementsAndConstrains: licenseAgreement.requirementsAndConstrains,
+ addedFeatureGroupsIds: featureGroupsIds.filter(featureGroupId => prevFeatureGroupsIds.indexOf(featureGroupId) === -1),
+ removedFeatureGroupsIds: prevFeatureGroupsIds.filter(prevFeatureGroupsId => featureGroupsIds.indexOf(prevFeatureGroupsId) === -1)
+ });
+}
+
+function deleteLicenseAgreement(licenseModelId, licenseAgreementId) {
+ return RestAPIUtil.destroy(`${baseUrl(licenseModelId)}/${licenseAgreementId}`);
+}
+
+export default {
+
+ fetchLicenseAgreementList(dispatch, {licenseModelId, version}) {
+ return fetchLicenseAgreementList(licenseModelId, version).then(response => dispatch({
+ type: licenseAgreementActionTypes.LICENSE_AGREEMENT_LIST_LOADED,
+ response
+ }));
+ },
+
+ openLicenseAgreementEditor(dispatch, {licenseModelId, licenseAgreement}) {
+ FeatureGroupsActionHelper.fetchFeatureGroupsList(dispatch, {licenseModelId});
+ dispatch({
+ type: licenseAgreementActionTypes.licenseAgreementEditor.OPEN,
+ licenseAgreement
+ });
+ },
+
+ licenseAgreementEditorDataChanged(dispatch, {deltaData}) {
+ dispatch({
+ type: licenseAgreementActionTypes.licenseAgreementEditor.DATA_CHANGED,
+ deltaData
+ });
+ },
+
+ closeLicenseAgreementEditor(dispatch) {
+ dispatch({
+ type: licenseAgreementActionTypes.licenseAgreementEditor.CLOSE
+ });
+ },
+
+
+ saveLicenseAgreement(dispatch, {licenseModelId, previousLicenseAgreement, licenseAgreement}) {
+ if (previousLicenseAgreement) {
+ return putLicenseAgreement(licenseModelId, previousLicenseAgreement, licenseAgreement).then(() => {
+ dispatch({
+ type: licenseAgreementActionTypes.EDIT_LICENSE_AGREEMENT,
+ licenseAgreement
+ });
+ });
+ }
+ else {
+ return postLicenseAgreement(licenseModelId, licenseAgreement).then(response => {
+ dispatch({
+ type: licenseAgreementActionTypes.ADD_LICENSE_AGREEMENT,
+ licenseAgreement: {
+ ...licenseAgreement,
+ id: response.value
+ }
+ });
+ });
+ }
+ },
+
+ deleteLicenseAgreement(dispatch, {licenseModelId, licenseAgreementId}) {
+ return deleteLicenseAgreement(licenseModelId, licenseAgreementId).then(() => {
+ dispatch({
+ type: licenseAgreementActionTypes.DELETE_LICENSE_AGREEMENT,
+ licenseAgreementId
+ });
+ });
+ },
+
+ selectLicenseAgreementEditorTab(dispatch, {tab}) {
+ dispatch({
+ type: licenseAgreementActionTypes.licenseAgreementEditor.SELECT_TAB,
+ tab
+ });
+ },
+
+ selectLicenseAgreementEditorFeatureGroupsButtonTab(dispatch, {buttonTab}) {
+ dispatch({
+ type: licenseAgreementActionTypes.licenseAgreementEditor.SELECT_FEATURE_GROUPS_BUTTONTAB,
+ buttonTab
+ });
+ },
+
+ hideDeleteConfirm(dispatch) {
+ dispatch({
+ type: licenseAgreementActionTypes.LICENSE_AGREEMENT_DELETE_CONFIRM,
+ licenseAgreementToDelete: false
+ });
+ },
+
+ openDeleteLicenseAgreementConfirm(dispatch, {licenseAgreement} ) {
+ dispatch({
+ type: licenseAgreementActionTypes.LICENSE_AGREEMENT_DELETE_CONFIRM,
+ licenseAgreementToDelete: licenseAgreement
+ });
+ },
+
+ switchVersion(dispatch, {licenseModelId, version}) {
+ LicenseModelActionHelper.fetchLicenseModelById(dispatch, {licenseModelId, version}).then(() => {
+ this.fetchLicenseAgreementList(dispatch, {licenseModelId, version});
+ });
+ }
+};
+
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementConfirmationModal.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementConfirmationModal.jsx
new file mode 100644
index 0000000..42f2407
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementConfirmationModal.jsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import {connect} from 'react-redux';
+import ConfirmationModalView from 'nfvo-components/confirmations/ConfirmationModalView.jsx';
+import LicenseAgreementActionHelper from './LicenseAgreementActionHelper.js';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+function renderMsg(licenseAgreementToDelete) {
+ let name = licenseAgreementToDelete ? licenseAgreementToDelete.name : '';
+ let msg = i18n('Are you sure you want to delete "{name}"?', {name});
+ return(
+ <div>
+ <p>{msg}</p>
+ </div>
+ );
+};
+
+const mapStateToProps = ({licenseModel: {licenseAgreement}}, {licenseModelId}) => {
+ let {licenseAgreementToDelete} = licenseAgreement;
+ const show = licenseAgreementToDelete !== false;
+ return {
+ show,
+ title: 'Warning!',
+ type: 'warning',
+ msg: renderMsg(licenseAgreementToDelete),
+ confirmationDetails: {licenseAgreementToDelete, licenseModelId}
+ };
+};
+
+const mapActionsToProps = (dispatch) => {
+ return {
+ onConfirmed: ({licenseAgreementToDelete, licenseModelId}) => {
+
+ LicenseAgreementActionHelper.deleteLicenseAgreement(dispatch, {licenseModelId, licenseAgreementId: licenseAgreementToDelete.id});
+ LicenseAgreementActionHelper.hideDeleteConfirm(dispatch);
+ },
+ onDeclined: () => {
+ LicenseAgreementActionHelper.hideDeleteConfirm(dispatch);
+ }
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(ConfirmationModalView);
+
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementConstants.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementConstants.js
new file mode 100644
index 0000000..af5c454
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementConstants.js
@@ -0,0 +1,66 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+export const actionTypes = keyMirror({
+ LICENSE_AGREEMENT_LIST_LOADED: null,
+ ADD_LICENSE_AGREEMENT: null,
+ EDIT_LICENSE_AGREEMENT: null,
+ DELETE_LICENSE_AGREEMENT: null,
+ LICENSE_AGREEMENT_DELETE_CONFIRM: null,
+
+ licenseAgreementEditor: {
+ OPEN: null,
+ CLOSE: null,
+ DATA_CHANGED: null,
+ SELECT_TAB: null,
+ SELECT_FEATURE_GROUPS_BUTTONTAB: null,
+ }
+
+});
+
+export const enums = keyMirror({
+ SELECTED_LICENSE_AGREEMENT_TAB: {
+ GENERAL: 1,
+ FEATURE_GROUPS: 2
+ },
+
+ SELECTED_FEATURE_GROUPS_BUTTONTAB: {
+ ASSOCIATED_FEATURE_GROUPS: 1,
+ AVAILABLE_FEATURE_GROUPS: 2
+ }
+});
+
+export const defaultState = {
+ LICENSE_AGREEMENT_EDITOR_DATA: {
+ licenseTerm: {choice: '', other: ''}
+ }
+};
+
+export const optionsInputValues = {
+ LICENSE_MODEL_TYPE: [
+ {enum: '', title: i18n('please select…')},
+ {enum: 'Fixed_Term', title: 'Fixed Term'},
+ {enum: 'Perpetual', title: 'Perpetual'},
+ {enum: 'Unlimited', title: 'Unlimited'}
+ ]
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementEditor.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementEditor.js
new file mode 100644
index 0000000..6a3e4db
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementEditor.js
@@ -0,0 +1,60 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import LicenseAgreementActionHelper from './LicenseAgreementActionHelper.js';
+import LicenseAgreementEditorView from './LicenseAgreementEditorView.jsx';
+
+export const mapStateToProps = ({licenseModel: {licenseAgreement, featureGroup}}) => {
+
+
+ let {data, selectedTab, selectedFeatureGroupsButtonTab} = licenseAgreement.licenseAgreementEditor;
+
+ let previousData;
+ const licenseAgreementId = data ? data.id : null;
+ if(licenseAgreementId) {
+ previousData = licenseAgreement.licenseAgreementList.find(licenseAgreement => licenseAgreement.id === licenseAgreementId);
+ }
+
+ const {featureGroupsList = []} = featureGroup;
+
+ return {
+ data,
+ previousData,
+ selectedTab,
+ selectedFeatureGroupsButtonTab,
+ featureGroupsList
+ };
+};
+
+export const mapActionsToProps = (dispatch, {licenseModelId}) => {
+ return {
+ onDataChanged: deltaData => LicenseAgreementActionHelper.licenseAgreementEditorDataChanged(dispatch, {deltaData}),
+ onTabSelect: tab => LicenseAgreementActionHelper.selectLicenseAgreementEditorTab(dispatch, {tab}),
+ onFeatureGroupsButtonTabSelect: buttonTab => LicenseAgreementActionHelper.selectLicenseAgreementEditorFeatureGroupsButtonTab(dispatch, {buttonTab}),
+ onCancel: () => LicenseAgreementActionHelper.closeLicenseAgreementEditor(dispatch),
+ onSubmit: ({previousLicenseAgreement, licenseAgreement}) => {
+ LicenseAgreementActionHelper.closeLicenseAgreementEditor(dispatch);
+ LicenseAgreementActionHelper.saveLicenseAgreement(dispatch, {licenseModelId, previousLicenseAgreement, licenseAgreement});
+ }
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(LicenseAgreementEditorView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementEditorReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementEditorReducer.js
new file mode 100644
index 0000000..74e2f6e
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementEditorReducer.js
@@ -0,0 +1,54 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes, defaultState} from './LicenseAgreementConstants.js';
+
+export default (state = {}, action) => {
+ switch (action.type) {
+ case actionTypes.licenseAgreementEditor.OPEN:
+ return {
+ ...state,
+ data: action.licenseAgreement ? { ...action.licenseAgreement } : defaultState.LICENSE_AGREEMENT_EDITOR_DATA
+ };
+ case actionTypes.licenseAgreementEditor.DATA_CHANGED:
+ return {
+ ...state,
+ data: {
+ ...state.data,
+ ...action.deltaData
+ }
+ };
+ case actionTypes.licenseAgreementEditor.CLOSE:
+ return {};
+ case actionTypes.licenseAgreementEditor.SELECT_TAB:
+ return {
+ ...state,
+ selectedTab: action.tab
+ };
+ case actionTypes.licenseAgreementEditor.SELECT_FEATURE_GROUPS_BUTTONTAB:
+ return {
+ ...state,
+ selectedFeatureGroupsButtonTab: action.buttonTab
+ };
+ default:
+ return state;
+ }
+
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementEditorView.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementEditorView.jsx
new file mode 100644
index 0000000..b21f943
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementEditorView.jsx
@@ -0,0 +1,247 @@
+import React from 'react';
+import ButtonGroup from 'react-bootstrap/lib/ButtonGroup.js';
+import Button from 'react-bootstrap/lib/Button.js';
+import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx';
+import ValidationTabs from 'nfvo-components/input/validation/ValidationTabs.jsx';
+import ValidationTab from 'nfvo-components/input/validation/ValidationTab.jsx';
+import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx';
+import DualListboxView from 'nfvo-components/input/dualListbox/DualListboxView.jsx';
+import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx';
+import ListEditorViewItem from 'nfvo-components/listEditor/ListEditorItemView.jsx';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+import {enums as LicenseAgreementEnums, optionsInputValues as LicenseAgreementOptionsInputValues} from './LicenseAgreementConstants.js';
+
+
+const LicenseAgreementPropType = React.PropTypes.shape({
+ id: React.PropTypes.string,
+ name: React.PropTypes.string,
+ description: React.PropTypes.string,
+ requirementsAndConstrains: React.PropTypes.string,
+ licenseTerm: React.PropTypes.object,
+ featureGroupsIds: React.PropTypes.arrayOf(React.PropTypes.string)
+});
+
+class LicenseAgreementEditorView extends React.Component {
+
+ static propTypes = {
+ data: LicenseAgreementPropType,
+ previousData: LicenseAgreementPropType,
+ isReadOnlyMode: React.PropTypes.bool,
+ onDataChanged: React.PropTypes.func.isRequired,
+ onSubmit: React.PropTypes.func.isRequired,
+ onCancel: React.PropTypes.func.isRequired,
+
+ selectedTab: React.PropTypes.number,
+ onTabSelect: React.PropTypes.func,
+
+ selectedFeatureGroupsButtonTab: React.PropTypes.number,
+ onFeatureGroupsButtonTabSelect: React.PropTypes.func,
+ featureGroupsList: DualListboxView.propTypes.availableList
+ };
+
+ static defaultProps = {
+ selectedTab: LicenseAgreementEnums.SELECTED_LICENSE_AGREEMENT_TAB.GENERAL,
+ selectedFeatureGroupsButtonTab: LicenseAgreementEnums.SELECTED_FEATURE_GROUPS_BUTTONTAB.AVAILABLE_FEATURE_GROUPS,
+ data: {}
+ };
+
+ state = {
+ localFeatureGroupsListFilter: ''
+ };
+
+ render() {
+ let {selectedTab, onTabSelect, isReadOnlyMode} = this.props;
+ return (
+ <ValidationForm
+ ref='validationForm'
+ hasButtons={true}
+ onSubmit={ () => this.submit() }
+ onReset={ () => this.props.onCancel() }
+ labledButtons={true}
+ isReadOnlyMode={isReadOnlyMode}
+ className='license-agreement-form'>
+ <ValidationTabs activeKey={onTabSelect ? selectedTab : undefined} onSelect={onTabSelect}>
+ {this.renderGeneralTab()}
+ {this.renderFeatureGroupsTab()}
+ </ValidationTabs>
+ </ValidationForm>
+ );
+ }
+
+ submit() {
+ const {data: licenseAgreement, previousData: previousLicenseAgreement} = this.props;
+ this.props.onSubmit({licenseAgreement, previousLicenseAgreement});
+ }
+
+ renderGeneralTab() {
+ let {data = {}, onDataChanged} = this.props;
+ let {name, description, requirementsAndConstrains, licenseTerm} = data;
+ return (
+ <ValidationTab
+ eventKey={LicenseAgreementEnums.SELECTED_LICENSE_AGREEMENT_TAB.GENERAL}
+ title={i18n('General')}>
+ <div className='license-agreement-form-row'>
+ <div className='license-agreement-form-col'>
+ <ValidationInput
+ onChange={name => onDataChanged({name})}
+ label={i18n('Name')}
+ value={name}
+ name='license-agreement-name'
+ validations={{maxLength: 25, required: true}}
+ type='text'/>
+ <ValidationInput
+ onChange={requirementsAndConstrains => onDataChanged({requirementsAndConstrains})}
+ label={i18n('Requirements and Constraints')}
+ value={requirementsAndConstrains}
+ name='license-agreement-requirements-and-constraints'
+ validations={{maxLength: 1000}}
+ type='textarea'/>
+ </div>
+ <ValidationInput
+ onChange={description => onDataChanged({description})}
+ label={i18n('Description')}
+ value={description}
+ name='license-agreement-description'
+ validations={{maxLength: 1000, required: true}}
+ type='textarea'/>
+ </div>
+ <div className='license-agreement-form-row'>
+ <ValidationInput
+ onEnumChange={licenseTerm => onDataChanged({licenseTerm:{choice: licenseTerm, other: ''}})}
+ selectedEnum={licenseTerm && licenseTerm.choice}
+ validations={{required: true}}
+ type='select'
+ label={i18n('License Term')}
+ values={LicenseAgreementOptionsInputValues.LICENSE_MODEL_TYPE}/>
+ </div>
+ </ValidationTab>
+ );
+ }
+
+ renderFeatureGroupsTab() {
+ let {onFeatureGroupsButtonTabSelect, selectedFeatureGroupsButtonTab, featureGroupsList} = this.props;
+ if (featureGroupsList.length > 0) {
+ return (
+ <ValidationTab
+ eventKey={LicenseAgreementEnums.SELECTED_LICENSE_AGREEMENT_TAB.FEATURE_GROUPS}
+ title={i18n('Feature Groups')}>
+ <ButtonGroup>
+ {
+ this.renderFeatureGroupsButtonTab(
+ LicenseAgreementEnums.SELECTED_FEATURE_GROUPS_BUTTONTAB.ASSOCIATED_FEATURE_GROUPS,
+ selectedFeatureGroupsButtonTab,
+ i18n('Associated Feature Groups'),
+ onFeatureGroupsButtonTabSelect
+ )
+ }
+ {
+ this.renderFeatureGroupsButtonTab(
+ LicenseAgreementEnums.SELECTED_FEATURE_GROUPS_BUTTONTAB.AVAILABLE_FEATURE_GROUPS,
+ selectedFeatureGroupsButtonTab,
+ i18n('Available Feature Groups'),
+ onFeatureGroupsButtonTabSelect
+ )
+ }
+ </ButtonGroup>
+ {this.renderFeatureGroupsButtonTabContent(selectedFeatureGroupsButtonTab)}
+ </ValidationTab>
+ );
+ } else {
+ return (
+ <ValidationTab
+ eventKey={LicenseAgreementEnums.SELECTED_LICENSE_AGREEMENT_TAB.FEATURE_GROUPS}
+ title={i18n('Feature Groups')}>
+ <p>{i18n('There is no available feature groups')}</p>
+ </ValidationTab>
+ );
+ }
+ }
+
+ renderFeatureGroupsButtonTabContent(selectedFeatureGroupsButtonTab) {
+ const {featureGroupsList = [], data: {featureGroupsIds = []}} = this.props;
+ const {localFeatureGroupsListFilter} = this.state;
+ let selectedFeatureGroups = featureGroupsIds.map(featureGroupId => featureGroupsList.find(featureGroup => featureGroup.id === featureGroupId));
+
+ const dualBoxFilterTitle = {
+ left: i18n('Available Feature Groups'),
+ right: i18n('Selected Feature Groups')
+ };
+
+ switch (selectedFeatureGroupsButtonTab) {
+ case LicenseAgreementEnums.SELECTED_FEATURE_GROUPS_BUTTONTAB.ASSOCIATED_FEATURE_GROUPS:
+ if (!selectedFeatureGroups.length) {
+ return (
+ <div className='no-items-msg'>
+ {i18n('There are currently no feature groups associated with this license agreement. Click "Available Feature Groups" to associate.')}
+ </div>
+ );
+ }
+ if (featureGroupsList.length) {
+ return (
+ <ListEditorView
+ className='thinner-list'
+ filterValue={localFeatureGroupsListFilter}
+ onFilter={localFeatureGroupsListFilter => this.setState({localFeatureGroupsListFilter})}>
+ {this.filterAssociatedFeatureGroupsList(selectedFeatureGroups).map(featureGroup => this.renderAssociatedFeatureGroupListItem(featureGroup))}
+ </ListEditorView>
+ );
+ }
+ return;
+ case LicenseAgreementEnums.SELECTED_FEATURE_GROUPS_BUTTONTAB.AVAILABLE_FEATURE_GROUPS:
+ return (
+ <DualListboxView
+ filterTitle={dualBoxFilterTitle}
+ selectedValuesList={this.props.data.featureGroupsIds}
+ availableList={this.props.featureGroupsList}
+ onChange={ selectedValuesList => this.props.onDataChanged( { featureGroupsIds: selectedValuesList } )}/>
+ );
+ }
+ }
+
+ renderFeatureGroupsButtonTab(buttonTab, selectedButtonTab, title, onClick) {
+ const isSelected = buttonTab === selectedButtonTab;
+ return (
+ <Button
+ className='button-tab'
+ active={isSelected}
+ onClick={() => !isSelected && onClick(buttonTab)}>
+ { title }
+ </Button>
+ );
+ }
+
+ renderAssociatedFeatureGroupListItem({id, name, entitlementPoolsIds = [], licenseKeyGroupsIds = []}) {
+ const {onDataChanged, data: {featureGroupsIds}, isReadOnlyMode} = this.props;
+ return (
+ <ListEditorViewItem
+ key={id}
+ onDelete={() => onDataChanged({featureGroupsIds: featureGroupsIds.filter(featureGroupId => featureGroupId !== id)})}
+ isReadOnlyMode={isReadOnlyMode}>
+ <div className='name'>{name}</div>
+ <div className='inner-objects-count'>{
+ i18n(
+ 'Entitlement Pools({entitlementPoolsCounter}), License Key Groups({licenseKeyGroupsCount})',
+ {
+ entitlementPoolsCounter: entitlementPoolsIds.length,
+ licenseKeyGroupsCount: licenseKeyGroupsIds.length
+ }
+ )
+ }</div>
+ </ListEditorViewItem>
+ );
+ }
+
+ filterAssociatedFeatureGroupsList(featureGroupsList) {
+ let {localFeatureGroupsListFilter} = this.state;
+ if (localFeatureGroupsListFilter) {
+ const filter = new RegExp(escape(localFeatureGroupsListFilter), 'i');
+ return featureGroupsList.filter(({name}) => name.match(filter));
+ }
+ else {
+ return featureGroupsList;
+ }
+ }
+}
+
+export default LicenseAgreementEditorView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementListEditor.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementListEditor.js
new file mode 100644
index 0000000..ca18bfa
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementListEditor.js
@@ -0,0 +1,59 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import LicenseModelActionHelper from 'sdc-app/onboarding/licenseModel/LicenseModelActionHelper.js';
+import LicenseAgreementActionHelper from './LicenseAgreementActionHelper.js';
+import LicenseAgreementListEditorView from './LicenseAgreementListEditorView.jsx';
+import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js';
+import OnboardingActionHelper from 'sdc-app/onboarding/OnboardingActionHelper.js';
+
+const mapStateToProps = ({licenseModel: {licenseAgreement, licenseModelEditor}}) => {
+ let {licenseAgreementList} = licenseAgreement;
+ let {data} = licenseAgreement.licenseAgreementEditor;
+ let {vendorName} = licenseModelEditor.data;
+
+ let isReadOnlyMode = VersionControllerUtils.isReadOnly(licenseModelEditor.data);
+
+ return {
+ vendorName,
+ licenseAgreementList,
+ isReadOnlyMode,
+ isDisplayModal: Boolean(data),
+ isModalInEditMode: Boolean(data && data.id)
+ };
+};
+
+const mapActionsToProps = (dispatch, {licenseModelId}) => {
+ return {
+ onAddLicenseAgreementClick: () => LicenseAgreementActionHelper.openLicenseAgreementEditor(dispatch, {licenseModelId}),
+ onEditLicenseAgreementClick: licenseAgreement => LicenseAgreementActionHelper.openLicenseAgreementEditor(dispatch, {licenseModelId, licenseAgreement}),
+ onDeleteLicenseAgreement: licenseAgreement => LicenseAgreementActionHelper.openDeleteLicenseAgreementConfirm(dispatch, {licenseAgreement}),
+ onCallVCAction: action => {
+ LicenseModelActionHelper.performVCAction(dispatch, {licenseModelId, action}).then(() => {
+ LicenseAgreementActionHelper.fetchLicenseAgreementList(dispatch, {licenseModelId});
+ });
+ },
+ switchLicenseModelVersion: version => LicenseAgreementActionHelper.switchVersion(dispatch, {licenseModelId, version}),
+ onClose: () => OnboardingActionHelper.navigateToOnboardingCatalog(dispatch)
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(LicenseAgreementListEditorView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementListEditorView.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementListEditorView.jsx
new file mode 100644
index 0000000..4d7e704
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementListEditorView.jsx
@@ -0,0 +1,126 @@
+import React from 'react';
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import Modal from 'nfvo-components/modal/Modal.jsx';
+
+import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx';
+import ListEditorItemView from 'nfvo-components/listEditor/ListEditorItemView.jsx';
+import LicenseAgreementEditor from './LicenseAgreementEditor.js';
+import InputOptions, {other as optionInputOther} from 'nfvo-components/input/inputOptions/InputOptions.jsx';
+import {optionsInputValues} from './LicenseAgreementConstants';
+import LicenseAgreementConfirmationModal from './LicenseAgreementConfirmationModal.jsx';
+
+
+class LicenseAgreementListEditorView extends React.Component {
+ static propTypes = {
+ vendorName: React.PropTypes.string,
+ licenseModelId: React.PropTypes.string.isRequired,
+ licenseAgreementList: React.PropTypes.array,
+ isReadOnlyMode: React.PropTypes.bool.isRequired,
+ isDisplayModal: React.PropTypes.bool,
+ isModalInEditMode: React.PropTypes.bool,
+ onAddLicenseAgreementClick: React.PropTypes.func,
+ onEditLicenseAgreementClick: React.PropTypes.func,
+ onDeleteLicenseAgreement: React.PropTypes.func,
+ onCallVCAction: React.PropTypes.func
+ };
+
+ static defaultProps = {
+ licenseAgreementList: []
+ };
+
+ state = {
+ localFilter: ''
+ };
+
+ render() {
+ const {licenseModelId, vendorName, isReadOnlyMode, isDisplayModal, isModalInEditMode} = this.props;
+ const {onAddLicenseAgreementClick} = this.props;
+ const {localFilter} = this.state;
+
+ return (
+ <div className='license-agreement-list-editor'>
+ <ListEditorView
+ title={i18n('License Agreements for {vendorName} License Model', {vendorName})}
+ plusButtonTitle={i18n('Add License Agreement')}
+ onAdd={onAddLicenseAgreementClick}
+ filterValue={localFilter}
+ onFilter={filter => this.setState({localFilter: filter})}
+ isReadOnlyMode={isReadOnlyMode}>
+ {this.filterList().map(licenseAgreement => this.renderLicenseAgreementListItem(licenseAgreement, isReadOnlyMode))}
+ </ListEditorView>
+ <Modal show={isDisplayModal} bsSize='large' animation={true} className='license-agreement-modal'>
+ <Modal.Header>
+ <Modal.Title>{`${isModalInEditMode ? i18n('Edit License Agreement') : i18n('Create New License Agreement')}`}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ {
+ isDisplayModal && (
+ <LicenseAgreementEditor licenseModelId={licenseModelId} isReadOnlyMode={isReadOnlyMode} />
+ )
+ }
+ </Modal.Body>
+ </Modal>
+ <LicenseAgreementConfirmationModal licenseModelId={licenseModelId}/>
+
+ </div>
+ );
+ }
+
+ filterList() {
+ let {licenseAgreementList} = this.props;
+ let {localFilter} = this.state;
+ if (localFilter.trim()) {
+ const filter = new RegExp(escape(localFilter), 'i');
+ return licenseAgreementList.filter(({name = '', description = '', licenseTerm = ''}) => {
+ return escape(name).match(filter) || escape(description).match(filter) || escape(this.extractValue(licenseTerm)).match(filter);
+ });
+ }
+ else {
+ return licenseAgreementList;
+ }
+ }
+
+ renderLicenseAgreementListItem(licenseAgreement, isReadOnlyMode) {
+ let {id, name, description, licenseTerm, featureGroupsIds = []} = licenseAgreement;
+ let {onEditLicenseAgreementClick, onDeleteLicenseAgreement} = this.props;
+ return (
+ <ListEditorItemView
+ key={id}
+ onSelect={() => onEditLicenseAgreementClick(licenseAgreement)}
+ onDelete={() => onDeleteLicenseAgreement(licenseAgreement)}
+ className='list-editor-item-view'
+ isReadOnlyMode={isReadOnlyMode}>
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Name')}</div>
+ <div className='text name'>{name}</div>
+ </div>
+ <div className='list-editor-item-view-field'>
+ <div className='list-editor-item-view-field-tight'>
+ <div className='title'>{i18n('Type')}</div>
+ <div className='text type'>{this.extractValue(licenseTerm)}</div>
+ </div>
+ <div className='list-editor-item-view-field-tight'>
+ <div className='title'>{i18n('Feature')}</div>
+ <div className='title'>{i18n('Groups')}</div>
+ <div className='feature-groups-count'>{featureGroupsIds.length}</div>
+ </div>
+ </div>
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Description')}</div>
+ <div className='text description'>{description}</div>
+ </div>
+ </ListEditorItemView>
+ );
+ }
+
+ extractValue(item) {
+ if (item === undefined) {
+ return '';
+ } //TODO fix it later
+
+ return item ? item.choice === optionInputOther.OTHER ? item.other : InputOptions.getTitleByName(optionsInputValues, item.choice) : '';
+ }
+}
+
+export default LicenseAgreementListEditorView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementListReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementListReducer.js
new file mode 100644
index 0000000..5b5fa00
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementListReducer.js
@@ -0,0 +1,37 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes as licenseAgreementActionTypes} from './LicenseAgreementConstants';
+
+export default (state = [], action) => {
+ switch (action.type) {
+ case licenseAgreementActionTypes.LICENSE_AGREEMENT_LIST_LOADED:
+ return [...action.response.results];
+ case licenseAgreementActionTypes.ADD_LICENSE_AGREEMENT:
+ return [...state, action.licenseAgreement];
+ case licenseAgreementActionTypes.EDIT_LICENSE_AGREEMENT:
+ const indexForEdit = state.findIndex(licenseAgreement => licenseAgreement.id === action.licenseAgreement.id);
+ return [...state.slice(0, indexForEdit), action.licenseAgreement, ...state.slice(indexForEdit + 1)];
+ case licenseAgreementActionTypes.DELETE_LICENSE_AGREEMENT:
+ return state.filter(licenseAgreement => licenseAgreement.id !== action.licenseAgreementId);
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsActionHelper.js
new file mode 100644
index 0000000..50ac2c8
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsActionHelper.js
@@ -0,0 +1,139 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
+import Configuration from 'sdc-app/config/Configuration.js';
+import {actionTypes as licenseKeyGroupsConstants} from './LicenseKeyGroupsConstants.js';
+import LicenseModelActionHelper from 'sdc-app/onboarding/licenseModel/LicenseModelActionHelper.js';
+
+function baseUrl(licenseModelId) {
+ const restPrefix = Configuration.get('restPrefix');
+ return `${restPrefix}/v1.0/vendor-license-models/${licenseModelId}/license-key-groups`;
+}
+
+function fetchLicenseKeyGroupsList(licenseModelId, version) {
+ let versionQuery = version ? `?version=${version}` : '';
+ return RestAPIUtil.fetch(`${baseUrl(licenseModelId)}${versionQuery}`);
+}
+
+function deleteLicenseKeyGroup(licenseModelId, licenseKeyGroupId) {
+ return RestAPIUtil.destroy(`${baseUrl(licenseModelId)}/${licenseKeyGroupId}`);
+}
+
+function postLicenseKeyGroup(licenseModelId, licenseKeyGroup) {
+ return RestAPIUtil.create(baseUrl(licenseModelId), {
+ name: licenseKeyGroup.name,
+ description: licenseKeyGroup.description,
+ operationalScope: licenseKeyGroup.operationalScope,
+ type: licenseKeyGroup.type
+ });
+}
+
+function putLicenseKeyGroup(licenseModelId, licenseKeyGroup) {
+ return RestAPIUtil.save(`${baseUrl(licenseModelId)}/${licenseKeyGroup.id}`, {
+ name: licenseKeyGroup.name,
+ description: licenseKeyGroup.description,
+ operationalScope: licenseKeyGroup.operationalScope,
+ type: licenseKeyGroup.type
+ });
+}
+
+
+export default {
+ fetchLicenseKeyGroupsList(dispatch, {licenseModelId, version}) {
+ return fetchLicenseKeyGroupsList(licenseModelId, version).then(response => dispatch({
+ type: licenseKeyGroupsConstants.LICENSE_KEY_GROUPS_LIST_LOADED,
+ response
+ }));
+ },
+
+ openLicenseKeyGroupsEditor(dispatch, {licenseKeyGroup} = {}) {
+ dispatch({
+ type: licenseKeyGroupsConstants.licenseKeyGroupsEditor.OPEN,
+ licenseKeyGroup
+ });
+ },
+
+ closeLicenseKeyGroupEditor(dispatch){
+ dispatch({
+ type: licenseKeyGroupsConstants.licenseKeyGroupsEditor.CLOSE
+ });
+ },
+
+ saveLicenseKeyGroup(dispatch, {licenseModelId, previousLicenseKeyGroup, licenseKeyGroup}) {
+ if (previousLicenseKeyGroup) {
+ return putLicenseKeyGroup(licenseModelId, licenseKeyGroup).then(() => {
+ dispatch({
+ type: licenseKeyGroupsConstants.EDIT_LICENSE_KEY_GROUP,
+ licenseKeyGroup
+ });
+ });
+ }
+ else {
+ return postLicenseKeyGroup(licenseModelId, licenseKeyGroup).then(response => {
+ dispatch({
+ type: licenseKeyGroupsConstants.ADD_LICENSE_KEY_GROUP,
+ licenseKeyGroup: {
+ ...licenseKeyGroup,
+ id: response.value
+ }
+ });
+ });
+ }
+
+
+ },
+
+ deleteLicenseKeyGroup(dispatch, {licenseModelId, licenseKeyGroupId}){
+ return deleteLicenseKeyGroup(licenseModelId, licenseKeyGroupId).then(()=> {
+ dispatch({
+ type: licenseKeyGroupsConstants.DELETE_LICENSE_KEY_GROUP,
+ licenseKeyGroupId
+ });
+ });
+ },
+
+ licenseKeyGroupEditorDataChanged(dispatch, {deltaData}) {
+ dispatch({
+ type: licenseKeyGroupsConstants.licenseKeyGroupsEditor.DATA_CHANGED,
+ deltaData
+ });
+ },
+
+ hideDeleteConfirm(dispatch) {
+ dispatch({
+ type: licenseKeyGroupsConstants.LICENSE_KEY_GROUPS_DELETE_CONFIRM,
+ licenseKeyGroupToDelete: false
+ });
+ },
+
+ openDeleteLicenseAgreementConfirm(dispatch, {licenseKeyGroup}) {
+ dispatch({
+ type: licenseKeyGroupsConstants.LICENSE_KEY_GROUPS_DELETE_CONFIRM,
+ licenseKeyGroupToDelete: licenseKeyGroup
+ });
+ },
+
+ switchVersion(dispatch, {licenseModelId, version}) {
+ LicenseModelActionHelper.fetchLicenseModelById(dispatch, {licenseModelId, version}).then(() => {
+ this.fetchLicenseKeyGroupsList(dispatch, {licenseModelId, version});
+ });
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsConfirmationModal.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsConfirmationModal.jsx
new file mode 100644
index 0000000..2413db5
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsConfirmationModal.jsx
@@ -0,0 +1,49 @@
+import React from 'react';
+import {connect} from 'react-redux';
+import ConfirmationModalView from 'nfvo-components/confirmations/ConfirmationModalView.jsx';
+import LicenseKeyGroupsActionHelper from './LicenseKeyGroupsActionHelper.js';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+function renderMsg(licenseKeyGroupToDelete) {
+ let name = licenseKeyGroupToDelete ? licenseKeyGroupToDelete.name : '';
+ let msg = i18n('Are you sure you want to delete "{name}"?', {name});
+ let subMsg = licenseKeyGroupToDelete
+ && licenseKeyGroupToDelete.referencingFeatureGroups
+ && licenseKeyGroupToDelete.referencingFeatureGroups.length > 0 ?
+ i18n('This license key group is associated with one or more feature groups') :
+ '';
+ return(
+ <div>
+ <p>{msg}</p>
+ <p>{subMsg}</p>
+ </div>
+ );
+};
+
+const mapStateToProps = ({licenseModel: {licenseKeyGroup}}, {licenseModelId}) => {
+ let {licenseKeyGroupToDelete} = licenseKeyGroup;
+ const show = licenseKeyGroupToDelete !== false;
+ return {
+ show,
+ title: 'Warning!',
+ type: 'warning',
+ msg: renderMsg(licenseKeyGroupToDelete),
+ confirmationDetails: {licenseKeyGroupToDelete, licenseModelId}
+ };
+};
+
+const mapActionsToProps = (dispatch) => {
+ return {
+ onConfirmed: ({licenseKeyGroupToDelete, licenseModelId}) => {
+
+ LicenseKeyGroupsActionHelper.deleteLicenseKeyGroup(dispatch, {licenseModelId, licenseKeyGroupId:licenseKeyGroupToDelete.id});
+ LicenseKeyGroupsActionHelper.hideDeleteConfirm(dispatch);
+ },
+ onDeclined: () => {
+ LicenseKeyGroupsActionHelper.hideDeleteConfirm(dispatch);
+ }
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(ConfirmationModalView);
+
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsConstants.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsConstants.js
new file mode 100644
index 0000000..d32bc52
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsConstants.js
@@ -0,0 +1,64 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+export const actionTypes = keyMirror({
+
+ LICENSE_KEY_GROUPS_LIST_LOADED: null,
+ DELETE_LICENSE_KEY_GROUP: null,
+ EDIT_LICENSE_KEY_GROUP: null,
+ ADD_LICENSE_KEY_GROUP: null,
+ LICENSE_KEY_GROUPS_DELETE_CONFIRM: null,
+ licenseKeyGroupsEditor: {
+ OPEN: null,
+ CLOSE: null,
+ DATA_CHANGED: null,
+ }
+});
+
+export const defaultState = {
+ licenseKeyGroupsEditor: {
+ type: '',
+ operationalScope: {choices: [], other: ''}
+ }
+};
+
+export const optionsInputValues = {
+ OPERATIONAL_SCOPE: [
+ {enum: '', title: i18n('please select…')},
+ {enum: 'Network_Wide', title: 'Network Wide'},
+ {enum: 'Availability_Zone', title: 'Availability Zone'},
+ {enum: 'Data_Center', title: 'Data Center'},
+ {enum: 'Tenant', title: 'Tenant'},
+ {enum: 'VM', title: 'VM'},
+ {enum: 'CPU', title: 'CPU'},
+ {enum: 'Core', title: 'Core'}
+ ],
+ TYPE: [
+ {enum: '', title: i18n('please select…')},
+ {enum: 'Universal', title: 'Universal'},
+ {enum: 'Unique', title: 'Unique'},
+ {enum: 'One_Time', title: 'One Time'}
+ ]
+};
+
+
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsEditor.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsEditor.js
new file mode 100644
index 0000000..3940ec5
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsEditor.js
@@ -0,0 +1,53 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import LicenseKeyGroupsActionHelper from './LicenseKeyGroupsActionHelper.js';
+import LicenseKeyGroupsEditorView from './LicenseKeyGroupsEditorView.jsx';
+
+const mapStateToProps = ({licenseModel: {licenseKeyGroup}}) => {
+
+
+ let {data} = licenseKeyGroup.licenseKeyGroupsEditor;
+
+ let previousData;
+ const licenseKeyGroupId = data ? data.id : null;
+ if(licenseKeyGroupId) {
+ previousData = licenseKeyGroup.licenseKeyGroupsList.find(licenseKeyGroup => licenseKeyGroup.id === licenseKeyGroupId);
+ }
+
+ return {
+ data,
+ previousData
+ };
+};
+
+const mapActionsToProps = (dispatch, {licenseModelId}) => {
+ return {
+ onDataChanged: deltaData => LicenseKeyGroupsActionHelper.licenseKeyGroupEditorDataChanged(dispatch, {deltaData}),
+ onCancel: () => LicenseKeyGroupsActionHelper.closeLicenseKeyGroupEditor(dispatch),
+ onSubmit: ({previousLicenseKeyGroup, licenseKeyGroup}) => {
+ LicenseKeyGroupsActionHelper.closeLicenseKeyGroupEditor(dispatch);
+ LicenseKeyGroupsActionHelper.saveLicenseKeyGroup(dispatch, {licenseModelId, previousLicenseKeyGroup, licenseKeyGroup});
+ }
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(LicenseKeyGroupsEditorView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsEditorReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsEditorReducer.js
new file mode 100644
index 0000000..a744982
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsEditorReducer.js
@@ -0,0 +1,43 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes, defaultState} from './LicenseKeyGroupsConstants.js';
+
+export default (state = {}, action) => {
+ switch (action.type) {
+ case actionTypes.licenseKeyGroupsEditor.OPEN:
+ return {
+ ...state,
+ data: action.licenseKeyGroup ? {...action.licenseKeyGroup} : defaultState.licenseKeyGroupsEditor
+ };
+ case actionTypes.licenseKeyGroupsEditor.CLOSE:
+ return {};
+ case actionTypes.licenseKeyGroupsEditor.DATA_CHANGED:
+ return {
+ ...state,
+ data: {
+ ...state.data,
+ ...action.deltaData
+ }
+ };
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsEditorView.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsEditorView.jsx
new file mode 100644
index 0000000..102e713
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsEditorView.jsx
@@ -0,0 +1,92 @@
+import React from 'react';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx';
+import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx';
+import {optionsInputValues as licenseKeyGroupOptionsInputValues} from './LicenseKeyGroupsConstants.js';
+import {other as optionInputOther} from 'nfvo-components/input/inputOptions/InputOptions.jsx';
+
+const LicenseKeyGroupPropType = React.PropTypes.shape({
+ id: React.PropTypes.string,
+ name: React.PropTypes.string,
+ description: React.PropTypes.string,
+ operationalScope: React.PropTypes.shape({
+ choices: React.PropTypes.array,
+ other: React.PropTypes.string
+ }),
+ type: React.PropTypes.string
+});
+
+class LicenseKeyGroupsEditorView extends React.Component {
+ static propTypes = {
+ data: LicenseKeyGroupPropType,
+ previousData: LicenseKeyGroupPropType,
+ isReadOnlyMode: React.PropTypes.bool,
+ onDataChanged: React.PropTypes.func.isRequired,
+ onSubmit: React.PropTypes.func.isRequired,
+ onCancel: React.PropTypes.func.isRequired
+ };
+
+ static defaultProps = {
+ data: {}
+ };
+
+ render() {
+ let {data = {}, onDataChanged, isReadOnlyMode} = this.props;
+ let {name, description, operationalScope, type} = data;
+ return (
+ <ValidationForm
+ ref='validationForm'
+ hasButtons={true}
+ onSubmit={ () => this.submit() }
+ onReset={ () => this.props.onCancel() }
+ labledButtons={true}
+ isReadOnlyMode={isReadOnlyMode}
+ className='license-key-groups-form'>
+ <div className='license-key-groups-form-row'>
+ <ValidationInput
+ onChange={name => onDataChanged({name})}
+ ref='name'
+ label={i18n('Name')}
+ value={name}
+ validations={{maxLength: 120, required: true}}
+ type='text'/>
+ <ValidationInput
+ isMultiSelect={true}
+ isRequired={true}
+ onEnumChange={operationalScope => onDataChanged({operationalScope:{choices: operationalScope, other: ''}})}
+ onOtherChange={operationalScope => onDataChanged({operationalScope:{choices: [optionInputOther.OTHER], other: operationalScope}})}
+ label={i18n('Operational Scope')}
+ validations={{required: true}}
+ multiSelectedEnum={operationalScope && operationalScope.choices}
+ otherValue={operationalScope && operationalScope.other}
+ values={licenseKeyGroupOptionsInputValues.OPERATIONAL_SCOPE}/>
+ </div>
+ <div className='license-key-groups-form-row'>
+ <ValidationInput
+ onChange={description => onDataChanged({description})}
+ ref='description'
+ label={i18n('Description')}
+ value={description}
+ validations={{maxLength: 1000, required: true}}
+ type='textarea'/>
+ <ValidationInput
+ isRequired={true}
+ onEnumChange={type => onDataChanged({type})}
+ selectedEnum={type}
+ label={i18n('Type')}
+ type='select'
+ validations={{required: true}}
+ values={licenseKeyGroupOptionsInputValues.TYPE}/>
+ </div>
+ </ValidationForm>
+ );
+ }
+
+ submit() {
+ const {data: licenseKeyGroup, previousData: previousLicenseKeyGroup} = this.props;
+ this.props.onSubmit({licenseKeyGroup, previousLicenseKeyGroup});
+ }
+}
+
+export default LicenseKeyGroupsEditorView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsListEditor.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsListEditor.js
new file mode 100644
index 0000000..e1b610f
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsListEditor.js
@@ -0,0 +1,50 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import LicenseKeyGroupsActionHelper from './LicenseKeyGroupsActionHelper.js';
+import LicenseKeyGroupsListEditorView from './LicenseKeyGroupsListEditorView.jsx';
+import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js';
+
+const mapStateToProps = ({licenseModel: {licenseKeyGroup, licenseModelEditor}}) => {
+ let {licenseKeyGroupsList} = licenseKeyGroup;
+ let {data} = licenseKeyGroup.licenseKeyGroupsEditor;
+ let {vendorName} = licenseModelEditor.data;
+ let isReadOnlyMode = VersionControllerUtils.isReadOnly(licenseModelEditor.data);
+
+ return {
+ vendorName,
+ licenseKeyGroupsList,
+ isReadOnlyMode,
+ isDisplayModal: Boolean(data),
+ isModalInEditMode: Boolean(data && data.id)
+ };
+};
+
+const mapActionsToProps = (dispatch) => {
+ return {
+ onAddLicenseKeyGroupClick: () => LicenseKeyGroupsActionHelper.openLicenseKeyGroupsEditor(dispatch),
+ onEditLicenseKeyGroupClick: licenseKeyGroup => LicenseKeyGroupsActionHelper.openLicenseKeyGroupsEditor(dispatch, {licenseKeyGroup}),
+ onDeleteLicenseKeyGroupClick: licenseKeyGroup => LicenseKeyGroupsActionHelper.openDeleteLicenseAgreementConfirm(dispatch, {licenseKeyGroup})
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(LicenseKeyGroupsListEditorView);
+
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsListEditorView.jsx b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsListEditorView.jsx
new file mode 100644
index 0000000..1ed1d20
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsListEditorView.jsx
@@ -0,0 +1,138 @@
+import React from 'react';
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import Modal from 'nfvo-components/modal/Modal.jsx';
+import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx';
+import ListEditorItemView from 'nfvo-components/listEditor/ListEditorItemView.jsx';
+
+import LicenseKeyGroupsEditor from './LicenseKeyGroupsEditor.js';
+import InputOptions, {other as optionInputOther} from 'nfvo-components/input/inputOptions/InputOptions.jsx';
+import {optionsInputValues} from './LicenseKeyGroupsConstants';
+import LicenseKeyGroupsConfirmationModal from './LicenseKeyGroupsConfirmationModal.jsx';
+
+
+class LicenseKeyGroupsListEditorView extends React.Component {
+ static propTypes = {
+ vendorName: React.PropTypes.string,
+ licenseModelId: React.PropTypes.string.isRequired,
+ licenseKeyGroupsList: React.PropTypes.array,
+ isReadOnlyMode: React.PropTypes.bool.isRequired,
+ isDisplayModal: React.PropTypes.bool,
+ isModalInEditMode: React.PropTypes.bool,
+ onAddLicenseKeyGroupClick: React.PropTypes.func,
+ onEditLicenseKeyGroupClick: React.PropTypes.func,
+ onDeleteLicenseKeyGroupClick: React.PropTypes.func
+ };
+
+ static defaultProps = {
+ licenseKeyGroupsList: []
+ };
+
+ state = {
+ localFilter: ''
+ };
+
+ render() {
+ let {licenseModelId, vendorName, isReadOnlyMode, isDisplayModal, isModalInEditMode} = this.props;
+ let {onAddLicenseKeyGroupClick} = this.props;
+ const {localFilter} = this.state;
+
+ return (
+ <div className='license-key-groups-list-editor'>
+ <ListEditorView
+ title={i18n('License Key Groups for {vendorName} License Model', {vendorName})}
+ plusButtonTitle={i18n('Add License Key Group')}
+ onAdd={onAddLicenseKeyGroupClick}
+ filterValue={localFilter}
+ onFilter={filter => this.setState({localFilter: filter})}
+ isReadOnlyMode={isReadOnlyMode}>
+ {this.filterList().map(licenseKeyGroup => (this.renderLicenseKeyGroupListItem(licenseKeyGroup, isReadOnlyMode)))}
+ </ListEditorView>
+ <Modal show={isDisplayModal} bsSize='large' animation={true} className='license-key-groups-modal'>
+ <Modal.Header>
+ <Modal.Title>{`${isModalInEditMode ? i18n('Edit License Key Group') : i18n('Create New License Key Group')}`}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ {
+ isDisplayModal && (
+ <LicenseKeyGroupsEditor licenseModelId={licenseModelId} isReadOnlyMode={isReadOnlyMode}/>
+ )
+ }
+ </Modal.Body>
+ </Modal>
+ <LicenseKeyGroupsConfirmationModal licenseModelId={licenseModelId}/>
+
+ </div>
+ );
+ }
+
+ filterList() {
+ let {licenseKeyGroupsList} = this.props;
+ let {localFilter} = this.state;
+ if (localFilter.trim()) {
+ const filter = new RegExp(escape(localFilter), 'i');
+ return licenseKeyGroupsList.filter(({name = '', description = '', operationalScope = '', type = ''}) => {
+ return escape(name).match(filter) || escape(description).match(filter) || escape(this.extractValue(operationalScope)).match(filter) || escape(type).match(filter);
+ });
+ }
+ else {
+ return licenseKeyGroupsList;
+ }
+ }
+
+ renderLicenseKeyGroupListItem(licenseKeyGroup, isReadOnlyMode) {
+ let {id, name, description, operationalScope, type} = licenseKeyGroup;
+ let {onEditLicenseKeyGroupClick, onDeleteLicenseKeyGroupClick} = this.props;
+ return (
+ <ListEditorItemView
+ key={id}
+ onSelect={() => onEditLicenseKeyGroupClick(licenseKeyGroup)}
+ onDelete={() => onDeleteLicenseKeyGroupClick(licenseKeyGroup)}
+ className='list-editor-item-view'
+ isReadOnlyMode={isReadOnlyMode}>
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Name')}</div>
+ <div className='text name'>{name}</div>
+ </div>
+
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Operational Scope')}</div>
+ <div className='text operational-scope'>{operationalScope && this.getOperationalScopes(operationalScope)}</div>
+
+ <div className='title'>{i18n('Type')}</div>
+ <div className='text type'>{InputOptions.getTitleByName(optionsInputValues, type)}</div>
+ </div>
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Description')}</div>
+ <div className='text description'>{description}</div>
+ </div>
+ </ListEditorItemView>
+ );
+ }
+
+ getOperationalScopes(operationalScope) {
+ if(operationalScope.choices.toString() === i18n(optionInputOther.OTHER) && operationalScope.other !== '') {
+ return operationalScope.other;
+ }
+ else {
+ let allOpScopes = '';
+ for (let opScope of operationalScope.choices) {
+ allOpScopes += allOpScopes === '' ? InputOptions.getTitleByName(optionsInputValues, opScope) : `, ${InputOptions.getTitleByName(optionsInputValues, opScope)}`;
+ }
+ return allOpScopes;
+ }
+ }
+
+ extractValue(item) {
+ if (item === undefined) {
+ return '';
+ } //TODO fix it later
+
+ return item ? item.choice === optionInputOther.OTHER ? item.other : InputOptions.getTitleByName(optionsInputValues, item.choice) : '';
+ }
+}
+
+export default LicenseKeyGroupsListEditorView;
+
+
+
diff --git a/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsListReducer.js b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsListReducer.js
new file mode 100644
index 0000000..54ce4e3
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/licenseModel/licenseKeyGroups/LicenseKeyGroupsListReducer.js
@@ -0,0 +1,37 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './LicenseKeyGroupsConstants.js';
+export default (state = [], action) => {
+ switch (action.type) {
+ case actionTypes.LICENSE_KEY_GROUPS_LIST_LOADED:
+ return [...action.response.results];
+ case actionTypes.DELETE_LICENSE_KEY_GROUP:
+ return state.filter(licenseKeyGroup => licenseKeyGroup.id !== action.licenseKeyGroupId);
+ case actionTypes.ADD_LICENSE_KEY_GROUP:
+ return [...state, action.licenseKeyGroup];
+ case actionTypes.EDIT_LICENSE_KEY_GROUP:
+ const indexForEdit = state.findIndex(licenseKeyGroup => licenseKeyGroup.id === action.licenseKeyGroup.id);
+ return [...state.slice(0, indexForEdit), action.licenseKeyGroup, ...state.slice(indexForEdit + 1)];
+ default:
+ return state;
+ }
+};
+
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProduct.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProduct.js
new file mode 100644
index 0000000..2dbef6b
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProduct.js
@@ -0,0 +1,321 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import {statusEnum as versionStatusEnum} from 'nfvo-components/panel/versionController/VersionControllerConstants.js';
+import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js';
+import TabulatedEditor from 'src/nfvo-components/editor/TabulatedEditor.jsx';
+
+import {enums} from 'sdc-app/onboarding/OnboardingConstants.js';
+import OnboardingActionHelper from 'sdc-app/onboarding/OnboardingActionHelper.js';
+
+import {navigationItems} from './SoftwareProductConstants.js';
+import SoftwareProductActionHelper from './SoftwareProductActionHelper.js';
+import SoftwareProductComponentsActionHelper from './components/SoftwareProductComponentsActionHelper.js';
+
+const buildComponentNavigationBarGroups = ({componentId, meta}) => {
+ const groups = ([
+ {
+ id: navigationItems.GENERAL + '|' + componentId,
+ name: i18n('General'),
+ disabled: false,
+ meta
+ }, {
+ id: navigationItems.COMPUTE + '|' + componentId,
+ name: i18n('Compute'),
+ disabled: false,
+ meta
+ }, {
+ id: navigationItems.LOAD_BALANCING + '|' + componentId,
+ name: i18n('High Availability & Load Balancing'),
+ disabled: false,
+ meta
+ }, {
+ id: navigationItems.NETWORKS + '|' + componentId,
+ name: i18n('Networks'),
+ disabled: false,
+ meta
+ }, {
+ id: navigationItems.STORAGE + '|' + componentId,
+ name: i18n('Storage'),
+ disabled: false,
+ meta
+ }, {
+ id: navigationItems.PROCESS_DETAILS + '|' + componentId,
+ name: i18n('Process Details'),
+ disabled: false,
+ meta
+ }, {
+ id: navigationItems.MONITORING + '|' + componentId,
+ name: i18n('Monitoring'),
+ disabled: false,
+ meta
+ }
+ ]);
+
+ return groups;
+};
+
+const buildNavigationBarProps = ({softwareProduct, meta, screen, componentId, componentsList, mapOfExpandedIds}) => {
+ const {softwareProductEditor: {data: currentSoftwareProduct = {}}} = softwareProduct;
+ const {id, name} = currentSoftwareProduct;
+ const groups = [{
+ id: id,
+ name: name,
+ items: [
+ {
+ id: navigationItems.VENDOR_SOFTWARE_PRODUCT,
+ name: i18n('Overview'),
+ disabled: false,
+ meta
+ }, {
+ id: navigationItems.GENERAL,
+ name: i18n('General'),
+ disabled: false,
+ meta
+ }, {
+ id: navigationItems.PROCESS_DETAILS,
+ name: i18n('Process Details'),
+ disabled: false,
+ meta
+ }, {
+ id: navigationItems.NETWORKS,
+ name: i18n('Networks'),
+ disabled: false,
+ meta
+ }, {
+ id: navigationItems.ATTACHMENTS,
+ name: i18n('Attachments'),
+ disabled: false,
+ meta
+ }, {
+ id: navigationItems.COMPONENTS,
+ name: i18n('Components'),
+ hidden: componentsList.length <= 0,
+ meta,
+ expanded: mapOfExpandedIds[navigationItems.COMPONENTS] === true && screen !== enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE,
+ items: [
+ ...componentsList.map(({id, displayName}) => ({
+ id: navigationItems.COMPONENTS + '|' + id,
+ name: displayName,
+ meta,
+ expanded: mapOfExpandedIds[navigationItems.COMPONENTS + '|' + id] === true && screen !== enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE,
+ items: buildComponentNavigationBarGroups({componentId: id, meta})
+ }))
+ ]
+ }
+ ]
+ }];
+ let activeItemId = ({
+ [enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE]: navigationItems.VENDOR_SOFTWARE_PRODUCT,
+ [enums.SCREEN.SOFTWARE_PRODUCT_DETAILS]: navigationItems.GENERAL,
+ [enums.SCREEN.SOFTWARE_PRODUCT_ATTACHMENTS]: navigationItems.ATTACHMENTS,
+ [enums.SCREEN.SOFTWARE_PRODUCT_PROCESSES]: navigationItems.PROCESS_DETAILS,
+ [enums.SCREEN.SOFTWARE_PRODUCT_NETWORKS]: navigationItems.NETWORKS,
+ [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENTS]: navigationItems.COMPONENTS
+ })[screen];
+
+ if(componentId) {
+ activeItemId =
+ Object.keys(mapOfExpandedIds).length === 1 && mapOfExpandedIds[navigationItems.COMPONENTS] === true ?
+ navigationItems.COMPONENTS : ({
+ [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_GENERAL]: navigationItems.GENERAL,
+ [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_COMPUTE]: navigationItems.COMPUTE,
+ [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING]: navigationItems.LOAD_BALANCING,
+ [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_NETWORK]: navigationItems.NETWORKS,
+ [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_STORAGE]: navigationItems.STORAGE,
+ [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_PROCESSES]: navigationItems.PROCESS_DETAILS,
+ [enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_MONITORING]: navigationItems.MONITORING
+ })[screen] + '|' + componentId;
+ }
+
+ return {
+ activeItemId, groups
+ };
+};
+
+const buildVersionControllerProps = (softwareProduct) => {
+ const {softwareProductEditor} = softwareProduct;
+ const {data: currentSoftwareProduct = {}, isValidityData = true} = softwareProductEditor;
+
+ const {version, viewableVersions, status: currentStatus, lockingUser} = currentSoftwareProduct;
+ const {status, isCheckedOut} = (currentStatus === versionStatusEnum.CHECK_OUT_STATUS) ?
+ VersionControllerUtils.getCheckOutStatusKindByUserID(currentStatus, lockingUser) :
+ {status: currentStatus, isCheckedOut: false};
+
+ return {
+ status, isCheckedOut, version, viewableVersions,
+ isFormDataValid: isValidityData
+ };
+};
+
+const mapStateToProps = ({softwareProduct}, {currentScreen: {screen, props: {componentId}}}) => {
+ const {softwareProductEditor, softwareProductComponents, softwareProductQuestionnaire} = softwareProduct;
+ const {data: currentSoftwareProduct = {}, mapOfExpandedIds = []} = softwareProductEditor;
+ const {version} = currentSoftwareProduct;
+ const {componentsList = []} = softwareProductComponents;
+ const isReadOnlyMode = VersionControllerUtils.isReadOnly(currentSoftwareProduct);
+ const {qdata} = softwareProductQuestionnaire;
+ let currentComponentMeta = {};
+ if(componentId) {
+ const {componentEditor: {data: componentData = {} , qdata: componentQdata}} = softwareProductComponents;
+ currentComponentMeta = {componentData, componentQdata};
+ }
+ const meta = {softwareProduct: currentSoftwareProduct, qdata, version, isReadOnlyMode, currentComponentMeta};
+ return {
+ versionControllerProps: buildVersionControllerProps(softwareProduct),
+ navigationBarProps: buildNavigationBarProps({softwareProduct, meta, screen, componentId, componentsList, mapOfExpandedIds})
+ };
+};
+
+const autoSaveBeforeNavigate = ({dispatch, screen, softwareProductId, componentId, meta: {isReadOnlyMode, softwareProduct, qdata, currentComponentMeta: {componentData, componentQdata}}}) => {
+ let promise;
+ if (isReadOnlyMode) {
+ promise = Promise.resolve();
+ } else {
+ switch(screen) {
+ case enums.SCREEN.SOFTWARE_PRODUCT_DETAILS:
+ promise = SoftwareProductActionHelper.updateSoftwareProduct(dispatch, {softwareProduct, qdata});
+ break;
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_GENERAL:
+ promise = SoftwareProductComponentsActionHelper.updateSoftwareProductComponent(dispatch, {softwareProductId, vspComponentId: componentId, componentData, qdata: componentQdata});
+ break;
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_COMPUTE:
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_STORAGE:
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_NETWORK:
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_LOAD_BALANCING:
+ promise = SoftwareProductComponentsActionHelper.updateSoftwareProductComponentQuestionnaire(dispatch, {softwareProductId, vspComponentId: componentId, qdata: componentQdata});
+ break;
+ default:
+ promise = Promise.resolve();
+ break;
+ }
+ }
+ return promise;
+};
+
+
+const onComponentNavigate = (dispatch, {id, softwareProductId, version, currentComponentId}) => {
+ const [nextScreen, nextComponentId] = id.split('|');
+ switch(nextScreen) {
+ case navigationItems.COMPONENTS:
+ if(nextComponentId === currentComponentId) {
+ OnboardingActionHelper.navigateToSoftwareProductComponents(dispatch, {softwareProductId});
+ } else {
+ OnboardingActionHelper.navigateToSoftwareProductComponentGeneral(dispatch, {softwareProductId, componentId: nextComponentId, version});
+ }
+ break;
+ case navigationItems.GENERAL:
+ OnboardingActionHelper.navigateToSoftwareProductComponentGeneral(dispatch, {softwareProductId, componentId: nextComponentId, version});
+ break;
+ case navigationItems.COMPUTE:
+ OnboardingActionHelper.navigateToComponentCompute(dispatch, {softwareProductId, componentId: nextComponentId});
+ break;
+ case navigationItems.LOAD_BALANCING:
+ OnboardingActionHelper.navigateToComponentLoadBalancing(dispatch, {softwareProductId, componentId: nextComponentId});
+ break;
+ case navigationItems.NETWORKS:
+ OnboardingActionHelper.navigateToComponentNetwork(dispatch, {softwareProductId, componentId: nextComponentId, version});
+ break;
+ case navigationItems.STORAGE:
+ OnboardingActionHelper.navigateToComponentStorage(dispatch, {softwareProductId, componentId: nextComponentId});
+ break;
+ case navigationItems.PROCESS_DETAILS:
+ OnboardingActionHelper.navigateToSoftwareProductComponentProcesses(dispatch, {softwareProductId, componentId: nextComponentId});
+ break;
+ case navigationItems.MONITORING:
+ OnboardingActionHelper.navigateToSoftwareProductComponentMonitoring(dispatch, {softwareProductId, componentId: nextComponentId});
+ break;
+ }
+};
+
+const mapActionsToProps = (dispatch, {currentScreen: {screen, props: {softwareProductId, componentId: currentComponentId}}}) => {
+
+ const props = {
+ onClose: ({version}) => {
+ if (screen === enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE) {
+ OnboardingActionHelper.navigateToOnboardingCatalog(dispatch);
+ } else {
+ OnboardingActionHelper.navigateToSoftwareProductLandingPage(dispatch, {softwareProductId, version});
+ }
+ },
+ onVersionSwitching: (version) => {
+ OnboardingActionHelper.navigateToSoftwareProductLandingPage(dispatch, {softwareProductId, version});
+ },
+ onToggle: (groups, itemIdToExpand) => groups.map(({items}) => SoftwareProductActionHelper.toggleNavigationItems(dispatch, {items, itemIdToExpand})),
+ onNavigate: ({id, meta}) => {
+ let preNavigate = autoSaveBeforeNavigate({dispatch, screen, meta, softwareProductId, componentId: currentComponentId});
+ preNavigate.then(() => {
+ switch(id) {
+ case navigationItems.VENDOR_SOFTWARE_PRODUCT:
+ OnboardingActionHelper.navigateToSoftwareProductLandingPage(dispatch, {softwareProductId, version: meta.version});
+ break;
+ case navigationItems.GENERAL:
+ OnboardingActionHelper.navigateToSoftwareProductDetails(dispatch, {softwareProductId});
+ break;
+ case navigationItems.PROCESS_DETAILS:
+ OnboardingActionHelper.navigateToSoftwareProductProcesses(dispatch, {softwareProductId, version: meta.version});
+ break;
+ case navigationItems.NETWORKS:
+ OnboardingActionHelper.navigateToSoftwareProductNetworks(dispatch, {softwareProductId, version: meta.version});
+ break;
+ case navigationItems.ATTACHMENTS:
+ OnboardingActionHelper.navigateToSoftwareProductAttachments(dispatch, {softwareProductId});
+ break;
+ case navigationItems.COMPONENTS:
+ OnboardingActionHelper.navigateToSoftwareProductComponents(dispatch, {softwareProductId});
+ break;
+ default:
+ onComponentNavigate(dispatch, {id, softwareProductId, version: meta.version, screen, currentComponentId});
+ break;
+ }
+ });
+ }
+ };
+
+ switch (screen) {
+ case enums.SCREEN.SOFTWARE_PRODUCT_LANDING_PAGE:
+ case enums.SCREEN.SOFTWARE_PRODUCT_ATTACHMENTS:
+ case enums.SCREEN.SOFTWARE_PRODUCT_PROCESSES:
+ case enums.SCREEN.SOFTWARE_PRODUCT_NETWORKS:
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENTS:
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_PROCESSES:
+ case enums.SCREEN.SOFTWARE_PRODUCT_COMPONENT_MONITORING:
+ props.onSave = () => {
+ return Promise.resolve();
+ };
+ break;
+ default:
+ props.onSave = ({softwareProduct, qdata}) => SoftwareProductActionHelper.updateSoftwareProduct(dispatch, {softwareProduct, qdata});
+ break;
+ }
+
+
+ props.onVersionControllerAction = (action) =>
+ SoftwareProductActionHelper.performVCAction(dispatch, {softwareProductId, action}).then(() => {
+ SoftwareProductActionHelper.fetchSoftwareProduct(dispatch, {softwareProductId});
+ });
+
+ return props;
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(TabulatedEditor);
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js
new file mode 100644
index 0000000..d9ed8af
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js
@@ -0,0 +1,333 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
+import Configuration from 'sdc-app/config/Configuration.js';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import LicenseModelActionHelper from 'sdc-app/onboarding/licenseModel/LicenseModelActionHelper.js';
+import LicenseAgreementActionHelper from 'sdc-app/onboarding/licenseModel/licenseAgreement/LicenseAgreementActionHelper.js';
+import FeatureGroupsActionHelper from 'sdc-app/onboarding/licenseModel/featureGroups/FeatureGroupsActionHelper.js';
+
+import {actionTypes} from './SoftwareProductConstants.js';
+import NotificationConstants from 'nfvo-components/notifications/NotificationConstants.js';
+import OnboardingActionHelper from 'sdc-app/onboarding/OnboardingActionHelper.js';
+import SoftwareProductComponentsActionHelper from './components/SoftwareProductComponentsActionHelper.js';
+import {actionsEnum as VersionControllerActionsEnum} from 'nfvo-components/panel/versionController/VersionControllerConstants.js';
+
+function baseUrl() {
+ const restPrefix = Configuration.get('restPrefix');
+ return `${restPrefix}/v1.0/vendor-software-products/`;
+}
+function softwareProductCategoriesUrl() {
+ const restATTPrefix = Configuration.get('restATTPrefix');
+ return `${restATTPrefix}/v1/categories/resources/`;
+}
+
+function uploadFile(vspId, formData) {
+
+ return RestAPIUtil.create(`${baseUrl()}${vspId}/upload`, formData);
+
+}
+
+function putSoftwareProduct(softwareData) {
+ return RestAPIUtil.save(`${baseUrl()}${softwareData.id}`, {
+ name: softwareData.name,
+ description: softwareData.description,
+ category: softwareData.category,
+ subCategory: softwareData.subCategory,
+ vendorId: softwareData.vendorId,
+ vendorName: softwareData.vendorName,
+ licensingVersion: softwareData.licensingVersion,
+ icon: softwareData.icon,
+ licensingData: softwareData.licensingData
+ });
+}
+
+function putSoftwareProductQuestionnaire(vspId, qdata) {
+ return RestAPIUtil.save(`${baseUrl()}${vspId}/questionnaire`, qdata);
+}
+
+function putSoftwareProductAction(id, action) {
+ return RestAPIUtil.save(`${baseUrl()}${id}/actions`, {action: action});
+}
+
+function fetchSoftwareProductList() {
+ return RestAPIUtil.fetch(baseUrl());
+}
+
+function fetchSoftwareProduct(vspId, version) {
+ let versionQuery = version ? `?version=${version}` : '';
+ return RestAPIUtil.fetch(`${baseUrl()}${vspId}${versionQuery}`);
+}
+
+function fetchSoftwareProductQuestionnaire(vspId, version) {
+ let versionQuery = version ? `?version=${version}` : '';
+ return RestAPIUtil.fetch(`${baseUrl()}${vspId}/questionnaire${versionQuery}`);
+}
+
+function objToString(obj) {
+ let str = '';
+ if (obj instanceof Array) {
+ obj.forEach((item) => {
+ str += objToString(item) + '\n';
+ });
+ } else {
+ for (let p in obj) {
+ if (obj.hasOwnProperty(p)) {
+ str += obj[p] + '\n';
+ }
+ }
+ }
+ return str;
+}
+
+function parseUploadErrorMsg(error) {
+ let message = '';
+ for (let key in error) {
+ if (error.hasOwnProperty(key)) {
+ message += objToString(error[key]) + '\n';
+ }
+ }
+ return message;
+}
+
+function fetchSoftwareProductCategories(dispatch) {
+ let handleResponse = response => dispatch({
+ type: actionTypes.SOFTWARE_PRODUCT_CATEGORIES_LOADED,
+ softwareProductCategories: response
+ });
+ return RestAPIUtil.fetch(softwareProductCategoriesUrl())
+ .then(handleResponse)
+ .fail(() => handleResponse(null));
+}
+
+function loadLicensingData(dispatch, {licenseModelId, licensingVersion}) {
+ LicenseAgreementActionHelper.fetchLicenseAgreementList(dispatch, {licenseModelId, version: licensingVersion});
+ FeatureGroupsActionHelper.fetchFeatureGroupsList(dispatch, {licenseModelId, version: licensingVersion});
+}
+
+function getExpandedItemsId(items, itemIdToToggle) {
+ for(let i = 0; i < items.length; i++) {
+ if(items[i].id === itemIdToToggle) {
+ if (items[i].expanded) {
+ return {};
+ } else {
+ return {[itemIdToToggle]: true};
+ }
+ }
+ else if(items[i].items && items[i].items.length > 0) {
+ let mapOfExpandedIds = getExpandedItemsId(items[i].items, itemIdToToggle);
+ if (mapOfExpandedIds !== false) {
+ mapOfExpandedIds[items[i].id] = true;
+ return mapOfExpandedIds;
+ }
+ }
+ }
+ return false;
+}
+
+const SoftwareProductActionHelper = {
+
+ loadSoftwareProductAssociatedData(dispatch) {
+ fetchSoftwareProductCategories(dispatch);
+ LicenseModelActionHelper.fetchFinalizedLicenseModels(dispatch);
+ },
+
+ loadSoftwareProductDetailsData(dispatch, {licenseModelId, licensingVersion}) {
+ SoftwareProductActionHelper.loadSoftwareProductAssociatedData(dispatch);
+ loadLicensingData(dispatch, {licenseModelId, licensingVersion});
+ },
+
+ fetchSoftwareProductList(dispatch) {
+ return fetchSoftwareProductList().then(response => dispatch({
+ type: actionTypes.SOFTWARE_PRODUCT_LIST_LOADED,
+ response
+ }));
+ },
+
+ uploadFile(dispatch, {softwareProductId, formData, failedNotificationTitle}) {
+ Promise.resolve()
+ .then(() => uploadFile(softwareProductId, formData))
+ .then(response => {
+ if (response.status !== 'Success') {
+ throw new Error(parseUploadErrorMsg(response.errors));
+ }
+ })
+ .then(() => {
+ SoftwareProductComponentsActionHelper.fetchSoftwareProductComponents(dispatch, {softwareProductId});
+ OnboardingActionHelper.navigateToSoftwareProductAttachments(dispatch, {softwareProductId});
+ SoftwareProductActionHelper.fetchSoftwareProduct(dispatch, {softwareProductId});
+ })
+ .catch(error => {
+ dispatch({
+ type: NotificationConstants.NOTIFY_ERROR,
+ data: {title: failedNotificationTitle, msg: error.message}
+ });
+ });
+ },
+
+ uploadConfirmation(dispatch, {softwareProductId, formData, failedNotificationTitle}) {
+ dispatch({
+ type: actionTypes.softwareProductEditor.UPLOAD_CONFIRMATION,
+ uploadData: {
+ softwareProductId,
+ formData,
+ failedNotificationTitle
+ }
+ });
+ },
+ hideUploadConfirm (dispatch) {
+ dispatch({
+ type: actionTypes.softwareProductEditor.UPLOAD_CONFIRMATION
+ });
+ },
+ updateSoftwareProduct(dispatch, {softwareProduct, qdata}) {
+ return Promise.all([
+ SoftwareProductActionHelper.updateSoftwareProductData(dispatch, {softwareProduct}).then(
+ () => dispatch({
+ type: actionTypes.SOFTWARE_PRODUCT_LIST_EDIT,
+ payload: {softwareProduct}
+ })
+ ),
+ SoftwareProductActionHelper.updateSoftwareProductQuestionnaire(dispatch, {
+ softwareProductId: softwareProduct.id,
+ qdata
+ })
+ ]);
+ },
+
+ updateSoftwareProductData(dispatch, {softwareProduct}) {
+ return putSoftwareProduct(softwareProduct);
+ },
+
+ updateSoftwareProductQuestionnaire(dispatch, {softwareProductId, qdata}) {
+ return putSoftwareProductQuestionnaire(softwareProductId, qdata);
+ },
+
+ softwareProductEditorDataChanged(dispatch, {deltaData}) {
+ dispatch({
+ type: actionTypes.softwareProductEditor.DATA_CHANGED,
+ deltaData
+ });
+ },
+
+ softwareProductQuestionnaireUpdate(dispatch, {data}) {
+ dispatch({
+ type: actionTypes.SOFTWARE_PRODUCT_QUESTIONNAIRE_UPDATE,
+ payload: {qdata: data}
+ });
+ },
+
+ softwareProductEditorVendorChanged(dispatch, {deltaData}) {
+ LicenseAgreementActionHelper.fetchLicenseAgreementList(dispatch, {licenseModelId: deltaData.vendorId, version: deltaData.licensingVersion});
+ FeatureGroupsActionHelper.fetchFeatureGroupsList(dispatch, {licenseModelId: deltaData.vendorId, version: deltaData.licensingVersion});
+ SoftwareProductActionHelper.softwareProductEditorDataChanged(dispatch, {deltaData});
+ },
+
+ setIsValidityData(dispatch, {isValidityData}) {
+ dispatch({
+ type: actionTypes.softwareProductEditor.IS_VALIDITY_DATA_CHANGED,
+ isValidityData
+ });
+ },
+
+ addSoftwareProduct(dispatch, {softwareProduct}) {
+ dispatch({
+ type: actionTypes.ADD_SOFTWARE_PRODUCT,
+ softwareProduct
+ });
+ },
+
+ fetchSoftwareProduct(dispatch, {softwareProductId, version}) {
+ return Promise.all([
+ fetchSoftwareProduct(softwareProductId, version).then(response => {
+ dispatch({
+ type: actionTypes.SOFTWARE_PRODUCT_LOADED,
+ response
+ });
+ return response;
+ }),
+ fetchSoftwareProductQuestionnaire(softwareProductId, version).then(response => {
+ dispatch({
+ type: actionTypes.SOFTWARE_PRODUCT_QUESTIONNAIRE_UPDATE,
+ payload: {
+ qdata: response.data ? JSON.parse(response.data) : {},
+ qschema: JSON.parse(response.schema)
+ }
+ });
+ })
+ ]);
+ },
+
+ performVCAction(dispatch, {softwareProductId, action}) {
+ if (action === VersionControllerActionsEnum.SUBMIT) {
+ return putSoftwareProductAction(softwareProductId, action).then(() => {
+ return putSoftwareProductAction(softwareProductId, VersionControllerActionsEnum.CREATE_PACKAGE).then(() => {
+ dispatch({
+ type: NotificationConstants.NOTIFY_SUCCESS,
+ data: {
+ title: i18n('Submit Succeeded'),
+ msg: i18n('This software product successfully submitted'),
+ timeout: 2000
+ }
+ });
+ fetchSoftwareProduct(softwareProductId).then(response => {
+ dispatch({
+ type: actionTypes.SOFTWARE_PRODUCT_LOADED,
+ response
+ });
+ });
+ });
+ }, error => dispatch({
+ type: NotificationConstants.NOTIFY_ERROR,
+ data: {title: i18n('Submit Failed'), validationResponse: error.responseJSON}
+ }));
+ }
+ else {
+ return putSoftwareProductAction(softwareProductId, action).then(() => {
+ fetchSoftwareProduct(softwareProductId).then(response => {
+ dispatch({
+ type: actionTypes.SOFTWARE_PRODUCT_LOADED,
+ response
+ });
+ });
+ });
+ }
+ },
+
+ switchVersion(dispatch, {softwareProductId, licenseModelId, version}) {
+ OnboardingActionHelper.navigateToSoftwareProductLandingPage(dispatch, {softwareProductId, licenseModelId, version});
+ },
+
+ toggleNavigationItems(dispatch, {items, itemIdToExpand}) {
+ let mapOfExpandedIds = getExpandedItemsId(items, itemIdToExpand);
+ dispatch({
+ type: actionTypes.TOGGLE_NAVIGATION_ITEM,
+ mapOfExpandedIds
+ });
+ },
+
+ /** for the next verision */
+ addComponent(dispatch) {
+ return dispatch;
+ }
+};
+
+export default SoftwareProductActionHelper;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductCategoriesHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductCategoriesHelper.js
new file mode 100644
index 0000000..812afe5
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductCategoriesHelper.js
@@ -0,0 +1,35 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+export default {
+
+ getCurrentCategoryOfSubCategory(selectedSubCategory, softwareProductCategories) {
+ let category, subCategory;
+ for (var i = 0; i < softwareProductCategories.length; i++) {
+ let {subcategories = []} = softwareProductCategories[i];
+ subCategory = subcategories.find(sub => sub.uniqueId === selectedSubCategory);
+ if (subCategory) {
+ category = softwareProductCategories[i].uniqueId;
+ break;
+ }
+ }
+ return category;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js
new file mode 100644
index 0000000..5f10c27
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js
@@ -0,0 +1,53 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export const actionTypes = keyMirror({
+ SOFTWARE_PRODUCT_LOADED: null,
+ SOFTWARE_PRODUCT_LIST_LOADED: null,
+ SOFTWARE_PRODUCT_LIST_EDIT: null,
+ SOFTWARE_PRODUCT_CATEGORIES_LOADED: null,
+ SOFTWARE_PRODUCT_QUESTIONNAIRE_UPDATE: null,
+ ADD_SOFTWARE_PRODUCT: null,
+ TOGGLE_NAVIGATION_ITEM: null,
+
+ softwareProductEditor: {
+ OPEN: null,
+ CLOSE: null,
+ DATA_CHANGED: null,
+ IS_VALIDITY_DATA_CHANGED: null,
+ UPLOAD_CONFIRMATION: null
+ }
+});
+
+export const navigationItems = keyMirror({
+ VENDOR_SOFTWARE_PRODUCT: 'Vendor Software Product',
+ GENERAL: 'General',
+ PROCESS_DETAILS: 'Process Details',
+ NETWORKS: 'Networks',
+ ATTACHMENTS: 'Attachments',
+ COMPONENTS: 'Components',
+
+ COMPUTE: 'Compute',
+ LOAD_BALANCING: 'Load Balancing',
+ STORAGE: 'Storage',
+ MONITORING: 'Monitoring'
+});
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductListReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductListReducer.js
new file mode 100644
index 0000000..6d1db16
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductListReducer.js
@@ -0,0 +1,35 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './SoftwareProductConstants.js';
+
+export default (state = [], action) => {
+ switch (action.type) {
+ case actionTypes.SOFTWARE_PRODUCT_LIST_LOADED:
+ return [...action.response.results];
+ case actionTypes.SOFTWARE_PRODUCT_LIST_EDIT:
+ const indexForEdit = state.findIndex(vsp => vsp.id === action.payload.softwareProduct.id);
+ return [...state.slice(0, indexForEdit), action.payload.softwareProduct, ...state.slice(indexForEdit + 1)];
+ case actionTypes.ADD_SOFTWARE_PRODUCT:
+ return [...state, action.softwareProduct];
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductReducer.js
new file mode 100644
index 0000000..784ac9d
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductReducer.js
@@ -0,0 +1,80 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {combineReducers} from 'redux';
+import {actionTypes} from './SoftwareProductConstants.js';
+import SoftwareProductAttachmentsReducer from './attachments/SoftwareProductAttachmentsReducer.js';
+import SoftwareProductCreationReducer from './creation/SoftwareProductCreationReducer.js';
+import SoftwareProductDetailsReducer from './details/SoftwareProductDetailsReducer.js';
+import SoftwareProductProcessesListReducer from './processes/SoftwareProductProcessesListReducer.js';
+import SoftwareProductProcessesEditorReducer from './processes/SoftwareProductProcessesEditorReducer.js';
+import SoftwareProductNetworksListReducer from './networks/SoftwareProductNetworksListReducer.js';
+import SoftwareProductComponentsListReducer from './components/SoftwareProductComponentsListReducer.js';
+import SoftwareProductComponentEditorReducer from './components/SoftwareProductComponentEditorReducer.js';
+import {actionTypes as processesActionTypes} from './processes/SoftwareProductProcessesConstants.js';
+import SoftwareProductComponentProcessesListReducer from './components/processes/SoftwareProductComponentProcessesListReducer.js';
+import SoftwareProductComponentProcessesEditorReducer from './components/processes/SoftwareProductComponentProcessesEditorReducer.js';
+import {actionTypes as componentProcessesActionTypes} from './components/processes/SoftwareProductComponentProcessesConstants.js';
+import SoftwareProductComponentsNICListReducer from './components/network/SoftwareProductComponentsNICListReducer.js';
+import SoftwareProductComponentsNICEditorReducer from './components/network/SoftwareProductComponentsNICEditorReducer.js';
+import SoftwareProductComponentsMonitoringReducer from './components/monitoring/SoftwareProductComponentsMonitoringReducer.js';
+
+export default combineReducers({
+ softwareProductAttachments: SoftwareProductAttachmentsReducer,
+ softwareProductCreation: SoftwareProductCreationReducer,
+ softwareProductEditor: SoftwareProductDetailsReducer,
+ softwareProductProcesses: combineReducers({
+ processesList: SoftwareProductProcessesListReducer,
+ processesEditor: SoftwareProductProcessesEditorReducer,
+ processToDelete: (state = false, action) => action.type === processesActionTypes.SOFTWARE_PRODUCT_PROCESS_DELETE_CONFIRM ? action.processToDelete : state
+ }),
+ softwareProductNetworks: combineReducers({
+ networksList: SoftwareProductNetworksListReducer
+ }),
+ softwareProductComponents: combineReducers({
+ componentsList: SoftwareProductComponentsListReducer,
+ componentEditor: SoftwareProductComponentEditorReducer,
+ componentProcesses: combineReducers({
+ processesList: SoftwareProductComponentProcessesListReducer,
+ processesEditor: SoftwareProductComponentProcessesEditorReducer,
+ processToDelete: (state = false, action) => action.type === componentProcessesActionTypes.SOFTWARE_PRODUCT_PROCESS_DELETE_COMPONENTS_CONFIRM ? action.processToDelete : state,
+ }),
+ network: combineReducers({
+ nicList: SoftwareProductComponentsNICListReducer,
+ nicEditor: SoftwareProductComponentsNICEditorReducer
+ }),
+ monitoring: SoftwareProductComponentsMonitoringReducer
+ }),
+ softwareProductCategories: (state = [], action) => {
+ if (action.type === actionTypes.SOFTWARE_PRODUCT_CATEGORIES_LOADED) {
+ return action.softwareProductCategories;
+ }
+ return state;
+ },
+ softwareProductQuestionnaire: (state = {}, action) => {
+ if (action.type === actionTypes.SOFTWARE_PRODUCT_QUESTIONNAIRE_UPDATE) {
+ return {
+ ...state,
+ ...action.payload
+ };
+ }
+ return state;
+ }
+});
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachments.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachments.js
new file mode 100644
index 0000000..a4b95a4
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachments.js
@@ -0,0 +1,43 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import SoftwareProductAttachmentsView from './SoftwareProductAttachmentsView.jsx';
+import SoftwareProductAttachmentsActionHelper from './SoftwareProductAttachmentsActionHelper.js';
+
+export const mapStateToProps = ({softwareProduct: {softwareProductAttachments}}) => {
+ let {attachmentsTree, hoveredNode, selectedNode, errorList} = softwareProductAttachments;
+ return {
+ attachmentsTree,
+ hoveredNode,
+ selectedNode,
+ errorList
+ };
+};
+
+const mapActionsToProps = (dispatch) => {
+ return {
+ toggleExpanded: (path) => SoftwareProductAttachmentsActionHelper.toggleExpanded(dispatch, {path}),
+ onSelectNode: (nodeName) => SoftwareProductAttachmentsActionHelper.onSelectNode(dispatch, {nodeName}),
+ onUnselectNode: () => SoftwareProductAttachmentsActionHelper.onUnselectNode(dispatch)
+ };
+};
+
+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
new file mode 100644
index 0000000..a7f7a51
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsActionHelper.js
@@ -0,0 +1,44 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './SoftwareProductAttachmentsConstants.js';
+
+export default {
+
+ toggleExpanded(dispatch, {path}) {
+ dispatch({
+ type: actionTypes.TOGGLE_EXPANDED,
+ path
+ });
+ },
+
+ onSelectNode(dispatch, {nodeName}) {
+ dispatch({
+ type: actionTypes.SELECTED_NODE,
+ nodeName
+ });
+ },
+
+ onUnselectNode(dispatch) {
+ dispatch({
+ type: actionTypes.UNSELECTED_NODE
+ });
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsConstants.js
new file mode 100644
index 0000000..33af476
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsConstants.js
@@ -0,0 +1,55 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+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
+});
+
+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')
+});
+
+export const nodeTypes = keyMirror({
+ 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
+});
+
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsReducer.js
new file mode 100644
index 0000000..5c5567b
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsReducer.js
@@ -0,0 +1,199 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes as softwareProductsActionTypes} from 'sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js';
+import {actionTypes} from './SoftwareProductAttachmentsConstants.js';
+
+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 mapArtifactsData = ({fileName, errors}) => ({
+ name: fileName,
+ type: 'artifact',
+ 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) : [])
+ ]
+});
+
+function createErrorList(node, parent, deep = 0, errorList = []) {
+ if (node.errors) {
+ errorList.push(...node.errors.map((error) => ({
+ errorLevel: 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 => {
+ let {HEAT, volume, network, artifacts, other} = validationData.importStructure || {};
+ return {
+ children: [
+ {
+ name: 'HEAT',
+ expanded: true,
+ type: 'heat',
+ children: (HEAT ? HEAT.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;
+};
+
+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;
+ });
+
+ 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;
+ let attachmentsTree = currentSoftwareProduct.validationData ? mapValidationDataToTree(currentSoftwareProduct.validationData) : {};
+ let errorList = createErrorList(attachmentsTree);
+ return {
+ ...state,
+ attachmentsTree,
+ errorList
+ };
+ 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: undefined
+ };
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsView.jsx
new file mode 100644
index 0000000..c52999c
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/attachments/SoftwareProductAttachmentsView.jsx
@@ -0,0 +1,182 @@
+import React from 'react';
+import FontAwesome from 'react-fontawesome';
+import classNames from 'classnames';
+import Collapse from 'react-bootstrap/lib/Collapse.js';
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import {nodeTypes, mouseActions} from './SoftwareProductAttachmentsConstants';
+
+const typeToIcon = Object.freeze({
+ heat: 'building-o',
+ volume: 'database',
+ network: 'cloud',
+ artifact: 'gear',
+ env: 'server',
+ other: 'cube'
+});
+
+const leftPanelWidth = 250;
+
+class SoftwareProductAttachmentsView extends React.Component {
+
+ static propTypes = {
+ attachmentsTree: React.PropTypes.object.isRequired
+ };
+ state = {
+ treeWidth: '400'
+ };
+
+ render() {
+ let {attachmentsTree, errorList} = this.props;
+ let {treeWidth} = this.state;
+ return (
+ <div className='software-product-attachments'>
+ <div className='software-product-attachments-tree' style={{'width' : treeWidth + 'px'}}>
+ <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='software-product-attachments-separator'/>
+
+ <div className='software-product-attachments-error-list'>
+ {errorList.length ? this.renderErrorList(errorList) : <div className='no-errors'>{attachmentsTree.children ?
+ i18n('VALIDATION SUCCESS') : i18n('THERE IS NO HEAT DATA TO PRESENT') }</div>}
+ </div>
+ </div>
+ );
+ }
+
+ renderNode(node, path) {
+ let isFolder = node.children && node.children.length > 0;
+ let {onSelectNode} = this.props;
+ return (
+ <div key={node.name} className='tree-block-inside'>
+ {
+ <div onDoubleClick={() => this.props.toggleExpanded(path)} className={this.getTreeRowClassName(node.name)}>
+ {
+ isFolder &&
+ <div onClick={() => this.props.toggleExpanded(path)} className={classNames('tree-node-expander', {'tree-node-expander-collapsed': !node.expanded})}>
+ <FontAwesome name='caret-down'/>
+ </div>
+ }
+ {
+
+ <span className='tree-node-icon'>
+ <FontAwesome name={typeToIcon[node.type]}/>
+ </span>
+ }
+ {
+
+ <span onClick={() => onSelectNode(node.name)} className={this.getTreeTextClassName(node)}>
+ {node.name}
+ </span>
+ }
+ </div>
+ }
+ {
+ isFolder &&
+ <Collapse in={node.expanded}>
+ <div className='tree-node-children'>
+ {
+ node.children.map((child, ind) => this.renderNode(child, [...path, ind]))
+ }
+ </div>
+ </Collapse>
+ }
+ </div>
+ );
+ }
+
+ createErrorList(errorList, node, parent) {
+ if (node.errors) {
+ node.errors.forEach(error => errorList.push({
+ error,
+ name: node.name,
+ parentName: parent.name,
+ type: node.type
+ }));
+ }
+ if (node.children && node.children.length) {
+ node.children.map((child) => this.createErrorList(errorList, child, node));
+ }
+ }
+
+ renderErrorList(errors) {
+ let prevError = {};
+ let {selectedNode} = this.props;
+ return errors.map(error => {
+ let isSameNodeError = error.name === prevError.name && error.parentName === prevError.parentName;
+ prevError = error;
+
+ return (
+ <div
+ key={error.name + error.errorMessage + error.parentName}
+
+ onClick={() => this.selectNode(error.name)}
+ className={classNames('error-item', {'clicked': selectedNode === error.name, 'shifted': !isSameNodeError})}>
+ <span className={classNames('error-item-file-type', {'strong': !isSameNodeError})}>
+ {
+ error.hasParent ?
+ i18n('{type} {name} in {parentName}: ', {
+ type: nodeTypes[error.type],
+ name: error.name,
+ parentName: error.parentName
+ }) :
+ i18n('{type} {name}: ', {
+ type: nodeTypes[error.type],
+ name: error.name
+ })
+ }
+ </span>
+ <span className={`error-item-file-type ${error.errorLevel}`}> {error.errorMessage} </span>
+ </div>
+ );
+ });
+ }
+
+ selectNode(currentSelectedNode) {
+ let {onUnselectNode, onSelectNode, selectedNode} = this.props;
+ if (currentSelectedNode !== selectedNode) {
+ onSelectNode(currentSelectedNode);
+ }else{
+ onUnselectNode();
+ }
+
+ }
+
+ getTreeRowClassName(name) {
+ let {hoveredNode, selectedNode} = this.props;
+ return classNames({
+ 'tree-node-row': true,
+ 'tree-node-selected': name === hoveredNode,
+ 'tree-node-clicked': name === selectedNode
+ });
+ }
+
+ getTreeTextClassName(node) {
+ let {selectedNode} = this.props;
+ return classNames({
+ 'tree-element-text': true,
+ 'error-status': node.errors,
+ 'error-status-selected': node.name === selectedNode
+ });
+ }
+
+ 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);
+ }
+ }
+}
+
+export default SoftwareProductAttachmentsView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentEditorReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentEditorReducer.js
new file mode 100644
index 0000000..3b8bc4f
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentEditorReducer.js
@@ -0,0 +1,47 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './SoftwareProductComponentsConstants.js';
+
+export default (state = {}, action) => {
+ switch (action.type) {
+ case actionTypes.COMPONENT_UPDATE:
+ return {
+ ...state,
+ data: action.component
+ };
+ case actionTypes.COMPONENT_QUESTIONNAIRE_UPDATE:
+ return {
+ ...state,
+ qdata: action.payload.qdata || state.qdata,
+ qschema: action.payload.qschema || state.qschema
+ };
+ case actionTypes.COMPONENT_DATA_CHANGED:
+ return {
+ ...state,
+ data: {
+ ...state.data,
+ ...action.deltaData
+ }
+ };
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsActionHelper.js
new file mode 100644
index 0000000..e53b2ec
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsActionHelper.js
@@ -0,0 +1,129 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
+import Configuration from 'sdc-app/config/Configuration.js';
+
+import {actionTypes} from './SoftwareProductComponentsConstants.js';
+
+function baseUrl(softwareProductId) {
+ const restPrefix = Configuration.get('restPrefix');
+ return `${restPrefix}/v1.0/vendor-software-products/${softwareProductId}/components`;
+}
+
+function fetchSoftwareProductComponents(softwareProductId, version) {
+ let versionQuery = version ? `?version=${version}` : '';
+ return RestAPIUtil.fetch(`${baseUrl(softwareProductId)}${versionQuery}`);
+}
+
+function putSoftwareProductComponentQuestionnaire(softwareProductId, vspComponentId, vspComponent) {
+ return RestAPIUtil.save(`${baseUrl(softwareProductId)}/${vspComponentId}/questionnaire`, vspComponent);
+}
+
+function fetchSoftwareProductComponentQuestionnaire(softwareProductId, vspComponentId, version){
+ let versionQuery = version ? `?version=${version}` : '';
+ return RestAPIUtil.fetch(`${baseUrl(softwareProductId)}/${vspComponentId}/questionnaire${versionQuery}`);
+}
+
+function fetchSoftwareProductComponent(softwareProductId, vspComponentId, version){
+ let versionQuery = version ? `?version=${version}` : '';
+ return RestAPIUtil.fetch(`${baseUrl(softwareProductId)}/${vspComponentId}${versionQuery}`);
+}
+
+function putSoftwareProductComponent(softwareProductId, vspComponentId, vspComponent) {
+ return RestAPIUtil.save(`${baseUrl(softwareProductId)}/${vspComponentId}`, {
+ name: vspComponent.name,
+ displayName: vspComponent.displayName,
+ description: vspComponent.description
+ });
+}
+
+const SoftwareProductComponentsActionHelper = {
+ fetchSoftwareProductComponents(dispatch, {softwareProductId, version}) {
+ return fetchSoftwareProductComponents(softwareProductId, version).then(response => {
+ dispatch({
+ type: actionTypes.COMPONENTS_LIST_UPDATE,
+ componentsList: response.results
+ });
+ });
+ },
+
+ componentDataChanged(dispatch, {deltaData}) {
+ dispatch({
+ type: actionTypes.COMPONENT_DATA_CHANGED,
+ deltaData
+ });
+ },
+
+
+ updateSoftwareProductComponent(dispatch, {softwareProductId, vspComponentId, componentData, qdata}) {
+ return Promise.all([
+ SoftwareProductComponentsActionHelper.updateSoftwareProductComponentQuestionnaire(dispatch, {softwareProductId, vspComponentId, qdata}),
+ SoftwareProductComponentsActionHelper.updateSoftwareProductComponentData(dispatch, {softwareProductId, vspComponentId, componentData})
+ ]);
+ },
+
+ updateSoftwareProductComponentQuestionnaire(dispatch, {softwareProductId, vspComponentId, qdata}) {
+ return putSoftwareProductComponentQuestionnaire(softwareProductId, vspComponentId, qdata);
+ },
+
+ updateSoftwareProductComponentData(dispatch, {softwareProductId, vspComponentId, componentData}) {
+ return putSoftwareProductComponent(softwareProductId, vspComponentId, componentData).then(() => dispatch({
+ type: actionTypes.COMPONENTS_LIST_EDIT,
+ component: {
+ id: vspComponentId,
+ ...componentData
+ }
+ }));
+ },
+
+
+ fetchSoftwareProductComponentQuestionnaire(dispatch, {softwareProductId, vspComponentId, version}) {
+ return fetchSoftwareProductComponentQuestionnaire(softwareProductId, vspComponentId, version).then(response => {
+ dispatch({
+ type: actionTypes.COMPONENT_QUESTIONNAIRE_UPDATE,
+ payload: {
+ qdata: response.data ? JSON.parse(response.data) : {},
+ qschema: JSON.parse(response.schema)
+ }
+ });
+ });
+ },
+
+ fetchSoftwareProductComponent(dispatch, {softwareProductId, vspComponentId, version}) {
+ return fetchSoftwareProductComponent(softwareProductId, vspComponentId, version).then(response => {
+ dispatch({
+ type: actionTypes.COMPONENT_UPDATE,
+ component: response.data
+ });
+ });
+ },
+
+ componentQuestionnaireUpdated(dispatch, {data}) {
+ dispatch({
+ type: actionTypes.COMPONENT_QUESTIONNAIRE_UPDATE,
+ payload: {
+ qdata: data
+ }
+ });
+ },
+};
+
+export default SoftwareProductComponentsActionHelper;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsConstants.js
new file mode 100644
index 0000000..dee517e
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsConstants.js
@@ -0,0 +1,46 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export const actionTypes = keyMirror({
+ COMPONENTS_LIST_UPDATE: null,
+ COMPONENTS_LIST_EDIT: null,
+ COMPONENT_UPDATE: null,
+ COMPONENT_DATA_CHANGED: null,
+ COMPONENT_QUESTIONNAIRE_UPDATE: null
+});
+
+export const storageConstants = keyMirror({
+ backupType: {
+ ON_SITE: 'OnSite',
+ OFF_SITE: 'OffSite'
+ }
+});
+
+export const navigationItems = keyMirror({
+ STORAGE: 'Storage',
+ PROCESS_DETAILS: 'Process Details',
+ MONITORING: 'Monitoring',
+ NETWORK: 'Network',
+ COMPUTE: 'Compute',
+ NETWORK: 'Network',
+ LOAD_BALANCING: 'High Availability & Load Balancing'
+});
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsList.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsList.js
new file mode 100644
index 0000000..f1c1ed1
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsList.js
@@ -0,0 +1,48 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import SoftwareProductComponentsListView from './SoftwareProductComponentsListView.jsx';
+import OnboardingActionHelper from 'sdc-app/onboarding/OnboardingActionHelper.js';
+import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js';
+
+
+const mapStateToProps = ({softwareProduct}) => {
+ let {softwareProductEditor: {data: currentSoftwareProduct}, softwareProductComponents} = softwareProduct;
+ let {componentsList} = softwareProductComponents;
+ let isReadOnlyMode = VersionControllerUtils.isReadOnly(currentSoftwareProduct);
+
+ return {
+ currentSoftwareProduct,
+ isReadOnlyMode,
+ componentsList
+ };
+};
+
+
+const mapActionToProps = (dispatch, {version}) => {
+ return {
+ onComponentSelect: ({id: softwareProductId, componentId}) => {
+ OnboardingActionHelper.navigateToSoftwareProductComponentGeneralAndUpdateLeftPanel(dispatch, {softwareProductId, componentId, version });
+ }
+ };
+};
+
+export default connect(mapStateToProps, mapActionToProps, null, {withRef: true})(SoftwareProductComponentsListView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsListReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsListReducer.js
new file mode 100644
index 0000000..b91362a
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsListReducer.js
@@ -0,0 +1,33 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './SoftwareProductComponentsConstants.js';
+
+export default (state = [], action) => {
+ switch (action.type) {
+ case actionTypes.COMPONENTS_LIST_UPDATE:
+ return [...action.componentsList];
+ case actionTypes.COMPONENTS_LIST_EDIT:
+ const indexForEdit = state.findIndex(component => component.id === action.component.id);
+ return [...state.slice(0, indexForEdit), action.component, ...state.slice(indexForEdit + 1)];
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsListView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsListView.jsx
new file mode 100644
index 0000000..0c0ba0f
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsListView.jsx
@@ -0,0 +1,89 @@
+import React from 'react';
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx';
+import ListEditorItemView from 'nfvo-components/listEditor/ListEditorItemView.jsx';
+
+const ComponentPropType = React.PropTypes.shape({
+ id: React.PropTypes.string,
+ name: React.PropTypes.string,
+ displayName: React.PropTypes.string,
+ description: React.PropTypes.string
+});
+
+class SoftwareProductComponentsListView extends React.Component {
+
+ state = {
+ localFilter: ''
+ };
+
+ static propTypes = {
+ isReadOnlyMode: React.PropTypes.bool,
+ componentsList: React.PropTypes.arrayOf(ComponentPropType),
+ onComponentSelect: React.PropTypes.func
+ };
+
+ render() {
+ let {componentsList = []} = this.props;
+ return (
+ <div className=''>
+ {
+ componentsList.length > 0 && this.renderComponents()
+ }
+ </div>
+ );
+ }
+
+ renderComponents() {
+ const {localFilter} = this.state;
+ let {isReadOnlyMode} = this.props;
+
+ return (
+ <ListEditorView
+ title={i18n('Virtual Function Components')}
+ filterValue={localFilter}
+ placeholder={i18n('Filter Components')}
+ onFilter={filter => this.setState({localFilter: filter})}
+ isReadOnlyMode={isReadOnlyMode}>
+ {this.filterList().map(component => this.renderComponentsListItem(component))}
+ </ListEditorView>
+ );
+ }
+
+ renderComponentsListItem(component) {
+ let {id: componentId, name, displayName, description = ''} = component;
+ let {currentSoftwareProduct: {id}, onComponentSelect} = this.props;
+ return (
+ <ListEditorItemView
+ key={name + Math.floor(Math.random() * (100 - 1) + 1).toString()}
+ className='list-editor-item-view'
+ onSelect={() => onComponentSelect({id, componentId})}>
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Component')}</div>
+ <div className='name'>{displayName}</div>
+ </div>
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Description')}</div>
+ <div className='description'>{description}</div>
+ </div>
+ </ListEditorItemView>
+ );
+ }
+
+ filterList() {
+ let {componentsList = []} = this.props;
+
+ let {localFilter} = this.state;
+ if (localFilter.trim()) {
+ const filter = new RegExp(escape(localFilter), 'i');
+ return componentsList.filter(({displayName = '', description = ''}) => {
+ return escape(displayName).match(filter) || escape(description).match(filter);
+ });
+ }
+ else {
+ return componentsList;
+ }
+ }
+}
+
+export default SoftwareProductComponentsListView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/compute/SoftwareProductComponentCompute.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/compute/SoftwareProductComponentCompute.js
new file mode 100644
index 0000000..7ac1c70
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/compute/SoftwareProductComponentCompute.js
@@ -0,0 +1,52 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import SoftwareProductComponentComputeView from './SoftwareProductComponentComputeView.jsx';
+import SoftwareProductComponentsActionHelper from 'sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsActionHelper.js';
+import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js';
+
+
+const mapStateToProps = ({softwareProduct}) => {
+ let {softwareProductEditor: {data: currentVSP}, softwareProductComponents} = softwareProduct;
+ let {componentEditor: {qdata, qschema}} = softwareProductComponents;
+ let isReadOnlyMode = VersionControllerUtils.isReadOnly(currentVSP);
+
+ let minNumberOfVMsSelectedByUser = 0;
+ if(qdata && qdata.compute && qdata.compute.numOfVMs){
+ minNumberOfVMsSelectedByUser = qdata.compute.numOfVMs.minimum || 0;
+ }
+
+ return {
+ qdata,
+ qschema,
+ isReadOnlyMode,
+ minNumberOfVMsSelectedByUser
+ };
+};
+
+const mapActionToProps = (dispatch, {softwareProductId, componentId}) => {
+ return {
+ onQDataChanged: ({data}) => SoftwareProductComponentsActionHelper.componentQuestionnaireUpdated(dispatch, {data}),
+ onSubmit: ({qdata}) =>{ return SoftwareProductComponentsActionHelper.updateSoftwareProductComponentQuestionnaire(dispatch, {softwareProductId, vspComponentId: componentId, qdata});}
+ };
+};
+
+export default connect(mapStateToProps, mapActionToProps, null, {withRef: true}) (SoftwareProductComponentComputeView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/compute/SoftwareProductComponentComputeView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/compute/SoftwareProductComponentComputeView.jsx
new file mode 100644
index 0000000..3bad147
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/compute/SoftwareProductComponentComputeView.jsx
@@ -0,0 +1,129 @@
+import React from 'react';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx';
+import ValidationInput from'nfvo-components/input/validation/ValidationInput.jsx';
+
+
+class SoftwareProductComponentComputeView extends React.Component {
+
+ static propTypes = {
+ qdata: React.PropTypes.object,
+ qschema: React.PropTypes.object,
+ isReadOnlyMode: React.PropTypes.bool,
+ minNumberOfVMsSelectedByUser: React.PropTypes.number,
+ onQDataChanged: React.PropTypes.func.isRequired,
+ onSubmit: React.PropTypes.func.isRequired
+ };
+
+ render() {
+ let {qdata, qschema, isReadOnlyMode, minNumberOfVMsSelectedByUser, onQDataChanged, onSubmit} = this.props;
+
+ return (
+ <div className='vsp-component-questionnaire-view'>
+ <ValidationForm
+ ref='computeValidationForm'
+ hasButtons={false}
+ onSubmit={() => onSubmit({qdata})}
+ className='component-questionnaire-validation-form'
+ isReadOnlyMode={isReadOnlyMode}
+ onDataChanged={onQDataChanged}
+ data={qdata}
+ schema={qschema}>
+
+ <div className='section-title'>{i18n('VM Sizing')}</div>
+ <div className='rows-section'>
+ <div className='row-flex-components input-row'>
+ <div className='single-col'>
+ <ValidationInput
+ type='text'
+ label={i18n('Number of CPUs')}
+ pointer={'/compute/vmSizing/numOfCPUs'}/>
+ </div>
+ <div className='single-col'>
+ <ValidationInput
+ type='text'
+ label={i18n('File System Size (GB)')}
+ pointer={'/compute/vmSizing/fileSystemSizeGB'}/>
+ </div>
+ <div className='single-col'>
+ <ValidationInput
+ type='text'
+ label={i18n('Persistent Storage/Volume Size (GB)')}
+ pointer={'/compute/vmSizing/persistentStorageVolumeSize'}/>
+ </div>
+ <ValidationInput
+ type='text'
+ label={i18n('I/O Operations (per second)')}
+ pointer={'/compute/vmSizing/IOOperationsPerSec'}/>
+ </div>
+ </div>
+ <div className='section-title'>{i18n('Number of VMs')}</div>
+ <div className='rows-section'>
+ <div className='row-flex-components input-row'>
+ <div className='single-col'>
+ <ValidationInput
+ type='text'
+ label={i18n('Minimum')}
+ pointer={'/compute/numOfVMs/minimum'}/>
+ </div>
+ <div className='single-col'>
+ <ValidationInput
+ type='text'
+ label={i18n('Maximum')}
+ pointer={'/compute/numOfVMs/maximum'}
+ validations={{minValue: minNumberOfVMsSelectedByUser}}/>
+ </div>
+ <div className='single-col'>
+ <ValidationInput
+ type='select'
+ label={i18n('CPU Oversubscription Ratio')}
+ pointer={'/compute/numOfVMs/CpuOverSubscriptionRatio'}/>
+ </div>
+ <ValidationInput
+ type='select'
+ label={i18n('Memory - RAM')}
+ pointer={'/compute/numOfVMs/MemoryRAM'}/>
+ </div>
+ </div>
+
+ <div className='section-title'>{i18n('Guest OS')}</div>
+ <div className='rows-section'>
+ <div className='section-field row-flex-components input-row'>
+ <div className='two-col'>
+ <ValidationInput
+ label={i18n('Guest OS')}
+ type='text'
+ pointer={'/compute/guestOS/name'}/>
+ </div>
+ <div className='empty-two-col'/>
+ </div>
+ <div className='vertical-flex input-row'>
+ <label key='label' className='control-label'>{i18n('OS Bit Size')}</label>
+ <div className='radio-options-content-row input-row'>
+ <ValidationInput
+ type='radiogroup'
+ pointer={'/compute/guestOS/bitSize'}
+ className='radio-field'/>
+ </div>
+ </div>
+ <div className='section-field row-flex-components input-row'>
+ <div className='two-col'>
+ <ValidationInput
+ type='textarea'
+ label={i18n('Guest OS Tools:')}
+ pointer={'/compute/guestOS/tools'}/>
+ </div>
+ <div className='empty-two-col'/>
+ </div>
+ </div>
+ </ValidationForm>
+ </div>
+ );
+ }
+
+ save(){
+ return this.refs.computeValidationForm.handleFormSubmit(new Event('dummy'));
+ }
+}
+
+export default SoftwareProductComponentComputeView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/general/SoftwareProductComponentsGeneral.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/general/SoftwareProductComponentsGeneral.js
new file mode 100644
index 0000000..e4c330b
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/general/SoftwareProductComponentsGeneral.js
@@ -0,0 +1,52 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import SoftwareProductComponentsGeneralView from './SoftwareProductComponentsGeneralView.jsx';
+import SoftwareProductComponentsActionHelper from '../SoftwareProductComponentsActionHelper.js';
+import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js';
+
+export const mapStateToProps = ({softwareProduct}) => {
+ let {softwareProductEditor: {data: currentVSP}, softwareProductComponents} = softwareProduct;
+ let {componentEditor: {data: componentData = {} , qdata, qschema}} = softwareProductComponents;
+
+ let isReadOnlyMode = VersionControllerUtils.isReadOnly(currentVSP);
+
+ return {
+ componentData,
+ qdata,
+ qschema,
+ isReadOnlyMode
+ };
+};
+
+
+const mapActionsToProps = (dispatch, {softwareProductId, componentId}) => {
+ return {
+ onDataChanged: deltaData => SoftwareProductComponentsActionHelper.componentDataChanged(dispatch, {deltaData}),
+ onQDataChanged: ({data}) => SoftwareProductComponentsActionHelper.componentQuestionnaireUpdated(dispatch, {data}),
+ onSubmit: ({componentData, qdata}) => { return SoftwareProductComponentsActionHelper.updateSoftwareProductComponent(dispatch,
+ {softwareProductId, vspComponentId: componentId, componentData, qdata});
+ }
+ };
+
+};
+
+export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(SoftwareProductComponentsGeneralView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/general/SoftwareProductComponentsGeneralView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/general/SoftwareProductComponentsGeneralView.jsx
new file mode 100644
index 0000000..5d11e42
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/general/SoftwareProductComponentsGeneralView.jsx
@@ -0,0 +1,186 @@
+import React from 'react';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx';
+import ValidationInput from'nfvo-components/input/validation/ValidationInput.jsx';
+
+
+class SoftwareProductComponentsGeneralView extends React.Component {
+
+ render() {
+ let {qdata, qschema, onQDataChanged, onDataChanged, componentData: {displayName, description}, isReadOnlyMode} = this.props;
+ return(
+ <div className='vsp-components-general'>
+ <div className='general-data'>
+ <ValidationForm
+ isReadOnlyMode={isReadOnlyMode}
+ hasButtons={false}>
+ <div className=''>
+ <h3 className='section-title'>{i18n('General')}</h3>
+ <div className='rows-section'>
+ <div className='row-flex-components input-row'>
+ {/** disabled until backend will be ready to implement it
+ <div className='validation-input-wrapper'>
+ <div className='form-group'>
+ <label className='control-label'>{i18n('Name')}</label>
+ <div>{name}</div>
+ </div>
+ </div>
+
+ */}
+ <div className='single-col'>
+ <ValidationInput label={i18n('Name')} value={displayName} disabled={true} type='text'/>
+ </div>
+ <div className='two-col'>
+ <ValidationInput
+ label={i18n('Description')}
+ onChange={description => onDataChanged({description})}
+ disabled={isReadOnlyMode}
+ value={description}
+ type='textarea'/>
+ </div>
+ <div className='empty-col' />
+ </div>
+ </div>
+ </div>
+ </ValidationForm>
+ {
+ qschema &&
+ <ValidationForm
+ onDataChanged={onQDataChanged}
+ data={qdata}
+ schema={qschema}
+ isReadOnlyMode={isReadOnlyMode}
+ hasButtons={false}>
+ <h3 className='section-title additional-validation-form'>{i18n('Hypervisor')}</h3>
+ <div className='rows-section'>
+ <div className='row-flex-components input-row'>
+ <div className='single-col'>
+ <ValidationInput
+ label={i18n('Supported Hypervisors')}
+ type='select'
+ pointer='/general/hypervisor/hypervisor'/>
+ </div>
+ <div className='two-col'>
+ <ValidationInput
+ label={i18n('Hypervisor Drivers')}
+ type='text'
+ pointer='/general/hypervisor/drivers'/>
+ </div>
+ <div className='empty-col' />
+ </div>
+ <div className='row-flex-components input-row'>
+ <div className='three-col'>
+ <ValidationInput
+ label={i18n('Describe Container Features')}
+ type='textarea'
+ pointer='/general/hypervisor/containerFeaturesDescription'/>
+ </div>
+ <div className='empty-col' />
+ </div>
+ </div>
+ <h3 className='section-title'>{i18n('Image')}</h3>
+ <div className='rows-section'>
+ <div className='row-flex-components input-row'>
+ <div className='single-col'>
+ <ValidationInput
+ label={i18n('Image format')}
+ type='select'
+ pointer='/general/image/format'/>
+ </div>
+ <div className='single-col'>
+ <ValidationInput
+ label={i18n('Image provided by')}
+ type='select'
+ pointer='/general/image/providedBy'/>
+ </div>
+ <div className='single-col'>
+ <ValidationInput
+ label={i18n('Size of boot disk per VM (GB)')}
+ type='text'
+ pointer='/general/image/bootDiskSizePerVM'/>
+ </div>
+ <ValidationInput
+ label={i18n('Size of ephemeral disk per VM (GB)')}
+ type='text'
+ pointer='/general/image/ephemeralDiskSizePerVM'/>
+ </div>
+ </div>
+ <h3 className='section-title'>{i18n('Recovery')}</h3>
+ <div className='rows-section'>
+ <div className='row-flex-components input-row'>
+ <div className='single-col'>
+ <ValidationInput
+ label={i18n('VM Recovery Point Objective (Minutes)')}
+ type='text'
+ pointer='/general/recovery/pointObjective'/>
+ </div>
+ <ValidationInput
+ label={i18n('VM Recovery Time Objective (Minutes)')}
+ type='text'
+ pointer='/general/recovery/timeObjective'/>
+ <div className='empty-two-col' />
+ </div>
+
+
+ <div className='row-flex-components input-row'>
+ <div className='two-col'>
+ <ValidationInput
+ className='textarea'
+ label={i18n('How are in VM process failures handled?')}
+ type='textarea'
+ pointer='/general/recovery/vmProcessFailuresHandling'/>
+ </div>
+ <div className='empty-two-col' />
+ {
+ /** disabled until backend will be ready to implement it
+ <div className='row'>
+ <div className='col-md-3'>
+ <ValidationInput
+ label={i18n('VM Recovery Document')}
+ type='text'
+ pointer='/general/recovery/VMRecoveryDocument'/>
+ </div>
+ </div>
+ */
+ }
+ </div>
+ </div>
+ <h3 className='section-title'>{i18n('DNS Configuration')}</h3>
+ <div className='rows-section'>
+ <div className='row-flex-components input-row'>
+ <div className='two-col'>
+ <ValidationInput
+ label={i18n('Do you have a need for DNS as a Service? Please describe.')}
+ type='textarea'
+ pointer='/general/dnsConfiguration'/>
+ </div>
+ <div className='empty-two-col' />
+ </div>
+ </div>
+ <h3 className='section-title'>{i18n('Clone')}</h3>
+ <div className='rows-section'>
+ <div className='row-flex-components input-row'>
+ <div className='two-col'>
+ <ValidationInput
+ label={i18n('Describe VM Clone Use')}
+ type='textarea'
+ pointer='/general/vmCloneUsage'/>
+ </div>
+ <div className='empty-two-col' />
+ </div>
+ </div>
+ </ValidationForm>
+ }
+ </div>
+ </div>
+ );
+ }
+
+ save() {
+ let {onSubmit, componentData, qdata} = this.props;
+ return onSubmit({componentData, qdata});
+ }
+}
+
+export default SoftwareProductComponentsGeneralView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/loadBalancing/SoftwareProductComponentLoadBalancing.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/loadBalancing/SoftwareProductComponentLoadBalancing.js
new file mode 100644
index 0000000..4d4034d
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/loadBalancing/SoftwareProductComponentLoadBalancing.js
@@ -0,0 +1,47 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import SoftwareProductComponentLoadBalancingView from './SoftwareProductComponentLoadBalancingRefView.jsx';
+import SoftwareProductComponentsActionHelper from '../SoftwareProductComponentsActionHelper.js';
+import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js';
+
+export const mapStateToProps = ({softwareProduct}) => {
+ let {softwareProductEditor: {data: currentVSP}, softwareProductComponents} = softwareProduct;
+ let {componentEditor: {qdata, qschema}} = softwareProductComponents;
+ let isReadOnlyMode = VersionControllerUtils.isReadOnly(currentVSP);
+
+ return {
+ qdata,
+ qschema,
+ isReadOnlyMode
+ };
+};
+
+
+const mapActionsToProps = (dispatch, {softwareProductId, componentId}) => {
+ return {
+ onQDataChanged: ({data}) => SoftwareProductComponentsActionHelper.componentQuestionnaireUpdated(dispatch, {data}),
+ onSubmit: ({qdata}) =>{ return SoftwareProductComponentsActionHelper.updateSoftwareProductComponentQuestionnaire(dispatch, {softwareProductId, vspComponentId: componentId, qdata});}
+ };
+
+};
+
+export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(SoftwareProductComponentLoadBalancingView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/loadBalancing/SoftwareProductComponentLoadBalancingRefView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/loadBalancing/SoftwareProductComponentLoadBalancingRefView.jsx
new file mode 100644
index 0000000..1aa2bab
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/loadBalancing/SoftwareProductComponentLoadBalancingRefView.jsx
@@ -0,0 +1,103 @@
+import React from 'react';
+import FontAwesome from 'react-fontawesome';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx';
+import ValidationInput from'nfvo-components/input/validation/ValidationInput.jsx';
+
+const prefix = '/highAvailabilityAndLoadBalancing/';
+
+const pointers = [
+ {
+ key: 'failureLoadDistribution',
+ description: 'How is load distributed across live vms in the event of a vm/host failure? please describe'
+ },
+ {
+ key: 'nkModelImplementation',
+ description: 'Does each VM implement the N+K model for redundancy and failure protection? Please describe.'
+ },
+ {
+ key: 'architectureChoice',
+ description: 'What architecture is being implemented: ACTIVE-ACTIVE and/or ACTIVE-PASSIVE. ',
+ added: 'Will the arrangement be 1-1 or N-M? Please describe.'
+ },
+ {key: 'slaRequirements', description: 'Specify application SLA requirements on Cloud platform.'},
+ {
+ key: 'horizontalScaling',
+ description: 'Is horizontal scaling the preferred solution for HA and resiliency? Please describe.'
+ },
+ {
+ key: 'loadDistributionMechanism',
+ description: 'Can load be distributed across VMs? If so, are special mechanisms needed to re-balance data across VMs?',
+ added: 'Please describe.'
+ }
+];
+
+class SoftwareProductComponentLoadBalancingView extends React.Component {
+ static propTypes = {
+ componentId: React.PropTypes.string.isRequired,
+ softwareProductId: React.PropTypes.string.isRequired,
+ qdata: React.PropTypes.object,
+ qschema: React.PropTypes.object,
+ currentSoftwareProduct: React.PropTypes.object
+ };
+
+ state = {
+ expanded: {}
+ };
+
+ renderTextAreaItem(item) {
+ return (
+ <div className='question'>
+ <div className={this.state.expanded[item.key] ? 'title' : 'title add-padding'}
+ onClick={() => this.toggle(item.key)}>
+ <FontAwesome name={this.state.expanded[item.key] ? 'chevron-up' : 'chevron-down'}/>
+ {i18n(item.description)}
+ {item.added && <div className='new-line'>{i18n(item.added)}</div>}
+ </div>
+ <div className={this.state.expanded[item.key] ? 'collapse in' : 'collapse'}>
+ <div className='row'>
+ <div className='col-md-9'>
+ <ValidationInput
+ type='textarea'
+ pointer={`${prefix}${item.key}`}
+ maxLength='1000' />
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ render() {
+ let {qdata, qschema, onQDataChanged, isReadOnlyMode} = this.props;
+ return (
+ <div className='vsp-components-load-balancing'>
+ <div className='halb-data'>
+ <div className='load-balancing-page-title'>{i18n('High Availability & Load Balancing')}</div>
+ <ValidationForm
+ onDataChanged={onQDataChanged}
+ data={qdata} schema={qschema}
+ isReadOnlyMode={isReadOnlyMode}
+ hasButtons={false}>
+ {pointers.map(pointer => this.renderTextAreaItem(pointer))}
+ </ValidationForm>
+ </div>
+ </div>
+ );
+ }
+
+ toggle(name) {
+ let st = this.state.expanded[name] ? true : false;
+ let newState = {...this.state};
+ newState.expanded[name] = !st;
+ this.setState(newState);
+ }
+
+ save() {
+ let {onSubmit, qdata} = this.props;
+ return onSubmit({qdata});
+ }
+}
+
+export default SoftwareProductComponentLoadBalancingView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoring.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoring.js
new file mode 100644
index 0000000..ed7c5a1
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoring.js
@@ -0,0 +1,59 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js';
+import SoftwareProductComponentsMonitoringView from './SoftwareProductComponentsMonitoringView.jsx';
+import SoftwareProductComponentsMonitoringAction from './SoftwareProductComponentsMonitoringActionHelper.js';
+
+
+export const mapStateToProps = ({softwareProduct}) => {
+
+ let {softwareProductEditor: {data:currentVSP = {}}, softwareProductComponents: {monitoring}} = softwareProduct;
+ let {trapFilename, pollFilename} = monitoring;
+ let isReadOnlyMode = VersionControllerUtils.isReadOnly(currentVSP);
+
+ return {
+ isReadOnlyMode,
+ trapFilename,
+ pollFilename
+ };
+};
+
+const mapActionsToProps = (dispatch, {softwareProductId, componentId}) => {
+ return {
+ onDropMibFileToUpload: (formData, type) =>
+ SoftwareProductComponentsMonitoringAction.uploadSnmpFile(dispatch, {
+ softwareProductId,
+ componentId,
+ formData,
+ type
+ }),
+
+ onDeleteSnmpFile: type => SoftwareProductComponentsMonitoringAction.deleteSnmpFile(dispatch, {
+ softwareProductId,
+ componentId,
+ type
+ })
+
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(SoftwareProductComponentsMonitoringView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringActionHelper.js
new file mode 100644
index 0000000..3faf571
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringActionHelper.js
@@ -0,0 +1,110 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
+import NotificationConstants from 'nfvo-components/notifications/NotificationConstants.js';
+import Configuration from 'sdc-app/config/Configuration.js';
+import SoftwareProductComponentsMonitoringConstants, {actionTypes} from './SoftwareProductComponentsMonitoringConstants.js';
+
+const UPLOAD = true;
+
+function baseUrl(vspId, componentId) {
+ const restPrefix = Configuration.get('restPrefix');
+ return `${restPrefix}/v1.0/vendor-software-products/${vspId}/components/${componentId}/monitors`;
+}
+
+function snmpTrapUrl(vspId, componentId, isUpload) {
+ return `${baseUrl(vspId, componentId)}/snmp-trap${isUpload ? '/upload' : ''}`;
+}
+
+function snmpPollUrl(vspId, componentId, isUpload) {
+ return `${baseUrl(vspId, componentId)}/snmp${isUpload ? '/upload' : ''}`;
+}
+
+let onInvalidFileSizeUpload = (dispatch) => dispatch({
+ type: NotificationConstants.NOTIFY_ERROR,
+ data: {
+ title: i18n('Upload Failed'),
+ msg: i18n('no zip file was uploaded or zip file doesn\'t exist')
+ }
+});
+
+let uploadSnmpTrapFile = (dispatch, {softwareProductId, componentId, formData}) => {
+ RestAPIUtil.create(snmpTrapUrl(softwareProductId, componentId, UPLOAD), formData).then(()=> dispatch({
+ type: actionTypes.SNMP_TRAP_UPLOADED, data: {filename: formData.get('upload').name}
+ }));
+};
+
+let uploadSnmpPollFile = (dispatch, {softwareProductId, componentId, formData}) => {
+ RestAPIUtil.create(snmpPollUrl(softwareProductId, componentId, UPLOAD), formData).then(()=> dispatch({
+ type: actionTypes.SNMP_POLL_UPLOADED, data: {filename: formData.get('upload').name}
+ }));
+};
+
+let deleteSnmpTrapFile = (dispatch, {softwareProductId, componentId}) => {
+ RestAPIUtil.destroy(snmpTrapUrl(softwareProductId, componentId, !UPLOAD)).then(()=> dispatch({
+ type: actionTypes.SNMP_TRAP_DELETED
+ }));
+};
+
+let deleteSnmpPollFile = (dispatch, {softwareProductId, componentId}) => {
+ RestAPIUtil.destroy(snmpPollUrl(softwareProductId, componentId, !UPLOAD)).then(()=> dispatch({
+ type: actionTypes.SNMP_POLL_DELETED
+ }));
+};
+
+const SoftwareProductComponentsMonitoringAction = {
+
+ fetchExistingFiles(dispatch, {softwareProductId, componentId}){
+ RestAPIUtil.fetch(`${baseUrl(softwareProductId, componentId)}/snmp`).then(response =>
+ dispatch({
+ type: actionTypes.SNMP_FILES_DATA_CHANGE,
+ data: {trapFilename: response.snmpTrap, pollFilename: response.snmpPoll}
+ })
+ );
+ },
+
+ uploadSnmpFile(dispatch, {softwareProductId, componentId, formData, type}){
+ if (formData.get('upload').size) {
+ if (type === SoftwareProductComponentsMonitoringConstants.SNMP_TRAP) {
+ uploadSnmpTrapFile(dispatch, {softwareProductId, componentId, formData});
+ }
+ else {
+ uploadSnmpPollFile(dispatch, {softwareProductId, componentId, formData});
+ }
+ }
+ else {
+ onInvalidFileSizeUpload(dispatch);
+ }
+ },
+
+ deleteSnmpFile(dispatch, {softwareProductId, componentId, type}){
+ if (type === SoftwareProductComponentsMonitoringConstants.SNMP_TRAP) {
+ deleteSnmpTrapFile(dispatch, {softwareProductId, componentId});
+ }
+ else {
+ deleteSnmpPollFile(dispatch, {softwareProductId, componentId});
+ }
+ }
+
+};
+
+export default SoftwareProductComponentsMonitoringAction;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringConstants.js
new file mode 100644
index 0000000..eeececb
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringConstants.js
@@ -0,0 +1,38 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export const actionTypes = keyMirror({
+
+ SNMP_FILES_DATA_CHANGE: null,
+
+ SNMP_TRAP_UPLOADED: null,
+ SNMP_POLL_UPLOADED: null,
+
+ SNMP_TRAP_DELETED: null,
+ SNMP_POLL_DELETED: null
+});
+
+export default keyMirror({
+ SNMP_TRAP: null,
+ SNMP_POLL: null
+});
+
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringReducer.js
new file mode 100644
index 0000000..72e0a85
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringReducer.js
@@ -0,0 +1,54 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './SoftwareProductComponentsMonitoringConstants.js';
+
+export default (state = {}, action) => {
+ switch (action.type) {
+ case actionTypes.SNMP_FILES_DATA_CHANGE:
+ return {
+ ...state,
+ trapFilename: action.data.trapFilename,
+ pollFilename: action.data.pollFilename
+ };
+ case actionTypes.SNMP_TRAP_UPLOADED:
+ return {
+ ...state,
+ trapFilename: action.data.filename
+ };
+ case actionTypes.SNMP_POLL_UPLOADED:
+ return {
+ ...state,
+ pollFilename: action.data.filename
+ };
+ case actionTypes.SNMP_TRAP_DELETED:
+ return {
+ ...state,
+ trapFilename: undefined
+ };
+ case actionTypes.SNMP_POLL_DELETED:
+ return {
+ ...state,
+ pollFilename: undefined
+ };
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringView.jsx
new file mode 100644
index 0000000..ca090c5
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/monitoring/SoftwareProductComponentsMonitoringView.jsx
@@ -0,0 +1,117 @@
+import React, {Component, PropTypes} from 'react';
+import Dropzone from 'react-dropzone';
+import ButtonGroup from 'react-bootstrap/lib/ButtonGroup.js';
+import ButtonToolbar from 'react-bootstrap/lib/ButtonToolbar.js';
+import Button from 'react-bootstrap/lib/Button.js';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import SoftwareProductComponentsMonitoringConstants from './SoftwareProductComponentsMonitoringConstants.js';
+
+class SoftwareProductComponentsMonitoringView extends Component {
+
+ static propTypes = {
+ isReadOnlyMode: PropTypes.bool,
+ trapFilename: PropTypes.string,
+ pollFilename: PropTypes.string,
+ softwareProductId: PropTypes.string,
+
+ onDropMibFileToUpload: PropTypes.func,
+ onDeleteSnmpFile: PropTypes.func
+ };
+
+ state = {
+ dragging: false
+ };
+
+
+ render() {
+ return (
+ <div className='vsp-component-monitoring'>
+ {this.renderDropzoneWithType(SoftwareProductComponentsMonitoringConstants.SNMP_TRAP)}
+ {this.renderDropzoneWithType(SoftwareProductComponentsMonitoringConstants.SNMP_POLL)}
+ </div>
+ );
+ }
+
+ renderDropzoneWithType(type) {
+ let {isReadOnlyMode, trapFilename, pollFilename} = this.props;
+ let fileName;
+ if (type === SoftwareProductComponentsMonitoringConstants.SNMP_TRAP) {
+ fileName = trapFilename;
+ }
+ else {
+ fileName = pollFilename;
+ }
+ let refAndName = `fileInput${type.toString()}`;
+ let typeDisplayName = this.getFileTypeDisplayName(type);
+ return (
+ <Dropzone
+ className={`snmp-dropzone ${this.state.dragging ? 'active-dragging' : ''}`}
+ onDrop={files => this.handleImport(files, {isReadOnlyMode, type, refAndName})}
+ onDragEnter={() => this.handleOnDragEnter(isReadOnlyMode)}
+ onDragLeave={() => this.setState({dragging:false})}
+ multiple={false}
+ disableClick={true}
+ ref={refAndName}
+ name={refAndName}
+ accept='.zip'
+ disabled>
+ <div className='draggable-wrapper'>
+ <div className='section-title'>{typeDisplayName}</div>
+ {fileName ? this.renderUploadedFileName(fileName, type) : this.renderUploadButton(refAndName)}
+ </div>
+ </Dropzone>
+ );
+ }
+
+ renderUploadButton(refAndName) {
+ let {isReadOnlyMode} = this.props;
+ return (
+ <div
+ className={`software-product-landing-view-top-block-col-upl${isReadOnlyMode ? ' disabled' : ''}`}>
+ <div className='drag-text'>{i18n('Drag & drop for upload')}</div>
+ <div className='or-text'>{i18n('or')}</div>
+ <div className='upload-btn primary-btn' onClick={() => this.refs[refAndName].open()}>
+ <span className='primary-btn-text'>{i18n('Select file')}</span>
+ </div>
+ </div>
+ );
+ }
+
+ renderUploadedFileName(filename, type) {
+ return (
+ <ButtonToolbar>
+ <ButtonGroup>
+ <Button disabled>{filename}</Button>
+ <Button className='delete-button' onClick={()=>this.props.onDeleteSnmpFile(type)}>X</Button>
+ </ButtonGroup>
+ </ButtonToolbar>
+ );
+ }
+
+
+ handleOnDragEnter(isReadOnlyMode) {
+ if (!isReadOnlyMode) {
+ this.setState({dragging: true});
+ }
+ }
+
+ handleImport(files, {isReadOnlyMode, type, refAndName}) {
+ if (isReadOnlyMode) {
+ return;
+ }
+
+ this.setState({dragging: false});
+ let file = files[0];
+ let formData = new FormData();
+ formData.append('upload', file);
+ this.refs[refAndName].value = '';
+ this.props.onDropMibFileToUpload(formData, type);
+ }
+
+ getFileTypeDisplayName(type) {
+ return type === SoftwareProductComponentsMonitoringConstants.SNMP_TRAP ? 'SNMP Trap' : 'SNMP Poll';
+ }
+
+}
+
+export default SoftwareProductComponentsMonitoringView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICEditor.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICEditor.js
new file mode 100644
index 0000000..a412456
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICEditor.js
@@ -0,0 +1,54 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import SoftwareProductComponentsNetworkActionHelper from './SoftwareProductComponentsNetworkActionHelper.js';
+import SoftwareProductComponentsNICEditorView from './SoftwareProductComponentsNICEditorView.jsx';
+import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js';
+
+export const mapStateToProps = ({softwareProduct}) => {
+
+ let {softwareProductEditor: {data:currentSoftwareProduct = {}, isValidityData = true}, softwareProductComponents} = softwareProduct;
+
+ let {network: {nicEditor = {}}} = softwareProductComponents;
+ let {data, qdata, qschema} = nicEditor;
+ let isReadOnlyMode = VersionControllerUtils.isReadOnly(currentSoftwareProduct);
+
+ return {
+ currentSoftwareProduct,
+ isValidityData,
+ data,
+ qdata,
+ qschema,
+ isReadOnlyMode
+ };
+
+};
+
+const mapActionsToProps = (dispatch, {softwareProductId, componentId}) => {
+ return {
+ onDataChanged: deltaData => SoftwareProductComponentsNetworkActionHelper.updateNICData(dispatch, {deltaData}),
+ onSubmit: ({data, qdata}) => SoftwareProductComponentsNetworkActionHelper.saveNICDataAndQuestionnaire(dispatch, {softwareProductId, componentId, data, qdata}),
+ onCancel: () => SoftwareProductComponentsNetworkActionHelper.closeNICEditor(dispatch),
+ onQDataChanged: ({data}) => SoftwareProductComponentsNetworkActionHelper.updateNICQuestionnaire(dispatch, {data})
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(SoftwareProductComponentsNICEditorView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICEditorReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICEditorReducer.js
new file mode 100644
index 0000000..d49f9cc
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICEditorReducer.js
@@ -0,0 +1,49 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './SoftwareProductComponentsNetworkConstants.js';
+
+export default (state = {}, action) => {
+ switch (action.type) {
+ case actionTypes.NICEditor.OPEN:
+ return {
+ ...state,
+ data: action.nic
+ };
+ case actionTypes.NICEditor.CLOSE:
+ return {};
+ case actionTypes.NICEditor.NIC_QUESTIONNAIRE_UPDATE:
+ return {
+ ...state,
+ qdata: action.payload.qdata || state.qdata,
+ qschema: action.payload.qschema || state.qschema
+ };
+ case actionTypes.NICEditor.DATA_CHANGED:
+ return {
+ ...state,
+ data: {
+ ...state.data,
+ ...action.deltaData
+ }
+ };
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICEditorView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICEditorView.jsx
new file mode 100644
index 0000000..7393a83
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICEditorView.jsx
@@ -0,0 +1,322 @@
+import React from 'react';
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx';
+import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx';
+
+class SoftwareProductComponentsNetworkEditorView extends React.Component {
+
+ render() {
+ let {onCancel, isReadOnlyMode} = this.props;
+ return (
+ <ValidationForm
+ ref='validationForm'
+ hasButtons={true}
+ onSubmit={ () => this.submit() }
+ onReset={ () => onCancel() }
+ labledButtons={true}
+ isReadOnlyMode={isReadOnlyMode}
+ className='vsp-components-network-editor'>
+ {this.renderEditorFields()}
+ </ValidationForm>
+ );
+ }
+
+ renderEditorFields() {
+ let {data = {}, qdata = {}, qschema = {}, onQDataChanged, onDataChanged, isReadOnlyMode} = this.props;
+ let {name, description, networkName} = data;
+ let netWorkValues = [{
+ enum: networkName,
+ title: networkName
+ }];
+ return(
+ <div className='editor-data'>
+ <div className='row'>
+ <div className='col-md-6'>
+ <ValidationInput
+ label={i18n('Name')}
+ value={name}
+ disabled={true}
+ type='text'/>
+ </div>
+ <div className='col-md-6'>
+ <ValidationInput
+ label={i18n('Purpose of NIC')}
+ value={description}
+ onChange={description => onDataChanged({description})}
+ disabled={isReadOnlyMode}
+ type='textarea'/>
+ </div>
+ </div>
+ <ValidationForm
+ onDataChanged={onQDataChanged}
+ data={qdata}
+ schema={qschema}
+ isReadOnlyMode={isReadOnlyMode}
+ hasButtons={false}>
+ <div className='row'>
+ <div className='part-title'>{i18n('Protocols')}</div>
+ <div className='col-md-6'>
+ <ValidationInput
+ label={i18n('Protocols')}
+ type='select'
+ pointer='/protocols/protocols'/>
+ </div>
+ <div className='col-md-6'>
+ <ValidationInput
+ label={i18n('Protocol with Highest Traffic Profile')}
+ type='select'
+ pointer='/protocols/protocolWithHighestTrafficProfile'/>
+ </div>
+ </div>
+ <div className='row'>
+ <div className='part-title'>{i18n('IP Configuration')}</div>
+ <div className='col-md-3'>
+ <ValidationInput
+ label={i18n('IPv4 Required')}
+ type='checkbox'
+ pointer='/ipConfiguration/ipv4Required'/>
+ </div>
+ <div className='col-md-9'>
+ <ValidationInput
+ label={i18n('IPv6 Required')}
+ type='checkbox'
+ pointer='/ipConfiguration/ipv6Required'/>
+ </div>
+ </div>
+ </ValidationForm>
+ <div className='row'>
+ <div className='part-title'>{i18n('Network')}</div>
+ <div className='col-md-2'>
+ <ValidationInput
+ label={i18n('Internal')}
+ disabled
+ checked={true}
+ className='network-radio disabled'
+ type='radio'/>
+ </div>
+ <div className='col-md-4'>
+ <ValidationInput
+ label={i18n('External')}
+ disabled
+ checked={false}
+ className='network-radio disabled'
+ type='radio'/>
+ </div>
+ <div className='col-md-6'>
+ <ValidationInput
+ label={i18n('Network')}
+ type='select'
+ disabled={true}
+ values={netWorkValues}/>
+ </div>
+ </div>
+ <ValidationForm
+ onDataChanged={onQDataChanged}
+ data={qdata}
+ schema={qschema}
+ isReadOnlyMode={isReadOnlyMode}
+ hasButtons={false}>
+ <div className='row'>
+ <div className='part-title'>{i18n('Sizing')}</div>
+ <div className='col-md-12'>
+ <ValidationInput
+ label={i18n('Describe Quality of Service')}
+ type='textarea'
+ pointer='/sizing/describeQualityOfService'/>
+ </div>
+ </div>
+
+ <div className='row'>
+ <div className='part-title'>{i18n('Inflow Traffic per second')}</div>
+ </div>
+
+ <div className='row'>
+ <div className='col-md-6'>
+ <div className='row'>
+ <div className='part-title-small'>{i18n('Packets')}</div>
+ </div>
+ <div className='row'>
+ <div className='col-md-6'>
+ <ValidationInput
+ label={i18n('Peak')}
+ type='text'
+ pointer='/sizing/inflowTrafficPerSecond/packets/peak'/>
+ </div>
+ <div className='col-md-6'>
+ <ValidationInput
+ label={i18n('Avg')}
+ type='text'
+ pointer='/sizing/inflowTrafficPerSecond/packets/avg'/>
+ </div>
+ </div>
+ </div>
+ <div className='col-md-6'>
+ <div className='row'>
+ <div className='part-title-small'>{i18n('Bytes')}</div>
+ </div>
+ <div className='row'>
+ <div className='col-md-6'>
+ <ValidationInput
+ label={i18n('Peak')}
+ type='text'
+ pointer='/sizing/inflowTrafficPerSecond/bytes/peak'/>
+
+ </div>
+ <div className='col-md-6'>
+ <ValidationInput
+ label={i18n('Avg')}
+ type='text'
+ pointer='/sizing/inflowTrafficPerSecond/bytes/avg'/>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div className='row'>
+ <div className='part-title'>{i18n('Outflow Traffic per second')}</div>
+ </div>
+
+ <div className='row'>
+ <div className='col-md-6'>
+ <div className='row'>
+ <div className='part-title-small'>{i18n('Packets')}</div>
+ </div>
+ <div className='row'>
+ <div className='col-md-6'>
+ <ValidationInput
+ label={i18n('Peak')}
+ type='text'
+ pointer='/sizing/outflowTrafficPerSecond/packets/peak'/>
+ </div>
+ <div className='col-md-6'>
+ <ValidationInput
+ label={i18n('Avg')}
+ type='text'
+ pointer='/sizing/outflowTrafficPerSecond/packets/avg'/>
+
+ </div>
+ </div>
+ </div>
+ <div className='col-md-6'>
+ <div className='row'>
+ <div className='part-title-small'>{i18n('Bytes')}</div>
+ </div>
+ <div className='row'>
+ <div className='col-md-6'>
+ <ValidationInput
+ label={i18n('Peak')}
+ type='text'
+ pointer='/sizing/outflowTrafficPerSecond/bytes/peak'/>
+
+ </div>
+ <div className='col-md-6'>
+ <ValidationInput
+ label={i18n('Avg')}
+ type='text'
+ pointer='/sizing/outflowTrafficPerSecond/bytes/avg'/>
+
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div className='row'>
+ <div className='part-title'>{i18n('Flow Length')}</div>
+ </div>
+
+ <div className='row'>
+ <div className='col-md-6'>
+ <div className='row'>
+ <div className='part-title-small'>{i18n('Packets')}</div>
+ </div>
+ <div className='row'>
+ <div className='col-md-6'>
+ <ValidationInput
+ label={i18n('Peak')}
+ type='text'
+ pointer='/sizing/flowLength/packets/peak'/>
+ </div>
+ <div className='col-md-6'>
+ <ValidationInput
+ label={i18n('Avg')}
+ type='text'
+ pointer='/sizing/flowLength/packets/avg'/>
+ </div>
+ </div>
+ </div>
+ <div className='col-md-6'>
+ <div className='row'>
+ <div className='part-title-small'>{i18n('Bytes')}</div>
+ </div>
+ <div className='row'>
+ <div className='col-md-6'>
+ <ValidationInput
+ label={i18n('Peak')}
+ type='text'
+ pointer='/sizing/flowLength/bytes/peak'/>
+
+ </div>
+ <div className='col-md-6'>
+ <ValidationInput
+ label={i18n('Avg')}
+ type='text'
+ pointer='/sizing/flowLength/bytes/avg'/>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div className='row'>
+ <div className='col-md-9'>
+ <div className='row'>
+ <div className='part-title-small'>{i18n('Acceptable Jitter')}</div>
+ </div>
+ <div className='row'>
+ <div className='col-md-4'>
+ <ValidationInput
+ label={i18n('Min')}
+ type='text'
+ pointer='/sizing/acceptableJitter/mean'/>
+ </div>
+ <div className='col-md-4'>
+ <ValidationInput
+ label={i18n('Max')}
+ type='text'
+ pointer='/sizing/acceptableJitter/max'/>
+ </div>
+ <div className='col-md-4'>
+ <ValidationInput
+ label={i18n('Var')}
+ type='text'
+ pointer='/sizing/acceptableJitter/variable'/>
+ </div>
+ </div>
+ </div>
+ <div className='col-md-3'>
+ <div className='row'>
+ <div className='part-title-small'>{i18n('Acceptable Packet Loss %')}</div>
+ </div>
+ <div className='row'>
+ <div className='col-md-12'>
+ <ValidationInput
+ label={i18n('In Percent')}
+ type='text'
+ pointer='/sizing/acceptablePacketLoss'/>
+ </div>
+ </div>
+ </div>
+ </div>
+ </ValidationForm>
+ </div>
+
+ );
+ }
+ submit() {
+ let {data, qdata, onSubmit} = this.props;
+ onSubmit({data, qdata});
+ }
+}
+
+export default SoftwareProductComponentsNetworkEditorView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICListReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICListReducer.js
new file mode 100644
index 0000000..bc53e1a
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICListReducer.js
@@ -0,0 +1,33 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './SoftwareProductComponentsNetworkConstants.js';
+
+export default (state = [], action) => {
+ switch (action.type) {
+ case actionTypes.NIC_LIST_UPDATE:
+ return [...action.response];
+ case actionTypes.NIC_LIST_EDIT:
+ const indexForEdit = state.findIndex(nic => nic.id === action.nic.id);
+ return [...state.slice(0, indexForEdit), action.nic, ...state.slice(indexForEdit + 1)];
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkActionHelper.js
new file mode 100644
index 0000000..8ff6b50
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkActionHelper.js
@@ -0,0 +1,129 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
+import Configuration from 'sdc-app/config/Configuration.js';
+
+import {actionTypes} from './SoftwareProductComponentsNetworkConstants.js';
+
+function baseUrl(softwareProductId, componentId) {
+ const restPrefix = Configuration.get('restPrefix');
+ return `${restPrefix}/v1.0/vendor-software-products/${softwareProductId}/components/${componentId}/nics`;
+}
+
+
+function fetchNICQuestionnaire({softwareProductId, componentId, nicId, version}) {
+ let versionQuery = version ? `?version=${version}` : '';
+ return RestAPIUtil.fetch(`${baseUrl(softwareProductId, componentId)}/${nicId}/questionnaire${versionQuery}`);
+}
+
+function fetchNIC({softwareProductId, componentId, nicId, version}) {
+ let versionQuery = version ? `?version=${version}` : '';
+ return RestAPIUtil.fetch(`${baseUrl(softwareProductId, componentId)}/${nicId}${versionQuery}`);
+}
+
+function fetchNICsList({softwareProductId, componentId, version}) {
+ let versionQuery = version ? `?version=${version}` : '';
+ return RestAPIUtil.fetch(`${baseUrl(softwareProductId, componentId)}${versionQuery}`);
+}
+
+function saveNIC({softwareProductId, componentId, nic: {id, name, description, networkId}}) {
+ return RestAPIUtil.save(`${baseUrl(softwareProductId, componentId)}/${id}`,{
+ name,
+ description,
+ networkId
+ });
+}
+
+function saveNICQuestionnaire({softwareProductId, componentId, nicId, qdata}) {
+ return RestAPIUtil.save(`${baseUrl(softwareProductId, componentId)}/${nicId}/questionnaire`, qdata);
+}
+
+const SoftwareProductComponentNetworkActionHelper = {
+
+ fetchNICsList(dispatch, {softwareProductId, componentId, version}) {
+ return fetchNICsList({softwareProductId, componentId, version}).then((response) => {
+ dispatch({
+ type: actionTypes.NIC_LIST_UPDATE,
+ response: response.results
+ });
+ });
+ },
+
+ openNICEditor(dispatch, {nic = {}, data = {}}) {
+ dispatch({
+ type: actionTypes.NICEditor.OPEN,
+ nic: {...data, id: nic.id}
+ });
+ },
+
+ closeNICEditor(dispatch) {
+ dispatch({
+ type: actionTypes.NICEditor.CLOSE
+ });
+ },
+
+ loadNICData({softwareProductId, componentId, nicId, version}) {
+ return fetchNIC({softwareProductId, componentId, nicId, version});
+ },
+
+ loadNICQuestionnaire(dispatch, {softwareProductId, componentId, nicId, version}) {
+ return fetchNICQuestionnaire({softwareProductId, componentId, nicId, version}).then((response) => {
+ dispatch({
+ type: actionTypes.NICEditor.NIC_QUESTIONNAIRE_UPDATE,
+ payload: {
+ qdata: response.data ? JSON.parse(response.data) : {},
+ qschema: JSON.parse(response.schema)
+ }
+ });
+ });
+ },
+
+ updateNICData(dispatch, {deltaData}) {
+ dispatch({
+ type: actionTypes.NICEditor.DATA_CHANGED,
+ deltaData
+ });
+ },
+
+ updateNICQuestionnaire(dispatch, {data}) {
+ dispatch({
+ type: actionTypes.NICEditor.NIC_QUESTIONNAIRE_UPDATE,
+ payload: {
+ qdata: data
+ }
+ });
+ },
+
+ saveNICDataAndQuestionnaire(dispatch, {softwareProductId, componentId, data, qdata}) {
+ SoftwareProductComponentNetworkActionHelper.closeNICEditor(dispatch);
+ return Promise.all([
+ saveNICQuestionnaire({softwareProductId, componentId, nicId: data.id, qdata}),
+ saveNIC({softwareProductId, componentId, nic: data}).then(() => {
+ dispatch({
+ type: actionTypes.NIC_LIST_EDIT,
+ nic: data
+ });
+ })
+ ]);
+ }
+};
+
+export default SoftwareProductComponentNetworkActionHelper;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkConstants.js
new file mode 100644
index 0000000..193f4b2
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkConstants.js
@@ -0,0 +1,33 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export const actionTypes = keyMirror({
+ NIC_LIST_EDIT: null,
+ NIC_LIST_UPDATE: null,
+
+ NICEditor: {
+ OPEN: null,
+ CLOSE: null,
+ NIC_QUESTIONNAIRE_UPDATE: null,
+ DATA_CHANGED: null
+ }
+});
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkList.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkList.js
new file mode 100644
index 0000000..9172dc6
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkList.js
@@ -0,0 +1,86 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js';
+
+import SoftwareProductComponentsActionHelper from 'sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsActionHelper.js';
+import SoftwareProductComponentsNetworkListView from './SoftwareProductComponentsNetworkListView.jsx';
+import SoftwareProductComponentsNetworkActionHelper from './SoftwareProductComponentsNetworkActionHelper.js';
+
+
+export const mapStateToProps = ({softwareProduct}) => {
+
+ let {softwareProductEditor: {data: currentSoftwareProduct = {}, isValidityData = true}, softwareProductComponents} = softwareProduct;
+ let {network: {nicEditor = {}, nicList = []}, componentEditor: {data: componentData, qdata, qschema}} = softwareProductComponents;
+ let {data} = nicEditor;
+ let isReadOnlyMode = VersionControllerUtils.isReadOnly(currentSoftwareProduct);
+ let {version} = currentSoftwareProduct;
+ let manualMode = nicList.length <= 0;
+ let isModalInEditMode = true;
+
+ return {
+ version,
+ componentData,
+ qdata,
+ qschema,
+ isValidityData,
+ nicList,
+ isDisplayModal: Boolean(data),
+ isModalInEditMode,
+ manualMode,
+ isReadOnlyMode
+ };
+
+};
+
+const mapActionsToProps = (dispatch, {softwareProductId, componentId}) => {
+ return {
+ onQDataChanged: ({data}) => SoftwareProductComponentsActionHelper.componentQuestionnaireUpdated(dispatch, {data}),
+ onAddNIC: () => SoftwareProductComponentsNetworkActionHelper.openNICEditor(dispatch),
+ onEditNicClick: (nic, version) => {
+ Promise.all([
+ SoftwareProductComponentsNetworkActionHelper.loadNICData({
+ softwareProductId,
+ componentId,
+ nicId: nic.id,
+ version
+ }),
+ SoftwareProductComponentsNetworkActionHelper.loadNICQuestionnaire(dispatch, {
+ softwareProductId,
+ componentId,
+ nicId: nic.id,
+ version
+ })
+ ]).then(
+ ([{data}]) => SoftwareProductComponentsNetworkActionHelper.openNICEditor(dispatch, {nic, data})
+ );
+ },
+ onSubmit: ({qdata}) => { return SoftwareProductComponentsActionHelper.updateSoftwareProductComponentQuestionnaire(dispatch,
+ {softwareProductId,
+ vspComponentId: componentId,
+ qdata});
+ }
+
+
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(SoftwareProductComponentsNetworkListView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkListView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkListView.jsx
new file mode 100644
index 0000000..b3e17ff
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkListView.jsx
@@ -0,0 +1,136 @@
+import React from 'react';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx';
+
+import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx';
+import ListEditorItemView from 'nfvo-components/listEditor/ListEditorItemView.jsx';
+import ValidationInput from'nfvo-components/input/validation/ValidationInput.jsx';
+import Modal from 'nfvo-components/modal/Modal.jsx';
+
+import SoftwareProductComponentsNICEditor from './SoftwareProductComponentsNICEditor.js';
+
+class SoftwareProductComponentsNetworkView extends React.Component {
+
+ state = {
+ localFilter: ''
+ };
+
+ render() {
+ let {qdata, qschema, onQDataChanged, isModalInEditMode, isDisplayModal, softwareProductId, componentId, isReadOnlyMode} = this.props;
+
+ return(
+ <div className='vsp-components-network'>
+ <div className='network-data'>
+ <div>
+ <ValidationForm
+ onDataChanged={onQDataChanged}
+ data={qdata}
+ isReadOnlyMode={isReadOnlyMode}
+ schema={qschema}
+ hasButtons={false}>
+ <h3 className='section-title'>{i18n('Network Capacity')}</h3>
+ <div className='rows-section'>
+ <div className='row-flex-components input-row'>
+ <div className='single-col'>
+ <ValidationInput
+ label={i18n('Protocol with Highest Traffic Profile across all NICs')}
+ type='select'
+ pointer='/network/networkCapacity/protocolWithHighestTrafficProfileAcrossAllNICs'/>
+ </div>
+ <div className='single-col add-line-break'>
+ <ValidationInput
+ label={i18n('Network Transactions per Second')}
+ type='text'
+ pointer='/network/networkCapacity/networkTransactionsPerSecond'/>
+ </div>
+ <div className='empty-two-col' />
+ </div>
+ </div>
+
+ </ValidationForm>
+ </div>
+ {this.renderNicList()}
+ </div>
+ <Modal show={isDisplayModal} bsSize='large' animation={true} className='network-nic-modal'>
+ <Modal.Header>
+ <Modal.Title>{isModalInEditMode ? i18n('Edit NIC') : i18n('Create New NIC')}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ {
+ <SoftwareProductComponentsNICEditor
+ softwareProductId={softwareProductId}
+ componentId={componentId}
+ isReadOnlyMode={isReadOnlyMode}/>
+ }
+ </Modal.Body>
+ </Modal>
+ </div>
+ );
+ }
+
+ renderNicList() {
+ const {localFilter} = this.state;
+ let {onAddNIC, manualMode, isReadOnlyMode} = this.props;
+ let onAdd = manualMode ? onAddNIC : false;
+ return (
+ <ListEditorView
+ title={i18n('Interfaces')}
+ plusButtonTitle={i18n('Add NIC')}
+ filterValue={localFilter}
+ placeholder={i18n('Filter NICs by Name')}
+ onAdd={onAdd}
+ isReadOnlyMode={isReadOnlyMode}
+ onFilter={filter => this.setState({localFilter: filter})}>
+ {!manualMode && this.filterList().map(nic => this.renderNicListItem(nic, isReadOnlyMode))}
+ </ListEditorView>
+ );
+ }
+
+ renderNicListItem(nic, isReadOnlyMode) {
+ let {id, name, description, networkName = ''} = nic;
+ let {onEditNicClick, version} = this.props;
+ return (
+ <ListEditorItemView
+ key={id}
+ className='list-editor-item-view'
+ isReadOnlyMode={isReadOnlyMode}
+ onSelect={() => onEditNicClick(nic, version)}>
+
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Name')}</div>
+ <div className='name'>{name}</div>
+ </div>
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Purpose of NIC')}</div>
+ <div className='description'>{description}</div>
+ </div>
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Network')}</div>
+ <div className='artifact-name'>{networkName}</div>
+ </div>
+
+ </ListEditorItemView>
+ );
+ }
+
+ filterList() {
+ let {nicList} = this.props;
+ let {localFilter} = this.state;
+ if (localFilter.trim()) {
+ const filter = new RegExp(escape(localFilter), 'i');
+ return nicList.filter(({name = '', description = ''}) => {
+ return escape(name).match(filter) || escape(description).match(filter);
+ });
+ }
+ else {
+ return nicList;
+ }
+ }
+
+ save() {
+ let {onSubmit, qdata} = this.props;
+ return onSubmit({qdata});
+ }
+}
+
+export default SoftwareProductComponentsNetworkView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesActionHelper.js
new file mode 100644
index 0000000..d535a34
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesActionHelper.js
@@ -0,0 +1,145 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
+import Configuration from 'sdc-app/config/Configuration.js';
+import {actionTypes} from './SoftwareProductComponentProcessesConstants.js';
+
+function baseUrl(softwareProductId, componentId) {
+ const restPrefix = Configuration.get('restPrefix');
+ return `${restPrefix}/v1.0/vendor-software-products/${softwareProductId}/components/${componentId}/processes`;
+}
+
+function fetchProcessesList({softwareProductId, componentId, version}) {
+ let versionQuery = version ? `?version=${version}` : '';
+ return RestAPIUtil.fetch(`${baseUrl(softwareProductId, componentId)}${versionQuery}`);
+}
+
+function deleteProcess({softwareProductId, componentId, processId}) {
+ return RestAPIUtil.destroy(`${baseUrl(softwareProductId, componentId)}/${processId}`);
+}
+
+function putProcess({softwareProductId, componentId, process}) {
+ return RestAPIUtil.save(`${baseUrl(softwareProductId, componentId)}/${process.id}`, {
+ name: process.name,
+ description: process.description
+ });
+}
+
+function postProcess({softwareProductId,componentId, process}) {
+ return RestAPIUtil.create(`${baseUrl(softwareProductId, componentId)}`, {
+ name: process.name,
+ description: process.description
+ });
+}
+
+function uploadFileToProcess({softwareProductId, processId, componentId, formData}) {
+ return RestAPIUtil.create(`${baseUrl(softwareProductId, componentId)}/${processId}/upload`, formData);
+}
+
+
+
+const SoftwareProductComponentProcessesActionHelper = {
+ fetchProcessesList(dispatch, {softwareProductId, componentId, version}) {
+ dispatch({
+ type: actionTypes.FETCH_SOFTWARE_PRODUCT_COMPONENTS_PROCESSES,
+ processesList: []
+ });
+
+ return fetchProcessesList({softwareProductId, componentId, version}).then(response => {
+ dispatch({
+ type: actionTypes.FETCH_SOFTWARE_PRODUCT_COMPONENTS_PROCESSES,
+ processesList: response.results
+ });
+ });
+ },
+
+ deleteProcess(dispatch, {process, softwareProductId, componentId}) {
+ return deleteProcess({softwareProductId, processId:process.id, componentId}).then(() => {
+ dispatch({
+ type: actionTypes.DELETE_SOFTWARE_PRODUCT_COMPONENTS_PROCESS,
+ processId: process.id
+ });
+ });
+
+ },
+
+ saveProcess(dispatch, {softwareProductId, componentId, previousProcess, process}) {
+ if (previousProcess) {
+ return putProcess({softwareProductId,componentId, process}).then(() => {
+ if (process.formData && process.formData.name !== previousProcess.artifactName){
+ uploadFileToProcess({softwareProductId, processId: process.id, formData: process.formData, componentId});
+ }
+ dispatch({
+ type: actionTypes.EDIT_SOFTWARE_PRODUCT_COMPONENTS_PROCESS,
+ process
+ });
+ });
+ }
+ else {
+ return postProcess({softwareProductId, componentId, process}).then(response => {
+ if (process.formData) {
+ uploadFileToProcess({softwareProductId, processId: response.value, formData: process.formData, componentId});
+ }
+ dispatch({
+ type: actionTypes.ADD_SOFTWARE_PRODUCT_COMPONENTS_PROCESS,
+ process: {
+ ...process,
+ id: response.value
+ }
+ });
+ });
+ }
+ },
+
+ hideDeleteConfirm(dispatch) {
+ dispatch({
+ type: actionTypes.SOFTWARE_PRODUCT_PROCESS_DELETE_COMPONENTS_CONFIRM,
+ processToDelete: false
+ });
+ },
+
+ openDeleteProcessesConfirm(dispatch, {process} ) {
+ dispatch({
+ type: actionTypes.SOFTWARE_PRODUCT_PROCESS_DELETE_COMPONENTS_CONFIRM,
+ processToDelete: process
+ });
+ },
+
+ openEditor(dispatch, process = {}) {
+ dispatch({
+ type: actionTypes.SOFTWARE_PRODUCT_PROCESS_COMPONENTS_EDITOR_OPEN,
+ process
+ });
+ },
+ closeEditor(dispatch) {
+ dispatch({
+ type:actionTypes.SOFTWARE_PRODUCT_PROCESS_COMPONENTS_EDITOR_CLOSE
+ });
+ },
+ processEditorDataChanged(dispatch, {deltaData}) {
+ dispatch({
+ type: actionTypes.processEditor.DATA_CHANGED,
+ deltaData
+ });
+ }
+};
+
+export default SoftwareProductComponentProcessesActionHelper;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesConstants.js
new file mode 100644
index 0000000..78a111a
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesConstants.js
@@ -0,0 +1,34 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export const actionTypes = keyMirror({
+ ADD_SOFTWARE_PRODUCT_COMPONENTS_PROCESS: null,
+ EDIT_SOFTWARE_PRODUCT_COMPONENTS_PROCESS: null,
+ DELETE_SOFTWARE_PRODUCT_COMPONENTS_PROCESS: null,
+ SOFTWARE_PRODUCT_PROCESS_COMPONENTS_EDITOR_OPEN: null,
+ SOFTWARE_PRODUCT_PROCESS_COMPONENTS_EDITOR_CLOSE: null,
+ FETCH_SOFTWARE_PRODUCT_COMPONENTS_PROCESSES: null,
+ SOFTWARE_PRODUCT_PROCESS_DELETE_COMPONENTS_CONFIRM: null,
+ processEditor: {
+ DATA_CHANGED: null
+ }
+});
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesEditor.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesEditor.js
new file mode 100644
index 0000000..0138023
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesEditor.js
@@ -0,0 +1,54 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import SoftwareProductComponentProcessesActionHelper from './SoftwareProductComponentProcessesActionHelper';
+import SoftwareProductComponentProcessesEditorView from './SoftwareProductComponentProcessesEditorView.jsx';
+
+const mapStateToProps = ({softwareProduct}) => {
+ let {softwareProductComponents: {componentProcesses = {}}} = softwareProduct;
+ let {processesList = [], processesEditor = {}} = componentProcesses;
+ let {data} = processesEditor;
+
+ let previousData;
+ const processId = data ? data.id : null;
+ if(processId) {
+ previousData = processesList.find(process => process.id === processId);
+ }
+
+ return {
+ data,
+ previousData
+ };
+};
+
+const mapActionsToProps = (dispatch, {softwareProductId, componentId}) => {
+
+ return {
+ onDataChanged: deltaData => SoftwareProductComponentProcessesActionHelper.processEditorDataChanged(dispatch, {deltaData}),
+ onCancel: () => SoftwareProductComponentProcessesActionHelper.closeEditor(dispatch),
+ onSubmit: ({previousProcess, process}) => {
+ SoftwareProductComponentProcessesActionHelper.closeEditor(dispatch);
+ SoftwareProductComponentProcessesActionHelper.saveProcess(dispatch, {softwareProductId, previousProcess, componentId, process});
+ }
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(SoftwareProductComponentProcessesEditorView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesEditorReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesEditorReducer.js
new file mode 100644
index 0000000..f859f69
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesEditorReducer.js
@@ -0,0 +1,44 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './SoftwareProductComponentProcessesConstants.js';
+
+export default (state = {}, action) => {
+ switch (action.type) {
+ case actionTypes.SOFTWARE_PRODUCT_PROCESS_COMPONENTS_EDITOR_OPEN:
+ return {
+ ...state,
+ data: action.process
+ };
+ case actionTypes.SOFTWARE_PRODUCT_PROCESS_COMPONENTS_EDITOR_CLOSE:
+ return {};
+
+ case actionTypes.processEditor.DATA_CHANGED:
+ return {
+ ...state,
+ data: {
+ ...state.data,
+ ...action.deltaData
+ }
+ };
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesEditorView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesEditorView.jsx
new file mode 100644
index 0000000..ca6d843
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesEditorView.jsx
@@ -0,0 +1,124 @@
+import React from 'react';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import Dropzone from 'react-dropzone';
+
+
+import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx';
+import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx';
+
+const SoftwareProductProcessEditorPropType = React.PropTypes.shape({
+ id: React.PropTypes.string,
+ name: React.PropTypes.string,
+ description: React.PropTypes.string,
+ artifactName: React.PropTypes.string
+});
+
+class SoftwareProductProcessesEditorView extends React.Component {
+
+ state = {
+ dragging: false,
+ files: []
+ };
+
+ static propTypes = {
+ data: SoftwareProductProcessEditorPropType,
+ previousData: SoftwareProductProcessEditorPropType,
+ isReadOnlyMode: React.PropTypes.bool,
+ onDataChanged: React.PropTypes.func,
+ onSubmit: React.PropTypes.func,
+ onCancel: React.PropTypes.func
+ };
+
+ render() {
+ let {isReadOnlyMode, onCancel, onDataChanged, data = {}} = this.props;
+ let {name, description, artifactName} = data;
+
+ return (
+ <div>
+ <ValidationForm
+ ref='validationForm'
+ isReadOnlyMode={isReadOnlyMode}
+ hasButtons={true}
+ labledButtons={true}
+ onSubmit={ () => this.submit() }
+ onReset={ () => onCancel() }
+ className='vsp-processes-editor'>
+ <div className={`vsp-processes-editor-data${isReadOnlyMode ? ' disabled' : '' }`}>
+ <Dropzone
+ className={`vsp-process-dropzone-view ${this.state.dragging ? 'active-dragging' : ''}`}
+ onDrop={files => this.handleImportSubmit(files)}
+ onDragEnter={() => this.setState({dragging:true})}
+ onDragLeave={() => this.setState({dragging:false})}
+ multiple={false}
+ disableClick={true}
+ ref='processEditorFileInput'
+ name='processEditorFileInput'
+ accept='*.*'>
+ <div className='row'>
+ <div className='col-md-6'>
+ <ValidationInput
+ onChange={name => onDataChanged({name})}
+ label={i18n('Name')}
+ value={name}
+ validations={{validateName: true, maxLength: 120, required: true}}
+ type='text'/>
+ <ValidationInput
+ label={i18n('Artifacts')}
+ value={artifactName}
+ type='text'
+ disabled/>
+ </div>
+ <div className='col-md-6'>
+ <div className='file-upload-box'>
+ <div className='drag-text'>{i18n('Drag & drop for upload')}</div>
+ <div className='or-text'>{i18n('or')}</div>
+ <div className='upload-btn primary-btn' onClick={() => this.refs.processEditorFileInput.open()}>
+ <span className='primary-btn-text'>{i18n('Select file')}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ <ValidationInput
+ onChange={description => onDataChanged({description})}
+ label={i18n('Notes')}
+ value={description}
+ name='vsp-process-description'
+ className='vsp-process-description'
+ validations={{maxLength: 1000}}
+ type='textarea'/>
+ </Dropzone>
+ </div>
+ </ValidationForm>
+ </div>
+ );
+ }
+
+ submit() {
+ const {data: process, previousData: previousProcess} = this.props;
+ let {files} = this.state;
+ let formData = new FormData();
+ if (files.length) {
+ let file = files[0];
+ formData.append('upload', file);
+ }
+
+ let updatedProcess = {
+ ...process,
+ formData: files.length ? formData : false
+ };
+ this.props.onSubmit({process: updatedProcess, previousProcess});
+ }
+
+
+ handleImportSubmit(files) {
+ let {onDataChanged} = this.props;
+ this.setState({
+ dragging: false,
+ complete: '0',
+ files
+ });
+ onDataChanged({artifactName: files[0].name});
+ }
+}
+
+export default SoftwareProductProcessesEditorView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesList.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesList.js
new file mode 100644
index 0000000..5f69328
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesList.js
@@ -0,0 +1,54 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js';
+import SoftwareProductComponentProcessesActionHelper from './SoftwareProductComponentProcessesActionHelper.js';
+
+import SoftwareProductComponentsProcessesListView from './SoftwareProductComponentsProcessesListView.jsx';
+
+const mapStateToProps = ({softwareProduct}) => {
+
+ let {softwareProductEditor: {data:currentSoftwareProduct = {}, isValidityData = true}, softwareProductComponents: {componentProcesses = {}}} = softwareProduct;
+ let{processesList = [], processesEditor = {}} = componentProcesses;
+ let {data} = processesEditor;
+ let isReadOnlyMode = VersionControllerUtils.isReadOnly(currentSoftwareProduct);
+
+ return {
+ currentSoftwareProduct,
+ isValidityData,
+ processesList,
+ isDisplayModal: Boolean(data),
+ isModalInEditMode: Boolean(data && data.id),
+ isReadOnlyMode
+ };
+
+};
+
+const mapActionsToProps = (dispatch, {softwareProductId}) => {
+
+ return {
+ onAddProcess: () => SoftwareProductComponentProcessesActionHelper.openEditor(dispatch),
+ onEditProcessClick: (process) => SoftwareProductComponentProcessesActionHelper.openEditor(dispatch, process),
+ onDeleteProcessClick: (process) => SoftwareProductComponentProcessesActionHelper.openDeleteProcessesConfirm(dispatch, {process, softwareProductId})
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(SoftwareProductComponentsProcessesListView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesListReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesListReducer.js
new file mode 100644
index 0000000..4bb124d
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentProcessesListReducer.js
@@ -0,0 +1,37 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './SoftwareProductComponentProcessesConstants.js';
+
+export default (state = [], action) => {
+ switch (action.type) {
+ case actionTypes.FETCH_SOFTWARE_PRODUCT_COMPONENTS_PROCESSES:
+ return [...action.processesList];
+ case actionTypes.EDIT_SOFTWARE_PRODUCT_COMPONENTS_PROCESS:
+ const indexForEdit = state.findIndex(process => process.id === action.process.id);
+ return [...state.slice(0, indexForEdit), action.process, ...state.slice(indexForEdit + 1)];
+ case actionTypes.ADD_SOFTWARE_PRODUCT_COMPONENTS_PROCESS:
+ return [...state, action.process];
+ case actionTypes.DELETE_SOFTWARE_PRODUCT_COMPONENTS_PROCESS:
+ return state.filter(process => process.id !== action.processId);
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentsProcessesConfirmationModal.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentsProcessesConfirmationModal.jsx
new file mode 100644
index 0000000..48fa862
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentsProcessesConfirmationModal.jsx
@@ -0,0 +1,45 @@
+import React from 'react';
+import {connect} from 'react-redux';
+import ConfirmationModalView from 'nfvo-components/confirmations/ConfirmationModalView.jsx';
+import SoftwareProductComponentProcessesActionHelper from './SoftwareProductComponentProcessesActionHelper.js';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+function renderMsg(processToDelete) {
+ let name = processToDelete ? processToDelete.name : '';
+ let msg = i18n('Are you sure you want to delete "{name}"?', {name});
+ return (
+ <div>
+ <p>{msg}</p>
+ </div>
+ );
+};
+
+const mapStateToProps = ({softwareProduct}) => {
+ let {softwareProductEditor, softwareProductComponents} = softwareProduct;
+ let {componentProcesses} = softwareProductComponents;
+ let {processToDelete} = componentProcesses;
+ let softwareProductId = softwareProductEditor.data.id;
+ const show = processToDelete !== false;
+ return {
+ show,
+ title: 'Warning!',
+ type: 'warning',
+ msg: renderMsg(processToDelete),
+ confirmationDetails: {processToDelete, softwareProductId}
+ };
+};
+
+const mapActionsToProps = (dispatch,{componentId, softwareProductId}) => {
+ return {
+ onConfirmed: ({processToDelete}) => {
+ SoftwareProductComponentProcessesActionHelper.deleteProcess(dispatch, {process: processToDelete, softwareProductId, componentId});
+ SoftwareProductComponentProcessesActionHelper.hideDeleteConfirm(dispatch);
+ },
+ onDeclined: () => {
+ SoftwareProductComponentProcessesActionHelper.hideDeleteConfirm(dispatch);
+ }
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(ConfirmationModalView);
+
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentsProcessesListView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentsProcessesListView.jsx
new file mode 100644
index 0000000..a8b07e9
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/processes/SoftwareProductComponentsProcessesListView.jsx
@@ -0,0 +1,125 @@
+import React from 'react';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import Modal from 'nfvo-components/modal/Modal.jsx';
+
+import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx';
+import ListEditorItemView from 'nfvo-components/listEditor/ListEditorItemView.jsx';
+
+import SoftwareProductProcessesEditor from './SoftwareProductComponentProcessesEditor.js';
+import SoftwareProductComponentsProcessesConfirmationModal from './SoftwareProductComponentsProcessesConfirmationModal.jsx';
+
+class SoftwareProductProcessesView extends React.Component {
+
+ state = {
+ localFilter: ''
+ };
+
+ static propTypes = {
+ onAddProcess: React.PropTypes.func,
+ onEditProcessClick: React.PropTypes.func,
+ onDeleteProcessClick: React.PropTypes.func,
+ isDisplayModal: React.PropTypes.bool,
+ isModalInEditMode: React.PropTypes.bool,
+ onStorageSelect: React.PropTypes.func,
+ componentId: React.PropTypes.string,
+ softwareProductId: React.PropTypes.string
+ };
+
+ render() {
+ let { softwareProductId, componentId} = this.props;
+
+ return (
+ <div className='vsp-processes-page'>
+ <div className='software-product-view'>
+ <div className='software-product-landing-view-right-side flex-column'>
+ {this.renderEditor()}
+ {this.renderProcessList()}
+ </div>
+ <SoftwareProductComponentsProcessesConfirmationModal
+ componentId={componentId}
+ softwareProductId={softwareProductId}/>
+ </div>
+ </div>
+ );
+ }
+
+ renderEditor() {
+ let {softwareProductId, componentId, isReadOnlyMode, isDisplayModal, isModalInEditMode} = this.props;
+ return (
+ <Modal show={isDisplayModal} bsSize='large' animation={true}>
+ <Modal.Header>
+ <Modal.Title>{isModalInEditMode ? i18n('Edit Process Details') : i18n('Create New Process Details')}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body className='edit-process-modal'>
+ <SoftwareProductProcessesEditor
+ componentId={componentId}
+ softwareProductId={softwareProductId}
+ isReadOnlyMode={isReadOnlyMode}/>
+ </Modal.Body>
+ </Modal>
+
+ );
+ }
+
+ renderProcessList() {
+ const {localFilter} = this.state;
+ let {onAddProcess, isReadOnlyMode} = this.props;
+ return (
+ <div className='processes-list'>
+ <ListEditorView
+ plusButtonTitle={i18n('Add Component Process Details')}
+ filterValue={localFilter}
+ placeholder={i18n('Filter Process')}
+ onAdd={onAddProcess}
+ isReadOnlyMode={isReadOnlyMode}
+ onFilter={filter => this.setState({localFilter: filter})}>
+ {this.filterList().map(processes => this.renderProcessListItem(processes, isReadOnlyMode))}
+ </ListEditorView>
+ </div>
+ );
+ }
+
+ renderProcessListItem(process, isReadOnlyMode) {
+ let {id, name, description, artifactName = ''} = process;
+ let {onEditProcessClick, onDeleteProcessClick} = this.props;
+ return (
+ <ListEditorItemView
+ key={id}
+ className='list-editor-item-view'
+ isReadOnlyMode={isReadOnlyMode}
+ onSelect={() => onEditProcessClick(process)}
+ onDelete={() => onDeleteProcessClick(process)}>
+
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Name')}</div>
+ <div className='name'>{name}</div>
+ </div>
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Artifact name')}</div>
+ <div className='artifact-name'>{artifactName}</div>
+ </div>
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Notes')}</div>
+ <div className='description'>{description}</div>
+ </div>
+ </ListEditorItemView>
+ );
+ }
+
+
+ filterList() {
+ let {processesList} = this.props;
+ let {localFilter} = this.state;
+ if (localFilter.trim()) {
+ const filter = new RegExp(escape(localFilter), 'i');
+ return processesList.filter(({name = '', description = ''}) => {
+ return escape(name).match(filter) || escape(description).match(filter);
+ });
+ }
+ else {
+ return processesList;
+ }
+ }
+}
+
+export default SoftwareProductProcessesView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/storage/SoftwareProductComponentStorage.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/storage/SoftwareProductComponentStorage.js
new file mode 100644
index 0000000..fbd3f81
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/storage/SoftwareProductComponentStorage.js
@@ -0,0 +1,48 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+
+import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js';
+
+import SoftwareProductComponentsActionHelper from 'sdc-app/onboarding/softwareProduct/components/SoftwareProductComponentsActionHelper.js';
+import SoftwareProductComponentStorageView from './SoftwareProductComponentStorageView.jsx';
+
+const mapStateToProps = ({softwareProduct}) => {
+ let {softwareProductEditor: {data: currentVSP}, softwareProductComponents} = softwareProduct;
+ let {componentEditor: {data: componentData , qdata, qschema}} = softwareProductComponents;
+ let isReadOnlyMode = VersionControllerUtils.isReadOnly(currentVSP);
+
+ return {
+ componentData,
+ qdata,
+ qschema,
+ isReadOnlyMode
+ };
+};
+
+const mapActionToProps = (dispatch, {softwareProductId, componentId}) => {
+ return {
+ onQDataChanged: ({data}) => SoftwareProductComponentsActionHelper.componentQuestionnaireUpdated(dispatch, {data}),
+ onSubmit: ({qdata}) => { return SoftwareProductComponentsActionHelper.updateSoftwareProductComponentQuestionnaire(dispatch, {softwareProductId, vspComponentId: componentId, qdata});}
+ };
+};
+
+export default connect(mapStateToProps, mapActionToProps, null, {withRef: true}) (SoftwareProductComponentStorageView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/storage/SoftwareProductComponentStorageView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/storage/SoftwareProductComponentStorageView.jsx
new file mode 100644
index 0000000..9c9600c
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/components/storage/SoftwareProductComponentStorageView.jsx
@@ -0,0 +1,124 @@
+import React from 'react';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx';
+import ValidationInput from'nfvo-components/input/validation/ValidationInput.jsx';
+
+
+class SoftwareProductComponentStorageView extends React.Component {
+
+ static propTypes = {
+ componentId: React.PropTypes.string,
+ onQDataChanged: React.PropTypes.func,
+ onSubmit: React.PropTypes.func,
+ isReadOnlyMode: React.PropTypes.bool
+ };
+
+ render() {
+ let {qdata, qschema, onQDataChanged, onSubmit, isReadOnlyMode} = this.props;
+
+ return(
+ <div className='vsp-component-questionnaire-view'>
+ <ValidationForm
+ ref='storageValidationForm'
+ hasButtons={false}
+ onSubmit={() => onSubmit({qdata})}
+ className='component-questionnaire-validation-form'
+ isReadOnlyMode={isReadOnlyMode}
+ onDataChanged={onQDataChanged}
+ data={qdata}
+ schema={qschema}>
+
+ <div className='section-title'>{i18n('Backup')}</div>
+ <div className='rows-section'>
+ <div className='row-flex-components input-row'>
+ <div className='single-col'>
+ <div className='vertical-flex'>
+ <label key='label' className='control-label'>{i18n('Backup Type')}</label>
+ <div className='radio-options-content-row'>
+ <ValidationInput
+ label={i18n('On Site')}
+ type='radiogroup'
+ pointer={'/storage/backup/backupType'}
+ className='radio-field'/>
+ </div>
+ </div>
+ </div>
+ <div className='single-col'>
+ <ValidationInput
+ type='text'
+ label={i18n('Backup Solution')}
+ pointer={'/storage/backup/backupSolution'}
+ className='section-field'/>
+ </div>
+ <div className='single-col'>
+ <ValidationInput
+ type='text'
+ label={i18n('Backup Storage Size (GB)')}
+ pointer={'/storage/backup/backupStorageSize'}
+ className='section-field'/>
+ </div>
+ <ValidationInput
+ type='select'
+ label={i18n('Backup NIC')}
+ pointer={'/storage/backup/backupNIC'}
+ className='section-field'/>
+ </div>
+ </div>
+
+ <div className='section-title'>{i18n('Snapshot Backup')}</div>
+ <div className='rows-section'>
+ <div className='row-flex-components input-row'>
+ <div className='single-col'>
+ <ValidationInput
+ type='text'
+ label={i18n('Snapshot Frequency (hours)')}
+ pointer={'/storage/snapshotBackup/snapshotFrequency'}
+ className='section-field'/>
+ </div>
+ <div className='empty-two-col' />
+ <div className='empty-col' />
+ </div>
+ </div>
+
+ <div className='section-title'>{i18n('Log Backup')}</div>
+ <div className='rows-section'>
+ <div className='row-flex-components input-row'>
+ <div className='single-col'>
+ <ValidationInput
+ type='text'
+ label={i18n('Size of Log Files (GB)')}
+ pointer={'/storage/logBackup/sizeOfLogFiles'}
+ className='section-field'/>
+ </div>
+ <div className='single-col'>
+ <ValidationInput
+ type='text'
+ label={i18n('Log Retention Period (days)')}
+ pointer={'/storage/logBackup/logRetentionPeriod'}
+ className='section-field'/>
+ </div>
+ <div className='single-col'>
+ <ValidationInput
+ type='text'
+ label={i18n('Log Backup Frequency (days)')}
+ pointer={'/storage/logBackup/logBackupFrequency'}
+ className='section-field'/>
+ </div>
+ <ValidationInput
+ type='text'
+ label={i18n('Log File Location')}
+ pointer={'/storage/logBackup/logFileLocation'}
+ className='section-field'/>
+ </div>
+ </div>
+ </ValidationForm>
+ </div>
+ );
+ }
+
+ save(){
+ return this.refs.storageValidationForm.handleFormSubmit(new Event('dummy'));
+ }
+}
+
+export default SoftwareProductComponentStorageView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreation.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreation.js
new file mode 100644
index 0000000..46308f0
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreation.js
@@ -0,0 +1,49 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+
+import OnboardingActionHelper from 'sdc-app/onboarding/OnboardingActionHelper.js';
+import SoftwareProductCreationActionHelper from './SoftwareProductCreationActionHelper.js';
+import SoftwareProductCreationView from './SoftwareProductCreationView.jsx';
+
+const mapStateToProps = ({finalizedLicenseModelList, softwareProduct: {softwareProductCreation, softwareProductCategories} }) => {
+ return {
+ data: softwareProductCreation.data,
+ softwareProductCategories,
+ finalizedLicenseModelList
+ };
+};
+
+const mapActionsToProps = (dispatch) => {
+ return {
+ onDataChanged: deltaData => SoftwareProductCreationActionHelper.changeData(dispatch, {deltaData}),
+ onCancel: () => SoftwareProductCreationActionHelper.resetData(dispatch),
+ onSubmit: (softwareProduct) => {
+ SoftwareProductCreationActionHelper.resetData(dispatch);
+ SoftwareProductCreationActionHelper.createSoftwareProduct(dispatch, {softwareProduct}).then(softwareProductId => {
+ let {vendorId: licenseModelId, licensingVersion} = softwareProduct;
+ OnboardingActionHelper.navigateToSoftwareProductLandingPage(dispatch, {softwareProductId, licenseModelId, licensingVersion});
+ });
+ }
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(SoftwareProductCreationView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationActionHelper.js
new file mode 100644
index 0000000..f4e51f1
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationActionHelper.js
@@ -0,0 +1,77 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
+import Configuration from 'sdc-app/config/Configuration.js';
+
+import SoftwareProductActionHelper from 'sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js';
+import {actionTypes} from './SoftwareProductCreationConstants.js';
+
+
+function baseUrl() {
+ const restPrefix = Configuration.get('restPrefix');
+ return `${restPrefix}/v1.0/vendor-software-products/`;
+}
+
+function createSoftwareProduct(softwareProduct) {
+ return RestAPIUtil.create(baseUrl(), {
+ ...softwareProduct,
+ icon: 'icon',
+ licensingData: {}
+ });
+}
+
+const SoftwareProductCreationActionHelper = {
+
+ open(dispatch) {
+ SoftwareProductActionHelper.loadSoftwareProductAssociatedData(dispatch);
+ dispatch({
+ type: actionTypes.OPEN
+ });
+ },
+
+ resetData(dispatch) {
+ dispatch({
+ type: actionTypes.RESET_DATA
+ });
+ },
+
+ changeData(dispatch, {deltaData}) {
+ dispatch({
+ type: actionTypes.DATA_CHANGED,
+ deltaData
+ });
+ },
+
+ createSoftwareProduct(dispatch, {softwareProduct}) {
+ return createSoftwareProduct(softwareProduct).then(response => {
+ SoftwareProductActionHelper.addSoftwareProduct(dispatch, {
+ softwareProduct: {
+ ...softwareProduct,
+ id: response.vspId
+ }
+ });
+ return response.vspId;
+ });
+ }
+
+};
+
+export default SoftwareProductCreationActionHelper;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationConstants.js
new file mode 100644
index 0000000..0a9cdb9
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationConstants.js
@@ -0,0 +1,27 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export const actionTypes = keyMirror({
+ OPEN: null,
+ RESET_DATA: null,
+ DATA_CHANGED: null
+});
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationReducer.js
new file mode 100644
index 0000000..5e3db09
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationReducer.js
@@ -0,0 +1,44 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './SoftwareProductCreationConstants.js';
+
+export default (state = {}, action) => {
+ switch (action.type) {
+ case actionTypes.OPEN:
+ return {
+ ...state,
+ data: {},
+ showModal: true
+ };
+ case actionTypes.DATA_CHANGED:
+ return {
+ ...state,
+ data: {
+ ...state.data,
+ ...action.deltaData
+ }
+ };
+ case actionTypes.RESET_DATA:
+ return {};
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationView.jsx
new file mode 100644
index 0000000..2c8f243
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/creation/SoftwareProductCreationView.jsx
@@ -0,0 +1,123 @@
+import React from 'react';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx';
+import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx';
+
+import SoftwareProductCategoriesHelper from 'sdc-app/onboarding/softwareProduct/SoftwareProductCategoriesHelper.js';
+
+
+const SoftwareProductPropType = React.PropTypes.shape({
+ id: React.PropTypes.string,
+ name: React.PropTypes.string,
+ description: React.PropTypes.string,
+ category: React.PropTypes.string,
+ subCategory: React.PropTypes.string,
+ vendorId: React.PropTypes.string
+});
+
+class SoftwareProductCreationView extends React.Component {
+
+ static propTypes = {
+ data: SoftwareProductPropType,
+ finalizedLicenseModelList: React.PropTypes.array,
+ softwareProductCategories: React.PropTypes.array,
+ onDataChanged: React.PropTypes.func.isRequired,
+ onSubmit: React.PropTypes.func.isRequired,
+ onCancel: React.PropTypes.func.isRequired
+ };
+
+ render() {
+ let {softwareProductCategories, data = {}, onDataChanged, onCancel} = this.props;
+ let {name, description, vendorId, subCategory} = data;
+
+ const vendorList = this.getVendorList();
+
+ return (
+ <div className='software-product-creation-page'>
+ <ValidationForm
+ ref='validationForm'
+ hasButtons={true}
+ onSubmit={() => this.submit() }
+ onReset={() => onCancel() }
+ labledButtons={true}>
+ <div className='software-product-form-row'>
+ <div className='software-product-inline-section'>
+ <ValidationInput
+ value={name}
+ label={i18n('Name')}
+ ref='software-product-name'
+ onChange={name => onDataChanged({name})}
+ validations={{validateName: true, maxLength: 25, required: true}}
+ type='text'
+ className='field-section'/>
+ <ValidationInput
+ onEnumChange={vendorId => onDataChanged({vendorId})}
+ value={vendorId}
+ label={i18n('Vendor')}
+ values={vendorList}
+ validations={{required: true}}
+ type='select'
+ className='field-section'/>
+ <ValidationInput
+ label={i18n('Category')}
+ type='select'
+ value={subCategory}
+ onChange={subCategory => this.onSelectSubCategory(subCategory)}
+ validations={{required: true}}
+ className='options-input-category'>
+ <option key='' value=''>{i18n('please select…')}</option>
+ {softwareProductCategories.map(category =>
+ category.subcategories &&
+ <optgroup
+ key={category.name}
+ label={category.name}>{category.subcategories.map(sub =>
+ <option key={sub.uniqueId} value={sub.uniqueId}>{`${sub.name} (${category.name})`}</option>)}
+ </optgroup>)
+ }
+ </ValidationInput>
+ </div>
+ <div className='software-product-inline-section'>
+ <ValidationInput
+ value={description}
+ label={i18n('Description')}
+ ref='description'
+ onChange={description => onDataChanged({description})}
+ validations={{freeEnglishText: true, maxLength: 1000, required: true}}
+ type='textarea'
+ className='field-section'/>
+ </div>
+ </div>
+ </ValidationForm>
+ </div>
+ );
+ }
+
+ getVendorList() {
+ let {finalizedLicenseModelList} = this.props;
+
+ return [{enum: '', title: i18n('please select...')}].concat(finalizedLicenseModelList.map(vendor => {
+ return {
+ enum: vendor.id,
+ title: vendor.vendorName
+ };
+ }));
+ }
+
+ onSelectSubCategory(subCategory) {
+ let {softwareProductCategories, onDataChanged} = this.props;
+ let category = SoftwareProductCategoriesHelper.getCurrentCategoryOfSubCategory(subCategory, softwareProductCategories);
+ onDataChanged({category, subCategory});
+ }
+
+ create(){
+ this.refs.validationForm.handleFormSubmit(new Event('dummy'));
+ }
+
+ submit() {
+ const {data:softwareProduct, finalizedLicenseModelList} = this.props;
+ softwareProduct.vendorName = finalizedLicenseModelList.find(vendor => vendor.id === softwareProduct.vendorId).vendorName;
+ this.props.onSubmit(softwareProduct);
+ }
+}
+
+export default SoftwareProductCreationView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/details/SoftwareProductDetails.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/details/SoftwareProductDetails.js
new file mode 100644
index 0000000..16a100c
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/details/SoftwareProductDetails.js
@@ -0,0 +1,66 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+
+import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js';
+import SoftwareProductActionHelper from 'sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js';
+import SoftwareProductDetailsView from './SoftwareProductDetailsView.jsx';
+
+export const mapStateToProps = ({finalizedLicenseModelList, softwareProduct, licenseModel: {licenseAgreement, featureGroup}}) => {
+ let {softwareProductEditor: {data: currentSoftwareProduct}, softwareProductCategories, softwareProductQuestionnaire} = softwareProduct;
+ let {licensingData = {}, licensingVersion} = currentSoftwareProduct;
+ let licenseAgreementList = [], filteredFeatureGroupsList = [];
+ if(licensingVersion && licensingVersion !== '') {
+ licenseAgreementList = licenseAgreement.licenseAgreementList;
+ let selectedLicenseAgreement = licenseAgreementList.find(la => la.id === licensingData.licenseAgreement);
+ if (selectedLicenseAgreement) {
+ let featureGroupsList = featureGroup.featureGroupsList.filter(({referencingLicenseAgreements}) => referencingLicenseAgreements.includes(selectedLicenseAgreement.id));
+ if (featureGroupsList.length) {
+ filteredFeatureGroupsList = featureGroupsList.map(featureGroup => ({enum: featureGroup.id, title: featureGroup.name}));
+ }
+ }
+ }
+ let {qdata, qschema} = softwareProductQuestionnaire;
+ let isReadOnlyMode = VersionControllerUtils.isReadOnly(currentSoftwareProduct);
+
+ return {
+ currentSoftwareProduct,
+ softwareProductCategories,
+ licenseAgreementList,
+ featureGroupsList: filteredFeatureGroupsList,
+ finalizedLicenseModelList,
+ qdata,
+ qschema,
+ isReadOnlyMode
+ };
+};
+
+export const mapActionsToProps = (dispatch) => {
+ return {
+ onDataChanged: deltaData => SoftwareProductActionHelper.softwareProductEditorDataChanged(dispatch, {deltaData}),
+ onVendorParamChanged: deltaData => SoftwareProductActionHelper.softwareProductEditorVendorChanged(dispatch, {deltaData}),
+ onQDataChanged: ({data}) => SoftwareProductActionHelper.softwareProductQuestionnaireUpdate(dispatch, {data}),
+ onValidityChanged: isValidityData => SoftwareProductActionHelper.setIsValidityData(dispatch, {isValidityData}),
+ onSubmit: (softwareProduct, qdata) =>{ return SoftwareProductActionHelper.updateSoftwareProduct(dispatch, {softwareProduct, qdata});}
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(SoftwareProductDetailsView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/details/SoftwareProductDetailsReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/details/SoftwareProductDetailsReducer.js
new file mode 100644
index 0000000..e060706
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/details/SoftwareProductDetailsReducer.js
@@ -0,0 +1,63 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from 'sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js';
+
+export default (state = {}, action) => {
+ switch (action.type) {
+ case actionTypes.softwareProductEditor.OPEN:
+ return {
+ ...state,
+ data: {}
+ };
+ case actionTypes.softwareProductEditor.DATA_CHANGED:
+ return {
+ ...state,
+ data: {
+ ...state.data,
+ ...action.deltaData
+ }
+ };
+ case actionTypes.softwareProductEditor.UPLOAD_CONFIRMATION:
+ return {
+ ...state,
+ uploadData:action.uploadData
+ };
+ case actionTypes.softwareProductEditor.IS_VALIDITY_DATA_CHANGED:
+ return {
+ ...state,
+ isValidityData: action.isValidityData
+ };
+ case actionTypes.softwareProductEditor.CLOSE:
+ return {};
+ case actionTypes.SOFTWARE_PRODUCT_LOADED:
+ return {
+ ...state,
+ data: action.response
+ };
+ case actionTypes.TOGGLE_NAVIGATION_ITEM:
+ return {
+ ...state,
+ mapOfExpandedIds: action.mapOfExpandedIds
+ };
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/details/SoftwareProductDetailsView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/details/SoftwareProductDetailsView.jsx
new file mode 100644
index 0000000..75a5797
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/details/SoftwareProductDetailsView.jsx
@@ -0,0 +1,264 @@
+import React, {Component, PropTypes} from 'react';
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import Form from 'nfvo-components/input/validation/ValidationForm.jsx';
+import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx';
+import SoftwareProductCategoriesHelper from 'sdc-app/onboarding/softwareProduct/SoftwareProductCategoriesHelper.js';
+
+class SoftwareProductDetails extends Component {
+
+ static propTypes = {
+ vendorName: PropTypes.string,
+ currentSoftwareProduct: PropTypes.shape({
+ id: PropTypes.string,
+ name: PropTypes.string,
+ description: PropTypes.string,
+ category: PropTypes.string,
+ subCategory: PropTypes.string,
+ vendorId: PropTypes.string,
+ vendorName: PropTypes.string,
+ licensingVersion: PropTypes.string,
+ licensingData: PropTypes.shape({
+ licenceAgreement: PropTypes.string,
+ featureGroups: PropTypes.array
+ })
+ }),
+ softwareProductCategories: PropTypes.array,
+ finalizedLicenseModelList: PropTypes.array,
+ licenseAgreementList: PropTypes.array,
+ featureGroupsList: PropTypes.array,
+ onSubmit: PropTypes.func.isRequired,
+ onDataChanged: PropTypes.func.isRequired,
+ onValidityChanged: PropTypes.func.isRequired,
+ qdata: PropTypes.object.isRequired,
+ qschema: PropTypes.object.isRequired,
+ onQDataChanged: PropTypes.func.isRequired,
+ onVendorParamChanged: PropTypes.func.isRequired
+ };
+
+ state = {
+ licensingVersionsList: []
+ };
+
+ render() {
+ let {softwareProductCategories, finalizedLicenseModelList, onDataChanged, featureGroupsList, licenseAgreementList, currentSoftwareProduct} = this.props;
+ let {name, description, vendorId, licensingVersion, subCategory, licensingData = {}} = currentSoftwareProduct;
+ let licensingVersionsList = this.state.licensingVersionsList.length > 0 ? this.state.licensingVersionsList : this.refreshVendorVersionsList(vendorId);
+ let {qdata, qschema, onQDataChanged} = this.props;
+ let {isReadOnlyMode} = this.props;
+
+ return (
+ <div className='vsp-details-page'>
+ <Form
+ ref='validationForm'
+ className='vsp-general-tab'
+ hasButtons={false}
+ onSubmit={() => this.props.onSubmit(currentSoftwareProduct, qdata)}
+ onValidityChanged={(isValidityData) => this.props.onValidityChanged(isValidityData)}
+ isReadOnlyMode={isReadOnlyMode}>
+ <div className='section-title general'>{i18n('General')}</div>
+ <div className='vsp-general-tab-inline-section'>
+ <div className='vsp-general-tab-sub-section'>
+ <ValidationInput
+ label={i18n('Name')}
+ type='text'
+ value={name}
+ onChange={name => onDataChanged({name})}
+ validations={{validateName: true, maxLength: 120, required: true}}
+ className='field-section'/>
+ <ValidationInput
+ label={i18n('Vendor')}
+ type='select'
+ selectedEnum={vendorId}
+ onEnumChange={vendorId => this.onVendorParamChanged({vendorId})}
+ className='field-section'>
+ {finalizedLicenseModelList.map(lm => <option key={lm.id} value={lm.id}>{lm.vendorName}</option>)}
+ </ValidationInput>
+ <div className='input-row'>
+ <ValidationInput
+ label={i18n('Category')}
+ type='select'
+ selectedEnum={subCategory}
+ onEnumChange={subCategory => this.onSelectSubCategory(subCategory)}
+ className='field-section'>
+ {
+ softwareProductCategories.map(category =>
+ category.subcategories &&
+ <optgroup
+ key={category.name}
+ label={category.name}>{category.subcategories.map(sub =>
+ <option
+ key={sub.uniqueId}
+ value={sub.uniqueId}>{`${sub.name} (${category.name})`}</option>)}
+ </optgroup>
+ )
+ }
+ </ValidationInput>
+ </div>
+ </div>
+ <div className='vsp-general-tab-sub-section input-row'>
+ <ValidationInput
+ label={i18n('Description')}
+ type='textarea'
+ value={description}
+ onChange={description => onDataChanged({description})}
+ className='field-section'
+ validations={{required: true}}/>
+ </div>
+ </div>
+ <div className='vsp-general-tab-section licenses'>
+ <div className='section-title'>{i18n('Licenses')}</div>
+ <div className='vsp-general-tab-inline-section input-row'>
+ <ValidationInput
+ onEnumChange={licensingVersion => this.onVendorParamChanged({vendorId, licensingVersion})}
+ selectedEnum={licensingVersion}
+ label={i18n('Licensing Version')}
+ values={licensingVersionsList}
+ type='select'
+ className='field-section'/>
+ <ValidationInput
+ label={i18n('License Agreement')}
+ type='select'
+ selectedEnum={licensingData.licenseAgreement}
+ className='field-section'
+ onEnumChange={(licenseAgreement) => this.onLicensingDataChanged({licenseAgreement, featureGroups: []})}>
+ <option key='placeholder' value=''>{i18n('Select...')}</option>
+ {licenseAgreementList.map(la => <option value={la.id} key={la.id}>{la.name}</option>)}
+ </ValidationInput>
+ </div>
+ <div className='vsp-general-tab-inline-section input-row'>
+ {licensingData.licenseAgreement && (
+ <ValidationInput
+ type='select'
+ isMultiSelect={true}
+ onEnumChange={featureGroups => this.onFeatureGroupsChanged({featureGroups})}
+ multiSelectedEnum={licensingData.featureGroups}
+ name='feature-groups'
+ label={i18n('Feature Groups')}
+ clearable={false}
+ values={featureGroupsList}/>)
+ }
+ </div>
+ </div>
+ </Form>
+ <Form
+ data={qdata}
+ schema={qschema}
+ onDataChanged={onQDataChanged}
+ className='vsp-general-tab'
+ hasButtons={false}
+ isReadOnlyMode={isReadOnlyMode}>
+ <div className='vsp-general-tab-section'>
+ <div className='section-title'> {i18n('Availability')} </div>
+ <div className='vsp-general-tab-inline-section'>
+ <div className='vsp-general-tab-sub-section input-row'>
+ <ValidationInput
+ label={i18n('Use Availability Zones for High Availability')}
+ type='checkbox'
+ pointer='/general/availability/useAvailabilityZonesForHighAvailability'/>
+ </div>
+ </div>
+ <div className='section-title'> {i18n('Regions')} </div>
+ <div className='vsp-general-tab-inline-section'>
+ <div className='vsp-general-tab-sub-section input-row'>
+ <ValidationInput
+ type='select'
+ laebl='Ziv'
+ pointer='/general/regionsData/regions'/>
+ </div>
+ </div>
+ <div className='section-title'> {i18n('Storage Data Replication')} </div>
+ <div className='vsp-general-tab-inline-section'>
+ <div className='vsp-general-tab-sub-section'>
+ <ValidationInput
+ label={i18n('Storage Replication Size (GB)')}
+ type='text'
+ pointer='/general/storageDataReplication/storageReplicationSize'
+ className='field-section'/>
+ <ValidationInput
+ label={i18n('Storage Replication Source')}
+ type='text'
+ pointer='/general/storageDataReplication/storageReplicationSource'
+ className='field-section'/>
+ </div>
+ <div className='vsp-general-tab-sub-section'>
+ <ValidationInput
+ label={i18n('Storage Replication Frequency (minutes)')}
+ type='text'
+ pointer='/general/storageDataReplication/storageReplicationFrequency'
+ className='field-section'/>
+ <ValidationInput
+ label={i18n('Storage Replication Destination')}
+ type='text'
+ pointer='/general/storageDataReplication/storageReplicationDestination'
+ className='field-section'/>
+ </div>
+ </div>
+ </div>
+ </Form>
+ </div>
+ );
+ }
+
+ onVendorParamChanged({vendorId, licensingVersion}) {
+ let {finalizedLicenseModelList, onVendorParamChanged} = this.props;
+ if(!licensingVersion) {
+ const licensingVersionsList = this.refreshVendorVersionsList(vendorId);
+ licensingVersion = licensingVersionsList.length > 0 ? licensingVersionsList[0].enum : '';
+ }
+ let vendorName = finalizedLicenseModelList.find(licenseModelItem => licenseModelItem.id === vendorId).vendorName || '';
+ let deltaData = {
+ vendorId,
+ vendorName,
+ licensingVersion,
+ licensingData: {}
+ };
+ onVendorParamChanged(deltaData);
+ }
+
+ refreshVendorVersionsList(vendorId) {
+ if(!vendorId) {
+ return [];
+ }
+
+ let {finalVersions} = this.props.finalizedLicenseModelList.find(vendor => vendor.id === vendorId);
+
+ let licensingVersionsList = [{
+ enum: '',
+ title: i18n('Select...')
+ }];
+ if(finalVersions) {
+ finalVersions.forEach(version => licensingVersionsList.push({
+ enum: version,
+ title: version
+ }));
+ }
+
+ return licensingVersionsList;
+ }
+
+ onSelectSubCategory(subCategory) {
+ let {softwareProductCategories, onDataChanged} = this.props;
+ let category = SoftwareProductCategoriesHelper.getCurrentCategoryOfSubCategory(subCategory, softwareProductCategories);
+ onDataChanged({category, subCategory});
+ }
+
+ onFeatureGroupsChanged({featureGroups}) {
+ this.onLicensingDataChanged({featureGroups});
+ }
+
+ onLicensingDataChanged(deltaData) {
+ this.props.onDataChanged({
+ licensingData: {
+ ...this.props.currentSoftwareProduct.licensingData,
+ ...deltaData
+ }
+ });
+ }
+
+ save(){
+ return this.refs.validationForm.handleFormSubmit(new Event('dummy'));
+ }
+}
+
+export default SoftwareProductDetails;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPage.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPage.js
new file mode 100644
index 0000000..7604f58
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPage.js
@@ -0,0 +1,97 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js';
+import NotificationConstants from 'nfvo-components/notifications/NotificationConstants.js';
+import OnboardingActionHelper from 'sdc-app/onboarding/OnboardingActionHelper.js';
+import SoftwareProductActionHelper from 'sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js';
+import LandingPageView from './SoftwareProductLandingPageView.jsx';
+
+const mapStateToProps = ({softwareProduct, licenseModel: {licenseAgreement}}) => {
+ let {softwareProductEditor: {data:currentSoftwareProduct = {}}, softwareProductComponents, softwareProductCategories = []} = softwareProduct;
+ let {licensingData = {}} = currentSoftwareProduct;
+ let {licenseAgreementList} = licenseAgreement;
+ let {componentsList} = softwareProductComponents;
+ let licenseAgreementName = licenseAgreementList.find(la => la.id === licensingData.licenseAgreement);
+ if (licenseAgreementName) {
+ licenseAgreementName = licenseAgreementName.name;
+ }
+
+ let categoryName = '', subCategoryName = '', fullCategoryDisplayName = '';
+ const category = softwareProductCategories.find(ca => ca.uniqueId === currentSoftwareProduct.category);
+ if (category) {
+ categoryName = category.name;
+ const subcategories = category.subcategories || [];
+ const subcat = subcategories.find(sc => sc.uniqueId === currentSoftwareProduct.subCategory);
+ subCategoryName = subcat && subcat.name ? subcat.name : '';
+ }
+ fullCategoryDisplayName = `${subCategoryName} (${categoryName})`;
+
+ const isReadOnlyMode = VersionControllerUtils.isReadOnly(currentSoftwareProduct);
+
+ return {
+ currentSoftwareProduct: {
+ ...currentSoftwareProduct,
+ licenseAgreementName,
+ fullCategoryDisplayName
+ },
+ isReadOnlyMode,
+ componentsList
+ };
+};
+
+const mapActionsToProps = (dispatch, {version}) => {
+ return {
+ onDetailsSelect: ({id: softwareProductId, vendorId: licenseModelId}) => OnboardingActionHelper.navigateToSoftwareProductDetails(dispatch, {
+ softwareProductId,
+ licenseModelId
+ }),
+ onAttachmentsSelect: ({id: softwareProductId}) => OnboardingActionHelper.navigateToSoftwareProductAttachments(dispatch, {softwareProductId}),
+ onUpload: (softwareProductId, formData) =>
+ SoftwareProductActionHelper.uploadFile(dispatch, {
+ softwareProductId,
+ formData,
+ failedNotificationTitle: i18n('Upload validation failed')
+ }),
+ onUploadConfirmation: (softwareProductId, formData) =>
+ SoftwareProductActionHelper.uploadConfirmation(dispatch, {
+ softwareProductId,
+ formData,
+ failedNotificationTitle: i18n('Upload validation failed')}),
+
+ onInvalidFileSizeUpload: () => dispatch({
+ type: NotificationConstants.NOTIFY_ERROR,
+ data: {
+ title: i18n('Upload Failed'),
+ msg: i18n('no zip file was uploaded or zip file doesn\'t exist')
+ }
+ }),
+ onComponentSelect: ({id: softwareProductId, componentId}) => {
+ OnboardingActionHelper.navigateToSoftwareProductComponentGeneralAndUpdateLeftPanel(dispatch, {softwareProductId, componentId, version });
+ },
+ /** for the next version */
+ onAddComponent: () => SoftwareProductActionHelper.addComponent(dispatch)
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(LandingPageView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageUploadConfirmationModal.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageUploadConfirmationModal.jsx
new file mode 100644
index 0000000..4a84883
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageUploadConfirmationModal.jsx
@@ -0,0 +1,38 @@
+import {connect} from 'react-redux';
+import ConfirmationModalView from 'nfvo-components/confirmations/ConfirmationModalView.jsx';
+import SoftwareProductActionHelper from 'sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js';
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+const mapStateToProps = ({softwareProduct}) => {
+ let {softwareProductEditor} = softwareProduct;
+ let {uploadData} = softwareProductEditor;
+ const show = uploadData ? true : false;
+ return {
+ show,
+ title: 'Warning!',
+ type: 'warning',
+ msg: i18n('Upload will erase existing data. Do you want to continue?'),
+ confirmationDetails: {uploadData}
+ };
+};
+
+const mapActionsToProps = (dispatch) => {
+ return {
+ onConfirmed: ({uploadData}) => {
+ let {softwareProductId, formData, failedNotificationTitle} = uploadData;
+ SoftwareProductActionHelper.uploadFile(dispatch, {
+ softwareProductId,
+ formData,
+ failedNotificationTitle
+ });
+ SoftwareProductActionHelper.hideUploadConfirm(dispatch);
+ },
+ onDeclined: () => {
+ SoftwareProductActionHelper.hideUploadConfirm(dispatch);
+ }
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(ConfirmationModalView);
+
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageView.jsx
new file mode 100644
index 0000000..cf7c7a3
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageView.jsx
@@ -0,0 +1,272 @@
+import React from 'react';
+import classnames from 'classnames';
+import Dropzone from 'react-dropzone';
+
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx';
+import ListEditorItemView from 'nfvo-components/listEditor/ListEditorItemView.jsx';
+
+import FontAwesome from 'react-fontawesome';
+import SoftwareProductLandingPageUploadConfirmationModal from './SoftwareProductLandingPageUploadConfirmationModal.jsx';
+
+
+const SoftwareProductPropType = React.PropTypes.shape({
+ name: React.PropTypes.string,
+ description: React.PropTypes.string,
+ version: React.PropTypes.string,
+ id: React.PropTypes.string,
+ categoryId: React.PropTypes.string,
+ vendorId: React.PropTypes.string,
+ status: React.PropTypes.string,
+ licensingData: React.PropTypes.object,
+ validationData: React.PropTypes.object
+});
+
+const ComponentPropType = React.PropTypes.shape({
+ id: React.PropTypes.string,
+ name: React.PropTypes.string,
+ displayName: React.PropTypes.string,
+ description: React.PropTypes.string
+});
+
+class SoftwareProductLandingPageView extends React.Component {
+
+ state = {
+ localFilter: '',
+ fileName: '',
+ dragging: false,
+ files: []
+ };
+
+ static propTypes = {
+ currentSoftwareProduct: SoftwareProductPropType,
+ isReadOnlyMode: React.PropTypes.bool,
+ componentsList: React.PropTypes.arrayOf(ComponentPropType),
+ onDetailsSelect: React.PropTypes.func,
+ onAttachmentsSelect: React.PropTypes.func,
+ onUpload: React.PropTypes.func,
+ onUploadConfirmation: React.PropTypes.func,
+ onInvalidFileSizeUpload: React.PropTypes.func,
+ onComponentSelect: React.PropTypes.func,
+ onAddComponent: React.PropTypes.func
+ };
+
+ render() {
+ let {currentSoftwareProduct, isReadOnlyMode, componentsList = []} = this.props;
+ return (
+ <div className='software-product-landing-wrapper'>
+ <Dropzone
+ className={classnames('software-product-landing-view', {'active-dragging': this.state.dragging})}
+ onDrop={files => this.handleImportSubmit(files, isReadOnlyMode)}
+ onDragEnter={() => this.handleOnDragEnter(isReadOnlyMode)}
+ onDragLeave={() => this.setState({dragging:false})}
+ multiple={false}
+ disableClick={true}
+ ref='fileInput'
+ name='fileInput'
+ accept='.zip'
+ disabled>
+ <div className='draggable-wrapper'>
+ <div className='software-product-landing-view-top'>
+ <div className='row'>
+ {this.renderProductSummary(currentSoftwareProduct)}
+ {this.renderProductDetails(currentSoftwareProduct, isReadOnlyMode)}
+ </div>
+ </div>
+ </div>
+ </Dropzone>
+ {
+ componentsList.length > 0 && this.renderComponents()
+ }
+ <SoftwareProductLandingPageUploadConfirmationModal confirmationButtonText={i18n('Continue')}/>
+ </div>
+ );
+ }
+
+ handleOnDragEnter(isReadOnlyMode) {
+ if (!isReadOnlyMode) {
+ this.setState({dragging: true});
+ }
+ }
+
+ renderProductSummary(currentSoftwareProduct) {
+ let {name = '', description = '', vendorName = '', fullCategoryDisplayName = '', licenseAgreementName = ''} = currentSoftwareProduct;
+ let {onDetailsSelect} = this.props;
+ return (
+ <div className='details-panel'>
+ <div className='software-product-landing-view-heading-title'>{i18n('Software Product Details')}</div>
+ <div
+ className='software-product-landing-view-top-block clickable'
+ onClick={() => onDetailsSelect(currentSoftwareProduct)}>
+ <div className='details-container'>
+ <div className='single-detail-section title-section'>
+ <div>
+ <div>{name}</div>
+ </div>
+ </div>
+ <div className='multiple-details-section'>
+ <div className='detail-col' >
+ <div className='title'>{i18n('Vendor')}</div>
+ <div className='description'>{vendorName}</div>
+ </div>
+ <div className='detail-col'>
+ <div className='title'>{i18n('Category')}</div>
+ <div className='description'>{fullCategoryDisplayName}</div>
+ </div>
+ <div className='detail-col'>
+ <div className='title extra-large'>{i18n('License Agreement')}</div>
+ <div className='description'>
+ {this.renderLicenseAgreement(licenseAgreementName)}
+ </div>
+ </div>
+ </div>
+ <div className='single-detail-section'>
+ <div className='title'>{i18n('Description')}</div>
+ <div className='description'>{description}</div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ renderProductDetails(currentSoftwareProduct, isReadOnlyMode) {
+ let {validationData} = currentSoftwareProduct;
+ let {onAttachmentsSelect} = this.props;
+ let details = {
+ heatTemplates: validationData ? '1' : '0',
+ images: '0',
+ otherArtifacts: '0'
+ };
+
+ return (
+ <div className='details-panel'>
+ <div className='software-product-landing-view-heading-title'>{i18n('Software Product Attachments')}</div>
+ <div className='software-product-landing-view-top-block'>
+ <div
+ className='software-product-landing-view-top-block-col'
+ onClick={() => onAttachmentsSelect(currentSoftwareProduct)}>
+ <div>
+ <div className='attachment-details'>{i18n('HEAT Templates')} (<span
+ className='attachment-details-count'>{details.heatTemplates}</span>)
+ </div>
+ <div className='attachment-details'>{i18n('Images')} (<span
+ className='attachment-details-count'>{details.images}</span>)
+ </div>
+ <div className='attachment-details'>{i18n('Other Artifacts')} (<span
+ className='attachment-details-count'>{details.otherArtifacts}</span>)
+ </div>
+ </div>
+ </div>
+ <div
+ className={classnames('software-product-landing-view-top-block-col-upl', {'disabled': isReadOnlyMode})}>
+ <div className='drag-text'>{i18n('Drag & drop for upload')}</div>
+ <div className='or-text'>{i18n('or')}</div>
+ <div className='upload-btn primary-btn' onClick={() => this.refs.fileInput.open()}>
+ <span className='primary-btn-text'>{i18n('Select file')}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ renderComponents() {
+ const {localFilter} = this.state;
+
+ return (
+ <ListEditorView
+ title={i18n('Virtual Function Components')}
+ filterValue={localFilter}
+ placeholder={i18n('Filter Components')}
+ onFilter={filter => this.setState({localFilter: filter})}>
+ {this.filterList().map(component => this.renderComponentsListItem(component))}
+ </ListEditorView>
+ );
+ }
+
+ renderComponentsListItem(component) {
+ let {id: componentId, name, displayName, description = ''} = component;
+ let {currentSoftwareProduct: {id}, onComponentSelect} = this.props;
+ return (
+ <ListEditorItemView
+ key={name + Math.floor(Math.random() * (100 - 1) + 1).toString()}
+ className='list-editor-item-view'
+ onSelect={() => onComponentSelect({id, componentId})}>
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Component')}</div>
+ <div className='name'>{displayName}</div>
+ </div>
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Description')}</div>
+ <div className='description'>{description}</div>
+ </div>
+ </ListEditorItemView>
+ );
+ }
+
+ renderLicenseAgreement(licenseAgreementName) {
+ if (!licenseAgreementName) {
+ return (<FontAwesome name='exclamation-triangle' className='warning-icon'/>);
+ }
+ return (licenseAgreementName);
+ }
+
+
+ filterList() {
+ let {componentsList = []} = this.props;
+
+ let {localFilter} = this.state;
+ if (localFilter.trim()) {
+ const filter = new RegExp(escape(localFilter), 'i');
+ return componentsList.filter(({displayName = '', description = ''}) => {
+ return escape(displayName).match(filter) || escape(description).match(filter);
+ });
+ }
+ else {
+ return componentsList;
+ }
+ }
+
+ handleImportSubmit(files, isReadOnlyMode) {
+ if (isReadOnlyMode) {
+ return;
+ }
+ if (files[0] && files[0].size) {
+ this.setState({
+ fileName: files[0].name,
+ dragging: false,
+ complete: '0',
+ });
+ this.startUploading(files);
+ }
+ else {
+ this.props.onInvalidFileSizeUpload();
+ }
+
+ }
+
+ startUploading(files) {
+ let {onUpload, currentSoftwareProduct, onUploadConfirmation} = this.props;
+
+ let {validationData} = currentSoftwareProduct;
+
+ if (!(files && files.length)) {
+ return;
+ }
+ let file = files[0];
+ let formData = new FormData();
+ formData.append('upload', file);
+ this.refs.fileInput.value = '';
+
+ if (validationData) {
+ onUploadConfirmation(currentSoftwareProduct.id, formData);
+ }else {
+ onUpload(currentSoftwareProduct.id, formData);
+ }
+
+ }
+}
+
+export default SoftwareProductLandingPageView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworks.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworks.js
new file mode 100644
index 0000000..dadc777
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworks.js
@@ -0,0 +1,30 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import SoftwareProductNetworksView from './SoftwareProductNetworksView.jsx';
+
+export const mapStateToProps = ({softwareProduct}) => {
+ let {softwareProductNetworks: {networksList = []}} = softwareProduct;
+ return {networksList};
+};
+
+export default connect(mapStateToProps, null, null, {withRef: true})(SoftwareProductNetworksView);
+
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksActionHelper.js
new file mode 100644
index 0000000..d0e29bc
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksActionHelper.js
@@ -0,0 +1,47 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './SoftwareProductNetworksConstants.js';
+import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
+import Configuration from 'sdc-app/config/Configuration.js';
+
+function baseUrl(svpId) {
+ const restPrefix = Configuration.get('restPrefix');
+ return `${restPrefix}/v1.0/vendor-software-products/${svpId}/networks`;
+}
+
+
+function fetchNetworksList(softwareProductId, version) {
+ let versionQuery = version ? `?version=${version}` : '';
+ return RestAPIUtil.fetch(`${baseUrl(softwareProductId)}${versionQuery}`);
+}
+
+const SoftwareProductNetworksActionHelper = {
+ fetchNetworksList(dispatch, {softwareProductId, version}) {
+ return fetchNetworksList(softwareProductId, version).then(response => {
+ dispatch({
+ type: actionTypes.FETCH_SOFTWARE_PRODUCT_NETWORKS,
+ networksList: response.results
+ });
+ });
+ }
+};
+
+export default SoftwareProductNetworksActionHelper;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksConstants.js
new file mode 100644
index 0000000..d428d21
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksConstants.js
@@ -0,0 +1,25 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export const actionTypes = keyMirror({
+ FETCH_SOFTWARE_PRODUCT_NETWORKS: null,
+});
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksListReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksListReducer.js
new file mode 100644
index 0000000..0c9c623
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksListReducer.js
@@ -0,0 +1,30 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './SoftwareProductNetworksConstants.js';
+
+export default (state = [], action) => {
+ switch (action.type) {
+ case actionTypes.FETCH_SOFTWARE_PRODUCT_NETWORKS:
+ return [...action.networksList];
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksView.jsx
new file mode 100644
index 0000000..bd47467
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/networks/SoftwareProductNetworksView.jsx
@@ -0,0 +1,73 @@
+import React from 'react';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx';
+import ListEditorItemView from 'nfvo-components/listEditor/ListEditorItemView.jsx';
+
+class SoftwareProductNetworksView extends React.Component {
+
+ static propTypes = {
+ networksList: React.PropTypes.arrayOf(React.PropTypes.shape({
+ id: React.PropTypes.string.isRequired,
+ name: React.PropTypes.string.isRequired,
+ dhcp: React.PropTypes.bool.isRequired
+ })).isRequired
+ };
+
+ state = {
+ localFilter: ''
+ };
+
+ render() {
+ const {localFilter} = this.state;
+
+ return (
+ <div className='vsp-networks-page'>
+ <ListEditorView
+ title={i18n('Networks')}
+ filterValue={localFilter}
+ placeholder={i18n('Filter Networks')}
+ onFilter={filter => this.setState({localFilter: filter})}>
+ {this.filterList().map(network => this.renderNetworksListItem(network))}
+ </ListEditorView>
+ </div>
+ );
+ }
+
+ renderNetworksListItem(network) {
+ let {id, name, dhcp} = network;
+ return (
+ <ListEditorItemView
+ key={id}
+ className='list-editor-item-view'
+ isReadOnlyMode={true}>
+
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Name')}</div>
+ <div className='name'>{name}</div>
+ </div>
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('DHCP')}</div>
+ <div className='artifact-name'>{dhcp ? i18n('YES') : i18n('NO')}</div>
+ </div>
+ </ListEditorItemView>
+ );
+ }
+
+ filterList() {
+ let {networksList} = this.props;
+
+ let {localFilter} = this.state;
+ if (localFilter.trim()) {
+ const filter = new RegExp(escape(localFilter), 'i');
+ return networksList.filter(({name = ''}) => {
+ return escape(name).match(filter);
+ });
+ }
+ else {
+ return networksList;
+ }
+ }
+}
+
+export default SoftwareProductNetworksView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcesses.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcesses.js
new file mode 100644
index 0000000..5c3a8da
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcesses.js
@@ -0,0 +1,49 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import VersionControllerUtils from 'nfvo-components/panel/versionController/VersionControllerUtils.js';
+
+import SoftwareProductProcessesActionHelper from './SoftwareProductProcessesActionHelper.js';
+import SoftwareProductProcessesView from './SoftwareProductProcessesView.jsx';
+
+const mapStateToProps = ({softwareProduct}) => {
+ let {softwareProductEditor: {data: currentSoftwareProduct = {}}, softwareProductProcesses: {processesList, processesEditor}} = softwareProduct;
+ let isReadOnlyMode = VersionControllerUtils.isReadOnly(currentSoftwareProduct);
+ let {data} = processesEditor;
+
+ return {
+ currentSoftwareProduct,
+ processesList,
+ isDisplayEditor: Boolean(data),
+ isModalInEditMode: Boolean(data && data.id),
+ isReadOnlyMode
+ };
+};
+
+const mapActionsToProps = (dispatch, {softwareProductId}) => {
+ return {
+ onAddProcess: () => SoftwareProductProcessesActionHelper.openEditor(dispatch),
+ onEditProcess: (process) => SoftwareProductProcessesActionHelper.openEditor(dispatch, process),
+ onDeleteProcess: (process) => SoftwareProductProcessesActionHelper.openDeleteProcessesConfirm(dispatch, {process, softwareProductId})
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(SoftwareProductProcessesView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesActionHelper.js
new file mode 100644
index 0000000..df5d08f
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesActionHelper.js
@@ -0,0 +1,151 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './SoftwareProductProcessesConstants.js';
+import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
+import Configuration from 'sdc-app/config/Configuration.js';
+
+function baseUrl(svpId) {
+ const restPrefix = Configuration.get('restPrefix');
+ return `${restPrefix}/v1.0/vendor-software-products/${svpId}/processes`;
+}
+
+function putProcess(softwareProductId, process) {
+ return RestAPIUtil.save(`${baseUrl(softwareProductId)}/${process.id}`, {
+ name: process.name,
+ description: process.description
+ });
+}
+
+function postProcess(softwareProductId, process) {
+ return RestAPIUtil.create(`${baseUrl(softwareProductId)}`, {
+ name: process.name,
+ description: process.description
+ });
+}
+
+function deleteProcess(softwareProductId, processId) {
+ return RestAPIUtil.destroy(`${baseUrl(softwareProductId)}/${processId}`);
+}
+
+function uploadFileToProcess(softwareProductId, processId, formData)
+{
+ return RestAPIUtil.create(`${baseUrl(softwareProductId)}/${processId}/upload`, formData);
+}
+
+function fetchProcesses(softwareProductId, version) {
+ let versionQuery = version ? `?version=${version}` : '';
+ return RestAPIUtil.fetch(`${baseUrl(softwareProductId)}${versionQuery}`);
+}
+
+
+
+const SoftwareProductActionHelper = {
+
+ fetchProcessesList(dispatch, {softwareProductId, version}) {
+
+ dispatch({
+ type: actionTypes.FETCH_SOFTWARE_PRODUCT_PROCESSES,
+ processesList: []
+ });
+
+ return fetchProcesses(softwareProductId, version).then(response => {
+ dispatch({
+ type: actionTypes.FETCH_SOFTWARE_PRODUCT_PROCESSES,
+ processesList: response.results
+ });
+ });
+ },
+ openEditor(dispatch, process = {}) {
+ dispatch({
+ type: actionTypes.SOFTWARE_PRODUCT_PROCESS_EDITOR_OPEN,
+ process
+ });
+ },
+
+ deleteProcess(dispatch, {process, softwareProductId}) {
+ return deleteProcess(softwareProductId, process.id).then(() => {
+ dispatch({
+ type: actionTypes.DELETE_SOFTWARE_PRODUCT_PROCESS,
+ processId: process.id
+ });
+ });
+
+ },
+
+ closeEditor(dispatch) {
+ dispatch({
+ type:actionTypes.SOFTWARE_PRODUCT_PROCESS_EDITOR_CLOSE
+ });
+ },
+
+ processEditorDataChanged(dispatch, {deltaData}) {
+ dispatch({
+ type: actionTypes.processEditor.DATA_CHANGED,
+ deltaData
+ });
+ },
+
+ saveProcess(dispatch, {softwareProductId, previousProcess, process}) {
+ if (previousProcess) {
+ return putProcess(softwareProductId, process).then(() => {
+ if (process.formData){
+ uploadFileToProcess(softwareProductId, process.id, process.formData);
+ }
+ dispatch({
+ type: actionTypes.EDIT_SOFTWARE_PRODUCT_PROCESS,
+ process
+ });
+ });
+ }
+ else {
+ return postProcess(softwareProductId, process).then(response => {
+ if (process.formData) {
+ uploadFileToProcess(softwareProductId, response.value, process.formData);
+ }
+ dispatch({
+ type: actionTypes.ADD_SOFTWARE_PRODUCT_PROCESS,
+ process: {
+ ...process,
+ id: response.value
+ }
+ });
+ });
+ }
+ },
+
+ hideDeleteConfirm(dispatch) {
+ dispatch({
+ type: actionTypes.SOFTWARE_PRODUCT_PROCESS_DELETE_CONFIRM,
+ processToDelete: false
+ });
+ },
+
+ openDeleteProcessesConfirm(dispatch, {process} ) {
+ dispatch({
+ type: actionTypes.SOFTWARE_PRODUCT_PROCESS_DELETE_CONFIRM,
+ processToDelete: process
+ });
+ }
+
+};
+
+export default SoftwareProductActionHelper;
+
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesConfirmationModal.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesConfirmationModal.jsx
new file mode 100644
index 0000000..0159352
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesConfirmationModal.jsx
@@ -0,0 +1,45 @@
+import React from 'react';
+import {connect} from 'react-redux';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import ConfirmationModalView from 'nfvo-components/confirmations/ConfirmationModalView.jsx';
+import SoftwareProductProcessesActionHelper from './SoftwareProductProcessesActionHelper.js';
+
+function renderMsg(processToDelete) {
+ let name = processToDelete ? processToDelete.name : '';
+ let msg = i18n('Are you sure you want to delete "{name}"?', {name});
+ return (
+ <div>
+ <p>{msg}</p>
+ </div>
+ );
+};
+
+const mapStateToProps = ({softwareProduct}) => {
+ let {softwareProductEditor, softwareProductProcesses} = softwareProduct;
+ let {processToDelete} = softwareProductProcesses;
+ let softwareProductId = softwareProductEditor.data.id;
+
+ const show = processToDelete !== false;
+ return {
+ show,
+ title: i18n('Warning!'),
+ type: 'warning',
+ msg: renderMsg(processToDelete),
+ confirmationDetails: {processToDelete, softwareProductId}
+ };
+};
+
+const mapActionsToProps = (dispatch) => {
+ return {
+ onConfirmed: ({processToDelete, softwareProductId}) => {
+ SoftwareProductProcessesActionHelper.deleteProcess(dispatch, {process: processToDelete, softwareProductId});
+ SoftwareProductProcessesActionHelper.hideDeleteConfirm(dispatch);
+ },
+ onDeclined: () => {
+ SoftwareProductProcessesActionHelper.hideDeleteConfirm(dispatch);
+ }
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(ConfirmationModalView);
+
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesConstants.js
new file mode 100644
index 0000000..63f3067
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesConstants.js
@@ -0,0 +1,34 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export const actionTypes = keyMirror({
+ ADD_SOFTWARE_PRODUCT_PROCESS: null,
+ EDIT_SOFTWARE_PRODUCT_PROCESS: null,
+ DELETE_SOFTWARE_PRODUCT_PROCESS: null,
+ SOFTWARE_PRODUCT_PROCESS_EDITOR_OPEN: null,
+ SOFTWARE_PRODUCT_PROCESS_EDITOR_CLOSE: null,
+ FETCH_SOFTWARE_PRODUCT_PROCESSES: null,
+ SOFTWARE_PRODUCT_PROCESS_DELETE_CONFIRM: null,
+ processEditor: {
+ DATA_CHANGED: null
+ }
+});
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesEditor.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesEditor.js
new file mode 100644
index 0000000..8dc48c5
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesEditor.js
@@ -0,0 +1,52 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import SoftwareProductProcessesActionHelper from './SoftwareProductProcessesActionHelper';
+import SoftwareProductProcessesEditorView from './SoftwareProductProcessesEditorView.jsx';
+
+const mapStateToProps = ({softwareProduct}) => {
+ let {softwareProductProcesses: {processesList, processesEditor}} = softwareProduct;
+ let {data} = processesEditor;
+
+ let previousData;
+ const processId = data ? data.id : null;
+ if(processId) {
+ previousData = processesList.find(process => process.id === processId);
+ }
+
+ return {
+ data,
+ previousData
+ };
+};
+
+const mapActionsToProps = (dispatch, {softwareProductId}) => {
+ return {
+ onDataChanged: deltaData => SoftwareProductProcessesActionHelper.processEditorDataChanged(dispatch, {deltaData}),
+ onSubmit: ({previousProcess, process}) => {
+ SoftwareProductProcessesActionHelper.closeEditor(dispatch);
+ SoftwareProductProcessesActionHelper.saveProcess(dispatch, {softwareProductId, previousProcess, process});
+ },
+ onClose: () => SoftwareProductProcessesActionHelper.closeEditor(dispatch)
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(SoftwareProductProcessesEditorView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesEditorReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesEditorReducer.js
new file mode 100644
index 0000000..cae25e2
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesEditorReducer.js
@@ -0,0 +1,44 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './SoftwareProductProcessesConstants.js';
+
+export default (state = {}, action) => {
+ switch (action.type) {
+ case actionTypes.SOFTWARE_PRODUCT_PROCESS_EDITOR_OPEN:
+ return {
+ ...state,
+ data: action.process
+ };
+ case actionTypes.SOFTWARE_PRODUCT_PROCESS_EDITOR_CLOSE:
+ return {};
+
+ case actionTypes.processEditor.DATA_CHANGED:
+ return {
+ ...state,
+ data: {
+ ...state.data,
+ ...action.deltaData
+ }
+ };
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesEditorView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesEditorView.jsx
new file mode 100644
index 0000000..c2c4aff
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesEditorView.jsx
@@ -0,0 +1,122 @@
+import React from 'react';
+import Dropzone from 'react-dropzone';
+import classnames from 'classnames';
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import ValidationForm from 'nfvo-components/input/validation/ValidationForm.jsx';
+import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx';
+
+const SoftwareProductProcessEditorPropType = React.PropTypes.shape({
+ id: React.PropTypes.string,
+ name: React.PropTypes.string,
+ description: React.PropTypes.string,
+ artifactName: React.PropTypes.string
+});
+
+class SoftwareProductProcessesEditorView extends React.Component {
+
+ state = {
+ dragging: false,
+ files: []
+ };
+
+ static propTypes = {
+ data: SoftwareProductProcessEditorPropType,
+ previousData: SoftwareProductProcessEditorPropType,
+ isReadOnlyMode: React.PropTypes.bool,
+ onDataChanged: React.PropTypes.func,
+ onSubmit: React.PropTypes.func,
+ onClose: React.PropTypes.func
+ };
+
+ render() {
+ let {data = {}, isReadOnlyMode, onDataChanged, onClose} = this.props;
+ let {name, description, artifactName} = data;
+ return (
+ <ValidationForm
+ ref='validationForm'
+ hasButtons={true}
+ labledButtons={true}
+ isReadOnlyMode={isReadOnlyMode}
+ onSubmit={ () => this.submit() }
+ onReset={ () => onClose() }
+ className='vsp-processes-editor'>
+ <div className={classnames('vsp-processes-editor-data', {'disabled': isReadOnlyMode})}>
+ <Dropzone
+ className={classnames('vsp-process-dropzone-view', {'active-dragging': this.state.dragging})}
+ onDrop={files => this.handleImportSubmit(files)}
+ onDragEnter={() => this.setState({dragging: true})}
+ onDragLeave={() => this.setState({dragging: false})}
+ multiple={false}
+ disableClick={true}
+ ref='processEditorFileInput'
+ name='processEditorFileInput'
+ accept='*.*'>
+ <div className='row'>
+ <div className='col-md-6'>
+ <ValidationInput
+ onChange={name => onDataChanged({name})}
+ label={i18n('Name')}
+ value={name}
+ validations={{validateName: true, maxLength: 120, required: true}}
+ type='text'/>
+ <ValidationInput
+ label={i18n('Artifacts')}
+ value={artifactName}
+ type='text'
+ disabled/>
+ </div>
+ <div className='col-md-6'>
+ <div className='file-upload-box'>
+ <div className='drag-text'>{i18n('Drag & drop for upload')}</div>
+ <div className='or-text'>{i18n('or')}</div>
+ <div className='upload-btn primary-btn' onClick={() => this.refs.processEditorFileInput.open()}>
+ <span className='primary-btn-text'>{i18n('Select file')}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ <ValidationInput
+ onChange={description => onDataChanged({description})}
+ label={i18n('Notes')}
+ value={description}
+ name='vsp-process-description'
+ className='vsp-process-description'
+ validations={{maxLength: 1000}}
+ type='textarea'/>
+ </Dropzone>
+ </div>
+ </ValidationForm>
+ );
+ }
+
+ submit() {
+ const {data: process, previousData: previousProcess} = this.props;
+ let {files} = this.state;
+ let formData = false;
+ if (files.length) {
+ let file = files[0];
+ formData = new FormData();
+ formData.append('upload', file);
+ }
+
+ let updatedProcess = {
+ ...process,
+ formData
+ };
+ this.props.onSubmit({process: updatedProcess, previousProcess});
+ }
+
+
+ handleImportSubmit(files) {
+ let {onDataChanged} = this.props;
+ this.setState({
+ dragging: false,
+ complete: '0',
+ files
+ });
+ onDataChanged({artifactName: files[0].name});
+ }
+}
+
+export default SoftwareProductProcessesEditorView;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesListReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesListReducer.js
new file mode 100644
index 0000000..619a2db
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesListReducer.js
@@ -0,0 +1,37 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './SoftwareProductProcessesConstants.js';
+
+export default (state = [], action) => {
+ switch (action.type) {
+ case actionTypes.FETCH_SOFTWARE_PRODUCT_PROCESSES:
+ return [...action.processesList];
+ case actionTypes.EDIT_SOFTWARE_PRODUCT_PROCESS:
+ const indexForEdit = state.findIndex(process => process.id === action.process.id);
+ return [...state.slice(0, indexForEdit), action.process, ...state.slice(indexForEdit + 1)];
+ case actionTypes.ADD_SOFTWARE_PRODUCT_PROCESS:
+ return [...state, action.process];
+ case actionTypes.DELETE_SOFTWARE_PRODUCT_PROCESS:
+ return state.filter(process => process.id !== action.processId);
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesView.jsx
new file mode 100644
index 0000000..a2aa3d4
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/processes/SoftwareProductProcessesView.jsx
@@ -0,0 +1,112 @@
+import React from 'react';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import Modal from 'nfvo-components/modal/Modal.jsx';
+
+import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx';
+import ListEditorItemView from 'nfvo-components/listEditor/ListEditorItemView.jsx';
+
+import SoftwareProductProcessesEditor from './SoftwareProductProcessesEditor.js';
+import SoftwareProductProcessesConfirmationModal from './SoftwareProductProcessesConfirmationModal.jsx';
+
+
+class SoftwareProductProcessesView extends React.Component {
+
+ state = {
+ localFilter: ''
+ };
+
+ static propTypes = {
+ onAddProcess: React.PropTypes.func.isRequired,
+ onEditProcess: React.PropTypes.func.isRequired,
+ onDeleteProcess: React.PropTypes.func.isRequired,
+ isDisplayEditor: React.PropTypes.bool.isRequired,
+ isReadOnlyMode: React.PropTypes.bool.isRequired
+ };
+
+ render() {
+ let { currentSoftwareProduct} = this.props;
+ return (
+ <div className='software-product-landing-view-right-side vsp-processes-page'>
+ {this.renderEditor()}
+ {this.renderProcessList()}
+ <SoftwareProductProcessesConfirmationModal softwareProductId={currentSoftwareProduct.id}/>
+ </div>
+ );
+ }
+
+ renderEditor() {
+ let {currentSoftwareProduct: {id}, isModalInEditMode, isReadOnlyMode, isDisplayEditor} = this.props;
+ return (
+
+ <Modal show={isDisplayEditor} bsSize='large' animation={true}>
+ <Modal.Header>
+ <Modal.Title>{isModalInEditMode ? i18n('Edit Process Details') : i18n('Create New Process Details')}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body className='edit-process-modal'>
+ <SoftwareProductProcessesEditor softwareProductId={id} isReadOnlyMode={isReadOnlyMode}/>
+ </Modal.Body>
+ </Modal>
+ );
+ }
+
+ renderProcessList() {
+ const {localFilter} = this.state;
+ let {onAddProcess, isReadOnlyMode} = this.props;
+
+ return (
+ <ListEditorView
+ plusButtonTitle={i18n('Add Process Details')}
+ filterValue={localFilter}
+ placeholder={i18n('Filter Process')}
+ onAdd={onAddProcess}
+ isReadOnlyMode={isReadOnlyMode}
+ onFilter={filter => this.setState({localFilter: filter})}>
+ {this.filterList().map(processes => this.renderProcessListItem(processes, isReadOnlyMode))}
+ </ListEditorView>
+ );
+ }
+
+ renderProcessListItem(process, isReadOnlyMode) {
+ let {id, name, description, artifactName = ''} = process;
+ let {onEditProcess, onDeleteProcess} = this.props;
+ return (
+ <ListEditorItemView
+ key={id}
+ className='list-editor-item-view'
+ isReadOnlyMode={isReadOnlyMode}
+ onSelect={() => onEditProcess(process)}
+ onDelete={() => onDeleteProcess(process)}>
+
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Name')}</div>
+ <div className='name'>{name}</div>
+ </div>
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Artifact name')}</div>
+ <div className='artifact-name'>{artifactName}</div>
+ </div>
+ <div className='list-editor-item-view-field'>
+ <div className='title'>{i18n('Notes')}</div>
+ <div className='description'>{description}</div>
+ </div>
+ </ListEditorItemView>
+ );
+ }
+
+ filterList() {
+ let {processesList} = this.props;
+ let {localFilter} = this.state;
+
+ if (localFilter.trim()) {
+ const filter = new RegExp(escape(localFilter), 'i');
+ return processesList.filter(({name = '', description = ''}) => {
+ return escape(name).match(filter) || escape(description).match(filter);
+ });
+ }
+ else {
+ return processesList;
+ }
+ }
+}
+
+export default SoftwareProductProcessesView;
diff --git a/openecomp-ui/src/sdc-app/punch-outs.js b/openecomp-ui/src/sdc-app/punch-outs.js
new file mode 100644
index 0000000..46e84a6
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/punch-outs.js
@@ -0,0 +1,29 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import '../../resources/scss/onboarding.scss';
+import 'dox-sequence-diagram-ui/src/main/webapp/res/sdc-sequencer.scss';
+
+import 'core-js/fn/array/includes.js';
+import OnboardingPunchOut from './onboarding/OnboardingPunchOut.jsx';
+import FlowsPunchOut from './flows/FlowsPunchOut.jsx';
+
+PunchOutRegistry.register('onboarding/vendor', () => new OnboardingPunchOut());
+PunchOutRegistry.register('sequence-diagram', () => new FlowsPunchOut());
diff --git a/openecomp-ui/src/sdc-app/sdc.app.jsx b/openecomp-ui/src/sdc-app/sdc.app.jsx
new file mode 100644
index 0000000..0929fa0
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/sdc.app.jsx
@@ -0,0 +1,18 @@
+import '../../resources/scss/bootstrap.scss';
+import '../../resources/css/font-awesome.min.css';
+import 'react-select/dist/react-select.min.css';
+import 'dox-sequence-diagram-ui/src/main/webapp/res/sdc-sequencer.scss';
+import '../../resources/scss/style.scss';
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+import Application from './Application.jsx';
+import Modules from './ModulesOptions.jsx';
+
+//chrome 48 remove svg method which is used in jointjs core -> https://github.com/cpettitt/dagre-d3/issues/202 --> http://jointjs.com/blog/get-transform-to-element-polyfill.html
+SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(toElement) {
+ return toElement.getScreenCTM().inverse().multiply(this.getScreenCTM());
+};
+
+ReactDOM.render(<Application><Modules/></Application>, document.getElementById('sdc-app'));