Skip to content

Commit be690b7

Browse files
committed
feat(FR-2685): migrate VFolder selector queries to Strawberry V2 GraphQL API
Resolves #6929(FR-2685) ## Summary - Migrate `BAIVFolderSelect`, `useGetAvailableFolderName`, `VFolderMountFormItem` (auto-mount section), and the two caller-side filters in `AdminModelCardSettingModal` / `ImportArtifactRevisionToFolderModal` from the Graphene V1 `vfolder_nodes` query to the Strawberry V2 `myVfolders` / `projectVfolders` / `vfolderV2` API. - The paginated list routes to `projectVfolders` when `currentProjectId` is set, otherwise `myVfolders` — matching the V1 `scope_id` behavior. - Search and status-exclusion semantics are preserved via `VFolderFilter` (`name.iContains` for search, `status.notIn DELETE_*` for `excludeDeleted`). The `filter` prop is now a structured `VFolderFilter` (previously a V1 filter string). - Preselected-value resolution uses up to 10 aliased `vfolderV2(vfolderId:)` lookups gated by `@include` booleans, since `VFolderFilter` does not expose an id filter. Values beyond that fall back to paginated name resolution or the raw id label. - `BAIVFolderSelect.stories.tsx` mock shapes are updated to the V2 `{ id, status, metadata.name }` form for both `myVfolders` and `projectVfolders`. ## Caller adjustments - `AdminModelCardSettingModal` and `ImportArtifactRevisionToFolderModal` previously passed V1 filter strings (`ownership_type == "group"`, `group == "<id>"`). These clauses are redundant under V2 because `projectVfolders` already scopes results to project-owned folders; `VFolderFilter` does not expose `ownershipType` or `projectId`. The model-store modal now scopes via `currentProjectId={modelStoreProject.id}`. ## Part of Epic FR-2572 — Migrate WebUI to Strawberry V2 GraphQL API. ## Verification - `bash scripts/verify.sh` → `=== ALL PASS ===`
1 parent c573afe commit be690b7

6 files changed

Lines changed: 482 additions & 238 deletions

File tree

packages/backend.ai-ui/src/components/fragments/BAIVFolderSelect.stories.tsx

Lines changed: 93 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -29,49 +29,64 @@ const VFolderRelayResolver = ({
2929
);
3030
};
3131

32+
// V2 VFolder mock shape: `id`, `status`, `metadata { name }`. The component
33+
// derives local UUIDs (used for `valuePropName='row_id'`) via `toLocalId(id)`
34+
// at render time, so stories no longer need to provide a separate `row_id`.
3235
const sampleVFolders = [
3336
{
3437
node: {
35-
id: 'VkZvbGRlck5vZGU6MTIzNDU2Nzg5MA==',
36-
name: 'my-project-data',
37-
row_id: 'abcd1234-5678-90ef-ghij-klmnopqrstuv',
38+
id: 'VkZvbGRlcjphYmNkMTIzNC01Njc4LTkwZWYtZ2hpai1rbG1ub3BxcnN0dXY=',
39+
status: 'READY',
40+
metadata: {
41+
name: 'my-project-data',
42+
},
3843
},
3944
},
4045
{
4146
node: {
42-
id: 'VkZvbGRlck5vZGU6MDk4NzY1NDMyMQ==',
43-
name: 'shared-datasets',
44-
row_id: 'wxyz9876-5432-10ab-cdef-ghijklmnopqr',
47+
id: 'VkZvbGRlcjp3eHl6OTg3Ni01NDMyLTEwYWItY2RlZi1naGlqa2xtbm9wcXI=',
48+
status: 'READY',
49+
metadata: {
50+
name: 'shared-datasets',
51+
},
4552
},
4653
},
4754
{
4855
node: {
49-
id: 'VkZvbGRlck5vZGU6MTExMTExMTExMQ==',
50-
name: 'model-checkpoints',
51-
row_id: 'aaaa1111-2222-3333-4444-555566667777',
56+
id: 'VkZvbGRlcjphYWFhMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NjY2Njc3Nzc=',
57+
status: 'READY',
58+
metadata: {
59+
name: 'model-checkpoints',
60+
},
5261
},
5362
},
5463
{
5564
node: {
56-
id: 'VkZvbGRlck5vZGU6MjIyMjIyMjIyMg==',
57-
name: 'training-logs',
58-
row_id: 'bbbb2222-3333-4444-5555-666677778888',
65+
id: 'VkZvbGRlcjpiYmJiMjIyMi0zMzMzLTQ0NDQtNTU1NS02NjY2Nzc3Nzg4ODg=',
66+
status: 'READY',
67+
metadata: {
68+
name: 'training-logs',
69+
},
5970
},
6071
},
6172
{
6273
node: {
63-
id: 'VkZvbGRlck5vZGU6MzMzMzMzMzMzMw==',
64-
name: 'test-results',
65-
row_id: 'cccc3333-4444-5555-6666-777788889999',
74+
id: 'VkZvbGRlcjpjY2NjMzMzMy00NDQ0LTU1NTUtNjY2Ni03Nzc3ODg4ODk5OTk=',
75+
status: 'READY',
76+
metadata: {
77+
name: 'test-results',
78+
},
6679
},
6780
},
6881
];
6982

7083
const sampleManyVFolders = Array.from({ length: 15 }, (_, i) => ({
7184
node: {
72-
id: `VkZvbGRlck5vZGU6${i + 1000000000}`,
73-
name: `vfolder-${i + 1}`,
74-
row_id: `${i.toString().padStart(8, '0')}-aaaa-bbbb-cccc-ddddeeeefffff`,
85+
id: btoa(`VFolder:00000000-aaaa-bbbb-cccc-${String(i).padStart(12, '0')}`),
86+
status: 'READY',
87+
metadata: {
88+
name: `vfolder-${i + 1}`,
89+
},
7590
},
7691
}));
7792

@@ -106,24 +121,24 @@ const meta: Meta<typeof BAIVFolderSelect> = {
106121
107122
| Name | Type | Default | Description |
108123
|------|------|---------|-------------|
109-
| \`currentProjectId\` | \`string\` | - | Project ID to scope vfolder selection |
124+
| \`currentProjectId\` | \`string\` | - | Project ID to scope vfolder selection. When set, the paginated query uses \`projectVfolders\`; otherwise \`myVfolders\`. |
110125
| \`onClickVFolder\` | \`(value: string) => void\` | - | Callback when vfolder name is clicked |
111-
| \`filter\` | \`string\` | - | Additional filter string for vfolder query |
126+
| \`filter\` | \`VFolderFilter \\| null\` | - | Additional structured filter merged into the paginated query (AND-combined with internal filters) |
112127
| \`valuePropName\` | \`'id' \\| 'row_id'\` | \`'id'\` | Which field to use as option value |
113128
| \`excludeDeleted\` | \`boolean\` | \`false\` | Exclude deleted or deleting vfolders |
114129
| \`ref\` | \`React.Ref<BAIVFolderSelectRef>\` | - | Ref exposing \`refetch()\` method |
115130
116131
## Features
117-
- Fetches vfolders from two GraphQL queries:
118-
- \`BAIVFolderSelectValueQuery\`: Fetch selected vfolder labels
119-
- \`BAIVFolderSelectPaginatedQuery\`: Fetch paginated available vfolders
132+
- Strawberry V2 GraphQL queries:
133+
- \`BAIVFolderSelectValueQuery\`: Resolves preselected vfolders by id via aliased \`vfolderV2(vfolderId:)\` lookups (up to 10 slots)
134+
- \`BAIVFolderSelectPaginatedQuery\`: Paginated \`myVfolders\` / \`projectVfolders\` depending on \`currentProjectId\`
120135
- Pagination support with \`useLazyPaginatedQuery\` hook
121-
- Search functionality with debounced loading state
136+
- Search functionality with debounced loading state, using \`VFolderFilter.name.iContains\`
122137
- Infinite scroll via \`endReached\` callback
123138
- Total count footer with loading indicator
124139
- Custom label/option rendering with ID display (truncated)
125140
- Clickable vfolder names when \`onClickVFolder\` is provided
126-
- Automatic filtering of deleted vfolders when \`excludeDeleted\` is true
141+
- Automatic filtering of deleted vfolders via \`VFolderOperationStatusFilter\` when \`excludeDeleted\` is true
127142
- Project scoping through \`currentProjectId\` prop
128143
- Optimistic UI updates for smooth user experience
129144
- Exposed \`refetch()\` method via ref
@@ -133,23 +148,15 @@ const meta: Meta<typeof BAIVFolderSelect> = {
133148
### Value Query (for selected items)
134149
\`\`\`graphql
135150
query BAIVFolderSelectValueQuery(
136-
$selectedFilter: String
137-
$skipSelectedVFolder: Boolean!
138-
$scopeId: ScopeField
151+
$id0: UUID!
152+
$include0: Boolean!
153+
# ... up to $id9 / $include9
139154
) {
140-
vfolder_nodes(
141-
scope_id: $scopeId
142-
filter: $selectedFilter
143-
permission: "read_attribute"
144-
) @skip(if: $skipSelectedVFolder) {
145-
edges {
146-
node {
147-
name
148-
id
149-
row_id
150-
}
151-
}
155+
v0: vfolderV2(vfolderId: $id0) @include(if: $include0) {
156+
id
157+
metadata { name }
152158
}
159+
# ... v1..v9
153160
}
154161
\`\`\`
155162
@@ -158,26 +165,25 @@ query BAIVFolderSelectValueQuery(
158165
query BAIVFolderSelectPaginatedQuery(
159166
$offset: Int!
160167
$limit: Int!
161-
$scopeId: ScopeField
162-
$filter: String
163-
$permission: VFolderPermissionValueField
168+
$projectId: UUID!
169+
$filter: VFolderFilter
170+
$orderBy: [VFolderOrderBy!]
171+
$useProject: Boolean!
164172
) {
165-
vfolder_nodes(
166-
scope_id: $scopeId
173+
myVfolders(offset: $offset, limit: $limit, filter: $filter, orderBy: $orderBy)
174+
@skip(if: $useProject) {
175+
count
176+
edges { node { id metadata { name } } }
177+
}
178+
projectVfolders(
179+
projectId: $projectId
167180
offset: $offset
168-
first: $limit
181+
limit: $limit
169182
filter: $filter
170-
permission: $permission
171-
order: "-created_at"
172-
) {
183+
orderBy: $orderBy
184+
) @include(if: $useProject) {
173185
count
174-
edges {
175-
node {
176-
id
177-
name
178-
row_id
179-
}
180-
}
186+
edges { node { id metadata { name } } }
181187
}
182188
}
183189
\`\`\`
@@ -190,16 +196,16 @@ query BAIVFolderSelectPaginatedQuery(
190196
onChange={(value) => console.log(value)}
191197
/>
192198
193-
// With project scoping
199+
// With project scoping (uses projectVfolders)
194200
<BAIVFolderSelect
195201
currentProjectId="project-123"
196202
excludeDeleted
197203
onChange={(value) => console.log(value)}
198204
/>
199205
200-
// With clickable names
206+
// With structured filter
201207
<BAIVFolderSelect
202-
onClickVFolder={(id) => window.open(\`/vfolder/\${id}\`)}
208+
filter={{ name: { iContains: 'test' } }}
203209
onChange={(value) => console.log(value)}
204210
/>
205211
@@ -230,11 +236,11 @@ For all other props, refer to [BAISelect](/?path=/docs/components-input-baiselec
230236
description: 'Callback when vfolder name is clicked',
231237
},
232238
filter: {
233-
control: { type: 'text' },
239+
control: false,
234240
description:
235-
'Additional filter string for vfolder query (e.g., "name ilike \'%test%\'")',
241+
'Additional VFolderFilter merged into the paginated query (e.g., `{ name: { iContains: "test" } }`)',
236242
table: {
237-
type: { summary: 'string' },
243+
type: { summary: 'VFolderFilter | null' },
238244
},
239245
},
240246
valuePropName: {
@@ -308,7 +314,11 @@ export const Default: Story = {
308314
<VFolderRelayResolver
309315
mockResolvers={{
310316
Query: () => ({
311-
vfolder_nodes: {
317+
myVfolders: {
318+
count: 5,
319+
edges: sampleVFolders,
320+
},
321+
projectVfolders: {
312322
count: 5,
313323
edges: sampleVFolders,
314324
},
@@ -338,7 +348,11 @@ export const Empty: Story = {
338348
<VFolderRelayResolver
339349
mockResolvers={{
340350
Query: () => ({
341-
vfolder_nodes: {
351+
myVfolders: {
352+
count: 0,
353+
edges: [],
354+
},
355+
projectVfolders: {
342356
count: 0,
343357
edges: [],
344358
},
@@ -370,7 +384,11 @@ export const ManyVFolders: Story = {
370384
<VFolderRelayResolver
371385
mockResolvers={{
372386
Query: () => ({
373-
vfolder_nodes: {
387+
myVfolders: {
388+
count: 15,
389+
edges: sampleManyVFolders,
390+
},
391+
projectVfolders: {
374392
count: 15,
375393
edges: sampleManyVFolders,
376394
},
@@ -402,7 +420,11 @@ export const WithClickableNames: Story = {
402420
<VFolderRelayResolver
403421
mockResolvers={{
404422
Query: () => ({
405-
vfolder_nodes: {
423+
myVfolders: {
424+
count: 5,
425+
edges: sampleVFolders,
426+
},
427+
projectVfolders: {
406428
count: 5,
407429
edges: sampleVFolders,
408430
},
@@ -439,7 +461,11 @@ export const WithRowId: Story = {
439461
<VFolderRelayResolver
440462
mockResolvers={{
441463
Query: () => ({
442-
vfolder_nodes: {
464+
myVfolders: {
465+
count: 5,
466+
edges: sampleVFolders,
467+
},
468+
projectVfolders: {
443469
count: 5,
444470
edges: sampleVFolders,
445471
},

0 commit comments

Comments
 (0)