25ad90f351b9d486d663e82a54ed86b16113f7c1
[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 Icon from 'nfvo-components/icon/Icon.jsx';
20 import SVGIcon from 'nfvo-components/icon/SVGIcon.jsx';
21 import i18n from 'nfvo-utils/i18n/i18n.js';
22 import {mouseActions, errorLevels, nodeFilters} from './HeatValidationConstants.js';
23
24 const leftPanelWidth = 250;
25 const typeToIcon = Object.freeze({
26         heat: 'heat',
27         volume: 'volume',
28         network: 'network',
29         artifact: 'validation-artifacts',
30         env: 'env',
31         other: 'validation-other'
32 });
33
34
35 class HeatValidationView extends Component {
36
37         static propTypes = {
38                 attachmentsTree: PropTypes.object.isRequired,
39                 errorList: PropTypes.array.isRequired,
40                 currentErrors: PropTypes.array.isRequired,
41                 currentWarnings: PropTypes.array.isRequired,
42                 onSelectNode: PropTypes.func.isRequired,
43                 onDeselectNode: PropTypes.func.isRequired,
44                 toggleExpanded: PropTypes.func.isRequired,
45                 selectedNode: PropTypes.string
46         };
47
48         render() {
49                 return (<div className='vsp-attachments-heat-validation' data-test-id='heat-validation-editor'>
50                         <HeatFileTree errorList={this.props.errorList} attachmentsTree={this.props.attachmentsTree}
51                                 onSelectNode={this.props.onSelectNode} toggleExpanded={this.props.toggleExpanded}
52                                 selectedNode={this.props.selectedNode} onDeselectNode={this.props.onDeselectNode} />
53                         <HeatMessageBoard errors={this.props.currentErrors} warnings={this.props.currentWarnings} selectedNode={this.props.selectedNode} />
54                 </div> );
55         }
56 }
57
58 function HeatFileTreeRow(props) {
59         let {node, path, toggleExpanded, selectedNode, selectNode} = props;
60         let isFolder = node.children && node.children.length > 0;
61         return (
62                 <div onDoubleClick={() => toggleExpanded(path)} className={classNames({
63                         'tree-node-row': true,
64                         'tree-node-clicked': node.name === props.selectedNode
65                 })} data-test-id='validation-tree-node'>
66                         <div className='name-section'>
67                                 {
68                                         isFolder &&
69                                                 <div onClick={() => toggleExpanded(path)}
70                                                         className='tree-node-expander'>
71                                                         <SVGIcon name={!node.expanded ? 'chevron-up' : 'chevron-down'} data-test-id='validation-tree-block-toggle'/>
72                                                 </div>
73                                 }
74                                 {
75
76                                         <span className='tree-node-icon'>
77                                                 <Icon image={typeToIcon[node.type]} iconClassName={selectedNode === node.name ? 'selected' : ''}/>
78                                         </span>
79                                 }
80                                 {
81
82                                         <span  className='tree-node-name' onClick={() => selectNode(node.name)} data-test-id='validation-tree-node-name'>
83                                                 {node.name ? node.name : 'UNKNOWN'}
84                                         </span>
85                                 }
86                         </div>
87                         <ErrorsAndWarningsCount errorList={node.errors} onClick={() => selectNode(node.name)} />
88                 </div>);
89 }
90
91 function HeatFileTreeHeader(props) {
92         let hasErrors = props.errorList.filter(error => error.level === errorLevels.ERROR).length > 0;
93         return (
94                 <div onClick={() => props.selectNode(nodeFilters.ALL)} className={classNames({'attachments-tree-header': true,
95                         'header-selected' : props.selectedNode === nodeFilters.ALL})} data-test-id='validation-tree-header'>
96                         <div className='tree-header-title' >
97                                 <Icon image='zip' iconClassName={classNames(props.selectedNode === nodeFilters.ALL ? 'selected' : '', 'header-icon')} />
98                                 <span className={classNames({'tree-header-title-text' : true,
99                                         'tree-header-title-selected' : props.selectedNode === nodeFilters.ALL})}>{i18n(`HEAT${hasErrors ? ' (Draft)' : ''}`)}</span>
100                         </div>
101                         <ErrorsAndWarningsCount errorList={props.errorList} size='large' />
102                 </div>);
103 }
104
105 class HeatFileTree extends React.Component  {
106         static propTypes = {
107                 attachmentsTree: PropTypes.object.isRequired,
108                 errorList: PropTypes.array.isRequired,
109                 onSelectNode: PropTypes.func.isRequired,
110                 onDeselectNode: PropTypes.func.isRequired,
111                 toggleExpanded: PropTypes.func.isRequired,
112                 selectedNode: PropTypes.string
113         };
114         state = {
115                 treeWidth: '400'
116         };
117         render() {
118                 let {attachmentsTree} = this.props;
119                 return (
120                         <div className='validation-tree-section' style={{'width' : this.state.treeWidth + 'px'}}>
121                                 <div className='vsp-attachments-heat-validation-tree'>
122                                         <div className='tree-wrapper'>
123                                                 {attachmentsTree && attachmentsTree.children && attachmentsTree.children.map((child, ind) => this.renderNode(child, [ind]))}
124                                         </div>
125                                 </div>
126                                 <div onMouseDown={(e) => this.onChangeTreeWidth(e)}
127                                          className='vsp-attachments-heat-validation-separator' data-test-id='validation-tree-separator'></div>
128                         </div>);
129         }
130         renderNode(node, path) {
131                 let rand = Math.random() * (3000 - 1) + 1;
132                 let isFolder = node.children && node.children.length > 0;
133                 let {selectedNode} = this.props;
134                 return (
135                         <div key={node.name + rand} className={classNames({'tree-block-inside' : !node.header})}>
136                                 {
137                                         node.header ?
138                                         <HeatFileTreeHeader selectedNode={selectedNode} errorList={this.props.errorList} selectNode={(nodeName) => this.selectNode(nodeName)}  /> :
139                                         <HeatFileTreeRow toggleExpanded={this.props.toggleExpanded} node={node} path={path} selectedNode={selectedNode} selectNode={() => this.selectNode(node.name)} />
140                                 }
141                                 {
142                                         isFolder &&
143                                         <Collapse in={node.expanded}>
144                                                 <div className='tree-node-children'>
145                                                         {
146                                                                 node.children.map((child, ind) => this.renderNode(child, [...path, ind]))
147                                                         }
148                                                 </div>
149                                         </Collapse>
150                                 }
151                         </div>
152                 );
153         }
154
155
156
157
158
159         selectNode(currentSelectedNode) {
160                 let {onDeselectNode, onSelectNode, selectedNode} = this.props;
161                 if (currentSelectedNode !== selectedNode) {
162                         onSelectNode(currentSelectedNode);
163                 } else {
164                         onDeselectNode();
165                 }
166
167
168
169         }
170
171         onChangeTreeWidth(e) {
172                 if (e.button === mouseActions.MOUSE_BUTTON_CLICK) {
173                         let onMouseMove = (e) => {
174                                 this.setState({treeWidth: e.clientX - leftPanelWidth});
175                         };
176                         let onMouseUp = () => {
177                                 document.removeEventListener('mousemove', onMouseMove);
178                                 document.removeEventListener('mouseup', onMouseUp);
179                         };
180                         document.addEventListener('mousemove', onMouseMove);
181                         document.addEventListener('mouseup', onMouseUp);
182                 }
183         }
184 }
185
186 class HeatMessageBoard extends Component {
187         static propTypes = {
188                 currentErrors: PropTypes.array,
189                 currentWarnings: PropTypes.array,
190                 selectedNode: PropTypes.string
191         };
192         render() {
193                 let {errors, warnings} = this.props;
194                 let allItems = [...errors, ...warnings];
195                 return (
196                         <div className='message-board-section'>
197                                 { allItems.map(error => this.renderError(error)) }
198                         </div>
199                 );
200         }
201         renderError(error) {
202                 let rand = Math.random() * (3000 - 1) + 1;
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='exclamation-triangle-line' iconClassName='large' /> : <Icon image='error-lg' /> }
209                                 <span className='error-item-file-type'>
210                                 {
211                                         (this.props.selectedNode === nodeFilters.ALL) ?
212                                                 <span>
213                                                         <span className='error-file-name'>
214                                                                 {i18n('{errorName}:', {
215                                                                         errorName: error.name
216                                                                 })}
217                                                         </span>
218                                                         <span>
219                                                                 {i18n('{message}', {message: error.errorMessage})}
220                                                         </span>
221                                                 </span> :
222                                                 i18n('{errorMsg}', {
223                                                         errorMsg: error.errorMessage
224                                                 })
225                                 }
226                                 </span>
227                         </div>
228                 );
229         }
230 }
231 class ErrorsAndWarningsCount extends Component {
232         static propTypes = {
233                 errorList: PropTypes.array,
234                 size: PropTypes.string
235         };
236         render() {
237                 let errors = this.getErrorsAndWarningsCount(this.props.errorList);
238                 if (!errors) {
239                         return null;
240                 }
241                 let errIcon = 'error';
242                 let {size} = this.props;
243                 if (size && size === 'large') {
244                         errIcon += '-lg';
245                 }
246                 return (<div className='counters'>
247                         {(errors.errorCount > 0) && <div className='counter'>
248                                 <Icon image={errIcon} iconClassName='counter-icon'/>
249                                 <div className={'error-text ' + (size ? size : '')} data-test-id='validation-error-count'>{errors.errorCount}</div>
250                         </div>}
251                         {(errors.warningCount > 0) && <div className='counter'>
252                                 <SVGIcon name='exclamation-triangle-line' iconClassName={size} />
253                                 <div className={'warning-text ' + (size ? size : '')} data-test-id='validation-warning-count'>{errors.warningCount}</div>
254                         </div>}
255                 </div>);
256         }
257         getErrorsAndWarningsCount(errorList) {
258                 let errorCount = 0, warningCount = 0;
259                 if (errorList && errorList.length > 0) {
260                         for (let i = 0; i < errorList.length; i++) {
261                                 if (errorList[i].level === errorLevels.ERROR) {
262                                         errorCount++;
263                                 } else if (errorList[i].level === errorLevels.WARNING) {
264                                         warningCount++;
265                                 }
266                         }
267                 }
268                 if (errorCount === 0 && warningCount === 0) {
269                         return null;
270                 }
271                 return {errorCount, warningCount};
272         }
273 }
274 export default HeatValidationView;