Skip to content

Commit f7a4277

Browse files
katinthehatsiteKateryna Kodonenko
andauthored
Studio: Handle error when reading backup directories from /ls endpoint (#2366)
* Handle remote errors * Cleanup * Fix unintended changes * Use addError boolean * Clean up code duplication --------- Co-authored-by: Kateryna Kodonenko <kateryna@automattic.com>
1 parent 6ab139b commit f7a4277

File tree

4 files changed

+57
-14
lines changed

4 files changed

+57
-14
lines changed

src/components/tree-view.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export type TreeNode = {
1717
children?: TreeNode[];
1818
type?: TreeNodeType;
1919
loading?: boolean;
20+
hasError?: boolean;
2021
pathId?: string;
2122
path?: string;
2223
};
@@ -97,7 +98,7 @@ const TreeItem = ( {
9798
isLast?: boolean;
9899
disabled?: boolean;
99100
renderAfterChildren?: ( nodeId: string ) => React.ReactNode;
100-
renderEmptyContent?: ( nodeId: string ) => React.ReactNode;
101+
renderEmptyContent?: ( nodeId: string, node: TreeNode ) => React.ReactNode;
101102
} ) => {
102103
const { __ } = useI18n();
103104
const isFirstLevel = level === 1;
@@ -168,7 +169,7 @@ const TreeItem = ( {
168169
>
169170
{ node.children.length === 0 ? (
170171
renderEmptyContent ? (
171-
renderEmptyContent( node.id )
172+
renderEmptyContent( node.id, node )
172173
) : (
173174
<div className="text-gray-500 italic" aria-label={ __( 'Empty folder' ) }>
174175
{ __( 'Empty' ) }
@@ -202,7 +203,7 @@ export type TreeViewProps = {
202203
onExpand?: ( node: TreeNode ) => Promise< void >;
203204
disabled?: boolean;
204205
renderAfterChildren?: ( nodeId: string ) => React.ReactNode;
205-
renderEmptyContent?: ( nodeId: string ) => React.ReactNode;
206+
renderEmptyContent?: ( nodeId: string, node: TreeNode ) => React.ReactNode;
206207
};
207208

208209
export const TreeView = ( {

src/modules/sync/components/sync-dialog.tsx

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ const useDynamicTreeState = (
5757
isLoading: isLoadingLocalFileTree,
5858
error: localFileTreeError,
5959
} = useLocalFileTree();
60+
const [ remoteFileTreeError, setRemoteFileTreeError ] = useState< Error | null >( null );
6061

6162
// If the site was just created and if there is no rewind_id yet,
6263
// then all options are pre-checked to allow only a full sync
@@ -73,6 +74,7 @@ const useDynamicTreeState = (
7374
if ( type === 'pull' && remoteSiteId ) {
7475
let isCancelled = false;
7576
const loadRemoteTree = async () => {
77+
setRemoteFileTreeError( null );
7678
try {
7779
if ( rewindId ) {
7880
const remoteTree = await fetchChildren( remoteSiteId, rewindId, '/wp-content/', false );
@@ -83,7 +85,10 @@ const useDynamicTreeState = (
8385
}
8486
}
8587
} catch ( error ) {
86-
console.error( 'Failed to load remote file tree:', error );
88+
const errorObj = error instanceof Error ? error : new Error( 'Unknown error occurred' );
89+
if ( ! isCancelled ) {
90+
setRemoteFileTreeError( errorObj );
91+
}
8792
}
8893
};
8994
void loadRemoteTree();
@@ -125,6 +130,7 @@ const useDynamicTreeState = (
125130
isErrorRewindId,
126131
isLoadingLocalFileTree,
127132
localFileTreeError,
133+
remoteFileTreeError,
128134
};
129135
};
130136

@@ -162,6 +168,7 @@ export function SyncDialog( {
162168
isErrorRewindId,
163169
isLoadingLocalFileTree,
164170
localFileTreeError,
171+
remoteFileTreeError,
165172
} = useDynamicTreeState( type, localSite.id, remoteSite.id, setTreeState );
166173

167174
const [ wpVersion ] = useGetWpVersion( localSite );
@@ -225,8 +232,23 @@ export function SyncDialog( {
225232
}
226233

227234
if ( type === 'pull' && rewindId && node.path && node.children?.length === 0 ) {
228-
const children = await fetchChildren( remoteSite.id, rewindId, node.path, node.checked );
229-
setTreeState( ( prev ) => updateNodeById( prev, node.id, { children } ) );
235+
// Set loading state for the node
236+
setTreeState( ( prev ) => updateNodeById( prev, node.id, { loading: true } ) );
237+
238+
try {
239+
const children = await fetchChildren( remoteSite.id, rewindId, node.path, node.checked );
240+
setTreeState( ( prev ) =>
241+
updateNodeById( prev, node.id, { children, loading: false, hasError: false } )
242+
);
243+
} catch ( error ) {
244+
setTreeState( ( prev ) =>
245+
updateNodeById( prev, node.id, {
246+
children: [],
247+
loading: false,
248+
hasError: true,
249+
} )
250+
);
251+
}
230252
}
231253
// For push operations, children are already loaded - no async fetching needed
232254
},
@@ -361,7 +383,7 @@ export function SyncDialog( {
361383
}
362384
return null;
363385
} }
364-
renderEmptyContent={ ( nodeId ) => {
386+
renderEmptyContent={ ( nodeId, node ) => {
365387
if ( nodeId === 'wp-content' && type === 'push' && localFileTreeError ) {
366388
return (
367389
<div className="text-gray-500 italic">
@@ -371,6 +393,18 @@ export function SyncDialog( {
371393
</div>
372394
);
373395
}
396+
if (
397+
( nodeId === 'wp-content' && type === 'pull' && remoteFileTreeError ) ||
398+
node.hasError
399+
) {
400+
return (
401+
<div className="text-gray-500 italic">
402+
{ __(
403+
'Error retrieving remote files and directories. Please close and reopen this dialog to try again.'
404+
) }
405+
</div>
406+
);
407+
}
374408
return (
375409
<div className="text-gray-500 italic" aria-label={ __( 'Empty folder' ) }>
376410
{ __( 'Empty' ) }

src/stores/sync/sync-api.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,18 @@ export const fetchRemoteFileTree = createAsyncThunk(
110110
path,
111111
};
112112

113-
const rawResponse = await client.req.post( {
114-
path: `/sites/${ remoteSiteId }/rewind/backup/ls`,
115-
apiNamespace: 'wpcom/v2',
116-
body: requestBody,
117-
} );
113+
let rawResponse;
114+
try {
115+
rawResponse = await client.req.post( {
116+
path: `/sites/${ remoteSiteId }/rewind/backup/ls`,
117+
apiNamespace: 'wpcom/v2',
118+
body: requestBody,
119+
} );
120+
} catch ( err ) {
121+
const errorMessage =
122+
err instanceof Error ? err.message : 'Network error while fetching remote file tree';
123+
throw new Error( errorMessage );
124+
}
118125

119126
const validationResult = BackupLsResponseSchema.shape.body.safeParse( rawResponse );
120127
if ( ! validationResult.success ) {

src/stores/sync/sync-hooks.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,9 @@ export function useRemoteFileTree() {
6060

6161
return result.children;
6262
} catch ( err ) {
63-
console.error( 'Failed to fetch remote file tree:', err );
64-
return [];
63+
const errorMessage =
64+
err instanceof Error ? err.message : 'Failed to fetch remote file tree';
65+
throw new Error( errorMessage );
6566
}
6667
},
6768
[ client, dispatch ]

0 commit comments

Comments
 (0)