-
Notifications
You must be signed in to change notification settings - Fork 382
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add error boundary to the Settings page #6320
Conversation
@westonruter In the current iteration I haven't included the "submit a support topic" button you asked for. Should this button create a ticker on wordpress.org? If yes, is it possible to create a ticket with a predefined topic/content? I briefly searched for something like this but couldn't find anything relevant. |
Plugin builds for 555324b are ready 🛎️!
|
<ErrorBoundary exitLink={ FINISH_LINK } fullScreen={ true }> | ||
|
||
<ErrorBoundary | ||
exitLink={ { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The prop type for exitLink
is a string
, but you're passing an object here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. In fact, after addressing your feedback on the finishLink
prop, this becomes outdated.
@@ -19,27 +19,38 @@ import './style.css'; | |||
* | |||
* @param {Object} props Component props. | |||
* @param {Object} props.error Error object containing a message string. | |||
* @param {string} props.finishLink The link to return to the admin. | |||
* @param {Object} props.finishLink The link to return to the admin. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accepting an object for a prop named finishLink
seems like a misnomer. Accepting the link and label as separate props seems more appropriate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. I've broken down the finishLink
object into 2 separate props in 81e29d8.
Maybe we could add a button to copy the error stack-trace as well, similar to the one in the block editor. |
@pierlon I think it's a good idea. I've migrated the (now deprecated) Here's how it looks: |
PHPUnit failures to be fixed by #6366. |
You can rebase now against |
677defb
to
983e5e1
Compare
Great work @delawski. One thing left here is to catch and handle any errors that may occur outside of the error boundary, like in #6230 where an incorrect version of a dependency was loaded, causing the settings page components not to render. I think we can accomplish this by adding an So maybe something like this: diff --git a/assets/src/settings-page/index.js b/assets/src/settings-page/index.js
--- a/assets/src/settings-page/index.js (revision 983e5e106759bb2aecd845e958494c299c850a31)
+++ b/assets/src/settings-page/index.js (date 1623357057438)
@@ -217,6 +217,8 @@
const root = document.getElementById( 'amp-settings-root' );
if ( root ) {
+ global.addEventListener( 'error', ( error ) => wp.element.render( <ErrorScreen error={ error } /> ), root );
+
render( (
<Providers>
<Root appRoot={ root } />
I'll see if this actually works... |
Maybe this event listener should be removed once the app has successfully initialized. We wouldn't want errors caused by other random JS on the page to prevent the settings screen from rendering. Or rather, there could be a 5 second timeout after which if the app does not initialize, then allow whatever error(s) were caught by the |
Those are some really good points. I'll dig into that 👍 |
As suggested by @pierlon, in dea9fd4 we handle errors that are thrown outside of the error boundary by listening to the global The event listener is set up before the app is rendered. In the
@westonruter It seems that we can filter out "our own" errors by examining the
I think that checking the In order to build and test out a global error handler as suggested in the previous comments, I first had to simulate an error similar the one in #6230 (a white screen of death). Check out my test stepsFirst, I've added a dummy function to the compiled package export { sprintf } from "./sprintf";
export * from "./create-i18n";
+ export function Foo() {
+ return null;
+ };
export { default as defaultI18n, setLocaleData, resetLocaleData, getLocaleData, subscribe, __, _x, _n, _nx, isRTL, hasTranslation } from "./default-i18n";
//# sourceMappingURL=index.d.ts.map Thanks to that, webpack had no issues with importing that dummy function to our entry points: - import { __ } from '@wordpress/i18n';
+ import { __, Foo } from '@wordpress/i18n'; Then, I've added a call to that function before the domReady( () => {
const root = document.getElementById( 'amp-settings-root' );
+ Foo();
if ( root ) {
render( (
<Providers>
<Root appRoot={ root } />
</Providers>
), root );
}
} ); Since in the WP admin the Core version of the |
|
||
const errorHandler = ( error ) => { | ||
// Handle only own errors. | ||
if ( error?.filename?.match( /amp-onboarding-wizard(\.min)?\.js/ ) ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be more appropriate since we're not making use of the matches:
if ( error?.filename?.match( /amp-onboarding-wizard(\.min)?\.js/ ) ) { | |
if ( /amp-onboarding-wizard(\.min)?\.js/.test( error?.filename ) ) { |
} | ||
|
||
const errorHandler = ( error ) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually the ErrorEvent
, not the error
object, so we could use a more appropriate name:
const errorHandler = ( error ) => { | |
const errorHandler = ( event ) => { |
const errorHandler = ( error ) => { | ||
// Handle only own errors. | ||
if ( error?.filename?.match( /amp-onboarding-wizard(\.min)?\.js/ ) ) { | ||
render( <ErrorScreen error={ error } />, root ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error event is passed here, instead of the error object, so this should actually be:
render( <ErrorScreen error={ error } />, root ); | |
render( <ErrorScreen error={ error.error } />, root ); |
<SetupWizard closeLink={ CLOSE_LINK } finishLink={ FINISH_LINK } appRoot={ root } /> | ||
</Providers>, | ||
root, | ||
() => global.removeEventListener( 'error', errorHandler ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to the React docs the callback is executed after the component is rendered or updated, which means the error boundary will not be handling errors until all renders are complete. Maybe we could remove the listener in the Providers
component?
} | ||
|
||
const errorHandler = ( error ) => { | ||
// Handle only own errors. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comments above apply for this file as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made my suggestions in fa13a7f, feel free to modify if needed. That said, I think this is ready to ship .
Result after 2a4b083: |
@pierlon Thank you for the feedback and for addressing it right away. I've updated the |
I just tried this without the fix from #6396:
It seems like in Firefox the error message is not included in the details. |
Maybe the JS sourcemap isn't being detected properly in Firefox? |
I'm talking about the contents of the |
What about something like: diff --git a/assets/src/components/error-screen/index.js b/assets/src/components/error-screen/index.js
--- a/assets/src/components/error-screen/index.js (revision 2a4b083eb11bdd802ede455a0fcda1a160ad3a5f)
+++ b/assets/src/components/error-screen/index.js (date 1623968168232)
@@ -27,6 +27,7 @@
*/
export function ErrorScreen( { error, finishLinkLabel, finishLinkUrl, title } ) {
const [ hasCopied, setHasCopied ] = useState( false );
+ const { message, stack } = error;
return (
<div className="error-screen-container">
@@ -37,21 +38,21 @@
{ /* dangerouslySetInnerHTML reason: WordPress sometimes sends back HTML in error messages. */ }
<p dangerouslySetInnerHTML={ {
- __html: error.message || __( 'There was an error loading the page.', 'amp' ),
+ __html: message || __( 'There was an error loading the page.', 'amp' ),
} } />
- { error?.stack && (
+ { stack && (
<details>
<summary>
{ __( 'Details', 'amp' ) }
</summary>
<pre>
- { error.stack }
+ { stack }
</pre>
<ClipboardButton
isSmall={ true }
isSecondary={ true }
- text={ error.stack }
+ text={ JSON.stringify( { message, stack }, null, 2 ) }
onCopy={ () => setHasCopied( true ) }
onFinishCopy={ () => setHasCopied( false ) }
> Which would copy the error message and stack. |
Yeah, that looks good. |
Done in 94d183e. |
6e5602e
to
32f57dc
Compare
(Rebased against |
@delawski @pierlon I made a few more additional improvements:
Successful LoadNotice how there are two spinners: a pre-loading spinner and a loading spinner. amp-settings-load-success.movFailure due to JS disabledFailure with caught errorHere I tested by throwing an error at the top of amp-settings-load-failure-error-caught.movFailure with uncaught errorHere I tested by throwing an error at the top of amp-settings-load-failure-error-uncaught.mov |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Codecov Report
@@ Coverage Diff @@
## develop #6320 +/- ##
=============================================
+ Coverage 75.26% 75.45% +0.18%
- Complexity 5899 5900 +1
=============================================
Files 187 237 +50
Lines 17076 17867 +791
=============================================
+ Hits 12852 13481 +629
- Misses 4224 4386 +162
Flags with carried forward coverage won't be shown. Click here to find out more.
|
@westonruter Thank you for the improvements. They all look good to me. The only thing that I changed in 26c1206 is the loading spinner. Right now it looks exactly the same as in the React app so that it's hard to notice when they get swapped: Screen+Recording+2021-06-23+at+05.04.36+PM.mp4 |
|
||
#amp-pre-loading-spinner { | ||
visibility: visible; | ||
animation: amp-wp-hide-element 30s steps(1, end) 0s 1 normal both; /* This could probably reuse amp-wp-show-element if reversed. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how a slow connection would come into play here, as the page would have already loaded and the necessary JS would have either executed or not by then. A 30s wait time seems to be overkill here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was testing with Fast 3G and the JS files were taking over 30 seconds to load. Granted, I was testing with the development versions, so it was 8MB (😱). I'll test again with the production version instead and adjust the timeout down.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With Fast 3G, the settings page takes 10 seconds to load. With Slow 3G it takes 36 seconds to load. This is with the production build, where amp-settings.js
is 875KB. So I think 30 seconds is not actually far off.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see. That makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What an ingenious solution!
Co-authored-by: Pierre Gordon <16200219+pierlon@users.noreply.github.com>
Nice improvement. I do notice the second spinner shifts a tiny bit when it loads, but otherwise it's better as you have it now since the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks ready to ship. Great work @delawski and @westonruter!
|
||
#amp-pre-loading-spinner { | ||
visibility: visible; | ||
animation: amp-wp-hide-element 30s steps(1, end) 0s 1 normal both; /* This could probably reuse amp-wp-show-element if reversed. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What an ingenious solution!
Co-authored-by: Piotr Delawski <piotr.delawski@gmail.com> Co-authored-by: Pierre Gordon <16200219+pierlon@users.noreply.github.com>
Summary
This PR adds the error boundary and error screen components from the Onboarding Wizard to the AMP Settings page. The error screen is also extended. It now shows not only the error message but also the stack information. Some cosmetic updates to the look and feel of the error message have been done as well.
Fixes #6230
Checklist