Skip to content

Commit ac6318b

Browse files
p-jacksongabrielcaires
authored andcommitted
Detect errors in data layer and handle them when routing (#104920)
1 parent 1fc214f commit ac6318b

File tree

4 files changed

+87
-4
lines changed

4 files changed

+87
-4
lines changed

client/dashboard/app/router.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
createRootRoute,
55
redirect,
66
createLazyRoute,
7+
lazyRouteComponent,
78
} from '@tanstack/react-router';
89
import { HostingFeatures } from '../data/constants';
910
import { fetchTwoStep } from '../data/me';
@@ -111,6 +112,7 @@ const siteRoute = createRoute( {
111112
await queryClient.ensureQueryData( siteByIdQuery( otherEnvironmentSiteId ) );
112113
}
113114
},
115+
errorComponent: lazyRouteComponent( () => import( '../sites/site/error' ) ),
114116
} ).lazy( () =>
115117
import( '../sites/site' ).then( ( d ) =>
116118
createLazyRoute( 'site' )( {

client/dashboard/data/error.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
export class DashboardDataError extends Error {
2+
constructor(
3+
public code: string,
4+
cause?: unknown
5+
) {
6+
const message = cause instanceof Error ? cause.message : `Error: ${ cause }`;
7+
super( message, { cause } );
8+
this.name = 'DashboardDataError';
9+
10+
// Fix prototype chain (important for instanceof to work reliably)
11+
Object.setPrototypeOf( this, new.target.prototype );
12+
}
13+
}
14+
15+
interface WPError extends Error {
16+
status: number;
17+
statusCode: number;
18+
error?: string;
19+
[ key: string ]: unknown;
20+
}
21+
22+
export function isWpError( error: unknown ): error is WPError {
23+
return (
24+
error instanceof Error &&
25+
'status' in error &&
26+
typeof error.status === 'number' &&
27+
'statusCode' in error &&
28+
typeof error.statusCode === 'number' &&
29+
( 'error' in error ? typeof error.error === 'string' : true )
30+
);
31+
}

client/dashboard/data/site.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import wpcom from 'calypso/lib/wp';
2+
import { isWpError, DashboardDataError } from './error';
23

34
export const SITE_FIELDS = [
45
'ID',
@@ -112,10 +113,17 @@ export interface Site {
112113
}
113114

114115
export async function fetchSite( siteIdOrSlug: number | string ): Promise< Site > {
115-
return await wpcom.req.get(
116-
{ path: `/sites/${ siteIdOrSlug }` },
117-
{ fields: JOINED_SITE_FIELDS, options: JOINED_SITE_OPTIONS }
118-
);
116+
try {
117+
return await wpcom.req.get(
118+
{ path: `/sites/${ siteIdOrSlug }` },
119+
{ fields: JOINED_SITE_FIELDS, options: JOINED_SITE_OPTIONS }
120+
);
121+
} catch ( error ) {
122+
if ( isWpError( error ) && error.error === 'parse_error' ) {
123+
throw new DashboardDataError( 'inaccessible_jetpack', error );
124+
}
125+
throw error;
126+
}
119127
}
120128

121129
export async function deleteSite( siteId: number ) {

client/dashboard/sites/site/error.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Notice } from '@wordpress/components';
2+
import { __ } from '@wordpress/i18n';
3+
import UnknownError from '../../app/500';
4+
import { siteRoute } from '../../app/router';
5+
import { PageHeader } from '../../components/page-header';
6+
import PageLayout from '../../components/page-layout';
7+
import RouterLinkButton from '../../components/router-link-button';
8+
import { DashboardDataError } from '../../data/error';
9+
10+
export default function Error( { error }: { error: Error } ) {
11+
switch ( error instanceof DashboardDataError && error.code ) {
12+
case 'inaccessible_jetpack':
13+
return <InaccessibleJetpackError error={ error } />;
14+
default:
15+
return <UnknownError error={ error } />;
16+
}
17+
}
18+
19+
function InaccessibleJetpackError( { error }: { error: Error } ) {
20+
const { siteSlug } = siteRoute.useParams();
21+
22+
return (
23+
<PageLayout
24+
header={
25+
<PageHeader
26+
title={ siteSlug }
27+
description={ __( 'Your Jetpack site can not be reached at this time.' ) }
28+
actions={
29+
<RouterLinkButton to="/sites" variant="primary" __next40pxDefaultSize>
30+
{ __( 'Go to Sites' ) }
31+
</RouterLinkButton>
32+
}
33+
/>
34+
}
35+
notices={
36+
<Notice status="error" isDismissible={ false }>
37+
{ error.message }
38+
</Notice>
39+
}
40+
></PageLayout>
41+
);
42+
}

0 commit comments

Comments
 (0)