feat(frontend): wire up bulk download for selected files#707
Merged
Conversation
Pull the single-file download lifecycle (auth check, async preparation polling with timeout + abort, transient-error tolerance, decryption option pass-through) out of ObjectDownloadModal into a reusable service. The modal now calls runObjectDownloadFlow; behavior is unchanged. Adds 10 jest cases covering cached/uncached, async polling, timeout, abort mid-poll, transient errors, and anonymous users. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The "Download" button on FileTable's selection toolbar previously had no onClick handler. This commit adds BulkObjectDownloadModal: a sequential queue that reuses objectDownloadFlow per item, with per-row progress, gating for insecure / encrypted items (UI gate + runtime re-check as defense in depth), retry-failed, aria-live status, and backgrounding of remaining items via createAsyncDownload + pendingAutoDownloads when the user closes the modal mid-queue. Pure helpers (resolveEncryptionOptions, shouldSkipInsecure, shouldSkipEncrypted, itemIsRunnable, hasEncryption) live in a new services/bulkObjectDownload module with 18 jest cases. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
👷 Deploy request for auto-drive-storage pending review.Visit the deploys page to approve it
|
👷 Deploy request for auto-drive-demo pending review.Visit the deploys page to approve it
|
Member
|
bugbot run |
closeModal unconditionally queued createAsyncDownload for every pending item, so dismissing the bulk download modal without clicking Start Download still kicked off background async downloads and showed the "will continue in the background" toast. Gate the backgrounding logic behind an isRunning check so it only runs when the queue was actually started. Co-authored-by: Cursor <cursoragent@cursor.com>
Member
|
bugbot run |
runObjectDownloadFlow did not check the AbortSignal after fetchFile returned, so closing the bulk modal during an active download let the fetch finish and emit a "downloaded" toast even though the user had already dismissed the UI. Add assertNotAborted after fetchFile so the flow throws ObjectDownloadAbortedError, which callers already handle by breaking without a toast. Co-authored-by: Cursor <cursoragent@cursor.com>
backgroundedCount was incremented synchronously before each createAsyncDownload promise resolved, so failed API calls were still counted in the "will continue in the background" toast. Collect the promises and defer the toast until Promise.all settles, counting only the calls that actually succeeded. Co-authored-by: Cursor <cursoragent@cursor.com>
Member
|
bugbot run |
closeModal only backgrounded items with status 'pending' and the item tracked in currentAsyncItemRef (set during 'preparing'). A file in the 'checking' phase — after its status was updated but before async preparation began — was silently dropped on close. Include 'checking' items alongside 'pending' in the backgrounding filter so they get createAsyncDownload + addPendingAutoDownload like any other not-yet- started item. Co-authored-by: Cursor <cursoragent@cursor.com>
…lose After createAsyncDownload succeeded for backgrounded items, updateAsyncDownloads was never called, so the Cached Downloads tray wouldn't see the new server-side records until its next 10s poll. Call updateAsyncDownloads once the background promises settle so pending auto-downloads can match immediately. Co-authored-by: Cursor <cursoragent@cursor.com>
Member
|
bugbot run |
When a bulk queue item failed during async preparation, currentAsyncItemRef was never cleared because the onPhaseChange callback that clears it was not invoked after the throw. If the next item was in checking phase when the user closed the modal, closeModal would call addPendingAutoDownload for the already-failed item. Clear the ref in the catch block when it still points at the failed item. Co-authored-by: Cursor <cursoragent@cursor.com>
Member
|
bugbot run |
…heck phase Exclude the actively-processing item (tracked by currentAsyncItemRef) from remainingNotStarted so closeModal does not call createAsyncDownload for an item whose runObjectDownloadFlow may already be creating one. Co-authored-by: Cursor <cursoragent@cursor.com>
Member
|
bugbot run |
…Close run Only set isDownloading to true on the 'downloading' phase instead of toggling it for every phase change. The 'completed' phase was resetting it to false, which made shouldShowModal false and unmounted the modal before the 250ms delay, success toast, and onClose() could execute. Co-authored-by: Cursor <cursoragent@cursor.com>
…fails Revert phase from 'preparing' to 'checking' when createAsyncDownload fails, so the fallback transition to 'downloading' does not trigger the "is ready — downloading now" toast meant for successful async prep. Co-authored-by: Cursor <cursoragent@cursor.com>
Member
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 88b2be5. Configure here.
EmilFattakhov
approved these changes
May 27, 2026
Member
EmilFattakhov
left a comment
There was a problem hiding this comment.
Thanks very much for your PR @skyvon97, it's an excellent UI improvement!
Your PR will be considered for the SF Contribution Contest 😉
EmilFattakhov
added a commit
that referenced
this pull request
May 27, 2026
* fix(s3): advance ListObjectsV2 continuation token past folded prefix When buildListResult exhausts the DB batch with every row folding into a single CommonPrefix, the truncation fallback set the continuation token to the last raw key scanned. That key lives *inside* the folded virtual directory, so the next page's `key > token` query returned the remaining keys in the same directory and re-emitted the same CommonPrefixes entry. Detect when the last scanned key folded into a CommonPrefix and advance the token to `<foldedPrefix>+ï¿¿` so the next page resumes after the entire virtual directory. Non-delimiter and non-folded cases keep the previous last-key fallback. Includes three unit tests covering the duplicate-prefix regression, the non-folded last-key case, and the no-delimiter case. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Merge pull request #713 from autonomys/fix/null-encoded-node-crash fix(backend): guard against NULL encoded_node in publishing pipeline * fix(backend): clear encoded_node when recovery-published node's object is already archived After onObjectArchived runs, removeNodeDataByRootCid only strips encoded_node for nodes already published (block_published_on IS NOT NULL). Nodes published later via recovery get block_published_on set but never lose encoded_node, causing indefinite local payload retention. Fix updateNodePublishedOn to conditionally clear encoded_node when the node's metadata is already marked archived. This is safe because at that point the data is confirmed on-chain and the archival cleanup won't fire again. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(s3): support AWS CLI clients (dispatch fallbacks + raw body) The S3 API only worked with clients that send the `x-id` query param and a Content-Type header (e.g. the AWS JS SDK). The AWS CLI / botocore omits both, which broke GetObject/PutObject/HeadObject and multipart uploads: - getS3Method had no GET->GetObject or PUT->PutObject fallback, so plain object requests without `x-id` returned "Method not found". - The S3 raw body parser used `type: '*/*'`, but type-is returns false when no Content-Type header is present, so the request body was never read and req.body was left as {} (planted by the global express.json() parser), causing a Buffer.concat TypeError deep in the upload path. Fixes: - Add GET->GetObject and PUT->PutObject method fallbacks (after the existing multipart/list-type query-param checks so they are not misrouted). - Read the S3 request body unconditionally (`type: () => true`) — object bodies are opaque binary regardless of Content-Type. - Mount the S3 controller ahead of the global JSON/urlencoded parsers in the download API so those never run for /s3. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Merge pull request #707 from skyvon97/fix/bulk-download-flow feat(frontend): wire up bulk download for selected files * test(s3): cover AWS CLI style requests (no x-id, no Content-Type) Adds raw-HTTP integration cases alongside the SDK suite that reproduce the AWS CLI / botocore behaviour the SDK masks: no `x-id` query param and object bodies sent without a Content-Type header. Covers single PutObject/GetObject round-trip and a full multipart round-trip. These fail on the prior code (GetObject/PutObject -> "Method not found"; UploadPart -> Buffer.concat on a {} body) and pass with the dispatch + raw body fixes. They live in the s3-sdk spec to share its server/auth harness. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Jim Counter <jimcounter@hotmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: skyvon97 <89288441+skyvon97@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The "Download" button on the FileTable selection toolbar currently renders on selection but has no
onClick. This wires it up.BulkObjectDownloadModal: serial queue with per-row progress, gating for insecure / encrypted items, retry-failed, and backgrounding of remaining items when you close mid-queue.services/objectDownloadFlowsoObjectDownloadModaland the bulk modal share one implementation.resolveEncryptionOptions, predicates for gating) split intoservices/bulkObjectDownloadwith unit tests.Test plan
Local:
yarn frontend lint,yarn frontend test(129 pass, 28 new),yarn frontend buildall green.To exercise end-to-end:
Note:
services.ymlis scoped to backend/auth/.github, so this PR won't show lint/test status checks. Happy to follow up with a workflow tweak.