-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ErrorHandler): handle react errors, crash reports
We've been getting complaints from users of a "blank white screen", which is caused by unhandled exceptions in react code. While we're learning from past lessons, we've continually gotten incredible ROI for time spent building tools that log and report errors. To that end, I've added a crash reporter to our error handler. Ideally this'll help us diagnose and address fixes faster without forcing users to send us data.
- Loading branch information
Showing
5 changed files
with
146 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
import React from 'react' | ||
import versions from '../../version' | ||
import { CRASH_REPORTER_URL } from '../constants' | ||
|
||
import localStore from '../utils/localStore' | ||
import ExternalLink from './ExternalLink' | ||
import Button from './chrome/Button' | ||
|
||
export default class ErrorHandler extends React.Component { | ||
state = { | ||
error: undefined, | ||
errorInfo: undefined, | ||
sendingReport: false, | ||
sentReport: false | ||
} | ||
|
||
constructor (props) { | ||
super(props) | ||
|
||
this.handleSendCrashReport = this.handleSendCrashReport.bind(this) | ||
this.handleReload = this.handleReload.bind(this) | ||
} | ||
|
||
componentDidCatch (error, errorInfo) { | ||
this.setState({ error, errorInfo, sendingReport: false }) | ||
|
||
// clear local storage, which may cause re-crashing if in an error put us | ||
// into an un-recoverable state | ||
localStore().setItem('peername', '') | ||
localStore().setItem('name', '') | ||
localStore().setItem('activeTab', 'status') | ||
localStore().setItem('component', '') | ||
localStore().setItem('commitComponent', '') | ||
} | ||
|
||
handleSendCrashReport (e: React.MouseEvent) { | ||
const { error, errorInfo } = this.state | ||
this.setState({ error, errorInfo, sendingReport: true }) | ||
|
||
console.log('sending report') | ||
postCrashReport(error, errorInfo) | ||
.then(() => { | ||
this.setState({ | ||
sentReport: true, | ||
sendingReport: false | ||
}) | ||
}) | ||
.catch((e) => { | ||
console.log(e) | ||
this.setState({ | ||
sendingReport: false | ||
}) | ||
}) | ||
} | ||
|
||
handleReload (e: React.MouseEvent) { | ||
window.location.hash = '/' | ||
window.location.reload() | ||
} | ||
|
||
render () { | ||
if (!this.state.error) { | ||
return this.props.children | ||
} | ||
|
||
const { sendingReport, sentReport } = this.state | ||
|
||
return ( | ||
<div className="error-container"> | ||
<div className="dialog"> | ||
<h1>Dang. It broke.</h1> | ||
<p>Apologies, Qri desktop encountered an error. Send us a crash report!</p> | ||
<div className="reporting actions"> | ||
{sentReport | ||
? <p>Thanks!</p> | ||
: <Button | ||
id="send-crash-report" | ||
text="Send Crash Report" | ||
color="primary" | ||
loading={sendingReport} | ||
onClick={this.handleSendCrashReport} | ||
/>} | ||
</div> | ||
<div className="reload"> | ||
<p>If you have additional info or questions, feel free to <ExternalLink id="file-issue" href="https://github.com/qri-io/desktop/issues/new">file an issue</ExternalLink> describing where things went wrong. The more detail, the better. Reload to get back to Qri.</p> | ||
<Button | ||
id="error-reload" | ||
text="Reload Qri Desktop" | ||
color="dark" | ||
onClick={this.handleReload} | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
) | ||
} | ||
} | ||
|
||
// TODO (b5) - bring this back in the near future for fetching home feed | ||
async function postCrashReport (err, errInfo): Promise<any> { | ||
const options: FetchOptions = { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json' | ||
}, | ||
body: JSON.stringify({ | ||
app: 'desktop', | ||
version: versions.desktopVersion, | ||
platform: navigator.platform, | ||
error: err.toString(), | ||
info: errInfo.componentStack | ||
}) | ||
} | ||
|
||
console.log(CRASH_REPORTER_URL) | ||
const r = await fetch(CRASH_REPORTER_URL, options) | ||
const res = await r.json() | ||
return res | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
|
||
.error-container { | ||
width: 100%; | ||
height: 100%; | ||
|
||
.dialog { | ||
margin: 0 auto; | ||
padding-top: 8em; | ||
width: 350px; | ||
|
||
.reporting.actions button { | ||
margin-right: 15px; | ||
} | ||
|
||
.reload { | ||
margin-top: 35px; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters