UNPKG

openstack-uicore-foundation

Version:

ui reactjs components for openstack marketing site

100 lines (74 loc) 7.81 kB
# UploadInputV3 Premature Completion on HTTP 202 Fix Plan Created: 2026-04-23 Author: smarcet@gmail.com Status: VERIFIED Approved: Yes Iterations: 0 Worktree: No Type: Bugfix ## Summary **Symptom:** After uploading a file via UploadInputV3 that triggers HTTP 202 (async server processing), the file immediately shows as "Complete" instead of waiting for polling to confirm the server has finished processing. **Trigger:** Upload a file via UploadInputV3. Server returns HTTP 202 with `file_id`. Dropzone's `success` event fires immediately (before polling starts or completes), and V3's `handleFileCompleted` marks the file as `complete: true`. **Root Cause:** `src/components/inputs/upload-input-v3/index.js:153``handleFileCompleted()` unconditionally marks the file as `complete: true` when Dropzone's `success` event fires, without checking `file._asyncProcessing`. For non-chunked 202 uploads, `_finished()` is called directly by Dropzone (`dropzone.js:2707`), bypassing the `chunksUploaded` override that defers completion for chunked uploads. **Secondary issue:** `wrappedOnUploadComplete` at line 190 is a passthrough — it doesn't mark uploading files as complete. When polling finishes, `onUploadComplete` fires but the `uploadingFiles` entry isn't cleaned up because `complete` was never set (if the guard blocks `handleFileCompleted`). ## Investigation - **V3-specific bug.** V2 has zero handling for `success`/`onFileCompleted`/`complete` — it renders only from the `value` prop, which is updated after `onUploadComplete` (correctly waits for polling in the 202 case). - **Flow for non-chunked 202:** `xhr.onload``file._asyncProcessing = true``dropzoneOnLoad(e)``_finishedUploading``!chunked``_finished(files, response, e)` → emits `success` → DropzoneV3 `success` handler → `onFileCompleted``handleFileCompleted` marks `complete: true` → file shows "Complete" immediately. Polling starts AFTER `dropzoneOnLoad` returns, but the file already appears done. - **Chunking decision:** `file.upload.chunked = options.chunking && (options.forceChunking || file.size > options.chunkSize)`. Config sets `chunking: true` but not `forceChunking` (default `false`), and `chunkSize` defaults to 2MB. Files ≤ 2MB are uploaded as non-chunked, bypassing the `chunksUploaded` override entirely. - **`chunksUploaded` override** at `dropzone/index.js:164` correctly defers `done()` for chunked 202 uploads, but is never called for non-chunked uploads. - **`wrappedOnUploadComplete`** at `upload-input-v3/index.js:190` just passes through to the parent — it doesn't update `uploadingFiles` state at all. ## Behavior Contract **Given:** UploadInputV3 with a file upload that returns HTTP 202 (async processing), where `file._asyncProcessing` is set to `true` on the Dropzone file object **When:** Dropzone's `success` event fires (immediately after `_finishedUploading` calls `_finished` for non-chunked uploads, or via `chunksUploaded` for chunked uploads) **Currently (bug):** `handleFileCompleted` marks the file as `complete: true` immediately, showing "Complete" in the UI before polling confirms server processing is done **Expected (fix):** When `file._asyncProcessing` is true, `handleFileCompleted` does NOT mark the file as complete. The file stays in "Loading" state. When polling finishes and `onUploadComplete` fires (via `wrappedOnUploadComplete`), uploading files are marked complete, enabling the existing useEffect cleanup to remove them when `value` updates. **Anti-regression:** Synchronous uploads (HTTP 200) must still mark files as "Complete" immediately via `handleFileCompleted`. File deletion, error display, progress tracking, and the existing server-renamed-filename cleanup must remain intact. ## Fix Approach **Chosen:** Guard in V3 handlers **Why:** The `_asyncProcessing` flag is already set on the Dropzone file object before `success` fires. V3 just needs to check it in `handleFileCompleted` and mark files complete in `wrappedOnUploadComplete`. No changes to DropzoneJS or DropzoneV3 — purely V3 state management. **Alternatives considered:** - *Guard in DropzoneV3 success handler* — would work but touches the shared wrapper file used by all upload versions, increasing regression surface. - *Override _finishedUploading in DropzoneJS* — fixes at the Dropzone level but patches library internals, fragile across Dropzone upgrades. **Files:** - `src/components/inputs/upload-input-v3/index.js` (primary fix — handleFileCompleted guard + wrappedOnUploadComplete) - `src/components/inputs/upload-input-v3/__tests__/upload-input-v3.test.js` (reproducing test) **Strategy:** 1. In `handleFileCompleted`: check `file._asyncProcessing` — if true, return early (don't mark complete) 2. In `wrappedOnUploadComplete`: before calling the parent callback, mark all uploading files as `complete: true` via `setUploadingFiles`. This ensures cleanup works for both sync (where `handleFileCompleted` already set it) and async (where it was skipped). **Tests:** Add test to `upload-input-v3.test.js` that simulates: file added → file completed with `_asyncProcessing: true` → verify file still shows "Loading" (not "Complete"). ## Verification Scenario ### TS-001: Async Upload Processing State **Preconditions:** UploadInputV3 with `maxFiles=1`, server configured to return HTTP 202 for uploads | Step | Action | Expected Result (after fix) | |------|--------|-----------------------------| | 1 | Upload a file that triggers HTTP 202 async processing | File shows "Loading" with progress bar, NOT "Complete" | | 2 | Wait for polling to return `status: 'complete'` and parent to update value | File transitions to "Complete" (from value section), no duplicate entries | ## Progress - [x] Task 1: Write Reproducing Test (RED) - [x] Task 2: Implement Fix at Root Cause - [x] Task 3: Quality Gate **Tasks:** 3 | **Done:** 3 ## Tasks ### Task 1: Write Reproducing Test (RED) **Objective:** Encode the Behavior Contract as a failing test BEFORE writing any fix code. **Files:** `src/components/inputs/upload-input-v3/__tests__/upload-input-v3.test.js` **Entry point:** `UploadInputV3` component (rendered with mock DropzoneV3) **Test scenario:** 1. Render UploadInputV3 with `maxFiles=1` and `value=[]` 2. Simulate `onAddedFile({ name: 'video.mp4', size: 5000000 })` 3. Simulate `onFileCompleted({ name: 'video.mp4', size: 5000000, _asyncProcessing: true })` 4. Assert: file still shows "Loading" (not "Complete") — the `_asyncProcessing` flag should prevent marking as complete **DoD:** Test exists, named `test('does not mark file as complete when _asyncProcessing is true')`, runs, fails because `handleFileCompleted` currently ignores `_asyncProcessing` and marks complete unconditionally. **Verify:** `npx jest src/components/inputs/upload-input-v3/__tests__/upload-input-v3.test.js --verbose` ### Task 2: Implement Fix at Root Cause **Objective:** Minimal change to `handleFileCompleted` and `wrappedOnUploadComplete` to prevent premature completion on HTTP 202. **Files:** `src/components/inputs/upload-input-v3/index.js` **Strategy:** 1. In `handleFileCompleted` (line 153): add early return when `file._asyncProcessing` is true 2. In `wrappedOnUploadComplete` (line 190): add `setUploadingFiles(prev => prev.map(f => ({ ...f, complete: true })))` before calling the parent callback, so all uploading files are marked complete when the server confirms processing is done **DoD:** Reproducing test PASSES. Full test suite PASSES. Diff touches root-cause file only. **Verify:** `npx jest --verbose` ### Task 3: Quality Gate **Objective:** Full suite re-run, build clean. **DoD:** Full suite green, build succeeds, no performance regressions. **Verify:** `npx jest --verbose && npm run build-dev`