Create new VSP, onboard from TOSCA file - UI
[sdc.git] / openecomp-ui / src / sdc-app / onboarding / softwareProduct / attachments / validation / HeatValidationView.jsx
1 /*!
2  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13  * or implied. See the License for the specific language governing
14  * permissions and limitations under the License.
15  */
16 import React, {Component, PropTypes} from 'react';
17 import classNames from 'classnames';
18 import Collapse from 'react-bootstrap/lib/Collapse.js';
19 import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js';
20 import i18n from 'nfvo-utils/i18n/i18n.js';
21 import {mouseActions, errorLevels, nodeFilters} from './HeatValidationConstants.js';
22
23 const leftPanelWidth = 250;
24 const typeToIcon = Object.freeze({
25         heat: 'nestedHeat',
26         volume: 'base',
27         network: 'network',
28         artifact: 'artifacts',
29         env: 'env',
30         other: 'other'
31 });
32
33
34 class HeatValidationView extends Component {
35
36         static propTypes = {
37                 attachmentsTree: PropTypes.object.isRequired,
38                 errorList: PropTypes.array.isRequired,
39                 currentErrors: PropTypes.array.isRequired,
40                 currentWarnings: PropTypes.array.isRequired,
41                 onSelectNode: PropTypes.func.isRequired,
42                 onDeselectNode: PropTypes.func.isRequired,
43                 toggleExpanded: PropTypes.func.isRequired,
44                 selectedNode: PropTypes.string
45         };
46
47         render() {
48                 return (<div className='vsp-attachments-heat-validation' data-test-id='heat-validation-editor'>
49                         <HeatFileTree errorList={this.props.errorList} attachmentsTree={this.props.attachmentsTree}
50                                 onSelectNode={this.props.onSelectNode} toggleExpanded={this.props.toggleExpanded}
51                                 selectedNode={this.props.selectedNode} onDeselectNode={this.props.onDeselectNode} />
52                         <HeatMessageBoard errors={this.props.currentErrors} warnings={this.props.currentWarnings} selectedNode={this.props.selectedNode} />
53                 </div> );
54         }
55 }
56
57 function HeatFileTreeRow(props) {
58         let {node, path, toggleExpanded, selectedNode, selectNode} = props;
59         let isFolder = node.children && node.children.length > 0;
60         return (
61                 <div onDoubleClick={() => toggleExpanded(path)} className={classNames({
62                         'tree-node-row': true,
63                         'tree-node-clicked': node.name === props.selectedNode
64                 })} data-test-id='validation-tree-node'>
65                         <div className='name-section'>
66                                 {
67                                         isFolder &&
68                                                 <div onClick={() => toggleExpanded(path)}
69                                                         className='tree-node-expander'>
70                                                         <SVGIcon name={!node.expanded ? 'chevronUp' : 'chevronDown'} data-test-id='validation-tree-block-toggle'/>
71                                                 </div>
72                                 }
73                                 {
74
75                                         <span className='tree-node-icon'>
76                                                 <SVGIcon name={typeToIcon[node.type]} color={selectedNode === node.name ? 'primary' : 'secondary'}/>
77                                         </span>
78                                 }
79                                 {
80
81                                         <span  className='tree-node-name' onClick={() => selectNode(node.name)} data-test-id='validation-tree-node-name'>
82                                                 {node.name ? node.name : 'UNKNOWN'}
83                                         </span>
84                                 }
85                         </div>
86                         <ErrorsAndWarningsCount errorList={node.errors} onClick={() => selectNode(node.name)} />
87                 </div>);
88 }
89
90 function HeatFileTreeHeader(props) {
91         let hasErrors = props.errorList.filter(error => error.level === errorLevels.ERROR).length > 0;
92         return (
93                 <div onClick={() => props.selectNode(nodeFilters.ALL)} className={classNames({'attachments-tree-header': true,
94                         'header-selected' : props.selectedNode === nodeFilters.ALL})} data-test-id='validation-tree-header'>
95                         <div className='tree-header-title' >
96                                 {/*<SVGIcon name='zip' color={props.selectedNode === nodeFilters.ALL ? 'primary' : ''}  iconClassName='header-icon' />*/}
97                                 <span className={classNames({'tree-header-title-text' : true,
98                                         'tree-header-title-selected' : props.selectedNode === nodeFilters.ALL})}>{i18n(`${props.headerTitle} ${hasErrors ? '(Draft)' : ''}`)}</span>
99                         </div>
100                         <ErrorsAndWarningsCount errorList={props.errorList} size='large' />
101                 </div>);
102 }
103
104 class HeatFileTree extends React.Component  {
105         static propTypes = {
106                 attachmentsTree: PropTypes.object.isRequired,
107                 errorList: PropTypes.array.isRequired,
108                 onSelectNode: PropTypes.func.isRequired,
109                 onDeselectNode: PropTypes.func.isRequired,
110                 toggleExpanded: PropTypes.func.isRequired,
111                 selectedNode: PropTypes.string
112         };
113         state = {
114                 treeWidth: '400'
115         };
116         render() {
117                 let {attachmentsTree} = this.props;
118                 return (
119                         <div className='validation-tree-section' style={{'width' : this.state.treeWidth + 'px'}}>
120                                 <div className='vsp-attachments-heat-validation-tree'>
121                                         <div className='tree-wrapper'>
122                                                 {attachmentsTree && attachmentsTree.children && attachmentsTree.children.map((child, ind) => this.renderNode(child, [ind]))}
123                                         </div>
124                                 </div>
125                                 <div onMouseDown={(e) => this.onChangeTreeWidth(e)}
126                                          className='vsp-attachments-heat-validation-separator' data-test-id='validation-tree-separator'></div>
127                         </div>);
128         }
129         renderNode(node, path) {
130                 let rand = Math.random() * (3000 - 1) + 1;
131                 let isFolder = node.children && node.children.length > 0;
132                 let {selectedNode} = this.props;
133                 return (
134                         <div key={node.name + rand} className={classNames({'tree-block-inside' : !node.header})}>
135                                 {
136                                         node.header ?
137                                         <HeatFileTreeHeader headerTitle={node.name} selectedNode={selectedNode} errorList={this.props.errorList} selectNode={(nodeName) => this.selectNode(nodeName)}  /> :
138                                         <HeatFileTreeRow toggleExpanded={this.props.toggleExpanded} node={node} path={path} selectedNode={selectedNode} selectNode={() => this.selectNode(node.name)} />
139                                 }
140                                 {
141                                         isFolder &&
142                                         <Collapse in={node.expanded}>
143                                                 <div className='tree-node-children'>
144                                                         {
145                                                                 node.children.map((child, ind) => this.renderNode(child, [...path, ind]))
146                                                         }
147                                                 </div>
148                                         </Collapse>
149                                 }
150                         </div>
151                 );
152         }
153
154
155
156
157
158         selectNode(currentSelectedNode) {
159                 let {onDeselectNode, onSelectNode, selectedNode} = this.props;
160                 if (currentSelectedNode !== selectedNode) {
161                         onSelectNode(currentSelectedNode);
162                 } else {
163                         onDeselectNode();
164                 }
165
166
167
168         }
169
170         onChangeTreeWidth(e) {
171                 if (e.button === mouseActions.MOUSE_BUTTON_CLICK) {
172                         let onMouseMove = (e) => {
173                                 this.setState({treeWidth: e.clientX - leftPanelWidth});
174                         };
175                         let onMouseUp = () => {
176                                 document.removeEventListener('mousemove', onMouseMove);
177                                 document.removeEventListener('mouseup', onMouseUp);
178                         };
179                         document.addEventListener('mousemove', onMouseMove);
180                         document.addEventListener('mouseup', onMouseUp);
181                 }
182         }
183 }
184
185 class HeatMessageBoard extends Component {
186         static propTypes = {
187                 currentErrors: PropTypes.array,
188                 currentWarnings: PropTypes.array,
189                 selectedNode: PropTypes.string
190         };
191         render() {
192                 let {errors, warnings} = this.props;
193                 let allItems = [...errors, ...warnings];
194                 return (
195                         <div className='message-board-section'>
196                                 { allItems.map(error => this.renderError(error)) }
197                         </div>
198                 );
199         }
200         renderError(error) {
201                 let rand = Math.random() * (3000 - 1) + 1;
202                 console.log(this.props.selectedNode );
203                 return (
204                         <div
205                                 key={error.name + error.errorMessage + error.parentName + rand}
206                                 className='error-item' data-test-id='validation-error'>
207                                 {error.level === errorLevels.WARNING ?
208                                         <SVGIcon name='exclamationTriangleLine' iconClassName='large' color='warning' /> : <SVGIcon name='error' iconClassName='large' color='negative' /> }
209                                 <span className='error-item-file-type'>
210                                 {
211                                         (this.props.selectedNode === nodeFilters.ALL) ?
212                                                 <span>
213                                                         <span className='error-file-name'>
214                                                                 {i18n(`${error.name}`)}
215                                                         </span>
216                                                         <span>
217                                                                 {i18n(error.errorMessage)}
218                                                         </span>
219                                                 </span> :
220                                                 i18n(error.errorMessage)
221                                 }
222                                 </span>
223                         </div>
224                 );
225         }
226 }
227 class ErrorsAndWarningsCount extends Component {
228         static propTypes = {
229                 errorList: PropTypes.array,
230                 size: PropTypes.string
231         };
232         render() {
233                 let errors = this.getErrorsAndWarningsCount(this.props.errorList);
234                 if (!errors) {
235                         return null;
236                 }
237                 let {size} = this.props;
238                 return (<div className='counters'>
239                         {(errors.errorCount > 0) && <div className='counter'>
240                                 <SVGIcon name='error' color='negative' iconClassName={size}/>
241                                 <div className={'error-text ' + (size ? size : '')} data-test-id='validation-error-count'>{errors.errorCount}</div>
242                         </div>}
243                         {(errors.warningCount > 0) && <div className='counter'>
244                                 <SVGIcon name='exclamationTriangleLine' iconClassName={size} color='warning'/>
245                                 <div className={'warning-text ' + (size ? size : '')} data-test-id='validation-warning-count'>{errors.warningCount}</div>
246                         </div>}
247                 </div>);
248         }
249         getErrorsAndWarningsCount(errorList) {
250                 let errorCount = 0, warningCount = 0;
251                 if (errorList && errorList.length > 0) {
252                         for (let i = 0; i < errorList.length; i++) {
253                                 if (errorList[i].level === errorLevels.ERROR) {
254                                         errorCount++;
255                                 } else if (errorList[i].level === errorLevels.WARNING) {
256                                         warningCount++;
257                                 }
258                         }
259                 }
260                 if (errorCount === 0 && warningCount === 0) {
261                         return null;
262                 }
263                 return {errorCount, warningCount};
264         }
265 }
266 export default HeatValidationView;