Skip to content

Commit 2a84fd4

Browse files
committed
feat(FR-2688): migrate notification item VirtualFolderNode branch to Strawberry V2 (#6934)
Resolves #6932 (FR-2688) ## Summary - Migrates `... on VirtualFolderNode` branch in `BAINodeNotificationItem` to `... on VFolder` (Strawberry V2). - Renames `BAIVirtualFolderNodeNotificationItem` -> `BAIVFolderNotificationItem` and adapts field access (`metadata.name`, `toLocalId(id)` replacing V1 `row_id`). ## Part of Epic FR-2572 - Migrate WebUI to Strawberry V2 GraphQL API. ## Notes - No backend change required; both `VirtualFolderNode` and `VFolder` implement `Node`. - No existing producer currently supplies a VFolder node ref into notifications (only `useStartSession.tsx` for sessions), so this PR is preparatory - it aligns the branch with the V2 schema for future consumers. - The unused `status` field selection was dropped from both the parent `BAINodeNotificationItemFragment` (V2 `VFolder.status` is `VFolderOperationStatus!`, which conflicted with sibling fragments where `status` is `String`) and the child fragment, because neither the original nor the migrated component actually read `node.status`. ## Verification - `bash scripts/verify.sh` -> ALL PASS ## Test plan - [ ] Existing session notifications continue to render correctly (regression check). - [ ] (Smoke) Add a temporary producer feeding a VFolder id through the Node fragment and confirm the branch resolves - not strictly required for merge, but confirms correctness.
1 parent cd0ca53 commit 2a84fd4

6 files changed

Lines changed: 252 additions & 61 deletions

react/src/components/BAIGeneralNotificationItem.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,19 @@ const BAIGeneralNotificationItem: React.FC<{
7070
</Typography.Paragraph>
7171
</BAIFlex>
7272
<BAIFlex direction="row" align="end" gap={'xxs'} justify="between">
73-
<Typography.Paragraph>
73+
<Typography.Paragraph
74+
style={{ flex: 1, minWidth: 0, marginBottom: 0 }}
75+
>
7476
{_.isString(notification.description)
7577
? _.truncate(notification.description, {
7678
length: 300,
7779
})
7880
: notification.description}
7981
</Typography.Paragraph>
8082
{notification.to ? (
81-
<BAIFlex>
83+
<BAIFlex style={{ flexShrink: 0 }}>
8284
<Typography.Link
85+
style={{ whiteSpace: 'nowrap' }}
8386
onClick={(e) => {
8487
onClickAction && onClickAction(e, notification);
8588
}}
@@ -91,15 +94,16 @@ const BAIGeneralNotificationItem: React.FC<{
9194
</BAIFlex>
9295
) : null}
9396
{notification?.onCancel ? (
94-
<BAIFlex>
97+
<BAIFlex style={{ flexShrink: 0 }}>
9598
<Button type="link" onClick={notification.onCancel}>
9699
{t('button.Cancel')}
97100
</Button>
98101
</BAIFlex>
99102
) : null}
100103
{notification.extraDescription && !notification?.onCancel ? (
101-
<BAIFlex>
104+
<BAIFlex style={{ flexShrink: 0 }}>
102105
<Typography.Link
106+
style={{ whiteSpace: 'nowrap' }}
103107
onClick={() => {
104108
// onClickAction && onClickAction(e, notification);
105109
setShowExtraDescription(!showExtraDescription);

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: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
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 style={{ flex: 1, minWidth: 0 }}>
95+
{_.truncate(notification.description, { length: 300 })}
96+
</BAIText>
97+
) : (
98+
notification.description
99+
)}
100+
101+
{notification.extraDescription && !notification?.onCancel ? (
102+
<BAIFlex style={{ flexShrink: 0 }}>
103+
<Typography.Link
104+
style={{ whiteSpace: 'nowrap' }}
105+
onClick={() => {
106+
toggleShowExtraDescription();
107+
}}
108+
>
109+
{showExtraDescription
110+
? t('notification.SeeSummary')
111+
: t('notification.SeeDetail')}
112+
</Typography.Link>
113+
</BAIFlex>
114+
) : null}
115+
</BAIFlex>
116+
117+
{notification.extraDescription && showExtraDescription ? (
118+
<Card
119+
size="small"
120+
style={{
121+
maxHeight: '300px',
122+
overflow: 'auto',
123+
overflowX: 'hidden',
124+
marginTop: token.marginSM,
125+
}}
126+
>
127+
{_.isString(notification.extraDescription) ? (
128+
<Typography.Text type="secondary" copyable>
129+
{notification.extraDescription}
130+
</Typography.Text>
131+
) : (
132+
notification.extraDescription
133+
)}
134+
</Card>
135+
) : null}
136+
137+
{notification.backgroundTask && (
138+
<BAINotificationBackgroundProgress
139+
backgroundTask={notification.backgroundTask}
140+
showDate={showDate}
141+
/>
142+
)}
143+
</BAIFlex>
144+
</List.Item>
145+
}
146+
footer={showDate ? dayjs(notification.created).format('lll') : undefined}
147+
/>
148+
);
149+
};
150+
151+
export default BAIVFolderNotificationItem;

react/src/components/BAIVirtualFolderNodeNotificationItem.tsx

Lines changed: 9 additions & 2 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 }) => {
@@ -79,16 +85,17 @@ const BAIVirtualFolderNodeNotificationItem: React.FC<
7985
justify="between"
8086
>
8187
{_.isString(notification.description) ? (
82-
<BAIText>
88+
<BAIText style={{ flex: 1, minWidth: 0 }}>
8389
{_.truncate(notification.description, { length: 300 })}
8490
</BAIText>
8591
) : (
8692
notification.description
8793
)}
8894

8995
{notification.extraDescription && !notification?.onCancel ? (
90-
<BAIFlex>
96+
<BAIFlex style={{ flexShrink: 0 }}>
9197
<Typography.Link
98+
style={{ whiteSpace: 'nowrap' }}
9299
onClick={() => {
93100
toggleShowExtraDescription();
94101
}}

0 commit comments

Comments
 (0)