Skip to content

feat(uploads-manager): integrate shared feature into ContentUploader#4573

Open
dealwith wants to merge 3 commits into
masterfrom
integrate-shared-feature-into-content-uploader
Open

feat(uploads-manager): integrate shared feature into ContentUploader#4573
dealwith wants to merge 3 commits into
masterfrom
integrate-shared-feature-into-content-uploader

Conversation

@dealwith
Copy link
Copy Markdown
Collaborator

@dealwith dealwith commented May 20, 2026

Wire @box/uploads-manager UploadsManager component into ContentUploader behind the enableModernizedUploads flag. Maps legacy upload state to the shared feature's item shape and delegates per-item cancel/retry/remove actions to existing handlers.

1/5 pr in the queue

  1. 👉 feat(uploads-manager): integrate shared feature into ContentUploader #4573
  2. feat(content-uploader): Implement cancel all retry all handlers #4577
  3. feat(content-uploader): Implement cancelled state uploads manager #4578
  4. feat(content-uploader): Implement cancel all confirmation modal #4579
  5. feat(content-uploader): Implement permission error handling uploads #4580

Summary by CodeRabbit

  • Refactor

    • Content uploader now uses a modernized uploads manager for improved item data, normalized upload statuses, and consistent cancel/retry/remove interactions.
  • Tests

    • Expanded test coverage for upload item mapping, status translation, folder handling, and uploads-manager integration to improve reliability.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

Walkthrough

ContentUploader maps legacy upload items to UploadsManager item objects, exposes rootFolderId to the mapper, and wires UploadsManagerBP callbacks through bridging handlers that resolve modernized IDs back to legacy UploadItem actions (cancel/retry/remove/toggle).

Changes

ContentUploader Modernized Uploads Integration

Layer / File(s) Summary
Modernized upload item mapping
src/elements/content-uploader/utils/mapToModernizedUploadItem.ts, src/elements/content-uploader/utils/__tests__/mapToModernizedUploadItem.test.ts
New mapper functions getModernizedItemId, mapToModernizedUploadItem, and mapToModernizedUploadItems convert legacy file/folder items to UploadsManager UploadItem objects: status translation via STATUS_MAP, id derivation (file id or <name>_<folderId>), errorMessage extraction, and defaults for extension/progress. Tests cover field mapping, batch mapping, folder-item id stability, and missing-file handling.
ContentUploader rendering and callback wiring
src/elements/content-uploader/ContentUploader.tsx, src/elements/content-uploader/__tests__/ContentUploader.test.js
Import mapper, destructure rootFolderId in render, add handlers to find legacy items by modernized id and route UploadsManagerBP actions: cancel/retry -> onClick(legacyItem), remove -> removeFileFromUploadQueue(legacyItem). Render UploadsManagerBP with mapped items, isExpanded, and wired callbacks. Tests assert rendering path, items mapping, isExpanded passthrough, callback routing, folder-item behavior, and no-op on unknown ids.
Dependency bump
package.json
Bump @box/uploads-manager from ^1.14.0 to ^1.15.0 in devDependencies and peerDependencies.

Sequence Diagram(s)

sequenceDiagram
  participant LegacyState as ContentUploader (state.items)
  participant Mapper as mapToModernizedUploadItems
  participant UploadsManagerBP as UploadsManagerBP
  participant Bridging as Bridging Handlers (findItemByModernizedId)
  participant LegacyHandlers as Legacy Handlers (onClick, removeFileFromUploadQueue)

  LegacyState->>Mapper: state.items + rootFolderId
  Mapper->>UploadsManagerBP: ModernizedUploadItem[]
  UploadsManagerBP->>Bridging: onItemCancel(modernizedId)
  Bridging->>Bridging: findItemByModernizedId(modernizedId)
  Bridging->>LegacyHandlers: onClick(legacyItem)
  UploadsManagerBP->>Bridging: onItemRemove(modernizedId)
  Bridging->>LegacyHandlers: removeFileFromUploadQueue(legacyItem)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • box/box-ui-elements#4571: Both PRs modify ContentUploader's enableModernizedUploads rendering path; #4571 adds the flag/placeholder while this PR wires item mapping and callbacks.
  • box/box-ui-elements#4563: Related dependency changes for @box/uploads-manager and earlier integration steps.
  • box/box-ui-elements#4425: Touches cancel/remove logic in ContentUploader that is reused by the modernized remove/cancel handlers.

Suggested labels

ready-to-merge

Suggested reviewers

  • olehrybak
  • tjiang-box
  • dlasecki-box

Poem

🐰 I hop through code with mapper bright,

Turning old uploads into new light.
IDs matched, callbacks tied,
Folder and file both verified.
A rabbit cheers — integration bites!

🚥 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 and concisely describes the main change: integrating @box/uploads-manager into ContentUploader behind a feature flag.
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.
Description check ✅ Passed The pull request description adequately explains the changes: wiring the @box/uploads-manager component into ContentUploader with the enableModernizedUploads flag, mapping legacy state, and delegating actions.

✏️ 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 integrate-shared-feature-into-content-uploader

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.

@dealwith dealwith force-pushed the create-feature-flag-in-content-uploader branch 3 times, most recently from 62792f4 to 55f3e27 Compare May 21, 2026 20:51
Base automatically changed from create-feature-flag-in-content-uploader to master May 21, 2026 21:07
Wire @box/uploads-manager UploadsManager into ContentUploader behind the
enableModernizedUploads flag. Maps legacy upload state to the shared
feature's item shape and delegates per-item cancel/retry/remove actions
to existing handlers.
@dealwith dealwith force-pushed the integrate-shared-feature-into-content-uploader branch from b7bf9cc to 4acdc6a Compare May 25, 2026 13:50
@dealwith dealwith marked this pull request as ready for review May 25, 2026 14:50
@dealwith dealwith requested review from a team as code owners May 25, 2026 14:50
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/elements/content-uploader/__tests__/ContentUploader.test.js (1)

3-3: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Remove duplicate UploadsManagerBP import (build-blocking parse error).

UploadsManagerBP is declared twice, which triggers the exact parser/lint failures shown in CI. Keep only one import.

Also applies to: 10-10

🤖 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/__tests__/ContentUploader.test.js` at line 3,
There are duplicate imports of UploadsManagerBP causing a parse error; locate
both import statements that declare UploadsManagerBP (the import from
'`@box/uploads-manager`') and remove the redundant one so UploadsManagerBP is
imported only once; ensure any usages rely on the remaining import and that no
other alias or name change is introduced.
🧹 Nitpick comments (1)
src/elements/content-uploader/utils/__tests__/mapToModernizedUploadItem.test.ts (1)

10-19: ⚡ Quick win

Add a regression test for folder items without file.

Current fixtures always include file, so the folder path is untested. Add a case with { isFolder: true, file: undefined } to ensure mapping (including id) is safe.

Also applies to: 69-79

🤖 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/__tests__/mapToModernizedUploadItem.test.ts`
around lines 10 - 19, Add a regression test in mapToModernizedUploadItem.test.ts
that constructs a legacy item with isFolder: true and file: undefined (use the
existing buildLegacyItem helper) to cover the folder path; call the mapping
function under test (mapToModernizedUploadItem or whichever test subject is
imported) with that item and assert it returns a valid modernized upload item
without throwing and includes a safe id (e.g., non-null/defined) and expected
folder-specific fields. Ensure the new test mirrors other fixtures but overrides
file: undefined and isFolder: true so the mapping logic that reads file
properties is exercised safely.
🤖 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 1226-1229: findItemByModernizedId currently recomputes IDs from
item.file (using getFileId) which fails for folder entries that lack a file;
update findItemByModernizedId to use the same ID derivation contract used when
mapping this.state.items (i.e., reuse the precomputed modernized ID stored on
each UploadItem or the helper that produced it) and explicitly handle folder
items by deriving their ID from the folder-specific fields instead of item.file
so cancel/retry/remove operations find the correct item whether it's a file or
folder.

In `@src/elements/content-uploader/utils/mapToModernizedUploadItem.ts`:
- Around line 31-36: mapToModernizedUploadItem currently calls
getFileId(item.file, rootFolderId) unconditionally which breaks for folder items
that lack item.file; change the id assignment in mapToModernizedUploadItem so it
uses getFileId(item.file, rootFolderId) only when item.file exists and otherwise
returns the same folder-safe fallback used by
ContentUploader.findItemByModernizedId (use the exact same fallback value/logic
from that method to keep lookups consistent). Ensure you reference getFileId,
mapToModernizedUploadItem, and ContentUploader.findItemByModernizedId while
making this conditional fix.

---

Outside diff comments:
In `@src/elements/content-uploader/__tests__/ContentUploader.test.js`:
- Line 3: There are duplicate imports of UploadsManagerBP causing a parse error;
locate both import statements that declare UploadsManagerBP (the import from
'`@box/uploads-manager`') and remove the redundant one so UploadsManagerBP is
imported only once; ensure any usages rely on the remaining import and that no
other alias or name change is introduced.

---

Nitpick comments:
In
`@src/elements/content-uploader/utils/__tests__/mapToModernizedUploadItem.test.ts`:
- Around line 10-19: Add a regression test in mapToModernizedUploadItem.test.ts
that constructs a legacy item with isFolder: true and file: undefined (use the
existing buildLegacyItem helper) to cover the folder path; call the mapping
function under test (mapToModernizedUploadItem or whichever test subject is
imported) with that item and assert it returns a valid modernized upload item
without throwing and includes a safe id (e.g., non-null/defined) and expected
folder-specific fields. Ensure the new test mirrors other fixtures but overrides
file: undefined and isFolder: true so the mapping logic that reads file
properties is exercised safely.
🪄 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

Run ID: 5c857d9c-d038-41d0-af47-52ee7be0952e

📥 Commits

Reviewing files that changed from the base of the PR and between 1dfadde and 4acdc6a.

📒 Files selected for processing (4)
  • src/elements/content-uploader/ContentUploader.tsx
  • src/elements/content-uploader/__tests__/ContentUploader.test.js
  • src/elements/content-uploader/utils/__tests__/mapToModernizedUploadItem.test.ts
  • src/elements/content-uploader/utils/mapToModernizedUploadItem.ts

Comment thread src/elements/content-uploader/ContentUploader.tsx
Comment thread src/elements/content-uploader/utils/mapToModernizedUploadItem.ts Outdated
@socket-security
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.14.0 ⏵ 1.15.077 +1100100 +198 +250

View full report

@dealwith dealwith self-assigned this May 25, 2026
Copy link
Copy Markdown
Contributor

@olehrybak olehrybak left a comment

Choose a reason for hiding this comment

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

lgtm

@dealwith
Copy link
Copy Markdown
Collaborator Author

@Mergifyio queue

@mergify
Copy link
Copy Markdown
Contributor

mergify Bot commented May 26, 2026

Merge Queue Status

  • 🟠 Waiting for queue conditions
  • ⏳ Enter queue
  • ⏳ Run checks
  • ⏳ Merge
Waiting for any of
  • all of: [📌 queue conditions of queue rule Automatic boxmoji merge]
    • #review-threads-unresolved = 0 [🛡 GitHub branch protection]
    • files ~= ^i18n/
    • github-review-decision = APPROVED [🛡 GitHub branch protection]
    • title ~= ^(fix)\(i18n\)?:\supdate translations$
  • all of: [📌 queue conditions of queue rule Automatic strict merge]
    • #changes-requested-reviews-by = 0
    • #review-threads-unresolved = 0
    • #review-threads-unresolved = 0 [🛡 GitHub branch protection]
    • branch-protection-review-decision = APPROVED
    • github-review-decision = APPROVED [🛡 GitHub branch protection]
All conditions
  • any of [🔀 queue conditions]:
    • all of [📌 queue conditions of queue rule Automatic boxmoji merge]:
      • #review-threads-unresolved = 0 [🛡 GitHub branch protection]
      • author = boxmoji
      • files ~= ^i18n/
      • github-review-decision = APPROVED [🛡 GitHub branch protection]
      • title ~= ^(fix)\(i18n\)?:\supdate translations$
      • #approved-reviews-by >= 1 [🛡 GitHub branch protection]
      • status-success = lint_test_build
      • any of [🛡 GitHub branch protection]:
        • check-success = Summary
        • check-neutral = Summary
        • check-skipped = Summary
      • any of [🛡 GitHub branch protection]:
        • check-success = lint_test_build
        • check-neutral = lint_test_build
        • check-skipped = lint_test_build
      • any of [🛡 GitHub branch protection]:
        • check-success = license/cla
        • check-neutral = license/cla
        • check-skipped = license/cla
      • any of [🛡 GitHub branch protection]:
        • check-success = lint_pull_request
        • check-neutral = lint_pull_request
        • check-skipped = lint_pull_request
    • all of [📌 queue conditions of queue rule Automatic strict merge]:
      • #changes-requested-reviews-by = 0
      • #review-threads-unresolved = 0
      • #review-threads-unresolved = 0 [🛡 GitHub branch protection]
      • branch-protection-review-decision = APPROVED
      • github-review-decision = APPROVED [🛡 GitHub branch protection]
      • #approved-reviews-by >= 1 [🛡 GitHub branch protection]
      • #approved-reviews-by >= 2
      • label = ready-to-merge
      • status-success = lint_test_build
      • title ~= ^(build|ci|chore|docs|feat|fix|perf|refactor|revert|style|test)(\([^)]+\))?:\s.+$
      • any of [🛡 GitHub branch protection]:
        • check-success = Summary
        • check-neutral = Summary
        • check-skipped = Summary
      • any of [🛡 GitHub branch protection]:
        • check-success = lint_test_build
        • check-neutral = lint_test_build
        • check-skipped = lint_test_build
      • any of [🛡 GitHub branch protection]:
        • check-success = license/cla
        • check-neutral = license/cla
        • check-skipped = license/cla
      • any of [🛡 GitHub branch protection]:
        • check-success = lint_pull_request
        • check-neutral = lint_pull_request
        • check-skipped = lint_pull_request
  • -closed [📌 queue requirement]
  • -conflict [📌 queue requirement]
  • -draft [📌 queue requirement]
  • any of [📌 queue -> configuration change requirements]:
    • -mergify-configuration-changed
    • check-success = Configuration changed

[STATUS_ERROR]: 'error',
};

export function getModernizedItemId(item: LegacyUploadItem | FolderUploadItem, rootFolderId: string): string {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

i think the name getModernizedItemId is a bit misleading - it reads like "the item id is modernized" rather than "we're generating an id for the modernized component." also "id" is ambiguous here - i initially read it as the Box item id, not a client-side tracking key.

get also implies retrieving something that already exists. the rest of the repo uses get for these (getFileId, getDataTransferItemId) so i'm not suggesting we fight that convention, but something like getUploadItemKey would make the intent clearer - this is a UI-only lookup key, not a server-side id.

return getFileId(fileWithOptions, rootFolderId);
}
const folderId = item.options?.folderId ?? rootFolderId;
return `${item.name}_${folderId}`;
Copy link
Copy Markdown
Collaborator

@jpan-box jpan-box May 27, 2026

Choose a reason for hiding this comment

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

this could produce duplicate ids if two folders with the same name are uploaded to the same destination. if a user drags two folders both named shared into the uploader, they'd both produce shared_0, and then findItemByModernizedId would always .find() the first one. canceling/removing would act on the wrong item.

the file path avoids this via uploadInitTimestamp (added in #395 for exactly this reason). we should add something similar for folder items so the ids are unique per upload session.


handleModernizedItemAction = (id: string) => {
const item = this.findItemByModernizedId(id);
if (item) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

if findItemByModernizedId doesn't find a match, this just proceeds without issue. we should catch any unhandled scenarios here with an else.

isExpanded={isUploadsManagerExpanded}
onToggle={this.toggleUploadsManager}
onItemCancel={this.handleModernizedItemAction}
onItemRetry={this.handleModernizedItemAction}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

onItemCancel and onItemRetry both point to the same handler here. i see this gets split into dedicated handlers in #4577, so not blocking.

status: STATUS_IN_PROGRESS,
size: 100,
file: { name: 'foo.pdf' } as File,
api: {} as never,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

the as never troubles me, but i'm guessing it's there because the fixtures include api: {} and FolderUploadItem doesn't have an api field in the type definition. however at runtime, addFolderToUploadQueue does put api: folderUpload on the item.

i think the right fix is to add api? to FolderUploadItem so the type matches what actually happens at runtime. that would make these casts unnecessary.

<ThemingStyles selector={`#${this.id}`} theme={theme} />
<UploadsManagerBP items={[]} />
<UploadsManagerBP
items={mapToModernizedUploadItems(items, rootFolderId)}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

mapToModernizedUploadItems runs on every render without memoization, which means we're re-mapping all items in the queue even when nothing changed. I believe our consuming app sets fileLimit to 100,000 - so this could get expensive. if you can think of a way to optimize this or get ahead of the performance issues that would be great.

Copy link
Copy Markdown
Collaborator

@jpan-box jpan-box left a comment

Choose a reason for hiding this comment

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

requesting some changes. some lower priority, some that i think should be changed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants