-
Notifications
You must be signed in to change notification settings - Fork 55
Move pull and push states from SyncSitesProvider to a new Redux slice #2037
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Move pull and push states from SyncSitesProvider to a new Redux slice #2037
Conversation
📊 Performance Test ResultsComparing fb2546e vs trunk site-editor
site-startup
Results are median values from multiple test runs. Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Push: it seems it stucks at "Creating backup..." step. Tried a few times and also tried in trunk - worked quickly for me.
Pull: not related to the changes, since I reproduce the error in trunk too, but if you are working there - take a look please at this error when pull is finished, maybe it's a quck win:
![]()
…dio-refactor-pull-and-push-states-from-syncsitesprovider
Thanks for reviewing this @nightnei! I took another look at this, and found that earlier we used a ref to keep track of the state, which let us read it back immediately after updating it. For now, I added the ref to the hooks that were previously in the I'm also testing a simpler approach that updated the state objects directly where needed. If it works reliably, I'll update the PR with this approach. |
|
@gcsecsey, I would challenge us to remove With this change, we are preserving functions like |
That's a good point @fredrikekelund, thanks! I'll try to move as much as possible to the Redux slice, and ping for another review. |
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
…dio-refactor-pull-and-push-states-from-syncsitesprovider
…dio-refactor-pull-and-push-states-from-syncsitesprovider
|
I carried out the changes to move most of the logic into thunks within @fredrikekelund could you please take another look when you're back? Thanks! |
nightnei
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for addressing feedback, works well now, no regressions 👍
…s-from-syncsitesprovider Resolve merge conflicts: - use-sync-push.ts: Keep Redux-based approach with thunks and useSyncPolling - use-add-site.test.tsx: Update mock to use separate useSyncPull hook - sync-connected-sites.tsx: Merge isUploadingPaused feature with optional chaining - index.test.tsx: Use useSyncPull mock instead of useSyncSites - add-site.test.tsx: Update mock for separate hook pattern Also fix: - sync-operations-slice.ts: Add missing selectedSite.id parameter to pushArchive call 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
|
I tested the changes again after resolving conflicts, the pull/push functionality still works as before. We can proceed with merging this as soon as the project process is 🟢 again. |
|
Trunk is open for merge again. I'll happily review this PR this afternoon, though |
Thanks @fredrikekelund, that'd be great! |
|
Looking into this now. At first glance, I'm a little skeptical to keeping To me, the end goal is to move "business logic" into Redux slices and have components focused on consuming the Redux-derived state, with as little data crunching as possible. The Anyway, you mentioned the need for additional boilerplate in components if we removed those hooks, @gcsecsey. Is that stuff like |
This reverts commit 55b1c33.
Yes, exactly, I wanted to avoid having to add these as dependencies to each component. I think the boilerplate would be the |
fredrikekelund
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for not following up on my review last week, @gcsecsey. I'm coming back to this now!
I think there's a solid foundation in sync-operations-slice.ts, but there's room to simplify the implementation, not least by following some Redux patterns more closely.
I'll submit multiple reviews to make it a bit easier to digest. Starting with sync-operations-slice.ts and pushSiteThunk primarily.
| const isKeyCancelled = ( key: string | undefined ): boolean => { | ||
| return key === 'cancelled'; | ||
| }; | ||
|
|
||
| const isKeyFailed = ( key: string | undefined ): boolean => { | ||
| return key === 'failed'; | ||
| }; | ||
|
|
||
| const isKeyFinished = ( key: string | undefined ): boolean => { | ||
| return key === 'finished'; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These functions shouldn't be needed in this file. We should just rely on well-typed string props and compare against known values. I.e. state.pullStates[ key ].status.key already has an enum type.
| dispatch( | ||
| syncOperationsActions.clearPushState( { selectedSiteId: selectedSite.id, remoteSiteId } ) | ||
| ); | ||
| void dispatch( | ||
| syncOperationsThunks.clearPushState( { selectedSiteId: selectedSite.id, remoteSiteId } ) | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The clearPushState thunk could also be responsible for updating the state. Seems logical to consolidate that and not depend on the consumer to remember to also update the state if calling the thunk.
| updatePushStateWithIpc( | ||
| dispatch, | ||
| selectedSite.id, | ||
| remoteSiteId, | ||
| { | ||
| remoteSiteId, | ||
| status: pushStatesProgressInfo.creatingBackup, | ||
| selectedSite, | ||
| remoteSiteUrl, | ||
| }, | ||
| isKeyFailed, | ||
| isKeyFinished | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| updatePushStateWithIpc( | |
| dispatch, | |
| selectedSite.id, | |
| remoteSiteId, | |
| { | |
| remoteSiteId, | |
| status: pushStatesProgressInfo.creatingBackup, | |
| selectedSite, | |
| remoteSiteUrl, | |
| }, | |
| isKeyFailed, | |
| isKeyFinished | |
| ); | |
| dispatch( | |
| syncOperationsActions.updatePushState( { | |
| selectedSiteId: selectedSite.id, | |
| remoteSiteId, | |
| state: { | |
| remoteSiteId, | |
| status: pushStatesProgressInfo.creatingBackup, | |
| selectedSite, | |
| remoteSiteUrl, | |
| }, | |
| } ) | |
| ); |
A couple of things here:
- Let's remove
updatePushStateWithIpc. - The calls to
getIpcApi().addSyncOperationandgetIpcApi().clearSyncOperationshould be an effect of state updates, they shouldn't have to be managed by whoever is updating the state. In other words, we should subscribe to state changes and callgetIpcApi().addSyncOperationandgetIpcApi().clearSyncOperationas needed. - Let's simplify the signature of
updatePushState. PassingselectedSiteIdandstate.selectedSite,remoteSiteIdtwice, andremoteSiteUrltwice seems redundant.updatePushStatecould be smarter internally about passing the right data to the right place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
subscribe to state changes
Here's an example of that:
Lines 56 to 68 in 704107a
| // Save chat messages to local storage | |
| listenerMiddleware.startListening( { | |
| predicate( action, currentState, previousState ) { | |
| return currentState.chat.messagesDict !== previousState.chat.messagesDict; | |
| }, | |
| effect( action, listenerApi ) { | |
| const state = listenerApi.getState(); | |
| localStorage.setItem( | |
| LOCAL_STORAGE_CHAT_MESSAGES_KEY, | |
| JSON.stringify( state.chat.messagesDict ) | |
| ); | |
| }, | |
| } ); |
| let archivePath: string, archiveSizeInBytes: number; | ||
|
|
||
| try { | ||
| const result = await getIpcApi().exportSiteForPush( selectedSite.id, operationId, { | ||
| optionsToSync: options?.optionsToSync, | ||
| specificSelectionPaths: options?.specificSelectionPaths, | ||
| } ); | ||
| ( { archivePath, archiveSizeInBytes } = result ); | ||
| } catch ( error ) { | ||
| if ( error instanceof Error && error.message === 'Export aborted' ) { | ||
| updatePushStateWithIpc( | ||
| dispatch, | ||
| selectedSite.id, | ||
| remoteSiteId, | ||
| { status: pushStatesProgressInfo.cancelled }, | ||
| isKeyFailed, | ||
| isKeyFinished | ||
| ); | ||
| throw error; // Signal cancellation | ||
| } | ||
|
|
||
| Sentry.captureException( error ); | ||
| updatePushStateWithIpc( | ||
| dispatch, | ||
| selectedSite.id, | ||
| remoteSiteId, | ||
| { status: pushStatesProgressInfo.failed }, | ||
| isKeyFailed, | ||
| isKeyFinished | ||
| ); | ||
| getIpcApi().showErrorMessageBox( { | ||
| title: sprintf( __( 'Error pushing to %s' ), connectedSite.name ), | ||
| message: __( | ||
| 'An error occurred while pushing the site. If this problem persists, please contact support.' | ||
| ), | ||
| error, | ||
| showOpenLogs: true, | ||
| } ); | ||
| throw error; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| let archivePath: string, archiveSizeInBytes: number; | |
| try { | |
| const result = await getIpcApi().exportSiteForPush( selectedSite.id, operationId, { | |
| optionsToSync: options?.optionsToSync, | |
| specificSelectionPaths: options?.specificSelectionPaths, | |
| } ); | |
| ( { archivePath, archiveSizeInBytes } = result ); | |
| } catch ( error ) { | |
| if ( error instanceof Error && error.message === 'Export aborted' ) { | |
| updatePushStateWithIpc( | |
| dispatch, | |
| selectedSite.id, | |
| remoteSiteId, | |
| { status: pushStatesProgressInfo.cancelled }, | |
| isKeyFailed, | |
| isKeyFinished | |
| ); | |
| throw error; // Signal cancellation | |
| } | |
| Sentry.captureException( error ); | |
| updatePushStateWithIpc( | |
| dispatch, | |
| selectedSite.id, | |
| remoteSiteId, | |
| { status: pushStatesProgressInfo.failed }, | |
| isKeyFailed, | |
| isKeyFinished | |
| ); | |
| getIpcApi().showErrorMessageBox( { | |
| title: sprintf( __( 'Error pushing to %s' ), connectedSite.name ), | |
| message: __( | |
| 'An error occurred while pushing the site. If this problem persists, please contact support.' | |
| ), | |
| error, | |
| showOpenLogs: true, | |
| } ); | |
| throw error; | |
| } | |
| const { archivePath, archiveSizeInBytes } = await getIpcApi().exportSiteForPush( | |
| selectedSite.id, | |
| operationId, | |
| { | |
| optionsToSync: options?.optionsToSync, | |
| specificSelectionPaths: options?.specificSelectionPaths, | |
| } | |
| ); |
Let's unwrap this:
- The
updatePushStateWithIpccalls could be moved toextraReducers. We would usebuilder.addCase( pushSiteThunk.rejectedto listen for failures. - The error modal (as triggered by
getIpcApi().showErrorMessageBox) is also a side effect of the state update. Let's try moving it to a state subscription callback.
| getIpcApi().showErrorMessageBox( { | ||
| title: sprintf( __( 'Error pushing to %s' ), connectedSite.name ), | ||
| message: __( | ||
| 'The site is too large to push. Please reduce the size of the site and try again.' | ||
| ), | ||
| } ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| getIpcApi().showErrorMessageBox( { | |
| title: sprintf( __( 'Error pushing to %s' ), connectedSite.name ), | |
| message: __( | |
| 'The site is too large to push. Please reduce the size of the site and try again.' | |
| ), | |
| } ); |
This call isn't particularly bulky, but if we consider the error modals a side effect of state updates elsewhere (i.e., that they should be triggered by a state subscription listener), then let's be consistent in that approach.
| // Check if cancelled before upload | ||
| const state = getState(); | ||
| const currentPushState = syncOperationsSelectors.selectPushState( | ||
| selectedSite.id, | ||
| remoteSiteId | ||
| )( state ); | ||
| if ( | ||
| ! currentPushState || | ||
| ! currentPushState.status || | ||
| isKeyCancelled( currentPushState.status.key ) | ||
| ) { | ||
| await getIpcApi().removeExportedSiteTmpFile( archivePath ); | ||
| throw new Error( 'Push cancelled' ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we use something more similar to this approach? https://redux-toolkit.js.org/api/createAsyncThunk#canceling-while-running
| // Check if cancelled after upload | ||
| const stateAfterUpload = getState(); | ||
| const pushStateAfterUpload = syncOperationsSelectors.selectPushState( | ||
| selectedSite.id, | ||
| remoteSiteId | ||
| )( stateAfterUpload ); | ||
|
|
||
| if ( isKeyCancelled( pushStateAfterUpload?.status.key ) ) { | ||
| await getIpcApi().removeExportedSiteTmpFile( archivePath ); | ||
| throw new Error( 'Push cancelled' ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my previous comment about aborting the thunk.
| remoteSiteUrl, | ||
| }; | ||
| } else { | ||
| throw response; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| throw response; | |
| throw new Error( response.error ); |
This seems more appropriate.
| Sentry.captureException( error ); | ||
| updatePushStateWithIpc( | ||
| dispatch, | ||
| selectedSite.id, | ||
| remoteSiteId, | ||
| { status: pushStatesProgressInfo.failed }, | ||
| isKeyFailed, | ||
| isKeyFinished | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move this to an extraReducers handler.
| getIpcApi().showErrorMessageBox( { | ||
| title: sprintf( __( 'Error pushing to %s' ), connectedSite.name ), | ||
| message: getErrorFromResponse( error ), | ||
| } ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move this to a state subscription handler.
|
Thanks @fredrikekelund for following up with the review! 🙌 First and foremost, I'll try to resolve all conflicts with trunk, then I'll start working on these changes. |
fredrikekelund
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments related to pullSiteThunk
| // Clear existing state | ||
| dispatch( | ||
| syncOperationsActions.clearPullState( { selectedSiteId: selectedSite.id, remoteSiteId } ) | ||
| ); | ||
| void dispatch( | ||
| syncOperationsThunks.clearPullState( { selectedSiteId: selectedSite.id, remoteSiteId } ) | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, let's make the clearPullState thunk update the state. Using the extraReducers API might be the best way to accomplish that.
| dispatch( | ||
| syncOperationsActions.updatePullState( { | ||
| selectedSiteId: selectedSite.id, | ||
| remoteSiteId, | ||
| state: { | ||
| backupId: null, | ||
| status: pullStatesProgressInfo[ 'in-progress' ], | ||
| downloadUrl: null, | ||
| remoteSiteId, | ||
| remoteSiteUrl, | ||
| selectedSite, | ||
| }, | ||
| } ) | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as for the updatePushState action creator, let's see if we can simplify the function signature to reduce the repetition here.
| // Add sync operation for tracking | ||
| const stateId = generateStateId( selectedSite.id, remoteSiteId ); | ||
| getIpcApi().addSyncOperation( stateId ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move this to a state subscription handler.
| Sentry.captureException( error ); | ||
| dispatch( | ||
| syncOperationsActions.updatePullState( { | ||
| selectedSiteId: selectedSite.id, | ||
| remoteSiteId, | ||
| state: { | ||
| status: pullStatesProgressInfo.failed, | ||
| }, | ||
| } ) | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move this to an extraReducers callback.
| getIpcApi().showErrorMessageBox( { | ||
| title: sprintf( __( 'Error pulling from %s' ), connectedSite.name ), | ||
| message: __( 'Studio was unable to connect to WordPress.com. Please try again.' ), | ||
| } ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move this to a state subscription handler.
| let status: PushStateProgressInfo = pushStatesProgressInfo.creatingRemoteBackup; | ||
| if ( response.success && response.status === 'finished' ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| let status: PushStateProgressInfo = pushStatesProgressInfo.creatingRemoteBackup; | |
| if ( response.success && response.status === 'finished' ) { | |
| if ( ! response.success ) { | |
| throw new Error( response.error ); | |
| } | |
| let status: PushStateProgressInfo; | |
| switch ( response.status ) { | |
| case 'initial_backup_started': | |
| status = pushStatesProgressInfo.creatingRemoteBackup; | |
| break; | |
| case 'finished': |
Let's make this a switch statement and add a case for initial_backup_started. This will allow TS to understand that we've exhausted all possible values of response.status when we call getPushStatusWithProgress
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAICT, the getPushStatusWithProgress function exported by this hook isn't used anywhere. Let's remove it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same for getPullStatusWithProgress
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And for getBackupStatusWithProgress
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also think we could remove isKeyPulling, isKeyPushing, isKeyFinished, isKeyFailed, isKeyCancelled, and isKeyUploadingPaused. They're only used in src/modules/sync/components/sync-connected-sites.tsx and could easily be replaced with plain equality comparisons.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isKeyImporting isn't used anywhere. Let's remove it.
| getIpcApi().showNotification( { | ||
| title: currentPushState.selectedSite.name, | ||
| body: sprintf( | ||
| // translators: %s is the site url without the protocol. | ||
| __( '%s has been updated' ), | ||
| getHostnameFromUrl( currentPushState.remoteSiteUrl ) | ||
| ), | ||
| } ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Side effect. Let's try to move this to a state subscription listener.
| // Helper function to calculate push status with progress (inlined from useSyncStatesProgressInfo) | ||
| const getPushStatusWithProgress = ( | ||
| status: PushStateProgressInfo, | ||
| response: ImportResponse, | ||
| pushStatesProgressInfo: Record< PushStateProgressInfo[ 'key' ], PushStateProgressInfo > | ||
| ): PushStateProgressInfo => { | ||
| if ( status.key === pushStatesProgressInfo.creatingRemoteBackup.key ) { | ||
| const progressRange = | ||
| pushStatesProgressInfo.applyingChanges.progress - | ||
| pushStatesProgressInfo.creatingRemoteBackup.progress; | ||
|
|
||
| // This step will increase the progress to the next step progressively based on the backup_progress | ||
| return { | ||
| ...status, | ||
| progress: | ||
| pushStatesProgressInfo.creatingRemoteBackup.progress + | ||
| progressRange * ( response.backup_progress / 100 ), | ||
| }; | ||
| } | ||
|
|
||
| // This step will increase the progress to the next step progressively based on the import_progress | ||
| if ( | ||
| status.key === pushStatesProgressInfo.applyingChanges.key && | ||
| response.import_progress < 100 | ||
| ) { | ||
| const progressRange = | ||
| pushStatesProgressInfo.finishing.progress - pushStatesProgressInfo.applyingChanges.progress; | ||
| return { | ||
| ...status, | ||
| progress: | ||
| pushStatesProgressInfo.applyingChanges.progress + | ||
| progressRange * ( response.import_progress / 100 ), | ||
| }; | ||
| } | ||
| return status; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's inline this logic in pollPushProgressThunk. I believe it will end up shorter and easier to read.
| updatePushStateWithIpc( | ||
| dispatch, | ||
| selectedSiteId, | ||
| remoteSiteId, | ||
| { status }, | ||
| isKeyFailed, | ||
| isKeyFinished | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| updatePushStateWithIpc( | |
| dispatch, | |
| selectedSiteId, | |
| remoteSiteId, | |
| { status }, | |
| isKeyFailed, | |
| isKeyFinished | |
| ); | |
| dispatch( | |
| syncOperationsActions.updatePushState( { | |
| selectedSiteId, | |
| remoteSiteId, | |
| state: { status }, | |
| } ) | |
| ); |
Again, let's remove updatePushStateWithIpc
fredrikekelund
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments related to pollPullBackupThunk and completePullThunk
| // Check if state exists and is not cancelled | ||
| const state = getState(); | ||
| const currentPullState = syncOperationsSelectors.selectPullState( | ||
| selectedSiteId, | ||
| remoteSiteId | ||
| )( state ); | ||
|
|
||
| if ( | ||
| ! currentPullState || | ||
| ! currentPullState.status || | ||
| isKeyCancelled( currentPullState.status.key ) | ||
| ) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as for pollPushProgressThunk, we should trigger (or perhaps register/unregister a setInterval/setTimeout callback) in a state subscription callback. If we do that, we should be able to remove this logic that starts the thunk by checking if we should cancel the operation.
| // Backup completed, trigger completion thunk | ||
| await dispatch( | ||
| syncOperationsThunks.completePull( { | ||
| selectedSiteId, | ||
| remoteSiteId, | ||
| downloadUrl, | ||
| pullStatesProgressInfo, | ||
| } ) | ||
| ).unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
completePull doesn't actually stand on its own legs. I.e., this is the only place we call that thunk. I argue we should inline the logic from this function into this thunk. It'll clarify the overall API in this file.
| // Helper function to calculate backup status with progress (inlined from useSyncStatesProgressInfo) | ||
| const getBackupStatusWithProgress = ( | ||
| hasBackupCompleted: boolean, | ||
| pullStatesProgressInfo: Record< PullStateProgressInfo[ 'key' ], PullStateProgressInfo >, | ||
| response: SyncBackupResponse | ||
| ): PullStateProgressInfo => { | ||
| const frontendStatus = hasBackupCompleted | ||
| ? pullStatesProgressInfo.downloading.key | ||
| : response.status; | ||
| let newProgressInfo: PullStateProgressInfo | null = null; | ||
| if ( response.status === 'in-progress' ) { | ||
| newProgressInfo = { | ||
| ...pullStatesProgressInfo[ frontendStatus ], | ||
| // Update progress from the initial value to the new step proportionally to the response.progress | ||
| // on every update of the response.progress | ||
| progress: | ||
| IN_PROGRESS_INITIAL_VALUE + IN_PROGRESS_TO_DOWNLOADING_STEP * ( response.percent / 100 ), | ||
| }; | ||
| } | ||
| const statusWithProgress = newProgressInfo || pullStatesProgressInfo[ frontendStatus ]; | ||
|
|
||
| return statusWithProgress; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as with getPushStatusWithProgress, I would just inline this logic in pollPullBackupThunk.
| } catch ( error ) { | ||
| console.error( 'Failed to fetch backup status:', error ); | ||
| throw error; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move this error handling to extraReducers
| // Update IPC sync operation | ||
| const stateId = generateStateId( selectedSiteId, remoteSiteId ); | ||
| getIpcApi().addSyncOperation( stateId ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Side effect. Let's move to a state subscription callback.
| // Check if cancelled after download | ||
| const stateAfterDownload = getState(); | ||
| const pullStateAfterDownload = syncOperationsSelectors.selectPullState( | ||
| selectedSiteId, | ||
| remoteSiteId | ||
| )( stateAfterDownload ); | ||
|
|
||
| if ( | ||
| ! pullStateAfterDownload || | ||
| ! pullStateAfterDownload.status || | ||
| isKeyCancelled( pullStateAfterDownload.status.key ) | ||
| ) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, let's try this strategy for aborting thunks.
| // Show notification | ||
| getIpcApi().showNotification( { | ||
| title: selectedSite.name, | ||
| body: sprintf( | ||
| // translators: %s is the site url without the protocol. | ||
| __( 'Studio site has been updated from %s' ), | ||
| getHostnameFromUrl( remoteSiteUrl ) | ||
| ), | ||
| } ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Side effect. Let's move to a state subscription listener.
| Sentry.captureException( error ); | ||
| dispatch( | ||
| syncOperationsActions.updatePullState( { | ||
| selectedSiteId, | ||
| remoteSiteId, | ||
| state: { | ||
| status: pullStatesProgressInfo.failed, | ||
| }, | ||
| } ) | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move this to extraReducers. Again, using a notation like this: builder.addCase( pollPullBackupThunk.rejected
| getIpcApi().showErrorMessageBox( { | ||
| title: sprintf( __( 'Error pulling from %s' ), selectedSite.name ), | ||
| message: __( 'Failed to check backup file size. Please try again.' ), | ||
| } ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Side effect. Let's move to a state subscription handler.
| } catch ( error ) { | ||
| console.error( 'Backup completion failed:', error ); | ||
|
|
||
| // Check if cancelled | ||
| const errorState = getState(); | ||
| const pullStateOnError = syncOperationsSelectors.selectPullState( | ||
| selectedSiteId, | ||
| remoteSiteId | ||
| )( errorState ); | ||
|
|
||
| if ( | ||
| pullStateOnError && | ||
| pullStateOnError.status && | ||
| isKeyCancelled( pullStateOnError.status.key ) | ||
| ) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove this. Errors can be handled per the strategy outlined below.
…dio-refactor-pull-and-push-states-from-syncsitesprovider
Migrate the changes of PR #2332
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make this a thunk in src/stores/sync/sync-operations-slice.ts. Because the auth details are still not stored in Redux, we can dispatch the thunk from src/components/app.tsx and pass the client from the useAuth return value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we can trigger the polling thunks from state subscription callbacks, then we should be able to remove this hook.
|
If we move the logic for triggering polling thunks to state subscription callbacks, that should unblock us from removing Let's focus on simplifying |
Related issues
Fixes STU-711
Proposed Changes
SyncSitesProvidercompletelysyncOperationsRedux sliceuse-sync-pullanduse-sync-pushhooks to Redux thunksTesting Instructions
npm startPre-merge Checklist