Skip to content

Conversation

@nowgnuesLee
Copy link
Contributor

@nowgnuesLee nowgnuesLee commented Dec 15, 2025

resolves #4830 (FR-1792)

  • add download vfolder button to data page
  • Enhance the UI for the download and delete buttons

Notice
Since the backend sends the downloaded file with the name vfolderId.zip, it is not possible to set the file name to the vfolder name.

CleanShot 2025-12-15 at 15.46.01@2x.png

CleanShot 2025-12-15 at 15.46.11@2x.png

Checklist: (if applicable)

  • Documentation
  • Minium required manager version
  • Specific setting for review (eg., KB link, endpoint or how to setup)
  • Minimum requirements to check during review
  • Test case(s) to demonstrate the difference of before/after

@github-actions github-actions bot added the size:L 100~500 LoC label Dec 15, 2025
Copy link
Contributor Author


How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • flow:merge-queue - adds this PR to the back of the merge queue
  • flow:hotfix - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has required the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions
Copy link
Contributor

Coverage report for ./packages/backend.ai-ui

St.
Category Percentage Covered / Total
🔴 Statements
52.17% (-2.61% 🔻)
168/322
🔴 Branches
32.29% (-0.45% 🔻)
93/288
🔴 Functions
40.26% (-2.21% 🔻)
31/77
🔴 Lines
53.55% (-3.27% 🔻)
151/282
Show files with reduced coverage 🔻
St.
File Statements Branches Functions Lines
🔴 helper/index.ts
57.86% (-6.42% 🔻)
43.75% (-1.12% 🔻)
51.61% (-7.65% 🔻)
59.85% (-8.57% 🔻)

Test suite run success

62 tests passing in 4 suites.

Report generated by 🧪jest coverage report action from 65df5a3

@nowgnuesLee nowgnuesLee marked this pull request as ready for review December 15, 2025 06:50
Copilot AI review requested due to automatic review settings December 15, 2025 06:50
@github-actions
Copy link
Contributor

Coverage report for ./react

St.
Category Percentage Covered / Total
🔴 Statements
4.37% (-0% 🔻)
517/11843
🔴 Branches
3.58% (-0% 🔻)
298/8331
🔴 Functions 2.62% 95/3625
🔴 Lines
4.34% (-0% 🔻)
503/11583

Test suite run success

145 tests passing in 14 suites.

Report generated by 🧪jest coverage report action from 65df5a3

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a download button for virtual folders on the Data page, allowing users to download entire vfolders as zip archives. The implementation extracts a reusable initiateDownload helper function and enhances the UI styling for both download and delete buttons to provide better visual feedback when disabled.

Key changes:

  • New BAIVFolderDownloadButton component for downloading vfolders
  • Extracted initiateDownload helper function for reusability across components
  • Enhanced UI styling for download and delete buttons with proper disabled states

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
react/src/components/VFolderNodes.tsx Added download button to vfolder actions, integrated permission checks using useMergedAllowedStorageHostPermission hook, and refactored editable variable for better code clarity
packages/backend.ai-ui/src/helper/index.ts Extracted initiateDownload helper function from FileItemControls to enable reuse across components
packages/backend.ai-ui/src/components/fragments/index.ts Added exports for new BAIVFolderDownloadButton component and its props interface
packages/backend.ai-ui/src/components/fragments/BAIVFolderDownloadButton.tsx New button component for downloading vfolders with styled icon and background based on disabled state
packages/backend.ai-ui/src/components/baiClient/FileExplorer/FileItemControls.tsx Refactored to use extracted initiateDownload helper and enhanced icon colors to properly reflect disabled state

Comment on lines +23 to +37
try {
const tokenResponse = await baiClient.vfolder.request_download_token(
'./',
vfolderId,
true,
);
const downloadParams = new URLSearchParams({
token: tokenResponse.token,
archive: 'true',
});
const downloadURL = `${tokenResponse.url}?${downloadParams.toString()}`;
await initiateDownload(downloadURL, `${vfolderName}.zip`);
} catch (error) {
throw error;
}
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The try-catch block is redundant because it simply re-throws the error without any additional handling. Since the mutation errors will be handled by the useMutation hook's error handling mechanism, this try-catch can be removed and the mutationFn can directly perform the operations and let errors propagate naturally.

Suggested change
try {
const tokenResponse = await baiClient.vfolder.request_download_token(
'./',
vfolderId,
true,
);
const downloadParams = new URLSearchParams({
token: tokenResponse.token,
archive: 'true',
});
const downloadURL = `${tokenResponse.url}?${downloadParams.toString()}`;
await initiateDownload(downloadURL, `${vfolderName}.zip`);
} catch (error) {
throw error;
}
const tokenResponse = await baiClient.vfolder.request_download_token(
'./',
vfolderId,
true,
);
const downloadParams = new URLSearchParams({
token: tokenResponse.token,
archive: 'true',
});
const downloadURL = `${tokenResponse.url}?${downloadParams.toString()}`;
await initiateDownload(downloadURL, `${vfolderName}.zip`);

Copilot uses AI. Check for mistakes.
Comment on lines +425 to +427
*
* @param downloadURL
* @param fileName
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSDoc comment for the initiateDownload function is incomplete. It should include a description of what the function does, document the return type (Promise that resolves when download is initiated), and explain the iOS Safari special handling. Consider following the style of other JSDoc comments in this file with proper descriptions and examples.

Suggested change
*
* @param downloadURL
* @param fileName
* Initiates a file download in the browser for the given URL and file name.
*
* This function programmatically triggers a download of the resource at `downloadURL`
* and saves it as `fileName`. On iOS Safari, due to download restrictions, it opens
* the URL in a new tab instead of using the download attribute.
*
* @param downloadURL - The URL of the file to download.
* @param fileName - The desired name for the downloaded file.
* @returns A Promise that resolves when the download is initiated.
*
* @example
* ```typescript
* await initiateDownload('https://example.com/file.txt', 'myfile.txt');
* ```
*
* @remarks
* - On iOS Safari, the function uses `window.open` to open the file in a new tab,
* as the download attribute is not supported for programmatic downloads.
* - On other browsers, it creates a temporary anchor element with the `download`
* attribute to trigger the download.

Copilot uses AI. Check for mistakes.
Comment on lines +437 to +438
const newWindow = window.open(downloadURL, '_blank');
newWindow && resolve();
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The iOS Safari check could fail silently if window.open is blocked by popup blockers. When newWindow is null (popup blocked), the promise resolves without actually initiating the download. Consider rejecting the promise or throwing an error when newWindow is null to properly handle this case.

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +17
const BAIVFolderDownloadButton = ({
vfolderId,
vfolderName,
...buttonProps
}: BAIVFolderDownloadButtonProps) => {
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new component is missing the 'use memo' directive. According to the React Component Guidelines for this project, new React components should include the 'use memo' directive at the top of the component function to leverage React Compiler optimizations. This directive should be added immediately after the opening brace of the component function.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +21 to +39
const { mutateAsync } = useMutation({
mutationFn: async () => {
try {
const tokenResponse = await baiClient.vfolder.request_download_token(
'./',
vfolderId,
true,
);
const downloadParams = new URLSearchParams({
token: tokenResponse.token,
archive: 'true',
});
const downloadURL = `${tokenResponse.url}?${downloadParams.toString()}`;
await initiateDownload(downloadURL, `${vfolderName}.zip`);
} catch (error) {
throw error;
}
},
});
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mutation lacks error handling and user feedback. Unlike the similar FileItemControls component which provides success/error messages using onSuccess and onError callbacks with the useMutation hook, this component silently fails if the download fails. Add onSuccess callback to show a success message (using the existing 'comp:FileExplorer.DownloadStarted' translation key with the vfolder name) and onError callback to display error messages to the user.

Copilot uses AI. Check for mistakes.
@nowgnuesLee nowgnuesLee marked this pull request as draft December 16, 2025 05:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100~500 LoC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

add vfolder download button to Data page

2 participants