Skip to content

Sync: Show file upload progress in the UI#2332

Merged
gavande1 merged 5 commits intotrunkfrom
stu-1144-sync-show-file-upload-progress-in-in-the-ui
Jan 6, 2026
Merged

Sync: Show file upload progress in the UI#2332
gavande1 merged 5 commits intotrunkfrom
stu-1144-sync-show-file-upload-progress-in-in-the-ui

Conversation

@gavande1
Copy link
Copy Markdown
Contributor

@gavande1 gavande1 commented Dec 29, 2025

Related

Fixes STU-1144

Summary

This PR implements file upload progress tracking for sync operations, showing the upload percentage (0-100%) next to the "Uploading Studio site…" message in the UI.

Changes

  • Adds progress updates in real-time as chunks are uploaded via Tus protocol for sync uploads
  • Updated UI component to display upload percentage in parentheses before ellipsis

Testing

  • Checkout this branch
  • Connect your site for sync
  • Start push action with different granular options.
  • Check followings:
    • Test upload progress displays correctly during sync push operations
    • Verify percentage updates smoothly as chunks are uploaded
    • Confirm percentage is cleared when upload completes and transitions to next state
    • Test with various push options to ensure progress calculation is accurate
    • The progress bar also moves with progress between the range of 40%-50%
    • The progress bar also moves with progress between the range of 40%-50% to indicate overall push progress.

@gavande1 gavande1 self-assigned this Dec 29, 2025
@gavande1 gavande1 changed the title Sync: Show file upload progress in % in the UI Sync: Show file upload progress in the UI Dec 30, 2025
@gavande1 gavande1 marked this pull request as ready for review December 30, 2025 05:53
<div className="a8c-body-small flex items-center gap-0.5">
<Icon icon={ info } size={ 16 } />
{ pushState.status.message }
{ getPushUploadMessage( pushState.status.message, uploadPercentage ) }
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I am not sure if this is best practice but this is best I could come up with. Let me know if you have any better idea to implement this in other way.

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.

Nice! Could also be memoized with useMemo, but fine as-is.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It makes sense. I have updated in 9d16c42

@gavande1 gavande1 requested a review from a team December 30, 2025 05:56
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Dec 30, 2025

📊 Performance Test Results

Comparing 9d16c42 vs trunk

site-editor

Metric trunk 9d16c42 Diff Change
load 7537.00 ms 6081.00 ms -1456.00 ms 🟢 -19.3%

site-startup

Metric trunk 9d16c42 Diff Change
siteCreation 9084.00 ms 9076.00 ms -8.00 ms 🟢 -0.1%
siteStartup 3956.00 ms 3947.00 ms -9.00 ms 🟢 -0.2%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change

Copy link
Copy Markdown
Contributor

@bcotrim bcotrim left a comment

Choose a reason for hiding this comment

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

LGMT and works as described

Image

I added some comments that I think we could address before merging, but not blockers.

return key === 'uploadingPaused';
};

const isKeyUploading = useCallback( ( key: PushStateProgressInfo[ 'key' ] | 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.

All of the isKeyXXX, getPushUploadPercentage, and getPushUploadMessage functions could be defined outside the hook, since they're pure functions with no dependencies on hook state. This provides stable references without the useCallback overhead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Interesting. Let me refactor them.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@bcotrim I have moved them to pure functions in 4e37ec6. Let me know what do you think?

<div className="a8c-body-small flex items-center gap-0.5">
<Icon icon={ info } size={ 16 } />
{ pushState.status.message }
{ getPushUploadMessage( pushState.status.message, uploadPercentage ) }
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.

Nice! Could also be memoized with useMemo, but fine as-is.

- Add sync-upload-progress IPC event to send upload progress from main process
- Update Tus onProgress callback to send progress percentage (0-100%)
- Store raw upload progress in SyncPushState for display
- Display upload percentage next to 'Uploading Studio site…' message
- Progress updates in real-time as chunks are uploaded via Tus protocol

Fixes STU-1144
- Move upload progress mapping logic to useSyncStatesProgressInfo hook
- Add getPushUploadMessage helper for i18n-formatted messages
- Add isKeyUploading helper function
- Update message format to show percentage in parentheses: 'Uploading site (98%)…'
- Follow i18n best practices using sprintf for formatted messages
- Centralize all progress-related calculations in one place
@gavande1 gavande1 force-pushed the stu-1144-sync-show-file-upload-progress-in-in-the-ui branch from e41d130 to 9d16c42 Compare January 6, 2026 05:38
Copy link
Copy Markdown
Contributor

@bcotrim bcotrim left a comment

Choose a reason for hiding this comment

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

Thanks for making the changes @gavande1
LGTM 👍

Copy link
Copy Markdown
Contributor

@gcsecsey gcsecsey left a comment

Choose a reason for hiding this comment

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

This LGTM and work well! 👍

CleanShot.2026-01-06.at.10.13.05.mp4

Just a small heads up that I've been working on refactoring the pull/push states in #2037. I think we can proceed with merging this and it will be fairly straightforward to resolve any conflicts.

@gavande1 gavande1 merged commit 849442d into trunk Jan 6, 2026
10 checks passed
@gavande1 gavande1 deleted the stu-1144-sync-show-file-upload-progress-in-in-the-ui branch January 6, 2026 11:43
gcsecsey added a commit that referenced this pull request Jan 14, 2026
Migrate the changes of PR #2332
gcsecsey added a commit that referenced this pull request Mar 3, 2026
…#2037)

* add new slice for pull and push states

* update useSyncPull and useSyncPush hooks

* add selectors from hook to slice

* move clear/cancel operations to thunks

* move pushsite to thunk

* move pullsite to thunk

* extract polling logic to separate hook

* Remove SyncSitesProvider context, use hooks directly

Remove the SyncSitesProvider React context layer and migrate all
components to use useSyncPull and useSyncPush hooks directly.

Changes:
- Extract getLastSyncTimeText to useLastSyncTimeText hook
- Extract initialization logic to useInitializeSyncStates hook
- Move useListenDeepLinkConnection and initialization to App component
- Update all components to use hooks directly instead of context
- Remove sync-sites-context.tsx

* move polling logic to thunk

* remove unused onPullSuccess/onPushSuccess props

* remove unused actions

* fix unused reference after merge

* update tests

* add null checks

* fix linter errors

* add runtime checks

* add optional chaining to progress data checks

* Merge trunk and resolve conflicts

* Revert "Merge trunk and resolve conflicts"

This reverts commit 55b1c33.

* Add back upload progress indicator

Migrate the changes of PR #2332

* Compare status keys directly, remove unecessary helpers

* make clearPullState and clearPushState thunks responsible for clearing the state as well

* Inline single-use helper functions into their sole callers

Inline getPushStatusWithProgress, getBackupStatusWithProgress, and
completePullThunk directly into their only call sites within
pollPushProgressThunk and pollPullBackupThunk. Remove the now-unused
copies of getPushStatusWithProgress and getBackupStatusWithProgress
from useSyncStatesProgressInfo.

* Remove redundant remoteSiteId from updatePushState/updatePullState call sites

The reducers now auto-set remoteSiteId from the top-level payload,
so callers no longer need to pass it in the nested state object.

* Remove updatePushStateWithIpc, move IPC sync to listener middleware

The calls to addSyncOperation/clearSyncOperation are now an effect of
Redux state updates via listener middleware, rather than being manually
managed at each call site. This removes the coupling between state
updates and IPC side effects.

* Guard against missing status in backup polling response

The backup API can return error responses (e.g. job_not_found) without
a status field. Without this guard, response.status is undefined, which
silently breaks the pull state progression.

* Replace if-else chain with switch statement in pollPushProgressThunk, remove getPullStatusWithProgress

* Move error state handling to extraReducers, use rejectWithValue in thunks

* Move error modals to listener middleware, remove showErrorMessageBox from thunks

* Add createAsyncThunk condition to polling thunks, remove manual cancellation guards

* Remove isKey* helpers, replace with direct comparisons in sync-connected-sites

* Move progress info definitions into slice, remove from thunk payloads

* Move polling to listener middleware, delete use-sync-polling hook

* Move useInitializeSyncStates to initializeSyncStatesThunk, move mapImportResponseToPushState to slice

* Remove useSyncPull and useSyncPush hooks, replace with direct Redux access

Delete the wrapper hooks and update all consumers to use useRootSelector +
syncOperationsSelectors for reactive state and dispatch + syncOperationsThunks
for actions. Move shared types (SyncBackupState, PullSiteOptions, PullStates,
SyncPushState, PushStates) into the slice and re-export from stores/sync.

* use selectors in settings-site-menu

* Replace non-reactive store.getState() with useRootSelector for sync state

store.getState() in component render bodies doesn't subscribe to Redux
changes, so sync UI (spinners, disabled states) wouldn't update reactively.

* fix tests

* Addressing review comments

* Clean up

* Cleanup

* Fix bad merge and clear import state when pulls finish

* zod schemas

* Correctness and simplification

* Fix schema

* Fix more merge issues

* Bring back ability to manually pause pushes

* Re-enable skipped tests

* Restore more of the tests

* Restore even more of the tests

* Fix diff

* Minor tweaks

* remove unused export

* Fix tests

* Update listeners to react to state updates rather than action types, and to cancel other active listeners

* Revert "Update listeners to react to state updates rather than action types, and to cancel other active listeners"

This reverts commit e041e31.

* Simplify polling setup

* Fix bug

If I start a push and cancel it before it starts uploading, then quickly start a new push, the second operation will fail.

That's because it generates an identical archive path for every push, and the abortion of the first push call is async, meaning the file isn't deleted when the operation is aborted, but only after the first export finishes.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Fredrik Rombach Ekelund <fredrik.rombach.ekelund@automattic.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.

3 participants