Add error handling service for calendar import (#6203)

Add error handling service for calendar import

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
bosiraphael
2024-07-12 18:56:45 +02:00
committed by GitHub
parent 52aa9abd73
commit c8a889995f
11 changed files with 365 additions and 30 deletions

View File

@ -6,9 +6,9 @@ import { CacheStorageNamespace } from 'src/engine/integrations/cache-storage/typ
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import {
CalendarChannelWorkspaceEntity,
CalendarChannelSyncStage,
CalendarChannelSyncStatus,
CalendarChannelWorkspaceEntity,
} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
@Injectable()
@ -74,11 +74,18 @@ export class CalendarChannelSyncStatusService {
});
}
public async markAsCalendarEventsImportCompleted(calendarChannelId: string) {
public async markAsCompletedAndSchedulePartialMessageListFetch(
calendarChannelId: string,
) {
await this.calendarChannelRepository.update(calendarChannelId, {
syncStage: CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_PENDING,
syncStage:
CalendarChannelSyncStage.PARTIAL_CALENDAR_EVENT_LIST_FETCH_PENDING,
syncStatus: CalendarChannelSyncStatus.ACTIVE,
throttleFailureCount: 0,
syncStageStartedAt: null,
});
await this.schedulePartialCalendarEventListFetch(calendarChannelId);
}
public async markAsFailedUnknownAndFlushCalendarEventsToImport(

View File

@ -0,0 +1,144 @@
import { Injectable } from '@nestjs/common';
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { CALENDAR_THROTTLE_MAX_ATTEMPTS } from 'src/modules/calendar/calendar-event-import-manager/constants/calendar-throttle-max-attempts';
import { CalendarChannelSyncStatusService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-channel-sync-status.service';
import { CalendarEventError } from 'src/modules/calendar/calendar-event-import-manager/types/calendar-event-error.type';
import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
export enum CalendarEventImportSyncStep {
FULL_CALENDAR_EVENT_LIST_FETCH = 'FULL_CALENDAR_EVENT_LIST_FETCH',
PARTIAL_CALENDAR_EVENT_LIST_FETCH = 'PARTIAL_CALENDAR_EVENT_LIST_FETCH',
CALENDAR_EVENTS_IMPORT = 'CALENDAR_EVENTS_IMPORT',
}
@Injectable()
export class CalendarEventImportErrorHandlerService {
constructor(
private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService,
@InjectWorkspaceRepository(CalendarChannelWorkspaceEntity)
private readonly calendarChannelRepository: WorkspaceRepository<CalendarChannelWorkspaceEntity>,
) {}
public async handleError(
error: CalendarEventError,
syncStep: CalendarEventImportSyncStep,
calendarChannel: Pick<
CalendarChannelWorkspaceEntity,
'id' | 'throttleFailureCount'
>,
workspaceId: string,
): Promise<void> {
switch (error.code) {
case 'NOT_FOUND':
await this.handleNotFoundError(syncStep, calendarChannel, workspaceId);
break;
case 'TEMPORARY_ERROR':
await this.handleTemporaryError(syncStep, calendarChannel, workspaceId);
break;
case 'INSUFFICIENT_PERMISSIONS':
await this.handleInsufficientPermissionsError(
calendarChannel,
workspaceId,
);
break;
case 'UNKNOWN':
await this.handleUnknownError(error, calendarChannel, workspaceId);
break;
}
}
private async handleTemporaryError(
syncStep: CalendarEventImportSyncStep,
calendarChannel: Pick<
CalendarChannelWorkspaceEntity,
'id' | 'throttleFailureCount'
>,
workspaceId: string,
): Promise<void> {
if (
calendarChannel.throttleFailureCount >= CALENDAR_THROTTLE_MAX_ATTEMPTS
) {
await this.calendarChannelSyncStatusService.markAsFailedUnknownAndFlushCalendarEventsToImport(
calendarChannel.id,
workspaceId,
);
return;
}
await this.calendarChannelRepository.increment(
{
id: calendarChannel.id,
},
'throttleFailureCount',
1,
);
switch (syncStep) {
case CalendarEventImportSyncStep.FULL_CALENDAR_EVENT_LIST_FETCH:
await this.calendarChannelSyncStatusService.scheduleFullCalendarEventListFetch(
calendarChannel.id,
);
break;
case CalendarEventImportSyncStep.PARTIAL_CALENDAR_EVENT_LIST_FETCH:
await this.calendarChannelSyncStatusService.schedulePartialCalendarEventListFetch(
calendarChannel.id,
);
break;
case CalendarEventImportSyncStep.CALENDAR_EVENTS_IMPORT:
await this.calendarChannelSyncStatusService.scheduleCalendarEventsImport(
calendarChannel.id,
);
break;
default:
break;
}
}
private async handleInsufficientPermissionsError(
calendarChannel: Pick<CalendarChannelWorkspaceEntity, 'id'>,
workspaceId: string,
): Promise<void> {
await this.calendarChannelSyncStatusService.markAsFailedInsufficientPermissionsAndFlushCalendarEventsToImport(
calendarChannel.id,
workspaceId,
);
}
private async handleUnknownError(
error: CalendarEventError,
calendarChannel: Pick<CalendarChannelWorkspaceEntity, 'id'>,
workspaceId: string,
): Promise<void> {
await this.calendarChannelSyncStatusService.markAsFailedUnknownAndFlushCalendarEventsToImport(
calendarChannel.id,
workspaceId,
);
throw new Error(
`Unknown error occurred while importing calendar events for calendar channel ${calendarChannel.id} in workspace ${workspaceId}: ${error.message}`,
);
}
private async handleNotFoundError(
syncStep: CalendarEventImportSyncStep,
calendarChannel: Pick<CalendarChannelWorkspaceEntity, 'id'>,
workspaceId: string,
): Promise<void> {
if (
syncStep === CalendarEventImportSyncStep.FULL_CALENDAR_EVENT_LIST_FETCH
) {
return;
}
await this.calendarChannelSyncStatusService.resetAndScheduleFullCalendarEventListFetch(
calendarChannel.id,
workspaceId,
);
}
}

View File

@ -7,11 +7,21 @@ import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inje
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { CalendarEventCleanerService } from 'src/modules/calendar/calendar-event-cleaner/services/calendar-event-cleaner.service';
import { CalendarChannelSyncStatusService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-channel-sync-status.service';
import { CalendarGetCalendarEventsService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-get-events.service';
import {
CalendarEventImportErrorHandlerService,
CalendarEventImportSyncStep,
} from 'src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-error-handling.service';
import {
CalendarGetCalendarEventsService,
GetCalendarEventsResponse,
} from 'src/modules/calendar/calendar-event-import-manager/services/calendar-get-events.service';
import { CalendarSaveEventsService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service';
import { filterEventsAndReturnCancelledEvents } from 'src/modules/calendar/calendar-event-import-manager/utils/filter-events.util';
import { CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity';
import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
import {
CalendarChannelSyncStage,
CalendarChannelWorkspaceEntity,
} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
import { BlocklistRepository } from 'src/modules/connected-account/repositories/blocklist.repository';
import { BlocklistWorkspaceEntity } from 'src/modules/connected-account/standard-objects/blocklist.workspace-entity';
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
@ -29,6 +39,7 @@ export class CalendarEventsImportService {
private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService,
private readonly getCalendarEventsService: CalendarGetCalendarEventsService,
private readonly calendarSaveEventsService: CalendarSaveEventsService,
private readonly calendarEventImportErrorHandlerService: CalendarEventImportErrorHandlerService,
) {}
public async processCalendarEventsImport(
@ -36,16 +47,38 @@ export class CalendarEventsImportService {
connectedAccount: ConnectedAccountWorkspaceEntity,
workspaceId: string,
): Promise<void> {
const syncStep =
calendarChannel.syncStage ===
CalendarChannelSyncStage.FULL_CALENDAR_EVENT_LIST_FETCH_PENDING
? CalendarEventImportSyncStep.FULL_CALENDAR_EVENT_LIST_FETCH
: CalendarEventImportSyncStep.PARTIAL_CALENDAR_EVENT_LIST_FETCH;
await this.calendarChannelSyncStatusService.markAsCalendarEventListFetchOngoing(
calendarChannel.id,
);
let calendarEvents: GetCalendarEventsResponse['calendarEvents'] = [];
let nextSyncCursor: GetCalendarEventsResponse['nextSyncCursor'] = '';
const { calendarEvents, nextSyncCursor } =
await this.getCalendarEventsService.getCalendarEvents(
connectedAccount,
calendarChannel.syncCursor,
try {
const getCalendarEventsResponse =
await this.getCalendarEventsService.getCalendarEvents(
connectedAccount,
calendarChannel.syncCursor,
);
calendarEvents = getCalendarEventsResponse.calendarEvents;
nextSyncCursor = getCalendarEventsResponse.nextSyncCursor;
} catch (error) {
await this.calendarEventImportErrorHandlerService.handleError(
error,
syncStep,
calendarChannel,
workspaceId,
);
return;
}
if (!calendarEvents || calendarEvents?.length === 0) {
await this.calendarChannelRepository.update(
{
@ -104,7 +137,7 @@ export class CalendarEventsImportService {
},
);
await this.calendarChannelSyncStatusService.schedulePartialCalendarEventListFetch(
await this.calendarChannelSyncStatusService.markAsCompletedAndSchedulePartialMessageListFetch(
calendarChannel.id,
);
}