refactor messaging error handling (#15416)

This commit is contained in:
neo773
2025-11-01 20:03:41 +01:00
committed by GitHub
parent c42c2590de
commit 44c0b9f94b
39 changed files with 454 additions and 282 deletions
@@ -9,6 +9,7 @@ import { type ExceptionHandlerOptions } from 'src/engine/core-modules/exception-
import { PostgresException } from 'src/engine/api/graphql/workspace-query-runner/utils/postgres-exception';
import { type ExceptionHandlerDriverInterface } from 'src/engine/core-modules/exception-handler/interfaces';
import { MessageImportDriverException } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
import { CustomException } from 'src/utils/custom-exception';
export class ExceptionHandlerSentryDriver
@@ -61,6 +62,20 @@ export class ExceptionHandlerSentryDriver
});
}
if ('context' in exception && exception.context) {
Object.entries(exception.context).forEach(([key, value]) => {
scope.setExtra(key, value);
});
}
if ('cause' in exception && exception.cause) {
scope.setContext('cause', {
name: exception.cause.name,
message: exception.cause.message,
stack: exception.cause.stack,
});
}
if (
exception instanceof CustomException &&
exception.code !== 'UNKNOWN'
@@ -84,6 +99,11 @@ export class ExceptionHandlerSentryDriver
exception.name = exception.message;
}
if (exception instanceof MessageImportDriverException) {
scope.setTag('messageImportDriverCode', exception.code);
scope.setFingerprint([exception.code]);
}
const eventId = Sentry.captureException(exception, {
contexts: {
GraphQL: {
@@ -8,7 +8,7 @@ import {
import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service';
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { MESSAGING_GMAIL_DEFAULT_NOT_SYNCED_LABELS } from 'src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-default-not-synced-labels';
import { GmailHandleErrorService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-handle-error.service';
import { GmailMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-message-list-fetch-error-handler.service';
@Injectable()
export class GmailGetAllFoldersService implements MessageFolderDriver {
@@ -16,7 +16,7 @@ export class GmailGetAllFoldersService implements MessageFolderDriver {
constructor(
private readonly oAuth2ClientManagerService: OAuth2ClientManagerService,
private readonly gmailHandleErrorService: GmailHandleErrorService,
private readonly gmailMessageListFetchErrorHandler: GmailMessageListFetchErrorHandler,
) {}
private isSyncedByDefault(labelId: string): boolean {
@@ -44,7 +44,7 @@ export class GmailGetAllFoldersService implements MessageFolderDriver {
`Connected account ${connectedAccount.id}: Error fetching labels: ${error.message}`,
);
this.gmailHandleErrorService.handleGmailMessageListFetchError(error);
this.gmailMessageListFetchErrorHandler.handleError(error);
return { data: { labels: [] } };
});
@@ -7,7 +7,7 @@ import {
import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service';
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { MicrosoftHandleErrorService } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-handle-error.service';
import { MicrosoftMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-message-list-fetch-error-handler.service';
import { StandardFolder } from 'src/modules/messaging/message-import-manager/drivers/types/standard-folder';
import { getStandardFolderByRegex } from 'src/modules/messaging/message-import-manager/drivers/utils/get-standard-folder-by-regex';
@@ -26,8 +26,7 @@ export class MicrosoftGetAllFoldersService implements MessageFolderDriver {
constructor(
private readonly oAuth2ClientManagerService: OAuth2ClientManagerService,
private readonly microsoftHandleErrorService: MicrosoftHandleErrorService,
private readonly microsoftMessageListFetchErrorHandler: MicrosoftMessageListFetchErrorHandler,
) {}
async getAllMessageFolders(
@@ -51,9 +50,7 @@ export class MicrosoftGetAllFoldersService implements MessageFolderDriver {
this.logger.error(
`Connected account ${connectedAccount.id}: Error fetching folders: ${error.message}`,
);
this.microsoftHandleErrorService.handleMicrosoftGetMessageListError(
error,
);
this.microsoftMessageListFetchErrorHandler.handleError(error);
return { value: [] };
});
@@ -1,9 +1,37 @@
import { CustomException } from 'src/utils/custom-exception';
import { type MessageNetworkExceptionCode } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-network.exception';
export class MessageImportDriverException extends CustomException<
MessageImportDriverExceptionCode | MessageNetworkExceptionCode
> {}
export class MessageImportDriverException extends Error {
code: MessageImportDriverExceptionCode | MessageNetworkExceptionCode;
cause?: Error;
context?: {
messageChannelId?: string;
workspaceId?: string;
syncStep?: string;
};
constructor(
message: string,
code: MessageImportDriverExceptionCode | MessageNetworkExceptionCode,
options?: {
cause?: Error;
context?: {
messageChannelId?: string;
workspaceId?: string;
syncStep?: string;
};
},
) {
super(message);
this.name = 'MessageImportDriverException';
this.code = code;
this.cause = options?.cause;
this.context = options?.context;
if (options?.cause?.stack) {
this.stack = `${this.stack}\nCaused by: ${options.cause.stack}`;
}
}
}
export enum MessageImportDriverExceptionCode {
NOT_FOUND = 'NOT_FOUND',
@@ -15,7 +15,9 @@ import { GmailFetchByBatchService } from 'src/modules/messaging/message-import-m
import { GmailGetHistoryService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-history.service';
import { GmailGetMessageListService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-message-list.service';
import { GmailGetMessagesService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-messages.service';
import { GmailHandleErrorService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-handle-error.service';
import { GmailMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-message-list-fetch-error-handler.service';
import { GmailMessagesImportErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-messages-import-error-handler.service';
import { GmailNetworkErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-network-error-handler.service';
import { MessageParticipantManagerModule } from 'src/modules/messaging/message-participant-manager/message-participant-manager.module';
@Module({
@@ -38,13 +40,16 @@ import { MessageParticipantManagerModule } from 'src/modules/messaging/message-p
GmailFetchByBatchService,
GmailGetMessagesService,
GmailGetMessageListService,
GmailHandleErrorService,
GmailNetworkErrorHandler,
GmailMessageListFetchErrorHandler,
GmailMessagesImportErrorHandler,
],
exports: [
GmailGetMessagesService,
GmailGetMessageListService,
GmailHandleErrorService,
GmailNetworkErrorHandler,
GmailMessageListFetchErrorHandler,
GmailMessagesImportErrorHandler,
],
})
export class MessagingGmailDriverModule {}
@@ -3,12 +3,12 @@ import { Injectable } from '@nestjs/common';
import { type gmail_v1 } from 'googleapis';
import { MESSAGING_GMAIL_USERS_HISTORY_MAX_RESULT } from 'src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-users-history-max-result.constant';
import { GmailHandleErrorService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-handle-error.service';
import { GmailMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-message-list-fetch-error-handler.service';
@Injectable()
export class GmailGetHistoryService {
constructor(
private readonly gmailHandleErrorService: GmailHandleErrorService,
private readonly gmailMessageListFetchErrorHandler: GmailMessageListFetchErrorHandler,
) {}
public async getHistory(
@@ -36,7 +36,7 @@ export class GmailGetHistoryService {
labelId,
})
.catch((error) => {
this.gmailHandleErrorService.handleGmailMessageListFetchError(error);
this.gmailMessageListFetchErrorHandler.handleError(error);
return {
data: {
@@ -6,7 +6,7 @@ import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { GmailGetHistoryService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-history.service';
import { GmailGetMessageListService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-message-list.service';
import { GmailHandleErrorService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-handle-error.service';
import { GmailMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-message-list-fetch-error-handler.service';
describe('GmailGetMessageListService', () => {
let service: GmailGetMessageListService;
@@ -47,10 +47,9 @@ describe('GmailGetMessageListService', () => {
},
},
{
provide: GmailHandleErrorService,
provide: GmailMessageListFetchErrorHandler,
useValue: {
handleGmailMessageListFetchError: jest.fn(),
handleGmailMessagesImportError: jest.fn(),
handleError: jest.fn(),
},
},
],
@@ -13,7 +13,7 @@ import {
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
import { MESSAGING_GMAIL_USERS_MESSAGES_LIST_MAX_RESULT } from 'src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-users-messages-list-max-result.constant';
import { GmailGetHistoryService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-history.service';
import { GmailHandleErrorService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-handle-error.service';
import { GmailMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-message-list-fetch-error-handler.service';
import { computeGmailExcludeSearchFilter } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/compute-gmail-exclude-search-filter.util';
import { type GetMessageListsArgs } from 'src/modules/messaging/message-import-manager/types/get-message-lists-args.type';
import { type GetMessageListsResponse } from 'src/modules/messaging/message-import-manager/types/get-message-lists-response.type';
@@ -25,8 +25,7 @@ export class GmailGetMessageListService {
constructor(
private readonly gmailGetHistoryService: GmailGetHistoryService,
private readonly oAuth2ClientManagerService: OAuth2ClientManagerService,
private readonly gmailHandleErrorService: GmailHandleErrorService,
private readonly gmailMessageListFetchErrorHandler: GmailMessageListFetchErrorHandler,
) {}
private async getMessageListWithoutCursor(
@@ -69,7 +68,7 @@ export class GmailGetMessageListService {
`Connected account ${connectedAccount.id}: Error fetching message list: ${JSON.stringify(error)}`,
);
this.gmailHandleErrorService.handleGmailMessageListFetchError(error);
this.gmailMessageListFetchErrorHandler.handleError(error);
return {
data: {
@@ -112,10 +111,7 @@ export class GmailGetMessageListService {
id: firstMessageExternalId,
})
.catch((error) => {
this.gmailHandleErrorService.handleGmailMessagesImportError(
error,
firstMessageExternalId as string,
);
this.gmailMessageListFetchErrorHandler.handleError(error);
});
const nextSyncCursor = firstMessageContent?.data?.historyId;
@@ -6,7 +6,7 @@ import { isDefined } from 'twenty-shared/utils';
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { GmailFetchByBatchService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-fetch-by-batch.service';
import { GmailHandleErrorService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-handle-error.service';
import { GmailMessagesImportErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-messages-import-error-handler.service';
import { parseAndFormatGmailMessage } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-and-format-gmail-message.util';
import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message';
@@ -14,7 +14,7 @@ import { type MessageWithParticipants } from 'src/modules/messaging/message-impo
export class GmailGetMessagesService {
constructor(
private readonly fetchByBatchesService: GmailFetchByBatchService,
private readonly gmailHandleErrorService: GmailHandleErrorService,
private readonly gmailMessagesImportErrorHandler: GmailMessagesImportErrorHandler,
) {}
async getMessages(
@@ -57,7 +57,7 @@ export class GmailGetMessagesService {
const messages = parsedResponses.map((response, index) => {
if ('error' in response) {
this.gmailHandleErrorService.handleGmailMessagesImportError(
this.gmailMessagesImportErrorHandler.handleError(
response.error,
messageIds[index],
);
@@ -1,45 +0,0 @@
import { Injectable } from '@nestjs/common';
import {
MessageImportDriverException,
MessageImportDriverExceptionCode,
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
import { isAxiosTemporaryError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-axios-gaxios-error.util';
import { parseGmailMessageListFetchError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-message-list-fetch-error.util';
import { parseGmailMessagesImportError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-messages-import-error.util';
@Injectable()
export class GmailHandleErrorService {
constructor() {}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public handleGmailMessageListFetchError(error: any): void {
if (isAxiosTemporaryError(error)) {
throw new MessageImportDriverException(
error.message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
}
throw parseGmailMessageListFetchError(error);
}
public handleGmailMessagesImportError(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
error: any,
messageExternalId: string,
): void {
if (isAxiosTemporaryError(error)) {
throw new MessageImportDriverException(
error.message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
}
const gmailError = parseGmailMessagesImportError(error, messageExternalId);
if (gmailError) {
throw gmailError;
}
}
}
@@ -0,0 +1,26 @@
import { Injectable, Logger } from '@nestjs/common';
import { GmailNetworkErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-network-error-handler.service';
import { parseGmailMessageListFetchError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-message-list-fetch-error.util';
@Injectable()
export class GmailMessageListFetchErrorHandler {
private readonly logger = new Logger(GmailMessageListFetchErrorHandler.name);
constructor(
private readonly gmailNetworkErrorHandler: GmailNetworkErrorHandler,
) {}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public handleError(error: any): void {
this.logger.log(`Error fetching message list`, error);
const networkError = this.gmailNetworkErrorHandler.handleError(error);
if (networkError) {
throw networkError;
}
throw parseGmailMessageListFetchError(error, { cause: error });
}
}
@@ -0,0 +1,32 @@
import { Injectable, Logger } from '@nestjs/common';
import { GmailNetworkErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-network-error-handler.service';
import { parseGmailMessagesImportError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-messages-import-error.util';
@Injectable()
export class GmailMessagesImportErrorHandler {
private readonly logger = new Logger(GmailMessagesImportErrorHandler.name);
constructor(
private readonly gmailNetworkErrorHandler: GmailNetworkErrorHandler,
) {}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public handleError(error: any, messageExternalId: string): void {
this.logger.log(`Error fetching messages`, error);
const networkError = this.gmailNetworkErrorHandler.handleError(error);
if (networkError) {
throw networkError;
}
const gmailError = parseGmailMessagesImportError(error, messageExternalId, {
cause: error,
});
if (gmailError) {
throw gmailError;
}
}
}
@@ -0,0 +1,23 @@
import { Injectable } from '@nestjs/common';
import {
MessageImportDriverException,
MessageImportDriverExceptionCode,
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
import { isAxiosTemporaryError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-axios-gaxios-error.util';
@Injectable()
export class GmailNetworkErrorHandler {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public handleError(error: any): MessageImportDriverException | null {
if (isAxiosTemporaryError(error)) {
return new MessageImportDriverException(
error.message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
{ cause: error },
);
}
return null;
}
}
@@ -3,13 +3,16 @@ import {
MessageImportDriverExceptionCode,
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
export const parseGmailMessageListFetchError = (error: {
code?: number;
errors: {
reason: string;
message: string;
}[];
}): MessageImportDriverException => {
export const parseGmailMessageListFetchError = (
error: {
code?: number;
errors: {
reason: string;
message: string;
}[];
},
options?: { cause?: Error },
): MessageImportDriverException => {
const { code, errors } = error;
const reason = errors?.[0]?.reason;
@@ -21,6 +24,7 @@ export const parseGmailMessageListFetchError = (error: {
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
{ cause: options?.cause },
);
}
if (reason === 'failedPrecondition') {
@@ -28,30 +32,35 @@ export const parseGmailMessageListFetchError = (error: {
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
{ cause: options?.cause },
);
}
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
{ cause: options?.cause },
);
}
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.UNKNOWN,
{ cause: options?.cause },
);
case 404:
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.NOT_FOUND,
{ cause: options?.cause },
);
case 429:
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
{ cause: options?.cause },
);
case 403:
@@ -63,12 +72,14 @@ export const parseGmailMessageListFetchError = (error: {
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
{ cause: options?.cause },
);
}
if (reason === 'domainPolicy') {
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
{ cause: options?.cause },
);
}
@@ -78,12 +89,14 @@ export const parseGmailMessageListFetchError = (error: {
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
{ cause: options?.cause },
);
case 503:
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
{ cause: options?.cause },
);
case 500:
@@ -93,6 +106,7 @@ export const parseGmailMessageListFetchError = (error: {
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
{ cause: options?.cause },
);
}
@@ -100,6 +114,7 @@ export const parseGmailMessageListFetchError = (error: {
return new MessageImportDriverException(
`${code} - ${reason} - ${message}`,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
{ cause: options?.cause },
);
}
break;
@@ -111,5 +126,6 @@ export const parseGmailMessageListFetchError = (error: {
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.UNKNOWN,
{ cause: options?.cause },
);
};
@@ -12,6 +12,7 @@ export const parseGmailMessagesImportError = (
}[];
},
messageExternalId: string,
options?: { cause?: Error },
): MessageImportDriverException | undefined => {
const { code, errors } = error;
@@ -25,6 +26,7 @@ export const parseGmailMessagesImportError = (
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
{ cause: options?.cause },
);
}
if (reason === 'failedPrecondition') {
@@ -32,18 +34,21 @@ export const parseGmailMessagesImportError = (
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
{ cause: options?.cause },
);
}
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
{ cause: options?.cause },
);
}
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.UNKNOWN,
{ cause: options?.cause },
);
case 404:
@@ -54,6 +59,7 @@ export const parseGmailMessagesImportError = (
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
{ cause: options?.cause },
);
case 403:
@@ -65,12 +71,14 @@ export const parseGmailMessagesImportError = (
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
{ cause: options?.cause },
);
}
if (reason === 'domainPolicy') {
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
{ cause: options?.cause },
);
}
@@ -80,12 +88,14 @@ export const parseGmailMessagesImportError = (
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
{ cause: options?.cause },
);
case 503:
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
{ cause: options?.cause },
);
case 500:
@@ -95,6 +105,7 @@ export const parseGmailMessagesImportError = (
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
{ cause: options?.cause },
);
}
@@ -102,6 +113,7 @@ export const parseGmailMessagesImportError = (
return new MessageImportDriverException(
`${code} - ${reason} - ${message}`,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
{ cause: options?.cause },
);
}
break;
@@ -113,5 +125,6 @@ export const parseGmailMessagesImportError = (
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.UNKNOWN,
{ cause: options?.cause },
);
};
@@ -14,11 +14,13 @@ import { ImapFetchByBatchService } from 'src/modules/messaging/message-import-ma
import { ImapFindSentFolderService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-find-sent-folder.service';
import { ImapGetMessageListService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-message-list.service';
import { ImapGetMessagesService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-messages.service';
import { ImapHandleErrorService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-handle-error.service';
import { ImapIncrementalSyncService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-incremental-sync.service';
import { ImapMessageFetcherService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-fetcher.service';
import { ImapMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-list-fetch-error-handler.service';
import { ImapMessageProcessorService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-processor.service';
import { ImapMessagesImportErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-messages-import-error-handler.service';
import { ImapMessageTextExtractorService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-text-extractor.service';
import { ImapNetworkErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-network-error-handler.service';
import { MessageParticipantManagerModule } from 'src/modules/messaging/message-participant-manager/message-participant-manager.module';
@Module({
@@ -37,7 +39,9 @@ import { MessageParticipantManagerModule } from 'src/modules/messaging/message-p
ImapFetchByBatchService,
ImapGetMessagesService,
ImapGetMessageListService,
ImapHandleErrorService,
ImapNetworkErrorHandler,
ImapMessageListFetchErrorHandler,
ImapMessagesImportErrorHandler,
ImapIncrementalSyncService,
ImapMessageFetcherService,
ImapMessageProcessorService,
@@ -4,7 +4,7 @@ import { type ImapFlow } from 'imapflow';
import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
import { ImapClientProvider } from 'src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider';
import { ImapHandleErrorService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-handle-error.service';
import { ImapMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-list-fetch-error-handler.service';
import { ImapIncrementalSyncService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-incremental-sync.service';
import { createSyncCursor } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/create-sync-cursor.util';
import { extractMailboxState } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/extract-mailbox-state.util';
@@ -25,7 +25,7 @@ export class ImapGetMessageListService {
constructor(
private readonly imapClientProvider: ImapClientProvider,
private readonly imapIncrementalSyncService: ImapIncrementalSyncService,
private readonly imapHandleErrorService: ImapHandleErrorService,
private readonly imapMessageListFetchErrorHandler: ImapMessageListFetchErrorHandler,
) {}
public async getMessageLists({
@@ -74,7 +74,7 @@ export class ImapGetMessageListService {
error.stack,
);
this.imapHandleErrorService.handleImapMessageListFetchError(error);
this.imapMessageListFetchErrorHandler.handleError(error);
return messageFolders.map((folder) => ({
messageExternalIds: [],
@@ -1,73 +0,0 @@
import { Injectable, Logger } from '@nestjs/common';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import {
MessageChannelSyncStatus,
type MessageChannelWorkspaceEntity,
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
import { parseImapError } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/parse-imap-error.util';
import { parseImapMessageListFetchError } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/parse-imap-message-list-fetch-error.util';
import { parseImapMessagesImportError } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/parse-imap-messages-import-error.util';
@Injectable()
export class ImapHandleErrorService {
private readonly logger = new Logger(ImapHandleErrorService.name);
constructor(
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}
async handleError(
error: Error,
workspaceId: string,
messageChannelId: string,
): Promise<void> {
this.logger.error(
`IMAP error for message channel ${messageChannelId}: ${error.message}`,
error.stack,
);
try {
const messageChannelRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelWorkspaceEntity>(
workspaceId,
'messageChannel',
);
await messageChannelRepository.update(
{ id: messageChannelId },
{
syncStatus: MessageChannelSyncStatus.FAILED_UNKNOWN,
},
);
} catch (handleErrorError) {
this.logger.error(
`Error handling IMAP error: ${handleErrorError.message}`,
handleErrorError.stack,
);
}
}
public handleImapMessageListFetchError(error: Error): void {
const imapError = parseImapError(error);
if (imapError) {
throw imapError;
}
throw parseImapMessageListFetchError(error);
}
public handleImapMessagesImportError(
error: Error,
messageExternalId: string,
): void {
const imapError = parseImapError(error);
if (imapError) {
throw imapError;
}
throw parseImapMessagesImportError(error, messageExternalId);
}
}
@@ -0,0 +1,21 @@
import { Injectable } from '@nestjs/common';
import { parseImapMessageListFetchError } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/parse-imap-message-list-fetch-error.util';
import { ImapNetworkErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-network-error-handler.service';
@Injectable()
export class ImapMessageListFetchErrorHandler {
constructor(
private readonly imapNetworkErrorHandler: ImapNetworkErrorHandler,
) {}
public handleError(error: Error): void {
const networkError = this.imapNetworkErrorHandler.handleError(error);
if (networkError) {
throw networkError;
}
throw parseImapMessageListFetchError(error, { cause: error });
}
}
@@ -3,7 +3,7 @@ import { Injectable, Logger } from '@nestjs/common';
import { type FetchMessageObject, type ImapFlow } from 'imapflow';
import { type ParsedMail, simpleParser } from 'mailparser';
import { ImapHandleErrorService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-handle-error.service';
import { ImapMessagesImportErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-messages-import-error-handler.service';
export type MessageFetchResult = {
uid: number;
@@ -16,7 +16,7 @@ export class ImapMessageProcessorService {
private readonly logger = new Logger(ImapMessageProcessorService.name);
constructor(
private readonly imapHandleErrorService: ImapHandleErrorService,
private readonly imapMessagesImportErrorHandler: ImapMessagesImportErrorHandler,
) {}
async processMessagesByUidsInFolder(
@@ -174,7 +174,7 @@ export class ImapMessageProcessorService {
return uids.map((uid) => {
this.logger.error(`Failed to fetch message UID ${uid}: ${error.message}`);
this.imapHandleErrorService.handleImapMessagesImportError(
this.imapMessagesImportErrorHandler.handleError(
error,
`${folder}:${uid}`,
);
@@ -0,0 +1,23 @@
import { Injectable } from '@nestjs/common';
import { parseImapMessagesImportError } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/parse-imap-messages-import-error.util';
import { ImapNetworkErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-network-error-handler.service';
@Injectable()
export class ImapMessagesImportErrorHandler {
constructor(
private readonly imapNetworkErrorHandler: ImapNetworkErrorHandler,
) {}
public handleError(error: Error, messageExternalId: string): void {
const networkError = this.imapNetworkErrorHandler.handleError(error);
if (networkError) {
throw networkError;
}
throw parseImapMessagesImportError(error, messageExternalId, {
cause: error,
});
}
}
@@ -0,0 +1,11 @@
import { Injectable } from '@nestjs/common';
import { type MessageImportDriverException } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
import { parseImapError } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/parse-imap-error.util';
@Injectable()
export class ImapNetworkErrorHandler {
public handleError(error: Error): MessageImportDriverException | null {
return parseImapError(error, { cause: error });
}
}
@@ -7,6 +7,7 @@ import { isImapFlowError } from 'src/modules/messaging/message-import-manager/dr
export const parseImapError = (
error: Error,
options?: { cause?: Error },
): MessageImportDriverException | null => {
if (!error) {
return null;
@@ -20,6 +21,7 @@ export const parseImapError = (
return new MessageImportDriverException(
`IMAP client not available: ${error.message}`,
MessageImportDriverExceptionCode.CLIENT_NOT_AVAILABLE,
{ cause: options?.cause || error },
);
}
@@ -27,6 +29,7 @@ export const parseImapError = (
return new MessageImportDriverException(
`IMAP connection timeout: ${error.message}`,
MessageNetworkExceptionCode.ETIMEDOUT,
{ cause: options?.cause || error },
);
}
@@ -34,6 +37,7 @@ export const parseImapError = (
return new MessageImportDriverException(
`IMAP connection error: ${error.message}`,
MessageImportDriverExceptionCode.UNKNOWN_NETWORK_ERROR,
{ cause: options?.cause || error },
);
}
@@ -42,6 +46,7 @@ export const parseImapError = (
return new MessageImportDriverException(
`IMAP authentication error: ${error.responseText || error.message}`,
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
{ cause: options?.cause || error },
);
}
@@ -49,6 +54,7 @@ export const parseImapError = (
return new MessageImportDriverException(
`IMAP mailbox not found: ${error.responseText || error.message}`,
MessageImportDriverExceptionCode.NOT_FOUND,
{ cause: options?.cause || error },
);
}
}
@@ -57,6 +63,7 @@ export const parseImapError = (
return new MessageImportDriverException(
`IMAP authentication error: ${error.responseText || error.message}`,
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
{ cause: options?.cause || error },
);
}
@@ -65,12 +72,14 @@ export const parseImapError = (
return new MessageImportDriverException(
`IMAP temporary error: ${error.responseText}`,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
{ cause: options?.cause || error },
);
}
return new MessageImportDriverException(
`IMAP command failed: ${error.responseText}`,
MessageImportDriverExceptionCode.UNKNOWN,
{ cause: options?.cause || error },
);
}
@@ -6,11 +6,13 @@ import { isImapFlowError } from 'src/modules/messaging/message-import-manager/dr
export const parseImapMessageListFetchError = (
error: Error,
options?: { cause?: Error },
): MessageImportDriverException => {
if (!error) {
return new MessageImportDriverException(
'Unknown IMAP message list fetch error: No error provided',
MessageImportDriverExceptionCode.UNKNOWN,
{ cause: options?.cause },
);
}
@@ -20,6 +22,7 @@ export const parseImapMessageListFetchError = (
return new MessageImportDriverException(
`Unknown IMAP message list fetch error: ${errorMessage}`,
MessageImportDriverExceptionCode.UNKNOWN,
{ cause: options?.cause || error },
);
}
@@ -31,6 +34,7 @@ export const parseImapMessageListFetchError = (
return new MessageImportDriverException(
`IMAP sync cursor error: ${error.responseText}`,
MessageImportDriverExceptionCode.SYNC_CURSOR_ERROR,
{ cause: options?.cause || error },
);
}
@@ -38,6 +42,7 @@ export const parseImapMessageListFetchError = (
return new MessageImportDriverException(
'No messages found for next sync cursor',
MessageImportDriverExceptionCode.NO_NEXT_SYNC_CURSOR,
{ cause: options?.cause || error },
);
}
}
@@ -46,6 +51,7 @@ export const parseImapMessageListFetchError = (
return new MessageImportDriverException(
`IMAP sync cursor error: ${errorMessage}`,
MessageImportDriverExceptionCode.SYNC_CURSOR_ERROR,
{ cause: options?.cause || error },
);
}
@@ -53,11 +59,13 @@ export const parseImapMessageListFetchError = (
return new MessageImportDriverException(
'No messages found for next sync cursor',
MessageImportDriverExceptionCode.NO_NEXT_SYNC_CURSOR,
{ cause: options?.cause || error },
);
}
return new MessageImportDriverException(
`Unknown IMAP message list fetch error: code: ${error.code} | responseText: ${error.responseText} | executedCommand: ${error.executedCommand}`,
MessageImportDriverExceptionCode.UNKNOWN,
{ cause: options?.cause || error },
);
};
@@ -7,11 +7,13 @@ import { isImapFlowError } from 'src/modules/messaging/message-import-manager/dr
export const parseImapMessagesImportError = (
error: Error,
messageExternalId: string,
options?: { cause?: Error },
): MessageImportDriverException => {
if (!error) {
return new MessageImportDriverException(
`Unknown IMAP message import error for message ${messageExternalId}: No error provided`,
MessageImportDriverExceptionCode.UNKNOWN,
{ cause: options?.cause },
);
}
@@ -21,6 +23,7 @@ export const parseImapMessagesImportError = (
return new MessageImportDriverException(
`Unknown IMAP message import error for message ${messageExternalId}: ${errorMessage}`,
MessageImportDriverExceptionCode.UNKNOWN,
{ cause: options?.cause || error },
);
}
@@ -29,6 +32,7 @@ export const parseImapMessagesImportError = (
return new MessageImportDriverException(
`IMAP message not found: ${messageExternalId}`,
MessageImportDriverExceptionCode.NOT_FOUND,
{ cause: options?.cause || error },
);
}
@@ -36,6 +40,7 @@ export const parseImapMessagesImportError = (
return new MessageImportDriverException(
`IMAP message no longer exists (expunged): ${messageExternalId}`,
MessageImportDriverExceptionCode.NOT_FOUND,
{ cause: options?.cause || error },
);
}
@@ -43,6 +48,7 @@ export const parseImapMessagesImportError = (
return new MessageImportDriverException(
`IMAP message fetch error for message ${messageExternalId}: ${error.responseText}`,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
{ cause: options?.cause || error },
);
}
}
@@ -54,6 +60,7 @@ export const parseImapMessagesImportError = (
return new MessageImportDriverException(
`IMAP message not found: ${messageExternalId}`,
MessageImportDriverExceptionCode.NOT_FOUND,
{ cause: options?.cause || error },
);
}
@@ -61,11 +68,13 @@ export const parseImapMessagesImportError = (
return new MessageImportDriverException(
`IMAP message fetch error for message ${messageExternalId}: ${errorMessage}`,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
{ cause: options?.cause || error },
);
}
return new MessageImportDriverException(
`Unknown IMAP message import error for message ${messageExternalId}: ${errorMessage}`,
MessageImportDriverExceptionCode.UNKNOWN,
{ cause: options?.cause || error },
);
};
@@ -8,7 +8,9 @@ import { OAuth2ClientManagerModule } from 'src/modules/connected-account/oauth2-
import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-common.module';
import { MicrosoftFetchByBatchService } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-fetch-by-batch.service';
import { MicrosoftGetMessagesService } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service';
import { MicrosoftHandleErrorService } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-handle-error.service';
import { MicrosoftMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-message-list-fetch-error-handler.service';
import { MicrosoftMessagesImportErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-messages-import-error-handler.service';
import { MicrosoftNetworkErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-network-error-handler.service';
import { MicrosoftGetMessageListService } from './services/microsoft-get-message-list.service';
@@ -25,12 +27,15 @@ import { MicrosoftGetMessageListService } from './services/microsoft-get-message
MicrosoftGetMessageListService,
MicrosoftGetMessagesService,
MicrosoftFetchByBatchService,
MicrosoftHandleErrorService,
MicrosoftNetworkErrorHandler,
MicrosoftMessageListFetchErrorHandler,
MicrosoftMessagesImportErrorHandler,
],
exports: [
MicrosoftGetMessageListService,
MicrosoftGetMessagesService,
MicrosoftHandleErrorService,
MicrosoftMessageListFetchErrorHandler,
MicrosoftMessagesImportErrorHandler,
],
})
export class MessagingMicrosoftDriverModule {}
@@ -13,7 +13,7 @@ import { microsoftGraphWithMessagesDeltaLink } from 'src/modules/messaging/messa
import { MessageFolderName } from 'src/modules/messaging/message-import-manager/drivers/microsoft/types/folders';
import { MicrosoftGetMessageListService } from './microsoft-get-message-list.service';
import { MicrosoftHandleErrorService } from './microsoft-handle-error.service';
import { MicrosoftMessageListFetchErrorHandler } from './microsoft-message-list-fetch-error-handler.service';
// in case you have "Please provide a valid token" it may be because you need to pass the env varible to the .env.test file
const accessToken = 'replace-with-your-access-token';
@@ -53,7 +53,10 @@ xdescribe('Microsoft dev tests : get message list service', () => {
providers: [
MicrosoftGetMessageListService,
OAuth2ClientManagerService,
MicrosoftHandleErrorService,
{
provide: MicrosoftMessageListFetchErrorHandler,
useValue: { handleError: jest.fn() },
},
MicrosoftOAuth2ClientManagerService,
ConfigService,
],
@@ -204,7 +207,10 @@ xdescribe('Microsoft dev tests : get message list service for folders', () => {
providers: [
MicrosoftGetMessageListService,
OAuth2ClientManagerService,
MicrosoftHandleErrorService,
{
provide: MicrosoftMessageListFetchErrorHandler,
useValue: { handleError: jest.fn() },
},
MicrosoftOAuth2ClientManagerService,
ConfigService,
],
@@ -14,7 +14,7 @@ import {
MessageImportDriverException,
MessageImportDriverExceptionCode,
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
import { MicrosoftHandleErrorService } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-handle-error.service';
import { MicrosoftMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-message-list-fetch-error-handler.service';
import { isAccessTokenRefreshingError } from 'src/modules/messaging/message-import-manager/drivers/microsoft/utils/is-access-token-refreshing-error.utils';
import { type GetMessageListsArgs } from 'src/modules/messaging/message-import-manager/types/get-message-lists-args.type';
import {
@@ -30,7 +30,7 @@ export class MicrosoftGetMessageListService {
private readonly logger = new Logger(MicrosoftGetMessageListService.name);
constructor(
private readonly oAuth2ClientManagerService: OAuth2ClientManagerService,
private readonly microsoftHandleErrorService: MicrosoftHandleErrorService,
private readonly microsoftMessageListFetchErrorHandler: MicrosoftMessageListFetchErrorHandler,
) {}
public async getMessageLists({
@@ -99,9 +99,7 @@ export class MicrosoftGetMessageListService {
MessageImportDriverExceptionCode.CLIENT_NOT_AVAILABLE,
);
}
this.microsoftHandleErrorService.handleMicrosoftGetMessageListError(
error,
);
this.microsoftMessageListFetchErrorHandler.handleError(error);
});
const callback: PageIteratorCallback = (data) => {
@@ -127,9 +125,7 @@ export class MicrosoftGetMessageListService {
MessageImportDriverExceptionCode.CLIENT_NOT_AVAILABLE,
);
}
this.microsoftHandleErrorService.handleMicrosoftGetMessageListError(
error,
);
this.microsoftMessageListFetchErrorHandler.handleError(error);
});
return {
@@ -9,7 +9,7 @@ import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2
import { MicrosoftFetchByBatchService } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-fetch-by-batch.service';
import { MicrosoftGetMessagesService } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service';
import { MicrosoftHandleErrorService } from './microsoft-handle-error.service';
import { MicrosoftMessagesImportErrorHandler } from './microsoft-messages-import-error-handler.service';
const mockMessageIds = [
'AAkALgAAAAAAHYQDEapmEc2byACqAC-EWg0AGnUPtcQC-Eiwmc39SmMpPgAAA8ZAfgAA',
@@ -27,7 +27,10 @@ xdescribe('Microsoft dev tests : get messages service', () => {
imports: [TwentyConfigModule.forRoot()],
providers: [
MicrosoftGetMessagesService,
MicrosoftHandleErrorService,
{
provide: MicrosoftMessagesImportErrorHandler,
useValue: { handleError: jest.fn() },
},
OAuth2ClientManagerService,
MicrosoftOAuth2ClientManagerService,
MicrosoftFetchByBatchService,
@@ -1,5 +1,5 @@
import { ConfigService } from '@nestjs/config';
import { Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Test, type TestingModule } from '@nestjs/testing';
import { ConnectedAccountProvider } from 'twenty-shared/types';
@@ -15,8 +15,7 @@ import {
import { MicrosoftFetchByBatchService } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-fetch-by-batch.service';
import { type MicrosoftGraphBatchResponse } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.interface';
import { MicrosoftGetMessagesService } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service';
import { MicrosoftHandleErrorService } from './microsoft-handle-error.service';
import { MicrosoftMessagesImportErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-messages-import-error-handler.service';
describe('Microsoft get messages service', () => {
let service: MicrosoftGetMessagesService;
@@ -25,7 +24,10 @@ describe('Microsoft get messages service', () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
MicrosoftGetMessagesService,
MicrosoftHandleErrorService,
{
provide: MicrosoftMessagesImportErrorHandler,
useValue: { handleError: jest.fn() },
},
OAuth2ClientManagerService,
GoogleOAuth2ClientManagerService,
MicrosoftOAuth2ClientManagerService,
@@ -13,7 +13,7 @@ import { formatAddressObjectAsParticipants } from 'src/modules/messaging/message
import { safeParseEmailAddress } from 'src/modules/messaging/message-import-manager/utils/safe-parse.util';
import { MicrosoftFetchByBatchService } from './microsoft-fetch-by-batch.service';
import { MicrosoftHandleErrorService } from './microsoft-handle-error.service';
import { MicrosoftMessagesImportErrorHandler } from './microsoft-messages-import-error-handler.service';
type ConnectedAccountType = Pick<
ConnectedAccountWorkspaceEntity,
@@ -31,7 +31,7 @@ export class MicrosoftGetMessagesService {
constructor(
private readonly microsoftFetchByBatchService: MicrosoftFetchByBatchService,
private readonly microsoftHandleErrorService: MicrosoftHandleErrorService,
private readonly microsoftMessagesImportErrorHandler: MicrosoftMessagesImportErrorHandler,
) {}
async getMessages(
@@ -52,7 +52,7 @@ export class MicrosoftGetMessagesService {
return messages;
} catch (error) {
this.microsoftHandleErrorService.handleMicrosoftGetMessagesError(error);
this.microsoftMessagesImportErrorHandler.handleError(error);
return [];
}
@@ -0,0 +1,28 @@
import { Injectable, Logger } from '@nestjs/common';
import { MicrosoftNetworkErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-network-error-handler.service';
import { parseMicrosoftMessagesImportError } from 'src/modules/messaging/message-import-manager/drivers/microsoft/utils/parse-microsoft-messages-import.util';
@Injectable()
export class MicrosoftMessageListFetchErrorHandler {
private readonly logger = new Logger(
MicrosoftMessageListFetchErrorHandler.name,
);
constructor(
private readonly microsoftNetworkErrorHandler: MicrosoftNetworkErrorHandler,
) {}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public handleError(error: any): void {
this.logger.log(`Error fetching message list`, error);
const networkError = this.microsoftNetworkErrorHandler.handleError(error);
if (networkError) {
throw networkError;
}
throw parseMicrosoftMessagesImportError(error, { cause: error });
}
}
@@ -0,0 +1,28 @@
import { Injectable, Logger } from '@nestjs/common';
import { MicrosoftNetworkErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-network-error-handler.service';
import { parseMicrosoftMessagesImportError } from 'src/modules/messaging/message-import-manager/drivers/microsoft/utils/parse-microsoft-messages-import.util';
@Injectable()
export class MicrosoftMessagesImportErrorHandler {
private readonly logger = new Logger(
MicrosoftMessagesImportErrorHandler.name,
);
constructor(
private readonly microsoftNetworkErrorHandler: MicrosoftNetworkErrorHandler,
) {}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public handleError(error: any): void {
this.logger.log(`Error fetching messages`, error);
const networkError = this.microsoftNetworkErrorHandler.handleError(error);
if (networkError) {
throw networkError;
}
throw parseMicrosoftMessagesImportError(error, { cause: error });
}
}
@@ -6,25 +6,18 @@ import {
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
import { isAccessTokenRefreshingError } from 'src/modules/messaging/message-import-manager/drivers/microsoft/utils/is-access-token-refreshing-error.utils';
import { isMicrosoftClientTemporaryError } from 'src/modules/messaging/message-import-manager/drivers/microsoft/utils/is-temporary-error.utils';
import { parseMicrosoftMessagesImportError } from 'src/modules/messaging/message-import-manager/drivers/microsoft/utils/parse-microsoft-messages-import.util';
@Injectable()
export class MicrosoftHandleErrorService {
private readonly logger = new Logger(MicrosoftHandleErrorService.name);
export class MicrosoftNetworkErrorHandler {
private readonly logger = new Logger(MicrosoftNetworkErrorHandler.name);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public handleMicrosoftGetMessageListError(error: any): void {
this.logger.log(`Error fetching message list`, error);
throw parseMicrosoftMessagesImportError(error);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public handleMicrosoftGetMessagesError(error: any): void {
this.logger.log(`Error fetching messages`, error);
public handleError(error: any): MessageImportDriverException | null {
if (isAccessTokenRefreshingError(error?.body)) {
throw new MessageImportDriverException(
return new MessageImportDriverException(
error.message,
MessageImportDriverExceptionCode.CLIENT_NOT_AVAILABLE,
{ cause: error },
);
}
@@ -33,12 +26,13 @@ export class MicrosoftHandleErrorService {
isBodyString && isMicrosoftClientTemporaryError(error.body);
if (isTemporaryError) {
throw new MessageImportDriverException(
return new MessageImportDriverException(
`code: ${error.code} - body: ${error.body}`,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
{ cause: error },
);
}
throw parseMicrosoftMessagesImportError(error);
return null;
}
}
@@ -3,15 +3,19 @@ import {
MessageImportDriverExceptionCode,
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
export const parseMicrosoftMessagesImportError = (error: {
statusCode: number;
message?: string;
code?: string;
}): MessageImportDriverException => {
export const parseMicrosoftMessagesImportError = (
error: {
statusCode: number;
message?: string;
code?: string;
},
options?: { cause?: Error },
): MessageImportDriverException => {
if (error.statusCode === 401) {
return new MessageImportDriverException(
'Unauthorized access to Microsoft Graph API',
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
{ cause: options?.cause },
);
}
@@ -19,6 +23,7 @@ export const parseMicrosoftMessagesImportError = (error: {
return new MessageImportDriverException(
'Forbidden access to Microsoft Graph API',
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
{ cause: options?.cause },
);
}
@@ -31,11 +36,13 @@ export const parseMicrosoftMessagesImportError = (error: {
return new MessageImportDriverException(
`Disabled, deleted, inactive or no licence Microsoft account - code:${error.code}`,
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
{ cause: options?.cause },
);
} else {
return new MessageImportDriverException(
`Not found - code:${error.code}`,
MessageImportDriverExceptionCode.NOT_FOUND,
{ cause: options?.cause },
);
}
}
@@ -48,11 +55,13 @@ export const parseMicrosoftMessagesImportError = (error: {
return new MessageImportDriverException(
`Microsoft Graph API ${error.code} ${error.statusCode} error: ${error.message}`,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
{ cause: options?.cause },
);
}
return new MessageImportDriverException(
`Microsoft Graph API unknown error: ${error} with status code ${error.statusCode}`,
MessageImportDriverExceptionCode.UNKNOWN,
{ cause: options?.cause },
);
};
@@ -1,8 +0,0 @@
import { CustomException } from 'src/utils/custom-exception';
export class MessageImportException extends CustomException<MessageImportExceptionCode> {}
export enum MessageImportExceptionCode {
UNKNOWN = 'UNKNOWN',
PROVIDER_NOT_SUPPORTED = 'PROVIDER_NOT_SUPPORTED',
}
@@ -7,13 +7,13 @@ import {
type MessageChannelWorkspaceEntity,
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
import { MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
import {
MessageImportDriverException,
MessageImportDriverExceptionCode,
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
import { GmailGetMessageListService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-message-list.service';
import { ImapGetMessageListService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-message-list.service';
import { MicrosoftGetMessageListService } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service';
import {
MessageImportException,
MessageImportExceptionCode,
} from 'src/modules/messaging/message-import-manager/exceptions/message-import.exception';
import { type GetMessageListsResponse } from 'src/modules/messaging/message-import-manager/types/get-message-lists-response.type';
type MessageFolder = Pick<
@@ -60,9 +60,9 @@ export class MessagingGetMessageListService {
});
}
default:
throw new MessageImportException(
throw new MessageImportDriverException(
`Provider ${messageChannel.connectedAccount.provider} is not supported`,
MessageImportExceptionCode.PROVIDER_NOT_SUPPORTED,
MessageImportDriverExceptionCode.PROVIDER_NOT_SUPPORTED,
);
}
}
@@ -3,13 +3,13 @@ import { Injectable } from '@nestjs/common';
import { ConnectedAccountProvider } from 'twenty-shared/types';
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import {
MessageImportDriverException,
MessageImportDriverExceptionCode,
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
import { GmailGetMessagesService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-messages.service';
import { ImapGetMessagesService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-messages.service';
import { MicrosoftGetMessagesService } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service';
import {
MessageImportException,
MessageImportExceptionCode,
} from 'src/modules/messaging/message-import-manager/exceptions/message-import.exception';
import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message';
export type GetMessagesResponse = MessageWithParticipants[];
@@ -53,9 +53,9 @@ export class MessagingGetMessagesService {
connectedAccount,
);
default:
throw new MessageImportException(
throw new MessageImportDriverException(
`Provider ${connectedAccount.provider} is not supported`,
MessageImportExceptionCode.PROVIDER_NOT_SUPPORTED,
MessageImportDriverExceptionCode.PROVIDER_NOT_SUPPORTED,
);
}
}
@@ -1,7 +1,5 @@
import { Injectable } from '@nestjs/common';
import { isDefined } from 'class-validator';
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
import {
type TwentyORMException,
@@ -19,10 +17,6 @@ import {
MessageImportDriverExceptionCode,
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
import { MessageNetworkExceptionCode } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-network.exception';
import {
MessageImportException,
MessageImportExceptionCode,
} from 'src/modules/messaging/message-import-manager/exceptions/message-import.exception';
export enum MessageImportSyncStep {
MESSAGE_LIST_FETCH = 'MESSAGE_LIST_FETCH',
@@ -47,6 +41,19 @@ export class MessageImportExceptionHandlerService {
>,
workspaceId: string,
): Promise<void> {
if (exception instanceof MessageImportDriverException) {
exception.context = {
...exception.context,
messageChannelId: messageChannel.id,
workspaceId,
syncStep,
};
}
this.exceptionHandlerService.captureExceptions([exception], {
workspace: { id: workspaceId },
});
if ('code' in exception) {
switch (exception.code) {
case MessageImportDriverExceptionCode.NOT_FOUND:
@@ -78,24 +85,16 @@ export class MessageImportExceptionHandlerService {
);
break;
case MessageImportDriverExceptionCode.SYNC_CURSOR_ERROR:
await this.handlePermanentException(
exception,
messageChannel,
workspaceId,
);
await this.handlePermanentException(messageChannel, workspaceId);
break;
case MessageImportDriverExceptionCode.UNKNOWN:
case MessageImportDriverExceptionCode.UNKNOWN_NETWORK_ERROR:
default:
await this.handleUnknownException(
exception,
messageChannel,
workspaceId,
);
await this.handleUnknownException(messageChannel, workspaceId);
break;
}
} else {
await this.handleUnknownException(exception, messageChannel, workspaceId);
await this.handleUnknownException(messageChannel, workspaceId);
}
}
@@ -119,7 +118,9 @@ export class MessageImportExceptionHandlerService {
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}`,
new Error(
`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: {
@@ -129,10 +130,7 @@ export class MessageImportExceptionHandlerService {
},
);
throw new MessageImportException(
`Temporary error occurred multiple times while importing messages for message channel ${messageChannel.id} in workspace ${workspaceId}: ${exception?.message}`,
MessageImportExceptionCode.UNKNOWN,
);
return;
}
const messageChannelRepository =
@@ -179,7 +177,6 @@ export class MessageImportExceptionHandlerService {
}
private async handleUnknownException(
exception: MessageImportDriverException | Error,
messageChannel: Pick<MessageChannelWorkspaceEntity, 'id'>,
workspaceId: string,
): Promise<void> {
@@ -188,27 +185,9 @@ export class MessageImportExceptionHandlerService {
workspaceId,
MessageChannelSyncStatus.FAILED_UNKNOWN,
);
const messageImportException = new MessageImportException(
isDefined(exception.name)
? `${exception.name}: ${exception.message}`
: exception.message,
MessageImportExceptionCode.UNKNOWN,
);
this.exceptionHandlerService.captureExceptions([messageImportException], {
additionalData: {
exception,
messageChannelId: messageChannel.id,
},
workspace: { id: workspaceId },
});
throw messageImportException;
}
private async handlePermanentException(
exception: MessageImportDriverException,
messageChannel: Pick<MessageChannelWorkspaceEntity, 'id'>,
workspaceId: string,
): Promise<void> {
@@ -217,11 +196,6 @@ export class MessageImportExceptionHandlerService {
workspaceId,
MessageChannelSyncStatus.FAILED_UNKNOWN,
);
throw new MessageImportException(
`Permanent error occurred while importing messages for message channel ${messageChannel.id} in workspace ${workspaceId}: ${exception.message}`,
MessageImportExceptionCode.UNKNOWN,
);
}
private async handleNotFoundException(
@@ -230,14 +204,27 @@ export class MessageImportExceptionHandlerService {
workspaceId: string,
): Promise<void> {
if (syncStep === MessageImportSyncStep.MESSAGE_LIST_FETCH) {
await this.handleUnknownException(
new MessageImportDriverException(
'Not Found exception occurred while fetching message list, which should never happen',
MessageImportDriverExceptionCode.UNKNOWN,
),
messageChannel,
await this.messageChannelSyncStatusService.markAsFailed(
[messageChannel.id],
workspaceId,
MessageChannelSyncStatus.FAILED_UNKNOWN,
);
this.exceptionHandlerService.captureExceptions(
[
new Error(
'Not Found exception occurred while fetching message list, which should never happen',
),
],
{
additionalData: {
messageChannelId: messageChannel.id,
},
workspace: { id: workspaceId },
},
);
return;
}
await this.messageChannelSyncStatusService.resetAndScheduleMessageListFetch(