Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/hooks/sync-sites/use-sync-push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export type SyncPushState = {
status: PushStateProgressInfo;
selectedSite: SiteDetails;
remoteSiteUrl: string;
uploadProgress?: number;
};

type PushSiteOptions = {
Expand Down Expand Up @@ -99,11 +100,13 @@ export function useSyncPush( {
const {
pushStatesProgressInfo,
isKeyPushing,
isKeyUploading,
isKeyImporting,
isKeyFinished,
isKeyFailed,
isKeyCancelled,
getPushStatusWithProgress,
mapUploadProgressToOverallProgress,
} = useSyncStatesProgressInfo();

const updatePushState = useCallback< UpdateState< SyncPushState > >(
Expand Down Expand Up @@ -324,6 +327,7 @@ export function useSyncPush( {
if ( response.success ) {
updatePushState( selectedSite.id, remoteSiteId, {
status: pushStatesProgressInfo.creatingRemoteBackup,
uploadProgress: undefined, // Clear upload progress when transitioning to next state
} );
} else {
throw response;
Expand Down Expand Up @@ -392,12 +396,32 @@ export function useSyncPush( {
useIpcListener(
'sync-upload-resumed',
( _event, payload: { selectedSiteId: string; remoteSiteId: number } ) => {
const currentState = getPushState( payload.selectedSiteId, payload.remoteSiteId );
updatePushState( payload.selectedSiteId, payload.remoteSiteId, {
status: pushStatesProgressInfo.uploading,
uploadProgress: currentState?.uploadProgress,
} );
}
);

useIpcListener(
'sync-upload-progress',
( _event, payload: { selectedSiteId: string; remoteSiteId: number; progress: number } ) => {
const currentState = getPushState( payload.selectedSiteId, payload.remoteSiteId );
if ( currentState && isKeyUploading( currentState.status.key ) ) {
const mappedProgress = mapUploadProgressToOverallProgress( payload.progress );

updatePushState( payload.selectedSiteId, payload.remoteSiteId, {
status: {
...currentState.status,
progress: mappedProgress,
},
uploadProgress: payload.progress,
} );
}
}
);

const isAnySitePushing = useMemo< boolean >( () => {
return Object.values( pushStates ).some( ( state ) => isKeyPushing( state.status.key ) );
}, [ pushStates, isKeyPushing ] );
Expand Down
171 changes: 109 additions & 62 deletions src/hooks/use-sync-states-progress-info.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { sprintf } from '@wordpress/i18n';
import { useI18n } from '@wordpress/react-i18n';
import { useCallback, useMemo } from 'react';
import { ImportProgressState } from './use-import-export';
Expand Down Expand Up @@ -56,6 +57,80 @@ const DOWNLOADING_INITIAL_VALUE = 60;
const IN_PROGRESS_TO_DOWNLOADING_STEP = DOWNLOADING_INITIAL_VALUE - IN_PROGRESS_INITIAL_VALUE;
const PULL_IMPORTING_INITIAL_VALUE = 80;

function isKeyPulling( key: PullStateProgressInfo[ 'key' ] | undefined ): boolean {
const pullingStateKeys: PullStateProgressInfo[ 'key' ][] = [
'in-progress',
'downloading',
'importing',
];
if ( ! key ) {
return false;
}
return pullingStateKeys.includes( key );
}

function isKeyPushing( key: PushStateProgressInfo[ 'key' ] | undefined ): boolean {
const pushingStateKeys: PushStateProgressInfo[ 'key' ][] = [
'creatingBackup',
'uploading',
'creatingRemoteBackup',
'applyingChanges',
'finishing',
];
if ( ! key ) {
return false;
}
return pushingStateKeys.includes( key );
}

function isKeyUploadingPaused( key: PushStateProgressInfo[ 'key' ] | undefined ): boolean {
return key === 'uploadingPaused';
}

function isKeyUploading( key: PushStateProgressInfo[ 'key' ] | undefined ): boolean {
return key === 'uploading';
}

function isKeyImporting( key: PushStateProgressInfo[ 'key' ] | undefined ): boolean {
const pushingStateKeys: PushStateProgressInfo[ 'key' ][] = [
'creatingRemoteBackup',
'applyingChanges',
'finishing',
];
if ( ! key ) {
return false;
}
return pushingStateKeys.includes( key );
}

function isKeyFinished(
key: PullStateProgressInfo[ 'key' ] | PushStateProgressInfo[ 'key' ] | undefined
): boolean {
return key === 'finished';
}

function isKeyFailed(
key: PullStateProgressInfo[ 'key' ] | PushStateProgressInfo[ 'key' ] | undefined
): boolean {
return key === 'failed';
}

function isKeyCancelled(
key: PullStateProgressInfo[ 'key' ] | PushStateProgressInfo[ 'key' ] | undefined
): boolean {
return key === 'cancelled';
}

function getPushUploadPercentage(
statusKey: PushStateProgressInfo[ 'key' ] | undefined,
uploadProgress: number | undefined
): number | null {
if ( isKeyUploading( statusKey ) && uploadProgress !== undefined ) {
return Math.round( uploadProgress );
}
return null;
}

export function useSyncStatesProgressInfo() {
const { __ } = useI18n();
const pullStatesProgressInfo = useMemo( () => {
Expand Down Expand Up @@ -104,7 +179,7 @@ export function useSyncStatesProgressInfo() {
uploading: {
key: 'uploading',
progress: 40,
message: __( 'Uploading Studio site…' ),
message: __( 'Uploading site…' ),
},
uploadingPaused: {
key: 'uploadingPaused',
Expand Down Expand Up @@ -144,67 +219,7 @@ export function useSyncStatesProgressInfo() {
} as const satisfies PushStateProgressInfoValues;
}, [ __ ] );

const isKeyPulling = ( key: PullStateProgressInfo[ 'key' ] | undefined ) => {
const pullingStateKeys: PullStateProgressInfo[ 'key' ][] = [
'in-progress',
'downloading',
'importing',
];
if ( ! key ) {
return false;
}
return pullingStateKeys.includes( key );
};

const isKeyPushing = ( key: PushStateProgressInfo[ 'key' ] | undefined ) => {
const pushingStateKeys: PushStateProgressInfo[ 'key' ][] = [
'creatingBackup',
'uploading',
'creatingRemoteBackup',
'applyingChanges',
'finishing',
];
if ( ! key ) {
return false;
}
return pushingStateKeys.includes( key );
};

const isKeyUploadingPaused = ( key: PushStateProgressInfo[ 'key' ] | undefined ) => {
return key === 'uploadingPaused';
};

const isKeyImporting = ( key: PushStateProgressInfo[ 'key' ] | undefined ) => {
const pushingStateKeys: PushStateProgressInfo[ 'key' ][] = [
'creatingRemoteBackup',
'applyingChanges',
'finishing',
];
if ( ! key ) {
return false;
}
return pushingStateKeys.includes( key );
};
const isKeyFinished = useCallback(
( key: PullStateProgressInfo[ 'key' ] | PushStateProgressInfo[ 'key' ] | undefined ) => {
return key === 'finished';
},
[]
);

const isKeyFailed = useCallback(
( key: PullStateProgressInfo[ 'key' ] | PushStateProgressInfo[ 'key' ] | undefined ) => {
return key === 'failed';
},
[]
);

const isKeyCancelled = useCallback(
( key: PullStateProgressInfo[ 'key' ] | PushStateProgressInfo[ 'key' ] | undefined ) => {
return key === 'cancelled';
},
[]
);
const uploadingProgressMessageTemplate = useMemo( () => __( 'Uploading site (%d%%)…' ), [ __ ] );

const getBackupStatusWithProgress = useCallback(
(
Expand Down Expand Up @@ -294,6 +309,34 @@ export function useSyncStatesProgressInfo() {
]
);

const getPushUploadMessage = useCallback(
( message: string, uploadPercentage: number | null ): string => {
if ( uploadPercentage !== null ) {
// translators: %d is the upload progress percentage
return sprintf( uploadingProgressMessageTemplate, uploadPercentage );
}
return message;
},
[ uploadingProgressMessageTemplate ]
);

const mapUploadProgressToOverallProgress = useCallback(
( uploadProgress: number ): number => {
// Map upload progress (0-100%) to the uploading state range (40-50%)
const uploadingProgressRange =
pushStatesProgressInfo.creatingRemoteBackup.progress -
pushStatesProgressInfo.uploading.progress;
return (
pushStatesProgressInfo.uploading.progress +
( uploadProgress / 100 ) * uploadingProgressRange
);
},
[
pushStatesProgressInfo.creatingRemoteBackup.progress,
pushStatesProgressInfo.uploading.progress,
]
);

return {
pullStatesProgressInfo,
pushStatesProgressInfo,
Expand All @@ -303,9 +346,13 @@ export function useSyncStatesProgressInfo() {
isKeyFinished,
isKeyFailed,
isKeyCancelled,
isKeyUploading,
getBackupStatusWithProgress,
getPullStatusWithProgress,
getPushStatusWithProgress,
getPushUploadPercentage,
getPushUploadMessage,
mapUploadProgressToOverallProgress,
isKeyUploadingPaused,
};
}
1 change: 1 addition & 0 deletions src/ipc-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface IpcEvents {
'site-context-menu-action': [ { action: string; siteId: string } ];
'sync-upload-paused': [ { error: string; selectedSiteId: string; remoteSiteId: number } ];
'sync-upload-resumed': [ { selectedSiteId: string; remoteSiteId: number } ];
'sync-upload-progress': [ { selectedSiteId: string; remoteSiteId: number; progress: number } ];
'snapshot-error': [ { operationId: crypto.UUID; data: SnapshotEventData } ];
'snapshot-fatal-error': [ { operationId: crypto.UUID; data: { message: string } } ];
'snapshot-output': [ { operationId: crypto.UUID; data: SnapshotEventData } ];
Expand Down
9 changes: 8 additions & 1 deletion src/modules/sync/components/sync-connected-sites.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ const SyncConnectedSitesSectionItem = ( {
isKeyFailed,
isKeyCancelled,
getPullStatusWithProgress,
getPushUploadPercentage,
getPushUploadMessage,
isKeyUploadingPaused,
} = useSyncStatesProgressInfo();

Expand All @@ -217,6 +219,11 @@ const SyncConnectedSitesSectionItem = ( {
const hasPushFinished = pushState && isKeyFinished( pushState.status.key );
const hasPushCancelled = pushState && isKeyCancelled( pushState.status.key );

const uploadPercentage = getPushUploadPercentage(
pushState?.status.key,
pushState?.uploadProgress
);

return (
<div className="grid grid-cols-[max-content_1fr_max-content]">
<div
Expand Down Expand Up @@ -322,7 +329,7 @@ const SyncConnectedSitesSectionItem = ( {
<div className="flex flex-col gap-2 min-w-44 flex-shrink">
<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
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
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
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

</div>
<ProgressBar value={ pushState.status.progress } maxValue={ 100 } />
</div>
Expand Down
10 changes: 9 additions & 1 deletion src/modules/sync/lib/ipc-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ export async function pushArchive(
console.error( '[TUS] Upload error', error );
reject( error );
},
onProgress: () => {
onProgress: ( bytesSent: number, bytesTotal: number ) => {
if ( isUploadingPaused ) {
isUploadingPaused = false;
void sendIpcEventToRenderer( 'sync-upload-resumed', {
Expand All @@ -207,6 +207,14 @@ export async function pushArchive(
if ( ! hasUploadStarted ) {
hasUploadStarted = true;
}

// Calculate upload progress percentage (0-100)
const uploadProgress = bytesTotal > 0 ? ( bytesSent / bytesTotal ) * 100 : 0;
void sendIpcEventToRenderer( 'sync-upload-progress', {
selectedSiteId: selectedSiteId,
remoteSiteId: remoteSiteId,
progress: uploadProgress,
} );
},
onSuccess: ( payload ) => {
if ( ! payload.lastResponse ) {
Expand Down