2 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
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';
23 const leftPanelWidth = 250;
24 const typeToIcon = Object.freeze({
28 artifact: 'artifacts',
34 class HeatValidationView extends Component {
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
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} />
57 function HeatFileTreeRow(props) {
58 let {node, path, toggleExpanded, selectedNode, selectNode} = props;
59 let isFolder = node.children && node.children.length > 0;
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'>
68 <div onClick={() => toggleExpanded(path)}
69 className='tree-node-expander'>
70 <SVGIcon name={!node.expanded ? 'chevronUp' : 'chevronDown'} data-test-id='validation-tree-block-toggle'/>
75 <span className='tree-node-icon'>
76 <SVGIcon name={typeToIcon[node.type]} color={selectedNode === node.name ? 'primary' : 'secondary'}/>
81 <span className='tree-node-name' onClick={() => selectNode(node.name)} data-test-id='validation-tree-node-name'>
82 {node.name ? node.name : 'UNKNOWN'}
86 <ErrorsAndWarningsCount errorList={node.errors} onClick={() => selectNode(node.name)} />
90 function HeatFileTreeHeader(props) {
91 let hasErrors = props.errorList.filter(error => error.level === errorLevels.ERROR).length > 0;
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>
100 <ErrorsAndWarningsCount errorList={props.errorList} size='large' />
104 class HeatFileTree extends React.Component {
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
117 let {attachmentsTree} = this.props;
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]))}
125 <div onMouseDown={(e) => this.onChangeTreeWidth(e)}
126 className='vsp-attachments-heat-validation-separator' data-test-id='validation-tree-separator'></div>
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;
134 <div key={node.name + rand} className={classNames({'tree-block-inside' : !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)} />
142 <Collapse in={node.expanded}>
143 <div className='tree-node-children'>
145 node.children.map((child, ind) => this.renderNode(child, [...path, ind]))
158 selectNode(currentSelectedNode) {
159 let {onDeselectNode, onSelectNode, selectedNode} = this.props;
160 if (currentSelectedNode !== selectedNode) {
161 onSelectNode(currentSelectedNode);
170 onChangeTreeWidth(e) {
171 if (e.button === mouseActions.MOUSE_BUTTON_CLICK) {
172 let onMouseMove = (e) => {
173 this.setState({treeWidth: e.clientX - leftPanelWidth});
175 let onMouseUp = () => {
176 document.removeEventListener('mousemove', onMouseMove);
177 document.removeEventListener('mouseup', onMouseUp);
179 document.addEventListener('mousemove', onMouseMove);
180 document.addEventListener('mouseup', onMouseUp);
185 class HeatMessageBoard extends Component {
187 currentErrors: PropTypes.array,
188 currentWarnings: PropTypes.array,
189 selectedNode: PropTypes.string
192 let {errors, warnings} = this.props;
193 let allItems = [...errors, ...warnings];
195 <div className='message-board-section'>
196 { allItems.map(error => this.renderError(error)) }
201 let rand = Math.random() * (3000 - 1) + 1;
202 console.log(this.props.selectedNode );
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'>
211 (this.props.selectedNode === nodeFilters.ALL) ?
213 <span className='error-file-name'>
214 {i18n(`${error.name}`)}
217 {i18n(error.errorMessage)}
220 i18n(error.errorMessage)
227 class ErrorsAndWarningsCount extends Component {
229 errorList: PropTypes.array,
230 size: PropTypes.string
233 let errors = this.getErrorsAndWarningsCount(this.props.errorList);
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>
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>
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) {
255 } else if (errorList[i].level === errorLevels.WARNING) {
260 if (errorCount === 0 && warningCount === 0) {
263 return {errorCount, warningCount};
266 export default HeatValidationView;