Updated Sparky to add ECOMP functionality Browse, Specialized Search, BYOQ, and the...
[aai/sparky-fe.git] / src / app / model / history / HistoryQuery.jsx
1 /*
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017-2021 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *       http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 import React, { Component } from 'react';
22 import { connect } from 'react-redux';
23
24 import commonApi from 'utils/CommonAPIService.js';
25 import deepDiffMapper from 'utils/DiffUtil.js';
26 import {GlobalExtConstants} from 'utils/GlobalExtConstants.js';
27
28 import moment from "moment";
29 import Grid from 'react-bootstrap/lib/Grid';
30 import Row from 'react-bootstrap/lib/Row';
31 import Col from 'react-bootstrap/lib/Col';
32 import Button from 'react-bootstrap/lib/Button';
33 import Modal from 'react-bootstrap/lib/Modal';
34 import * as d3 from "d3";
35 import 'd3-selection-multi';
36 import ReactBootstrapSlider from 'react-bootstrap-slider';
37 import HistoryCard from './components/HistoryCard.jsx';
38 import NodeDiffCard from './components/NodeDiffCard.jsx';
39 import AnimationControls from './components/AnimationControls.jsx';
40 import TopologyDiffCard from './components/TopologyDiffCard.jsx';
41 import OutputVisualization, {Visualization} from 'generic-components/OutputVisualization.jsx';
42
43
44
45 let INVLIST = GlobalExtConstants.INVLIST;
46
47 /**
48  * This class is to show visualizations of queries and show a historical state
49  */
50
51 class HistoryQuery extends Component {
52
53   svgWidth = window.outerWidth * 0.8;
54   elements = [];
55   pageTitle = '';
56   nodeType = '';
57   nodeResults = '';
58
59   constructor(props) {
60     console.log(props);
61     super(props);
62     this.state = {
63                       totalResults: 0,
64                       enableBusyRecentFeedback: true,
65                       enableBusyHistoryStateFeedback: true,
66                       enableBusyDiffFeedback: true,
67                       data: [],
68                       nodes: [],
69                       payload: ((this.props.match.params.type == 'cq') ? atob(this.props.match.params.payloadEnc) : { "dsl" : atob(this.props.match.params.payloadEnc)}),
70                       topologyCurrentState: null,
71                       topologyHistoryState: null,
72                       currentStateHistoryValue: parseInt(this.props.match.params.epochTime),
73                       stepEpochStateTime: 1000,
74                       maxEpochStartTime: Math.round(new Date().getTime()),
75                       minEpochStartTime: parseInt(this.props.match.params.epochTime) - 259200000,
76                       nodeDiff: null,
77                       selectedHistoryStateFormatted: moment(parseInt(this.props.match.params.epochTime)).format('dddd, MMMM Do, YYYY h:mm:ss A'),
78                       queryDisplay: ((this.props.match.params.type == 'cq') ?  JSON.parse(atob(this.props.match.params.payloadEnc)).query : atob(this.props.match.params.payloadEnc)),
79                       currentGraphNodes: [],
80                       currentGraphLinks: [],
81                       historicGraphNodes: [],
82                       historicGraphLinks: [],
83                       selectedNodeHistoryState: null,
84                       selectedNodeCurrentState: null,
85                       showNodeModal: false,
86                       showTopologyDiffModal: false,
87                       nodeDisplay: '',
88                       totalDiff: null,
89                       showSlider: false,
90                       sliderTickArray: null,
91                       showTicks: INVLIST.showTicks,
92                       rawMappedCurrentState: null,
93                       rawMappedHistoricState: null,
94                       isPlaying: false,
95                       isPaused: false,
96                       isStopped: false,
97                       intervalId: null,
98                       diffOverlayOn: false,
99                       currentErrMsg: null,
100                       historicErrMsg: null
101
102                     };
103      console.log('minEpochStateTime: '+this.state.minEpochStateTime);
104      console.log('maxEpochStartTime: '+this.state.maxEpochStartTime);
105      console.log('stepEpochStateTime: '+this.state.stepEpochStateTime);
106      console.log('currentStateHistoryValue: '+this.state.currentStateHistoryValue);
107   }
108   resultsMessage = '';
109   componentDidMount = () => {
110      console.log('[HistoryQuery.jsx] componentDidMount props available are', JSON.stringify(this.props));
111      if(INVLIST.isHistoryEnabled){
112         this.getCurrentStateCall(this.getSettings());
113      }
114   };
115
116   componentWillUnmount  = () => {
117     console.log('[History.jsx] componentWillUnMount');
118   }
119   beforefetchInventoryData = (param) => {
120     if (param) {
121       this.setState({});
122     } else {
123       this.fetchInventoryData();
124     }
125   };
126   initState = () =>{
127               this.setState({
128                     totalResults: 0,
129                     enableBusyRecentFeedback: true,
130                     enableBusyHistoryStateFeedback: true,
131                     enableBusyDiffFeedback: true,
132                     data: [],
133                     nodes: [],
134                     payload : ((this.props.match.params.type == 'cq') ? atob(this.props.match.params.payloadEnc) : { "dsl" : atob(this.props.match.params.payloadEnc)}),
135                     topologyHistoryState: null,
136                     topologyCurrentState: null,
137                     stepEpochStateTime: 1000,
138                     maxEpochStartTime: Math.round(new Date().getTime()),
139                     minEpochStartTime: parseInt(this.props.match.params.epochTime) - 259200000,
140                     nodeDiff: null,
141                     queryDisplay: ((this.props.match.params.type == 'cq') ?  JSON.parse(atob(this.props.match.params.payloadEnc)).query : atob(this.props.match.params.payloadEnc)),
142                     currentGraphNodes: [],
143                     currentGraphLinks: [],
144                     historicGraphNodes: [],
145                     historicGraphLinks: [],
146                     selectedNodeHistoryState: null,
147                     selectedNodeCurrentState: null,
148                     showNodeModal: false,
149                     showTopologyDiffModal: false,
150                     nodeDisplay: '',
151                     totalDiff: null,
152                     showSlider: false,
153                     sliderTickArray: null,
154                     rawMappedCurrentState: null,
155                     rawMappedHistoricState: null,
156                     showTicks: INVLIST.showTicks,
157                     diffOverlayOn: false,
158                     currentErrMsg: null,
159                     historicErrMsg: null
160                   });
161   }
162   getSettings = () => {
163     const settings = {
164           'NODESERVER': INVLIST.NODESERVER,
165           'PROXY': INVLIST.PROXY,
166           'PREFIX': INVLIST.PREFIX,
167           'VERSION': INVLIST.VERSION,
168           'USESTUBS': INVLIST.useStubs
169         };
170     return settings;
171   }
172   fetchInventoryData = (param) => {
173     console.log('fetchInventoryData', param);
174   };
175
176   generateDiffArray = (arr) => {
177     let tempArray = {};
178     tempArray['properties'] = [];
179     tempArray['related-to'] = [];
180
181     for (var i = 0; i < arr.properties.length; i++ ){
182         if(arr.properties[i].key !== "length"){
183             tempArray['properties'][arr.properties[i].key] = arr.properties[i];
184         }else{
185             tempArray['properties']["LENGTH"] = arr.properties[i];
186         }
187     }
188     for (var j = 0; j < arr['related-to'].length; j++ ){
189         tempArray['related-to'][arr['related-to'][j].url] = arr['related-to'][j];
190     }
191     return tempArray;
192   }
193
194   getCurrentStateCall = (settings,url,param) =>{
195       let path = "";
196       let stubPath = "";
197       if(this.props.match.params.type == 'cq'){
198         path = 'query';
199         stubPath = 'currentCQState';
200       }else{
201         path = 'dsl';
202         stubPath = "currentBYOQState";
203       }
204       this.setState({currentErrMsg:null});
205       commonApi(settings, path + '?format=state', 'PUT', this.state.payload, stubPath, null, 'history-traversal')
206       .then(res => {
207         this.getHistoryStateCall(this.getSettings());
208         Visualization.chart('currentState', this.state.currentGraphNodes, this.state.currentGraphLinks, res.data, this);
209         this.setState(
210         {
211           topologyCurrentState : res.data,
212           enableBusyRecentFeedback: false
213         });
214         console.log('After recent node service call ......',this.state);
215         console.log('[HistoryQuery.jsx] recent node results : ',  res.data);
216       }, error=>{
217         this.triggerError(error, "current");
218       }).catch(error => {
219         this.triggerError(error, "current");
220       });
221   };
222   triggerError = (error, type) => {
223     console.error('[HistoryQuery.jsx] error : ', JSON.stringify(error));
224         let errMsg = '';
225         if (error.response) {
226                 // The request was made and the server responded with a status code
227                 // that falls out of the range of 2xx
228                 console.log(error.response.data);
229                 console.log(error.response.status);
230                 console.log(error.response.headers);
231                 if(error.response.status){
232                     errMsg += " Code: " + error.response.status;
233                 }
234                 if(error.response.data){
235             errMsg += " - " + JSON.stringify(error.response.data);
236         }
237         } else if (error.request) {
238                 // The request was made but no response was received
239                 // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
240                 // http.ClientRequest in node.js
241                 console.log(error.request);
242                 errMsg += " - Request was made but no response received";
243         } else {
244                 // Something happened in setting up the request that triggered an Error
245                 console.log('Error', error.message);
246                 errMsg += " - Unknown error occurred " + error.message;
247         }
248         //Suppress 404's because that is just no results
249         if(error.response && error.response.status === 404){
250        errMsg = '';
251     }
252         if(type === 'current'){
253              this.setState({
254                   topologyCurrentState : null,
255                   currentErrMsg: errMsg,
256                   enableBusyRecentFeedback:false
257                 });
258         }else if (type === 'historic'){
259              if(this.state.isPlaying){
260                  this.setState({
261                         isPlaying: false,
262                     isStopped: true,
263                     isPaused: false });
264              }else{
265                 this.setState({
266                  topologyHistoryState : null,
267                  nodeDiff: null,
268                  enableBusyDiffFeedback: false,
269                  enableBusyHistoryStateFeedback:false,
270                  historicErrMsg: errMsg
271                });
272          }
273         }else{
274             console.log('[HistoryQuery.jsx] tiggerError method called without a type.' );
275         }
276   }
277   getHistoryStateCall = (settings, url, param, timeStamp) =>{
278       let path = "";
279       let stubPath = "";
280       let stubChangesPath = "";
281       if(this.props.match.params.type == 'cq'){
282         path = 'query';
283         stubPath = 'historicalCQState';
284         stubChangesPath = 'historicalCQChanges';
285       }else{
286         path = 'dsl';
287         stubPath = 'historicalBYOQState';
288         stubChangesPath = 'historicalBYOQChanges';
289       }
290       let ts = this.state.currentStateHistoryValue;
291       if(timeStamp){
292         ts = parseInt(timeStamp);
293       }
294       this.setState({historicErrMsg:null});
295       commonApi(settings, path + "?format=state" + "&startTs=" + ts + "&endTs=" + ts, 'PUT', this.state.payload, stubPath, null, 'history-traversal')
296       .then(res => {
297         Visualization.chart('historicState', this.state.historicGraphNodes, this.state.historicGraphLinks, res.data, this);
298         this.setState(
299         {
300           topologyHistoryState :  res.data,
301           enableBusyHistoryStateFeedback: false
302         });
303         console.log('After historical node state service call ......',this.state);
304         console.log('[HistoryQuery.jsx] historical node state results : ',  res.data);
305         if(this.state.topologyHistoryState != null && this.state.topologyCurrentState != null){
306                     let topologyDiffHistoryArr = [];
307                     let topologyDiffCurrentArr = [];
308                     let tempNodeCurrentState = [];
309                     let tempNodeHistoricState = [];
310                     for( var i = 0; i < this.state.topologyHistoryState.results.length; i++ ){
311                         topologyDiffHistoryArr[this.state.topologyHistoryState.results[i].url] = this.generateDiffArray(this.state.topologyHistoryState.results[i]);
312                         tempNodeHistoricState[this.state.topologyHistoryState.results[i].url] = this.state.topologyHistoryState.results[i];
313                     }
314                     for( var j = 0; j < this.state.topologyCurrentState.results.length; j++ ){
315                         topologyDiffCurrentArr[this.state.topologyCurrentState.results[j].url] = this.generateDiffArray(this.state.topologyCurrentState.results[j]);
316                         tempNodeCurrentState[this.state.topologyCurrentState.results[j].url] = this.state.topologyCurrentState.results[j];
317                     }
318                     var result = deepDiffMapper.map(topologyDiffHistoryArr, topologyDiffCurrentArr);
319                     console.log("diff map" + result);
320                     this.setState({ totalDiff: result, enableBusyDiffFeedback: false, rawMappedCurrentState: tempNodeCurrentState, rawMappedHistoricState: tempNodeHistoricState});
321                 }else{
322                     this.setState({ enableBusyDiffFeedback: false });
323          }
324       }, error=>{
325         this.triggerError(error, "historic");
326       }).catch(error => {
327         this.triggerError(error, "historic");
328       });
329       if(this.state.showTicks){
330             commonApi(settings, path + "?format=changes", 'PUT', this.state.payload, stubChangesPath, null, 'history-traversal')
331                                     .then(res => {
332                                       let tickTempArray = [];
333                                       for(var j = 0; j <  res.data.results.length; j++ ){
334                                           for(var k = 0; k < res.data.results[j].changes.length; k++){
335                                               if(!tickTempArray.includes(res.data.results[j].changes[k])){
336                                                   tickTempArray.push(res.data.results[j].changes[k]);
337                                               }
338                                           }
339                                       }
340                                 let tickArray = tickTempArray.sort(function(a, b) {
341                                                                                        var compareA = a;
342                                                                                        var compareB = b;
343                                                                                        if(compareA < compareB) return -1;
344                                                                                        if(compareA > compareB) return 1;
345                                                                                        return 0;
346                                                                                       });
347                                 console.log("tick array: " + tickArray);
348                                 this.setState({showSlider:true, sliderTickArray: tickArray});
349                              }).catch(error => {
350                                console.log('[HistoryQuery.jsx] historical node changes error : ', error);
351                                this.setState({showSlider:false});
352                         });
353       }else{
354             this.setState({showSlider:true});
355       }
356   };
357
358 componentWillReceiveProps(nextProps) {
359     console.log('[History.jsx] componentWillReceiveProps');
360     console.log('[History.jsx] next payloadEnc:', atob(nextProps.match.params.payloadEnc));
361     console.log('[History.jsx] this payloadEnc:', atob(this.props.match.params.payloadEnc));
362
363     if (nextProps.match.params.payloadEnc
364             && nextProps.match.params.type
365             && nextProps.match.params.epochTime
366             && ((nextProps.match.params.payloadEnc !== this.props.match.params.payloadEnc) ||
367                 (nextProps.match.params.type !== this.props.match.params.type) ||
368                 (nextProps.match.params.epochTime !== this.props.match.params.epochTime))
369     ) {
370       this.props = nextProps;
371       this.initState();
372       this.getCurrentStateCall(this.getSettings());
373     }
374   };
375
376   handlePageChange = (pageNumber) => {
377     console.log('[History.jsx] HandelPageChange active page is', pageNumber);
378     this.setState({});
379   };
380
381   stateHistoryFormat = (event) =>{
382      this.setState({ currentStateHistoryValue: event.target.value, selectedHistoryStateFormatted: moment(event.target.value).format('dddd, MMMM Do, YYYY h:mm:ss A')});
383   };
384
385   changeHistoryState = () =>{
386     console.log('minEpochStateTime: ' + this.state.minEpochStateTime);
387     console.log('maxEpochStartTime: ' + this.state.maxEpochStartTime);
388     console.log('stepEpochStateTime: ' + this.state.stepEpochStateTime);
389     console.log('currentStateHistoryValue: ' + this.state.currentStateHistoryValue);
390     console.log("Calling the route again with a new timestamp");
391     this.props.history.push('/historyQuery/'  + this.props.match.params.type + '/' + this.props.match.params.payloadEnc + '/' + this.state.currentStateHistoryValue);
392   }
393
394   viewTopologyComp = () =>{
395     this.setState({
396                 showTopologyDiffModal: true
397     });
398   }
399
400   resetGraph = () =>{
401         Visualization.chart('currentState', this.state.currentGraphNodes, this.state.currentGraphLinks, this.state.topologyCurrentState, this);
402         Visualization.chart('historicState', this.state.historicGraphNodes, this.state.historicGraphLinks, this.state.topologyHistoryState, this);
403   }
404
405   addOverlay = (elementType, key, modificationType) =>{
406     let chartToOverlay = 'currentState';
407     let color = '';
408     let modIcon = '';
409     if (modificationType === "deleted"){
410         chartToOverlay = "historicState";
411     }
412     switch (modificationType){
413         case 'deleted':
414             color = 'red';
415             modIcon = '-';
416             break;
417         case 'created':
418             color = 'green';
419             modIcon = '+';
420             break;
421         case 'modified':
422             color = 'orange';
423             modIcon = '*';
424             break;
425         default:
426             console.log("hit default " + modificationType);
427     }
428     if(key){
429         key = (((decodeURIComponent(key)).replace(new RegExp('\/', 'g'),'-')).replace(new RegExp(':', 'g'),'-')).replace(new RegExp('\\.', 'g'),'-');
430     }else{
431         key='';
432     }
433     console.log("adding overlay item for - element type: " + elementType + " key: " + key + " modificationType: " + modificationType );
434     let elementKey = elementType + chartToOverlay + key;
435     let element = d3.select("#" + elementKey);
436
437     if(elementType === "line"){
438         element.attrs({ 'stroke': color, 'stroke-opacity': .6, 'stroke-width': '3px'});
439     }
440     if(elementType === "nodeIcon"){
441         element.classed("nodeIcon-" + modificationType, true);
442         let elementKeyNode = 'node' + chartToOverlay + key;
443         let elementNode = d3.select("#" + elementKeyNode);
444         elementNode.append("text")
445                          .attr("dy", 10)
446                          .attr("dx", 35)
447                          .attr('font-size', 25)
448                          .text(modIcon);
449     }
450     //Need to also add to historicGraph for modifications in addition to current
451     if(modificationType === 'modified'){
452        let elementKeyMod = elementType + 'historicState' + key;
453        let elementMod = d3.select("#" + elementKeyMod);
454        elementMod.classed("nodeIcon-" + modificationType, true);
455
456        let elementKeyModNode =  'nodehistoricState' + key;
457        let elementModNode = d3.select("#" + elementKeyModNode);
458        elementModNode.append("text")
459                         .attr("dy", 10)
460                         .attr("dx", 35)
461                         .attr('font-size', 25)
462                         .text(modIcon);
463     }
464   }
465
466   viewTopologyCompVisual = () =>{
467     if(this.state.diffOverlayOn){
468         this.setState({ diffOverlayOn: false});
469         this.resetGraph();
470     }else if(this.state.totalDiff){
471         this.setState({ diffOverlayOn: true});
472         const properties =  Object.entries(this.state.totalDiff).forEach((prop) => {
473             if (prop){
474                         let propWorkaround = prop;
475                         if(prop.data){
476                             propWorkaround = prop.data;
477                         }
478                         let tempProp = propWorkaround[1];
479                         let attributeProperties = '';
480                         let relationships = '';
481                         let topLevelType = tempProp.type;
482                         let alreadyHighlighted = false;
483                         if(topLevelType){
484                             this.addOverlay('nodeIcon', propWorkaround[0], topLevelType);
485                             //set this to not mark as modified when it is added new
486                             alreadyHighlighted = true;
487                         }
488                         if(!topLevelType && tempProp.properties){
489                             for (var key in tempProp.properties) {
490                                     if (tempProp.properties.hasOwnProperty(key)) {
491                                        let property = tempProp.properties[key];
492                                        if(property && ((property.value && property.value.type !== 'unchanged') || property.type)){
493                                          this.addOverlay('nodeIcon', propWorkaround[0], 'modified');
494                                          break;
495                                        }
496                                     }
497                             }
498                         }
499                         if(tempProp['related-to'] || tempProp.data['related-to']){
500                             let rel = null;
501                             let topLevelType = null;
502                             if(tempProp['related-to']){
503                                 rel = tempProp['related-to'];
504                             }else if (tempProp.data['related-to']) {
505                                 rel = tempProp.data['related-to'];
506                                 topLevelType = tempProp.type;
507                             }
508                             relationships =  Object.entries(rel).forEach((property) => {
509                                     let relationProp = property[1];
510                                     if(relationProp && relationProp.type && relationProp.type.type &&  relationProp.type.type !== "unchanged"){
511                                         //do nothing since we only care about additions and deletions on relationships, should not be considered modified
512                                         console.log("relationship considered modified: id: " + relationProp.id + " type: "+ relationProp.type + " mod type: " + relationProp.type.type)
513                                     }else if(relationProp && relationProp.type && !relationProp.type.type && relationProp.url && relationProp.url.data){
514                                         if(!alreadyHighlighted){
515                                             this.addOverlay('nodeIcon', propWorkaround[0], 'modified');
516                                         }
517                                         this.addOverlay('line', relationProp.id.data, relationProp.type);
518                                     }else if (relationProp && relationProp.type && relationProp.data){
519                                         if(!alreadyHighlighted){
520                                             this.addOverlay('nodeIcon', propWorkaround[0], 'modified');
521                                         }
522                                         this.addOverlay('line', relationProp.data.id, relationProp.type);
523                                     }else if (topLevelType){
524                                         if(!alreadyHighlighted){
525                                             this.addOverlay('nodeIcon', propWorkaround[0], 'modified');
526                                         }
527                                         this.addOverlay('line', relationProp.id, topLevelType);
528                                     }
529                             });
530                         }
531                   }else{
532                       //No changes, do nothing
533                   }
534             });
535
536       }else{
537         // do nothing if no diff
538       }
539   }
540
541   closeTopologyDiffModal = () => {
542     this.setState({
543         showTopologyDiffModal: false
544     });
545   }
546   openNodeModal(nodeDisplay, nodeUri, nodeType){ // open modal
547                  console.log('history >> showModal');
548                  this.setState({ nodeDiff: this.state.totalDiff[nodeUri]});
549                  nodeDisplay = "State Comparison of " + nodeUri;
550                  if(!this.state.rawMappedHistoricState[nodeUri]){
551                     this.state.rawMappedHistoricState[nodeUri] = {};
552                  }
553                  if(!this.state.rawMappedCurrentState[nodeUri]){
554                     this.state.rawMappedCurrentState[nodeUri] = {};
555                  }
556                  this.state.rawMappedHistoricState[nodeUri].primaryHeader = "Historic State of " + nodeDisplay;
557                  this.state.rawMappedCurrentState[nodeUri].primaryHeader = "Current State of " + nodeDisplay;
558                  if(nodeDisplay){
559                     this.setState({
560                        nodeDisplay: nodeDisplay,
561                        selectedNodeHistoryState: this.state.rawMappedHistoricState[nodeUri],
562                        selectedNodeCurrentState: this.state.rawMappedCurrentState[nodeUri],
563                        focusedNodeUri: nodeUri,
564                        focusedNodeType: nodeType,
565                        showNodeModal:true
566                     });
567                  }else{
568                      this.setState({
569                      showNodeModal:true
570                      });
571                  }
572   }
573   closeNodeModal = () => {
574     this.setState({
575         showNodeModal: false
576     });
577   }
578
579   getStateIndex = () =>{
580     return this.state.sliderTickArray.indexOf(this.state.currentStateHistoryValue);
581   }
582
583   navigateAnimation = (index, command) => {
584      if(!command){
585         this.setState({isPlaying:false, isStopped: false, isPaused: true, currentStateHistoryValue: this.state.sliderTickArray[index], selectedHistoryStateFormatted: moment(this.state.sliderTickArray[index]).format('dddd, MMMM Do, YYYY h:mm:ss A')});
586      }else if (command === 'play'){
587         this.setState({currentStateHistoryValue: this.state.sliderTickArray[index], selectedHistoryStateFormatted: moment(this.state.sliderTickArray[index]).format('dddd, MMMM Do, YYYY h:mm:ss A')});
588      }
589      this.props.history.push('/historyQuery/'  + this.props.match.params.type + '/' + this.props.match.params.payloadEnc + '/' + this.state.sliderTickArray[index]);
590   }
591
592   play = () =>{
593     if(this.state.isPlaying){
594         if(!this.state.enableBusyHistoryStateFeedback){
595             var index = Math.min(this.state.sliderTickArray.length - 1, this.getStateIndex() + 1);
596             if(this.state.sliderTickArray.length > this.getStateIndex() + 1){
597                     this.navigateAnimation(this.getStateIndex() + 1, 'play');
598
599             }else{
600                  this.setState({isPlaying:false, isStopped: true, isPaused: false});
601             }
602         }
603     }else{
604          clearInterval(this.state.intervalId);
605     }
606   }
607
608   animationControl = (controlType) => {
609     console.log("Control was hit: " + controlType);
610     switch(controlType){
611         case 'play':
612             if(!this.state.isPlaying){
613                 this.setState({isPlaying:true, isStopped: false, isPaused: false, intervalId: setInterval(this.play, INVLIST.animationIntervalMs)});
614             }
615             break;
616         case 'pause':
617             if(this.state.isPlaying){
618                 clearInterval(this.state.intervalId);
619                 this.setState({isPlaying:false, isPaused: true});
620             }
621             break;
622         case 'stop':
623             if(this.state.isPlaying || this.state.isPaused){
624                 clearInterval(this.state.intervalId);
625                 this.setState({isPlaying:false, isStopped: true, isPaused: false});
626             }
627             break;
628         case 'skipForwardStep':
629             var index = Math.min(this.state.sliderTickArray.length - 1, this.getStateIndex() + 1);
630             this.navigateAnimation(index);
631             break;
632         case 'skipBackwardStep':
633             var index = Math.max(0, this.getStateIndex() - 1);
634             this.navigateAnimation(index);
635             break;
636         case 'skipForwardLast':
637             this.navigateAnimation(this.state.sliderTickArray.length - 1);
638             break;
639         case 'skipBackwardEpoch':
640             this.navigateAnimation(0);
641             break;
642         default:
643             this.setState({isPlaying:false, isStopped: false, isPaused: false});
644             break;
645     }
646   }
647   // START: These functions are for the animation controls
648   setHistoricStateValues = (currValue) =>{
649     this.setState({currentStateHistoryValue: currValue, selectedHistoryStateFormatted: moment(currValue).format('dddd, MMMM Do, YYYY h:mm:ss A')});
650   }
651
652   navigateHistory = (time) =>{
653     this.props.history.push('/historyQuery/'  + this.props.match.params.type + '/' + this.props.match.params.payloadEnc + '/' + time);
654   }
655
656   setStateValue = (key, value) =>{
657      this.setState((state) => { key : value });
658   }
659   getStateValue = (stateVar) => {
660     return this.state[stateVar];
661   }
662   clear = (interval) =>{
663     clearInterval(interval);
664   }
665   // END
666
667   render() {
668         if(INVLIST.isHistoryEnabled){
669             return (
670                <div>
671                 <header className='addPadding jumbotron my-4'>
672                             <h1 className='display-2'>History Visualization</h1>
673                             <p className='lead'>
674                               On this page you have the ability to view a series of network elements in their current and historic state.
675                               Zooming and panning are enabled in the graphs. You can use the scrollwheel to zoom and click+drag to pan.
676                               If a node is single clicked you can drag the elements to reposition them. Double-clicking a node will
677                               show the comparison between the two nodes in a pop-up modal. Animation controls are provided to seemlessly transition between states.
678                               You can view the graph difference in a visual form by clicking View Visual Comparison.
679                             </p>
680                 </header>
681                 <Grid fluid={true} className="addPadding">
682                   <Row className={this.state.currentErrMsg ? 'show' : 'hidden'} >
683                     <div className='addPaddingTop alert alert-danger' role="alert">
684                         An error occurred while trying to get current information, please try again later. If this issue persists, please contact the system administrator. {this.state.currentErrMsg}
685                     </div>
686                   </Row>
687                   <Row className={!this.state.currentErrMsg ? 'show' : 'hidden'} >
688                     <Col className='col-lg-12'>
689                           <div className='card d3-history-query-card'>
690                             <div className='card-header history-query-card-header'>
691                               <h2><strong>Current State</strong> of <em>{this.state.queryDisplay}</em></h2>
692                             </div>
693                             <div className={'card-header ' + (this.state.topologyHistoryState ? '' : 'hidden')}>
694                                  <button type='button' className='btn btn-outline-primary' onClick={this.viewTopologyCompVisual}>Toggle Visual Comparison</button>
695                             </div>
696                             <div className='history-query-card-content'>
697                                {!this.state.topologyCurrentState || (this.state.topologyCurrentState.results && this.state.topologyCurrentState.results.length === 0) && (<div className='addPaddingTop'><p>No current state for this query</p></div>)}
698                                <OutputVisualization identifier="currentState" width={this.svgWidth}  height="600" overflow="scroll"/>
699                             </div>
700                             <div className='card-footer'>
701                                <strong>Tip:</strong> <em>Click and drag network elements to reposition them, double-click nodes to see the node detail comparison. In addition: The graph supports pinch zoom or scrollwheel for zooming. Panning can be done by single-click and drag.</em>
702                             </div>
703                           </div>
704                     </Col>
705                   </Row>
706                   <Row className={this.state.historicErrMsg ? 'show' : 'hidden'} >
707                     <div className='addPaddingTop alert alert-danger' role="alert">
708                        An error occurred, while trying to get historic information, please try again later. If this issue persists, please contact the system administrator. {this.state.historicErrMsg}
709                     </div>
710                   </Row>
711                    <Row className={!this.state.historicErrMsg ? 'show' : 'hidden'}>
712                         <Col className='col-lg-12'>
713                               <div className='card d3-history-query-card'>
714                                 <div className='card-header history-query-card-header'>
715                                   <h2><strong>Historical State</strong> of <em>{this.state.queryDisplay}</em> at {moment(parseInt(this.props.match.params.epochTime)).format('dddd, MMMM Do, YYYY h:mm:ss A')}</h2>
716                                 </div>
717                                     <div className='card-header'>
718                                        { (this.state.showSlider && this.state.showTicks) && (<Row className='show-grid'>
719                                                                                                 <Col md={3}>
720                                                                                                     <ReactBootstrapSlider
721                                                                                                     value={this.state.currentStateHistoryValue}
722                                                                                                     change={this.stateHistoryFormat}
723                                                                                                     slideStop={this.stateHistoryFormat}
724                                                                                                     step={ 1 }
725                                                                                                     ticks={ this.state.sliderTickArray }
726                                                                                                     ticks_snap_bounds={ 10000 }
727                                                                                                     orientation="horizontal" />
728                                                                                                 </Col>
729                                                                                                 <Col md={8}>
730                                                                                                     <i className='icon-controls-skipbackstartover animationControlIcon'  onClick={() => this.animationControl('skipBackwardEpoch')} role="img"></i>
731                                                                                                     <i className='icon-controls-rewind animationControlIcon'  onClick={() => this.animationControl('skipBackwardStep')} role="img"></i>
732                                                                                                     <i className={'icon-controls-pointer ' + (this.state.isPlaying ? 'animationPlayingIcon' : 'animationControlIcon')}  onClick={() => this.animationControl('play')} role="img"></i>
733                                                                                                     <i className={'icon-controls-pause ' + (this.state.isPaused ? 'animationPausedIcon' : 'animationControlIcon')}  onClick={() => this.animationControl('pause')} role="img"></i>
734                                                                                                     <i className={'icon-controls-stop ' + (this.state.isStopped ? 'animationStoppedIcon' : 'animationControlIcon')}   onClick={() => this.animationControl('stop')} role="img"></i>
735                                                                                                     <i className='icon-controls-fastforward animationControlIcon'  onClick={() => this.animationControl('skipForwardStep')} role="img"></i>
736                                                                                                     <i className='icon-controls-skipforward animationControlIcon'  onClick={() => this.animationControl('skipForwardLast')} role="img"></i>
737                                                                                                 </Col>
738                                                                                                </Row>
739                                                                                                       )}
740                                        { (this.state.showSlider && !this.state.showTicks) && (<ReactBootstrapSlider
741                                                                                                 value={this.state.currentStateHistoryValue}
742                                                                                                 change={this.stateHistoryFormat}
743                                                                                                 slideStop={this.stateHistoryFormat}
744                                                                                                 step={this.state.stepEpochStateTime}
745                                                                                                 max={this.state.maxEpochStartTime}
746                                                                                                 min={this.state.minEpochStartTime}
747                                                                                                 orientation="horizontal" />)}
748
749                                                                     <p>{this.state.selectedHistoryStateFormatted}</p>
750                                                                     <button type='button' className='btn btn-outline-primary' onClick={this.changeHistoryState}>Refresh</button>
751                                     </div>
752                                     <div className='history-query-card-content' >
753                                       {!this.state.topologyHistoryState || (this.state.topologyHistoryState.results && this.state.topologyHistoryState.results.length  === 0) && (<div className="addPaddingTop"><p>No state during this time period</p></div>)}
754                                       <OutputVisualization identifier="historicState" width={this.svgWidth}  height="600" overflow="scroll"/>
755                                     </div>
756                                     <div className={'card-footer ' + (this.state.topologyHistoryState ? '' : 'hidden')}>
757                                       <strong>Tip:</strong> <em>Click and drag network elements to reposition them, double-click nodes to see the node detail comparison. In addition: The graph supports pinch zoom or scrollwheel for zooming. Panning can be done by single-click and drag.</em>
758                                     </div>
759                                 </div>
760
761                         </Col>
762                    </Row>
763                 </Grid>
764                 <div className='static-modal'>
765                                         <Modal show={this.state.showNodeModal} onHide={this.closeNodeModal} dialogClassName="modal-override">
766                                                 <Modal.Header>
767                                                         <Modal.Title>Retrieve {this.state.nodeDisplay} History</Modal.Title>
768                                                 </Modal.Header>
769                                                 <Modal.Body>
770                                         <Grid fluid={true}>
771                                               <Row className='show-grid'>
772                                                 <Col md={4}>
773                                                     <HistoryCard node={this.state.selectedNodeHistoryState}/>
774                                                 </Col>
775                                                 <NodeDiffCard diff={this.state.nodeDiff}/>
776                                                 <Col md={4}>
777                                                     <HistoryCard node={this.state.selectedNodeCurrentState}/>
778                                                 </Col>
779                                               </Row>
780                                         </Grid>
781                                                 </Modal.Body>
782                                                 <Modal.Footer>
783                                                         <Button onClick={this.closeNodeModal}>Close</Button>
784                                                 </Modal.Footer>
785                                         </Modal>
786                 </div>
787                 <div className='static-modal'>
788                                                 <Modal show={this.state.showTopologyDiffModal} onHide={this.closeTopologyDiffModal}>
789                                                         <Modal.Header>
790                                                                 <Modal.Title>Retrieve Topology History</Modal.Title>
791                                                         </Modal.Header>
792                                                         <Modal.Body>
793                                                 <Grid fluid={true}>
794                                                       <Row className='show-grid'>
795                                                         <Col md={12}>
796                                                             <TopologyDiffCard node={this.state.totalDiff}/>
797                                                         </Col>
798                                                       </Row>
799                                                 </Grid>
800                                                         </Modal.Body>
801                                                         <Modal.Footer>
802                                                                 <Button onClick={this.closeTopologyDiffModal}>Close</Button>
803                                                         </Modal.Footer>
804                                                 </Modal>
805                         </div>
806               </div>
807             );
808         }else{
809             return(<p>History Not Enabled for this instance, please check config.</p>)
810         }
811   }
812 }
813
814 export default HistoryQuery;