Skip to content

Commit

Permalink
feat(migration components): add migration components to conditionally…
Browse files Browse the repository at this point in the history
… render on app-launch

create `IncompatibleBackend`, `MigratingBackend` and `MigrationFailed` components to run on app-launch. Create `ReportCrashAndReload` component to handle crash report, issue filing and reload functionality and refactor `ErrorHandler` to use this
  • Loading branch information
uhLeeshUh authored Jul 15, 2020
1 parent 47621a3 commit 866a9e4
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 91 deletions.
99 changes: 8 additions & 91 deletions app/components/ErrorHandler.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,16 @@
import React from 'react'
import versions from '../../version'
import { CRASH_REPORTER_URL } from '../constants'

import ReportCrashAndReload, { ErrorProps } from './ReportCrashAndReload'
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
errorInfo: undefined
}

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 })
componentDidCatch (error: ErrorProps['error'], errorInfo: ErrorProps['errorInfo']) {
this.setState({ error, errorInfo })

// clear local storage, which may cause re-crashing if in an error put us
// into an un-recoverable state
Expand All @@ -33,87 +21,16 @@ export default class ErrorHandler extends React.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>
<ReportCrashAndReload
title='Dang. It broke.'
error={this.state.error}
errorInfo={this.state.errorInfo}/>
)
}
}

// 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
}
20 changes: 20 additions & 0 deletions app/components/IncompatibleBackend.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react'
import WelcomeTemplate from './onboard/WelcomeTemplate'
import ExternalLink from './ExternalLink'

interface IncompatibleBackendProps {
incompatibleVersion: string
}

// TODO: this variable needs to be replaced when the
// lowestCompatibleBackend variable exists in backend.js
const lowestCompatibleBackend = '0.9.8'

const IncompatibleBackend: React.FC<IncompatibleBackendProps> = ({ incompatibleVersion }) => (
<WelcomeTemplate title='Incompatible Backend Version'>
<p>{`The lowest supported backend version is v${lowestCompatibleBackend}, but you are running on v${incompatibleVersion}.`}</p>
<p>Please file an issue <ExternalLink id="file-issue" href="https://github.com/qri-io/desktop/issues/new">here</ExternalLink>.</p>
</WelcomeTemplate>
)

export default IncompatibleBackend
8 changes: 8 additions & 0 deletions app/components/MigratingBackend.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react'
import WelcomeTemplate from './onboard/WelcomeTemplate'

const MigratingBackend: React.FC<{}> = () => (
<WelcomeTemplate title='Migrating the Qri backend' subtitle='This might take a minute...' loading/>
)

export default MigratingBackend
6 changes: 6 additions & 0 deletions app/components/MigrationFailed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react'
import ReportCrashAndReload from './ReportCrashAndReload'

const MigrationFailed: React.FC<{}> = () => <ReportCrashAndReload title='The Qri backend migration failed!'/>

export default MigrationFailed
90 changes: 90 additions & 0 deletions app/components/ReportCrashAndReload.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React, { useState } from 'react'
import Button from './chrome/Button'
import ExternalLink from './ExternalLink'
import WelcomeTemplate from './onboard/WelcomeTemplate'
import { FetchOptions } from '../store/api'
import versions from '../../version'
import { CRASH_REPORTER_URL } from '../constants'

interface ReportCrashAndReloadProps extends ErrorProps {
title: string
}

export interface ErrorProps {
error?: Error
errorInfo?: React.ErrorInfo
}

const ReportCrashAndReload: React.FC<ReportCrashAndReloadProps> = ({ title, error, errorInfo }) => {
const [sendingReport, setSendingReport] = useState(false)
const [sentReport, setSentReport] = useState(false)

const handleSendCrashReport = async () => {
setSendingReport(true)
try {
console.log('sending report')
await postCrashReport(error, errorInfo)
setSendingReport(true)
setSentReport(true)
} catch (e) {
console.log(e)
setSendingReport(false)
}
}

const handleReload = () => {
window.location.hash = '/'
window.location.reload()
}

return (
<WelcomeTemplate title={title}>
<div className="reporting actions">
<p>Apologies, Qri desktop encountered an error. Send us a crash report!</p>
{sentReport
? <p>Thanks!</p>
: <Button
id="send-crash-report"
text="Send Crash Report"
color="primary"
loading={sendingReport}
onClick={handleSendCrashReport}
/>}
</div>
<br />
<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={handleReload}
/>
</div>
</WelcomeTemplate>
)
}

// TODO (b5) - bring this back in the near future for fetching home feed
const postCrashReport = async (error: ErrorProps['error'], errorInfo: ErrorProps['errorInfo']): Promise<any> => {
const options: FetchOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
app: 'desktop',
version: versions.desktopVersion,
platform: navigator.platform,
error: error?.toString(),
info: errorInfo?.componentStack
})
}

console.log(CRASH_REPORTER_URL)
const r = await fetch(CRASH_REPORTER_URL, options)
const res = await r.json()
return res
}

export default ReportCrashAndReload
33 changes: 33 additions & 0 deletions stories/AppLaunch.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react'

import IncompatibleBackend from '../app/components/IncompatibleBackend'
import MigratingBackend from '../app/components/MigratingBackend'
import MigrationFailed from '../app/components/MigrationFailed'

export default {
title: 'App Launch',
parameters: {
notes: 'List of migration components conditionally rendered on app launch'
}
}

export const incompatibleBackend = () => (<IncompatibleBackend incompatibleVersion='0.0.1'/>)

incompatibleBackend.story = {
name: 'Incompatible Backend',
parameters: { note: 'Screen when user is running an incompatible backend qri version' }
}

export const migratingBackend = () => <MigratingBackend />

migratingBackend.story = {
name: 'Migrating Backend',
parameters: { note: 'Screen when backend is in the process of migrating' }
}

export const migrationFailed = () => <MigrationFailed />

migrationFailed.story = {
name: 'Migration Failed',
parameters: { note: 'Screen when backend migration has failed' }
}
27 changes: 27 additions & 0 deletions stories/Error.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react'

import ReportCrashAndReload from '../app/components/ReportCrashAndReload'

export default {
title: 'Error',
parameters: {
notes: 'Application error handling'
}
}

const reportCrashAndReloadProps = {
title: 'Storybook test has crashed',
error: new Error('Test error for Storybook!'),
errorInfo: {
componentStack: `in ComponentThatThrows (created by App)
in ErrorBoundary (created by App)
in div (created by App)
in App` }
}

export const reportCrashAndReload = () => (<ReportCrashAndReload {...reportCrashAndReloadProps}/>)

reportCrashAndReload.story = {
name: 'Report Crash and Reload',
parameters: { note: 'Screen when app throws an error and is caught. Prompts error report, issue and reload CTAs' }
}

0 comments on commit 866a9e4

Please sign in to comment.