Skip to content

Commit f6d48a0

Browse files
committed
feat(FR-2688): migrate notification item VirtualFolderNode branch to Strawberry V2
Add a `... on VFolder` branch to `BAINodeNotificationItemFragment` so the V2 (Strawberry) `VFolder implements Node` type renders rich folder-link notifications via a new `BAIVFolderNotificationItem` component. The legacy V1 `... on VirtualFolderNode` branch and `BAIVirtualFolderNodeNotificationItem` component stay in place (marked @deprecated) until all V1 callers migrate. Also wires `VFolderNodesV2`'s delete/restore error handlers through `upsertNotification({ node: vfolder.notificationFrgmt })` so V2 list flows get the same folder-link + clickable-mounted-session UX the V1 `VFolderNodes` flow uses. `VFolderNodesV2Fragment` now spreads `BAINodeNotificationItemFragment` (`@alias(as: "notificationFrgmt")`). Because that spread reaches V1 `VirtualFolderNode.status: String` and `ComputeSessionNode.status: String` through the dispatcher, V2 `VFolder.status: VFolderOperationStatus!` selections are aliased as `vfolderStatus: status` in `VFolderNodesV2Fragment` and in `ProjectAdminDataPage` to dodge Relay's "ambiguous field types" diagnostic. Column key + V2 OrderField sort identifier stay `status`.
1 parent 0381546 commit f6d48a0

5 files changed

Lines changed: 240 additions & 55 deletions

File tree

react/src/components/BAINodeNotificationItem.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,17 @@
44
*/
55
import { NotificationState } from '../hooks/useBAINotification';
66
import BAIComputeSessionNodeNotificationItem from './BAIComputeSessionNodeNotificationItem';
7+
import BAIVFolderNotificationItem from './BAIVFolderNotificationItem';
78
import BAIVirtualFolderNodeNotificationItem from './BAIVirtualFolderNodeNotificationItem';
89
import React from 'react';
910
import { graphql, useRefetchableFragment } from 'react-relay';
1011
import { BAINodeNotificationItemFragment$key } from 'src/__generated__/BAINodeNotificationItemFragment.graphql';
1112

13+
// `... on VFolder` is the V2 (Strawberry GraphQL, FR-2573) branch and the
14+
// preferred path for new VFolder list/mutation flows.
15+
// `... on VirtualFolderNode` is the legacy V1 branch and is **deprecated** —
16+
// kept here only so callers that still hold V1 fragments keep rendering. It
17+
// will be removed once all VFolder callers migrate to V2 `VFolder`.
1218
const nodeFragmentOperation = graphql`
1319
fragment BAINodeNotificationItemFragment on Node
1420
@refetchable(queryName: "BAINodeNotificationItemRefetchQuery") {
@@ -20,6 +26,10 @@ const nodeFragmentOperation = graphql`
2026
...BAIComputeSessionNodeNotificationItemFragment
2127
@alias(as: "sessionFrgmt")
2228
}
29+
... on VFolder {
30+
__typename
31+
...BAIVFolderNotificationItemFragment @alias(as: "vfolderFrgmt")
32+
}
2333
... on VirtualFolderNode {
2434
__typename
2535
status
@@ -45,7 +55,18 @@ const BAINodeNotificationItem: React.FC<{
4555
primaryAppOption={notification.extraData}
4656
/>
4757
);
58+
} else if (node?.__typename === 'VFolder') {
59+
return (
60+
<BAIVFolderNotificationItem
61+
notification={notification}
62+
vfolderFrgmt={node.vfolderFrgmt || null}
63+
showDate={showDate}
64+
/>
65+
);
4866
} else if (node?.__typename === 'VirtualFolderNode') {
67+
// @deprecated Renders the legacy V1 VFolder notification. Will be removed
68+
// once all V1 callers are gone — see the matching note on the V2 branch
69+
// above.
4970
return (
5071
<BAIVirtualFolderNodeNotificationItem
5172
notification={notification}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/**
2+
@license
3+
Copyright (c) 2015-2026 Lablup Inc. All rights reserved.
4+
*/
5+
import BAINotificationBackgroundProgress from './BAINotificationBackgroundProgress';
6+
import { useToggle } from 'ahooks';
7+
import { Card, List, theme, Typography } from 'antd';
8+
import {
9+
BAIFlex,
10+
BAILink,
11+
BAINotificationItem,
12+
BAIText,
13+
toLocalId,
14+
} from 'backend.ai-ui';
15+
import dayjs from 'dayjs';
16+
import * as _ from 'lodash-es';
17+
import { useTranslation } from 'react-i18next';
18+
import { graphql, useFragment } from 'react-relay';
19+
import { useNavigate } from 'react-router-dom';
20+
import { BAIVFolderNotificationItemFragment$key } from 'src/__generated__/BAIVFolderNotificationItemFragment.graphql';
21+
import {
22+
NotificationState,
23+
useSetBAINotification,
24+
} from 'src/hooks/useBAINotification';
25+
26+
interface BAIVFolderNotificationItemProps {
27+
notification: NotificationState;
28+
vfolderFrgmt: BAIVFolderNotificationItemFragment$key | null;
29+
showDate?: boolean;
30+
}
31+
32+
// V2 counterpart of `BAIVirtualFolderNodeNotificationItem`. Operates on the
33+
// Strawberry V2 `VFolder` type (`VFolder implements Node`, FR-2573) so V2
34+
// list/mutation flows can pass `node: vfolder` to `upsertNotification` and
35+
// get the same rich folder-link + extra-description rendering as the legacy
36+
// V1 path. The V1 component stays in place until all callers migrate.
37+
const BAIVFolderNotificationItem: React.FC<BAIVFolderNotificationItemProps> = ({
38+
notification,
39+
vfolderFrgmt,
40+
showDate,
41+
}) => {
42+
'use memo';
43+
44+
const navigate = useNavigate();
45+
const { t } = useTranslation();
46+
const { token } = theme.useToken();
47+
const { closeNotification } = useSetBAINotification();
48+
const [showExtraDescription, { toggle: toggleShowExtraDescription }] =
49+
useToggle(false);
50+
51+
const node = useFragment(
52+
graphql`
53+
fragment BAIVFolderNotificationItemFragment on VFolder {
54+
id
55+
metadata {
56+
name
57+
}
58+
}
59+
`,
60+
vfolderFrgmt,
61+
);
62+
63+
if (!node) return null;
64+
65+
const localId = toLocalId(node.id);
66+
const folderName = node.metadata?.name;
67+
68+
return (
69+
<BAINotificationItem
70+
title={
71+
<BAIText ellipsis>
72+
{t('general.Folder')}:&nbsp;
73+
<BAILink
74+
style={{
75+
fontWeight: 'normal',
76+
}}
77+
title={folderName || ''}
78+
onClick={() => {
79+
navigate(
80+
`/data${localId ? `?${new URLSearchParams({ folder: localId }).toString()}` : ''}`,
81+
);
82+
closeNotification(notification.key);
83+
}}
84+
>
85+
{folderName}
86+
</BAILink>
87+
</BAIText>
88+
}
89+
description={
90+
<List.Item>
91+
<BAIFlex direction="column" align="stretch" gap={'xxs'}>
92+
<BAIFlex direction="row" align="end" gap={'xxs'} justify="between">
93+
{_.isString(notification.description) ? (
94+
<BAIText>
95+
{_.truncate(notification.description, { length: 300 })}
96+
</BAIText>
97+
) : (
98+
notification.description
99+
)}
100+
101+
{notification.extraDescription && !notification?.onCancel ? (
102+
<BAIFlex>
103+
<Typography.Link
104+
onClick={() => {
105+
toggleShowExtraDescription();
106+
}}
107+
>
108+
{showExtraDescription
109+
? t('notification.SeeSummary')
110+
: t('notification.SeeDetail')}
111+
</Typography.Link>
112+
</BAIFlex>
113+
) : null}
114+
</BAIFlex>
115+
116+
{notification.extraDescription && showExtraDescription ? (
117+
<Card
118+
size="small"
119+
style={{
120+
maxHeight: '300px',
121+
overflow: 'auto',
122+
overflowX: 'hidden',
123+
marginTop: token.marginSM,
124+
}}
125+
>
126+
{_.isString(notification.extraDescription) ? (
127+
<Typography.Text type="secondary" copyable>
128+
{notification.extraDescription}
129+
</Typography.Text>
130+
) : (
131+
notification.extraDescription
132+
)}
133+
</Card>
134+
) : null}
135+
136+
{notification.backgroundTask && (
137+
<BAINotificationBackgroundProgress
138+
backgroundTask={notification.backgroundTask}
139+
showDate={showDate}
140+
/>
141+
)}
142+
</BAIFlex>
143+
</List.Item>
144+
}
145+
footer={showDate ? dayjs(notification.created).format('lll') : undefined}
146+
/>
147+
);
148+
};
149+
150+
export default BAIVFolderNotificationItem;

react/src/components/BAIVirtualFolderNodeNotificationItem.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ interface BAIVirtualFolderNodeNotificationItemProps {
2323
showDate?: boolean;
2424
}
2525

26+
/**
27+
* @deprecated Renders V1 `VirtualFolderNode` notifications. The V2 counterpart
28+
* `BAIVFolderNotificationItem` (operating on `VFolder implements Node` from
29+
* the Strawberry GraphQL API, FR-2573) is the preferred path going forward.
30+
* This component will be removed once all V1 callers migrate.
31+
*/
2632
const BAIVirtualFolderNodeNotificationItem: React.FC<
2733
BAIVirtualFolderNodeNotificationItemProps
2834
> = ({ notification, virtualFolderNodeFrgmt, showDate }) => {

0 commit comments

Comments
 (0)