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:
@ -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(
|
||||
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user