Skip to content

Commit 4c583dc

Browse files
claudeironAiken2
authored andcommitted
feat(FR-2619): migrate single-folder detail reads to Strawberry V2 vfolderV2 query
Replace the legacy `vfolder_node(id)` GraphQL query and `VirtualFolderNode` Relay fragments with the V2 `vfolderV2(vfolderId)` query and `VFolder` fragments across the detail / explorer paths: - VFolderLazyView, EditableVFolderName, VFolderNodeDescription, FolderExplorerHeader, FolderExplorerModal switch to `vfolderV2` + VFolder fragments with nested `metadata` / `accessControl` / `ownership` shapes. - FileBrowserButton, SFTPServerButton, useVirtualFolderNodePath switch their fragments from `on VirtualFolderNode` to `on VFolder`. - V2 `VFolderMountPermission` enum is mapped to legacy REST 'ro'/'rw' for the existing `baiClient.vfolder.update_folder` call; `RW_DELETE` implies `delete_content`, `READ_WRITE`/`RW_DELETE` imply `write_content` in the explorer permission checks. TODO(needs-backend) markers note where V2 still lacks a richer per-operation permission array.
1 parent 0dd4e04 commit 4c583dc

8 files changed

Lines changed: 254 additions & 194 deletions

react/src/components/EditableVFolderName.tsx

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {
3232
} from 'react-relay';
3333

3434
type EditableVFolderNameProps = {
35-
vfolderFrgmt: EditableVFolderNameFragment$key;
35+
vfolderNodeFrgmt: EditableVFolderNameFragment$key;
3636
enableLink?: boolean;
3737
inputProps?: InputProps;
3838
onEditEnd?: () => void;
@@ -50,7 +50,7 @@ type EditableVFolderNameProps = {
5050

5151
const EditableVFolderName: React.FC<EditableVFolderNameProps> = ({
5252
component: Component = Typography.Text,
53-
vfolderFrgmt,
53+
vfolderNodeFrgmt,
5454
editable: editableOfProps,
5555
style,
5656
enableLink = true,
@@ -60,25 +60,31 @@ const EditableVFolderName: React.FC<EditableVFolderNameProps> = ({
6060
...otherProps
6161
}) => {
6262
'use memo';
63-
const vfolder = useFragment(
63+
const vfolderNode = useFragment(
6464
graphql`
65-
fragment EditableVFolderNameFragment on VirtualFolderNode {
65+
fragment EditableVFolderNameFragment on VFolder {
6666
id
67-
name
68-
user
69-
group
7067
status
68+
metadata {
69+
name
70+
}
71+
ownership {
72+
userId
73+
projectId
74+
}
7175
}
7276
`,
73-
vfolderFrgmt,
77+
vfolderNodeFrgmt,
78+
);
79+
const [optimisticName, setOptimisticName] = useState(
80+
vfolderNode.metadata?.name,
7481
);
75-
const [optimisticName, setOptimisticName] = useState(vfolder.name);
7682
const [userInfo] = useCurrentUserInfo();
7783
const currentProject = useCurrentProjectValue();
7884
const baiClient = useSuspendedBackendaiClient();
7985
const renameMutation = useTanMutation({
8086
mutationFn: (input: { id: string; name: string }) => {
81-
return baiClient.vfolder.rename(input.name, toLocalId(vfolder?.id));
87+
return baiClient.vfolder.rename(input.name, toLocalId(vfolderNode?.id));
8288
},
8389
});
8490
const relayEnv = useRelayEnvironment();
@@ -92,11 +98,12 @@ const EditableVFolderName: React.FC<EditableVFolderNameProps> = ({
9298

9399
const isEditingAllowed =
94100
editableOfProps &&
95-
(userInfo.uuid === vfolder.user || currentProject?.id === vfolder.group) &&
96-
!isDeletedCategory(vfolder.status);
101+
(userInfo.uuid === vfolderNode.ownership?.userId ||
102+
currentProject?.id === vfolderNode.ownership?.projectId) &&
103+
!isDeletedCategory(vfolderNode.status);
97104

98105
const isPendingRenameMutation =
99-
renameMutation.isPending || optimisticName !== vfolder.name;
106+
renameMutation.isPending || optimisticName !== vfolderNode.metadata?.name;
100107

101108
// focus back to the text component after editing for better UX related to keyboard shortcuts
102109
const textRef = useRef<HTMLElement>(null);
@@ -140,34 +147,38 @@ const EditableVFolderName: React.FC<EditableVFolderNameProps> = ({
140147
? token.colorTextTertiary
141148
: style?.color,
142149
}}
143-
title={vfolder.name || undefined}
150+
title={vfolderNode.metadata?.name || undefined}
144151
{...otherProps}
145152
>
146153
{enableLink && !isEditing && (
147154
<BAILink
148155
type="hover"
149-
to={generateFolderPath(toLocalId(vfolder?.id))}
156+
to={generateFolderPath(toLocalId(vfolderNode?.id))}
150157
>
151-
{isPendingRenameMutation ? optimisticName : vfolder.name}
158+
{isPendingRenameMutation
159+
? optimisticName
160+
: vfolderNode.metadata?.name}
152161
</BAILink>
153162
)}
154163
{!enableLink &&
155-
(isPendingRenameMutation ? optimisticName : vfolder.name)}
164+
(isPendingRenameMutation
165+
? optimisticName
166+
: vfolderNode.metadata?.name)}
156167
</Component>
157168
)}
158169
{isEditing && !isPendingRenameMutation && (
159170
<Form
160171
onFinish={(values) => {
161172
setIsEditing(false);
162173
focusFallback();
163-
if (values.vfolderName === vfolder.name) {
174+
if (values.vfolderName === vfolderNode.metadata?.name) {
164175
onEditEnd?.();
165176
return;
166177
}
167178
setOptimisticName(values.vfolderName);
168179
renameMutation.mutate(
169180
{
170-
id: vfolder.id,
181+
id: vfolderNode.id,
171182
name: values.vfolderName,
172183
},
173184
{
@@ -177,15 +188,17 @@ const EditableVFolderName: React.FC<EditableVFolderNameProps> = ({
177188
return fetchQuery<EditableVFolderNameRefetchQuery>(
178189
relayEnv,
179190
graphql`
180-
query EditableVFolderNameRefetchQuery($id: String!) {
181-
vfolder_node(id: $id) {
191+
query EditableVFolderNameRefetchQuery($vfolderId: UUID!) {
192+
vfolderV2(vfolderId: $vfolderId) {
182193
id
183-
name
194+
metadata {
195+
name
196+
}
184197
}
185198
}
186199
`,
187200
{
188-
id: vfolder.id,
201+
vfolderId: toLocalId(vfolderNode.id),
189202
},
190203
).toPromise();
191204
},
@@ -201,13 +214,13 @@ const EditableVFolderName: React.FC<EditableVFolderNameProps> = ({
201214
} else {
202215
message.error(errorMessage);
203216
}
204-
setOptimisticName(vfolder.name);
217+
setOptimisticName(vfolderNode.metadata?.name);
205218
},
206219
},
207220
);
208221
}}
209222
initialValues={{
210-
vfolderName: vfolder.name,
223+
vfolderName: vfolderNode.metadata?.name,
211224
}}
212225
style={{
213226
flex: 1,

react/src/components/FileBrowserButton.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ import {
2929

3030
interface FileBrowserButtonProps extends BAIButtonProps {
3131
showTitle?: boolean;
32-
vfolderFrgmt: FileBrowserButtonFragment$key;
32+
vfolderNodeFrgmt: FileBrowserButtonFragment$key;
3333
}
3434
const FileBrowserButton: React.FC<FileBrowserButtonProps> = ({
3535
showTitle = true,
36-
vfolderFrgmt,
36+
vfolderNodeFrgmt,
3737
...buttonProps
3838
}) => {
3939
'use memo';
@@ -63,18 +63,18 @@ const FileBrowserButton: React.FC<FileBrowserButtonProps> = ({
6363

6464
const filebrowserImage = useDefaultFileBrowserImageWithFallback();
6565

66-
const vfolder = useFragment(
66+
const vfolderNode = useFragment(
6767
graphql`
68-
fragment FileBrowserButtonFragment on VirtualFolderNode {
68+
fragment FileBrowserButtonFragment on VFolder {
6969
id
7070
host
7171
}
7272
`,
73-
vfolderFrgmt,
73+
vfolderNodeFrgmt,
7474
);
7575

7676
const hasAccessPermission = _.includes(
77-
unitedAllowedPermissionByVolume[vfolder?.host ?? ''],
77+
unitedAllowedPermissionByVolume[vfolderNode?.host ?? ''],
7878
'mount-in-session',
7979
);
8080

@@ -90,15 +90,15 @@ const FileBrowserButton: React.FC<FileBrowserButtonProps> = ({
9090

9191
// Helper to create launcher value for filebrowser
9292
const createFilebrowserLauncherValue = (): StartSessionWithDefaultValue => ({
93-
sessionName: `filebrowser-${toLocalId(vfolder.id || '')}`,
93+
sessionName: `filebrowser-${toLocalId(vfolderNode.id || '')}`,
9494
sessionType: 'interactive',
9595
environments: {
9696
version: filebrowserImage || '',
9797
},
9898
allocationPreset: 'minimum-required',
9999
cluster_mode: 'single-node',
100100
cluster_size: 1,
101-
mount_ids: [toLocalId(vfolder.id || '').replaceAll('-', '')],
101+
mount_ids: [toLocalId(vfolderNode.id || '').replaceAll('-', '')],
102102
reuseIfExists: true,
103103
});
104104

@@ -132,7 +132,7 @@ const FileBrowserButton: React.FC<FileBrowserButtonProps> = ({
132132
if (results?.fulfilled && results.fulfilled.length > 0) {
133133
upsertSessionNotification(results.fulfilled, [
134134
{
135-
key: `filebrowser-${toLocalId(vfolder.id || '')}`,
135+
key: `filebrowser-${toLocalId(vfolderNode.id || '')}`,
136136
extraData: {
137137
appName: 'filebrowser',
138138
} as PrimaryAppOption,

react/src/components/FolderExplorerHeader.tsx

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@ import EditableVFolderName from './EditableVFolderName';
77
import ErrorBoundaryWithNullFallback from './ErrorBoundaryWithNullFallback';
88
import FileBrowserButton from './FileBrowserButton';
99
import SFTPServerButton from './SFTPServerButton';
10-
import VFolderNodeIdenticon from './VFolderNodeIdenticon';
10+
import VFolderNodeIdenticonV2 from './VFolderNodeIdenticonV2';
1111
import { theme, Typography, Skeleton, Grid } from 'antd';
1212
import { BAIFlex } from 'backend.ai-ui';
13-
import * as _ from 'lodash-es';
1413
import React, { Suspense } from 'react';
1514
import { graphql, useFragment } from 'react-relay';
1615

@@ -30,14 +29,10 @@ const FolderExplorerHeader: React.FC<FolderExplorerHeaderProps> = ({
3029

3130
const vfolderNode = useFragment(
3231
graphql`
33-
fragment FolderExplorerHeaderFragment on VirtualFolderNode {
34-
id
35-
user
36-
permission
37-
row_id @required(action: THROW)
38-
unmanaged_path @since(version: "25.04.0")
39-
...VFolderNameTitleNodeFragment
40-
...VFolderNodeIdenticonFragment
32+
fragment FolderExplorerHeaderFragment on VFolder {
33+
id @required(action: THROW)
34+
unmanagedPath
35+
...VFolderNodeIdenticonV2Fragment
4136
...EditableVFolderNameFragment
4237
...FileBrowserButtonFragment
4338
...SFTPServerButtonFragment
@@ -60,7 +55,7 @@ const FolderExplorerHeader: React.FC<FolderExplorerHeaderProps> = ({
6055
style={{ flex: 1, fontWeight: 'normal', ...titleStyle }}
6156
>
6257
{vfolderNode ? (
63-
<VFolderNodeIdenticon
58+
<VFolderNodeIdenticonV2
6459
vfolderNodeIdenticonFrgmt={vfolderNode}
6560
style={{
6661
fontSize: token.fontSizeHeading4,
@@ -80,7 +75,7 @@ const FolderExplorerHeader: React.FC<FolderExplorerHeaderProps> = ({
8075
)}
8176
{vfolderNode && (
8277
<EditableVFolderName
83-
vfolderFrgmt={vfolderNode}
78+
vfolderNodeFrgmt={vfolderNode}
8479
enableLink={false}
8580
component={Typography.Title}
8681
level={3}
@@ -110,13 +105,16 @@ const FolderExplorerHeader: React.FC<FolderExplorerHeaderProps> = ({
110105
justify="end"
111106
gap={token.marginSM}
112107
>
113-
{vfolderNode && !vfolderNode?.unmanaged_path ? (
108+
{vfolderNode && !vfolderNode?.unmanagedPath ? (
114109
<Suspense fallback={<Skeleton.Button active />}>
115110
<ErrorBoundaryWithNullFallback>
116-
<FileBrowserButton vfolderFrgmt={vfolderNode} showTitle={lg} />
111+
<FileBrowserButton
112+
vfolderNodeFrgmt={vfolderNode}
113+
showTitle={lg}
114+
/>
117115
</ErrorBoundaryWithNullFallback>
118116
<ErrorBoundaryWithNullFallback>
119-
<SFTPServerButton vfolderFrgmt={vfolderNode} showTitle={lg} />
117+
<SFTPServerButton vfolderNodeFrgmt={vfolderNode} showTitle={lg} />
120118
</ErrorBoundaryWithNullFallback>
121119
</Suspense>
122120
) : null}

0 commit comments

Comments
 (0)