From 866a9e4c37473b4a35f039a7ead474705ef94bb4 Mon Sep 17 00:00:00 2001 From: Alicia Underhill <31083146+uhLeeshUh@users.noreply.github.com> Date: Wed, 15 Jul 2020 16:01:59 -0600 Subject: [PATCH] feat(migration components): add migration components to conditionally 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 --- app/components/ErrorHandler.tsx | 99 ++----------------------- app/components/IncompatibleBackend.tsx | 20 +++++ app/components/MigratingBackend.tsx | 8 ++ app/components/MigrationFailed.tsx | 6 ++ app/components/ReportCrashAndReload.tsx | 90 ++++++++++++++++++++++ stories/AppLaunch.stories.tsx | 33 +++++++++ stories/Error.stories.tsx | 27 +++++++ 7 files changed, 192 insertions(+), 91 deletions(-) create mode 100644 app/components/IncompatibleBackend.tsx create mode 100644 app/components/MigratingBackend.tsx create mode 100644 app/components/MigrationFailed.tsx create mode 100644 app/components/ReportCrashAndReload.tsx create mode 100644 stories/AppLaunch.stories.tsx create mode 100644 stories/Error.stories.tsx diff --git a/app/components/ErrorHandler.tsx b/app/components/ErrorHandler.tsx index ba31aaf4..60d830e9 100644 --- a/app/components/ErrorHandler.tsx +++ b/app/components/ErrorHandler.tsx @@ -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 @@ -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 ( -
-
-

Dang. It broke.

-

Apologies, Qri desktop encountered an error. Send us a crash report!

-
- {sentReport - ?

Thanks!

- :
-
-

If you have additional info or questions, feel free to file an issue describing where things went wrong. The more detail, the better. Reload to get back to Qri.

-
-
-
+ ) } } - -// TODO (b5) - bring this back in the near future for fetching home feed -async function postCrashReport (err, errInfo): Promise { - 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 -} diff --git a/app/components/IncompatibleBackend.tsx b/app/components/IncompatibleBackend.tsx new file mode 100644 index 00000000..9c7681ef --- /dev/null +++ b/app/components/IncompatibleBackend.tsx @@ -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 = ({ incompatibleVersion }) => ( + +

{`The lowest supported backend version is v${lowestCompatibleBackend}, but you are running on v${incompatibleVersion}.`}

+

Please file an issue here.

+
+) + +export default IncompatibleBackend diff --git a/app/components/MigratingBackend.tsx b/app/components/MigratingBackend.tsx new file mode 100644 index 00000000..9e680a60 --- /dev/null +++ b/app/components/MigratingBackend.tsx @@ -0,0 +1,8 @@ +import React from 'react' +import WelcomeTemplate from './onboard/WelcomeTemplate' + +const MigratingBackend: React.FC<{}> = () => ( + +) + +export default MigratingBackend diff --git a/app/components/MigrationFailed.tsx b/app/components/MigrationFailed.tsx new file mode 100644 index 00000000..efe9fccf --- /dev/null +++ b/app/components/MigrationFailed.tsx @@ -0,0 +1,6 @@ +import React from 'react' +import ReportCrashAndReload from './ReportCrashAndReload' + +const MigrationFailed: React.FC<{}> = () => + +export default MigrationFailed diff --git a/app/components/ReportCrashAndReload.tsx b/app/components/ReportCrashAndReload.tsx new file mode 100644 index 00000000..df03109b --- /dev/null +++ b/app/components/ReportCrashAndReload.tsx @@ -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 = ({ 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 ( + +
+

Apologies, Qri desktop encountered an error. Send us a crash report!

+ {sentReport + ?

Thanks!

+ :
+
+
+

If you have additional info or questions, feel free to file an issue describing where things went wrong. The more detail, the better. Reload to get back to Qri.

+
+
+ ) +} + +// TODO (b5) - bring this back in the near future for fetching home feed +const postCrashReport = async (error: ErrorProps['error'], errorInfo: ErrorProps['errorInfo']): Promise => { + 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 diff --git a/stories/AppLaunch.stories.tsx b/stories/AppLaunch.stories.tsx new file mode 100644 index 00000000..12a0172d --- /dev/null +++ b/stories/AppLaunch.stories.tsx @@ -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.story = { + name: 'Incompatible Backend', + parameters: { note: 'Screen when user is running an incompatible backend qri version' } +} + +export const migratingBackend = () => + +migratingBackend.story = { + name: 'Migrating Backend', + parameters: { note: 'Screen when backend is in the process of migrating' } +} + +export const migrationFailed = () => + +migrationFailed.story = { + name: 'Migration Failed', + parameters: { note: 'Screen when backend migration has failed' } +} \ No newline at end of file diff --git a/stories/Error.stories.tsx b/stories/Error.stories.tsx new file mode 100644 index 00000000..9d5c2d38 --- /dev/null +++ b/stories/Error.stories.tsx @@ -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.story = { + name: 'Report Crash and Reload', + parameters: { note: 'Screen when app throws an error and is caught. Prompts error report, issue and reload CTAs' } +} \ No newline at end of file