summaryrefslogtreecommitdiffstats
path: root/openecomp-ui/src/sdc-app/heatvalidation
diff options
context:
space:
mode:
authorMichael Lando <ml636r@att.com>2017-02-19 12:57:33 +0200
committerMichael Lando <ml636r@att.com>2017-02-19 13:47:13 +0200
commitefa037d34be7b1570efdc767c79fad8d4005f10e (patch)
treecf1036ba2728dea8a61492b678fa91954e629403 /openecomp-ui/src/sdc-app/heatvalidation
parentf5f13c4f6b6fe3b4d98e349dfd7db59339803436 (diff)
Add new code new version
Change-Id: Ic02a76313503b526f17c3df29eb387a29fe6a42a Signed-off-by: Michael Lando <ml636r@att.com>
Diffstat (limited to 'openecomp-ui/src/sdc-app/heatvalidation')
-rw-r--r--openecomp-ui/src/sdc-app/heatvalidation/UploadScreen.jsx182
-rw-r--r--openecomp-ui/src/sdc-app/heatvalidation/UploadScreenActionHelper.js60
-rw-r--r--openecomp-ui/src/sdc-app/heatvalidation/UploadScreenConstants.js28
-rw-r--r--openecomp-ui/src/sdc-app/heatvalidation/UploadScreenReducer.js33
-rw-r--r--openecomp-ui/src/sdc-app/heatvalidation/attachments/Attachments.js46
-rw-r--r--openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsActionHelper.js44
-rw-r--r--openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsConstants.js55
-rw-r--r--openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsReducer.js199
-rw-r--r--openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsView.jsx190
9 files changed, 837 insertions, 0 deletions
diff --git a/openecomp-ui/src/sdc-app/heatvalidation/UploadScreen.jsx b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreen.jsx
new file mode 100644
index 0000000000..0bb496fc51
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreen.jsx
@@ -0,0 +1,182 @@
+import React from 'react';
+import {connect} from 'react-redux';
+import Button from 'react-bootstrap/lib/Button.js';
+import Dropzone from 'react-dropzone';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import ProgressBar from 'nfvo-components/progressBar/ProgressBar.jsx';
+import Modal from 'nfvo-components/modal/Modal.jsx';
+import UploadScreenActionHelper from './UploadScreenActionHelper.js';
+import Attachments from './attachments/Attachments.js';
+
+const mapStateToProps = ({uploadScreen}) => {
+ let {upload} = uploadScreen;
+ return {uploadScreen: upload};
+};
+
+
+const mapActionsToProps = dispatch => {
+ return {
+ onUpload: (formData) => UploadScreenActionHelper.uploadFile(dispatch, formData),
+ openMainScreen: () => UploadScreenActionHelper.openMainScreen(dispatch)
+ };
+};
+
+
+class UploadScreen extends React.Component {
+
+ state = {
+ complete: '10',
+ showModal: false,
+ fileName: '',
+ dragging: false,
+ files: []
+ };
+
+ interval = '';
+
+ render() {
+ let {uploadScreen} = this.props;
+ let {showAttachments} = uploadScreen;
+ return(
+ <div className='heat-validation-stand-alone'>
+ {showAttachments ? this.renderTree() : this.renderUploadScreen()}
+ </div>
+ );
+ }
+
+ renderUploadModal() {
+ let {complete, showModal, fileName} = this.state;
+ return (
+ <Modal show={showModal} animation={true}>
+ <Modal.Header>
+ <Modal.Title>{i18n('Uploading attachments')}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <div className='upload-modal-body-content'>
+ <div>
+ <span className='title'>{i18n('File:')}</span>
+ <span className='file-name'>{fileName}</span>
+ </div>
+ <ProgressBar now={complete} label={`${complete}%`}/>
+ <div>{i18n('Upload in progress')}</div>
+ </div>
+ <Modal.Footer>
+ <Button bsStyle='primary' onClick={() => this.onRunBackground()}>
+ {i18n('Run in Background')}
+ </Button>
+ <Button bsStyle='primary' onClick={() => this.onCancel()}>{i18n('Cancel')}</Button>
+ </Modal.Footer>
+ </Modal.Body>
+ </Modal>
+ );
+ }
+
+ renderUploadScreen() {
+ return(
+ <div className='upload-screen'>
+ <div className='row'>
+ <div className='title'>
+ <h1>HEAT VALIDATION APPLICATION</h1>
+ </div>
+ </div>
+ <div className='row'>
+ <div className='col-md-2 col-md-offset-5'>
+ <Dropzone
+ className={`upload-screen-drop-zone ${this.state.dragging ? 'active-dragging' : ''}`}
+ onDrop={files => this.handleImportSubmit(files)}
+ onDragEnter={() => this.setState({dragging:true})}
+ onDragLeave={() => this.setState({dragging:false})}
+ multiple={false}
+ disableClick={true}
+ ref='fileInput'
+ name='fileInput'
+ accept='.zip'>
+ <div
+ className='upload-screen-upload-block'>
+ <div className='drag-text'>{i18n('Drag & drop for upload')}</div>
+ <div className='or-text'>{i18n('or')}</div>
+ <div className='upload-btn primary-btn' onClick={() => this.refs.fileInput.open()}>
+ <span className='primary-btn-text'>{i18n('Select file')}</span>
+ </div>
+ </div>
+ </Dropzone>
+ </div>
+ {this.renderUploadModal()}
+ </div>
+ </div>
+ );
+ }
+
+ renderTree() {
+ let {openMainScreen} = this.props;
+ return(
+ <div className='attachments-screen'>
+ <Attachments/>
+ <div className='back-button'>
+ <div className='upload-btn primary-btn' onClick={() => openMainScreen()}>
+ <span className='primary-btn-text'>{i18n('Back')}</span>
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ handleImportSubmit(files) {
+ this.setState({
+ showModal: true,
+ fileName: files[0].name,
+ dragging: false,
+ complete: '0',
+ files
+ });
+
+
+ this.interval = setInterval(() => {
+ if (this.state.complete >= 90) {
+ clearInterval(this.interval);
+ this.setState({
+ showModal: false,
+ fileName: ''
+ });
+ this.startUploading(files);
+ } else {
+ this.setState({
+ complete: (parseInt(this.state.complete) + 10).toString()
+ });
+ }
+ }, 20);
+
+ }
+
+ onRunBackground() {
+ let {files} = this.state;
+ clearInterval(this.interval);
+ this.startUploading(files);
+ this.setState({showModal: false, files: []});
+ }
+
+ onCancel() {
+ clearInterval(this.interval);
+ this.setState({
+ showModal: false,
+ fileName: '',
+ files: []
+ });
+
+ }
+
+ startUploading(files) {
+ let {onUpload} = this.props;
+ if (!(files && files.length)) {
+ return;
+ }
+ let file = files[0];
+ let formData = new FormData();
+ formData.append('upload', file);
+ this.refs.fileInput.value = '';
+ onUpload(formData);
+ }
+
+}
+
+export default connect(mapStateToProps, mapActionsToProps)(UploadScreen);
diff --git a/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenActionHelper.js b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenActionHelper.js
new file mode 100644
index 0000000000..3b8de0f0d4
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenActionHelper.js
@@ -0,0 +1,60 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
+import NotificationConstants from 'nfvo-components/notifications/NotificationConstants.js';
+import {actionTypes} from './UploadScreenConstants.js';
+import {actionTypes as softwareProductsActionTypes} from '../onboarding/softwareProduct/SoftwareProductConstants.js';
+
+function uploadFile(formData) {
+ return RestAPIUtil.create('/sdc1/feProxy/onboarding-api/v1.0/validation/HEAT/validate', formData);
+}
+
+const UploadScreenActionHelper = {
+ uploadFile(dispatch, formData) {
+
+
+ Promise.resolve()
+ .then(() => uploadFile(formData))
+ .then(response => {
+ dispatch({
+ type: softwareProductsActionTypes.SOFTWARE_PRODUCT_LOADED,
+ response
+ });
+
+ dispatch({
+ type: actionTypes.OPEN_UPLOAD_SCREEN
+ });
+ })
+ .catch(error => {
+ dispatch({
+ type: NotificationConstants.NOTIFY_ERROR,
+ data: {title: 'File Upload Failed', msg: error.responseJSON.message}
+ });
+ });
+ },
+ openMainScreen(dispatch) {
+ dispatch({
+ type: actionTypes.OPEN_MAIN_SCREEN
+ });
+ }
+};
+
+export default UploadScreenActionHelper;
diff --git a/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenConstants.js b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenConstants.js
new file mode 100644
index 0000000000..2766a975ec
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenConstants.js
@@ -0,0 +1,28 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export const actionTypes = keyMirror({
+ FILE_UPLOADED: null,
+ OPEN_UPLOAD_SCREEN: null,
+ OPEN_ATTACHMENTS_SCREEN: null,
+ OPEN_MAIN_SCREEN: null
+});
diff --git a/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenReducer.js b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenReducer.js
new file mode 100644
index 0000000000..e73e028233
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/heatvalidation/UploadScreenReducer.js
@@ -0,0 +1,33 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './UploadScreenConstants.js';
+
+
+export default (state = {}, action) => {
+ switch (action.type) {
+ case actionTypes.OPEN_UPLOAD_SCREEN:
+ return {...state, showAttachments: true};
+ case actionTypes.OPEN_MAIN_SCREEN:
+ return {...state, showAttachments: false};
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/heatvalidation/attachments/Attachments.js b/openecomp-ui/src/sdc-app/heatvalidation/attachments/Attachments.js
new file mode 100644
index 0000000000..2a6a992844
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/heatvalidation/attachments/Attachments.js
@@ -0,0 +1,46 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {connect} from 'react-redux';
+import AttachmentsView from './AttachmentsView.jsx';
+import AttachmentsActionHelper from './AttachmentsActionHelper.js';
+
+
+const mapStateToProps = ({uploadScreen: {attachments}}) => {
+ let {attachmentsTree = false, hoveredNode, selectedNode, errorList} = attachments;
+ return {
+ attachmentsTree,
+ hoveredNode,
+ selectedNode,
+ errorList
+ };
+};
+
+const mapActionsToProps = (dispatch) => {
+ return {
+
+ toggleExpanded: (path) => AttachmentsActionHelper.toggleExpanded(dispatch, {path}),
+ onSelectNode: (nodeName) => AttachmentsActionHelper.onSelectNode(dispatch, {nodeName}),
+ onUnselectNode: () => AttachmentsActionHelper.onUnselectNode(dispatch),
+
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps)(AttachmentsView);
diff --git a/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsActionHelper.js b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsActionHelper.js
new file mode 100644
index 0000000000..15b0ffa4a9
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsActionHelper.js
@@ -0,0 +1,44 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes} from './AttachmentsConstants.js';
+
+export default {
+
+ toggleExpanded(dispatch, {path}) {
+ dispatch({
+ type: actionTypes.TOGGLE_EXPANDED,
+ path
+ });
+ },
+
+ onSelectNode(dispatch, {nodeName}) {
+ dispatch({
+ type: actionTypes.SELECTED_NODE,
+ nodeName
+ });
+ },
+
+ onUnselectNode(dispatch) {
+ dispatch({
+ type: actionTypes.UNSELECTED_NODE
+ });
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsConstants.js b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsConstants.js
new file mode 100644
index 0000000000..33af476d9c
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsConstants.js
@@ -0,0 +1,55 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+export const actionTypes = keyMirror({
+ TOGGLE_EXPANDED: null,
+ SELECTED_NODE: null,
+ UNSELECTED_NODE: null
+});
+
+export const errorTypes = keyMirror({
+ MISSING_FILE_IN_ZIP: i18n('missing file in zip'),
+ MISSING_FILE_IN_MANIFEST: i18n('missing file in manifest'),
+ MISSING_OR_ILLEGAL_FILE_TYPE_IN_MANIFEST: i18n('missing or illegal file type in manifest'),
+ FILE_IS_YML_WITHOUT_YML_EXTENSION: i18n('file is defined as a heat file but it doesn\'t have .yml or .yaml extension'),
+ FILE_IS_ENV_WITHOUT_ENV_EXTENSION: i18n('file is defined as an env file but it doesn\'t have .env extension'),
+ ILLEGAL_YAML_FILE_CONTENT: i18n('illegal yaml file content'),
+ ILLEGAL_HEAT_YAML_FILE_CONTENT: i18n('illegal HEAT yaml file content'),
+ MISSING_FILE_NAME_IN_MANIFEST: i18n('a file is written in manifest without file name'),
+ MISSING_ENV_FILE_IN_ZIP: i18n('missing env file in zip'),
+ ARTIFACT_NOT_IN_USE: i18n('artifact not in use')
+});
+
+export const nodeTypes = keyMirror({
+ heat: i18n('Heat'),
+ volume: i18n('Volume'),
+ network: i18n('Network'),
+ artifact: i18n('Artifact'),
+ env: i18n('Environment'),
+ other: i18n('')
+});
+
+export const mouseActions = keyMirror({
+ MOUSE_BUTTON_CLICK: 0
+});
+
diff --git a/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsReducer.js b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsReducer.js
new file mode 100644
index 0000000000..01f68aede8
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsReducer.js
@@ -0,0 +1,199 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import {actionTypes as softwareProductsActionTypes} from 'sdc-app/onboarding/softwareProduct/SoftwareProductConstants.js';
+import {actionTypes} from './AttachmentsConstants.js';
+
+const mapVolumeData = ({fileName, env, errors}) => ({
+ name: fileName,
+ expanded: true,
+ type: 'volume',
+ children: env && [{
+ name: env.fileName,
+ errors: env.errors,
+ type: 'env'
+ }],
+ errors
+});
+
+const mapNetworkData = ({fileName, env, errors}) => ({
+ name: fileName,
+ expanded: true,
+ type: 'network',
+ children: env && [{
+ name: env.fileName,
+ errors: env.errors,
+ type: 'env'
+ }],
+ errors
+});
+
+const mapArtifactsData = ({fileName, errors}) => ({
+ name: fileName,
+ type: 'artifact',
+ errors
+});
+
+const mapOtherData = ({fileName, errors}) => ({
+ name: fileName,
+ type: 'other',
+ errors
+});
+
+
+const mapHeatData = ({fileName, env, nested, volume, network, artifacts, errors, other}) => ({
+ name: fileName,
+ expanded: true,
+ type: 'heat',
+ errors,
+ children: [
+ ...(volume ? volume.map(mapVolumeData) : []),
+ ...(network ? network.map(mapNetworkData) : []),
+ ...(env ? [{
+ name: env.fileName,
+ errors: env.errors,
+ type: 'env'
+ }] : []),
+ ...(artifacts ? artifacts.map(mapArtifactsData) : []),
+ ...(other ? other.map(mapOtherData) : []),
+ ...(nested ? nested.map(mapHeatData) : [])
+ ]
+});
+
+function createErrorList(node, parent, deep = 0, errorList = []) {
+ if (node.errors) {
+ errorList.push(...node.errors.map((error) => ({
+ errorLevel: error.level,
+ errorMessage: error.message,
+ name: node.name,
+ hasParent: deep > 2,
+ parentName: parent.name,
+ type: node.type,
+ })));
+ }
+ if (node.children && node.children.length) {
+ node.children.map((child) => createErrorList(child, node, deep + 1, errorList));
+ }
+ return errorList;
+}
+
+const mapValidationDataToTree = validationData => {
+ let {HEAT, volume, network, artifacts, other} = validationData.importStructure || {};
+ return {
+ children: [
+ {
+ name: 'HEAT',
+ expanded: true,
+ type: 'heat',
+ children: (HEAT ? HEAT.map(mapHeatData) : [])
+ },
+ ...(artifacts ? [{
+ name: 'artifacts',
+ expanded: true,
+ type: 'artifact',
+ children: (artifacts ? artifacts.map(mapArtifactsData) : [])
+ }] : []),
+ ...(network ? [{
+ name: 'networks',
+ expanded: true,
+ type: 'network',
+ children: (network ? network.map(mapNetworkData) : []),
+ }] : []),
+ ...(volume ? [{
+ name: 'volume',
+ expanded: true,
+ type: 'volume',
+ children: (volume ? volume.map(mapVolumeData) : []),
+ }] : []),
+ ...(other ? [{
+ name: 'other',
+ expanded: true,
+ type: 'other',
+ children: (other ? other.map(mapOtherData) : []),
+ }] : [])
+ ]
+ };
+};
+
+const toggleExpanded = (node, path) => {
+ let newNode = {...node};
+ if (path.length === 0) {
+ newNode.expanded = !node.expanded;
+ } else {
+ let index = path[0];
+ newNode.children = [
+ ...node.children.slice(0, index),
+ toggleExpanded(node.children[index], path.slice(1)),
+ ...node.children.slice(index + 1)
+ ];
+ }
+ return newNode;
+};
+
+const expandSelected = (node, selectedNode) => {
+ let shouldExpand = node.name === selectedNode;
+ let children = node.children && node.children.map(child => {
+ let {shouldExpand: shouldExpandChild, node: newChild} = expandSelected(child, selectedNode);
+ shouldExpand = shouldExpand || shouldExpandChild;
+ return newChild;
+ });
+
+ return {
+ node: {
+ ...node,
+ expanded: node.expanded || shouldExpand,
+ children
+ },
+ shouldExpand
+ };
+};
+
+export default (state = {attachmentsTree: {}}, action) => {
+ switch (action.type) {
+ case softwareProductsActionTypes.SOFTWARE_PRODUCT_LOADED:
+ let currentSoftwareProduct = action.response;
+ let attachmentsTree = currentSoftwareProduct.validationData ? mapValidationDataToTree(currentSoftwareProduct.validationData) : {};
+ let errorList = createErrorList(attachmentsTree);
+ return {
+ ...state,
+ attachmentsTree,
+ errorList
+ };
+ case actionTypes.TOGGLE_EXPANDED:
+ return {
+ ...state,
+ attachmentsTree: toggleExpanded(state.attachmentsTree, action.path)
+ };
+ case actionTypes.SELECTED_NODE:
+ let selectedNode = action.nodeName;
+ return {
+ ...state,
+ attachmentsTree: expandSelected(state.attachmentsTree, selectedNode).node,
+ selectedNode
+ };
+ case actionTypes.UNSELECTED_NODE:
+ return {
+ ...state,
+ selectedNode: undefined
+ };
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsView.jsx b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsView.jsx
new file mode 100644
index 0000000000..7e2dda8d47
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/heatvalidation/attachments/AttachmentsView.jsx
@@ -0,0 +1,190 @@
+import React from 'react';
+import FontAwesome from 'react-fontawesome';
+import classNames from 'classnames';
+import Collapse from 'react-bootstrap/lib/Collapse.js';
+
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import {nodeTypes, mouseActions} from './AttachmentsConstants';
+
+const typeToIcon = Object.freeze({
+ heat: 'building-o',
+ volume: 'database',
+ network: 'cloud',
+ artifact: 'gear',
+ env: 'server',
+ other: 'cube'
+});
+
+const leftPanelWidth = 250;
+
+class SoftwareProductAttachmentsView extends React.Component {
+
+ static propTypes = {
+ attachmentsTree: React.PropTypes.object.isRequired
+ };
+ state = {
+ treeWidth: '400',
+ };
+
+ render() {
+ let {attachmentsTree, errorList = []} = this.props;
+
+ let {treeWidth} = this.state;
+ return (
+ <div className='software-product-attachments'>
+ <div className='software-product-view'>
+ <div className='software-product-landing-view-right-side'>
+ <div className='software-product-attachments-main'>
+ <div className='software-product-attachments-tree' style={{'width' : treeWidth + 'px'}}>
+ <div className='tree-wrapper'>
+ {
+ attachmentsTree && attachmentsTree.children && attachmentsTree.children.map((child, ind) => this.renderNode(child, [ind]))
+ }
+ </div>
+ </div>
+ <div className='software-product-attachments-separator' onMouseDown={e => this.onChangeTreeWidth(e)} />
+ <div className='software-product-attachments-error-list'>
+ {errorList.length ? this.renderErrorList(errorList) : <div className='no-errors'>{attachmentsTree.children ?
+ i18n('VALIDATION SUCCESS') : i18n('THERE IS NO HEAT DATA TO PRESENT') }</div>}
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+
+
+ renderNode(node, path) {
+ let isFolder = node.children && node.children.length > 0;
+ let {onSelectNode} = this.props;
+ return (
+ <div key={node.name} className='tree-block-inside'>
+ {
+ <div onDoubleClick={() => this.props.toggleExpanded(path)} className={this.getTreeRowClassName(node.name)}>
+ {
+ isFolder &&
+ <div onClick={() => this.props.toggleExpanded(path)} className={classNames('tree-node-expander', {'tree-node-expander-collapsed': !node.expanded})}>
+ <FontAwesome name='caret-down'/>
+ </div>
+ }
+ {
+
+ <span className='tree-node-icon'>
+ <FontAwesome name={typeToIcon[node.type]}/>
+ </span>
+ }
+ {
+
+ <span onClick={() => onSelectNode(node.name)} className={this.getTreeTextClassName(node)}>
+ {node.name}
+ </span>
+ }
+ </div>
+ }
+ {
+ isFolder &&
+ <Collapse in={node.expanded}>
+ <div className='tree-node-children'>
+ {
+ node.children.map((child, ind) => this.renderNode(child, [...path, ind]))
+ }
+ </div>
+ </Collapse>
+ }
+ </div>
+ );
+ }
+
+ createErrorList(errorList, node, parent) {
+ if (node.errors) {
+ node.errors.forEach(error => errorList.push({
+ error,
+ name: node.name,
+ parentName: parent.name,
+ type: node.type
+ }));
+ }
+ if (node.children && node.children.length) {
+ node.children.map((child) => this.createErrorList(errorList, child, node));
+ }
+ }
+
+ renderErrorList(errors) {
+ let prevError = {};
+ let {selectedNode} = this.props;
+ return errors.map(error => {
+ let isSameNodeError = error.name === prevError.name && error.parentName === prevError.parentName;
+ prevError = error;
+
+ return (
+ <div
+ key={error.name + error.errorMessage + error.parentName}
+
+ onClick={() => this.selectNode(error.name)}
+ className={classNames('error-item', {'clicked': selectedNode === error.name, 'shifted': !isSameNodeError})}>
+ <span className={classNames('error-item-file-type', {'strong': !isSameNodeError})}>
+ {
+ error.hasParent ?
+ i18n('{type} {name} in {parentName}: ', {
+ type: nodeTypes[error.type],
+ name: error.name,
+ parentName: error.parentName
+ }) :
+ i18n('{type} {name}: ', {
+ type: nodeTypes[error.type],
+ name: error.name
+ })
+ }
+ </span>
+ <span className={`error-item-file-type ${error.errorLevel}`}> {error.errorMessage} </span>
+ </div>
+ );
+ });
+ }
+
+ selectNode(currentSelectedNode) {
+ let {onUnselectNode, onSelectNode, selectedNode} = this.props;
+ if (currentSelectedNode !== selectedNode) {
+ onSelectNode(currentSelectedNode);
+ }else{
+ onUnselectNode();
+ }
+
+ }
+
+ getTreeRowClassName(name) {
+ let {hoveredNode, selectedNode} = this.props;
+ return classNames({
+ 'tree-node-row': true,
+ 'tree-node-selected': name === hoveredNode,
+ 'tree-node-clicked': name === selectedNode
+ });
+ }
+
+ getTreeTextClassName(node) {
+ let {selectedNode} = this.props;
+ return classNames({
+ 'tree-element-text': true,
+ 'error-status': node.errors,
+ 'error-status-selected': node.name === selectedNode
+ });
+ }
+
+ onChangeTreeWidth(e) {
+ if (e.button === mouseActions.MOUSE_BUTTON_CLICK) {
+ let onMouseMove = (e) => {
+ this.setState({treeWidth: e.clientX - leftPanelWidth});
+ };
+ let onMouseUp = () => {
+ document.removeEventListener('mousemove', onMouseMove);
+ document.removeEventListener('mouseup', onMouseUp);
+ };
+ document.addEventListener('mousemove', onMouseMove);
+ document.addEventListener('mouseup', onMouseUp);
+ }
+ }
+}
+
+export default SoftwareProductAttachmentsView;