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

Social: Fix parallel social connections requests mixing up UI state #38408

Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: fixed

Social | Fixed parallel social connection requests messing up the UI state
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import {
TOGGLE_CONNECTIONS_MODAL,
UPDATE_CONNECTION,
UPDATING_CONNECTION,
REQUEST_TYPE_REFRESH_CONNECTIONS,
ADD_ABORT_CONTROLLER,
REMOVE_ABORT_CONTROLLERS,
} from './constants';

/**
Expand Down Expand Up @@ -107,6 +110,64 @@ export function mergeConnections( freshConnections ) {
};
}

/**
* Create an abort controller.
* @param {AbortController} abortController - Abort controller.
* @param {string} requestType - Type of abort request.
*
* @returns {object} - an action object.
*/
export function createAbortController( abortController, requestType ) {
return {
type: ADD_ABORT_CONTROLLER,
requestType,
abortController,
};
}

/**
* Remove abort controllers.
*
* @param {string} requestType - Type of abort request.
*
* @returns {object} - an action object.
*/
export function removeAbortControllers( requestType ) {
return {
type: REMOVE_ABORT_CONTROLLERS,
requestType,
};
}

/**
* Abort a request.
*
* @param {string} requestType - Type of abort request.
*
* @returns {Function} - a function to abort a request.
*/
export function abortRequest( requestType ) {
return function ( { dispatch, select } ) {
const abortControllers = select.getAbortControllers( requestType );

for ( const controller of abortControllers ) {
controller.abort();
}

// Remove the abort controllers.
dispatch( removeAbortControllers( requestType ) );
};
}

/**
* Abort the refresh connections request.
*
* @returns {Function} - a function to abort a request.
*/
export function abortRefreshConnectionsRequest() {
return abortRequest( REQUEST_TYPE_REFRESH_CONNECTIONS );
}

/**
* Effect handler which will refresh the connection test results.
*
Expand All @@ -118,15 +179,32 @@ export function refreshConnectionTestResults( syncToMeta = false ) {
try {
const path = select.connectionRefreshPath() || '/wpcom/v2/publicize/connection-test-results';

const freshConnections = await apiFetch( { path } );
// Wait until all connections are done updating/deleting.
while (
select.getUpdatingConnections().length > 0 ||
select.getDeletingConnections().length > 0
) {
await new Promise( resolve => setTimeout( resolve, 100 ) );
}

const abortController = new AbortController();

dispatch( createAbortController( abortController, REQUEST_TYPE_REFRESH_CONNECTIONS ) );

// Pass the abort controller signal to the fetch request.
const freshConnections = await apiFetch( { path, signal: abortController.signal } );

dispatch( mergeConnections( freshConnections ) );

if ( syncToMeta ) {
dispatch( syncConnectionsToPostMeta() );
}
} catch ( e ) {
// Do nothing.
// If the request was aborted.
if ( 'AbortError' === e.name ) {
// Fire it again to run after the current operation that cancelled the request.
dispatch( refreshConnectionTestResults( syncToMeta ) );
}
}
};
}
Expand Down Expand Up @@ -210,6 +288,9 @@ export function deleteConnectionById( { connectionId, showSuccessNotice = true }
try {
const path = `/jetpack/v4/social/connections/${ connectionId }`;

// Abort the refresh connections request.
dispatch( abortRefreshConnectionsRequest() );

dispatch( deletingConnection( connectionId ) );

await apiFetch( { method: 'DELETE', path } );
Expand Down Expand Up @@ -269,6 +350,9 @@ export function createConnection( data, optimisticData = {} ) {
...optimisticData,
} )
);
// Abort the refresh connections request.
dispatch( abortRefreshConnectionsRequest() );

// Mark the connection as updating to show the spinner.
dispatch( updatingConnection( tempId ) );

Expand Down Expand Up @@ -383,6 +467,9 @@ export function updateConnectionById( connectionId, data ) {
try {
const path = `/jetpack/v4/social/connections/${ connectionId }`;

// Abort the refresh connections request.
dispatch( abortRefreshConnectionsRequest() );

// Optimistically update the connection.
dispatch( updateConnection( connectionId, data ) );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,11 @@ export const SET_RECONNECTING_ACCOUNT = 'SET_RECONNECTING_ACCOUNT';
export const SET_KEYRING_RESULT = 'SET_KEYRING_RESULT';

export const TOGGLE_CONNECTIONS_MODAL = 'TOGGLE_CONNECTIONS_MODAL';

export const ADD_ABORT_CONTROLLER = 'ADD_ABORT_CONTROLLER';

export const REMOVE_ABORT_CONTROLLERS = 'REMOVE_ABORT_CONTROLLERS';

export const REQUEST_TYPE_DEFAULT = 'DEFAULT';

export const REQUEST_TYPE_REFRESH_CONNECTIONS = 'REFRESH_CONNECTIONS';
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import {
TOGGLE_CONNECTIONS_MODAL,
UPDATE_CONNECTION,
UPDATING_CONNECTION,
ADD_ABORT_CONTROLLER,
REMOVE_ABORT_CONTROLLERS,
REQUEST_TYPE_DEFAULT,
} from '../actions/constants';

/**
Expand Down Expand Up @@ -92,6 +95,33 @@ const connectionData = ( state = {}, action ) => {
};
}

case ADD_ABORT_CONTROLLER: {
const requestType = action.requestType || REQUEST_TYPE_DEFAULT;

return {
...state,
abortControllers: {
...state.abortControllers,
[ requestType ]: [
...( state.abortControllers?.[ requestType ] || [] ),
action.abortController,
],
},
};
}

case REMOVE_ABORT_CONTROLLERS: {
const requestType = action.requestType || REQUEST_TYPE_DEFAULT;

return {
...state,
abortControllers: {
...state.abortControllers,
[ requestType ]: [],
},
};
}

case SET_KEYRING_RESULT:
return {
...state,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { REQUEST_TYPE_DEFAULT } from '../actions/constants';

/**
* Returns the connections list from the store.
*
Expand Down Expand Up @@ -161,6 +163,18 @@ export function getReconnectingAccount( state ) {
return state.connectionData?.reconnectingAccount ?? '';
}

/**
* Get the abort controllers for a specific request type.
*
* @param {import("../types").SocialStoreState} state - State object.
* @param {string} requestType - The request type.
*
* @returns {Array<AbortController>} The abort controllers.
*/
export function getAbortControllers( state, requestType = REQUEST_TYPE_DEFAULT ) {
return state.connectionData?.abortControllers?.[ requestType ] ?? [];
}

/**
* Whether a mastodon account is already connected.
*
Expand Down
Loading