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 context for sync sites #642

Merged
merged 51 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
42f16e9
Create basic logic with new context
sejas Oct 22, 2024
a6eb057
create download function to be used as in sciprts and ipc handler
sejas Oct 22, 2024
b3f0e32
create states and progress to download a remote site zip
sejas Oct 22, 2024
111de25
Merge branch 'trunk' of github.com:Automattic/studio into add/sync-pu…
sejas Oct 29, 2024
3446340
remove calculated pull states type
sejas Oct 29, 2024
d79d62a
create auxiliar function onBackupCompleted
sejas Oct 29, 2024
b2485d2
pass selected site
sejas Oct 29, 2024
16e7943
Pass the correct filetype
sejas Oct 29, 2024
498a01c
import tar.gz jetpack backup
sejas Oct 29, 2024
dfc15d4
Add provider on content tab sync tests
sejas Oct 29, 2024
3f774d1
Merge branch 'trunk' of github.com:Automattic/studio into add/sync-pu…
sejas Oct 31, 2024
6180331
move pull state progress info to its own hook
sejas Oct 31, 2024
e7ddb99
disable importing when any site is pulling
sejas Oct 31, 2024
8663bb9
disconect pull push and disconnect when a site is pulling
sejas Oct 31, 2024
62488d6
Display min width and correct font type for progress indicator
sejas Oct 31, 2024
27f6e6b
handle error when creating backup
sejas Oct 31, 2024
369316f
Merge branch 'trunk' of github.com:Automattic/studio into add/sync-pu…
sejas Oct 31, 2024
9a7175a
Fix tests
sejas Oct 31, 2024
1c8d1db
Remove use site sync management in favor of use sync sites context
sejas Oct 31, 2024
f6fc2cd
fix tests to use useSyncSites mock
sejas Oct 31, 2024
4f3e14e
Simplify test checks
sejas Oct 31, 2024
b2aaf98
Follow same convention for import and export site components
sejas Nov 11, 2024
e8a0a07
block export while a site is being pulled
sejas Nov 11, 2024
44b70bc
Merge branch 'trunk' of github.com:Automattic/studio into add/sync-pu…
sejas Nov 11, 2024
a22422f
Refactor context to live in its own folder and separate hooks
sejas Nov 13, 2024
63641a8
Move the state management to the context to avoid using useSyncPull d…
sejas Nov 13, 2024
a8d6497
extract setPullStates logic in favor of a custom callback
sejas Nov 13, 2024
e7387df
do not update state before calling onBackupCompleted
sejas Nov 13, 2024
48ace4b
Disable start stop button when pulling a site
sejas Nov 13, 2024
fe30df2
capture sentry error when pulling
sejas Nov 13, 2024
27a8214
capture error on sentry
sejas Nov 13, 2024
33b521a
using timeout instead of interval as the state update will retrigger …
sejas Nov 13, 2024
41d070d
Replace states for consistency, mapping old completed, current finish…
sejas Nov 13, 2024
6baea65
remove sync backup after imoprt
sejas Nov 13, 2024
fd635f0
add provider to tests
sejas Nov 13, 2024
fc570c3
add provider to remaining tests
sejas Nov 13, 2024
776f1c2
display pull complete state to allow user to clear it
sejas Nov 13, 2024
05f2b98
avoid jumping row height when changing states
sejas Nov 13, 2024
51e5b9b
Display notificiation when pull completes making difference between s…
sejas Nov 13, 2024
8eef663
Display error message inside connected sites list
sejas Nov 13, 2024
4e0eb56
Merge branch 'add/sync-pull-remote-site' of github.com:Automattic/stu…
sejas Nov 13, 2024
b1bcd17
Refactor use site sync management to be inside the context
sejas Nov 13, 2024
3e95562
update tests to use the provider main hook
sejas Nov 13, 2024
7575350
clear import state after pull site to avoid showing import complete s…
sejas Nov 13, 2024
def0e92
rename type and make sure we track the sync pull progress per local site
sejas Nov 13, 2024
6f045ba
refactor to use local site id and remote site id as pull state key
sejas Nov 13, 2024
2f71b9c
create getPullState function to access the pull state
sejas Nov 13, 2024
6d7c92e
update clear site key, and make sure that we clear pull state after d…
sejas Nov 14, 2024
991b8bf
Merge branch 'add/sync-pull-remote-site' of github.com:Automattic/stu…
sejas Nov 14, 2024
8f1b599
fix tests
sejas Nov 14, 2024
a3941f7
Merge branch 'trunk' of github.com:Automattic/studio into add/sync-co…
sejas Nov 14, 2024
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
37 changes: 2 additions & 35 deletions scripts/download-wp-server-files.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import os from 'os';
import path from 'path';
import extract from 'extract-zip';
import { https } from 'follow-redirects';
import fs from 'fs-extra';
import { download } from '../src/lib/download';
import { getLatestSQLiteCommandRelease } from '../src/lib/sqlite-command-release';

const WP_SERVER_FILES_PATH = path.join( __dirname, '..', 'wp-files' );

interface FileToDownload {
Expand Down Expand Up @@ -55,40 +54,8 @@ const downloadFile = async ( file: FileToDownload ) => {
const fsError = err as { code: string };
if ( fsError.code !== 'EEXIST' ) throw err;
}
const zipFile = fs.createWriteStream( zipPath );

await new Promise< void >( ( resolve, reject ) => {
https.get( url, ( response ) => {
if ( response.statusCode !== 200 ) {
reject( new Error( `Request failed with status code: ${ response.statusCode }` ) );
return;
}

const totalSize = parseInt( response.headers[ 'content-length' ] ?? '', 10 );
let downloadedSize = 0;
const showDownloadProgress =
typeof process.stdout.clearLine === 'function' && ! isNaN( totalSize );

if ( showDownloadProgress ) {
response.on( 'data', ( chunk ) => {
downloadedSize += chunk.length;
const progress = ( ( downloadedSize / totalSize ) * 100 ).toFixed( 2 );
process.stdout.clearLine( 0 );
process.stdout.cursorTo( 0 );
process.stdout.write( `[${ name }] ${ progress }%` );
} );
}

response.pipe( zipFile );
response.on( 'end', () => {
if ( showDownloadProgress ) {
console.log();
}
zipFile.close( () => resolve() );
} );
response.on( 'error', ( err ) => reject( err ) );
} );
} );
await download( url, zipPath, true, name );

if ( name === 'wp-cli' ) {
console.log( `[${ name }] Moving WP-CLI to destination ...` );
Expand Down
18 changes: 10 additions & 8 deletions src/components/action-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useEffect, useRef, useState } from 'react';
import { cx } from '../lib/cx';
import Button, { ButtonProps } from './button';

type ActionButtonState = 'idle' | 'loading' | 'stop' | 'running' | 'importing';
type ActionButtonState = 'idle' | 'loading' | 'stop' | 'running' | 'disabled';

interface ActionButtonProps {
isRunning: boolean;
Expand All @@ -14,7 +14,8 @@ interface ActionButtonProps {
className?: string;
buttonClassName?: string;
iconSize?: number;
isImporting?: boolean;
disabled?: boolean;
buttonLabelOnDisabled: string;
}

const MIN_WIDTH = 96;
Expand Down Expand Up @@ -50,7 +51,8 @@ export const ActionButton = ( {
className,
buttonClassName,
iconSize = 10,
isImporting = false,
disabled = false,
buttonLabelOnDisabled,
}: ActionButtonProps ) => {
const { __ } = useI18n();
const [ isUserHighlighting, setIsUserHighlighting ] = useState( false );
Expand All @@ -59,8 +61,8 @@ export const ActionButton = ( {
const minSize = useRef( { width: MIN_WIDTH, height: 0 } );

let state: ActionButtonState = 'idle';
if ( isImporting ) {
state = 'importing';
if ( disabled ) {
state = 'disabled';
} else if ( isLoading ) {
state = 'loading';
} else if ( isRunning ) {
Expand All @@ -87,7 +89,7 @@ export const ActionButton = ( {
}, [ sizes ] );

const handleOnClick = () => {
if ( isLoading || isImporting ) {
if ( isLoading || disabled ) {
return;
}
onClick();
Expand Down Expand Up @@ -141,8 +143,8 @@ export const ActionButton = ( {
className: cx( defaultButtonClassName, '!text-a8c-green-50' ),
};
break;
case 'importing':
buttonLabel = __( 'Importing…' );
case 'disabled':
buttonLabel = buttonLabelOnDisabled;
buttonProps = {
// `aria-disabled` used rather than `disabled` to prevent losing button
// focus while the button's asynchronous action is pending.
Expand Down
19 changes: 19 additions & 0 deletions src/components/check-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export function CheckIcon( props: { className?: string; size?: number } ) {
const { className, size = 14 } = props;
return (
<svg
className={ className || 'fill-current' }
width={ size }
height={ size }
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M7.00016 12.4154C5.56357 12.4154 4.18582 11.8447 3.17 10.8289C2.15418 9.81304 1.5835 8.43529 1.5835 6.9987C1.5835 5.56211 2.15418 4.18436 3.17 3.16854C4.18582 2.15271 5.56357 1.58203 7.00016 1.58203C8.43675 1.58203 9.8145 2.15271 10.8303 3.16854C11.8461 4.18436 12.4168 5.56211 12.4168 6.9987C12.4168 8.43529 11.8461 9.81304 10.8303 10.8289C9.8145 11.8447 8.43675 12.4154 7.00016 12.4154ZM0.333496 6.9987C0.333496 5.23059 1.03588 3.5349 2.28612 2.28465C3.53636 1.03441 5.23205 0.332031 7.00016 0.332031C8.76827 0.332031 10.464 1.03441 11.7142 2.28465C12.9645 3.5349 13.6668 5.23059 13.6668 6.9987C13.6668 8.76681 12.9645 10.4625 11.7142 11.7127C10.464 12.963 8.76827 13.6654 7.00016 13.6654C5.23205 13.6654 3.53636 12.963 2.28612 11.7127C1.03588 10.4625 0.333496 8.76681 0.333496 6.9987ZM9.94183 5.7737L9.0585 4.89036L6.16683 7.78203L4.94183 6.55703L4.0585 7.44036L6.16683 9.5487L9.94183 5.7737Z"
/>
</svg>
);
}
22 changes: 14 additions & 8 deletions src/components/content-tab-import-export.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Icon, download } from '@wordpress/icons';
import { useI18n } from '@wordpress/react-i18n';
import { useRef } from 'react';
import { ACCEPTED_IMPORT_FILE_TYPES, STUDIO_DOCS_URL_IMPORT_EXPORT } from '../constants';
import { useSyncSites } from '../hooks/sync-sites/sync-sites-context';
import { useConfirmationDialog } from '../hooks/use-confirmation-dialog';
import { useDragAndDropFile } from '../hooks/use-drag-and-drop-file';
import { useImportExport } from '../hooks/use-import-export';
Expand All @@ -23,6 +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 siteNotReadyForExportMessage = __(
'This site is being imported. Please wait for the import to finish before you export it.'
);
Expand All @@ -49,22 +52,24 @@ export const ExportSite = ( { selectedSite }: { selectedSite: SiteDetails } ) =>
</div>
) : (
<div className="flex flex-row gap-4">
<Tooltip text={ siteNotReadyForExportMessage } disabled={ ! isSiteImporting }>
<Tooltip text={ siteNotReadyForExportMessage } disabled={ ! isExportDisabled }>
<Button
onClick={ () => handleExport( exportFullSite ) }
variant="primary"
disabled={ isSiteImporting }
disabled={ isExportDisabled }
>
{ __( 'Export entire site' ) }
</Button>
</Tooltip>
<Tooltip text={ siteNotReadyForExportMessage } disabled={ ! isSiteImporting }>
<Tooltip text={ siteNotReadyForExportMessage } disabled={ ! isExportDisabled }>
<Button
onClick={ () => handleExport( exportDatabase ) }
type="submit"
variant="secondary"
className={ cx( isSiteImporting ? '' : '!text-a8c-blueberry !shadow-a8c-blueberry' ) }
disabled={ isSiteImporting }
className={ cx(
isExportDisabled ? '' : '!text-a8c-blueberry !shadow-a8c-blueberry'
) }
disabled={ isExportDisabled }
>
{ __( 'Export database' ) }
</Button>
Expand Down Expand Up @@ -104,6 +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 isSiteExporting =
exportState[ props.selectedSite?.id ] && exportState[ props.selectedSite?.id ].progress < 100;

Expand Down Expand Up @@ -157,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;
const isImporting = currentProgress?.progress < 100 && ! isAnySitePulling;
const isImported = currentProgress?.progress === 100 && ! isDraggingOver;
const isInitial = ! isImporting && ! isImported;
return (
Expand All @@ -180,7 +186,7 @@ const ImportSite = ( props: { selectedSite: SiteDetails } ) => {
<InitialImportButton
isInitial={ isInitial }
openFileSelector={ openFileSelector }
disabled={ isSiteExporting }
disabled={ isSiteExporting || isAnySitePulling }
>
<div
className={ cx(
Expand Down Expand Up @@ -244,7 +250,7 @@ export function ContentTabImportExport( { selectedSite }: ContentTabImportExport
return (
<div className="flex flex-col p-8 gap-8">
<ImportSite selectedSite={ selectedSite } />
<ExportSite selectedSite={ selectedSite }></ExportSite>
<ExportSite selectedSite={ selectedSite } />
</div>
);
}
7 changes: 3 additions & 4 deletions src/components/content-tab-sync.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { check, Icon } from '@wordpress/icons';
import { useI18n } from '@wordpress/react-i18n';
import { PropsWithChildren, useState } from 'react';
import { CLIENT_ID, PROTOCOL_PREFIX, SCOPES, WP_AUTHORIZE_ENDPOINT } from '../constants';
import { useSyncSites } from '../hooks/sync-sites';
import { useAuth } from '../hooks/use-auth';
import { SyncSite } from '../hooks/use-fetch-wpcom-sites';
import { useOffline } from '../hooks/use-offline';
import { useSiteSyncManagement } from '../hooks/use-site-sync-management';
import { cx } from '../lib/cx';
import { getIpcApi } from '../lib/get-ipc-api';
import { ArrowIcon } from './arrow-icon';
Expand Down Expand Up @@ -160,11 +160,9 @@ function NoAuthSyncTab() {
);
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function ContentTabSync( { selectedSite }: { selectedSite: SiteDetails } ) {
const { __ } = useI18n();
const { connectedSites, connectSite, disconnectSite, syncSites, isFetching } =
useSiteSyncManagement();
const { connectedSites, connectSite, disconnectSite, syncSites, isFetching } = useSyncSites();
const [ isSyncSitesSelectorOpen, setIsSyncSitesSelectorOpen ] = useState( false );
const { isAuthenticated } = useAuth();
if ( ! isAuthenticated ) {
Expand All @@ -187,6 +185,7 @@ export function ContentTabSync( { selectedSite }: { selectedSite: SiteDetails }
{ connectedSites.length > 0 ? (
<SyncConnectedSites
connectedSites={ connectedSites }
selectedSite={ selectedSite }
openSitesSyncSelector={ () => setIsSyncSitesSelectorOpen( true ) }
disconnectSite={ ( id: number ) => disconnectSite( id ) }
/>
Expand Down
21 changes: 21 additions & 0 deletions src/components/error-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export function ErrorIcon( props: { className?: string; size?: number } ) {
const { className, size = 14 } = props;
return (
<svg
className={ className || 'fill-current' }
width={ size }
height={ size }
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M7 1.5625C3.99695 1.5625 1.5625 3.99695 1.5625 7C1.5625 10.003 3.99695 12.4375 7 12.4375C10.003 12.4375 12.4375 10.003 12.4375 7C12.4375 3.99695 10.003 1.5625 7 1.5625ZM0.4375 7C0.4375 3.37563 3.37563 0.4375 7 0.4375C10.6244 0.4375 13.5625 3.37563 13.5625 7C13.5625 10.6244 10.6244 13.5625 7 13.5625C3.37563 13.5625 0.4375 10.6244 0.4375 7Z"
/>
<path d="M7.75 3.25H6.25V7.75H7.75V3.25Z" />
<path d="M7.75 9.25H6.25V10.75H7.75V9.25Z" />
</svg>
);
}
5 changes: 4 additions & 1 deletion src/components/root.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { SyncSitesProvider } from '../hooks/sync-sites/sync-sites-context';
import { ChatProvider } from '../hooks/use-chat-context';
import { ChatInputProvider } from '../hooks/use-chat-input';
import { InstalledAppsProvider } from '../hooks/use-check-installed-apps';
Expand Down Expand Up @@ -32,7 +33,9 @@ const Root = () => {
<ChatProvider>
<ImportExportProvider>
<ChatInputProvider>
<App />
<SyncSitesProvider>
<App />
</SyncSitesProvider>
</ChatInputProvider>
</ImportExportProvider>
</ChatProvider>
Expand Down
17 changes: 14 additions & 3 deletions src/components/site-management-actions.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { __ } from '@wordpress/i18n';
import { useI18n } from '@wordpress/react-i18n';
import { useSyncSites } from '../hooks/sync-sites';
import { useImportExport } from '../hooks/use-import-export';
import { ActionButton } from './action-button';
import Tooltip from './tooltip';
Expand All @@ -16,17 +18,25 @@ export const SiteManagementActions = ( {
loading,
selectedSite,
}: SiteManagementActionProps ) => {
const { __ } = useI18n();
const { isSiteImporting } = useImportExport();

const { isSiteIdPulling } = useSyncSites();
if ( ! selectedSite ) {
return null;
}

const isImporting = isSiteImporting( selectedSite.id );
const isPulling = isSiteIdPulling( selectedSite.id );
const disabled = isImporting || isPulling;

let buttonLabelOnDisabled = __( 'Importing…' );
if ( isPulling ) {
buttonLabelOnDisabled = __( 'Pulling…' );
}

return (
<Tooltip
disabled={ ! isImporting }
disabled={ ! disabled }
text={ __( "A site can't be stopped or started during import." ) }
placement="left"
>
Expand All @@ -36,7 +46,8 @@ export const SiteManagementActions = ( {
onClick={ () => {
selectedSite.running ? onStop( selectedSite.id ) : onStart( selectedSite.id );
} }
isImporting={ isImporting }
disabled={ disabled }
buttonLabelOnDisabled={ buttonLabelOnDisabled }
/>
</Tooltip>
);
Expand Down
Loading
Loading