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} 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';
24 const leftPanelWidth = 250;
25 const typeToIcon = Object.freeze({
29 artifact: 'artifacts',
35 class HeatValidationView extends Component {
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
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} />
58 function HeatFileTreeRow(props) {
59 let {node, path, toggleExpanded, selectedNode, selectNode} = props;
60 let isFolder = node.children && node.children.length > 0;
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'>
69 <div onClick={() => toggleExpanded(path)}
70 className='tree-node-expander'>
71 <SVGIcon name={!node.expanded ? 'chevronUp' : 'chevronDown'} data-test-id='validation-tree-block-toggle'/>
76 <span className='tree-node-icon'>
77 <SVGIcon name={typeToIcon[node.type]} color={selectedNode === node.name ? 'primary' : 'secondary'}/>
82 <span className='tree-node-name' onClick={() => selectNode(node.name)} data-test-id='validation-tree-node-name'>
83 {node.name ? node.name : 'UNKNOWN'}
87 <ErrorsAndWarningsCount errorList={node.errors} onClick={() => selectNode(node.name)} />
91 function HeatFileTreeHeader(props) {
92 let hasErrors = props.errorList.filter(error => error.level === errorLevels.ERROR).length > 0;
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>
101 <ErrorsAndWarningsCount errorList={props.errorList} size='large' />
105 class HeatFileTree extends React.Component {
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
118 let {attachmentsTree} = this.props;
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]))}
126 <div onMouseDown={(e) => this.onChangeTreeWidth(e)}
127 className='vsp-attachments-heat-validation-separator' data-test-id='validation-tree-separator'></div>
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;
135 <div key={node.name + rand} className={classNames({'tree-block-inside' : !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)} />
143 <Collapse in={node.expanded}>
144 <div className='tree-node-children'>
146 node.children.map((child, ind) => this.renderNode(child, [...path, ind]))
159 selectNode(currentSelectedNode) {
160 let {onDeselectNode, onSelectNode, selectedNode} = this.props;
161 if (currentSelectedNode !== selectedNode) {
162 onSelectNode(currentSelectedNode);
171 onChangeTreeWidth(e) {
172 if (e.button === mouseActions.MOUSE_BUTTON_CLICK) {
173 let onMouseMove = (e) => {
174 this.setState({treeWidth: e.clientX - leftPanelWidth});
176 let onMouseUp = () => {
177 document.removeEventListener('mousemove', onMouseMove);
178 document.removeEventListener('mouseup', onMouseUp);
180 document.addEventListener('mousemove', onMouseMove);
181 document.addEventListener('mouseup', onMouseUp);
186 class HeatMessageBoard extends Component {
188 currentErrors: PropTypes.array,
189 currentWarnings: PropTypes.array,
190 selectedNode: PropTypes.string
193 let {errors, warnings} = this.props;
194 let allItems = [...errors, ...warnings];
196 <div className='message-board-section'>
197 { allItems.map(error => this.renderError(error)) }
202 let rand = Math.random() * (3000 - 1) + 1;
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'>
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;