Skip to content

feat(uploads-manager): add upload progress and time left to uploads manager#4720

Open
pierre-pigeon wants to merge 10 commits into
box:masterfrom
pierre-pigeon:add-counters-to-uploading-file
Open

feat(uploads-manager): add upload progress and time left to uploads manager#4720
pierre-pigeon wants to merge 10 commits into
box:masterfrom
pierre-pigeon:add-counters-to-uploading-file

Conversation

@pierre-pigeon

@pierre-pigeon pierre-pigeon commented Jul 23, 2026

Copy link
Copy Markdown

This PR enriches the modernized uploads manager with per-item byte progress and a smoothed time-remaining (ETA) estimate.

ContentUploader now tracks bytesUploaded/totalBytes from upload progress events and computes an EMA-smoothed ETA.

Summary by CodeRabbit

  • New Features
    • Added an optional feature flag to enable upload ETA display.
    • When enabled, the uploader surfaces byte-level progress (uploaded/total) and a smoothed estimated time remaining.
    • Updated Storybook with an “uploads in progress” example showing ETA-related fields.
  • Bug Fixes
    • Upload reset now fully clears prior byte/ETA state, and progress handling ignores invalid totals.
  • Tests
    • Added coverage for ETA calculations, progress/byte tracking, state resets, and the ETA feature flag behavior.

@pierre-pigeon
pierre-pigeon requested review from a team as code owners July 23, 2026 13:41
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


pgolebiowski seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@socket-security

socket-security Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatednpm/​@​box/​uploads-manager@​1.17.2 ⏵ 2.4.07710010099 +150

View full report

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

ContentUploader now supports optional upload ETA and byte-progress telemetry. ETA state is sampled and smoothed per upload, reset between attempts, and conditionally mapped into modernized upload items. Types, dependency versions, tests, and a Storybook example were updated.

Changes

Upload ETA integration

Layer / File(s) Summary
Upload contracts and modernized mapping
src/common/types/upload.js, src/elements/content-uploader/utils/mapToModernizedUploadItem.ts, src/elements/content-uploader/utils/__tests__/mapToModernizedUploadItem.test.ts, package.json
UploadItem now supports byte and ETA fields. Modernized mapping forwards them only when the ETA flag is enabled, with status-based completion handling. The uploads manager dependency is upgraded to ^2.4.0.
ETA calculation state
src/elements/content-uploader/utils/uploadEta.ts, src/elements/content-uploader/utils/__tests__/uploadEta.test.ts
ETA sampling maintains a sliding window, throttles frequent updates, applies smoothing, and exposes remaining milliseconds after the warm-up period.
Uploader progress and rendering integration
src/elements/content-uploader/ContentUploader.tsx, src/elements/content-uploader/__tests__/ContentUploader.test.js, src/elements/content-uploader/stories/ContentUploader.stories.js
ContentUploader tracks per-item ETA state, updates byte and ETA fields during progress, clears them on reset, passes the feature flag to item mapping, and adds an in-progress Storybook example.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant UploadEvent
  participant ContentUploader
  participant updateEta
  participant mapToModernizedUploadItems
  participant UploadsManager
  UploadEvent->>ContentUploader: loaded and total progress
  ContentUploader->>updateEta: update per-item ETA state
  updateEta-->>ContentUploader: smoothed remainingMs
  ContentUploader->>mapToModernizedUploadItems: upload items and ETA flag
  mapToModernizedUploadItems-->>UploadsManager: byte and ETA fields
Loading

Possibly related PRs

Suggested labels: ready-to-merge

Suggested reviewers: olehrybak, jpan-box, tjuanitas

Poem

I’m a rabbit counting bytes in a row,
Smoothing the seconds as uploads go.
ETA sprouts where progress streams,
Resetting cleanly between retry dreams.
Hop, hop—modern uploads glow!

🚥 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
Title check ✅ Passed The title clearly summarizes the main change: adding per-item upload progress and ETA to the uploads manager.
Description check ✅ Passed The description covers the main feature change and implementation details, and the template contains no additional required fill-in sections.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Checkov (3.3.8)
package.json

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

🔧 Biome (2.5.3)
src/common/types/upload.js

File contains syntax errors that prevent linting: Line 10: 'import type' are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 13: type alias are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 21: type alias are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 29: type alias are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 31: type alias are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 39: type alias are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 44: type alias are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 49: type alias are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 53: type alias are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 65: type alias are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 84: type alias are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 93: type alias are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 100: type alias are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 104: 'export type' declarations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

🔧 ast-grep (0.44.1)
src/elements/content-uploader/__tests__/ContentUploader.test.js

ast-grep timed out on this file

src/elements/content-uploader/stories/ContentUploader.stories.js

ast-grep retry budget exhausted before isolating this batch

src/elements/content-uploader/utils/__tests__/mapToModernizedUploadItem.test.ts

ast-grep retry budget exhausted before isolating this batch

  • 3 others

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.

@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: 2

🤖 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 `@src/elements/content-uploader/utils/mapToModernizedUploadItem.ts`:
- Around line 63-65: Update mapToModernizedUploadItem so items with
item.isFolder do not populate totalBytes, bytesUploaded, or remainingMs,
regardless of upload status or ETA configuration. Preserve the existing byte and
ETA calculations for non-folder file items.
- Around line 64-66: Update the remainingMs mapping in mapToModernizedUploadItem
so errored and canceled statuses return undefined rather than forwarding the
stale fileItem.remainingMs value. Preserve the existing ETA for active,
non-terminal upload statuses and keep staged/complete behavior unchanged.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7753101d-bae6-4a3b-a09d-255e8550dfac

📥 Commits

Reviewing files that changed from the base of the PR and between e8139fd and 821a657.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (9)
  • package.json
  • src/common/types/upload.js
  • src/elements/content-uploader/ContentUploader.tsx
  • src/elements/content-uploader/__tests__/ContentUploader.test.js
  • src/elements/content-uploader/stories/ContentUploader.stories.js
  • src/elements/content-uploader/utils/__tests__/mapToModernizedUploadItem.test.ts
  • src/elements/content-uploader/utils/__tests__/uploadEta.test.ts
  • src/elements/content-uploader/utils/mapToModernizedUploadItem.ts
  • src/elements/content-uploader/utils/uploadEta.ts

Comment on lines +63 to +65
const totalBytes = fileItem.totalBytes ?? item.size;
const isFullyUploaded = status === 'staged' || status === 'complete';
const bytesUploaded = isFullyUploaded && totalBytes != null ? totalBytes : fileItem.bytesUploaded;

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether progressCallback / handleUploadProgress is wired for folder uploads
# with real aggregate totals rather than the placeholder size of 1.
rg -n -A10 'progressCallback' src/elements/content-uploader/ContentUploader.tsx
rg -n 'class FolderUpload' -A30 src/elements/content-uploader

Repository: box/box-ui-elements

Length of output: 1242


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files in content-uploader:"
git ls-files 'src/elements/content-uploader/*' | sed -n '1,200p'

echo
echo "Locate handleUploadProgress and size/bytes fields:"
rg -n 'handleUploadProgress|totalBytes|bytesUploaded|isUploadEtaEnabled|isFolder|STATUS_IN_PROGRESS|STATUS_STAGED|complete' src/elements/content-uploader

echo
echo "Outline ContentUploader.tsx around relevant lines:"
wc -l src/elements/content-uploader/ContentUploader.tsx
sed -n '540,670p' src/elements/content-uploader/ContentUploader.tsx
echo
sed -n '920,1020p' src/elements/content-uploader/ContentUploader.tsx

echo
echo "Outline mapToModernizedUploadItem.ts:"
wc -l src/elements/content-uploader/utils/mapToModernizedUploadItem.ts
cat -n src/elements/content-uploader/utils/mapToModernizedUploadItem.ts

Repository: box/box-ui-elements

Length of output: 48163


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate FolderUpload class:"
rg -n 'class FolderUpload|function FolderUpload|export class FolderUpload|FolderUpload' src -g '*.ts' -g '*.tsx' -g '*.js'

echo
for f in $(rg -l 'class FolderUpload|folderUploads\.setBytesUploaded|setBytesUploaded|totalUploadedBytes|totalBytes|bytesUploaded|progressCallback' src/common src/elements/content-uploader); do
  echo "===== $f ====="
  wc -l "$f"
  rg -n 'class FolderUpload|function FolderUpload|setBytesUploaded|totalUploadedBytes|totalBytes|bytesUploaded|progressCallback|emitProgress|setComplete|complete' "$f" || true
done

echo
echo "Inspect candidate FolderUpload files:"
find src -path '*FolderUpload*' -o -path '*folderUploads*' | while read -r f; do
  echo "---- $f"
  sed -n '1,220p' "$f"
done

Repository: box/box-ui-elements

Length of output: 49676


Skip byte/ETA fields for isFolder items.

Folder roots are queued with size: 1 and never receive real folder aggregate progress via handleUploadProgress; with isUploadEtaEnabled enabled this mapper can expose “0 of 1 bytes” for that row. Treat folder items as non-file data and don’t populate bytesUploaded, totalBytes, or remainingMs for item.isFolder.

🤖 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 `@src/elements/content-uploader/utils/mapToModernizedUploadItem.ts` around
lines 63 - 65, Update mapToModernizedUploadItem so items with item.isFolder do
not populate totalBytes, bytesUploaded, or remainingMs, regardless of upload
status or ETA configuration. Preserve the existing byte and ETA calculations for
non-folder file items.

Comment on lines +64 to +66
const isFullyUploaded = status === 'staged' || status === 'complete';
const bytesUploaded = isFullyUploaded && totalBytes != null ? totalBytes : fileItem.bytesUploaded;
const remainingMs = !isFullyUploaded ? fileItem.remainingMs : undefined;

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate STATUS_MAP definition to confirm the modernized status literals.
rg -n -B2 -A15 'STATUS_MAP' src/elements/content-uploader --type=ts

Repository: box/box-ui-elements

Length of output: 4007


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the mapper implementation and nearby status-related code.
wc -l src/elements/content-uploader/utils/mapToModernizedUploadItem.ts
sed -n '1,120p' src/elements/content-uploader/utils/mapToModernizedUploadItem.ts

# Search for legacy status constants and remainingMs handling.
rg -n "STATUS_PENDING|STATUS_IN_PROGRESS|STATUS_STAGED|STATUS_COMPLETE|STATUS_ERROR|STATUS_CANCELED|remainingMs|bytesUploaded|totalBytes" src/elements/content-uploader --type=ts | sed -n '1,220p'

Repository: box/box-ui-elements

Length of output: 26143


Stale remainingMs leaks through for errored/canceled items.

remainingMs is forwarded for any status except staged/complete, but handleUploadError and cancellation do not clear the legacy item.remainingMs. This sends the last computed ETA through for error and canceled status values, so the modernized manager can display a stale "time remaining".

🐛 Proposed fix
-    const remainingMs = !isFullyUploaded ? fileItem.remainingMs : undefined;
+    const remainingMs = status === 'uploading' ? fileItem.remainingMs : undefined;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const isFullyUploaded = status === 'staged' || status === 'complete';
const bytesUploaded = isFullyUploaded && totalBytes != null ? totalBytes : fileItem.bytesUploaded;
const remainingMs = !isFullyUploaded ? fileItem.remainingMs : undefined;
const isFullyUploaded = status === 'staged' || status === 'complete';
const bytesUploaded = isFullyUploaded && totalBytes != null ? totalBytes : fileItem.bytesUploaded;
const remainingMs = status === 'uploading' ? fileItem.remainingMs : undefined;
🤖 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 `@src/elements/content-uploader/utils/mapToModernizedUploadItem.ts` around
lines 64 - 66, Update the remainingMs mapping in mapToModernizedUploadItem so
errored and canceled statuses return undefined rather than forwarding the stale
fileItem.remainingMs value. Preserve the existing ETA for active, non-terminal
upload statuses and keep staged/complete behavior unchanged.

@pierre-pigeon
pierre-pigeon force-pushed the add-counters-to-uploading-file branch from 821a657 to 71f4dc3 Compare July 23, 2026 14:42

@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

🧹 Nitpick comments (1)
src/elements/content-uploader/ContentUploader.tsx (1)

1273-1275: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Skip ETA sampling when the feature is disabled.

isUploadEtaEnabled only controls mapping, so every upload still invokes updateEta and allocates sampling arrays while the mapper drops the result. Guard this telemetry block with the feature flag to avoid adding work to the existing progress hot path.

🤖 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 `@src/elements/content-uploader/ContentUploader.tsx` around lines 1273 - 1275,
Guard the ETA telemetry block in the upload progress handler with
isUploadEtaEnabled so updateEta and etaByItem.set are skipped when the feature
is disabled. Preserve the existing progress handling and only sample or store
ETA data when the flag is enabled.
🤖 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 `@src/elements/content-uploader/ContentUploader.tsx`:
- Around line 1276-1278: Update handleUploadError and markItemCanceled to clear
item.remainingMs and remove the corresponding etaByItem entry when an upload
fails or is canceled. Preserve ETA updates for in-progress uploads and ensure
failed/canceled rows cannot retain stale time remaining.

---

Nitpick comments:
In `@src/elements/content-uploader/ContentUploader.tsx`:
- Around line 1273-1275: Guard the ETA telemetry block in the upload progress
handler with isUploadEtaEnabled so updateEta and etaByItem.set are skipped when
the feature is disabled. Preserve the existing progress handling and only sample
or store ETA data when the flag is enabled.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e9847b8e-6c4f-4824-bd01-3829e251a1ed

📥 Commits

Reviewing files that changed from the base of the PR and between 821a657 and 71f4dc3.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (9)
  • package.json
  • src/common/types/upload.js
  • src/elements/content-uploader/ContentUploader.tsx
  • src/elements/content-uploader/__tests__/ContentUploader.test.js
  • src/elements/content-uploader/stories/ContentUploader.stories.js
  • src/elements/content-uploader/utils/__tests__/mapToModernizedUploadItem.test.ts
  • src/elements/content-uploader/utils/__tests__/uploadEta.test.ts
  • src/elements/content-uploader/utils/mapToModernizedUploadItem.ts
  • src/elements/content-uploader/utils/uploadEta.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • package.json
  • src/elements/content-uploader/utils/mapToModernizedUploadItem.ts
  • src/elements/content-uploader/tests/ContentUploader.test.js
  • src/elements/content-uploader/utils/uploadEta.ts
  • src/common/types/upload.js
  • src/elements/content-uploader/utils/tests/uploadEta.test.ts
  • src/elements/content-uploader/utils/tests/mapToModernizedUploadItem.test.ts
  • src/elements/content-uploader/stories/ContentUploader.stories.js

Comment on lines +1276 to +1278
item.bytesUploaded = event.loaded;
item.totalBytes = event.total;
item.remainingMs = getRemainingMs(nextEta);

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clear ETA when an upload fails or is canceled.

After this block stores remainingMs, handleUploadError and markItemCanceled leave it intact. Since the modernized mapper forwards ETA for every status except staged/complete, failed and canceled rows can show stale time remaining. Clear item.remainingMs and delete the corresponding etaByItem entry in both transitions, or restrict mapping to STATUS_IN_PROGRESS.

🤖 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 `@src/elements/content-uploader/ContentUploader.tsx` around lines 1276 - 1278,
Update handleUploadError and markItemCanceled to clear item.remainingMs and
remove the corresponding etaByItem entry when an upload fails or is canceled.
Preserve ETA updates for in-progress uploads and ensure failed/canceled rows
cannot retain stale time remaining.

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