Refactor calendar to use new sync statuses and stages (#6141)
- Refactor calendar modules and some messaging modules to better organize them by business rules and decouple them - Work toward a common architecture for the different calendar providers by introducing interfaces for the drivers - Modify cron job to use the new sync statuses and stages
This commit is contained in:
@ -9,10 +9,9 @@ import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/perso
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-common.module';
|
||||
import { CalendarEventParticipantModule } from 'src/modules/calendar/calendar-event-participant-manager/calendar-event-participant.module';
|
||||
import { AutoCompaniesAndContactsCreationMessageChannelListener } from 'src/modules/connected-account/auto-companies-and-contacts-creation/listeners/auto-companies-and-contacts-creation-message-channel.listener';
|
||||
import { AutoCompaniesAndContactsCreationCalendarChannelListener } from 'src/modules/connected-account/auto-companies-and-contacts-creation/listeners/auto-companies-and-contacts-creation-calendar-channel.listener';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -22,10 +21,9 @@ import { AutoCompaniesAndContactsCreationCalendarChannelListener } from 'src/mod
|
||||
PersonWorkspaceEntity,
|
||||
WorkspaceMemberWorkspaceEntity,
|
||||
]),
|
||||
MessagingCommonModule,
|
||||
WorkspaceDataSourceModule,
|
||||
CalendarEventParticipantModule,
|
||||
TypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
|
||||
TypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'),
|
||||
],
|
||||
providers: [
|
||||
CreateCompanyAndContactService,
|
||||
|
||||
@ -5,9 +5,9 @@ import { v4 } from 'uuid';
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
|
||||
import { CompanyRepository } from 'src/modules/company/repositories/company.repository';
|
||||
import { getCompanyNameFromDomainName } from 'src/modules/calendar-messaging-participant/utils/get-company-name-from-domain-name.util';
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/company.workspace-entity';
|
||||
import { getCompanyNameFromDomainName } from 'src/modules/connected-account/auto-companies-and-contacts-creation/utils/get-company-name-from-domain-name.util';
|
||||
@Injectable()
|
||||
export class CreateCompanyService {
|
||||
private readonly httpService: AxiosInstance;
|
||||
|
||||
@ -4,7 +4,7 @@ import { EntityManager } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { PersonRepository } from 'src/modules/person/repositories/person.repository';
|
||||
import { getFirstNameAndLastNameFromHandleAndDisplayName } from 'src/modules/calendar-messaging-participant/utils/get-first-name-and-last-name-from-handle-and-display-name.util';
|
||||
import { getFirstNameAndLastNameFromHandleAndDisplayName } from 'src/modules/connected-account/auto-companies-and-contacts-creation/utils/get-first-name-and-last-name-from-handle-and-display-name.util';
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
|
||||
|
||||
|
||||
@ -6,11 +6,11 @@ import { objectRecordChangedProperties } from 'src/engine/integrations/event-emi
|
||||
import { InjectMessageQueue } from 'src/engine/integrations/message-queue/decorators/message-queue.decorator';
|
||||
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/engine/integrations/message-queue/services/message-queue.service';
|
||||
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import {
|
||||
CalendarCreateCompanyAndContactAfterSyncJobData,
|
||||
CalendarCreateCompanyAndContactAfterSyncJob,
|
||||
} from 'src/modules/messaging/message-participants-manager/jobs/calendar-create-company-and-contact-after-sync.job';
|
||||
} from 'src/modules/calendar/calendar-event-participant-manager/jobs/calendar-create-company-and-contact-after-sync.job';
|
||||
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
|
||||
@Injectable()
|
||||
export class AutoCompaniesAndContactsCreationCalendarChannelListener {
|
||||
|
||||
@ -10,7 +10,7 @@ import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/stan
|
||||
import {
|
||||
MessagingCreateCompanyAndContactAfterSyncJobData,
|
||||
MessagingCreateCompanyAndContactAfterSyncJob,
|
||||
} from 'src/modules/messaging/message-participants-manager/jobs/messaging-create-company-and-contact-after-sync.job';
|
||||
} from 'src/modules/messaging/message-participant-manager/jobs/messaging-create-company-and-contact-after-sync.job';
|
||||
|
||||
@Injectable()
|
||||
export class AutoCompaniesAndContactsCreationMessageChannelListener {
|
||||
|
||||
@ -1,25 +1,23 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import chunk from 'lodash.chunk';
|
||||
import compact from 'lodash.compact';
|
||||
import { EntityManager } from 'typeorm';
|
||||
import { EntityManager, Repository } from 'typeorm';
|
||||
|
||||
import { ObjectRecordCreateEvent } from 'src/engine/integrations/event-emitter/types/object-record-create.event';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||
import { InjectWorkspaceDatasource } from 'src/engine/twenty-orm/decorators/inject-workspace-datasource.decorator';
|
||||
import { getDomainNameFromHandle } from 'src/modules/calendar-messaging-participant/utils/get-domain-name-from-handle.util';
|
||||
import { CalendarEventParticipantService } from 'src/modules/calendar/calendar-event-participant-manager/services/calendar-event-participant.service';
|
||||
import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event-participant.workspace-entity';
|
||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||
import { CONTACTS_CREATION_BATCH_SIZE } from 'src/modules/connected-account/auto-companies-and-contacts-creation/constants/contacts-creation-batch-size.constant';
|
||||
import { CreateCompanyService } from 'src/modules/connected-account/auto-companies-and-contacts-creation/create-company/create-company.service';
|
||||
import { CreateContactService } from 'src/modules/connected-account/auto-companies-and-contacts-creation/create-contact/create-contact.service';
|
||||
import { Contact } from 'src/modules/connected-account/auto-companies-and-contacts-creation/types/contact.type';
|
||||
import { filterOutSelfAndContactsFromCompanyOrWorkspace } from 'src/modules/connected-account/auto-companies-and-contacts-creation/utils/filter-out-contacts-from-company-or-workspace.util';
|
||||
import { getDomainNameFromHandle } from 'src/modules/connected-account/auto-companies-and-contacts-creation/utils/get-domain-name-from-handle.util';
|
||||
import { getUniqueContactsAndHandles } from 'src/modules/connected-account/auto-companies-and-contacts-creation/utils/get-unique-contacts-and-handles.util';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { MessagingMessageParticipantService } from 'src/modules/messaging/common/services/messaging-message-participant.service';
|
||||
import { MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity';
|
||||
import { PersonRepository } from 'src/modules/person/repositories/person.repository';
|
||||
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
|
||||
import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository';
|
||||
@ -35,14 +33,12 @@ export class CreateCompanyAndContactService {
|
||||
private readonly personRepository: PersonRepository,
|
||||
@InjectObjectMetadataRepository(WorkspaceMemberWorkspaceEntity)
|
||||
private readonly workspaceMemberRepository: WorkspaceMemberRepository,
|
||||
@InjectWorkspaceDatasource()
|
||||
private readonly workspaceDataSource: WorkspaceDataSource,
|
||||
private readonly messageParticipantService: MessagingMessageParticipantService,
|
||||
private readonly calendarEventParticipantService: CalendarEventParticipantService,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
) {}
|
||||
|
||||
async createCompaniesAndPeople(
|
||||
private async createCompaniesAndPeople(
|
||||
connectedAccount: ConnectedAccountWorkspaceEntity,
|
||||
contactsToCreate: Contact[],
|
||||
workspaceId: string,
|
||||
@ -137,47 +133,37 @@ export class CreateCompanyAndContactService {
|
||||
CONTACTS_CREATION_BATCH_SIZE,
|
||||
);
|
||||
|
||||
// TODO: Remove this when events are emitted directly inside TwentyORM
|
||||
|
||||
const objectMetadata = await this.objectMetadataRepository.findOne({
|
||||
where: {
|
||||
standardId: STANDARD_OBJECT_IDS.person,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!objectMetadata) {
|
||||
throw new Error('Object metadata not found');
|
||||
}
|
||||
|
||||
for (const contactsBatch of contactsBatches) {
|
||||
let updatedMessageParticipants: MessageParticipantWorkspaceEntity[] = [];
|
||||
let updatedCalendarEventParticipants: CalendarEventParticipantWorkspaceEntity[] =
|
||||
[];
|
||||
|
||||
await this.workspaceDataSource?.transaction(
|
||||
async (transactionManager: EntityManager) => {
|
||||
const createdPeople = await this.createCompaniesAndPeople(
|
||||
connectedAccount,
|
||||
contactsBatch,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
updatedMessageParticipants =
|
||||
await this.messageParticipantService.updateMessageParticipantsAfterPeopleCreation(
|
||||
createdPeople,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
updatedCalendarEventParticipants =
|
||||
await this.calendarEventParticipantService.updateCalendarEventParticipantsAfterPeopleCreation(
|
||||
createdPeople,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
},
|
||||
const createdPeople = await this.createCompaniesAndPeople(
|
||||
connectedAccount,
|
||||
contactsBatch,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
this.eventEmitter.emit(`messageParticipant.matched`, {
|
||||
workspaceId,
|
||||
workspaceMemberId: connectedAccount.accountOwnerId,
|
||||
messageParticipants: updatedMessageParticipants,
|
||||
});
|
||||
|
||||
this.eventEmitter.emit(`calendarEventParticipant.matched`, {
|
||||
workspaceId,
|
||||
workspaceMemberId: connectedAccount.accountOwnerId,
|
||||
calendarEventParticipants: updatedCalendarEventParticipants,
|
||||
});
|
||||
for (const createdPerson of createdPeople) {
|
||||
this.eventEmitter.emit('person.created', {
|
||||
name: 'person.created',
|
||||
workspaceId,
|
||||
recordId: createdPerson.id,
|
||||
objectMetadata,
|
||||
properties: {
|
||||
after: createdPerson,
|
||||
},
|
||||
} satisfies ObjectRecordCreateEvent<any>);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getDomainNameFromHandle } from 'src/modules/calendar-messaging-participant/utils/get-domain-name-from-handle.util';
|
||||
import { getDomainNameFromHandle } from 'src/modules/connected-account/auto-companies-and-contacts-creation/utils/get-domain-name-from-handle.util';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { Contact } from 'src/modules/connected-account/auto-companies-and-contacts-creation/types/contact.type';
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import psl from 'psl';
|
||||
|
||||
import { capitalize } from 'src/utils/capitalize';
|
||||
|
||||
export const getCompanyNameFromDomainName = (domainName: string) => {
|
||||
const { sld } = psl.parse(domainName);
|
||||
|
||||
return sld ? capitalize(sld) : '';
|
||||
};
|
||||
@ -0,0 +1,9 @@
|
||||
import psl from 'psl';
|
||||
|
||||
export const getDomainNameFromHandle = (handle: string): string => {
|
||||
const wholeDomain = handle?.split('@')?.[1] || '';
|
||||
|
||||
const { domain } = psl.parse(wholeDomain);
|
||||
|
||||
return domain || '';
|
||||
};
|
||||
@ -0,0 +1,18 @@
|
||||
import { capitalize } from 'src/utils/capitalize';
|
||||
|
||||
export function getFirstNameAndLastNameFromHandleAndDisplayName(
|
||||
handle: string,
|
||||
displayName: string,
|
||||
): { firstName: string; lastName: string } {
|
||||
const firstName = displayName.split(' ')[0];
|
||||
const lastName = displayName.split(' ')[1];
|
||||
|
||||
const contactFullNameFromHandle = handle.split('@')[0];
|
||||
const firstNameFromHandle = contactFullNameFromHandle.split('.')[0];
|
||||
const lastNameFromHandle = contactFullNameFromHandle.split('.')[1];
|
||||
|
||||
return {
|
||||
firstName: capitalize(firstName || firstNameFromHandle || ''),
|
||||
lastName: capitalize(lastName || lastNameFromHandle || ''),
|
||||
};
|
||||
}
|
||||
@ -12,7 +12,10 @@ export class OAuth2ClientManagerService {
|
||||
) {}
|
||||
|
||||
public async getOAuth2Client(
|
||||
connectedAccount: ConnectedAccountWorkspaceEntity,
|
||||
connectedAccount: Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
'provider' | 'refreshToken'
|
||||
>,
|
||||
): Promise<OAuth2Client> {
|
||||
const { refreshToken } = connectedAccount;
|
||||
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
import { MESSAGING_THROTTLE_DURATION } from 'src/modules/messaging/common/constants/messaging-throttle-duration';
|
||||
|
||||
export const isThrottled = (
|
||||
syncStageStartedAt: string | null,
|
||||
throttleFailureCount: number,
|
||||
): boolean => {
|
||||
if (!syncStageStartedAt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
computeThrottlePauseUntil(syncStageStartedAt, throttleFailureCount) >
|
||||
new Date()
|
||||
);
|
||||
};
|
||||
|
||||
const computeThrottlePauseUntil = (
|
||||
syncStageStartedAt: string,
|
||||
throttleFailureCount: number,
|
||||
): Date => {
|
||||
return new Date(
|
||||
new Date(syncStageStartedAt).getTime() +
|
||||
MESSAGING_THROTTLE_DURATION * Math.pow(2, throttleFailureCount - 1),
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user