From 99147936e77c203bbdb3a42a5c263734878d6617 Mon Sep 17 00:00:00 2001 From: Ivan Ottinger <25105483+ivan-ottinger@users.noreply.github.com> Date: Tue, 4 Nov 2025 14:29:22 +0100 Subject: [PATCH 1/3] Add Jetpack connection status check for site sync --- .../tests/reconcile-connected-sites.test.ts | 2 ++ .../use-connected-sites-operations.test.tsx | 4 +++ src/hooks/use-fetch-wpcom-sites/index.tsx | 4 +++ .../reconcile-connected-sites.tsx | 1 + src/hooks/use-fetch-wpcom-sites/types.ts | 4 ++- .../components/sync-sites-modal-selector.tsx | 25 +++++++++++++++---- src/modules/sync/tests/index.test.tsx | 12 +++++++++ 7 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/hooks/tests/reconcile-connected-sites.test.ts b/src/hooks/tests/reconcile-connected-sites.test.ts index 646a2fd719..1e808ee79d 100644 --- a/src/hooks/tests/reconcile-connected-sites.test.ts +++ b/src/hooks/tests/reconcile-connected-sites.test.ts @@ -14,6 +14,7 @@ describe( 'reconcileConnectedSites', () => { syncSupport: 'already-connected', lastPullTimestamp: null, lastPushTimestamp: null, + hasJetpack: true, }, ]; const freshWpComSites: SyncSite[] = [ @@ -27,6 +28,7 @@ describe( 'reconcileConnectedSites', () => { syncSupport: 'unsupported', lastPullTimestamp: null, lastPushTimestamp: null, + hasJetpack: true, }, ]; const result = reconcileConnectedSites( connectedSites, freshWpComSites ); diff --git a/src/hooks/tests/use-connected-sites-operations.test.tsx b/src/hooks/tests/use-connected-sites-operations.test.tsx index ff18f1d1d5..21ab2ded9f 100644 --- a/src/hooks/tests/use-connected-sites-operations.test.tsx +++ b/src/hooks/tests/use-connected-sites-operations.test.tsx @@ -27,6 +27,7 @@ const mockConnectedWpcomSites: SyncSite[] = [ syncSupport: 'syncable', lastPullTimestamp: null, lastPushTimestamp: null, + hasJetpack: true, }, { id: 7, @@ -38,6 +39,7 @@ const mockConnectedWpcomSites: SyncSite[] = [ syncSupport: 'syncable', lastPullTimestamp: null, lastPushTimestamp: null, + hasJetpack: true, }, ]; @@ -52,6 +54,7 @@ const mockSyncSites: SyncSite[] = [ syncSupport: 'syncable', lastPullTimestamp: null, lastPushTimestamp: null, + hasJetpack: true, }, { id: 9, @@ -63,6 +66,7 @@ const mockSyncSites: SyncSite[] = [ syncSupport: 'syncable', lastPullTimestamp: null, lastPushTimestamp: null, + hasJetpack: true, }, ]; diff --git a/src/hooks/use-fetch-wpcom-sites/index.tsx b/src/hooks/use-fetch-wpcom-sites/index.tsx index cc226545e3..765c2add44 100644 --- a/src/hooks/use-fetch-wpcom-sites/index.tsx +++ b/src/hooks/use-fetch-wpcom-sites/index.tsx @@ -92,6 +92,9 @@ export function getSyncSupport( site: SitesEndpointSite, connectedSiteIds: numbe if ( needsTransfer( site ) ) { return 'needs-transfer'; } + if ( ! site.jetpack ) { + return 'jetpack-disconnected'; + } if ( connectedSiteIds.some( ( id ) => id === site.ID ) ) { return 'already-connected'; } @@ -114,6 +117,7 @@ function transformSingleSiteResponse( syncSupport, lastPullTimestamp: null, lastPushTimestamp: null, + hasJetpack: !! site.jetpack, }; } diff --git a/src/hooks/use-fetch-wpcom-sites/reconcile-connected-sites.tsx b/src/hooks/use-fetch-wpcom-sites/reconcile-connected-sites.tsx index 18dafaff4e..b17df17bf9 100644 --- a/src/hooks/use-fetch-wpcom-sites/reconcile-connected-sites.tsx +++ b/src/hooks/use-fetch-wpcom-sites/reconcile-connected-sites.tsx @@ -28,6 +28,7 @@ export const reconcileConnectedSites = ( isStaging: site.isStaging, isPressable: site.isPressable, environmentType: site.environmentType, + hasJetpack: site.hasJetpack, }; }, [] ); diff --git a/src/hooks/use-fetch-wpcom-sites/types.ts b/src/hooks/use-fetch-wpcom-sites/types.ts index e6e58d080f..d717b4e6da 100644 --- a/src/hooks/use-fetch-wpcom-sites/types.ts +++ b/src/hooks/use-fetch-wpcom-sites/types.ts @@ -5,7 +5,8 @@ export type SyncSupport = | 'already-connected' | 'needs-upgrade' | 'deleted' - | 'missing-permissions'; + | 'missing-permissions' + | 'jetpack-disconnected'; export type SyncSite = { id: number; @@ -18,4 +19,5 @@ export type SyncSite = { syncSupport: SyncSupport; lastPullTimestamp: string | null; lastPushTimestamp: string | null; + hasJetpack: boolean; }; diff --git a/src/modules/sync/components/sync-sites-modal-selector.tsx b/src/modules/sync/components/sync-sites-modal-selector.tsx index c06e4b1f8b..9fdae40d4b 100644 --- a/src/modules/sync/components/sync-sites-modal-selector.tsx +++ b/src/modules/sync/components/sync-sites-modal-selector.tsx @@ -155,10 +155,11 @@ const getSortedSites = ( sites: SyncSite[] ) => { syncable: 1, 'already-connected': 2, deleted: 3, - 'missing-permissions': 4, - 'needs-transfer': 5, - 'needs-upgrade': 6, - unsupported: 7, + 'jetpack-disconnected': 4, + 'missing-permissions': 5, + 'needs-transfer': 6, + 'needs-upgrade': 7, + unsupported: 8, }; return [ ...sites ].sort( ( a, b ) => order[ a.syncSupport ] - order[ b.syncSupport ] ); @@ -199,15 +200,18 @@ function SiteItem( { onClick: () => void; } ) { const { __ } = useI18n(); + const locale = useI18nLocale(); const isAlreadyConnected = site.syncSupport === 'already-connected'; const isSyncable = site.syncSupport === 'syncable'; const isNeedsTransfer = site.syncSupport === 'needs-transfer'; const isMissingPermissions = site.syncSupport === 'missing-permissions'; const needsUpgrade = site.syncSupport === 'needs-upgrade'; const isDeleted = site.syncSupport === 'deleted'; + const isJetpackDisconnected = site.syncSupport === 'jetpack-disconnected'; const isUnsupported = site.syncSupport === 'unsupported'; const isPressable = site.isPressable; - const isDisabled = isDeleted || isUnsupported || needsUpgrade || isMissingPermissions; + const isDisabled = + isDeleted || isJetpackDisconnected || isUnsupported || needsUpgrade || isMissingPermissions; return (
) } + { isJetpackDisconnected && ( +
+ +
+ ) } { isUnsupported && (
{ __( 'Unsupported site' ) }
) } diff --git a/src/modules/sync/tests/index.test.tsx b/src/modules/sync/tests/index.test.tsx index f37f38c4a2..3cb2b64b21 100644 --- a/src/modules/sync/tests/index.test.tsx +++ b/src/modules/sync/tests/index.test.tsx @@ -89,6 +89,7 @@ const fakeSyncSite = { name: 'My simple business site that needs a transfer', url: 'https:/developer.wordpress.com/studio/', syncSupport: 'already-connected', + hasJetpack: true, }; describe( 'ContentTabSync', () => { @@ -264,6 +265,7 @@ describe( 'ContentTabSync', () => { url: 'https:/developer.wordpress.com/studio/', isStaging: false, syncSupport: 'already-connected', + hasJetpack: true, }; ( useAuth as jest.Mock ).mockReturnValue( createAuthMock( true ) ); ( useConnectedSitesData as jest.Mock ).mockReturnValue( { @@ -298,6 +300,7 @@ describe( 'ContentTabSync', () => { url: 'https:/developer.wordpress.com/studio/', isStaging: false, syncSupport: 'already-connected', + hasJetpack: true, }; ( useAuth as jest.Mock ).mockReturnValue( createAuthMock( true ) ); ( useConnectedSitesData as jest.Mock ).mockReturnValue( { @@ -361,6 +364,7 @@ describe( 'ContentTabSync', () => { localSiteId: 'site-id', lastPullTimestamp: null, lastPushTimestamp: null, + hasJetpack: true, }; const fakePressableStagingSite: SyncSite = { id: 7, @@ -373,6 +377,7 @@ describe( 'ContentTabSync', () => { localSiteId: 'site-id', lastPullTimestamp: null, lastPushTimestamp: null, + hasJetpack: true, }; const fakePressableDevelopmentSite: SyncSite = { id: 8, @@ -385,6 +390,7 @@ describe( 'ContentTabSync', () => { localSiteId: 'site-id', lastPullTimestamp: null, lastPushTimestamp: null, + hasJetpack: true, }; ( useAuth as jest.Mock ).mockReturnValue( createAuthMock( true ) ); @@ -431,6 +437,7 @@ describe( 'ContentTabSync', () => { isPressable: false, lastPullTimestamp: null, lastPushTimestamp: null, + hasJetpack: true, }; setupConnectedSitesMocks( [ fakeSyncSite ], [ fakeSyncSite ] ); @@ -463,6 +470,7 @@ describe( 'ContentTabSync', () => { syncSupport: 'already-connected', isPressable: true, environmentType: 'development', + hasJetpack: true, }; ( useConnectedSitesData as jest.Mock ).mockReturnValue( { connectedSites: [ fakeSyncSite ], @@ -502,6 +510,7 @@ describe( 'ContentTabSync', () => { syncSupport: 'already-connected', isPressable: true, environmentType: 'non-supported-environment-example-or-sandbox', + hasJetpack: true, }; ( useConnectedSitesData as jest.Mock ).mockReturnValue( { connectedSites: [ fakeSyncSite ], @@ -538,6 +547,7 @@ describe( 'ContentTabSync', () => { name: 'My simple business site that needs a transfer', url: 'https:/developer.wordpress.com/studio/', syncSupport: 'already-connected', + hasJetpack: true, }; ( useConnectedSitesData as jest.Mock ).mockReturnValue( { connectedSites: [ fakeSyncSite ], @@ -583,6 +593,7 @@ describe( 'ContentTabSync', () => { name: 'My simple business site that needs a transfer', url: 'https:/developer.wordpress.com/studio/', syncSupport: 'already-connected', + hasJetpack: true, }; ( useConnectedSitesData as jest.Mock ).mockReturnValue( { connectedSites: [ fakeSyncSite ], @@ -691,6 +702,7 @@ describe( 'ContentTabSync', () => { name: 'My simple business site that needs a transfer', url: 'https:/developer.wordpress.com/studio/', syncSupport: 'already-connected', + hasJetpack: true, }; ( useConnectedSitesData as jest.Mock ).mockReturnValue( { connectedSites: [ fakeSyncSite ], From 3536e8ae1285721245c83971144b506ee225d9d8 Mon Sep 17 00:00:00 2001 From: Ivan Ottinger <25105483+ivan-ottinger@users.noreply.github.com> Date: Tue, 4 Nov 2025 14:40:23 +0100 Subject: [PATCH 2/3] Remove unnecessary `hasJetpack` prop --- src/hooks/tests/get-sync-support.test.ts | 16 +++++++++++++--- .../tests/reconcile-connected-sites.test.ts | 2 -- .../use-connected-sites-operations.test.tsx | 4 ---- src/hooks/use-fetch-wpcom-sites/index.tsx | 1 - .../reconcile-connected-sites.tsx | 1 - src/hooks/use-fetch-wpcom-sites/types.ts | 1 - src/modules/sync/tests/index.test.tsx | 12 ------------ 7 files changed, 13 insertions(+), 24 deletions(-) diff --git a/src/hooks/tests/get-sync-support.test.ts b/src/hooks/tests/get-sync-support.test.ts index 2975280182..24d56bcb7d 100644 --- a/src/hooks/tests/get-sync-support.test.ts +++ b/src/hooks/tests/get-sync-support.test.ts @@ -81,17 +81,27 @@ describe( 'getSyncSupport', () => { } ); it( 'returns "already-connected" if site ID is in connectedSiteIds', () => { - const site = { ...baseSite, ID: 42, is_wpcom_atomic: true }; + const site = { ...baseSite, ID: 42, is_wpcom_atomic: true, jetpack: true }; expect( getSyncSupport( site, [ 42 ] ) ).toBe( 'already-connected' ); } ); it( 'returns "syncable" for Atomic site', () => { - const site = { ...baseSite, is_wpcom_atomic: true }; + const site = { ...baseSite, is_wpcom_atomic: true, jetpack: true }; expect( getSyncSupport( site, [] ) ).toBe( 'syncable' ); } ); it( 'returns "syncable" for Pressable site', () => { - const site = { ...baseSite, hosting_provider_guess: 'pressable' }; + const site = { ...baseSite, hosting_provider_guess: 'pressable', jetpack: true }; expect( getSyncSupport( site, [] ) ).toBe( 'syncable' ); } ); + + it( 'returns "jetpack-disconnected" for Pressable site without Jetpack', () => { + const site = { ...baseSite, hosting_provider_guess: 'pressable', jetpack: false }; + expect( getSyncSupport( site, [] ) ).toBe( 'jetpack-disconnected' ); + } ); + + it( 'returns "jetpack-disconnected" for Atomic site without Jetpack', () => { + const site = { ...baseSite, is_wpcom_atomic: true, jetpack: false }; + expect( getSyncSupport( site, [] ) ).toBe( 'jetpack-disconnected' ); + } ); } ); diff --git a/src/hooks/tests/reconcile-connected-sites.test.ts b/src/hooks/tests/reconcile-connected-sites.test.ts index 1e808ee79d..646a2fd719 100644 --- a/src/hooks/tests/reconcile-connected-sites.test.ts +++ b/src/hooks/tests/reconcile-connected-sites.test.ts @@ -14,7 +14,6 @@ describe( 'reconcileConnectedSites', () => { syncSupport: 'already-connected', lastPullTimestamp: null, lastPushTimestamp: null, - hasJetpack: true, }, ]; const freshWpComSites: SyncSite[] = [ @@ -28,7 +27,6 @@ describe( 'reconcileConnectedSites', () => { syncSupport: 'unsupported', lastPullTimestamp: null, lastPushTimestamp: null, - hasJetpack: true, }, ]; const result = reconcileConnectedSites( connectedSites, freshWpComSites ); diff --git a/src/hooks/tests/use-connected-sites-operations.test.tsx b/src/hooks/tests/use-connected-sites-operations.test.tsx index 21ab2ded9f..ff18f1d1d5 100644 --- a/src/hooks/tests/use-connected-sites-operations.test.tsx +++ b/src/hooks/tests/use-connected-sites-operations.test.tsx @@ -27,7 +27,6 @@ const mockConnectedWpcomSites: SyncSite[] = [ syncSupport: 'syncable', lastPullTimestamp: null, lastPushTimestamp: null, - hasJetpack: true, }, { id: 7, @@ -39,7 +38,6 @@ const mockConnectedWpcomSites: SyncSite[] = [ syncSupport: 'syncable', lastPullTimestamp: null, lastPushTimestamp: null, - hasJetpack: true, }, ]; @@ -54,7 +52,6 @@ const mockSyncSites: SyncSite[] = [ syncSupport: 'syncable', lastPullTimestamp: null, lastPushTimestamp: null, - hasJetpack: true, }, { id: 9, @@ -66,7 +63,6 @@ const mockSyncSites: SyncSite[] = [ syncSupport: 'syncable', lastPullTimestamp: null, lastPushTimestamp: null, - hasJetpack: true, }, ]; diff --git a/src/hooks/use-fetch-wpcom-sites/index.tsx b/src/hooks/use-fetch-wpcom-sites/index.tsx index 765c2add44..28c97b97e0 100644 --- a/src/hooks/use-fetch-wpcom-sites/index.tsx +++ b/src/hooks/use-fetch-wpcom-sites/index.tsx @@ -117,7 +117,6 @@ function transformSingleSiteResponse( syncSupport, lastPullTimestamp: null, lastPushTimestamp: null, - hasJetpack: !! site.jetpack, }; } diff --git a/src/hooks/use-fetch-wpcom-sites/reconcile-connected-sites.tsx b/src/hooks/use-fetch-wpcom-sites/reconcile-connected-sites.tsx index b17df17bf9..18dafaff4e 100644 --- a/src/hooks/use-fetch-wpcom-sites/reconcile-connected-sites.tsx +++ b/src/hooks/use-fetch-wpcom-sites/reconcile-connected-sites.tsx @@ -28,7 +28,6 @@ export const reconcileConnectedSites = ( isStaging: site.isStaging, isPressable: site.isPressable, environmentType: site.environmentType, - hasJetpack: site.hasJetpack, }; }, [] ); diff --git a/src/hooks/use-fetch-wpcom-sites/types.ts b/src/hooks/use-fetch-wpcom-sites/types.ts index d717b4e6da..da83f7c66e 100644 --- a/src/hooks/use-fetch-wpcom-sites/types.ts +++ b/src/hooks/use-fetch-wpcom-sites/types.ts @@ -19,5 +19,4 @@ export type SyncSite = { syncSupport: SyncSupport; lastPullTimestamp: string | null; lastPushTimestamp: string | null; - hasJetpack: boolean; }; diff --git a/src/modules/sync/tests/index.test.tsx b/src/modules/sync/tests/index.test.tsx index 3cb2b64b21..f37f38c4a2 100644 --- a/src/modules/sync/tests/index.test.tsx +++ b/src/modules/sync/tests/index.test.tsx @@ -89,7 +89,6 @@ const fakeSyncSite = { name: 'My simple business site that needs a transfer', url: 'https:/developer.wordpress.com/studio/', syncSupport: 'already-connected', - hasJetpack: true, }; describe( 'ContentTabSync', () => { @@ -265,7 +264,6 @@ describe( 'ContentTabSync', () => { url: 'https:/developer.wordpress.com/studio/', isStaging: false, syncSupport: 'already-connected', - hasJetpack: true, }; ( useAuth as jest.Mock ).mockReturnValue( createAuthMock( true ) ); ( useConnectedSitesData as jest.Mock ).mockReturnValue( { @@ -300,7 +298,6 @@ describe( 'ContentTabSync', () => { url: 'https:/developer.wordpress.com/studio/', isStaging: false, syncSupport: 'already-connected', - hasJetpack: true, }; ( useAuth as jest.Mock ).mockReturnValue( createAuthMock( true ) ); ( useConnectedSitesData as jest.Mock ).mockReturnValue( { @@ -364,7 +361,6 @@ describe( 'ContentTabSync', () => { localSiteId: 'site-id', lastPullTimestamp: null, lastPushTimestamp: null, - hasJetpack: true, }; const fakePressableStagingSite: SyncSite = { id: 7, @@ -377,7 +373,6 @@ describe( 'ContentTabSync', () => { localSiteId: 'site-id', lastPullTimestamp: null, lastPushTimestamp: null, - hasJetpack: true, }; const fakePressableDevelopmentSite: SyncSite = { id: 8, @@ -390,7 +385,6 @@ describe( 'ContentTabSync', () => { localSiteId: 'site-id', lastPullTimestamp: null, lastPushTimestamp: null, - hasJetpack: true, }; ( useAuth as jest.Mock ).mockReturnValue( createAuthMock( true ) ); @@ -437,7 +431,6 @@ describe( 'ContentTabSync', () => { isPressable: false, lastPullTimestamp: null, lastPushTimestamp: null, - hasJetpack: true, }; setupConnectedSitesMocks( [ fakeSyncSite ], [ fakeSyncSite ] ); @@ -470,7 +463,6 @@ describe( 'ContentTabSync', () => { syncSupport: 'already-connected', isPressable: true, environmentType: 'development', - hasJetpack: true, }; ( useConnectedSitesData as jest.Mock ).mockReturnValue( { connectedSites: [ fakeSyncSite ], @@ -510,7 +502,6 @@ describe( 'ContentTabSync', () => { syncSupport: 'already-connected', isPressable: true, environmentType: 'non-supported-environment-example-or-sandbox', - hasJetpack: true, }; ( useConnectedSitesData as jest.Mock ).mockReturnValue( { connectedSites: [ fakeSyncSite ], @@ -547,7 +538,6 @@ describe( 'ContentTabSync', () => { name: 'My simple business site that needs a transfer', url: 'https:/developer.wordpress.com/studio/', syncSupport: 'already-connected', - hasJetpack: true, }; ( useConnectedSitesData as jest.Mock ).mockReturnValue( { connectedSites: [ fakeSyncSite ], @@ -593,7 +583,6 @@ describe( 'ContentTabSync', () => { name: 'My simple business site that needs a transfer', url: 'https:/developer.wordpress.com/studio/', syncSupport: 'already-connected', - hasJetpack: true, }; ( useConnectedSitesData as jest.Mock ).mockReturnValue( { connectedSites: [ fakeSyncSite ], @@ -702,7 +691,6 @@ describe( 'ContentTabSync', () => { name: 'My simple business site that needs a transfer', url: 'https:/developer.wordpress.com/studio/', syncSupport: 'already-connected', - hasJetpack: true, }; ( useConnectedSitesData as jest.Mock ).mockReturnValue( { connectedSites: [ fakeSyncSite ], From b8c75555a863821b42525b538eb2ee0579839400 Mon Sep 17 00:00:00 2001 From: Ivan Ottinger <25105483+ivan-ottinger@users.noreply.github.com> Date: Tue, 4 Nov 2025 14:44:03 +0100 Subject: [PATCH 3/3] Update test copy --- src/hooks/tests/get-sync-support.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks/tests/get-sync-support.test.ts b/src/hooks/tests/get-sync-support.test.ts index 24d56bcb7d..c36a8fb61f 100644 --- a/src/hooks/tests/get-sync-support.test.ts +++ b/src/hooks/tests/get-sync-support.test.ts @@ -95,12 +95,12 @@ describe( 'getSyncSupport', () => { expect( getSyncSupport( site, [] ) ).toBe( 'syncable' ); } ); - it( 'returns "jetpack-disconnected" for Pressable site without Jetpack', () => { + it( 'returns "jetpack-disconnected" for Pressable site with broken Jetpack connection', () => { const site = { ...baseSite, hosting_provider_guess: 'pressable', jetpack: false }; expect( getSyncSupport( site, [] ) ).toBe( 'jetpack-disconnected' ); } ); - it( 'returns "jetpack-disconnected" for Atomic site without Jetpack', () => { + it( 'returns "jetpack-disconnected" for Atomic site with broken Jetpack connection', () => { const site = { ...baseSite, is_wpcom_atomic: true, jetpack: false }; expect( getSyncSupport( site, [] ) ).toBe( 'jetpack-disconnected' ); } );