@@ -6,6 +6,27 @@ export interface ScalingGroupItem {
66 name : string ;
77}
88
9+ /**
10+ * Thrown when the vfolder host info fetch (`/folders/_/hosts`) inside
11+ * `useProjectResourceGroups` fails. Tagging the failure lets callers wrap
12+ * the hook with a dedicated error boundary that can distinguish this
13+ * specific case from unrelated render errors and surface a targeted
14+ * message (and discriminate it from the parallel scaling-groups fetch
15+ * failure, which is re-thrown as-is so an outer boundary handles it).
16+ */
17+ export class StorageHostFetchError extends Error {
18+ readonly originalError : unknown ;
19+ constructor ( originalError : unknown ) {
20+ super (
21+ originalError instanceof Error
22+ ? originalError . message
23+ : 'Failed to fetch storage host information.' ,
24+ ) ;
25+ this . name = 'StorageHostFetchError' ;
26+ this . originalError = originalError ;
27+ }
28+ }
29+
930interface VolumeInfo {
1031 backend : string ;
1132 capabilities : string [ ] ;
@@ -60,15 +81,21 @@ export const useProjectResourceGroups = (
6081
6182 const { data } = useSuspenseTanQuery < ProjectResourceGroupsQueryResult > ( {
6283 queryKey : [ 'ResourceGroupSelectQuery' , projectName ] ,
63- queryFn : ( ) => {
84+ queryFn : async ( ) => {
6485 // Short-circuit when there is no project context yet — avoids hitting
6586 // `/scaling-groups?group=` and `/folders/_/hosts` with an unscoped query.
6687 if ( ! projectName ) {
67- return Promise . resolve ( null ) ;
88+ return null ;
6889 }
6990 const search = new URLSearchParams ( ) ;
7091 search . set ( 'group' , projectName ) ;
71- return Promise . all ( [
92+ // Run both fetches concurrently but discriminate failures: a host-info
93+ // failure is tagged with `StorageHostFetchError` so a dedicated boundary
94+ // can surface it, while a scaling-groups failure is re-thrown as-is and
95+ // bubbles up to the generic error boundary. Host-info failure takes
96+ // precedence when both fail because SFTP filtering depends on it and
97+ // the result is otherwise unusable.
98+ const [ scalingGroupsResult , hostsResult ] = await Promise . allSettled ( [
7299 baiRequestWithPromise ( {
73100 method : 'GET' ,
74101 url : `/scaling-groups?${ search . toString ( ) } ` ,
@@ -78,6 +105,16 @@ export const useProjectResourceGroups = (
78105 url : `/folders/_/hosts` ,
79106 } ) ,
80107 ] ) ;
108+ if ( hostsResult . status === 'rejected' ) {
109+ throw new StorageHostFetchError ( hostsResult . reason ) ;
110+ }
111+ if ( scalingGroupsResult . status === 'rejected' ) {
112+ throw scalingGroupsResult . reason ;
113+ }
114+ return [
115+ scalingGroupsResult . value ,
116+ hostsResult . value ,
117+ ] as unknown as NonNullable < ProjectResourceGroupsQueryResult > ;
81118 } ,
82119 staleTime : 1000 * 60 * 5 , // Cache for 5 minutes
83120 } ) ;
0 commit comments