Skip to content

Commit 16b7a56

Browse files
committed
fix(FR-1384): handle backend API failures gracefully with Promise.allSettled (#4165)
Resolves [FR-1384](https://lablup.atlassian.net/browse/FR-1384) ## Problem When either `scalingGroup.list` or `vfolder.list_hosts` API calls fail, the entire Promise.all chain throws an error, preventing the UI from displaying any available data and potentially causing the entire page to crash. ## Solution - Replace `Promise.all` with `Promise.allSettled` to handle failures gracefully - Return `undefined` for failed API calls while preserving successful data - Update dependent components to handle undefined values appropriately - Rename variables for clarity (`allSftpScalingGroups` → `sftpScalingGroups`, `resourceGroups` → `nonSftpScalingGroups`) ## Impact This change improves the resilience of the UI when backend services are partially unavailable, allowing users to continue working with available resources rather than experiencing a complete failure. ## Changed Files - `react/src/hooks/useCurrentProject.tsx` - Core fix for Promise handling - `react/src/components/AgentSummaryList.tsx` - Updated variable references - `react/src/components/ResourceGroupSelectForCurrentProject.tsx` - Updated variable references - `react/src/hooks/useResourceLimitAndRemaining.tsx` - Updated variable references - `react/src/pages/ComputeSessionListPage.tsx` - Added undefined check for resource group [FR-1384]: https://lablup.atlassian.net/browse/FR-1384?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
1 parent 718ce52 commit 16b7a56

File tree

4 files changed

+38
-23
lines changed

4 files changed

+38
-23
lines changed

react/src/components/AgentSummaryList.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ const AgentSummaryList: React.FC<AgentSummaryListProps> = ({
8080
startRefreshTransition(() => {
8181
updateFetchKey();
8282
});
83-
const { allSftpScalingGroups } = useResourceGroupsForCurrentProject();
83+
const { sftpResourceGroups } = useResourceGroupsForCurrentProject();
8484

8585
const { agent_summary_list } = useLazyLoadQuery<AgentSummaryListQuery>(
8686
graphql`
@@ -127,7 +127,7 @@ const AgentSummaryList: React.FC<AgentSummaryListProps> = ({
127127
// Hide sFTP upload agents
128128
const filteredAgentSummaryList = _.filter(
129129
agent_summary_list?.items,
130-
(item) => !_.includes(allSftpScalingGroups, item?.scaling_group),
130+
(item) => !_.includes(sftpResourceGroups, item?.scaling_group),
131131
);
132132

133133
const columns: ColumnsType<AgentSummary> = [

react/src/components/ResourceGroupSelectForCurrentProject.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const ResourceGroupSelectForCurrentProject: React.FC<
5050
);
5151

5252
const [isPendingLoading, startLoadingTransition] = useTransition();
53-
const { resourceGroups } = useResourceGroupsForCurrentProject();
53+
const { nonSftpResourceGroups } = useResourceGroupsForCurrentProject();
5454
const [optimisticValue, setOptimisticValue] = useState(currentResourceGroup);
5555

5656
const searchProps: Pick<
@@ -68,7 +68,7 @@ const ResourceGroupSelectForCurrentProject: React.FC<
6868
<BAISelect
6969
defaultActiveFirstOption
7070
loading={isPendingLoading}
71-
options={_.map(resourceGroups, (resourceGroup) => {
71+
options={_.map(nonSftpResourceGroups, (resourceGroup) => {
7272
return { value: resourceGroup.name, label: resourceGroup.name };
7373
})}
7474
optionRender={(option) => {

react/src/hooks/useCurrentProject.tsx

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -47,22 +47,25 @@ const previousSelectedResourceGroupNameAtom = atom<string | null>(null);
4747

4848
export const useCurrentResourceGroupValue = () => {
4949
useSuspendedBackendaiClient();
50-
const { resourceGroups } = useResourceGroupsForCurrentProject();
50+
const { nonSftpResourceGroups } = useResourceGroupsForCurrentProject();
5151
const [prevSelectedRGName, setPrevSelectedRGName] = useAtom(
5252
previousSelectedResourceGroupNameAtom,
5353
);
5454

5555
let nextResourceGroupName: string | null = null;
56-
if (resourceGroups.length === 0) {
56+
if (
57+
nonSftpResourceGroups === undefined ||
58+
nonSftpResourceGroups.length === 0
59+
) {
5760
nextResourceGroupName = null;
5861
} else if (
59-
_.some(resourceGroups, (item) => item.name === prevSelectedRGName)
62+
_.some(nonSftpResourceGroups, (item) => item.name === prevSelectedRGName)
6063
) {
6164
nextResourceGroupName = prevSelectedRGName;
6265
} else {
6366
const autoSelectedResourceGroup =
6467
// _.find(resourceGroups, (item) => item.name === 'default') ||
65-
resourceGroups[0];
68+
nonSftpResourceGroups[0];
6669
nextResourceGroupName = autoSelectedResourceGroup.name;
6770
}
6871

@@ -91,30 +94,42 @@ export const useCurrentResourceGroupState = () => {
9194
const resourceGroupsForCurrentProjectAtom = atom(async (get) => {
9295
// NOTE: cannot use hook inside atom
9396
const currentProject = get(currentProjectAtom);
94-
const [resourceGroups, vhostInfo] = await Promise.all([
97+
const [resourceGroupsResult, vhostInfoResult] = await Promise.allSettled([
9598
// @ts-ignore
9699
globalThis.backendaiclient.scalingGroup.list(
97100
currentProject.name,
98-
) as ScalingGroupsResponse,
101+
) as Promise<ScalingGroupsResponse>,
99102
// @ts-ignore
100103
globalThis.backendaiclient.vfolder.list_hosts(
101104
currentProject.id,
102-
) as VHostInfo,
105+
) as Promise<VHostInfo>,
103106
]);
104107

105-
const allSftpScalingGroups = _.uniq(
106-
_.flatten(
107-
_.map(vhostInfo.volume_info, (volume) => volume?.sftp_scaling_groups),
108-
),
109-
);
108+
const resourceGroups =
109+
resourceGroupsResult.status === 'fulfilled'
110+
? resourceGroupsResult.value
111+
: undefined;
112+
const vhostInfo =
113+
vhostInfoResult.status === 'fulfilled' ? vhostInfoResult.value : undefined;
114+
115+
const sftpResourceGroups = vhostInfo
116+
? _.uniq(
117+
_.flatten(
118+
_.map(vhostInfo.volume_info, (volume) => volume?.sftp_scaling_groups),
119+
),
120+
)
121+
: undefined;
110122

111123
return {
112-
resourceGroups: _.filter(
113-
resourceGroups.scaling_groups,
114-
(rg) => !allSftpScalingGroups.includes(rg.name),
115-
),
124+
nonSftpResourceGroups:
125+
resourceGroups && vhostInfo && sftpResourceGroups
126+
? _.filter(
127+
resourceGroups.scaling_groups,
128+
(rg) => !sftpResourceGroups.includes(rg.name),
129+
)
130+
: resourceGroups?.scaling_groups || undefined,
116131
vhostInfo,
117-
allSftpScalingGroups,
132+
sftpResourceGroups,
118133
};
119134
});
120135

react/src/hooks/useResourceLimitAndRemaining.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ export const useResourceLimitAndRemaining = ({
115115
const baiClient = useSuspendedBackendaiClient();
116116
const [resourceSlots] = useResourceSlots();
117117
const acceleratorSlots = _.omit(resourceSlots, ['cpu', 'mem', 'shmem']);
118-
const { resourceGroups } = useResourceGroupsForCurrentProject();
118+
const { nonSftpResourceGroups } = useResourceGroupsForCurrentProject();
119119

120120
const currentResourceGroupForLimit = useFragment(
121121
graphql`
@@ -152,7 +152,7 @@ export const useResourceLimitAndRemaining = ({
152152

153153
if (
154154
currentResourceGroup &&
155-
_.some(resourceGroups, (rg) => rg.name === currentResourceGroup)
155+
_.some(nonSftpResourceGroups, (rg) => rg.name === currentResourceGroup)
156156
) {
157157
params.scaling_group = currentResourceGroup;
158158
}

0 commit comments

Comments
 (0)