Messaging issues (#12041)
messageImportException for messaging and calendar add more context to the Sentry Exceptions Fixes https://github.com/twentyhq/twenty/issues/11994
This commit is contained in:
@ -27,6 +27,10 @@ export class ExceptionHandlerSentryDriver
|
||||
scope.setExtra('workspace', options.workspace);
|
||||
}
|
||||
|
||||
if (options?.additionalData) {
|
||||
scope.setExtra('additionalData', options.additionalData);
|
||||
}
|
||||
|
||||
if (options?.user) {
|
||||
scope.setUser({
|
||||
id: options.user.id,
|
||||
|
||||
@ -9,6 +9,7 @@ export interface ExceptionHandlerOptions {
|
||||
name: string;
|
||||
};
|
||||
document?: string;
|
||||
additionalData?: Record<string, any>;
|
||||
user?: ExceptionHandlerUser | null;
|
||||
workspace?: ExceptionHandlerWorkspace | null;
|
||||
}
|
||||
|
||||
@ -85,10 +85,24 @@ export class CalendarEventImportErrorHandlerService {
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
throw new CalendarEventImportException(
|
||||
`Unknown temporary error occurred while importing calendar events for calendar channel ${calendarChannel.id} in workspace ${workspaceId} with throttleFailureCount${calendarChannel.throttleFailureCount}`,
|
||||
const calendarEventImportException = new CalendarEventImportException(
|
||||
`Temporary error occurred ${CALENDAR_THROTTLE_MAX_ATTEMPTS} times while importing calendar events for calendar channel ${calendarChannel.id.slice(0, 5)}... in workspace ${workspaceId} with throttleFailureCount ${calendarChannel.throttleFailureCount}`,
|
||||
CalendarEventImportExceptionCode.UNKNOWN,
|
||||
);
|
||||
|
||||
this.exceptionHandlerService.captureExceptions(
|
||||
[calendarEventImportException],
|
||||
{
|
||||
additionalData: {
|
||||
calendarChannelId: calendarChannel.id,
|
||||
},
|
||||
workspace: {
|
||||
id: workspaceId,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
throw calendarEventImportException;
|
||||
}
|
||||
|
||||
const calendarChannelRepository =
|
||||
@ -149,13 +163,16 @@ export class CalendarEventImportErrorHandlerService {
|
||||
);
|
||||
|
||||
const calendarEventImportException = new CalendarEventImportException(
|
||||
`Unknown error importing calendar events for calendar channel ${calendarChannel.id} in workspace ${workspaceId}: ${exception.message}`,
|
||||
`Unknown error importing calendar events for calendar channel ${calendarChannel.id.slice(0, 5)}... in workspace ${workspaceId}: ${exception.message}`,
|
||||
CalendarEventImportExceptionCode.UNKNOWN,
|
||||
);
|
||||
|
||||
this.exceptionHandlerService.captureExceptions(
|
||||
[calendarEventImportException],
|
||||
{
|
||||
additionalData: {
|
||||
calendarChannelId: calendarChannel.id,
|
||||
},
|
||||
workspace: {
|
||||
id: workspaceId,
|
||||
},
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { MicrosoftImportDriverException } from 'src/modules/messaging/message-import-manager/drivers/microsoft/exceptions/microsoft-import-driver.exception';
|
||||
import { MicrosoftClientProvider } from 'src/modules/messaging/message-import-manager/drivers/microsoft/providers/microsoft-client.provider';
|
||||
import { MicrosoftGraphBatchResponse } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.interface';
|
||||
import { isMicrosoftClientTemporaryError } from 'src/modules/messaging/message-import-manager/drivers/microsoft/utils/is-temporary-error.utils';
|
||||
|
||||
@Injectable()
|
||||
export class MicrosoftFetchByBatchService {
|
||||
@ -42,11 +44,23 @@ export class MicrosoftFetchByBatchService {
|
||||
},
|
||||
}));
|
||||
|
||||
const batchResponse = await client
|
||||
.api('/$batch')
|
||||
.post({ requests: batchRequests });
|
||||
try {
|
||||
const batchResponse = await client
|
||||
.api('/$batch')
|
||||
.post({ requests: batchRequests });
|
||||
|
||||
batchResponses.push(batchResponse);
|
||||
batchResponses.push(batchResponse);
|
||||
} catch (error) {
|
||||
if (
|
||||
error.body &&
|
||||
typeof error.body === 'string' &&
|
||||
isMicrosoftClientTemporaryError(error.body)
|
||||
) {
|
||||
throw new MicrosoftImportDriverException(error.body, error.code, 429);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
@ -54,4 +68,21 @@ export class MicrosoftFetchByBatchService {
|
||||
batchResponses,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Microsoft client.api.post sometimes throws (hard to catch) temporary errors like this one:
|
||||
*
|
||||
* {
|
||||
* statusCode: 200,
|
||||
* code: "SyntaxError",
|
||||
* requestId: null,
|
||||
* date: "2025-05-14T11:43:02.024Z",
|
||||
* body: "SyntaxError: Unexpected token < in JSON at position 19341",
|
||||
* headers: {
|
||||
* },
|
||||
* }
|
||||
*/
|
||||
private isTemporaryError(error: any): boolean {
|
||||
return error?.body?.includes('Unexpected token < in JSON at position');
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
import { isMicrosoftClientTemporaryError } from 'src/modules/messaging/message-import-manager/drivers/microsoft/utils/is-temporary-error.utils';
|
||||
|
||||
describe('isMicrosoftClientTemporaryError', () => {
|
||||
it('should return true when body contains the expected text', () => {
|
||||
const error = {
|
||||
statusCode: 200,
|
||||
code: 'SyntaxError',
|
||||
requestId: null,
|
||||
date: '2025-05-14T11:43:02.024Z',
|
||||
body: 'SyntaxError: Unexpected token < in JSON at position 19341',
|
||||
headers: {},
|
||||
};
|
||||
|
||||
expect(isMicrosoftClientTemporaryError(error.body)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when body does not contain it', () => {
|
||||
const error = {
|
||||
statusCode: 400,
|
||||
code: 'AuthError',
|
||||
requestId: '123456',
|
||||
date: '2025-05-14T11:43:02.024Z',
|
||||
body: 'Authentication failed',
|
||||
headers: {},
|
||||
};
|
||||
|
||||
expect(isMicrosoftClientTemporaryError(error.body)).toBe(false);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,3 @@
|
||||
export const isMicrosoftClientTemporaryError = (body: string): boolean => {
|
||||
return body.includes('Unexpected token < in JSON at position');
|
||||
};
|
||||
@ -55,6 +55,7 @@ export class MessageImportExceptionHandlerService {
|
||||
syncStep,
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
exception,
|
||||
);
|
||||
break;
|
||||
case MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS:
|
||||
@ -89,6 +90,7 @@ export class MessageImportExceptionHandlerService {
|
||||
'id' | 'throttleFailureCount'
|
||||
>,
|
||||
workspaceId: string,
|
||||
exception: MessageImportDriverException,
|
||||
): Promise<void> {
|
||||
if (
|
||||
messageChannel.throttleFailureCount >= MESSAGING_THROTTLE_MAX_ATTEMPTS
|
||||
@ -98,8 +100,21 @@ export class MessageImportExceptionHandlerService {
|
||||
workspaceId,
|
||||
MessageChannelSyncStatus.FAILED_UNKNOWN,
|
||||
);
|
||||
|
||||
this.exceptionHandlerService.captureExceptions(
|
||||
[
|
||||
`Temporary error occurred ${MESSAGING_THROTTLE_MAX_ATTEMPTS} times while importing messages for message channel ${messageChannel.id.slice(0, 5)}... in workspace ${workspaceId}: ${exception?.message}`,
|
||||
],
|
||||
{
|
||||
additionalData: {
|
||||
messageChannelId: messageChannel.id,
|
||||
},
|
||||
workspace: { id: workspaceId },
|
||||
},
|
||||
);
|
||||
|
||||
throw new MessageImportException(
|
||||
`Unknown temporary error occurred multiple times while importing messages for message channel ${messageChannel.id} in workspace ${workspaceId}`,
|
||||
`Temporary error occurred multiple times while importing messages for message channel ${messageChannel.id} in workspace ${workspaceId}: ${exception?.message}`,
|
||||
MessageImportExceptionCode.UNKNOWN,
|
||||
);
|
||||
}
|
||||
@ -161,17 +176,19 @@ export class MessageImportExceptionHandlerService {
|
||||
MessageChannelSyncStatus.FAILED_UNKNOWN,
|
||||
);
|
||||
|
||||
this.exceptionHandlerService.captureExceptions([
|
||||
new MessageImportException(
|
||||
`Unknown error importing messages for message channel ${messageChannel.id} in workspace ${workspaceId}: ${exception.message}`,
|
||||
MessageImportExceptionCode.UNKNOWN,
|
||||
),
|
||||
]);
|
||||
|
||||
throw new MessageImportException(
|
||||
const messageImportException = new MessageImportException(
|
||||
exception.message,
|
||||
MessageImportExceptionCode.UNKNOWN,
|
||||
);
|
||||
|
||||
this.exceptionHandlerService.captureExceptions([messageImportException], {
|
||||
additionalData: {
|
||||
messageChannelId: messageChannel.id,
|
||||
},
|
||||
workspace: { id: workspaceId },
|
||||
});
|
||||
|
||||
throw messageImportException;
|
||||
}
|
||||
|
||||
private async handlePermanentException(
|
||||
|
||||
Reference in New Issue
Block a user