diff options
author | wr148d <wr148d@att.com> | 2021-01-15 15:32:00 -0500 |
---|---|---|
committer | wr148d <wr148d@att.com> | 2021-02-11 09:47:17 -0500 |
commit | 5ee7367a101143715c2869d72ea4a6fbf55f5af6 (patch) | |
tree | 84bf43601c0cce4fb37b5b3b494e113c96d5591e /src/app/model/modelSearch | |
parent | ddc05d4ea0254b427fea6ec80e2b03950eeca4ce (diff) |
Updated Sparky to add ECOMP functionality Browse, Specialized Search, BYOQ, and the Builder FE Updates
Issue-ID: AAI-3250
Change-Id: I576e37f77f7e9b40d72e4a5e7de645e9f62bc7d2
Signed-off-by: wr148d <wr148d@att.com>
Diffstat (limited to 'src/app/model/modelSearch')
-rw-r--r-- | src/app/model/modelSearch/Model.jsx | 846 | ||||
-rw-r--r-- | src/app/model/modelSearch/ModelActions.js | 20 | ||||
-rw-r--r-- | src/app/model/modelSearch/ModelConstants.js | 25 | ||||
-rw-r--r-- | src/app/model/modelSearch/ModelReducer.js | 43 | ||||
-rw-r--r-- | src/app/model/modelSearch/components/ModelBreadcrumb.jsx | 72 | ||||
-rw-r--r-- | src/app/model/modelSearch/components/ModelCard.jsx | 75 | ||||
-rw-r--r-- | src/app/model/modelSearch/components/ModelGallery.jsx | 672 | ||||
-rw-r--r-- | src/app/model/modelSearch/components/ModelNodeCard.jsx | 51 | ||||
-rw-r--r-- | src/app/model/modelSearch/components/ModelNodeGallery.jsx | 46 | ||||
-rw-r--r-- | src/app/model/modelSearch/components/ModelRelationships.jsx | 137 | ||||
-rw-r--r-- | src/app/model/modelSearch/components/ModelTabularView.jsx | 197 |
11 files changed, 2184 insertions, 0 deletions
diff --git a/src/app/model/modelSearch/Model.jsx b/src/app/model/modelSearch/Model.jsx new file mode 100644 index 0000000..9a49be9 --- /dev/null +++ b/src/app/model/modelSearch/Model.jsx @@ -0,0 +1,846 @@ +/* + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017-2021 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 React, { Component } from 'react'; +import { connect } from 'react-redux'; +import Filter from 'generic-components/filter/Filter.jsx'; +import OutputToggle from 'generic-components/OutputToggle.jsx'; +import {ExportExcel} from 'utils/ExportExcel.js'; +import commonApi from 'utils/CommonAPIService.js'; +import {GlobalExtConstants} from 'utils/GlobalExtConstants.js'; +import Spinner from 'utils/SpinnerContainer.jsx'; +import ModelGallery from './components/ModelGallery.jsx'; +import DatePicker from 'react-datepicker'; +import moment from "moment"; +import ModelBreadcrumb from './components/ModelBreadcrumb.jsx'; +import Grid from 'react-bootstrap/lib/Grid'; +import Row from 'react-bootstrap/lib/Row'; +import Col from 'react-bootstrap/lib/Col'; +import Button from 'react-bootstrap/lib/Button'; +import Modal from 'react-bootstrap/lib/Modal'; +import Pagination from 'react-js-pagination'; +import { ModelConstants } from './ModelConstants'; +import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; +import Tooltip from 'react-bootstrap/lib/Tooltip'; +import DownloadRangeModel from 'generic-components/DownloadRangeModel.jsx'; +import BootstrapSwitchButton from 'bootstrap-switch-button-react'; + +let INVLIST = GlobalExtConstants.INVLIST; +let DOWNLOAD_ALL = GlobalExtConstants.DOWNLOAD_ALL; +let generateExcels = ExportExcel.generateExcels; +let buildAttrList = ExportExcel.buildAttrList; +let DOWNLOAD_TOOLTIP = GlobalExtConstants.DOWNLOAD_TOOLTIP; +let ENVIRONMENT = GlobalExtConstants.ENVIRONMENT; +let APERTURE_SERVICE = JSON.parse(sessionStorage.getItem(ENVIRONMENT + 'APERTURE_SERVICE')); +let filterTypeList = GlobalExtConstants.FILTER_TYPES; +let TABULAR_FILTER_TYPE = GlobalExtConstants.TABULAR_FILTER_TYPE; +let URI_DELIMITCHAR = GlobalExtConstants.URI_DELIMITCHAR; +/*const mapStateToProps = ({extensibility: {extModelReducer} }) => { + let {inventoryData} = extModelReducer; + return { + inventoryData + }; +};*/ + + const mapStateToProps = (state) => { + return { + currState: state.modelReducer + }; +}; + +const mapActionToProps = (dispatch) => { + return { + onInventoryDataReceived: (data) => { + dispatch({ type: 'UPDATE_INVENTORY_DATA', data: data }); + } + }; +}; + +/** + * This class is used to handle any url interactions for models. + * When a user selects a inventory item in browse or special search, + * this model class should be used to handle the url + params and query + * the proxy server. + */ + +export class model extends Component { + + elements = []; + filterQuery = ''; + pageTitle = ''; + nodeType = ''; + historyStackString = ''; + typeOfCall = true; + nodeResults = ''; + downloadTooltip = DOWNLOAD_TOOLTIP; + downloadAllTooltip = 'Downloads First ' + DOWNLOAD_ALL + ' Results'; + downloadRangeTooltip= 'Downloads Results By Custom Range Selection'; + initialFilterSelectedList = []; + initialFilterMessage = []; + initialNonDisplay = true; + constructor(props) { + console.log(props); + APERTURE_SERVICE = JSON.parse(sessionStorage.getItem(ENVIRONMENT + 'APERTURE_SERVICE')); + super(props); + var filterSelectedList = []; + var seletedFilter = []; + var filterMessage = []; + var nonDisplay = true; + var enableToggle= JSON.parse(sessionStorage.getItem(ENVIRONMENT + 'ENABLE_ANALYSIS')); + TABULAR_FILTER_TYPE=(APERTURE_SERVICE && enableToggle)?'CONTAINS':'='; + if (this.props.match.params.type) { + seletedFilter = this.props.match.params.type.split(';'); + seletedFilter.map((param) => { + console.log('param', param); + console.log('param.indexOf(=)', param.indexOf('=')); + if (param.indexOf(URI_DELIMITCHAR+'='+URI_DELIMITCHAR) !== -1) { + let id = param.split(URI_DELIMITCHAR+'='+URI_DELIMITCHAR)[0]; + let value = param.split(URI_DELIMITCHAR+'='+URI_DELIMITCHAR)[1]; + filterSelectedList.push({ 'id': id, 'value': value, 'type':'='}); + filterMessage.push(id+'='+value); + enableToggle=false; + }else{ + for(var x in filterTypeList){ + if (param.indexOf(URI_DELIMITCHAR+filterTypeList[x]+URI_DELIMITCHAR) !== -1) { + let paramArray=param.split(URI_DELIMITCHAR+filterTypeList[x]+URI_DELIMITCHAR); + let id = paramArray[0]; + let value = paramArray[1]; + filterSelectedList.push({ 'id': id, 'value': value, 'type': filterTypeList[x]}); + filterMessage.push(id+filterTypeList[x]+value); + enableToggle=true; + } + } + } + }); + } + if (this.props.match.params.nodeId) { + nonDisplay = false; + } + if(this.props.match.params.page==='false'){ + enableToggle=false; + } + if(!this.props.location.historyStackString){ + this.props.location.historyStackString = this.props.location.pathname + ',,Origin||'; + }else{ + this.historyStackString = this.props.location.historyStackString; + } + this.state = { + activePage: 1, + totalResults: 0, + enableBusyFeedback: true, + data: [], + filterList: [], + nodes: [], + filterSelected: (this.props.match.params.type) ? this.props.match.params.type.split(';')[0] : '', + filterDisplay: 'Select Filter', + filterTypeDisplay: 'Filter Type', + filterMessage: filterMessage, + filterSelectedList: filterSelectedList, + isRunEnable: false, + isFilterEnable: nonDisplay, + isPaginationEnable: nonDisplay, + showHistoryModal: false, + nodeDisplay: 'test', + startDate: moment(), + historyType: 'nodeState', + enableCalendar: true, + focusedNodeUri: 0, + viewName: localStorage.getItem(GlobalExtConstants.ENVIRONMENT + '_' + sessionStorage.getItem(GlobalExtConstants.ENVIRONMENT + 'userId') + '_viewPreference') || 'CardLayout', + errorResults: false, + errorMessage: '', + showResults: false, + resetColumnFilters: true, + defaultViewName: localStorage.getItem(GlobalExtConstants.ENVIRONMENT + '_' + sessionStorage.getItem(GlobalExtConstants.ENVIRONMENT + 'userId') + '_viewPreference') || 'CardLayout', + isPageNumberChange: false, + totalPages: 0, + pageRange: 1, + showDownloadResultsModal: false, + errorDownloadResults:false, + downloadErrorMsg: '', + enableModelBusyFeedback:false, + downloadCount:DOWNLOAD_ALL, + enableRealTime: enableToggle + }; + this.baseState=this.state; + } + resultsMessage = ''; + componentDidMount = () => { + console.log('[Model.jsx] componentDidMount props available are', JSON.stringify(this.props)); + if (this.state.isFilterEnable) { + this.populateFilteringOptions(); + } + this.initialFilterSelectedList = this.state.filterSelectedList; + this.initialFilterMessage = this.state.filterMessage; + this.initialNonDisplay = this.state.isFilterEnable; + this.beforefetchInventoryData(); + }; + handleDateChange = (newDate) =>{ + this.setState({ startDate: moment(+newDate) }); + console.log('[Model.jsx] handleDateChange date is ', this.state.startDate); + console.log('[Model.jsx] handleDateChange date is in millis ', +this.state.startDate); + } + openHistory = (nodeDisplay, nodeUri, nodeType) => { // open modal + console.log('history >> showModal'); + let historyNodeUri = (nodeUri)?nodeUri.replace('/aperture/','/'):nodeUri;//replace always first occurence + if(nodeDisplay){ + this.setState({ + nodeDisplay: nodeDisplay, + focusedNodeUri: historyNodeUri, + focusedNodeType: nodeType, + showHistoryModal:true + }); + }else{ + this.setState({ + showHistoryModal:true + }); + } + } + closeHistory = () => { + this.setState({ + showHistoryModal: false + }); + } + submitHistory = () => { + //do some logic in history + console.log("submitting history"); + let epochStartTime = (this.state.startDate).unix(); + this.props.history.push('/history/' + this.state.historyType+'/' + this.nodeType + '/' + btoa(this.state.focusedNodeUri) + '/' + epochStartTime * 1000); + } + setHistoryType(event) { + console.log(event.target.value); + let enableCalendar = false; + if(event.target.value === 'nodeLifeCycle'){ + enableCalendar = false; + }else{ + enableCalendar = true; + } + this.setState({ + historyType: event.target.value, + enableCalendar: enableCalendar + }); + console.log(this.state.enableCalendar); + } + setViewName(event) { + console.log(event.currentTarget.value); + this.setState({ + viewName: event.currentTarget.value + }); + } + setDefaultViewName = (event) =>{ + let ENVIRONMENT = GlobalExtConstants.ENVIRONMENT; + let layout = event.target.value; + + if(sessionStorage.getItem(ENVIRONMENT + 'userId')) { + if (event.target.checked) { + localStorage.setItem(ENVIRONMENT + '_' + sessionStorage.getItem(ENVIRONMENT + 'userId') + '_viewPreference', layout); + } else { + localStorage.removeItem(ENVIRONMENT + '_' + sessionStorage.getItem(ENVIRONMENT + 'userId') + '_viewPreference'); + } + } + + this.setState({ + defaultViewName: event.target.value + }); + this.baseState.viewName=event.target.value; + this.baseState.defaultViewName=event.target.value; + } + componentWillUnmount = () => { + console.log('[Model.jsx] componentWillUnMount'); + this.props.onInventoryDataReceived([]); + } + beforefetchInventoryData = (param) => { + this.typeOfCall = true; + if (param) { + this.props.onInventoryDataReceived([]); + this.formFilterQuery(param.filterMessage); + this.setState( + { enableBusyFeedback: true, activePage: 1, totalResults: 0, totalPages: 0,filterMessage: param.filterMessage, filterSelectedList: param.filterSelectedList}, + function () { this.fetchInventoryData(param); }.bind(this) + ); + } else { + this.formFilterQuery(this.state.filterMessage); + this.fetchInventoryData(); + } + }; + + formFilterQuery = (filterMessage) => { + let filterQuery = filterMessage.join('&'); + this.filterQuery = (filterMessage.length > 0) ? '&' + filterQuery : ''; + }; + + fetchInventoryData = (param) => { + console.log('fetchInventoryData', param); + this.resultsMessage = ''; + const inventory = INVLIST.INVENTORYLIST; + let url = ''; + console.log('[Model.jsx] fetchInventoryData nodeId= ', this.props.match.params.nodeId); + if (this.props.match.params.type !== undefined && this.props.match.params.type !== null) { + this.nodeType = this.props.match.params.type; + } + console.log('[Model.jsx] nodeType: ' + this.nodeType); + let pageName = this.nodeType.split(';')[0].replace(/\s/g, '').toUpperCase(); + console.log('[Model.jsx] pageName: ' + pageName); + if (inventory[pageName] && inventory[pageName].display) { + this.pageTitle = inventory[pageName].display; + } else { + this.pageTitle = pageName; + } + var nonRelationshipState = false; + if (this.props.match.params.nodeId) { + this.setUri(this.props.location.uri); + this.setBreadcrumb(this.props.location.uri); + url = sessionStorage.getItem(ENVIRONMENT + 'URI'); + if(this.state.enableRealTime){ + let versionPattern="^"+INVLIST.VERSION+"\\/"; + var versionRegularExp = new RegExp(versionPattern, 'g'); + let matchVersion = url.match(versionRegularExp,'g'); + if(!matchVersion){ + url= INVLIST.VERSION+'/'+url; + } + } + } else { + url = inventory[pageName].apiPath; + this.setBreadcrumb(url); + nonRelationshipState=true; + } + console.log('[Model.jsx] active page', this.state.activePage); + console.log('this.state.filterSelectedList', this.state.filterSelectedList); + console.log('filterQuery', this.filterQuery); + //Aperture with Diff Filter types Operater + this.nodeResults = ''; + var method = 'GET'; + var payload = {}; + const settings = { + 'NODESERVER': INVLIST.NODESERVER, + 'PROXY': INVLIST.PROXY, + 'PREFIX': INVLIST.PREFIX, + 'VERSION': INVLIST.VERSION, + 'USESTUBS': INVLIST.useStubs, + 'APERTURE': INVLIST.APERTURE, + 'APERTURE_SERVICENAME':INVLIST.APERTURE_SERVICENAME + }; + if(this.state.enableRealTime){ + settings['ISAPERTURE'] = (nonRelationshipState)? true : false; + }else{ + if(!(this.state.enableRealTime && nonRelationshipState)){ + url = (url)?url.replace(INVLIST.VERSION+'/',''):url; + } + } + if(this.state.enableRealTime && nonRelationshipState){ + var filterList=this.state.filterSelectedList; + var filters = []; + for(var k in filterList){ + if(filterList.hasOwnProperty(k)){ + let filter ={} + filter['filter']= filterList[k].type; + filter['key'] = filterList[k].id; + filter['value'] = filterList[k].value; + filters.push(filter); + } + } + method= 'POST'; + payload['node-type'] = url.split('/')[1]; + payload['filter-version'] = 'v1'; + payload['filters'] = filters; + }else{ + payload = null; + } + + var path = '?format=simple&resultIndex=' + this.state.activePage + '&resultSize='; + if(this.typeOfCall){ + path = path + ModelConstants.RESULTS_PER_PAGE; + url=(this.state.enableRealTime && nonRelationshipState)? path: url + path + this.filterQuery; + this.commonApiServiceCall(settings,url,param,method,payload); + }else{ + let pagerange=this.state.pageRange.toString(); + pagerange=pagerange.split('-'); + if(pagerange.length > 1){ + path = '?format=simple&resultIndex=' + parseInt(pagerange[0]) + '&resultSize='+ ModelConstants.RESULTS_PER_PAGE + '&resultRangeEnd=' + parseInt(pagerange[1]); + }else{ + path = '?format=simple&resultIndex=' + 1 + '&resultSize=' + parseInt(pagerange); + } + url=(this.state.enableRealTime && nonRelationshipState)? path: url + path + this.filterQuery; + this.commonApiServiceCallForAllData(settings,url,method,payload); + } + }; + commonApiServiceCall = (settings,url,param,method,payload) =>{ + + commonApi(settings, url, method, payload, 'modelDefault') + .then(res => { + // Call dispatcher to update state + console.log('once before service call ......',this.state); + this.resultsMessage = ''; + var totalResults = parseInt(res.headers['total-results']); + let downloadCount = DOWNLOAD_ALL; + if(totalResults > DOWNLOAD_ALL){ + this.downloadAllTooltip = DOWNLOAD_ALL + ' results out of '+ totalResults +' will be downloaded, please filter results further to obtain full report'; + }else{ + this.downloadAllTooltip = (totalResults === 1) ? 'Downloads ' + totalResults + ' Results' : 'Downloads all ' + totalResults + ' Results' + downloadCount= totalResults; + } + this.setState( + { + nodes : res.data.results, + totalResults : res.headers['total-results'], + totalPages: res.headers['total-results'], + enableBusyFeedback:false, + showResults: true, + errorResults: false, + downloadCount: downloadCount, + filterSelectedList:(param)?param.filterSelectedList:this.state.filterSelectedList + },function(){this.props.onInventoryDataReceived(res.data.results);}); + + console.log('After service call ......',this.state); + console.log('[Model.jsx] results : ', res); + }, error=>{ + this.triggerError(error); + }).catch(error => { + this.triggerError(error); + }); + }; + commonApiServiceCallForAllData = (settings,url,method,payload) => { + + commonApi(settings, url,method, payload, 'modelDefault') + .then(res => { + // Call dispatcher to update state + console.log('once before service call ......',this.state); + this.resultsMessage = ''; + this.nodeResults = res.data.results; + let totalResults = parseInt(res.headers['total-results']); + let totalPages = parseInt(res.headers['total-pages']); + this.setState({totalPages:totalPages,errorDownloadResults:false,downloadErrorMsg:''},() => {this.getAllExcels()}); + console.log('[Model.jsx] results : ', res); + }).catch(error => { + console.log('[Model.jsx] error : ', error); + this.nodeResults = ''; + let errMsg = this.renderErrorMsg(error); + this.setState({ enableBusyFeedback: false,errorDownloadResults:true,downloadErrorMsg:errMsg,enableModelBusyFeedback:false}); + }); + }; + + triggerError = (error) => { + console.error('[Model.jsx] error : ', JSON.stringify(error)); + this.props.onInventoryDataReceived([]); + this.resultsMessage = 'No Results Found'; + this.downloadAllTooltip = 'Downloads First ' + DOWNLOAD_ALL + ' Results'; + this.nodeResults = ''; + this.setState({ + enableBusyFeedback: false, + totalResults: 0, + totalPages: 0, + showResults: false, + errorResults: true + }); + let errMsg = this.renderErrorMsg(error); + //Suppress Error Message when 404 results not found occur + if(error.response && error.response.status === 404){ + this.setState({errorMessage:'', errorResults:false}); + }else{ + this.setState({errorMessage:errMsg}); + } + }; + renderErrorMsg = (error) =>{ + let errMsg=''; + if (error.response) { + // The request was made and the server responded with a status code + // that falls out of the range of 2xx + console.log(error.response.data); + console.log(error.response.status); + console.log(error.response.headers); + if(error.response.status){ + errMsg += " Code: " + error.response.status; + } + if(error.response.data){ + errMsg += " - " + JSON.stringify(error.response.data); + } + } else if (error.request) { + // The request was made but no response was received + // `error.request` is an instance of XMLHttpRequest in the browser and an instance of + // http.ClientRequest in node.js + console.log(error.request); + errMsg += " - Request was made but no response received"; + } else { + // Something happened in setting up the request that triggered an Error + console.log('Error', error.message); + errMsg += " - Unknown error occurred " + error.message; + } + + console.log(error.config); + return errMsg; + } + + componentWillReceiveProps(nextProps) { + console.log('[Model.jsx] componentWillReceiveProps'); + console.log('[Model.jsx] next nodeId:', nextProps.match.params.nodeId); + console.log('[Model.jsx] this nodeId:', this.props.match.params.nodeId); + + if(nextProps.match.params.nodeId !== undefined && this.props.match.params.nodeId !== undefined){ + if (nextProps.match.params.nodeId && nextProps.match.params.nodeId + !== this.props.match.params.nodeId) { + this.props = nextProps; + this.beforefetchInventoryData(); + } + } + }; + + setUri(uri) { + let delimiter = '\/'; + let start = 3; + let tokens = uri.split(delimiter).slice(start); + let result = tokens.join(delimiter); + sessionStorage.setItem(ENVIRONMENT + 'URI', result); + }; + + setBreadcrumb(uri){ + var nodeType = this.nodeType.split(';')[0]; + var display = nodeType + ': ' + (uri).split(nodeType+'\/').pop(); + var hsEntry = this.props.location.pathname + ',' + uri + ','+ display + '||'; + console.log("History Stack String: " + this.props.location.historyStackString); + if(this.historyStackString.indexOf(hsEntry) > -1){ + var tempHistoryStack = this.historyStackString.split(hsEntry)[0]; + var tempHistoryStackFormat = tempHistoryStack.replace(hsEntry, ""); + this.historyStackString = tempHistoryStackFormat; + } + this.historyStackString += hsEntry; + this.props.location.historyStackString = this.historyStackString; + console.log('[Model.jsx] historyStack in model' + this.historyStackString); + } + + handlePageChange = (pageNumber) => { + console.log('[Model.jsx] HandelPageChange active page is', pageNumber); + this.props.onInventoryDataReceived([]); + this.setState( + { activePage: pageNumber, enableBusyFeedback: true, resetColumnFilters: false, isPageNumberChange: true}, + function () { this.beforefetchInventoryData(); }.bind(this) + ); + }; + openDownloadRange = () =>{ + this.setState({ + showDownloadResultsModal: true, + errorDownloadResults: false, + downloadErrorMsg:''}); + } + closeDownloadResults = () =>{ + this.setState({ + showDownloadResultsModal: false, + enableModelBusyFeedback: false + }); + } + getAllExcels = (pageRange,rangeState) =>{ + console.log('getAllExcels>>>>>>>>>>>*',pageRange); + if(pageRange){ + this.typeOfCall=false; + let rangeModelState=(rangeState)? rangeState: false; + this.setState( + { pageRange: pageRange,enableBusyFeedback: true, enableModelBusyFeedback:true,showDownloadResultsModal:rangeModelState}, + function () { this.fetchInventoryData(); }.bind(this) + ); + }else{ + this.setState( + {errorDownloadResults: false, showDownloadResultsModal: false, downloadErrorMsg:'', enableBusyFeedback: false, enableModelBusyFeedback:false}, + function () { generateExcels(this.nodeResults);this.nodeResults='';this.typeOfCall = true;}.bind(this) + ); + } + }; + + populateFilteringOptions = () => { + let tempState = this.state; + tempState.filterList = buildAttrList(this.state.filterSelected, tempState.filterList); + this.setState(tempState); + }; + + // HELPER FUNCTIONS + isContaining = (nameKey, listArray) => { + let found = false; + listArray.map((lists) => { + if (lists.id === nameKey) { + found = true; + } + }); + return found; + }; + isTableFilterApply = (columnFilterList,nodeType,columnsList) => { + console.log('Model js ....columnFilterList:',columnFilterList); + console.log('Model js .... nodeType:',nodeType); + var columnFilter = columnFilterList[nodeType][0]; + console.log('model js columnFilter:',columnFilter); + var columns = columnsList[nodeType]; + var applyState = false; + var filterSelectedList = []; + var filterMessage = []; + + for(var i=0;i<columnFilter.length;i++){ + var colFilterValue = columnFilter[i][columns[i].value]; + if(colFilterValue != ""){ + filterSelectedList.push({ 'id': columns[i].value, 'value': colFilterValue, 'type': TABULAR_FILTER_TYPE}); + let filterMsg = columns[i].value + TABULAR_FILTER_TYPE + colFilterValue; + filterMessage.push(filterMsg); + applyState = true; + } + } + console.log('isTableFilterApply filterSelectedList>>>>>',filterSelectedList); + console.log('isTableFilterApply filterMessage>>>>>>>>>>',filterMessage); + + if(applyState){ + var tempState = this.state; + var state = true; + var stateFilterSelectedList = tempState.filterSelectedList; + var id = 'id'; + var value = 'value'; + if(stateFilterSelectedList.length > 0){ + for(var j in filterSelectedList){ + state = true; + for(var k in stateFilterSelectedList){ + if(stateFilterSelectedList[k][id] === filterSelectedList[j][id]){ + state =false; + tempState.filterSelectedList[k] = { 'id':filterSelectedList[j][id], 'value': filterSelectedList[j][value], 'type': TABULAR_FILTER_TYPE}; + } + } + if(state){ + tempState.filterSelectedList.push({ 'id':filterSelectedList[j][id], 'value': filterSelectedList[j][value], 'type': TABULAR_FILTER_TYPE}); + } + } + stateFilterSelectedList = tempState.filterSelectedList; + for(var k in stateFilterSelectedList){ + tempState.filterMessage[k] = stateFilterSelectedList[k][id] + TABULAR_FILTER_TYPE + stateFilterSelectedList[k][value] + } + }else{ + tempState.filterSelectedList = filterSelectedList; + tempState.filterMessage = filterMessage; + } + + console.log('isTableFilterApply tempState:',tempState); + this.beforefetchInventoryData(tempState); + } + }; + prepareModelGalleryElement = () =>{ + let modelGalleryElement=''; + if(this.state.isFilterEnable){ + modelGalleryElement = <ModelGallery nodes={this.props.currState.inventoryData} + viewName={this.state.viewName} + historyStackString={this.props.location.historyStackString} + openHistoryModal={this.openHistory} + isPageNumberChange={this.state.isPageNumberChange} + resetColumnInd={this.state.resetColumnFilters} + isTableFilterApply={this.isTableFilterApply} + enableRealTime={this.state.enableRealTime} + />; + }else{ + modelGalleryElement = <ModelGallery nodes={this.props.currState.inventoryData} + viewName={this.state.viewName} + historyStackString={this.props.location.historyStackString} + openHistoryModal={this.openHistory} + isPageNumberChange={this.state.isPageNumberChange} + resetColumnInd={this.state.resetColumnFilters} + enableRealTime={this.state.enableRealTime} + />; + } + return modelGalleryElement; + } + prepareDownloadRangeModel = () =>{ + + let downloadRangeModel =(this.state.showDownloadResultsModal)? <DownloadRangeModel + showDownloadResultsModal={this.state.showDownloadResultsModal} + totalPages={this.state.totalPages} + totalResults={this.state.totalResults} + triggerDownload={this.getAllExcels} + errorDownloadResults={this.state.errorDownloadResults} + downloadErrorMsg={this.state.downloadErrorMsg} + triggerClose={this.closeDownloadResults} + enableModelBusyFeedback={this.state.enableModelBusyFeedback} + /> : ''; + return downloadRangeModel; + } + toggleRealTimeAnalysisCallback=(checked)=>{ + console.log('toggleRealTimeAnalysisCallback>>>>',checked); + sessionStorage.setItem(ENVIRONMENT + 'ENABLE_ANALYSIS', !checked); + TABULAR_FILTER_TYPE=(APERTURE_SERVICE && !checked)?'CONTAINS':'='; + this.baseState.enableRealTime = !checked; + this.baseState.filterMessage = []; + this.baseState.filterSelectedList = []; + this.setState({...this.baseState},()=>{this.beforefetchInventoryData(this.state)}); + } + render() { + console.log('[Model Props] render: ', JSON.stringify(this.props) + 'elements : ', this.elements); + console.log('[Model nodeId] render: ', this.props.match.params.nodeId); + console.log('[Model nodeId] render this.state: ', this.state); + var toggelRealtimeAnalysis = ''; + if(APERTURE_SERVICE){ + toggelRealtimeAnalysis = <div className='toggleSwitch'><BootstrapSwitchButton + checked={!this.state.enableRealTime} + onlabel='Real Time' + onstyle='danger' + offlabel='Analysis' + offstyle='success' + style='w-100 mx-3' + onChange={(checked) => { + this.toggleRealTimeAnalysisCallback(checked); + }} + /></div> + } + const modelGalleryElement = this.prepareModelGalleryElement(); + let downloadRangeModel = this.prepareDownloadRangeModel(); + return ( + <div> + {toggelRealtimeAnalysis} + <Grid fluid={true} className='model-container'> + <Row className='show-grid'> + <Col md={12}> + <h1>{this.pageTitle}</h1> + <Filter key='browseSearch' + nodeType={this.state.filterSelected} + filterList={this.state.filterList} + filterDisplay={this.state.filterDisplay} + filterTypeDisplay={this.state.filterTypeDisplay} + isRunEnable={this.state.isRunEnable} + filterMessage={this.state.filterMessage} + loadInventory={this.beforefetchInventoryData} + filterSelectedList={this.state.filterSelectedList} + isFilterEnable={this.state.isFilterEnable} + enableRealTime={this.state.enableRealTime}/> + </Col> + </Row> + <Spinner loading={this.state.enableBusyFeedback}> + <Row className='show-grid'> + <Col md={8} className={this.state.isPaginationEnable && this.state.showResults ? 'show' : 'hidden'}> + <Pagination + activePage={this.state.activePage} + itemsCountPerPage={ModelConstants.RESULTS_PER_PAGE} + totalItemsCount={this.state.totalResults} + pageRangeDisplayed={ModelConstants.PAGE_RANGE_DISPLAY} + onChange={this.handlePageChange} /> + </Col> + <Col md={2} className={this.state.isPaginationEnable && this.state.showResults ? 'text-right' : 'text-left'}> + <OverlayTrigger placement='top' overlay={<Tooltip id='tooltip-top'>{this.downloadAllTooltip}</Tooltip>}> + <span className='d-inline-block' style={{display: 'inline-block'}}> + <Button bsSize='small' onClick={() => {this.getAllExcels(this.state.downloadCount)}}> + Download XLSX <i className='icon-documents-downloadablefile'></i> + </Button> + </span> + </OverlayTrigger> + </Col> + <Col md={2} className={this.state.isPaginationEnable && this.state.showResults ? 'text-right' : 'text-left'}> + <OverlayTrigger placement='top' overlay={<Tooltip id='tooltip-top'>{this.downloadRangeTooltip}</Tooltip>}> + <span className='d-inline-block' style={{display: 'inline-block'}}> + <Button bsSize='small' onClick={this.openDownloadRange}> + Download XLSX (Range)<i className='icon-documents-downloadablefile'></i> + </Button> + </span> + </OverlayTrigger> + </Col> + </Row> + <Row className='show-grid'> + <ModelBreadcrumb historyStackString={this.props.location.historyStackString}/> + </Row> + <Row> + <div className={'addPaddingTop alert alert-danger ' +(this.state.errorResults ? 'show' : 'hidden')} role="alert"> + An error occurred, please try again later. If this issue persists, please contact the system administrator. {this.state.errorMessage} + </div> + </Row> + <Row className='show-grid'> + { this.state.showResults && <div className='addPaddingTop'> + <OutputToggle scope={this} visualDisabled={true}/> + </div> + } + </Row> + <Row className={'show-grid ' + this.state.showResults ? 'show' : 'hidden'}> + <Col md={12}> + <hr /> + <h5>Total Results: <strong>{this.state.totalResults}</strong></h5> + <span className='resultMessage'>{this.resultsMessage}</span> + </Col> + </Row> + <Row className='show-grid'> + { + modelGalleryElement + } + </Row> + <Row className='show-grid'> + <Col md={12} className={this.state.isPaginationEnable && this.state.showResults ? 'show' : 'hidden'}> + <Pagination + activePage={this.state.activePage} + itemsCountPerPage={ModelConstants.RESULTS_PER_PAGE} + totalItemsCount={this.state.totalResults} + pageRangeDisplayed={ModelConstants.PAGE_RANGE_DISPLAY} + onChange={this.handlePageChange} /> + </Col> + </Row> + <div className='static-modal'> + <Modal show={this.state.showHistoryModal} onHide={this.closeHistory}> + <Modal.Header> + <Modal.Title>Retrieve {this.state.nodeDisplay} History</Modal.Title> + </Modal.Header> + <Modal.Body> + <form> + <div className="radio"> + <label> + <input type="radio" value="nodeState" + checked={this.state.historyType === 'nodeState'} + onChange={(e) => this.setHistoryType(e)} /> + View state at + </label> + </div> + <div className="radio"> + <label> + <input type="radio" value="nodeLifeCycleSince" + checked={this.state.historyType === 'nodeLifeCycleSince'} + onChange={(e) => this.setHistoryType(e)} /> + View updates since + </label> + </div> + <div className="radio"> + <label> + <input type="radio" value="nodeLifeCycle" + checked={this.state.historyType === 'nodeLifeCycle'} + onChange={(e) => this.setHistoryType(e)} /> + View all updates + </label> + </div> + </form> + <div className={this.state.enableCalendar ? 'show' : 'hidden'}> + <DatePicker + inline + selected={this.state.startDate} + onChange={(newDate) => this.handleDateChange(newDate)} + showTimeSelect + timeFormat="HH:mm" + timeIntervals={15} + dateFormat="MMMM D, YYYY h:mm a" + timeCaption="time" + /> + </div> + </Modal.Body> + <Modal.Footer> + <Button onClick={this.closeHistory}>Close</Button> + <Button onClick={this.submitHistory}>Submit</Button> + </Modal.Footer> + </Modal> + </div> + </Spinner> + <Spinner loading={this.state.enableModelBusyFeedback}> + {downloadRangeModel} + </Spinner> + </Grid> + </div> + ); + } +} + +export default connect(mapStateToProps, mapActionToProps)(model); diff --git a/src/app/model/modelSearch/ModelActions.js b/src/app/model/modelSearch/ModelActions.js new file mode 100644 index 0000000..e5faa35 --- /dev/null +++ b/src/app/model/modelSearch/ModelActions.js @@ -0,0 +1,20 @@ +/* + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017-2021 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========================================================= + */ + diff --git a/src/app/model/modelSearch/ModelConstants.js b/src/app/model/modelSearch/ModelConstants.js new file mode 100644 index 0000000..a9758c6 --- /dev/null +++ b/src/app/model/modelSearch/ModelConstants.js @@ -0,0 +1,25 @@ +/* + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017-2021 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========================================================= + */ + +export const ModelConstants = { + UPDATE_INVENTORY_DATA : 'UPDATE_INVENTORY_DATA', + RESULTS_PER_PAGE : 50 , + PAGE_RANGE_DISPLAY : 10 +}; diff --git a/src/app/model/modelSearch/ModelReducer.js b/src/app/model/modelSearch/ModelReducer.js new file mode 100644 index 0000000..0ea0c08 --- /dev/null +++ b/src/app/model/modelSearch/ModelReducer.js @@ -0,0 +1,43 @@ +/* + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017-2021 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 {ModelConstants} from './ModelConstants'; + +const initalState = { + inventoryData: [] +}; + +export default (state = initalState, action) => { + switch (action.type) { + case(ModelConstants.UPDATE_INVENTORY_DATA): + console.log('[ModelReducer] action.data:', action.data); + return { + ...state, + inventoryData: action.data + }; + default: + return { + ...state + }; + } + +}; + + diff --git a/src/app/model/modelSearch/components/ModelBreadcrumb.jsx b/src/app/model/modelSearch/components/ModelBreadcrumb.jsx new file mode 100644 index 0000000..ffcddb1 --- /dev/null +++ b/src/app/model/modelSearch/components/ModelBreadcrumb.jsx @@ -0,0 +1,72 @@ +/* + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017-2021 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 React from 'react'; +import Col from 'react-bootstrap/lib/Col'; +import Label from 'react-bootstrap/lib/Label'; +import { Link } from 'react-router-dom'; +import {GlobalExtConstants} from 'utils/GlobalExtConstants.js'; + +const modelBreadcrumb = (props) => { + + let links = null; + let historyStackArr = []; + var setURIInSession = function(uri){ + sessionStorage.setItem(GlobalExtConstants.ENVIRONMENT + 'URI', uri); + } + if (props.historyStackString) { + historyStackArr = props.historyStackString.split('||'); + for(var i = 0; i < historyStackArr.length; i++){ + historyStackArr[i] = historyStackArr[i].split(','); + console.log('[ModelBreadcrumb.jsx] previous url ' + historyStackArr[i][0] + ' previous api call '+ historyStackArr[i][1]); + } + links = historyStackArr.map((link, idx) => { + let breadCrumbTxt=decodeURI(link[2]).replace(/%2F/g,'/'); + return ( + <div className='customBreadCrumb'> + {idx === historyStackArr.length - 2 ? ( + <b id={'breadcrumbStatic' + idx} style={{'float' : 'left'}}>{breadCrumbTxt}</b> + ) : idx !== historyStackArr.length - 1 ? ( + <div id={'breadcrumbLink' + idx}> + <div style={{'float' : 'left'}}> + <Link + key={idx} + to={{ + pathname: link[0], + uri: link[1], + historyStackString: (breadCrumbTxt==='Origin')?'':props.historyStackString + }} onClick={() => setURIInSession(link[1])}>{breadCrumbTxt} + </Link> + </div> + <div style={{'float' : 'left'}}> >> </div> + </div> + ):(<div></div>)} + </div> + ); + }); + } + return ( + <Col md={12} className='addPaddingTop'> + {links} + </Col> + ); +}; + +export default modelBreadcrumb; diff --git a/src/app/model/modelSearch/components/ModelCard.jsx b/src/app/model/modelSearch/components/ModelCard.jsx new file mode 100644 index 0000000..2890b52 --- /dev/null +++ b/src/app/model/modelSearch/components/ModelCard.jsx @@ -0,0 +1,75 @@ +/* + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017-2021 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 React from 'react'; +import ModelRelationships from './ModelRelationships.jsx'; +import { Link } from 'react-router-dom'; +import {ExportExcel} from 'utils/ExportExcel.js'; +let buildAttrList = ExportExcel.buildAttrList; + +const modelCard = (props) => { + + var propKey = ''; + var navigateQueryBuilder = ''; + var editModalIcon = ''; + var requiredParams = buildAttrList(props.nodeType,[],'mandatory'); + const properties = Object.keys(props.nodeProps).map((prop, idx) => { + let description=''; + for(var a in requiredParams){ + if(requiredParams[a].value === prop){ + description=requiredParams[a].description; + if(propKey === ''){ + propKey = prop + ':' + btoa('<pre>' + props.nodeProps[prop].toString() + '</pre>'); + }else{ + propKey = propKey + ';' + prop + ':' + btoa('<pre>' + props.nodeProps[prop].toString() + '</pre>'); + } + } + } + return ( + <p className='pre-wrap-text' key={idx}><strong title={description}>{prop}:</strong> {props.nodeProps[prop].toString()}</p> + ); + }); + let pathNameStr = '/customDslBuilder/' + props.nodeType + '/' + propKey; + editModalIcon = <a className={props.isWriteAllowed ? 'show' : 'hidden'} onClick={e => {props.openEditNodeModal(props.nodeUrl)}}><i style={{cursor: 'pointer'}} className="pull-right fa fa-pencil-square-o" aria-hidden="true"></i></a>; + navigateQueryBuilder = <Link + to={{ + pathname: pathNameStr + }}> + <i className={'icon-misc-operationsL pull-right'} role="img"></i> + </Link>; + return ( + <div className='card model-card'> + <div className='card-header'> + <h4 className='card-title'>{props['nodeType']}{editModalIcon}{navigateQueryBuilder}</h4> + </div> + <div className='card-header'> + {props.nodeUrl} + </div> + <div className='card-content model-card-content'> + {properties} + </div> + <div className='card-footer'> + <ModelRelationships historyStackString={props.historyStackString} relatives={props} openHistoryModal={props.openHistoryModal} /> + </div> + </div> + ); +}; + +export default modelCard; diff --git a/src/app/model/modelSearch/components/ModelGallery.jsx b/src/app/model/modelSearch/components/ModelGallery.jsx new file mode 100644 index 0000000..d192708 --- /dev/null +++ b/src/app/model/modelSearch/components/ModelGallery.jsx @@ -0,0 +1,672 @@ +/* + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017-2021 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 React, { Component } from 'react'; +import Grid from 'react-bootstrap/lib/Grid'; +import Row from 'react-bootstrap/lib/Row'; +import Col from 'react-bootstrap/lib/Col'; +import ModelCard from './ModelCard.jsx'; +import Modal from 'react-bootstrap/lib/Modal'; +import Button from 'react-bootstrap/lib/Button'; +import BootstrapTable from 'react-bootstrap-table-next'; +import {ExportExcel} from 'utils/ExportExcel.js'; +import filterFactory, { textFilter, customFilter } from 'react-bootstrap-table2-filter'; +//import overlayFactory from 'react-bootstrap-table2-overlay'; +import OutputVisualization from 'generic-components/OutputVisualization.jsx'; +import RelationshipList from './ModelTabularView.jsx'; +import PropTypes from 'prop-types'; +import Tabs from 'react-bootstrap/lib/Tabs'; +import Tab from 'react-bootstrap/lib/Tab'; +import commonApi from 'utils/CommonAPIService.js'; +import {GlobalExtConstants} from 'utils/GlobalExtConstants.js'; +import {GeneralCommonFunctions} from 'utils/GeneralCommonFunctions.js'; +import Spinner from 'utils/SpinnerContainer.jsx'; + +let INVLIST = GlobalExtConstants.INVLIST; +let ENVIRONMENT = GlobalExtConstants.ENVIRONMENT; + +/** + * This function will take all of the node objects and turn them into + * a ui grid of ModelCard components. This function is essentially a container + * for the ModelCards + * @param props + * @returns {*} + */ +class AttributeFilter extends Component { + + constructor(props) { + super(props); + this.filter = this.filter.bind(this); + this.getValue = this.getValue.bind(this); + this.props = props; + this.state = { + filterText: '', + isPageChange: this.props.isPageChange, + columnValue : '' + }; + } + getValue = () => { + return this.input.value; + } + setPlaceHolder = () => { + let filterText = ''; + if(filterText === ''){ + filterText = 'Enter ' + this.props.column.text; + } + return filterText; + } + setFilterValue = () =>{ + let filterText = ''; + var columnFilter = this.props.columnFilterList[this.props.nodeType][0]; + for(var i=0;i<columnFilter.length;i++){ + if(columnFilter[i][this.props.column.text] != undefined){ + filterText = columnFilter[i][this.props.column.text]; + } + } + return filterText; + } + filter = () => { + let txt=this.props.column.text; + let obj = {}; + obj[txt] = this.getValue(); + var columnFilterList = this.props.columnFilterList; + var columnFilter = columnFilterList[this.props.nodeType][0]; + for(var i=0;i<columnFilter.length;i++){ + if(columnFilter[i][txt] != undefined){ + columnFilter[i][txt] = this.getValue(); + columnFilterList[this.props.nodeType] = []; + columnFilterList[this.props.nodeType].push(columnFilter); + this.props.handleOnFilter(columnFilterList,this.props.nodeType,this.props.columns,this.getValue(),this.props.aliasColumnList); + this.props.onFilter(this.getValue()); + } + } + } + render() { + return ( + <div> + + <input + key="input" + ref={ node => this.input = node } + type="text" + placeholder={this.setPlaceHolder()} + // value={this.setFilterValue} + onChange={this.filter} + /> + + </div> + ) + } +} + +class ModelGallery extends Component { + constructor(props){ + super(props); + this.props = props; + this.state = { + rerender: false, + expanded: [], + columnFilterList : {}, + columnsList: {}, + aliasColumnList: {}, + nodeType: '', + disableFilter: true, + showEditNodeModal: false, + focusedNode: null, + isEditSuccess: false, + isWriteAllowed: sessionStorage.getItem(ENVIRONMENT + 'roles') && sessionStorage.getItem(ENVIRONMENT + 'roles').indexOf('ui_write') > -1, + editInputFields: [] + } + } + componentWillMount() { + console.log('Model gallery component will mount****'); + } + componentWillUnmount() { + console.log('Model Gallery component will unmount****'); + } + handleOnExpand = (row, isExpand, rowIndex, e) => { + console.log('handleOnExpand single Row...',row.id); + if (isExpand) { + this.setState(() => ({ + expanded: [...this.state.expanded,row.id] + })); + } else { + this.setState(() => ({ + expanded: this.state.expanded.filter(x => x !== row.id) + }),function () { this.forceUpdate(); }.bind(this)); + + } + } + handleOnExpandAll = (isExpand, rows, e) => { + console.log('handleOnExpandAll to expand all rows'); + var expandArr = []; + if (isExpand) { + for(var r=0; r < rows.length; r++){ + expandArr.push(rows[r].id); + } + } + this.setState(() => ({ + expanded: expandArr + }),function () { this.forceUpdate(); }.bind(this)); + + } + handleOnFilter = (colFilterList,nodeType,columns,value,aliasColumnList) =>{ + console.log('handleOnFilter to Re-render',colFilterList); + var applyState = true; + if(value === ''){ + Object.keys(colFilterList).forEach(function(pkey){ + var filterList = colFilterList[pkey][0]; + for(var j in filterList){ + Object.keys(filterList[j]).forEach(function(key){ + if(filterList[j][key] !== ''){ + applyState = false; + } + }); + } + }); + }else{ + applyState = false; + } + this.setState({columnFilterList : colFilterList,rerender:true,columnsList : columns,nodeType: nodeType, disableFilter: applyState,aliasColumnList: aliasColumnList}); + } + generateRegexForDsl= (nodeType) =>{ + var nodePatternwithProp = nodeType+"\\*\\{.*?\\}\\(.*?\\)[\\,|\\>|\\]|\\)]|"+nodeType+"\\*\\(.*?\\)\\{.*?\\}[\\,|\\>|\\]|\\)]|"+nodeType+"\\{.*?\\}\\(.*?\\)[\\,|\\>|\\]|\\)]|"+nodeType+"\\(.*?\\)\\{.*?\\}[\\,|\\>|\\]|\\)]|"+nodeType+"\\{.*?\\}[\\,|\\>|\\]|\\)]|"+nodeType+"\\*\\{.*?\\}[\\,|\\>|\\]|\\)]"; + return nodePatternwithProp; + } + /* Start Edit Node Modal Functions */ + closeEditNodeModal = () =>{ + this.setState({editErrMsg: null, editInfoMsg: null, showEditNodeModal:false}); + } + submitEditNodeModal = () =>{ + var payload = {"operations": []}; + const settings = { + 'NODESERVER': INVLIST.NODESERVER, + 'PROXY': INVLIST.PROXY, + 'PREFIX': INVLIST.PREFIX, + 'VERSION': INVLIST.VERSION, + 'USESTUBS': INVLIST.useStubs, + 'APERTURE': INVLIST.APERTURE, + 'APERTURE_SERVICENAME':INVLIST.APERTURE_SERVICENAME + }; + let delimiter = '\/'; + let start = 3; + if((this.state.focusedNode.url).indexOf("/aperture/v") > -1){ + start = 4; + } + let tokens = (this.state.focusedNode.url).split(delimiter).slice(start); + let patchURL = tokens.join(delimiter); + var entry = { + "action": "patch", + "uri": patchURL, + "body": {} + }; + let path = "bulk/single-transaction"; + this.setState({editErrMsg: null, isPatchLoading: true}); + for(var key in this.state.editInputFields){ + if(this.state.editInputFields[key].isEdited){ + if(this.state.editInputFields[key].newValue !== ""){ + entry.body[key] = encodeURI(this.state.editInputFields[key].newValue); + }else{ + entry.body[key] = null; + } + } + } + payload.operations.push(entry); + console.log('ModelGallery: settings:' + JSON.stringify(settings)); + console.log('ModelGallery: path:' + path); + console.log('ModelGallery: payload:' + JSON.stringify(payload)); + commonApi(settings, path, 'POST', payload, 'SingleTransactionEdit', null, null, null, true) + .then(res => { + console.log('ModelGallery: Response', Object.keys(res.data)); + if(res.status === 201 || res.status === 200){ + if(res.data["operation-responses"] && res.data["operation-responses"][0] && res.data["operation-responses"][0]["response-status-code"] === 200 ){ + this.setState({isEditSuccess: true, isPatchLoading: false, showEditNodeModal:false}); + GeneralCommonFunctions.scrollTo("editSuccessMessage"); + }else{ + this.triggerError(res.data); + } + }else{ + this.triggerError(res.data); + } + }, error=>{ + this.triggerError(error); + }).catch(error => { + this.triggerError(error); + }); + } + triggerError = (error) => { + console.error('[ModelGallery.jsx] error : ', JSON.stringify(error)); + let errMsg = this.renderErrorMsg(error); + this.setState({ + isPatchLoading: false, + isEditSuccess: false, + editErrMsg: errMsg + }); + }; + renderErrorMsg = (error) =>{ + let errMsg=''; + if (error.response) { + // The request was made and the server responded with a status code + // that falls out of the range of 2xx + console.log('[ModeGallery.jsx] error :', error.response); + if(error.response.status){ + errMsg += " Code: " + error.response.status; + } + if(error.response.data){ + errMsg += " - " + JSON.stringify(error.response.data); + } + } else if (error["requestError"]){ + errMsg += JSON.stringify(error["requestError"]); + } else if (error.request) { + // The request was made but no response was received + // `error.request` is an instance of XMLHttpRequest in the browser and an instance of + // http.ClientRequest in node.js + console.log(error.request); + errMsg += " - Request was made but no response received"; + } else { + // Something happened in setting up the request that triggered an Error + console.log('Error', error.message); + errMsg += " - Unknown error occurred " + error.message; + } + return errMsg; + } + openEditNodeModal = (nodeKey) => { + console.log("ModelGallery :: openEditNodeModal called with " + nodeKey); + var focusedNode = null; + for (var i = 0; i < this.props.nodes.length && !focusedNode; i++){ + if(nodeKey === this.props.nodes[i].url){ + focusedNode = this.props.nodes[i]; + break; + } + } + var editInputFields = []; + if(focusedNode){ + var nodeType = focusedNode['node-type']; + focusedNode.allowedEditProps = []; + //call to check what props can be modified in oxm here; + focusedNode.allowedEditProps = GeneralCommonFunctions.getEditableAttributes(nodeType); + if(focusedNode.allowedEditProps.length > 0){ + for (var key in focusedNode.allowedEditProps){ + var attr = focusedNode.allowedEditProps[key]; + editInputFields[attr] = {}; + editInputFields[attr].isEdited = false; + if(focusedNode.properties[attr]){ + editInputFields[attr].oldValue = focusedNode.properties[attr]; + editInputFields[attr].newValue = focusedNode.properties[attr]; + }else{ + editInputFields[attr].oldValue = ""; + editInputFields[attr].newValue = ""; + } + } + }else{ + this.setState({editInfoMsg: "This element cannot be edited, please contact an administrator if you need the ability to edit the attributes on this element."}); + } + }else{ + //could not find the node, this shouldn't happen + console.log("ModelGallery :: openEditNodeModal could not find " + nodeKey + " in this.props.nodes. This shouldn't happen."); + } + + this.setState({showEditNodeModal:true, isEditSuccess: false, focusedNode: focusedNode, editInputFields: editInputFields}); + } + handleInputChange(event) { + const target = event.target; + const value = target.type === 'checkbox' ? target.checked : target.value; + const name = target.name; + var editInputFields = this.state.editInputFields; + if(target.type === 'text'){ + editInputFields[name].newValue = value; + }else if(target.type === 'checkbox'){ + if(!value){ + editInputFields[name].newValue = editInputFields[name].oldValue, + editInputFields[name].isEdited = value + }else{ + editInputFields[name].isEdited = value + } + } + this.setState({ + editInputFields: editInputFields + }); + } + /* End Edit Node Modal Functions */ + render(){ + let cards = null; + let tableColumns = []; + let tableValues = []; + let rowIndexValue = 0; + let svgWidth = window.outerWidth * 0.8; + let nodesList=[]; + let tableColumnsList={}; + let tableDataList={}; + let columnFilter = []; + let columnFilterList = (this.state.rerender) ? this.state.columnFilterList : {}; + let aliasColumnList=(this.state.rerender) ? this.state.aliasColumnList: {}; + let aliasRegex=/\'(\s)as(\s)\'|\'as\'/ig; + console.log('Model gallery this.props>>>',this.props); + console.log('columnFilterList while rendering:',columnFilterList); + this.onTableFilterClick = (nodeType) => { + this.setState({rerender:false},function(){this.props.isTableFilterApply(this.state.columnFilterList,nodeType,this.state.columnsList,this.state.aliasColumnList);}.bind(this)) + } + const expandRows = { + parentClassName: 'parent-expand-bar', + onlyOneExpanding: true, + renderer: (row, rowIndex) => ( + <div> + <RelationshipList node={this.props.nodes[parseInt(row.id.split('_')[0])]} + key={this.props.nodes[parseInt(row.id.split('_')[0])].id} + nodeId={this.props.nodes[parseInt(row.id.split('_')[0])].id} + nodeType={this.props.nodes[parseInt(row.id.split('_')[0])]['node-type']} + nodeProps={this.props.nodes[parseInt(row.id.split('_')[0])].properties} + nodeRelatives={this.props.nodes[parseInt(row.id.split('_')[0])]['related-to']} + nodeUrl={this.props.nodes[parseInt(row.id.split('_')[0])].url} + historyStackString={this.props.historyStackString} + openHistoryModal={this.props.openHistoryModal} + openEditNodeModal={this.openEditNodeModal} + isWriteAllowed={this.state.isWriteAllowed} + rowIndex={parseInt(row.id.split('_')[0])} + enableRealTime={this.props.enableRealTime} + aliasColumnList={this.props.tableFilterAliasColumns} + /> + </div> + ), + showExpandColumn: true, + expandByColumnOnly: true, + onExpandAll: this.handleOnExpandAll, + expanded: this.state.expanded, + onExpand: this.handleOnExpand + + }; + const rowEvents = { + onClick: (e, row, rowIndex) => { + //row index is usefull when single node type exist, for multiple node type use row id + rowIndexValue = parseInt(row.id.split('_')[0]); + }, + onMouseEnter: (e, row, rowIndex) => { + //row index is usefull when single node type exist, for multiple node type use row id + rowIndexValue = parseInt(row.id.split('_')[0]); + } + }; + let aliasColumns=[] + + if(this.props.nodes && this.props.nodes[0] && this.props.nodes[0]['node-type'] && this.props.viewName === "CellLayout" ){ + for(var n=0; n<this.props.nodes.length; n++){ + let nodeType = this.props.nodes[n]['node-type']; + let nodeTypeProperties =[]; + let aliasProperties=[]; + let plainNodes =''; + let dslQuery = this.props.dslQuery + ','; + if(this.props.dslQuery){ + var nodePatternwithProp = this.generateRegexForDsl(nodeType); + var nodeRegularExp = new RegExp(nodePatternwithProp, 'g'); + plainNodes = dslQuery.match(nodeRegularExp); + console.log('plainNodes model Gallery>>>>>*',plainNodes); + if(plainNodes){ + let propertiesPattern ="\\{.*?\\}"; + var propRegularExp = new RegExp(propertiesPattern, 'g'); + let nodeTypeProp = plainNodes[0].match(propRegularExp); + nodeTypeProp = nodeTypeProp[0].slice(1,-1).split(',');//.replace(/\'/g,'').toLowerCase().split(','); + for(var s=0;s<nodeTypeProp.length;s++){ + let nodeTypePropes=nodeTypeProp[s].match(aliasRegex); + let alias=''; + let nprop=''; + if(nodeTypePropes){ + let nodeTypeSplit=nodeTypeProp[s].split(aliasRegex); + nprop=nodeTypeSplit[0].replace(/\'/g,''); + alias=nodeTypeSplit[nodeTypeSplit.length-1].replace(/\'/g,''); + }else{ + nprop=nodeTypeProp[s].replace(/\'/g,'').toLowerCase(); + } + aliasProperties.push(alias); + nodeTypeProperties.push(nprop); + } + } + } + if(nodesList.indexOf(nodeType) === -1){ + tableColumns=[]; + tableValues=[]; + nodesList.push(nodeType); + let tableColumnsBuilt = ExportExcel.buildAttrList(nodeType,[],'required'); + if(this.props.dslQuery && plainNodes){ + for(var z=0;z<tableColumnsBuilt.length;z++){ + let index= nodeTypeProperties.indexOf(tableColumnsBuilt[z].value.toLowerCase()); + if(index !== -1){ + if(aliasProperties[index] !==''){ + let objAlias = {}; + objAlias[aliasProperties[index]]=nodeTypeProperties[index]; + aliasColumns.push(objAlias); + tableColumnsBuilt[z].value=aliasProperties[index]; + } + tableColumns.push(tableColumnsBuilt[z]); + } + } + }else{ + tableColumns=tableColumnsBuilt; + } + console.log('after condition table columns>>>>',tableColumns); + tableColumns.push({value:'id'}); + if(!columnFilterList[nodeType]){ + columnFilterList[nodeType] = []; + columnFilter = []; + for(var j = 0; j < tableColumns.length; j++){ + let txt = tableColumns[j].value; + //if(!this.state.reRender && (!columnFilter[j] || (columnFilter[j] && columnFilter[j][txt] === undefined))){ + let obj = {}; + obj[txt] = ''; + obj['description'] = tableColumns[j].description; + columnFilter.push(obj); + //} + } + columnFilterList[nodeType].push(columnFilter); + } + if(!aliasColumnList[nodeType]){ + aliasColumnList[nodeType]=[]; + aliasColumnList[nodeType].push(aliasColumns); + } + for(var j = 0; j < tableColumns.length; j++){ + if(j === tableColumns.length-1){ + tableColumns[j].dataField = 'id'; + tableColumns[j].hidden = true; + tableColumns[j].text = tableColumns[j].value; + }else{ + tableColumns[j].dataField = tableColumns[j].value; + tableColumns[j].text = tableColumns[j].value; + tableColumns[j].headerAttrs= { title:tableColumns[j].description}; + tableColumns[j].ref=tableColumns[j].value; + tableColumns[j].filter = customFilter(); + tableColumns[j].filterRenderer = (onFilter, column) => <AttributeFilter handleOnFilter= {this.handleOnFilter} onFilter={ onFilter } column={ column } isPageChange={this.props.isPageNumberChange} nodeType={nodeType} columnFilterList={columnFilterList} columns={tableColumnsList} aliasColumnList={aliasColumnList}/>; + } + } + tableColumnsList[nodeType] = tableColumns; + tableDataList[nodeType] = []; + for(var m=0; m<this.props.nodes.length; m++){ + let nodeTypeForData = this.props.nodes[m]['node-type']; + if(nodeTypeForData === nodeType){ + let propertiesOfNode = this.props.nodes[m].properties; + propertiesOfNode.id = m + '_' + nodeType + '_id'; + tableValues.push(propertiesOfNode); + tableDataList[nodeType].push(tableValues); + } + } + } + } + }else{ + cards = this.props.nodes.map(node => { + return ( + <Col key={node.id} lg={3} md={3} sm={6} xs={12}> + <ModelCard + key={node.id} + nodeId={node.id} + nodeType={node['node-type']} + nodeProps={node.properties} + nodeRelatives={node['related-to']} + nodeUrl={node.url} + historyStackString={this.props.historyStackString} + openHistoryModal={this.props.openHistoryModal} + openEditNodeModal={this.openEditNodeModal} + isWriteAllowed={this.state.isWriteAllowed} + enableRealTime={this.props.enableRealTime} + aliasColumnList={this.props.tableFilterAliasColumns}/> + </Col> + ); + }); + } + let tabs=nodesList.map((nodeType,index) => { + return( + <Tab eventKey={nodeType} title={nodeType} key={nodeType}> + <BootstrapTable + id={nodeType} + keyField='id' + data={tableDataList[nodeType][0]} + columns={tableColumnsList[nodeType]} + filter={filterFactory()} + bordered={true} + columnFilter={true} + headerClasses='table-header-view' + expandRow={expandRows} + rowEvents={rowEvents} + bootstrap4 striped hover condensed + /> + </Tab> + ) + }); + return ( + <div> + <div className={'addPaddingTop alert alert-success ' +(this.state.isEditSuccess ? 'show' : 'hidden')} id="editSuccessMessage" role="alert"> + Update made successfully to {this.state.focusedNode ? this.state.focusedNode.url : ""}. If you wish, you may check your update using a real-time mode query, it may take some time to reflect in analysis mode. + </div> + <div className='static-modal'> + <Modal show={this.state.showEditNodeModal} onHide={this.closeEditNodeModal}> + <Modal.Header> + <Modal.Title>Edit Element</Modal.Title> + </Modal.Header> + <Modal.Body> + <Spinner loading={this.state.isPatchLoading}> + <div className={'addPaddingTop alert alert-danger ' +(this.state.editErrMsg && this.state.editErrMsg !== '' ? 'show' : 'hidden')} id="editErrorMessage" role="alert"> + An error occurred in editing the element. Please see details {this.state.editErrMsg} + </div> + <div className={'addPaddingTop alert alert-info ' +(this.state.editInfoMsg && this.state.editInfoMsg !== '' ? 'show' : 'hidden')} id="editNotAllowedMessage" role="alert"> + {this.state.editInfoMsg} + </div> + <form> + {this.state.focusedNode && Object.keys(this.state.editInputFields).length > 0 && (this.state.focusedNode.allowedEditProps).sort().map((attr) => { + return <div class="form-group row"> + <div className="col-sm-3"> + <label for={attr} class="col-form-label">{attr}</label> + </div> + <div class="col-sm-1"> + <div className="checkbox"> + <input type="checkbox" name={attr} checked={this.state.editInputFields[attr].isEdited} onChange={this.handleInputChange.bind(this)} /> + </div> + </div> + <div class="col-sm-8"> + <input type="text" class="form-control" id={attr} name={attr} disabled={!this.state.editInputFields[attr].isEdited} onChange={this.handleInputChange.bind(this)} value={this.state.editInputFields[attr].newValue}/> + </div> + </div>; + + }) + } + </form> + </Spinner> + </Modal.Body> + <Modal.Footer> + <Button onClick={this.closeEditNodeModal}>Close</Button> + <Button className={this.state.editInfoMsg && this.state.editInfoMsg !== '' ? 'hidden' : ''} onClick={this.submitEditNodeModal}>Submit</Button> + </Modal.Footer> + </Modal> + </div> + {(() => { + if (this.props.viewName === "CellLayout" && tableValues.length > 0) { + if(nodesList.length > 1){ + if(this.props.isTableFilterApply){ + return ( + <div className="addPaddingSide"> + <button type='button' className={(this.state.disableFilter)? 'btn btn-outline-secondary' : 'btn btn-primary'} disabled={this.state.disableFilter} onClick={() => {this.onTableFilterClick(nodesList)}} style={{float: 'right', margin: '2px'}}>Apply Filters (All)</button> + <Tabs defaultActiveKey={nodesList[0]} id="multipleTabularView"> + {tabs} + </Tabs> + </div> + ) + }else{ + return ( + <div className="addPaddingSide"> + <Tabs defaultActiveKey={nodesList[0]} id="multipleTabularView"> + {tabs} + </Tabs> + </div> + ) + } + }else{ + if(this.props.isTableFilterApply){ + return( + <div className="addPaddingSide"> + <button type='button' className={(this.state.disableFilter)? 'btn btn-outline-secondary' : 'btn btn-primary'} disabled={this.state.disableFilter} onClick={() => {this.onTableFilterClick(this.state.nodeType)}} style={{float: 'right', margin: '10px'}}>Apply Filters (All)</button> + <BootstrapTable + id='modelGallery' + keyField='id' + data={ tableValues } + columns={ tableColumns } + filter={ filterFactory() } + bordered={ true } + columnFilter={ true } + headerClasses='table-header-view' + expandRow={ expandRows } + rowEvents={ rowEvents } + bootstrap4 striped hover condensed + /> + </div> + ) + }else{ + return ( + <div className="addPaddingSide"> + <BootstrapTable + id='modelGallery' + keyField='id' + data={ tableValues } + columns={ tableColumns } + filter={ filterFactory() } + bordered={ true } + columnFilter={ true } + headerClasses='table-header-view' + expandRow={ expandRows } + rowEvents={ rowEvents } + bootstrap4 striped hover condensed + /> + </div> + ) + } + } + } else if (this.props.viewName === "CardLayout") { + return ( + <Grid fluid={true}> + <Row className='show-grid'> + {cards} + </Row> + </Grid> + ) + } + })()} + <div className={this.props.viewName === "VisualLayout" ? 'show' : 'hidden'}> + <OutputVisualization identifier="currentState" width={svgWidth} height="1200" overflow="scroll"/> + </div> + </div> + ); + } +}; + +export default ModelGallery; diff --git a/src/app/model/modelSearch/components/ModelNodeCard.jsx b/src/app/model/modelSearch/components/ModelNodeCard.jsx new file mode 100644 index 0000000..0caf831 --- /dev/null +++ b/src/app/model/modelSearch/components/ModelNodeCard.jsx @@ -0,0 +1,51 @@ +/* + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017-2021 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 React from 'react'; +import ModelRelationships from './ModelRelationships.jsx'; +import Col from 'react-bootstrap/lib/Col'; + +const modelNodeCard = (props) => { + + console.log('[Model Node Card] props : ', props); + const properties = Object.keys(props.nodeProps).map( (prop, idx) => { + return ( + <p key={idx}><strong> {prop} : </strong> { props.nodeProps[prop].toString()} </p> + ); + }); + + return ( + <Col lg={8} md={8} sm={10}> + <div className='card model-card'> + <div className='card-header'> + Node {props.nodeId} + </div> + <div className='card-content model-card-content'> + {properties} + </div> + </div> + <ModelRelationships relatives={props}/> + </Col> + ); +}; + +export default modelNodeCard; + + diff --git a/src/app/model/modelSearch/components/ModelNodeGallery.jsx b/src/app/model/modelSearch/components/ModelNodeGallery.jsx new file mode 100644 index 0000000..ef6947e --- /dev/null +++ b/src/app/model/modelSearch/components/ModelNodeGallery.jsx @@ -0,0 +1,46 @@ +/* + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017-2021 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 React from 'react'; +import ModelNodeCard from './ModelNodeCard.jsx'; + +const modelNodeGallery = (props) => { + + const cards = props.nodes.map(node => { + console.log('[Model Node Gallery] : ', node); + return ( + <ModelNodeCard + key={node.id} + nodeId={node.id} + nodeType={node['node-type']} + nodeProps={node.properties} + nodeRelatives={node['related-to']} + nodeUrl={node.url}/> + ); + }); + + return ( + <div> + {cards} + </div> + ); +}; + +export default modelNodeGallery; diff --git a/src/app/model/modelSearch/components/ModelRelationships.jsx b/src/app/model/modelSearch/components/ModelRelationships.jsx new file mode 100644 index 0000000..8dec154 --- /dev/null +++ b/src/app/model/modelSearch/components/ModelRelationships.jsx @@ -0,0 +1,137 @@ +/* + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017-2021 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 React, { Component } from 'react'; +import { Link } from 'react-router-dom'; +import Label from 'react-bootstrap/lib/Label'; +import Panel from 'react-bootstrap/lib/Panel'; +import {GlobalExtConstants} from 'utils/GlobalExtConstants.js'; +import {ExportExcel} from 'utils/ExportExcel.js'; +let buildAttrList = ExportExcel.buildAttrList; + +let INVLIST = GlobalExtConstants.INVLIST; + +class ModelRelationships extends Component { + constructor(props){ + console.log(props); + super(props); + this.props = props; + } + + render(){ + console.log('[ModelRelationships.jsx] props : ', this.props); + let relationships = null; + let navigateByoq = null; + let relativesArray = []; + this.nodeDisplay = this.props.relatives.nodeType + ' : ' + (this.props.relatives.nodeUrl).split(this.props.relatives.nodeType + '\/').pop(); + this.historyClick = () => { + this.props.openHistoryModal(this.nodeDisplay, this.props.relatives.nodeUrl,this.props.relatives.nodeType); + } + + if (this.props.relatives.nodeRelatives && this.props.relatives.nodeRelatives.length > 0) { + relationships = this.props.relatives.nodeRelatives.sort(function(a, b) { + var compareA = (a['node-type'] + (a.url).split(a['node-type']+'\/').pop()).toLowerCase(); + var compareB = (b['node-type'] + (b.url).split(a['node-type']+'\/').pop()).toLowerCase(); + if(compareA < compareB) return -1; + if(compareA > compareB) return 1; + return 0; + }).map((relative, idx) => { + if (relativesArray.includes(relative['node-type']) === false) relativesArray.push(relative['node-type']); + return ( + <div> + <Link + key={idx} + to={{ + pathname: '/model/' + relative['node-type'] + '/' + relative.id + '/'+ this.props.relatives.enableRealTime, + uri: relative.url, + historyStackString: this.props.historyStackString + }}> + {relative['node-type']}: {(decodeURI((relative.url).split(relative['node-type']+'\/').pop())).replace(/%2F/g,'/')}</Link> <Label bsStyle='default'>{relative['relationship-label'].slice(33)}</Label> + </div> + ); + }); + } + relativesArray = relativesArray.join('&'); + var propKey = ''; + var requiredParams = buildAttrList(this.props.relatives.nodeType,[],'mandatory'); + var aliasColumnFilters = (this.props.relatives.aliasColumnList && this.props.relatives.aliasColumnList[this.props.relatives.nodeType])?this.props.relatives.aliasColumnList[this.props.relatives.nodeType][0]:[]; + console.log('requiredParams>>>>>>>>>>>>',requiredParams); + Object.keys(this.props.relatives.nodeProps).map((prop, idx) => { + for(var a in requiredParams){ + let alias=''; + if(aliasColumnFilters && aliasColumnFilters[requiredParams[a].value]){ + alias=requiredParams[a].value; + requiredParams[a].value=aliasColumnFilters[requiredParams[a].value]; + } + if(requiredParams[a].value === prop){ + let tag= (alias!='')? alias: prop; + if(propKey === ''){ + propKey = tag + ':' + btoa('<pre>' + this.props.relatives.nodeProps[prop].toString() + '</pre>'); + }else{ + propKey = propKey + ';' + tag + ':' + btoa('<pre>' + this.props.relatives.nodeProps[prop].toString() + '</pre>'); + } + } + } + }); + let pathNameStr = (relativesArray.length>0) ? '/customDsl/' + this.props.relatives.nodeType + '/' + propKey + '/' + relativesArray : '/customDsl/' + this.props.relatives.nodeType + '/' + propKey; + navigateByoq = <Link + to={{ + pathname: pathNameStr + }}> + <button type='button' className='btn btn-primary pull-right'>>>BYOQ</button> + </Link>; + if (this.props.relatives.nodeRelatives && this.props.relatives.nodeRelatives.length > 0) { + return ( + <Panel> + <Panel.Heading> + <Panel.Toggle> + <button type='button' className='btn btn-outline-primary'> + Relationships + </button> + </Panel.Toggle> + { INVLIST.isHistoryEnabled && (<button type='button' className='btn btn-outline-primary' onClick={this.historyClick}> + History + </button>)} + {navigateByoq} + </Panel.Heading> + <Panel.Collapse> + <Panel.Body className='cardwrap'> + {relationships} + </Panel.Body> + </Panel.Collapse> + </Panel> + ); + } else { + return ( + <div> + <button type='button' className='btn btn-outline-disabled'> + No relationships + </button> + { INVLIST.isHistoryEnabled && (<button type='button' className='btn btn-outline-primary' onClick={this.historyClick}> + History + </button>)} + {navigateByoq} + </div> + ); + } +} +}; + +export default ModelRelationships; diff --git a/src/app/model/modelSearch/components/ModelTabularView.jsx b/src/app/model/modelSearch/components/ModelTabularView.jsx new file mode 100644 index 0000000..3b5a94a --- /dev/null +++ b/src/app/model/modelSearch/components/ModelTabularView.jsx @@ -0,0 +1,197 @@ +/* + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017-2021 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 React, { Component } from 'react'; +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter, customFilter } from 'react-bootstrap-table2-filter'; +import PropTypes from 'prop-types'; +import { Link } from 'react-router-dom'; +import Label from 'react-bootstrap/lib/Label'; +import {GlobalExtConstants} from 'utils/GlobalExtConstants.js'; +import {ExportExcel} from 'utils/ExportExcel.js'; +let buildAttrList = ExportExcel.buildAttrList; + +let INVLIST = GlobalExtConstants.INVLIST; + + class RelationshipList extends Component { + + + constructor(props) { + super(props); + this.props = props; + this.relationships = null; + this.relativesArray = []; + this.state = { + filteron:false + + } + } + render() { + + + let navigateByoq = null; + this.nodeDisplay = this.props.nodeType + ' : ' + (this.props.nodeUrl).split(this.props.nodeType + '\/').pop(); + this.historyClick = () => { + this.props.openHistoryModal(this.nodeDisplay, this.props.nodeUrl,this.props.nodeType); + } + this.filter = (e) =>{ + let filterValue = e.target.value; + this.returnFilterList(filterValue); + this.setState({filteron:true}); + } + this.returnFilterList = (filterValue) =>{ + if (this.props.nodeRelatives && this.props.nodeRelatives.length > 0) { + this.relationships = null; + this.relationships = this.props.nodeRelatives.sort(function(a, b) { + var compareA = (a['node-type'] + (a.url).split(a['node-type']+'\/').pop()).toLowerCase(); + var compareB = (b['node-type'] + (b.url).split(a['node-type']+'\/').pop()).toLowerCase(); + if(compareA < compareB) return -1; + if(compareA > compareB) return 1; + return 0; + }).map((relative, idx) => { + if (this.relativesArray.includes(relative['node-type']) === false) this.relativesArray.push(relative['node-type']); + if(filterValue === '' || filterValue === ':' || relative['node-type'].toLowerCase().search(filterValue.toLowerCase()) != -1 || (decodeURI((relative.url).split(relative['node-type']+'\/').pop())).replace(/%2F/g,'/').toLowerCase().search(filterValue.toLowerCase()) != -1){ + return ( + <li key={idx + '' +relative.id}> + <Link + key={idx} + to={{ + pathname: '/model/' + relative['node-type'] + '/' + relative.id + '/'+ this.props.enableRealTime, + uri: relative.url, + historyStackString: this.props.historyStackString + }}> + {relative['node-type']}: {(decodeURI((relative.url).split(relative['node-type']+'\/').pop())).replace(/%2F/g,'/')}</Link> <Label bsStyle='default'>{relative['relationship-label'].slice(33)}</Label> + </li> + ); + } + }); + } + } + if(!this.state.filteron){ + this.returnFilterList(''); + } + let relativesArray = (this.relativesArray.length > 0) ? this.relativesArray.join('&') : this.relativesArray; + var propKey = ''; + var requiredParams = buildAttrList(this.props.nodeType,[],'mandatory'); + var aliasColumnFilters = (this.props.aliasColumnList && this.props.aliasColumnList[this.props.nodeType])?this.props.aliasColumnList[this.props.nodeType][0]:[]; + Object.keys(this.props.nodeProps).map((prop, idx) => { + for(var a in requiredParams){ + let alias=''; + if(aliasColumnFilters && aliasColumnFilters[requiredParams[a].value]){ + alias=requiredParams[a].value; + requiredParams[a].value=aliasColumnFilters[requiredParams[a].value]; + } + if(requiredParams[a].value === prop){ + let tag= (alias!='')? alias: prop; + if(propKey === ''){ + propKey = tag + ':' + btoa(this.props.nodeProps[prop].toString()); + }else{ + propKey = propKey + ';' + tag + ':' + btoa(this.props.nodeProps[prop].toString()); + } + } + } + }); + let editModalIcon = <a className={this.props.isWriteAllowed ? 'show' : 'hidden'} onClick={e => {this.props.openEditNodeModal(this.props.nodeUrl)}}><i style={{cursor: 'pointer'}} className="pull-right fa fa-pencil-square-o" aria-hidden="true"></i></a>; + let pathNameStr = (relativesArray.length>0) ? '/customDsl/' + this.props.nodeType + '/' + propKey + '/' + relativesArray : '/customDsl/' + this.props.nodeType + '/' + propKey; + navigateByoq = <Link + to={{ + pathname: pathNameStr + }}> + <button type='button' className='btn btn-primary pull-right'>>>BYOQ</button> + </Link>; + let relationships = []; + if(this.relationships){ + for(var n=0 ; n < this.relationships.length ; n++){ + if(this.relationships[n]){ + relationships.push(this.relationships[n]); + } + } + } + if (this.props.nodeRelatives && this.props.nodeRelatives.length > 0) { + return ( + <div> + <div style={{float: 'left'}}> + <table className='relationshipTable table-striped table-hover table-bordered table-sm'> + <thead> + <tr className='table-header-view'> + <th titlename='Relationships'> + Relationships + <div> + <input + key='input' + type='text' + placeholder='Enter Relationship...' + onChange={(e) => this.filter(e)} + /> + </div> + </th> + </tr> + </thead> + <tbody> + <tr> + <td> + <ul> + {relationships} + </ul> + </td> + </tr> + </tbody> + </table> + </div> + <div style={{float: 'left', margin: '10px'}}> + { INVLIST.isHistoryEnabled && (<button type='button' className='btn btn-primary pull-right' onClick={this.historyClick}> + History + </button>)} + </div> + <div style={{float: 'left',margin: '10px'}}> + {navigateByoq} + </div> + <div style={{float: 'left',margin: '10px'}}> + {editModalIcon} + </div> + </div> + ) + } else { + return ( + <div> + <div style={{float: 'left', margin: '10px'}}> + <button type='button' className='btn btn-outline-disabled'> + No relationships + </button> + </div> + <div style={{float: 'left', margin: '10px'}}> + { INVLIST.isHistoryEnabled && (<button type='button' className='btn btn-primary' onClick={this.historyClick}> + History + </button>)} + </div> + <div style={{float: 'left', margin: '10px'}}> + {navigateByoq} + </div> + <div style={{float: 'left',margin: '10px'}}> + {editModalIcon} + </div> + </div> + ); + } + } + } + +export default RelationshipList; +//export default AttributeFilter; |