import apiFetch from '@wordpress/api-fetch'; import { addQueryArgs } from '@wordpress/url'; import { UNKNOWN_ERROR_MESSAGE } from '../constants.ts'; import { INVALIDATE_INTEGRATIONS } from './action-types.ts'; import { receiveIntegrations, setIntegrationsError, setIntegrationsLoading } from './actions.ts'; import type { IntegrationsAction } from './types.ts'; import type { Integration, IntegrationMetadata } from '../../types/index.ts'; let hasLoadedMeta = false; /** * Resets the metadata loaded flag (for testing purposes). */ export const resetMetadataFlag = () => { hasLoadedMeta = false; }; /** * Fetches metadata for integrations (fast, preloaded endpoint). * * @return {Function} Thunk function that dispatches integration metadata */ const fetchIntegrationsMetadata = () => async ( { dispatch }: { dispatch: ( action: IntegrationsAction ) => void } ) => { const metadataPath = '/wp/v2/feedback/integrations-metadata'; const metadata = await apiFetch< IntegrationMetadata[] >( { path: metadataPath } ); // Convert metadata to partial Integration objects with default status values // This allows the UI to render immediately with names/descriptions const partialIntegrations: Integration[] = metadata.map( meta => ( { ...meta, pluginFile: null, isInstalled: false, isActive: false, isConnected: false, needsConnection: meta.type === 'service', version: null, settingsUrl: null, details: {}, __isPartial: true, // Flag to indicate this is metadata-only } ) ); // Dispatch partial data immediately for fast UI rendering dispatch( receiveIntegrations( partialIntegrations ) ); }; /** * Fetches full integration status including connection state. * * @return {Function} Thunk function that dispatches full integration data */ const fetchFullIntegrations = () => async ( { dispatch }: { dispatch: ( action: IntegrationsAction ) => void } ) => { const fullPath = addQueryArgs( '/wp/v2/feedback/integrations', { version: 2 } ); const fullIntegrations = await apiFetch< Integration[] >( { path: fullPath } ); // Update with full data including real status dispatch( receiveIntegrations( fullIntegrations ) ); }; /** * Fetches integrations with a two-stage approach for optimal performance: * 1. First, fetch fast metadata (preloaded) to render UI immediately (only on initial load) * 2. Then, fetch full status to update with real-time data * * This prevents jank and ensures the dashboard loads quickly. * * @return {Function} Thunk function that dispatches integration actions */ export const getIntegrations = () => async ( { dispatch }: { dispatch: ( action: IntegrationsAction ) => void } ) => { dispatch( setIntegrationsLoading( true ) ); try { if ( ! hasLoadedMeta ) { await fetchIntegrationsMetadata()( { dispatch } ); hasLoadedMeta = true; } // Stage 2: Always fetch full status (for initial load and refreshes) // This call may be slower (checks /me/connections) but UI is already rendered (if initial load) await fetchFullIntegrations()( { dispatch } ); } catch ( e ) { const message = e instanceof Error ? e.message : UNKNOWN_ERROR_MESSAGE; dispatch( setIntegrationsError( message ) ); } finally { dispatch( setIntegrationsLoading( false ) ); } }; // Attach invalidation rule getIntegrations.shouldInvalidate = ( action: IntegrationsAction ) => action.type === INVALIDATE_INTEGRATIONS;