Skip to content
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

Add Sync Push Site #662

Merged
merged 19 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from 16 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
10 changes: 5 additions & 5 deletions src/components/content-tab-import-export.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export const ExportSite = ( { selectedSite }: { selectedSite: SiteDetails } ) =>
const { exportState, exportFullSite, exportDatabase, importState } = useImportExport();
const { [ selectedSite.id ]: currentProgress } = exportState;
const isSiteImporting = importState[ selectedSite.id ]?.progress < 100;
const { isAnySitePulling } = useSyncSites();
const isExportDisabled = isSiteImporting || isAnySitePulling;
const { isAnySitePulling, isAnySitePushing } = useSyncSites();
const isExportDisabled = isSiteImporting || isAnySitePulling || isAnySitePushing;
const siteNotReadyForExportMessage = __(
'This site is being imported. Please wait for the import to finish before you export it.'
);
Expand Down Expand Up @@ -109,7 +109,7 @@ const ImportSite = ( props: { selectedSite: SiteDetails } ) => {
const { startServer, loadingServer } = useSiteDetails();
const { importState, importFile, clearImportState, exportState } = useImportExport();
const { [ props.selectedSite.id ]: currentProgress } = importState;
const { isAnySitePulling } = useSyncSites();
const { isAnySitePulling, isAnySitePushing } = useSyncSites();
const isSiteExporting =
exportState[ props.selectedSite?.id ] && exportState[ props.selectedSite?.id ].progress < 100;

Expand Down Expand Up @@ -163,7 +163,7 @@ const ImportSite = ( props: { selectedSite: SiteDetails } ) => {
const startLoadingCursorClassName =
loadingServer[ props.selectedSite.id ] && 'animate-pulse duration-100 cursor-wait';

const isImporting = currentProgress?.progress < 100 && ! isAnySitePulling;
const isImporting = currentProgress?.progress < 100 && ! isAnySitePulling && ! isAnySitePushing;
const isImported = currentProgress?.progress === 100 && ! isDraggingOver;
const isInitial = ! isImporting && ! isImported;
return (
Expand All @@ -186,7 +186,7 @@ const ImportSite = ( props: { selectedSite: SiteDetails } ) => {
<InitialImportButton
isInitial={ isInitial }
openFileSelector={ openFileSelector }
disabled={ isSiteExporting || isAnySitePulling }
disabled={ isSiteExporting || isAnySitePulling || isAnySitePushing }
>
<div
className={ cx(
Expand Down
149 changes: 98 additions & 51 deletions src/components/sync-connected-sites.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import { cloudUpload, cloudDownload } from '@wordpress/icons';
import { useI18n } from '@wordpress/react-i18n';
import { useMemo } from 'react';
import { useSyncSites } from '../hooks/sync-sites';
import { useConfirmationDialog } from '../hooks/use-confirmation-dialog';
import { SyncSite } from '../hooks/use-fetch-wpcom-sites';
import { useSyncStatesProgressInfo } from '../hooks/use-sync-states-progress-info';
import { getIpcApi } from '../lib/get-ipc-api';
import { ArrowIcon } from './arrow-icon';
import { Badge } from './badge';
import Button from './button';
import { CheckIcon } from './check-icon';
import { ErrorIcon } from './error-icon';
import ProgressBar from './progress-bar';
import { SyncPullPushClear } from './sync-pull-push-clear';
import Tooltip from './tooltip';
import { WordPressLogoCircle } from './wordpress-logo-circle';

Expand All @@ -35,8 +35,33 @@ export function SyncConnectedSites( {
selectedSite: SiteDetails;
} ) {
const { __ } = useI18n();
const { pullSite, clearPullState, getPullState, isAnySitePulling } = useSyncSites();
const {
pullSite,
clearPullState,
getPullState,
isAnySitePulling,
isAnySitePushing,
pushSite,
getPushState,
clearPushState,
} = useSyncSites();
const { isKeyPulling, isKeyFinished, isKeyFailed } = useSyncStatesProgressInfo();
const showPushStagingConfirmation = useConfirmationDialog( {
localStorageKey: 'dontShowPushConfirmation',
message: __( 'Overwrite Staging site' ),
detail: __(
'Pushing will replace the existing files and database with a copy from your local site.\n\n The staging site will be backed-up before any changes are applied.'
),
confirmButtonLabel: __( 'Push' ),
} );
const showPushProductionConfirmation = useConfirmationDialog( {
localStorageKey: 'dontShowPushConfirmation',
message: __( 'Overwrite Production site' ),
detail: __(
'Pushing will replace the existing files and database with a copy from your local site.\n\n The production site will be backed-up before any changes are applied.'
),
confirmButtonLabel: __( 'Push' ),
} );
const siteSections: ConnectedSiteSection[] = useMemo( () => {
const siteSections: ConnectedSiteSection[] = [];
const processedSites = new Set< number >();
Expand Down Expand Up @@ -107,6 +132,14 @@ export function SyncConnectedSites( {
}
};

const handlePushSite = async ( connectedSite: SyncSite ) => {
if ( connectedSite.isStaging ) {
showPushStagingConfirmation( () => pushSite( connectedSite, selectedSite ) );
} else {
showPushProductionConfirmation( () => pushSite( connectedSite, selectedSite ) );
}
};

return (
<div className="flex flex-col h-full overflow-hidden">
<div className="flex flex-col flex-1 pt-8 overflow-y-auto">
Expand All @@ -119,7 +152,7 @@ export function SyncConnectedSites( {
variant="link"
className="!ml-auto !text-a8c-gray-70 hover:!text-a8c-red-50 "
onClick={ () => handleDisconnectSite( section.id, section.name ) }
disabled={ isAnySitePulling }
disabled={ isAnySitePulling || isAnySitePushing }
>
{ __( 'Disconnect' ) }
</Button>
Expand All @@ -129,6 +162,8 @@ export function SyncConnectedSites( {
const isPulling = sitePullState && isKeyPulling( sitePullState.status.key );
const isError = sitePullState && isKeyFailed( sitePullState.status.key );
const hasPullFinished = sitePullState && isKeyFinished( sitePullState.status.key );

const pushState = getPushState( selectedSite.id, connectedSite.id );
return (
<div
key={ connectedSite.id }
Expand Down Expand Up @@ -163,58 +198,70 @@ export function SyncConnectedSites( {
</div>
) }
{ isError && (
<div className="flex gap-4 pl-4 ml-auto items-center shrink-0 text-a8c-red-50">
<span className="flex items-center gap-2">
<ErrorIcon />
{ __( 'Error pulling changes' ) }
</span>
<Button
variant="link"
className="ml-3"
onClick={ () => clearPullState( selectedSite.id, connectedSite.id ) }
>
{ __( 'Clear' ) }
</Button>
</div>
<SyncPullPushClear
onClick={ () => clearPullState( selectedSite.id, connectedSite.id ) }
isError
>
{ __( 'Error pulling changes' ) }
</SyncPullPushClear>
) }
{ hasPullFinished && (
<div className="flex gap-4 pl-4 ml-auto items-center shrink-0 text-a8c-green-50">
<span className="flex items-center gap-2">
<CheckIcon />
{ __( 'Pull complete' ) }
</span>
<Button
variant="link"
className="ml-3"
onClick={ () => clearPullState( selectedSite.id, connectedSite.id ) }
>
{ __( 'Clear' ) }
</Button>
</div>
<SyncPullPushClear
onClick={ () => clearPullState( selectedSite.id, connectedSite.id ) }
>
{ __( 'Pull complete' ) }
</SyncPullPushClear>
) }
{ ! isPulling && ! hasPullFinished && ! isError && (
<div className="flex gap-2 pl-4 ml-auto shrink-0 h-5">
<Button
variant="link"
className="!text-black hover:!text-a8c-blueberry"
onClick={ () => {
pullSite( connectedSite, selectedSite );
} }
disabled={ isAnySitePulling }
>
<Icon icon={ cloudDownload } />
{ __( 'Pull' ) }
</Button>
<Button
variant="link"
className="!text-black hover:!text-a8c-blueberry"
disabled={ isAnySitePulling }
>
<Icon icon={ cloudUpload } />
{ __( 'Push' ) }
</Button>
{ pushState.status && pushState.isInProgress && (
<div className="flex flex-col gap-2 min-w-44">
<div className="a8c-body-small">{ pushState.status.message }</div>
<ProgressBar value={ pushState.status.progress } maxValue={ 100 } />
</div>
) }
{ pushState.status && pushState.isError && (
<SyncPullPushClear
onClick={ () => clearPushState( selectedSite.id, connectedSite.id ) }
isError
>
{ pushState.status.message }
</SyncPullPushClear>
) }
{ pushState.status && pushState.hasFinished && (
<SyncPullPushClear
onClick={ () => clearPushState( selectedSite.id, connectedSite.id ) }
>
{ pushState.status.message }
</SyncPullPushClear>
) }
{ ! isPulling &&
! hasPullFinished &&
! isError &&
! pushState.isInProgress &&
! pushState.isError &&
! pushState.hasFinished && (
<div className="flex gap-2 pl-4 ml-auto shrink-0 h-5">
<Button
variant="link"
className="!text-black hover:!text-a8c-blueberry"
onClick={ () => {
pullSite( connectedSite, selectedSite );
} }
disabled={ isAnySitePulling || isAnySitePushing }
>
<Icon icon={ cloudDownload } />
{ __( 'Pull' ) }
</Button>
<Button
variant="link"
className="!text-black hover:!text-a8c-blueberry"
onClick={ () => handlePushSite( connectedSite ) }
disabled={ isAnySitePulling || isAnySitePushing }
>
<Icon icon={ cloudUpload } />
{ __( 'Push' ) }
</Button>
</div>
) }
</div>
</div>
);
Expand Down
33 changes: 33 additions & 0 deletions src/components/sync-pull-push-clear.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useI18n } from '@wordpress/react-i18n';
import { cx } from '../lib/cx';
import Button from './button';
import { CheckIcon } from './check-icon';
import { ErrorIcon } from './error-icon';

export function SyncPullPushClear( {
onClick,
children,
isError,
}: {
onClick: () => void;
children: React.ReactNode;
isError?: boolean;
} ) {
const { __ } = useI18n();
return (
<div
className={ cx(
'flex gap-4 pl-4 ml-auto items-center shrink-0',
isError ? 'text-a8c-red-50' : 'text-a8c-green-50'
) }
>
<span className="flex items-center gap-2">
{ isError ? <ErrorIcon /> : <CheckIcon /> }
{ children }
</span>
<Button variant="link" className="ml-3" onClick={ onClick }>
{ __( 'Clear' ) }
</Button>
</div>
);
}
22 changes: 18 additions & 4 deletions src/components/tests/content-tab-sync.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ const selectedSite: SiteDetails = {
id: 'site-id',
};

const defaultPushState = {
remoteSiteId: 1,
status: null,
selectedSite,
isStaging: false,
isInProgress: false,
isError: false,
hasFinished: false,
};

describe( 'ContentTabSync', () => {
beforeEach( () => {
jest.resetAllMocks();
Expand All @@ -36,9 +46,10 @@ describe( 'ContentTabSync', () => {
connectedSites: [],
syncSites: [],
pullSite: jest.fn(),
pullStates: {},
isAnySitePulling: false,
isAnySitePushing: false,
getPullState: jest.fn(),
getPushState: jest.fn().mockReturnValue( defaultPushState ),
refetchSites: jest.fn(),
} );
} );
Expand Down Expand Up @@ -105,9 +116,10 @@ describe( 'ContentTabSync', () => {
connectedSites: [ fakeSyncSite ],
syncSites: [ fakeSyncSite ],
pullSite: jest.fn(),
pullStates: {},
isAnySitePulling: false,
isAnySitePushing: false,
getPullState: jest.fn(),
getPushState: jest.fn().mockReturnValue( defaultPushState ),
refetchSites: jest.fn(),
} );
renderWithProvider( <ContentTabSync selectedSite={ selectedSite } /> );
Expand All @@ -133,9 +145,10 @@ describe( 'ContentTabSync', () => {
connectedSites: [ fakeSyncSite ],
syncSites: [ fakeSyncSite ],
pullSite: jest.fn(),
pullStates: {},
isAnySitePulling: false,
isAnySitePushing: false,
getPullState: jest.fn(),
getPushState: jest.fn().mockReturnValue( defaultPushState ),
refetchSites: jest.fn(),
} );
renderWithProvider( <ContentTabSync selectedSite={ selectedSite } /> );
Expand Down Expand Up @@ -170,9 +183,10 @@ describe( 'ContentTabSync', () => {
connectedSites: [ fakeProductionSite, fakeStagingSite ],
syncSites: [ fakeProductionSite ],
pullSite: jest.fn(),
pullStates: {},
isAnySitePulling: false,
isAnySitePushing: false,
getPullState: jest.fn(),
getPushState: jest.fn().mockReturnValue( defaultPushState ),
refetchSites: jest.fn(),
} );
renderWithProvider( <ContentTabSync selectedSite={ selectedSite } /> );
Expand Down
6 changes: 4 additions & 2 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ export const SCREENSHOT_WIDTH = 1040;
export const SCREENSHOT_HEIGHT = 1248;
export const LIMIT_OF_ZIP_SITES_PER_USER = 5;
export const LIMIT_OF_PROMPTS_PER_USER = 200;
export const SIZE_LIMIT_MB = 250;
export const SIZE_LIMIT_BYTES = SIZE_LIMIT_MB * 1024 * 1024; // 250MB
export const DEMO_SITE_SIZE_LIMIT_MB = 250;
export const DEMO_SITE_SIZE_LIMIT_BYTES = DEMO_SITE_SIZE_LIMIT_MB * 1024 * 1024; // 250MB
export const SYNC_PUSH_SIZE_LIMIT_GB = 2;
export const SYNC_PUSH_SIZE_LIMIT_BYTES = SYNC_PUSH_SIZE_LIMIT_GB * 1024 * 1024 * 1024; // 2GB
export const AUTO_UPDATE_INTERVAL_MS = 60 * 60 * 1000;
export const WINDOWS_TITLEBAR_HEIGHT = 32;
export const ABOUT_WINDOW_WIDTH = 284;
Expand Down
16 changes: 16 additions & 0 deletions src/hooks/sync-sites/sync-sites-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import React, { createContext, useContext, useState } from 'react';
import { SyncSite } from '../use-fetch-wpcom-sites';
import { useSiteSyncManagement } from './use-site-sync-management';
import { useSyncPull } from './use-sync-pull';
import { useSyncPush } from './use-sync-push';

type SyncSitesContextType = ReturnType< typeof useSyncPull > &
ReturnType< typeof useSyncPush > &
ReturnType< typeof useSiteSyncManagement >;

const SyncSitesContext = createContext< SyncSitesContextType | undefined >( undefined );
Expand All @@ -17,6 +19,14 @@ export function SyncSitesProvider( { children }: { children: React.ReactNode } )
}
);

const [ pushStates, setPushStates ] = useState< SyncSitesContextType[ 'pushStates' ] >( {} );
const { pushSite, isAnySitePushing, isSiteIdPushing, clearPushState, getPushState } = useSyncPush(
{
pushStates,
setPushStates,
}
);

const [ connectedSites, setConnectedSites ] = useState< SyncSite[] >( [] );
const { loadConnectedSites, connectSite, disconnectSite, syncSites, isFetching, refetchSites } =
useSiteSyncManagement( { connectedSites, setConnectedSites } );
Expand All @@ -36,7 +46,13 @@ export function SyncSitesProvider( { children }: { children: React.ReactNode } )
syncSites,
refetchSites,
isFetching,
pushStates,
getPullState,
getPushState,
pushSite,
isAnySitePushing,
isSiteIdPushing,
clearPushState,
} }
>
{ children }
Expand Down
Loading
Loading