diff options
Diffstat (limited to 'src/generic-components')
9 files changed, 1187 insertions, 138 deletions
diff --git a/src/generic-components/autoCompleteSearchBar/AutoCompleteSearchBar.jsx b/src/generic-components/autoCompleteSearchBar/AutoCompleteSearchBar.jsx index 455a3ab..bd71e66 100644 --- a/src/generic-components/autoCompleteSearchBar/AutoCompleteSearchBar.jsx +++ b/src/generic-components/autoCompleteSearchBar/AutoCompleteSearchBar.jsx @@ -1,25 +1,28 @@ /* - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. * Copyright © 2017 Amdocs - * ================================================================================ + * 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 + * 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========================================================= + * ============LICENSE_END===================================================== * - * ECOMP is a trademark and service mark of AT&T Intellectual Property. + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. */ + import React, {Component} from 'react'; import {Button} from 'react-bootstrap'; import AutoSuggest from 'react-autosuggest'; @@ -120,6 +123,7 @@ export default class AutoCompleteSearchBar extends Component { onSuggestionsClearRequested={onSuggestionsClearRequested} onSuggestionSelected={(event, {suggestion}) => { this.newSearchSelected(suggestion, onInvalidSearch, dispatchAnalytics, value); + this.props.onClearSuggestionsTextFieldRequested(); }} renderSuggestion={this.renderSuggestion} inputProps={inputProps} @@ -134,6 +138,7 @@ export default class AutoCompleteSearchBar extends Component { <Button type='submit' className='auto-complete-search-button' onClick={() => { this.newSearchSelected(this.getSelectedSuggestionObj(value, cachedSuggestions), onInvalidSearch, dispatchAnalytics, value); + this.props.onSuggestionsClearRequested(); }}> <i className={ICON_CLASS_SEARCH} aria-hidden='true'/> </Button> diff --git a/src/generic-components/dynamicViewLoader/DynamicViewLoaderReducer.js b/src/generic-components/dynamicViewLoader/DynamicViewLoaderReducer.js index c117fc5..969f645 100644 --- a/src/generic-components/dynamicViewLoader/DynamicViewLoaderReducer.js +++ b/src/generic-components/dynamicViewLoader/DynamicViewLoaderReducer.js @@ -1,32 +1,33 @@ /* - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. * Copyright © 2017 Amdocs - * ================================================================================ + * 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 + * 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========================================================= + * ============LICENSE_END===================================================== * - * ECOMP is a trademark and service mark of AT&T Intellectual Property. + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. */ + import {combineReducers} from 'redux'; -import DateRangeSelectorReducer from 'generic-components/dateRangeSelector/DateRangeSelectorReducer.js'; import {dynamicViewLoaderActionTypes} from 'generic-components/dynamicViewLoader/DynamicViewLoaderConstants.js'; export default combineReducers({ - dateRangeSelectorData: DateRangeSelectorReducer, dynamicViewLoadData: (state = {}, action) => { switch (action.type) { case dynamicViewLoaderActionTypes.DVL_LAYOUT_SOURCE_CHANGE: diff --git a/src/generic-components/filterBar/FilterBarConstants.js b/src/generic-components/filterBar/FilterBarConstants.js new file mode 100644 index 0000000..4226774 --- /dev/null +++ b/src/generic-components/filterBar/FilterBarConstants.js @@ -0,0 +1,58 @@ +/* + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * 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===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +import keyMirror from 'utils/KeyMirror.js'; +import {BASE_URL} from 'app/networking/NetworkConstants.js'; + +export const filterBarActionTypes = keyMirror({ + SET_FILTERS: null, + SET_FILTER_VALUES: null, + CLEAR_FILTERS: null, + FILTER_VALUE_CHANGE: null, + NEW_SELECTIONS: null, + SET_NON_CONVERTED_VALUES: null, + SET_CONVERTED_VALUES: null, + SET_UNIFIED_VALUES: null +}); + +export const UNIFIED_FILTERS_URL = BASE_URL + '/rest/search/unifiedFilterRequest'; + +export const DISCOVER_FILTERS_ERROR_MSG = 'There was an error retrieving the' + + ' list of available filters'; + +export const FILTER_BAR_TITLE = 'FILTER BY'; +export const DATE_TIME_ZONE = 'Z'; + +export const FILTER_TYPE_ENUM = { + LIST: 'dropDown', + DATE: 'date' +}; + +export const FILTER_ATTRIBUTE_DEFAULT_VALUE = 'defaultValue'; +export const FILTER_ATTRIBUTE_CONTROLS = 'controls'; +export const FILTER_ATTRIBUTE_CODE = 'code'; +export const FILTER_ATTRIBUTE_VALUES = 'values'; +export const FILTER_ATTRIBUTE_TO = 'to'; +export const FILTER_ATTRIBUTE_FROM = 'from'; diff --git a/src/generic-components/filterBar/FilterBarUtils.js b/src/generic-components/filterBar/FilterBarUtils.js new file mode 100644 index 0000000..982bda9 --- /dev/null +++ b/src/generic-components/filterBar/FilterBarUtils.js @@ -0,0 +1,352 @@ +/* + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * 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===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ +import isEmpty from 'lodash/isEmpty'; +import moment from 'moment-timezone'; +import { + POST, + POST_HEADER +} from 'app/networking/NetworkConstants.js'; +import { + UNIFIED_FILTERS_URL, + DISCOVER_FILTERS_ERROR_MSG, + DATE_TIME_ZONE, + FILTER_ATTRIBUTE_TO, + FILTER_ATTRIBUTE_FROM, + FILTER_ATTRIBUTE_CODE, + FILTER_ATTRIBUTE_VALUES, + FILTER_ATTRIBUTE_CONTROLS, + FILTER_ATTRIBUTE_DEFAULT_VALUE, + FILTER_TYPE_ENUM, + filterBarActionTypes +} from 'generic-components/filterBar/FilterBarConstants.js'; +import { + MESSAGE_LEVEL_WARNING +} from 'utils/GlobalConstants.js'; +import { + getSetGlobalMessageEvent +} from 'app/globalInlineMessageBar/GlobalInlineMessageBarActions.js'; + +export function buildFilterValueMap(filterValueString) { + let filterValueObj = {}; + let filters = filterValueString.split(','); + + for (let filterIndex in filters) { + let filterStringParts = filters[filterIndex].split('='); + + filterValueObj[filterStringParts[0]] = filterStringParts[1]; + } + + return filterValueObj; +} + +export function buildFilterValueMapFromObj(filterValues) { + let filterValueObj = {}; + + for (let filterIndex in filterValues) { + filterValueObj[filterValues[filterIndex].filterId] = filterValues[filterIndex].filterValue; + } + + return filterValueObj; +} + +export function getFilterListQueryString(filterValueList) { + let filterQueryList = []; + + for (let filter in filterValueList) { + if (filterValueList[filter]) { + filterQueryList.push( + { + 'filterId': filter, + 'filterValue': filterValueList[filter] + } + ); + } + } + + return filterQueryList; +} + +function getFilterSearchURL() { + return UNIFIED_FILTERS_URL.replace('@@IP-ADDRESS@@', document.location.hostname); +} + +function getFiltersQueryObject(viewName) { + return { + 'viewName': viewName + }; +} + +function getFiltersEvent(actionType, filterList) { + return { + type: actionType, + data: filterList + }; +} + +export function getUnifiedFilters(viewName, actionType) { + return dispatch => { + return fetch(getFilterSearchURL(), { + credentials: 'same-origin', + method: POST, + headers: POST_HEADER, + body: JSON.stringify(getFiltersQueryObject(viewName)) + }).then( + (response) => response.json() + ).then( + (responseJson) => { + dispatch( + getFiltersEvent(actionType, responseJson.filters) + ); + } + ).catch( + () => { + dispatch(getSetGlobalMessageEvent(DISCOVER_FILTERS_ERROR_MSG, MESSAGE_LEVEL_WARNING)); + } + ); + }; +} + +function extractConvertedDateValues(dateValues) { + let convertedValues = {}; + if (dateValues.from) { + let startMoment = moment(dateValues.from); + convertedValues.startDate = startMoment.toDate(); + convertedValues.time_zone = startMoment.format(DATE_TIME_ZONE); + } + + if (dateValues.to) { + let endMoment = moment(dateValues.to); + convertedValues.endDate = endMoment.toDate(); + convertedValues.time_zone = endMoment.format(DATE_TIME_ZONE); + } + + convertedValues.code = dateValues.code; + + return convertedValues; +} + +function convertFilterValues(filterValues) { + let convertedFilterValues = {}; + + for (let filterId in filterValues) { + if (filterValues.hasOwnProperty(filterId) && + !isEmpty(filterValues[filterId]) && !isEmpty(filterValues[filterId].controls)) { + let controls = filterValues[filterId].controls; + let firstControlKey = Object.keys(controls)[0]; + if (controls[firstControlKey][FILTER_ATTRIBUTE_VALUES][FILTER_ATTRIBUTE_FROM] || + controls[firstControlKey][FILTER_ATTRIBUTE_VALUES][FILTER_ATTRIBUTE_TO]) { + // TODO should check against filter type (ex: dropdown or date) + // rather than assuming value attributes (ex: 'to' or 'from') + convertedFilterValues[filterId] = + extractConvertedDateValues(controls[firstControlKey][FILTER_ATTRIBUTE_VALUES]); + } else { + let codeValue = controls[firstControlKey][FILTER_ATTRIBUTE_VALUES][FILTER_ATTRIBUTE_CODE]; + convertedFilterValues[filterId] = codeValue; + } + } + } + + return convertedFilterValues; +} + +function combineMissingFilters(filterValues, allFilters) { + let allFilterIds = Object.keys(allFilters); + + for (let id in allFilterIds) { + if (!filterValues.hasOwnProperty(allFilterIds[id])) { + filterValues[allFilterIds[id]] = ''; + } + } + + return filterValues; +} + +function getFilterSelectionEvent(selectedValuesMap, convertedValues) { + return { + type: filterBarActionTypes.NEW_SELECTIONS, + data: { + selectedValuesMap: convertedValues, + unifiedValues: selectedValuesMap + } + }; +} + +export function processFilterSelection(filterValues, allFilters) { + let convertedFilterValues = convertFilterValues(filterValues); + let combinedFilterValues = combineMissingFilters(convertedFilterValues, allFilters); + + // dispatch NEW_SELECTION event type with converted values as the data + return getFilterSelectionEvent(filterValues, combinedFilterValues); +} + +export function setNonConvertedFilterValues(nonConvertedValues) { + return { + type: filterBarActionTypes.SET_NON_CONVERTED_VALUES, + data: nonConvertedValues + }; +} + +function convertedFilterValuesEvent(nonConvertedValues, convertedValues) { + return { + type: filterBarActionTypes.SET_CONVERTED_VALUES, + data: { + nonConvertedValues: nonConvertedValues, + convertedValues: convertedValues + } + }; +} + +function mapValuesToOption(filterOptions, nonConvertedValue) { + let mappedValues = {}; + + // loop over options to find match for value + for (let i in filterOptions) { + if (filterOptions[i].code === nonConvertedValue) { + // found the matching + mappedValues = filterOptions[i]; + break; + } + } + + return mappedValues; +} + +function mapValuesToDateOption(nonConvertedValue) { + let mappedValues = {}; + + if (nonConvertedValue.startDate) { + mappedValues.from = new Date(nonConvertedValue.startDate); + } else { + mappedValues.from = null; + } + + if (nonConvertedValue.endDate) { + mappedValues.to = new Date(nonConvertedValue.endDate); + } else { + mappedValues.to = null; + } + + mappedValues.code = nonConvertedValue.code; + + return mappedValues; +} + +function mapValuesToFilter(nonConvertedValues, allFilters, currentlySetFilterValues) { + let convertedValues = {}; + + for (let nonConvertedId in nonConvertedValues) { + if (nonConvertedValues[nonConvertedId] !== '') { + let matchingFilterObj = allFilters[nonConvertedId]; + let filterControlId = Object.keys(matchingFilterObj.controls)[0]; + let mappedValue = {}; + + if (matchingFilterObj[FILTER_ATTRIBUTE_CONTROLS][filterControlId].type === FILTER_TYPE_ENUM.DATE) { + mappedValue = mapValuesToDateOption(nonConvertedValues[nonConvertedId]); + } else { + mappedValue = mapValuesToOption(matchingFilterObj[FILTER_ATTRIBUTE_CONTROLS][filterControlId].options, + nonConvertedValues[nonConvertedId]); + } + + let values = {}; + values[FILTER_ATTRIBUTE_VALUES] = mappedValue; + let filterControlers = {}; + filterControlers[filterControlId] = values; + let filter = {}; + filter[FILTER_ATTRIBUTE_CONTROLS] = filterControlers; + convertedValues[nonConvertedId] = filter; + } else if (!isEmpty(currentlySetFilterValues[nonConvertedId])) { + // currently a value is set for this filter, need to ensure we map this filter + // to an empty value so that it is cleared/reset + let matchingFilterObj = allFilters[nonConvertedId]; + let filterControlId = Object.keys(matchingFilterObj.controls)[0]; + let mappedValue = {}; + let values = {}; + values[FILTER_ATTRIBUTE_VALUES] = mappedValue; + let filterControlers = {}; + filterControlers[filterControlId] = values; + let filter = {}; + filter[FILTER_ATTRIBUTE_CONTROLS] = filterControlers; + convertedValues[nonConvertedId] = filter; + } + } + + return convertedValues; +} + +export function convertNonConvertedValues(nonConvertedValues, allFilters, currentlySetFilterValues) { + let convertedValues = mapValuesToFilter(nonConvertedValues, allFilters, currentlySetFilterValues); + return convertedFilterValuesEvent(nonConvertedValues, convertedValues); +} + +export function clearFilters() { + return { + type: filterBarActionTypes.CLEAR_FILTERS + }; +} + +function getSetUnifiedFilterValuesEvent(unifiedValues) { + return { + type: filterBarActionTypes.SET_UNIFIED_VALUES, + data: unifiedValues + }; +} + +function getFilterDefault(filters, filterId) { + let filterControlId = Object.keys(filters[filterId][FILTER_ATTRIBUTE_CONTROLS])[0]; + let defaultValue = filters[filterId][FILTER_ATTRIBUTE_CONTROLS][filterControlId][FILTER_ATTRIBUTE_DEFAULT_VALUE]; + if (!defaultValue) { + defaultValue = {}; + } + return defaultValue; +} + +export function setFilterSelectionsToDefaults(filters, filterValues) { + let defaultFilterMap = {}; + + for (let filterId in filters) { + let filterDefaultValue = getFilterDefault(filters, filterId); + if (!isEmpty(filterDefaultValue) || (filterValues && filterValues[filterId])) { + let filterControlId = Object.keys(filters[filterId][FILTER_ATTRIBUTE_CONTROLS])[0]; + let controller = {}; + controller.values = filterDefaultValue; + let controllers = {}; + controllers[filterControlId] = controller; + let controls = {}; + controls.controls = controllers; + defaultFilterMap[filterId] = controls; + } + } + + if (isEmpty(defaultFilterMap)) { + // there are no default values, so need to ensure all filters get cleared, + // but just incase this 'clearing' + let combinedValues = combineMissingFilters(defaultFilterMap, filters); + return setNonConvertedFilterValues(combinedValues); + } else { + // jsut set the Unified Filter Value which will be sent down to the filter (filter + // will set itself to the default value and then send notification back up of the selection + return getSetUnifiedFilterValuesEvent(defaultFilterMap); + } +} diff --git a/src/generic-components/graph/ForceDirectedGraph.jsx b/src/generic-components/graph/ForceDirectedGraph.jsx index 872b2d5..00c2575 100644 --- a/src/generic-components/graph/ForceDirectedGraph.jsx +++ b/src/generic-components/graph/ForceDirectedGraph.jsx @@ -1,25 +1,28 @@ /* - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. * Copyright © 2017 Amdocs - * ================================================================================ + * 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 + * 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========================================================= + * ============LICENSE_END===================================================== * - * ECOMP is a trademark and service mark of AT&T Intellectual Property. + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. */ + import {drag} from 'd3-drag'; import {forceSimulation, forceLink, forceManyBody, forceCenter} from 'd3-force'; import {interpolateNumber} from 'd3-interpolate'; @@ -44,7 +47,7 @@ class ForceDirectedGraph extends Component { nodeButtonSelectedCallback: PropTypes.func, currentlySelectedNodeView: PropTypes.string }; - + static defaultProps = { viewWidth: 0, viewHeight: 0, @@ -57,16 +60,16 @@ class ForceDirectedGraph extends Component { nodeButtonSelectedCallback: undefined, currentlySelectedNodeView: '' }; - + constructor(props) { super(props); - + this.state = { nodes: [], links: [], mainGroupTransform: zoomIdentity }; - + this.updateSimulationForce = this.updateSimulationForce.bind(this); - this.resetTransform = this.resetTransform.bind(this); + this.resetState = this.resetState.bind(this); this.applyBufferDataToState = this.applyBufferDataToState.bind(this); this.createNodePropForState = this.createNodePropForState.bind(this); this.createLinkPropForState = this.createLinkPropForState.bind(this); @@ -74,118 +77,138 @@ class ForceDirectedGraph extends Component { this.simulationComplete = this.simulationComplete.bind(this); this.simulationTick = this.simulationTick.bind(this); this.nodeSelected = this.nodeSelected.bind(this); + this.nodeButtonSelected = this.nodeButtonSelected.bind(this); this.onZoom = this.onZoom.bind(this); this.onGraphDrag = this.onGraphDrag.bind(this); this.onNodeDrag = this.onNodeDrag.bind(this); this.addNodeInterpolator = this.addNodeInterpolator.bind(this); this.runInterpolators = this.runInterpolators.bind(this); - + this.nodeBuffer = []; this.linkBuffer = []; this.nodeDatum = []; this.nodeButtonDatum = []; this.nodeFactory = new NodeFactory(); this.visualElementFactory = new NodeVisualElementFactory(); - + this.isGraphMounted = false; - + this.listenerGraphCounter = -1; this.nodeIndexTracker = new Map(); this.interpolators = new Map(); this.areInterpolationsRunning = false; - + this.newNodeSelected = true; this.currentlySelectedNodeButton = undefined; - + this.intervalTimer = interval(this.applyBufferDataToState, simulationKeys.DATA_COPY_INTERVAL); this.intervalTimer.stop(); - + this.interpolationTimer = interval(this.runInterpolators, simulationKeys.DATA_COPY_INTERVAL); this.interpolationTimer.stop(); - + this.simulation = forceSimulation(); this.simulation.on('end', this.simulationComplete); this.simulation.stop(); - + this.svgZoom = zoom().scaleExtent([NodeConstants.SCALE_EXTENT_MIN, NodeConstants.SACEL_EXTENT_MAX]); this.svgZoom.clickDistance(2); this.nodeDrag = drag().clickDistance(2); - + this.updateSimulationForce(); - + // Temporary code until backend supports NOT displaying the button in the response. + if(props.dataOverlayButtons.length === 1) { + this.hideButton = true; + } else { + this.hideButton = false; + } if (props.graphData) { if (props.graphData.graphCounter !== -1) { - this.startSimulation(props.graphData); + this.startSimulation(props.graphData, props.currentlySelectedNodeView, props.dataOverlayButtons); } } } - + componentDidMount() { this.isGraphMounted = true; } - + componentWillReceiveProps(nextProps) { if (nextProps.graphData.graphCounter !== this.props.graphData.graphCounter) { this.listenerGraphCounter = this.props.graphData.graphCounter; this.newNodeSelected = true; - this.resetTransform(); - this.startSimulation(nextProps.graphData); + this.resetState(); + this.startSimulation(nextProps.graphData, nextProps.currentlySelectedNodeView, nextProps.dataOverlayButtons); } } - + + componentDidUpdate(prevProps) { let hasNewGraphDataRendered = (prevProps.graphData.graphCounter === this.props.graphData.graphCounter); let shouldAttachListeners = (this.listenerGraphCounter !== this.props.graphData.graphCounter); let nodeCount = this.state.nodes.length; - + if (nodeCount > 0) { if (hasNewGraphDataRendered && shouldAttachListeners) { - let nodes = select('.fdgMainSvg').select('.fdgMainG') .selectAll('.aai-entity-node') .data(this.nodeDatum); - nodes.on('click', (d) => { this.nodeSelected(d); }); - + nodes.call(this.nodeDrag.on('drag', (d) => { let xAndY = [currentEvent.x, currentEvent.y]; this.onNodeDrag(d, xAndY); })); - + let mainSVG = select('.fdgMainSvg'); let mainView = mainSVG.select('.fdgMainView'); this.svgZoom.transform(mainSVG, zoomIdentity); this.svgZoom.transform(mainView, zoomIdentity); - + mainSVG.call(this.svgZoom.on('zoom', () => { // D3 Zoom also handles panning this.onZoom(currentEvent.transform); })).on('dblclick.zoom', null); // Ignore the double-click zoom event - + this.listenerGraphCounter = this.props.graphData.graphCounter; } + + if (this.newNodeSelected) { + let nodeButtons = select('.fdgMainSvg').select('.fdgMainG') + .selectAll('.aai-entity-node') + .selectAll('.node-button') + .data(this.nodeButtonDatum); + if (!nodeButtons.empty()) { + nodeButtons.on('click', (d) => { + this.nodeButtonSelected(d); + }); + if (hasNewGraphDataRendered && shouldAttachListeners) { + this.newNodeSelected = false; + } + } + } } } - + componentWillUnmount() { this.isGraphMounted = false; - + let nodes = select('.fdgMainSvg').select('.fdgMainG') .selectAll('.aai-entity-node'); let nodeButtons = nodes.selectAll('.node-button'); - + nodes.on('click', null); nodeButtons.on('click', null); - + let mainSVG = select('.fdgMainSvg'); - + mainSVG.call(this.svgZoom.on('zoom', null)).on('dblclick.zoom', null); mainSVG.call(drag().on('drag', null)); } - + updateSimulationForce() { this.simulation.force('link', forceLink()); this.simulation.force('link').id((d) => { @@ -193,55 +216,65 @@ class ForceDirectedGraph extends Component { }); this.simulation.force('link').strength(0.3); this.simulation.force('link').distance(100); - + this.simulation.force('charge', forceManyBody()); this.simulation.force('charge').strength(-1250); this.simulation.alpha(1); - + this.simulation.force('center', forceCenter(this.props.viewWidth / 2, this.props.viewHeight / 2)); } - - resetTransform() { + + resetState() { if (this.isGraphMounted) { this.setState(() => { return { - mainGroupTransform: zoomIdentity + mainGroupTransform: zoomIdentity, + nodes: [], links: [] }; }); } } - + applyBufferDataToState() { this.nodeIndexTracker.clear(); - + let newNodes = []; this.nodeBuffer.map((node, i) => { let nodeProps = this.createNodePropForState(node); - - if (nodeProps.meta.nodeMeta.className === NodeConstants.SELECTED_NODE_CLASS_NAME || - nodeProps.meta.nodeMeta.className === NodeConstants.SELECTED_SEARCHED_NODE_CLASS_NAME) { - + + if (nodeProps.meta.nodeMeta.className === + NodeConstants.SELECTED_NODE_CLASS_NAME || + nodeProps.meta.nodeMeta.className === + NodeConstants.SELECTED_SEARCHED_NODE_CLASS_NAME) { + this.nodeButtonDatum[0].data = nodeProps.meta; - - nodeProps = { - ...nodeProps, - buttons: [this.nodeButtonDatum[0].isSelected] - }; + if(this.nodeButtonDatum.length > 1) { + this.nodeButtonDatum[1].data = nodeProps.meta; + nodeProps = { + ...nodeProps, + buttons: [this.nodeButtonDatum[0].isSelected, this.nodeButtonDatum[1].isSelected] + }; + } else { + nodeProps = { + ...nodeProps, + buttons: [this.nodeButtonDatum[0].isSelected] + }; + } } - - newNodes.push(this.nodeFactory.buildNode(nodeProps.meta.nodeMeta.className, nodeProps)); - + + newNodes.push(this.nodeFactory.buildNode(nodeProps.meta.nodeMeta.className, nodeProps, this.hideButton)); + this.nodeIndexTracker.set(node.id, i); }); - + let newLinks = []; this.linkBuffer.map((link) => { let key = link.id; let linkProps = this.createLinkPropForState(link); newLinks.push(this.visualElementFactory.createSvgLine(linkProps, key)); }); - + if (this.isGraphMounted) { this.setState(() => { return { @@ -250,7 +283,7 @@ class ForceDirectedGraph extends Component { }); } } - + createNodePropForState(nodeData) { return { renderProps: { @@ -260,7 +293,7 @@ class ForceDirectedGraph extends Component { } }; } - + createLinkPropForState(linkData) { return { className: 'aai-entity-link', @@ -270,10 +303,10 @@ class ForceDirectedGraph extends Component { y2: linkData.target.y }; } - - startSimulation(graphData) { + + startSimulation(graphData, currentView, overlayButtons) { this.nodeFactory.setNodeMeta(graphData.graphMeta); - + // Experiment with removing length = 0... might not be needed as new array // assignment will likely destroy old reference this.nodeBuffer.length = 0; @@ -282,45 +315,60 @@ class ForceDirectedGraph extends Component { this.linkBuffer = Array.from(graphData.linkDataArray); this.nodeDatum.length = 0; this.nodeDatum = Array.from(graphData.nodeDataArray); - + this.nodeButtonDatum.length = 0; - - let isNodeDetailsSelected = true; + + let isNodeDetailsSelected = (currentView === + overlayButtons[0] || + currentView === + ''); this.nodeButtonDatum.push({ - name: NodeConstants.ICON_ELLIPSES, isSelected: isNodeDetailsSelected + name: NodeConstants.ICON_ELLIPSES, isSelected: isNodeDetailsSelected, overlayName: overlayButtons[0] }); - + + + if(overlayButtons.length > 1 ) { + let isSecondButtonSelected = (currentView === overlayButtons[1]); + + this.nodeButtonDatum.push({ + name: NodeConstants.ICON_TRIANGLE_WARNING, isSelected: isSecondButtonSelected, overlayName: overlayButtons[1] + }); + } + + if (isNodeDetailsSelected) { this.currentlySelectedNodeButton = NodeConstants.ICON_ELLIPSES; + } else { + this.currentlySelectedNodeButton = NodeConstants.ICON_TRIANGLE_WARNING; } - + this.updateSimulationForce(); - + this.simulation.nodes(this.nodeBuffer); this.simulation.force('link').links(this.linkBuffer); this.simulation.on('tick', this.simulationTick); this.simulation.restart(); } - + simulationComplete() { this.intervalTimer.stop(); this.applyBufferDataToState(); } - + simulationTick() { this.intervalTimer.restart(this.applyBufferDataToState, simulationKeys.DATA_COPY_INTERVAL); this.simulation.on('tick', null); } - + nodeSelected(datum) { if (this.props.nodeSelectedCallback) { this.props.nodeSelectedCallback(datum); } - + let didUpdateNew = false; let didUpdatePrevious = false; let isSameNodeSelected = true; - + // Check to see if a default node was previously selected let selectedDefaultNode = select('.fdgMainSvg').select('.fdgMainG') .selectAll('.aai-entity-node') @@ -333,7 +381,7 @@ class ForceDirectedGraph extends Component { isSameNodeSelected = false; } } - + // Check to see if a searched node was previously selected let selectedSearchedNode = select('.fdgMainSvg').select('.fdgMainG') .selectAll('.aai-entity-node') @@ -346,7 +394,7 @@ class ForceDirectedGraph extends Component { isSameNodeSelected = false; } } - + if (!isSameNodeSelected) { let newlySelectedNode = select('.fdgMainSvg').select('.fdgMainG') .selectAll('.aai-entity-node') @@ -354,7 +402,7 @@ class ForceDirectedGraph extends Component { return (datum.id === d.id); }); if (!newlySelectedNode.empty()) { - + if (newlySelectedNode.datum().nodeMeta.searchTarget) { this.nodeBuffer[newlySelectedNode.datum().index].nodeMeta.className = NodeConstants.SELECTED_SEARCHED_NODE_CLASS_NAME; @@ -365,13 +413,35 @@ class ForceDirectedGraph extends Component { didUpdateNew = true; } } - + if (didUpdatePrevious && didUpdateNew) { this.newNodeSelected = true; this.applyBufferDataToState(); } } + nodeButtonSelected(datum) { + if (this.props.nodeButtonSelectedCallback) { + let buttonClickEvent = { + buttonId: datum.overlayName + }; + this.props.nodeButtonSelectedCallback(buttonClickEvent); + } + + if (this.currentlySelectedNodeButton !== datum.name) { + if (datum.name === this.nodeButtonDatum[0].name) { + this.nodeButtonDatum[0].isSelected = true; + this.nodeButtonDatum[1].isSelected = false; + } + if (datum.name === this.nodeButtonDatum[1].name) { + this.nodeButtonDatum[0].isSelected = false; + this.nodeButtonDatum[1].isSelected = true; + } + this.currentlySelectedNodeButton = datum.name; + this.applyBufferDataToState(); + } + } + onZoom(eventTransform) { if (this.isGraphMounted) { this.setState(() => { @@ -381,7 +451,7 @@ class ForceDirectedGraph extends Component { }); } } - + onGraphDrag(xAndYCoords) { let translate = `translate(${xAndYCoords.x}, ${xAndYCoords.y})`; let oldTransform = this.state.mainGroupTransform; @@ -393,7 +463,7 @@ class ForceDirectedGraph extends Component { }); } } - + onNodeDrag(datum, xAndYCoords) { let nodeIndex = this.nodeIndexTracker.get(datum.id); if (this.nodeBuffer[nodeIndex]) { @@ -402,7 +472,7 @@ class ForceDirectedGraph extends Component { this.applyBufferDataToState(); } } - + addNodeInterpolator(nodeId, key, startingValue, endingValue, duration) { let numberInterpolator = interpolateNumber(startingValue, endingValue); let timeNow = now(); @@ -410,20 +480,20 @@ class ForceDirectedGraph extends Component { nodeId: nodeId, key: key, duration: duration, timeCreated: timeNow, method: numberInterpolator }; this.interpolators.set(nodeId, interpolationObject); - + if (!this.areInterpolationsRunning) { this.interpolationTimer.restart(this.runInterpolators, simulationKeys.DATA_COPY_INTERVAL); this.areInterpolationsRunning = true; } } - + runInterpolators() { // If we have no more interpolators to run then shut'r down! if (this.interpolators.size === 0) { this.interpolationTimer.stop(); this.areInterpolationsRunning = false; } - + let iterpolatorsComplete = []; // Apply interpolation values this.interpolators.forEach((interpolator) => { @@ -439,22 +509,22 @@ class ForceDirectedGraph extends Component { this.nodeBuffer[nodeIndex][interpolator.key] = interpolator.method(t); } }); - + // Remove any interpolators that are complete if (iterpolatorsComplete.length > 0) { for (let i = 0; i < iterpolatorsComplete.length; i++) { this.interpolators.delete(iterpolatorsComplete[i]); } } - + this.applyBufferDataToState(); } - + render() { // We will be using these values veru shortly, commenting out for eslint // reasons so we can build for PV let {viewWidth, viewHeight} = this.props; let {nodes, links, mainGroupTransform} = this.state; - + return ( <div className='ts-force-selected-graph'> <svg className={'fdgMainSvg'} width='100%' height='100%'> @@ -478,9 +548,9 @@ class ForceDirectedGraph extends Component { </div> ); } - + static graphCounter = 0; - + static generateNewProps(nodeArray, linkArray, metaData) { ForceDirectedGraph.graphCounter += 1; return { diff --git a/src/generic-components/graph/NodeFactory.js b/src/generic-components/graph/NodeFactory.js index da57ffb..abfd73b 100644 --- a/src/generic-components/graph/NodeFactory.js +++ b/src/generic-components/graph/NodeFactory.js @@ -1,25 +1,28 @@ /* - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. * Copyright © 2017 Amdocs - * ================================================================================ + * 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 + * 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========================================================= + * ============LICENSE_END===================================================== * - * ECOMP is a trademark and service mark of AT&T Intellectual Property. + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. */ + import React from 'react'; import NodeVisualElementConstants from './NodeVisualElementConstants.js'; @@ -38,11 +41,11 @@ class NodeFactory { this.graphMeta = metaObject; this.visualElementFactory.setVisualElementMeta(metaObject); } - - buildNode(nodeType, nodeProps) { + // hideButton a temporary solution to not display the button. + buildNode(nodeType, nodeProps, hideButton) { let translate = `translate( - ${nodeProps.renderProps.x}, + ${nodeProps.renderProps.x}, ${nodeProps.renderProps.y})`; let finalProps = { ...nodeProps.renderProps, @@ -51,14 +54,20 @@ class NodeFactory { }; let nodeVisualElementsData = this.extractVisualElementArrayFromMeta( - nodeType); + nodeType, hideButton); let nodeVisualElements = undefined; if (nodeVisualElementsData) { nodeVisualElements = []; nodeVisualElementsData.map((elementData, index) => { if (elementData.type === NodeVisualElementConstants.BUTTON) { if (nodeProps.buttons) { - let isButtonSelected = true; + let isButtonSelected = false; + if (index === 4) { + isButtonSelected = nodeProps.buttons[0]; + } + if (index === 5) { + isButtonSelected = nodeProps.buttons[1]; + } elementData = { ...elementData, isSelected: isButtonSelected @@ -99,11 +108,20 @@ class NodeFactory { return React.createElement('g', finalProps); } - extractVisualElementArrayFromMeta(nodeClassName) { + extractVisualElementArrayFromMeta(nodeClassName, hideButton) { let nodeVisualElements = undefined; if (this.graphMeta.aaiEntityNodeDescriptors) { nodeVisualElements = this.graphMeta.aaiEntityNodeDescriptors[nodeClassName].visualElements; + if(hideButton) { + // temp, until BE not sent the triangle button + for (var i = 0; i < nodeVisualElements.length; i++) { + if (nodeVisualElements[i].type === 'button' && nodeVisualElements[i].name === 'icon_triangle_warning') { + nodeVisualElements.splice(i, 1); + return; + } + } + } } return nodeVisualElements; } diff --git a/src/generic-components/graph/NodeVisualElementConstants.js b/src/generic-components/graph/NodeVisualElementConstants.js index 11d9f7d..aeeedf7 100644 --- a/src/generic-components/graph/NodeVisualElementConstants.js +++ b/src/generic-components/graph/NodeVisualElementConstants.js @@ -1,25 +1,28 @@ /* - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. * Copyright © 2017 Amdocs - * ================================================================================ + * 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 + * 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========================================================= + * ============LICENSE_END===================================================== * - * ECOMP is a trademark and service mark of AT&T Intellectual Property. + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. */ + export default { SVG_CIRCLE: 'circle', SVG_LINE: 'line', @@ -41,6 +44,5 @@ export default { ICON_ELLIPSES: 'icon_ellipses', ICON_TRIANGLE_WARNING: 'icon_triangle_warning', ICON_TICK: 'icon_tick', - ICON_WARNING: 'icon_warning', - BUTTON_CLICK_NODE_DETAILS: 'NODE_DETAILS' + ICON_WARNING: 'icon_warning' }; diff --git a/src/generic-components/graph/TempCreateAttributes.js b/src/generic-components/graph/TempCreateAttributes.js new file mode 100644 index 0000000..fac32ba --- /dev/null +++ b/src/generic-components/graph/TempCreateAttributes.js @@ -0,0 +1,464 @@ +/* + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * 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===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +import React from 'react'; + + +export default { + createNodeAttributes: () => { + return { + className: 'node' + }; + }, + createShapeAttributes: () => { + return { + r: 3 + }; + }, + createCircleStyle: () => { + return { + fill: 'rgb(98, 102, 104)', + stroke: 'rgb(78, 82, 84)', + strokeWidth: 1 + }; + }, + createLinkAttributes: () => { + return { + className: 'link', + x1: 50, + y1: 50, + x2: 100, + y2: 500, + }; + }, + createLineStyle: () => { + return { + stroke: 'rgb(217, 218, 218)', + strokeWidth: 1 + }; + }, + createTestCircle: () => { + return React.createElement('circle', { + cx: '100', + cy: '100', + r: '15', + fill: 'rgb(255, 255, 255)', + stroke: 'rgb(98, 102, 104)', + strokeWidth: '3' + }); + }, + getNodeLinkArray: () => { + return { + 'graphMeta': { + 'aaiEntityNodeDescriptors': { + 'generalNodeClass': { + 'class': 'aai-entity-node general-node', + 'visualElements': [ + { + 'type': 'circle', + 'class': 'outer', + 'svgAttributes': { + 'r': '16' + } + }, + { + 'type': 'circle', + 'class': 'inner', + 'svgAttributes': { + 'r': '10' + } + }, + { + 'type': 'text', + 'class': 'id-type-label', + 'displayKey': 'itemType', + 'shapeAttributes': { + 'offset': { + 'x': '0', + 'y': '33' + } + } + }, + { + 'type': 'text', + 'class': 'id-value-label', + 'displayKey': 'itemNameValue', + 'shapeAttributes': { + 'offset': { + 'x': '0', + 'y': '48' + } + } + } + ] + }, + 'searchedNodeClass': { + 'class': 'aai-entity-node search-node', + 'visualElements': [ + { + 'type': 'circle', + 'class': 'outer', + 'svgAttributes': { + 'r': '16' + } + }, + { + 'type': 'circle', + 'class': 'inner', + 'svgAttributes': { + 'r': '10' + } + }, + { + 'type': 'text', + 'class': 'id-type-label', + 'displayKey': 'itemType', + 'shapeAttributes': { + 'offset': { + 'x': '0', + 'y': '33' + } + } + }, + { + 'type': 'text', + 'class': 'id-value-label', + 'displayKey': 'itemNameValue', + 'shapeAttributes': { + 'offset': { + 'x': '0', + 'y': '48' + } + } + } + ] + }, + 'selectedSearchedNodeClass': { + 'class': 'aai-entity-node selected-search-node', + 'visualElements': [ + { + 'type': 'circle', + 'class': 'outer', + 'svgAttributes': { + 'r': '31' + } + }, + { + 'type': 'circle', + 'class': 'inner', + 'svgAttributes': { + 'r': '20' + } + }, + { + 'type': 'text', + 'class': 'id-type-label', + 'displayKey': 'itemType', + 'shapeAttributes': { + 'offset': { + 'x': '0', + 'y': '48' + } + } + }, + { + 'type': 'text', + 'class': 'id-value-label', + 'displayKey': 'itemNameValue', + 'shapeAttributes': { + 'offset': { + 'x': '0', + 'y': '63' + } + } + }, + { + 'type': 'button', + 'name': 'icon_ellipses', + 'class': 'node-button', + 'shapeAttributes': { + 'offset': { + 'x': '33', + 'y': '-35' + } + }, + 'svgAttributes': { + 'className': 'node-button', + 'r': '10' + } + }, + { + 'type': 'button', + 'name': 'icon_triangle_warning', + 'class': 'node-button', + 'shapeAttributes': { + 'offset': { + 'x': '46', + 'y': '-12' + } + }, + 'svgAttributes': { + 'className': 'node-button', + 'r': '10' + } + } + ] + }, + 'selectedNodeClass': { + 'class': 'aai-entity-node selected-node', + 'visualElements': [ + { + 'type': 'circle', + 'class': 'outer', + 'svgAttributes': { + 'r': '31' + } + }, + { + 'type': 'circle', + 'class': 'inner', + 'svgAttributes': { + 'r': '20' + } + }, + { + 'type': 'text', + 'class': 'id-type-label', + 'displayKey': 'itemType', + 'shapeAttributes': { + 'offset': { + 'x': '0', + 'y': '48' + } + } + }, + { + 'type': 'text', + 'class': 'id-value-label', + 'displayKey': 'itemNameValue', + 'shapeAttributes': { + 'offset': { + 'x': '0', + 'y': '63' + } + } + }, + { + 'type': 'button', + 'name': 'icon_ellipses', + 'class': 'node-button', + 'shapeAttributes': { + 'offset': { + 'x': '33', + 'y': '-35' + } + }, + 'svgAttributes': { + 'className': 'node-button', + 'r': '10' + } + }, + { + 'type': 'button', + 'name': 'icon_triangle_warning', + 'class': 'node-button', + 'shapeAttributes': { + 'offset': { + 'x': '46', + 'y': '-12' + } + }, + 'svgAttributes': { + 'className': 'node-button', + 'r': '10' + } + } + ] + } + }, + 'numNodes': 6, + 'numLinks': 5, + 'renderTimeInMs': 4550, + 'numLinksResolvedSuccessfullyFromCache': 0, + 'numLinksResolvedSuccessfullyFromServer': 7, + 'numLinkResolveFailed': 0 + }, + 'nodes': [{ + 'id': 'TRINITY-PSERVER', + 'itemType': 'pserver', + 'itemNameKey': 'pserver.TRINITY-PSERVER', + 'itemNameValue': 'TRINITY-PSERVER', + 'itemProperties': { + 'hostname': 'TRINITY-PSERVER', + 'in-maint': 'false', + 'resource-version': '1455590484' + }, + 'nodeMeta': { + 'className': 'selectedSearchedNodeClass', + 'nodeDebug': null, + 'selfLinkResponseTimeInMs': 628, + 'relationshipNode': false, + 'searchTarget': true, + 'enrichableNode': false, + 'nodeValidated': true, + 'nodeIssue': true + } + }, { + 'id': 'TRINITYSIL', + 'itemType': 'complex', + 'itemNameKey': 'complex.TRINITYSIL', + 'itemNameValue': 'TRINITYSIL', + 'itemProperties': { + 'country': 'USA', + 'postal-code': '07748', + 'city': 'Middletown', + 'physical-location-id': 'TRINITYSIL', + 'resource-version': '1459957457', + 'street1': 'Trinity', + 'state': 'NJ', + 'physical-location-type': 'Trinity', + 'region': 'US' + }, + 'nodeMeta': { + 'className': 'generalNodeClass', + 'nodeDebug': null, + 'selfLinkResponseTimeInMs': 644, + 'relationshipNode': false, + 'searchTarget': false, + 'enrichableNode': false, + 'nodeValidated': true, + 'nodeIssue': false + } + }, { + 'id': 'c385bb3e-6ebd-4898-bc92-792e0ac2db50', + 'itemType': 'vserver', + 'itemNameKey': 'vserver.c385bb3e-6ebd-4898-bc92-792e0ac2db50', + 'itemNameValue': 'c385bb3e-6ebd-4898-bc92-792e0ac2db50', + 'itemProperties': { + 'in-maint': 'false', + 'resource-version': '1475160142', + 'vserver-name': 'bems0001vm001', + 'prov-status': 'ACTIVE', + 'vserver-id': 'c385bb3e-6ebd-4898-bc92-792e0ac2db50', + 'vserver-name2': 'bems0001vm001bem001-1452', + 'vserver-selflink': 'TRINITY vserverLink', + 'is-closed-loop-disabled': 'false' + }, + 'nodeMeta': { + 'className': 'generalNodeClass', + 'nodeDebug': null, + 'selfLinkResponseTimeInMs': 2633, + 'relationshipNode': false, + 'searchTarget': false, + 'enrichableNode': false + } + }, { + 'id': '7c73d776-001d-4042-a958-37f2e419ed10', + 'itemType': 'vserver', + 'itemNameKey': 'vserver.7c73d776-001d-4042-a958-37f2e419ed10', + 'itemNameValue': '7c73d776-001d-4042-a958-37f2e419ed10', + 'itemProperties': { + 'resource-version': '1477075390', + 'vserver-name': 'nsbg0001vm002', + 'prov-status': 'NVTPROV', + 'vserver-id': '7c73d776-001d-4042-a958-37f2e419ed10', + 'vserver-name2': 'VM-19631', + 'vserver-selflink': 'TRINITY vserverLink' + }, + 'nodeMeta': { + 'className': 'generalNodeClass', + 'nodeDebug': null, + 'selfLinkResponseTimeInMs': 2368, + 'relationshipNode': false, + 'searchTarget': false, + 'enrichableNode': false + } + }, { + 'id': 'fc6be93d-915e-4034-a8f9-463b70130614', + 'itemType': 'vserver', + 'itemNameKey': 'vserver.fc6be93d-915e-4034-a8f9-463b70130614', + 'itemNameValue': 'fc6be93d-915e-4034-a8f9-463b70130614', + 'itemProperties': { + 'resource-version': '1477075398', + 'vserver-name': 'nsbg0001vm004', + 'prov-status': 'NVTPROV', + 'vserver-id': 'fc6be93d-915e-4034-a8f9-463b70130614', + 'vserver-name2': 'VM-19630', + 'vserver-selflink': 'TRINITY vserverLink' + }, + 'nodeMeta': { + 'className': 'generalNodeClass', + 'nodeDebug': null, + 'selfLinkResponseTimeInMs': 2621, + 'relationshipNode': false, + 'searchTarget': false, + 'enrichableNode': false + } + }, { + 'id': '8555c2ed-6818-43c5-8cf5-cd36b0169031', + 'itemType': 'vserver', + 'itemNameKey': 'vserver.8555c2ed-6818-43c5-8cf5-cd36b0169031', + 'itemNameValue': '8555c2ed-6818-43c5-8cf5-cd36b0169031', + 'itemProperties': { + 'resource-version': '1477075396', + 'vserver-name': 'nsbg0001vm003', + 'prov-status': 'NVTPROV', + 'vserver-id': '8555c2ed-6818-43c5-8cf5-cd36b0169031', + 'vserver-name2': 'VM-19629', + 'vserver-selflink': 'TRINITY vserverLink' + }, + 'nodeMeta': { + 'className': 'generalNodeClass', + 'nodeDebug': null, + 'selfLinkResponseTimeInMs': 2663, + 'relationshipNode': false, + 'searchTarget': false, + 'enrichableNode': false + } + }], + 'links': [{ + 'id': 'TRINITY-PSERVER_TRINITYSIL', + 'source': 'TRINITY-PSERVER', + 'target': 'TRINITYSIL' + }, { + 'id': 'TRINITY-PSERVER_c385bb3e-6ebd-4898-bc92-792e0ac2db50', + 'source': 'TRINITY-PSERVER', + 'target': 'c385bb3e-6ebd-4898-bc92-792e0ac2db50' + }, { + 'id': 'TRINITY-PSERVER_7c73d776-001d-4042-a958-37f2e419ed10', + 'source': 'TRINITY-PSERVER', + 'target': '7c73d776-001d-4042-a958-37f2e419ed10' + }, { + 'id': 'TRINITY-PSERVER_fc6be93d-915e-4034-a8f9-463b70130614', + 'source': 'TRINITY-PSERVER', + 'target': 'fc6be93d-915e-4034-a8f9-463b70130614' + }, { + 'id': 'TRINITY-PSERVER_8555c2ed-6818-43c5-8cf5-cd36b0169031', + 'source': 'TRINITY-PSERVER', + 'target': '8555c2ed-6818-43c5-8cf5-cd36b0169031' + }] + }; + } +}; diff --git a/src/generic-components/treeNode/TreeNode.jsx b/src/generic-components/treeNode/TreeNode.jsx new file mode 100644 index 0000000..b5fc3cc --- /dev/null +++ b/src/generic-components/treeNode/TreeNode.jsx @@ -0,0 +1,79 @@ +/* + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * 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===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +import React, {Component} from 'react'; +import classNames from 'classnames'; + + + + +class TreeNode extends Component { + + + constructor(props) { + super(props); + this.state = { + visible: false, + }; + } + + toggle = () => { + this.setState({visible: !this.state.visible}); + }; + + render() { + var childNodes; + var classObj; + if (this.props.node !== undefined && this.props.node.childNodes !== undefined) { + childNodes = this.props.node.childNodes.map(function (node, index) { + return <li key={index}><TreeNode node={node}/></li>; + }); + + classObj = { + togglable: true, + 'togglable-down': this.state.visible, + 'togglable-up': !this.state.visible + }; + } + + var style; + if (!this.state.visible) { + style = {display: 'none'}; + } + + return ( + <div> + <h7 onClick={this.toggle} className={classNames(classObj)}> + {this.props.node.title} + </h7> + <ul style={style} className='node-tree'> + {childNodes} + </ul> + </div> + ); + } +} + +export default TreeNode; |