Add collaboration feature
[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} from 'react';
17 import PropTypes from 'prop-types';
18 import classNames from 'classnames';
19 import Collapse from 'react-bootstrap/lib/Collapse.js';
20 import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js';
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: 'nestedHeat',
27         volume: 'base',
28         network: 'network',
29         artifact: 'artifacts',
30         env: 'env',
31         other: '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 ? 'chevronUp' : 'chevronDown'} data-test-id='validation-tree-block-toggle'/>
72                                                 </div>
73                                 }
74                                 {
75
76                                         <span className='tree-node-icon'>
77                                                 <SVGIcon name={typeToIcon[node.type]} color={selectedNode === node.name ? 'primary' : 'secondary'}/>
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                                 {/*<SVGIcon name='zip' color={props.selectedNode === nodeFilters.ALL ? 'primary' : ''}  iconClassName='header-icon' />*/}
98                                 <span className={classNames({'tree-header-title-text' : true,
99                                         'tree-header-title-selected' : props.selectedNode === nodeFilters.ALL})}>{i18n('{title} {hasErrors}', {title: props.headerTitle, hasErrors: 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 headerTitle={node.name} 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='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                                                                 {error.name}
215                                                         </span>
216                                                         <span>
217                                                                 {error.errorMessage}
218                                                         </span>
219                                                 </span> :
220                                                 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;