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

Newsletter: Fail silently on email stats fetch errors in the editor and skip the fetch for drafts so timeouts no longer flash as errors in Gutenberg.
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,12 @@ function SubscribersAffirmation( { accessLevel, prePublish = false } ) {

const _postEmailSentState = postId ? getPostEmailSentState( postId ) : null;
const emailSentAt = _postEmailSentState?.email_sent_at ?? null;
const shouldFetchTotalEmails = postId && blogId && postEmailResolved && emailSentAt == null;
// Only fetch email open stats for already-published posts. Drafts,
// auto-drafts, pending, and scheduled posts have never been emailed,
// so the WPCOM stats/opens/emails request would be a guaranteed miss
// (and can time out on large sites). See NL-578.
const shouldFetchTotalEmails =
postId && blogId && postEmailResolved && emailSentAt == null && status === 'publish';

return {
hasFinishedLoading: [
Expand All @@ -372,7 +377,7 @@ function SubscribersAffirmation( { accessLevel, prePublish = false } ) {
: null,
};
},
[ postId, blogId ]
[ postId, blogId, status ]
);

if ( ! hasFinishedLoading ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,16 +286,19 @@ export const getSubscriberCounts =

export const getTotalEmailsSentCount =
( blogId, postId ) =>
async ( { dispatch, registry } ) => {
async ( { dispatch } ) => {
await executionLock.blockExecution( TOTAL_EMAILS_SENT_COUNT_EXECUTION_KEY );

const lock = executionLock.acquire( TOTAL_EMAILS_SENT_COUNT_EXECUTION_KEY );
try {
const response = await fetchTotalEmailsSentCount( blogId, postId );
dispatch( setTotalEmailsSentCount( response?.total_sends ) );
} catch ( error ) {
dispatch( setApiState( API_STATE_NOTCONNECTED ) );
onError( error.message, registry );
// Email open stats are informational. Fail silently so a slow or
// failed WPCOM response (e.g. 5s timeout on stats/opens/emails) does
// not surface a snackbar error in the editor. See NL-578.
// eslint-disable-next-line no-console
console.warn( 'Failed to fetch total emails sent count:', error?.message );
} finally {
executionLock.release( lock );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,37 @@ describe( 'Membership Products Resolvers', () => {
expect( apiFetch ).not.toHaveBeenCalled();
} );

test( 'WP_Error response: calls onError', async () => {
test( 'WP_Error response: fails silently (no onError, no dispatch)', async () => {
// Email stats are informational — errors should never flash in the
// editor (see NL-578).
const warnSpy = jest.spyOn( console, 'warn' ).mockImplementation( () => {} );
apiFetch.mockResolvedValue( {
errors: { rest_forbidden: [ 'Sorry, you are not allowed.' ] },
} );

const thunk = getTotalEmailsSentCount( 123, 456 );
await thunk( { dispatch: mockDispatch, registry: mockRegistry } );

expect( utils.onError ).toHaveBeenCalled();
expect( utils.onError ).not.toHaveBeenCalled();
expect( mockDispatch ).not.toHaveBeenCalled();
expect( warnSpy ).toHaveBeenCalled();
warnSpy.mockRestore();
} );

test( 'apiFetch rejects (e.g. timeout): fails silently', async () => {
const warnSpy = jest.spyOn( console, 'warn' ).mockImplementation( () => {} );
apiFetch.mockRejectedValue( new Error( 'cURL error 28: Operation timed out' ) );

const thunk = getTotalEmailsSentCount( 123, 456 );
await thunk( { dispatch: mockDispatch, registry: mockRegistry } );

expect( utils.onError ).not.toHaveBeenCalled();
expect( mockDispatch ).not.toHaveBeenCalled();
expect( warnSpy ).toHaveBeenCalledWith(
'Failed to fetch total emails sent count:',
'cURL error 28: Operation timed out'
);
warnSpy.mockRestore();
} );
} );
} );
Loading