Skip to content

Commit 55d04bf

Browse files
committed
feat(FR-1296): Improve error message for deleting in-use vfolder
1 parent f2d8c62 commit 55d04bf

27 files changed

+284
-28
lines changed

react/src/components/BAIGeneralNotificationItem.tsx

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { NotificationState } from '../hooks/useBAINotification';
2+
import BAINotificationBackgroundItem from './BAINotificationBackgroundItem';
23
import {
34
CheckCircleOutlined,
45
ClockCircleOutlined,
56
CloseCircleOutlined,
67
} from '@ant-design/icons';
7-
import { Button, Card, List, Progress, Typography, theme } from 'antd';
8+
import { Button, Card, List, Typography, theme } from 'antd';
89
import { BAIFlex } from 'backend.ai-ui';
910
import dayjs from 'dayjs';
1011
import _ from 'lodash';
@@ -117,36 +118,23 @@ const BAIGeneralNotificationItem: React.FC<{
117118
marginTop: token.marginSM,
118119
}}
119120
>
120-
<Typography.Text type="secondary" copyable>
121-
{notification.extraDescription}
122-
</Typography.Text>
121+
{_.isString(notification.extraDescription) ? (
122+
<Typography.Text type="secondary" copyable>
123+
{notification.extraDescription}
124+
</Typography.Text>
125+
) : (
126+
notification.extraDescription
127+
)}
123128
</Card>
124129
) : null}
125130

126131
<BAIFlex direction="row" align="center" justify="end" gap={'sm'}>
127-
{notification.backgroundTask &&
128-
_.isNumber(notification.backgroundTask.percent) ? (
129-
<Progress
130-
size="small"
131-
showInfo={false}
132-
percent={notification.backgroundTask.percent}
133-
strokeColor={
134-
notification.backgroundTask.status === 'rejected'
135-
? token.colorTextDisabled
136-
: undefined
137-
}
138-
style={{
139-
margin: 0,
140-
opacity:
141-
notification.backgroundTask.status === 'resolved' &&
142-
showDate
143-
? 0
144-
: 1,
145-
}}
146-
147-
// status={item.progressStatus}
132+
{notification.backgroundTask && (
133+
<BAINotificationBackgroundItem
134+
backgroundTask={notification.backgroundTask}
135+
showDate={showDate}
148136
/>
149-
) : null}
137+
)}
150138
{showDate ? (
151139
<BAIFlex>
152140
<Typography.Text type="secondary">

react/src/components/BAINodeNotificationItem.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { NotificationState } from '../hooks/useBAINotification';
22
import BAIComputeSessionNodeNotificationItem from './BAIComputeSessionNodeNotificationItem';
3+
import BAIVirtualFolderNodeNotificationItem from './BAIVirtualFolderNodeNotificationItem';
34
import React from 'react';
45
import { graphql, useRefetchableFragment } from 'react-relay';
56
import { BAINodeNotificationItemFragment$key } from 'src/__generated__/BAINodeNotificationItemFragment.graphql';
@@ -18,6 +19,8 @@ const nodeFragmentOperation = graphql`
1819
... on VirtualFolderNode {
1920
__typename
2021
status
22+
...BAIVirtualFolderNodeNotificationItemFragment
23+
@alias(as: "virtualFolderNodeFrgmt")
2124
}
2225
}
2326
`;
@@ -39,6 +42,13 @@ const BAINodeNotificationItem: React.FC<{
3942
/>
4043
);
4144
} else if (node?.__typename === 'VirtualFolderNode') {
45+
return (
46+
<BAIVirtualFolderNodeNotificationItem
47+
notification={notification}
48+
virtualFolderNodeFrgmt={node.virtualFolderNodeFrgmt || null}
49+
showDate={showDate}
50+
/>
51+
);
4252
} else {
4353
// console.warn('Unknown node type in BAINodeNotificationItem:', node);
4454
return null;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Progress, theme } from 'antd';
2+
import _ from 'lodash';
3+
import { NotificationState } from 'src/hooks/useBAINotification';
4+
5+
interface BAINotificationBackgroundItemProps {
6+
backgroundTask: NotificationState['backgroundTask'];
7+
showDate?: boolean;
8+
}
9+
10+
const BAINotificationBackgroundItem: React.FC<
11+
BAINotificationBackgroundItemProps
12+
> = ({ backgroundTask, showDate }) => {
13+
'use memo';
14+
15+
const { token } = theme.useToken();
16+
17+
return _.isNumber(backgroundTask?.percent) ? (
18+
<Progress
19+
size="small"
20+
showInfo={false}
21+
percent={backgroundTask.percent}
22+
strokeColor={
23+
backgroundTask.status === 'rejected'
24+
? token.colorTextDisabled
25+
: undefined
26+
}
27+
style={{
28+
margin: 0,
29+
opacity: backgroundTask.status === 'resolved' && showDate ? 0 : 1,
30+
}}
31+
/>
32+
) : null;
33+
};
34+
35+
export default BAINotificationBackgroundItem;
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import BAINotificationBackgroundItem from './BAINotificationBackgroundItem';
2+
import { useToggle } from 'ahooks';
3+
import { Card, List, theme, Typography } from 'antd';
4+
import { BAIFlex, BAILink, BAINotificationItem, BAIText } from 'backend.ai-ui';
5+
import dayjs from 'dayjs';
6+
import _ from 'lodash';
7+
import { useTranslation } from 'react-i18next';
8+
import { graphql, useFragment } from 'react-relay';
9+
import { useNavigate } from 'react-router-dom';
10+
import { BAIVirtualFolderNodeNotificationItemFragment$key } from 'src/__generated__/BAIVirtualFolderNodeNotificationItemFragment.graphql';
11+
import {
12+
NotificationState,
13+
useSetBAINotification,
14+
} from 'src/hooks/useBAINotification';
15+
16+
interface BAIVirtualFolderNodeNotificationItemProps {
17+
notification: NotificationState;
18+
virtualFolderNodeFrgmt: BAIVirtualFolderNodeNotificationItemFragment$key | null;
19+
showDate?: boolean;
20+
}
21+
22+
const BAIVirtualFolderNodeNotificationItem: React.FC<
23+
BAIVirtualFolderNodeNotificationItemProps
24+
> = ({ notification, virtualFolderNodeFrgmt, showDate }) => {
25+
'use memo';
26+
27+
const navigate = useNavigate();
28+
const { t } = useTranslation();
29+
const { token } = theme.useToken();
30+
const { closeNotification } = useSetBAINotification();
31+
const [showExtraDescription, { toggle: toggleShowExtraDescription }] =
32+
useToggle(false);
33+
34+
const node = useFragment(
35+
graphql`
36+
fragment BAIVirtualFolderNodeNotificationItemFragment on VirtualFolderNode {
37+
row_id
38+
id
39+
name
40+
status
41+
}
42+
`,
43+
virtualFolderNodeFrgmt,
44+
);
45+
46+
return (
47+
node && (
48+
<BAINotificationItem
49+
title={
50+
<BAIText ellipsis>
51+
{t('general.Folder')}:&nbsp;
52+
<BAILink
53+
style={{
54+
fontWeight: 'normal',
55+
}}
56+
title={node.name || ''}
57+
onClick={() => {
58+
navigate(
59+
`/data${node.row_id ? `?${new URLSearchParams({ folder: node.row_id }).toString()}` : ''}`,
60+
);
61+
closeNotification(notification.key);
62+
}}
63+
>
64+
{node.name}
65+
</BAILink>
66+
</BAIText>
67+
}
68+
description={
69+
<List.Item>
70+
<BAIFlex direction="column" align="stretch" gap={'xxs'}>
71+
<BAIFlex
72+
direction="row"
73+
align="end"
74+
gap={'xxs'}
75+
justify="between"
76+
>
77+
{_.isString(notification.description) ? (
78+
<BAIText ellipsis>
79+
{_.truncate(notification.description, { length: 300 })}
80+
</BAIText>
81+
) : (
82+
notification.description
83+
)}
84+
85+
{notification.extraDescription && !notification?.onCancel ? (
86+
<BAIFlex>
87+
<Typography.Link
88+
onClick={() => {
89+
toggleShowExtraDescription();
90+
}}
91+
>
92+
{t('notification.SeeDetail')}
93+
</Typography.Link>
94+
</BAIFlex>
95+
) : null}
96+
</BAIFlex>
97+
98+
{notification.extraDescription && showExtraDescription ? (
99+
<Card
100+
size="small"
101+
style={{
102+
maxHeight: '300px',
103+
overflow: 'auto',
104+
overflowX: 'hidden',
105+
marginTop: token.marginSM,
106+
}}
107+
>
108+
{_.isString(notification.extraDescription) ? (
109+
<Typography.Text type="secondary" copyable>
110+
{notification.extraDescription}
111+
</Typography.Text>
112+
) : (
113+
notification.extraDescription
114+
)}
115+
</Card>
116+
) : null}
117+
118+
{notification.backgroundTask && (
119+
<BAINotificationBackgroundItem
120+
backgroundTask={notification.backgroundTask}
121+
showDate={showDate}
122+
/>
123+
)}
124+
</BAIFlex>
125+
</List.Item>
126+
}
127+
footer={
128+
showDate ? dayjs(notification.created).format('lll') : undefined
129+
}
130+
/>
131+
)
132+
);
133+
};
134+
135+
export default BAIVirtualFolderNodeNotificationItem;

react/src/components/VFolderNodes.tsx

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import _ from 'lodash';
4343
import React, { useState } from 'react';
4444
import { useTranslation } from 'react-i18next';
4545
import { graphql, useFragment } from 'react-relay';
46+
import { useNavigate } from 'react-router-dom';
4647

4748
export const statusTagColor = {
4849
// mountable
@@ -84,6 +85,7 @@ const VFolderNodes: React.FC<VFolderNodesProps> = ({
8485
const { upsertNotification } = useSetBAINotification();
8586
const { generateFolderPath } = useFolderExplorerOpener();
8687
const { getErrorMessage } = useErrorMessageResolver();
88+
const navigate = useNavigate();
8789

8890
const [deletingVFolder, setDeletingVFolder] =
8991
useState<VFolderNodeInList | null>(null);
@@ -107,6 +109,7 @@ const VFolderNodes: React.FC<VFolderNodesProps> = ({
107109
...VFolderNodeIdenticonFragment
108110
...SharedFolderPermissionInfoModalFragment
109111
...BAIVFolderDeleteButtonFragment
112+
...BAINodeNotificationItemFragment
110113
}
111114
`,
112115
vfoldersFrgmt,
@@ -274,6 +277,8 @@ const VFolderNodes: React.FC<VFolderNodesProps> = ({
274277
},
275278
onError: (error) => {
276279
upsertNotification({
280+
key: `vfolder-error-${vfolder?.id}`,
281+
node: vfolder,
277282
description: getErrorMessage(error),
278283
open: true,
279284
});
@@ -298,9 +303,46 @@ const VFolderNodes: React.FC<VFolderNodesProps> = ({
298303
);
299304
},
300305
onError: (error) => {
306+
const matchString = error?.message.match(
307+
/sessions\(ids: (\[.*?\])\)/,
308+
)?.[1];
309+
const occupiedSession = JSON.parse(
310+
matchString?.replace(/'/g, '"') || '[]',
311+
);
301312
upsertNotification({
302-
description: getErrorMessage(error),
303313
open: true,
314+
key: `vfolder-error-${vfolder?.id}`,
315+
node: vfolder,
316+
description: getErrorMessage(error).replace(
317+
/\(ids[\s\S]*$/,
318+
'',
319+
),
320+
extraDescription: !_.isEmpty(occupiedSession) ? (
321+
<BAIFlex direction="column" align="stretch">
322+
<Typography.Text
323+
style={{
324+
color: token.colorTextDescription,
325+
}}
326+
>
327+
{t('data.folders.MountedSessions')}
328+
</Typography.Text>
329+
{_.map(occupiedSession, (sessionId) => (
330+
<BAILink
331+
key={sessionId}
332+
style={{
333+
fontWeight: 'normal',
334+
}}
335+
onClick={() => {
336+
navigate(
337+
`/session${`?${new URLSearchParams({ sessionDetail: sessionId }).toString()}`}`,
338+
);
339+
}}
340+
>
341+
{sessionId}
342+
</BAILink>
343+
))}
344+
</BAIFlex>
345+
) : null,
304346
});
305347
},
306348
});
@@ -427,6 +469,8 @@ const VFolderNodes: React.FC<VFolderNodesProps> = ({
427469
},
428470
onError: (error) => {
429471
upsertNotification({
472+
key: `vfolder-error-${deletingVFolder?.id}`,
473+
...(deletingVFolder && { node: deletingVFolder }),
430474
description: getErrorMessage(error),
431475
open: true,
432476
});

react/src/hooks/useBAINotification.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export interface NotificationState
6464
renderDataMessage?: (message?: string) => React.ReactNode;
6565
promise?: Promise<unknown> | null;
6666
};
67-
extraDescription?: string | null;
67+
extraDescription?: ReactNode | null;
6868
onCancel?: (() => void) | null;
6969
skipDesktopNotification?: boolean;
7070
extraData: any;
@@ -279,6 +279,8 @@ export const useBAINotificationEffect = () => {
279279
* @returns An object containing functions for manipulating notifications.
280280
*/
281281
export const useSetBAINotification = () => {
282+
'use memo';
283+
282284
// Don't use _notifications carefully when you need to mutate it.
283285
const setNotifications = useSetAtom(notificationListState);
284286
const [desktopNotification] = useBAISettingUserState('desktop_notification');

resources/i18n/de.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@
402402
"MaxSize": "Maximale Größe",
403403
"ModifyPermissions": "Berechtigungen ändern",
404404
"MountPermission": "Erlaubnis montieren",
405+
"MountedSessions": "Gemountete Sitzungen",
405406
"MoveToTrash": "Ziehen Sie zu Müllbehälter",
406407
"MoveToTrashDescription": "Sind Sie sicher, dass Sie \"{{folderName}}\" in den Müll verschieben möchten?",
407408
"MoveToTrashMultipleDescription": "Sind Sie sicher, dass Sie {{folderLength}} Ordner in Müll bin verschieben möchten?",
@@ -690,6 +691,7 @@
690691
"Enabled": "aktiviert",
691692
"ErrorOccurred": "Etwas lief schief. \nBitte versuchen Sie es später erneut.",
692693
"ExtendLoginSession": "Eine Anmeldesitzung verlängern",
694+
"Folder": "Ordner",
693695
"Folders": "Ordner",
694696
"General": "Allgemein",
695697
"Image": "Bild",

0 commit comments

Comments
 (0)