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);
|
scope.setExtra('workspace', options.workspace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options?.additionalData) {
|
||||||
|
scope.setExtra('additionalData', options.additionalData);
|
||||||
|
}
|
||||||
|
|
||||||
if (options?.user) {
|
if (options?.user) {
|
||||||
scope.setUser({
|
scope.setUser({
|
||||||
id: options.user.id,
|
id: options.user.id,
|
||||||
|
|||||||
@ -9,6 +9,7 @@ export interface ExceptionHandlerOptions {
|
|||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
document?: string;
|
document?: string;
|
||||||
|
additionalData?: Record<string, any>;
|
||||||
user?: ExceptionHandlerUser | null;
|
user?: ExceptionHandlerUser | null;
|
||||||
workspace?: ExceptionHandlerWorkspace | null;
|
workspace?: ExceptionHandlerWorkspace | null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -85,10 +85,24 @@ export class CalendarEventImportErrorHandlerService {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
throw new CalendarEventImportException(
|
const calendarEventImportException = new CalendarEventImportException(
|
||||||
`Unknown temporary error occurred while importing calendar events for calendar channel ${calendarChannel.id} in workspace ${workspaceId} with throttleFailureCount${calendarChannel.throttleFailureCount}`,
|
`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,
|
CalendarEventImportExceptionCode.UNKNOWN,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.exceptionHandlerService.captureExceptions(
|
||||||
|
[calendarEventImportException],
|
||||||
|
{
|
||||||
|
additionalData: {
|
||||||
|
calendarChannelId: calendarChannel.id,
|
||||||
|
},
|
||||||
|
workspace: {
|
||||||
|
id: workspaceId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
throw calendarEventImportException;
|
||||||
}
|
}
|
||||||
|
|
||||||
const calendarChannelRepository =
|
const calendarChannelRepository =
|
||||||
@ -149,13 +163,16 @@ export class CalendarEventImportErrorHandlerService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const calendarEventImportException = new CalendarEventImportException(
|
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,
|
CalendarEventImportExceptionCode.UNKNOWN,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.exceptionHandlerService.captureExceptions(
|
this.exceptionHandlerService.captureExceptions(
|
||||||
[calendarEventImportException],
|
[calendarEventImportException],
|
||||||
{
|
{
|
||||||
|
additionalData: {
|
||||||
|
calendarChannelId: calendarChannel.id,
|
||||||
|
},
|
||||||
workspace: {
|
workspace: {
|
||||||
id: workspaceId,
|
id: workspaceId,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
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 { 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 { 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()
|
@Injectable()
|
||||||
export class MicrosoftFetchByBatchService {
|
export class MicrosoftFetchByBatchService {
|
||||||
@ -42,11 +44,23 @@ export class MicrosoftFetchByBatchService {
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const batchResponse = await client
|
try {
|
||||||
.api('/$batch')
|
const batchResponse = await client
|
||||||
.post({ requests: batchRequests });
|
.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 {
|
return {
|
||||||
@ -54,4 +68,21 @@ export class MicrosoftFetchByBatchService {
|
|||||||
batchResponses,
|
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,
|
syncStep,
|
||||||
messageChannel,
|
messageChannel,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
exception,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS:
|
case MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS:
|
||||||
@ -89,6 +90,7 @@ export class MessageImportExceptionHandlerService {
|
|||||||
'id' | 'throttleFailureCount'
|
'id' | 'throttleFailureCount'
|
||||||
>,
|
>,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
|
exception: MessageImportDriverException,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (
|
if (
|
||||||
messageChannel.throttleFailureCount >= MESSAGING_THROTTLE_MAX_ATTEMPTS
|
messageChannel.throttleFailureCount >= MESSAGING_THROTTLE_MAX_ATTEMPTS
|
||||||
@ -98,8 +100,21 @@ export class MessageImportExceptionHandlerService {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
MessageChannelSyncStatus.FAILED_UNKNOWN,
|
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(
|
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,
|
MessageImportExceptionCode.UNKNOWN,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -161,17 +176,19 @@ export class MessageImportExceptionHandlerService {
|
|||||||
MessageChannelSyncStatus.FAILED_UNKNOWN,
|
MessageChannelSyncStatus.FAILED_UNKNOWN,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.exceptionHandlerService.captureExceptions([
|
const messageImportException = new MessageImportException(
|
||||||
new MessageImportException(
|
|
||||||
`Unknown error importing messages for message channel ${messageChannel.id} in workspace ${workspaceId}: ${exception.message}`,
|
|
||||||
MessageImportExceptionCode.UNKNOWN,
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
throw new MessageImportException(
|
|
||||||
exception.message,
|
exception.message,
|
||||||
MessageImportExceptionCode.UNKNOWN,
|
MessageImportExceptionCode.UNKNOWN,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.exceptionHandlerService.captureExceptions([messageImportException], {
|
||||||
|
additionalData: {
|
||||||
|
messageChannelId: messageChannel.id,
|
||||||
|
},
|
||||||
|
workspace: { id: workspaceId },
|
||||||
|
});
|
||||||
|
|
||||||
|
throw messageImportException;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handlePermanentException(
|
private async handlePermanentException(
|
||||||
|
|||||||
Reference in New Issue
Block a user