From 936ac4027af5423dc338a6d44f1fdcbc0e638358 Mon Sep 17 00:00:00 2001 From: bosiraphael <71827178+bosiraphael@users.noreply.github.com> Date: Fri, 24 May 2024 18:55:21 +0200 Subject: [PATCH] Introduce a new feature flag for contact creation (#5570) Introduce new feature flag `IS_CONTACT_CREATION_FOR_SENT_AND_RECEIVED_EMAILS_ENABLED` to allow contacts to be created for sent and received emails. --- .../typeorm-seeds/core/feature-flags.ts | 5 +++ .../feature-flag/feature-flag.entity.ts | 1 + .../commands/add-standard-id.command.ts | 2 ++ ...eate-company-and-contact-after-sync.job.ts | 33 ++++++++++++++--- .../message-participant.repository.ts | 35 +++++++++++++++++++ .../gmail-messages-import.module.ts | 3 ++ .../gmail-messages-import.service.ts | 24 +++++++++++-- ...es-and-enqueue-contact-creation.service.ts | 24 +++++++++++-- 8 files changed, 116 insertions(+), 11 deletions(-) diff --git a/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts b/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts index b75c3d651..2e5365212 100644 --- a/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts +++ b/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts @@ -50,6 +50,11 @@ export const seedFeatureFlags = async ( workspaceId: workspaceId, value: true, }, + { + key: FeatureFlagKeys.IsContactCreationForSentAndReceivedEmailsEnabled, + workspaceId: workspaceId, + value: true, + }, ]) .execute(); }; diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts index b4df67d20..03618d83b 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts @@ -23,6 +23,7 @@ export enum FeatureFlagKeys { IsStripeIntegrationEnabled = 'IS_STRIPE_INTEGRATION_ENABLED', IsGmailSyncV2Enabled = 'IS_GMAIL_SYNC_V2_ENABLED', IsLinksFieldEnabled = 'IS_LINKS_FIELD_ENABLED', + IsContactCreationForSentAndReceivedEmailsEnabled = 'IS_CONTACT_CREATION_FOR_SENT_AND_RECEIVED_EMAILS_ENABLED', } @Entity({ name: 'featureFlag', schema: 'core' }) diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/add-standard-id.command.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/add-standard-id.command.ts index 8a4043c14..89e3136a9 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/add-standard-id.command.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/add-standard-id.command.ts @@ -60,6 +60,7 @@ export class AddStandardIdCommand extends CommandRunner { IS_STRIPE_INTEGRATION_ENABLED: false, IS_GMAIL_SYNC_V2_ENABLED: true, IS_LINKS_FIELD_ENABLED: true, + IS_CONTACT_CREATION_FOR_SENT_AND_RECEIVED_EMAILS_ENABLED: true, }, ); const standardFieldMetadataCollection = this.standardFieldFactory.create( @@ -76,6 +77,7 @@ export class AddStandardIdCommand extends CommandRunner { IS_STRIPE_INTEGRATION_ENABLED: false, IS_GMAIL_SYNC_V2_ENABLED: true, IS_LINKS_FIELD_ENABLED: true, + IS_CONTACT_CREATION_FOR_SENT_AND_RECEIVED_EMAILS_ENABLED: true, }, ); diff --git a/packages/twenty-server/src/modules/messaging/jobs/messaging-create-company-and-contact-after-sync.job.ts b/packages/twenty-server/src/modules/messaging/jobs/messaging-create-company-and-contact-after-sync.job.ts index 27e3b5728..a51ad8c0f 100644 --- a/packages/twenty-server/src/modules/messaging/jobs/messaging-create-company-and-contact-after-sync.job.ts +++ b/packages/twenty-server/src/modules/messaging/jobs/messaging-create-company-and-contact-after-sync.job.ts @@ -1,7 +1,14 @@ import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { Repository } from 'typeorm'; import { MessageQueueJob } from 'src/engine/integrations/message-queue/interfaces/message-queue-job.interface'; +import { + FeatureFlagEntity, + FeatureFlagKeys, +} from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; import { CreateCompanyAndContactService } from 'src/modules/connected-account/auto-companies-and-contacts-creation/services/create-company-and-contact.service'; import { MessageChannelRepository } from 'src/modules/messaging/repositories/message-channel.repository'; @@ -27,6 +34,8 @@ export class MessagingCreateCompanyAndContactAfterSyncJob private readonly messageChannelService: MessageChannelRepository, @InjectObjectMetadataRepository(MessageParticipantWorkspaceEntity) private readonly messageParticipantRepository: MessageParticipantRepository, + @InjectRepository(FeatureFlagEntity, 'core') + private readonly featureFlagRepository: Repository, ) {} async handle( @@ -48,11 +57,25 @@ export class MessagingCreateCompanyAndContactAfterSyncJob return; } - const contactsToCreate = - await this.messageParticipantRepository.getByMessageChannelIdWithoutPersonIdAndWorkspaceMemberIdAndMessageOutgoing( - messageChannelId, - workspaceId, - ); + const isContactCreationForSentAndReceivedEmailsEnabledFeatureFlag = + await this.featureFlagRepository.findOneBy({ + workspaceId: workspaceId, + key: FeatureFlagKeys.IsContactCreationForSentAndReceivedEmailsEnabled, + value: true, + }); + + const isContactCreationForSentAndReceivedEmailsEnabled = + isContactCreationForSentAndReceivedEmailsEnabledFeatureFlag?.value; + + const contactsToCreate = isContactCreationForSentAndReceivedEmailsEnabled + ? await this.messageParticipantRepository.getByMessageChannelIdWithoutPersonIdAndWorkspaceMemberId( + messageChannelId, + workspaceId, + ) + : await this.messageParticipantRepository.getByMessageChannelIdWithoutPersonIdAndWorkspaceMemberIdAndMessageOutgoing( + messageChannelId, + workspaceId, + ); await this.createCompanyAndContactService.createCompaniesAndContactsAndUpdateParticipants( handle, diff --git a/packages/twenty-server/src/modules/messaging/repositories/message-participant.repository.ts b/packages/twenty-server/src/modules/messaging/repositories/message-participant.repository.ts index 63a3e90f5..4ae92c68a 100644 --- a/packages/twenty-server/src/modules/messaging/repositories/message-participant.repository.ts +++ b/packages/twenty-server/src/modules/messaging/repositories/message-participant.repository.ts @@ -131,6 +131,41 @@ export class MessageParticipantRepository { return messageParticipants; } + public async getByMessageChannelIdWithoutPersonIdAndWorkspaceMemberId( + messageChannelId: string, + workspaceId: string, + transactionManager?: EntityManager, + ): Promise { + if (!messageChannelId || !workspaceId) { + return []; + } + + const dataSourceSchema = + this.workspaceDataSourceService.getSchemaName(workspaceId); + + const messageParticipants: ParticipantWithId[] = + await this.workspaceDataSourceService.executeRawQuery( + `SELECT "messageParticipant".id, + "messageParticipant"."role", + "messageParticipant"."handle", + "messageParticipant"."displayName", + "messageParticipant"."personId", + "messageParticipant"."workspaceMemberId", + "messageParticipant"."messageId" + FROM ${dataSourceSchema}."messageParticipant" "messageParticipant" + LEFT JOIN ${dataSourceSchema}."message" ON "messageParticipant"."messageId" = ${dataSourceSchema}."message"."id" + LEFT JOIN ${dataSourceSchema}."messageChannelMessageAssociation" ON ${dataSourceSchema}."messageChannelMessageAssociation"."messageId" = ${dataSourceSchema}."message"."id" + WHERE ${dataSourceSchema}."messageChannelMessageAssociation"."messageChannelId" = $1 + AND "messageParticipant"."personId" IS NULL + AND "messageParticipant"."workspaceMemberId" IS NULL`, + [messageChannelId], + workspaceId, + transactionManager, + ); + + return messageParticipants; + } + public async getWithoutPersonIdAndWorkspaceMemberId( workspaceId: string, transactionManager?: EntityManager, diff --git a/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/gmail-messages-import.module.ts b/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/gmail-messages-import.module.ts index 6ceabf3e2..12b73f1e7 100644 --- a/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/gmail-messages-import.module.ts +++ b/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/gmail-messages-import.module.ts @@ -1,5 +1,7 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; @@ -24,6 +26,7 @@ import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/standard-ob MessageModule, MessageParticipantModule, SetMessageChannelSyncStatusModule, + TypeOrmModule.forFeature([FeatureFlagEntity], 'core'), ], providers: [ GmailMessagesImportService, diff --git a/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/gmail-messages-import.service.ts b/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/gmail-messages-import.service.ts index c59721c0f..788d65391 100644 --- a/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/gmail-messages-import.service.ts +++ b/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/gmail-messages-import.service.ts @@ -1,6 +1,7 @@ import { Inject, Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; -import { EntityManager } from 'typeorm'; +import { EntityManager, Repository } from 'typeorm'; import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository'; @@ -31,6 +32,10 @@ import { CreateCompanyAndContactJobData, CreateCompanyAndContactJob, } from 'src/modules/connected-account/auto-companies-and-contacts-creation/jobs/create-company-and-contact.job'; +import { + FeatureFlagEntity, + FeatureFlagKeys, +} from 'src/engine/core-modules/feature-flag/feature-flag.entity'; @Injectable() export class GmailMessagesImportService { @@ -49,6 +54,8 @@ export class GmailMessagesImportService { private readonly messageQueueService: MessageQueueService, private readonly messageService: MessageService, private readonly messageParticipantService: MessageParticipantService, + @InjectRepository(FeatureFlagEntity, 'core') + private readonly featureFlagRepository: Repository, ) {} async fetchMessageContentFromCache( @@ -171,6 +178,16 @@ export class GmailMessagesImportService { const messageQueries = createQueriesFromMessageIds(messageIdsToFetch); + const isContactCreationForSentAndReceivedEmailsEnabledFeatureFlag = + await this.featureFlagRepository.findOneBy({ + workspaceId: workspaceId, + key: FeatureFlagKeys.IsContactCreationForSentAndReceivedEmailsEnabled, + value: true, + }); + + const isContactCreationForSentAndReceivedEmailsEnabled = + isContactCreationForSentAndReceivedEmailsEnabledFeatureFlag?.value; + try { const messagesToSave = await this.fetchMessagesByBatchesService.fetchAllMessages( @@ -214,8 +231,9 @@ export class GmailMessagesImportService { messageId, shouldCreateContact: gmailMessageChannel.isContactAutoCreationEnabled && - message.participants.find((p) => p.role === 'from') - ?.handle === connectedAccount.handle, + (isContactCreationForSentAndReceivedEmailsEnabled || + message.participants.find((p) => p.role === 'from') + ?.handle === connectedAccount.handle), })) : []; }); diff --git a/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/save-messages-and-enqueue-contact-creation.service.ts b/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/save-messages-and-enqueue-contact-creation.service.ts index fc99e3de2..63c07ba1a 100644 --- a/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/save-messages-and-enqueue-contact-creation.service.ts +++ b/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/save-messages-and-enqueue-contact-creation.service.ts @@ -1,6 +1,7 @@ import { Injectable, Inject } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; -import { EntityManager } from 'typeorm'; +import { EntityManager, Repository } from 'typeorm'; import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/integrations/message-queue/services/message-queue.service'; @@ -18,6 +19,10 @@ import { CreateCompanyAndContactJobData, CreateCompanyAndContactJob, } from 'src/modules/connected-account/auto-companies-and-contacts-creation/jobs/create-company-and-contact.job'; +import { + FeatureFlagEntity, + FeatureFlagKeys, +} from 'src/engine/core-modules/feature-flag/feature-flag.entity'; @Injectable() export class SaveMessagesAndEnqueueContactCreationService { @@ -27,6 +32,8 @@ export class SaveMessagesAndEnqueueContactCreationService { private readonly messageQueueService: MessageQueueService, private readonly messageService: MessageService, private readonly messageParticipantService: MessageParticipantService, + @InjectRepository(FeatureFlagEntity, 'core') + private readonly featureFlagRepository: Repository, ) {} async saveMessagesAndEnqueueContactCreationJob( @@ -40,6 +47,16 @@ export class SaveMessagesAndEnqueueContactCreationService { workspaceId, ); + const isContactCreationForSentAndReceivedEmailsEnabledFeatureFlag = + await this.featureFlagRepository.findOneBy({ + workspaceId: workspaceId, + key: FeatureFlagKeys.IsContactCreationForSentAndReceivedEmailsEnabled, + value: true, + }); + + const isContactCreationForSentAndReceivedEmailsEnabled = + isContactCreationForSentAndReceivedEmailsEnabledFeatureFlag?.value; + const participantsWithMessageId = await workspaceDataSource?.transaction( async (transactionManager: EntityManager) => { const messageExternalIdsAndIdsMap = @@ -62,8 +79,9 @@ export class SaveMessagesAndEnqueueContactCreationService { messageId, shouldCreateContact: messageChannel.isContactAutoCreationEnabled && - message.participants.find((p) => p.role === 'from') - ?.handle === connectedAccount.handle, + (isContactCreationForSentAndReceivedEmailsEnabled || + message.participants.find((p) => p.role === 'from') + ?.handle === connectedAccount.handle), })) : []; });