RIFT-15943 - Debug tab presented no content and download button broken
authorBob Gallagher <bob.gallagher@riftio.com>
Tue, 28 Mar 2017 21:41:31 +0000 (17:41 -0400)
committerBob Gallagher <bob.gallagher@riftio.com>
Tue, 28 Mar 2017 21:41:31 +0000 (17:41 -0400)
- the link eventDispatch was not working
— converted to link.click()
- cleaned up download
— removed prefix (no longer needed)
— postponed building download content till button pressed
- added isLoading support
— retrieving crash log from server can take some time
— display loading text on page when loading
— use loading component
- bit of code clean up
— formatting
— var -> let/const
— comments

Change-Id: I9aa154c115c5a8b73879347ecc6587595d4bd0fd
Signed-off-by: Bob Gallagher <bob.gallagher@riftio.com>
skyquake/plugins/debug/src/crash.jsx
skyquake/plugins/debug/src/crashStore.js

index 6c659c3..bed1dbf 100644 (file)
@@ -21,114 +21,126 @@ import './crash.scss';
 import TreeView from 'react-treeview';
 import '../node_modules/react-treeview/react-treeview.css';
 import AppHeader from 'widgets/header/header.jsx';
+import ScreenLoader from 'widgets/screen-loader/screenLoader.jsx';
 var crashActions = require('./crashActions.js');
 var crashStore = require('./crashStore.js');
 // var MissionControlStore = require('../missioncontrol/missionControlStore.js');
 function openDashboard() {
-  window.location.hash = "#/";
+    window.location.hash = "#/";
 }
 class CrashDetails extends React.Component {
-  constructor(props) {
-    super(props)
-    var self = this;
-    this.state = crashStore.getState();
-    crashStore.listen(this.storeListener);
-  }
-  storeListener = (data) => {
-     this.setState({
-        list:data.crashList,
-        noDebug:!this.hasDebugData(data.crashList)
-      });
-  }
-  componentWillUnmount(){
-    crashStore.unlisten(this.storeListener);
-  }
-  componentWillMount() {
-    crashStore.get();
-  }
-  hasDebugData(list) {
-    console.log(list);
-    if (list && list.length > 0) {
-      for (let i = 0; i < list.length; i++) {
-        var trace = list[i].backtrace;
-        for (let j = 0; j < trace.length; j++) {
-          console.log(trace[j])
-          if (trace[j].detail) {
-            return true;
-          }
+    constructor(props) {
+        super(props)
+        this.state = crashStore.getState();
+        crashStore.listen(this.storeListener);
+    }
+    storeListener = (data) => {
+        this.setState({
+            isLoading: data.isLoading,
+            list: data.crashList,
+            noDebug: !this.hasDebugData(data.crashList)
+        });
+    }
+    componentWillUnmount() {
+        crashStore.unlisten(this.storeListener);
+    }
+    componentWillMount() {
+        crashStore.get();
+    }
+    hasDebugData(list) {
+        console.log(list);
+        if (list && list.length > 0) {
+            for (let i = 0; i < list.length; i++) {
+                var trace = list[i].backtrace;
+                for (let j = 0; j < trace.length; j++) {
+                    console.log(trace[j])
+                    if (trace[j].detail) {
+                        return true;
+                    }
+                }
+            }
         }
-      }
+        return false;
     }
-    return false;
-  }
-  downloadFile(fileName, urlData) {
-    var replacedNewLines = urlData.replace(/\\n/g, '\n');
-    var replacedTabs = replacedNewLines.replace(/\\t/g, '\t');
-    var replacedQuotes= replacedTabs.replace(/\\"/g, '"');
-    var textFileBlob = new Blob([replacedQuotes], {type: 'text/plain;charset=UTF-8'});
-    var aLink = document.createElement('a');
-    var evt = document.createEvent("HTMLEvents");
-    evt.initEvent("click");
-    aLink.download = fileName;
-    aLink.href = window.URL.createObjectURL(textFileBlob);
-    aLink.dispatchEvent(evt);
-  }
-  render() {
-    let html;
-    var list = null;
-    if (this.state != null) {
-      var tree = <div style={{'marginLeft':'auto', 'marginRight':'auto', 'width':'230px', 'padding':'90px'}}> No Debug Information Available </div>;
-      if (!this.state.noDebug)
-      {
-        var tree = this.state.list && this.state.list.map((node, i) => {
-                  var vm = node.name;
-                  var vm_label = <span>{vm}</span>;
-                  var backtrace = node.backtrace;
-                  return (
-                    <TreeView key={vm + '|' + i} nodeLabel={vm_label} defaultCollapsed={false}>
-                      {backtrace.map(details => {
-
-                        //Needed to decode HTML
-                        var text_temp = document.createElement("textarea")
-                        text_temp.innerHTML = details.detail;
-                        var text_temp = text_temp.value;
-                        var arr = text_temp.split(/\n/);
-                        var text = [];
-                        for (let i = 0; i < arr.length; i++) {
-                          text.push(arr[i]);
-                          text.push(<br/>)
-                        }
+    downloadFile(fileName) {
+        // wait till download selected to create text blob
+        let urlData = JSON.stringify(this.state.list, null, 2);
+        let replacedNewLines = urlData.replace(/\\n/g, '\n');
+        let replacedTabs = replacedNewLines.replace(/\\t/g, '\t');
+        let replacedQuotes = replacedTabs.replace(/\\"/g, '"');
+        let textFileBlob = new Blob([replacedQuotes], { type: 'text/plain;charset=UTF-8' });
+        let aLink = document.createElement('a');
+        aLink.download = fileName;
+        aLink.href = window.URL.createObjectURL(textFileBlob);
+        aLink.click(); // suprise, this works without being on the page
+        // it seems to cause no problems cleaning up the blob right away
+        window.URL.revokeObjectURL(aLink.href);
+        // assuming aLink just goes away when this function ends
+    }
+    render() {
+        let html;
+        if (this.state != null) {
+            if (!this.state.noDebug) {
+                let tree = this.state.list && this.state.list.map((node, i) => {
+                    const vm = node.name;
+                    const vm_label = <span>{vm}</span>;
+                    const backtrace = node.backtrace;
+                    return (
+                        <TreeView key={vm + '|' + i} nodeLabel={vm_label} defaultCollapsed={false}>
+                            {backtrace.map(details => {
+                                // do some trickery to normalize details 'new line' char(s)
+                                let textareaElement = document.createElement("textarea")
+                                textareaElement.innerHTML = details.detail;
+                                let detailsFormatted = textareaElement.value;
+                                let arr = detailsFormatted.split(/\n/);
+                                let text = [];
+                                for (let i = 0; i < arr.length; i++) {
+                                    text.push(arr[i]);
+                                    text.push(<br key={'line-' + i} />); // react likes keys on array children
+                                }
 
-                        return (
-                          <TreeView nodeLabel={<span>{details.id}</span>} key={vm + '||' + details.id} defaultCollapsed={false}>
-                            <p>{text}</p>
-                          </TreeView>
-                        );
-                      })}
-                    </TreeView>
-                  );
-                })}
-      html = (
-        <div className="crash-details-wrapper">
-              <div className="form-actions">
-                <button role="button" className="dark" onClick={this.state.noDebug ? false : this.downloadFile.bind(this, 'crash.txt', 'data:text;charset=UTF-8,' + decodeURIComponent(JSON.stringify(this.state.list, null, 2)))}> Download Crash Details</button>
-              </div>
-              <div className="crash-container">
-                <h2> Debug Information </h2>
-                <div className="tree">{tree}</div>
-              </div>
-        </div>
-              );
-    } else {
-      html = <div className="crash-container"></div>
-    };
-    let refPage = window.sessionStorage.getItem('refPage');
-    refPage = JSON.parse(refPage);
-    return (
+                                return (
+                                    <TreeView nodeLabel={<span>{details.id}</span>} key={vm + '||' + details.id} defaultCollapsed={false}>
+                                        <p>{text}</p>
+                                    </TreeView>
+                                );
+                            })}
+                        </TreeView>
+                    );
+                });
+                let doDownload = this.downloadFile.bind(this, 'crash.txt');
+                html = (
+                    <div className="crash-details-wrapper">
+                        <div className="form-actions">
+                            <button role="button" className="dark" onClick={this.state.noDebug ? false : doDownload}>Download Crash Details</button>
+                        </div>
+                        <div className="crash-container">
+                            <h2> Debug Information </h2>
+                            <div className="tree">{tree}</div>
+                        </div>
+                    </div>
+                );
+            } else {
+                let text = this.state.isLoading ? "Loading Debug Information" : "No Debug Information Available"
+                html = (
+                    <div className="crash-details-wrapper">
+                        <div className="crash-container">
+                            <div style={{ 'marginLeft': 'auto', 'marginRight': 'auto', 'width': '230px', 'padding': '90px' }}>{text}</div>
+                        </div>
+                    </div>
+                );
+            }
+        } else {
+            html = <div className="crash-container"></div>
+        };
+        let refPage = window.sessionStorage.getItem('refPage');
+        refPage = JSON.parse(refPage);
+        return (
             <div className="crash-app">
-              {html}
+                {html}
+                <ScreenLoader show={this.state.isLoading}/> 
             </div>
-            );
-  }
+        );
+    }
 }
 export default CrashDetails;
index 51857d5..0b04445 100644 (file)
@@ -20,18 +20,29 @@ import Alt from 'widgets/skyquake_container/skyquakeAltInstance';
 function crashStore () {
   this.exportAsync(require('./crashSource.js'));
   this.bindActions(require('./crashActions.js'));
+  this.isLoading = false;
+  this.crashList = null;
 }
 
 crashStore.prototype.getCrashDetailsSuccess = function(list) {
   this.setState({
-    crashList:list
+    isLoading: false,
+    crashList: list
   })
-  console.log('success', list)
+  console.log('Crash details load success', list)
 };
 crashStore.prototype.getCrashDetailsLoading = function(info) {
+  this.setState({
+    isLoading: true,
+    crashList: null,
+  })
   console.log('Loading crash details...', info)
 };
 crashStore.prototype.getCrashDetailsFailure = function(info) {
+  this.setState({
+    isLoading: false,
+    error: info
+  })
   console.log('Failed to retrieve crash/debug details', info)
 };