Skip to content

Download as zip#1079

Merged
garronej merged 1 commit into
mainfrom
download_as_zip
Jun 19, 2026
Merged

Download as zip#1079
garronej merged 1 commit into
mainfrom
download_as_zip

Conversation

@garronej

@garronej garronej commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

Release Notes

  • New Features
    • Added bulk ZIP download: you can now select multiple S3 objects and folders and download them together as a single ZIP archive.
  • Enhancements
    • Updated the download flow to handle multi-item selections consistently, while preserving the dedicated path for single-object downloads.
    • Improved ZIP path handling to keep archive entries clean and safe.
  • Chores
    • Updated streaming support and ZIP creation tooling to enable reliable streamed downloads.

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5f95ec20-8a1b-4ec6-99a8-aaec6019ca51

📥 Commits

Reviewing files that changed from the base of the PR and between e09f8d2 and a2f2708.

⛔ Files ignored due to path filters (1)
  • web/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (11)
  • web/nginx.conf
  • web/package.json
  • web/public/streamsaver/mitm.html
  • web/public/streamsaver/sw.js
  • web/src/core/usecases/s3ExplorerUiController/decoupledLogic/downloadAsZip.ts
  • web/src/core/usecases/s3ExplorerUiController/thunks.ts
  • web/src/streamsaver.d.ts
  • web/src/ui/pages/s3Explorer/Page.tsx
  • web/src/ui/shared/codex/S3ExplorerMainView/S3ExplorerMainView.spec.md
  • web/src/ui/shared/codex/S3ExplorerMainView/S3ExplorerMainView.stories.tsx
  • web/src/ui/shared/codex/S3ExplorerMainView/S3ExplorerMainView.tsx

📝 Walkthrough

Walkthrough

Adds ZIP download capability to the S3 Explorer. A StreamSaver service worker (sw.js) and MITM page are bundled under public/streamsaver/, with an nginx location block serving them no-cache. A new downloadS3UrisAsZip function crawls S3 prefixes, sanitises archive paths, and streams a ZIP via @zip.js/zip.js. The onDownload UI callback is generalised from a single object URI to a list of URIs.

Changes

ZIP Download Feature

Layer / File(s) Summary
StreamSaver service worker infrastructure
web/package.json, web/src/streamsaver.d.ts, web/nginx.conf, web/public/streamsaver/mitm.html, web/public/streamsaver/sw.js
Adds streamsaver and @zip.js/zip.js dependencies, TypeScript declarations for the streamsaver module, an nginx location block for sw.js with no-cache headers and Service-Worker-Allowed scope, the MITM page that registers/reuses the service worker and forwards MessageChannel payloads to it, and the service worker itself that maps download URLs to ReadableStreams and serves them on fetch.
downloadS3UrisAsZip — S3 crawl, path helpers, and ZIP streaming
web/src/core/usecases/s3ExplorerUiController/decoupledLogic/downloadAsZip.ts
New module implementing recursive S3 prefix crawling, archive-path deduplication and sanitisation, ZIP-safe collision handling, writable-stream acquisition via streamsaver, and streaming of directory/object entries into a ZipWriter; falls back to HTTP fetch when getObjectContent is unavailable.
downloadAsZip Redux thunk
web/src/core/usecases/s3ExplorerUiController/thunks.ts
Adds the downloadAsZip thunk that reads state for profileName and current s3Uri, delegates to downloadS3UrisAsZip, and maps errors to evtDisplayError while silently ignoring AbortError.
UI: bulk download callback and S3ExplorerMainView generalisation
web/src/ui/shared/codex/S3ExplorerMainView/S3ExplorerMainView.spec.md, web/src/ui/shared/codex/S3ExplorerMainView/S3ExplorerMainView.tsx, web/src/ui/shared/codex/S3ExplorerMainView/S3ExplorerMainView.stories.tsx, web/src/ui/pages/s3Explorer/Page.tsx
onDownload callback signature changed from { s3Uri } to { s3Uris: S3Uri[] }; requestDownloadForItems now passes all eligible URIs in one call and enables download for folder rows; spec.md and Storybook stories updated; Page.tsx adds an onDownload dispatcher that routes single-object calls to downloadObject and all other cases to downloadAsZip.

Sequence Diagram

sequenceDiagram
  participant UI as S3ExplorerMainView
  participant Page as Page.tsx (onDownload)
  participant Thunk as downloadAsZip thunk
  participant Fn as downloadS3UrisAsZip
  participant SS as streamsaver (mitm.html + sw.js)
  participant S3 as S3Client
  participant Zip as ZipWriter

  UI->>Page: onDownload({ s3Uris })
  alt single non-folder URI
    Page->>Thunk: downloadObject({ s3Uri })
  else multiple or folder URIs
    Page->>Thunk: downloadAsZip({ s3Uris })
    Thunk->>Fn: downloadS3UrisAsZip(...)
    Fn->>SS: createWriteStream(filename.zip)
    SS-->>Fn: WritableStream
    Fn->>S3: listObjects / crawl prefixes
    S3-->>Fn: directories + objects
    Fn->>Zip: add directories
    loop each object
      Fn->>S3: getObjectContent or HTTP fetch
      S3-->>Fn: ReadableStream
      Fn->>Zip: add(path, stream)
    end
    Fn->>Zip: close()
    Zip-->>SS: piped bytes → browser download
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 Hop, hop, zip it up tight,
Folders and files in one bundled delight!
A service worker whispers, "the stream is prepared,"
The S3 rabbit has many objects to share.
No single file lonely — they all zip away,
With streamsaver magic on a bright fluffy day! 🗜️

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Download as zip' directly and accurately summarizes the main feature being added: multi-file ZIP download functionality for S3 objects.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch download_as_zip

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@web/src/core/usecases/s3ExplorerUiController/decoupledLogic/downloadAsZip.ts`:
- Around line 302-309: The unbounded Promise.all() call when processing
result.prefixes in the crawlPrefix recursive logic creates excessive concurrent
requests to listObjects, which can freeze or timeout the ZIP operation. Replace
the direct Promise.all() approach with a shared bounded-concurrency control
mechanism (such as a semaphore or queue worker pool) that limits the number of
concurrent crawlPrefix calls across all recursive levels. This ensures that
regardless of the folder tree depth, the total concurrency remains controlled
and prevents request storms.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1437e3ea-e954-451d-b277-e19e63dc758f

📥 Commits

Reviewing files that changed from the base of the PR and between a61ce64 and e09f8d2.

⛔ Files ignored due to path filters (1)
  • web/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (11)
  • web/nginx.conf
  • web/package.json
  • web/public/streamsaver/mitm.html
  • web/public/streamsaver/sw.js
  • web/src/core/usecases/s3ExplorerUiController/decoupledLogic/downloadAsZip.ts
  • web/src/core/usecases/s3ExplorerUiController/thunks.ts
  • web/src/streamsaver.d.ts
  • web/src/ui/pages/s3Explorer/Page.tsx
  • web/src/ui/shared/codex/S3ExplorerMainView/S3ExplorerMainView.spec.md
  • web/src/ui/shared/codex/S3ExplorerMainView/S3ExplorerMainView.stories.tsx
  • web/src/ui/shared/codex/S3ExplorerMainView/S3ExplorerMainView.tsx

Comment on lines +302 to +309
const nestedEntries = await Promise.all(
result.prefixes.map(s3Uri =>
crawlPrefix({
s3Client,
s3UriPrefix: s3Uri
})
)
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Bound recursive prefix crawling concurrency

Line 302 currently expands child prefixes with unbounded Promise.all. On large folder trees, this creates a request storm against listObjects and can freeze/timeout the ZIP flow. Please switch to a shared bounded-concurrency crawl (queue/semaphore) across the whole traversal.

💡 Suggested direction
-    const nestedEntries = await Promise.all(
-        result.prefixes.map(s3Uri =>
-            crawlPrefix({
-                s3Client,
-                s3UriPrefix: s3Uri
-            })
-        )
-    );
+    const nestedEntries = await crawlPrefixesWithSharedLimiter({
+        s3Client,
+        prefixes: result.prefixes
+    });

Use a single shared limiter (or iterative queue worker pool) so recursive levels do not multiply concurrency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/core/usecases/s3ExplorerUiController/decoupledLogic/downloadAsZip.ts`
around lines 302 - 309, The unbounded Promise.all() call when processing
result.prefixes in the crawlPrefix recursive logic creates excessive concurrent
requests to listObjects, which can freeze or timeout the ZIP operation. Replace
the direct Promise.all() approach with a shared bounded-concurrency control
mechanism (such as a semaphore or queue worker pool) that limits the number of
concurrent crawlPrefix calls across all recursive levels. This ensures that
regardless of the folder tree depth, the total concurrency remains controlled
and prevents request storms.

@garronej garronej merged commit 4c5ac79 into main Jun 19, 2026
4 of 5 checks passed
@garronej garronej deleted the download_as_zip branch June 19, 2026 21:56
@sonarqubecloud

Copy link
Copy Markdown

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.

1 participant