Skip to content

feat(frontend): wire up bulk download for selected files#707

Merged
EmilFattakhov merged 11 commits into
autonomys:mainfrom
skyvon97:fix/bulk-download-flow
May 27, 2026
Merged

feat(frontend): wire up bulk download for selected files#707
EmilFattakhov merged 11 commits into
autonomys:mainfrom
skyvon97:fix/bulk-download-flow

Conversation

@skyvon97
Copy link
Copy Markdown
Contributor

@skyvon97 skyvon97 commented May 22, 2026

The "Download" button on the FileTable selection toolbar currently renders on selection but has no onClick. This wires it up.

  • New BulkObjectDownloadModal: serial queue with per-row progress, gating for insecure / encrypted items, retry-failed, and backgrounding of remaining items when you close mid-queue.
  • Extracted the single-file download lifecycle into services/objectDownloadFlow so ObjectDownloadModal and the bulk modal share one implementation.
  • Pure helpers (resolveEncryptionOptions, predicates for gating) split into services/bulkObjectDownload with unit tests.

Test plan

Local: yarn frontend lint, yarn frontend test (129 pass, 28 new), yarn frontend build all green.

To exercise end-to-end:

  • Select a mix of plaintext + encrypted files, click Download — gating UI appears, Start disabled until resolved.
  • Close mid-queue — remaining items show up in the async-downloads tray.
  • Reopen with same selection — metadata refetches, no stale cache.

Note: services.yml is scoped to backend/auth/.github, so this PR won't show lint/test status checks. Happy to follow up with a workflow tweak.

skyvon97 and others added 2 commits May 22, 2026 02:16
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>
@netlify
Copy link
Copy Markdown

netlify Bot commented May 22, 2026

👷 Deploy request for auto-drive-storage pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit 88b2be5

@netlify
Copy link
Copy Markdown

netlify Bot commented May 22, 2026

👷 Deploy request for auto-drive-demo pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit 88b2be5

@EmilFattakhov EmilFattakhov self-requested a review May 22, 2026 12:57
@EmilFattakhov
Copy link
Copy Markdown
Member

bugbot run

Comment thread apps/frontend/src/components/molecules/BulkObjectDownloadModal.tsx
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>
@EmilFattakhov
Copy link
Copy Markdown
Member

bugbot run

Comment thread apps/frontend/src/components/molecules/BulkObjectDownloadModal.tsx
Comment thread apps/frontend/src/components/molecules/BulkObjectDownloadModal.tsx Outdated
emil and others added 2 commits May 26, 2026 15:26
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>
@EmilFattakhov
Copy link
Copy Markdown
Member

bugbot run

Comment thread apps/frontend/src/components/molecules/BulkObjectDownloadModal.tsx
Comment thread apps/frontend/src/components/molecules/BulkObjectDownloadModal.tsx
emil and others added 2 commits May 26, 2026 15:30
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>
@EmilFattakhov
Copy link
Copy Markdown
Member

bugbot run

Comment thread apps/frontend/src/components/molecules/BulkObjectDownloadModal.tsx
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>
@EmilFattakhov
Copy link
Copy Markdown
Member

bugbot run

Comment thread apps/frontend/src/components/molecules/BulkObjectDownloadModal.tsx
…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>
@EmilFattakhov
Copy link
Copy Markdown
Member

bugbot run

Comment thread apps/frontend/src/components/molecules/ObjectDownloadModal.tsx Outdated
Comment thread apps/frontend/src/components/molecules/ObjectDownloadModal.tsx
emil and others added 2 commits May 27, 2026 09:24
…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>
@EmilFattakhov
Copy link
Copy Markdown
Member

bugbot run

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

✅ 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.

Copy link
Copy Markdown
Member

@EmilFattakhov EmilFattakhov left a comment

Choose a reason for hiding this comment

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

Thanks very much for your PR @skyvon97, it's an excellent UI improvement!

Your PR will be considered for the SF Contribution Contest 😉

@EmilFattakhov EmilFattakhov merged commit 7019cfa into autonomys:main May 27, 2026
3 checks passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants