From b55c46f4a3765aa371bb9ce326c67b25c866917d Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Sat, 9 Aug 2025 03:08:11 +0200 Subject: [PATCH] Few fixes before release 1.3.0 (#13791) --- .../SettingsOptionCardContentSelect.tsx | 2 + .../SettingsDataModelFieldIsUniqueForm.tsx | 2 +- ...SettingsDataModelFieldSettingsFormCard.tsx | 2 +- ...gsDataModelFieldSelectSettingsFormCard.tsx | 4 - .../fields/forms/utils/canBeUnique.util.ts | 4 + .../utils/fetch-metadata-fields.utils.ts | 1 + .../hooks/before-update-one-field.hook.ts | 2 +- .../services/field-metadata.service.ts | 6 + .../twenty-orm/utils/format-data.util.ts | 2 +- .../gmail-get-message-list.service.ts | 10 +- .../microsoft-get-message-list.service.ts | 8 +- ...es-and-enqueue-contact-creation.service.ts | 127 +++++++++--------- .../utils/filter-emails.util.ts | 21 +-- 13 files changed, 106 insertions(+), 85 deletions(-) diff --git a/packages/twenty-front/src/modules/settings/components/SettingsOptions/SettingsOptionCardContentSelect.tsx b/packages/twenty-front/src/modules/settings/components/SettingsOptions/SettingsOptionCardContentSelect.tsx index 58f359bfbbd..41f1c7c679d 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsOptions/SettingsOptionCardContentSelect.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsOptions/SettingsOptionCardContentSelect.tsx @@ -17,7 +17,9 @@ type SettingsOptionCardContentSelectProps = { }; const StyledSelectContainer = styled.div` + justify-content: flex-end; margin-left: auto; + max-width: 120px; `; export const SettingsOptionCardContentSelect = ({ diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldIsUniqueForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldIsUniqueForm.tsx index 1ba76a92357..6160b9507c9 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldIsUniqueForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldIsUniqueForm.tsx @@ -41,7 +41,7 @@ export const SettingsDataModelFieldIsUniqueForm = ({ - } /> diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/utils/canBeUnique.util.ts b/packages/twenty-front/src/modules/settings/data-model/fields/forms/utils/canBeUnique.util.ts index fa57b658516..eec3ad8b639 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/utils/canBeUnique.util.ts +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/utils/canBeUnique.util.ts @@ -6,6 +6,10 @@ import { FieldMetadataType } from 'twenty-shared/types'; export const canBeUnique = ( field: Pick, ) => { + if (field.isCustom === false) { + return false; + } + if ( [FieldMetadataType.MORPH_RELATION, FieldMetadataType.RELATION].includes( field.type, diff --git a/packages/twenty-server/src/engine/api/rest/metadata/query-builder/utils/fetch-metadata-fields.utils.ts b/packages/twenty-server/src/engine/api/rest/metadata/query-builder/utils/fetch-metadata-fields.utils.ts index 7f31b503f90..58328087564 100644 --- a/packages/twenty-server/src/engine/api/rest/metadata/query-builder/utils/fetch-metadata-fields.utils.ts +++ b/packages/twenty-server/src/engine/api/rest/metadata/query-builder/utils/fetch-metadata-fields.utils.ts @@ -6,6 +6,7 @@ export const fetchMetadataFields = ( selector: Selectors, ) => { const defaultFields = ` + id type name label diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/hooks/before-update-one-field.hook.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/hooks/before-update-one-field.hook.ts index 1d21d182467..4aa0c19f2f5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/hooks/before-update-one-field.hook.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/hooks/before-update-one-field.hook.ts @@ -5,7 +5,7 @@ import { type BeforeUpdateOneHook, type UpdateOneInputType, } from '@ptc-org/nestjs-query-graphql'; -import { type APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations'; +import { SOURCE_LOCALE, type APP_LOCALES } from 'twenty-shared/translations'; import { isDefined } from 'twenty-shared/utils'; import { diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts index c967add6ec2..24ab6dbc1a7 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts @@ -188,6 +188,9 @@ export class FieldMetadataService extends TypeOrmQueryService( if (!fieldMetadata) { throw new Error( - `Field metadata for field "${key}" is missing in object metadata`, + `Field metadata for field "${key}" is missing in object metadata ${objectMetadataItemWithFieldMaps.nameSingular}`, ); } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-message-list.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-message-list.service.ts index d6502520ea1..8d8031e0db0 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-message-list.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-message-list.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { isNonEmptyString } from '@sniptt/guards'; import { type gmail_v1 as gmailV1 } from 'googleapis'; @@ -24,6 +24,7 @@ import { assertNotNull } from 'src/utils/assert'; @Injectable() export class GmailGetMessageListService { + private readonly logger = new Logger(GmailGetMessageListService.name); constructor( private readonly gmailClientProvider: GmailClientProvider, private readonly gmailGetHistoryService: GmailGetHistoryService, @@ -55,6 +56,13 @@ export class GmailGetMessageListService { q: computeGmailCategoryExcludeSearchFilter(excludedCategories), }) .catch((error) => { + this.logger.error( + `Connected account ${connectedAccount.id}: Error fetching message list: ${error.message}`, + ); + this.logger.error( + `Connected account ${connectedAccount.id}: Error fetching message list: ${JSON.stringify(error)}`, + ); + this.gmailHandleErrorService.handleGmailMessageListFetchError(error); return { diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.ts index 77ffb6c5a69..e4ac4a0f22a 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.ts @@ -1,8 +1,8 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { - type PageCollection, PageIterator, + type PageCollection, type PageIteratorCallback, } from '@microsoft/microsoft-graph-client'; import { isNonEmptyString } from '@sniptt/guards'; @@ -30,6 +30,7 @@ const MESSAGING_MICROSOFT_USERS_MESSAGES_LIST_MAX_RESULT = 999; @Injectable() export class MicrosoftGetMessageListService { + private readonly logger = new Logger(MicrosoftGetMessageListService.name); constructor( private readonly microsoftClientProvider: MicrosoftClientProvider, private readonly microsoftHandleErrorService: MicrosoftHandleErrorService, @@ -123,6 +124,9 @@ export class MicrosoftGetMessageListService { }) .get() .catch((error) => { + this.logger.error( + `Connected account ${connectedAccount.id}: Error fetching message list: ${JSON.stringify(error)}`, + ); if (isAccessTokenRefreshingError(error?.body)) { throw new MessageImportDriverException( error.message, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts index 9e9af456dec..a6f45b33eab 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts @@ -45,76 +45,71 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService { const workspaceDataSource = await this.twentyORMManager.getDatasource(); - const createdMessagesWithParticipants = - await workspaceDataSource?.transaction( - async (transactionManager: WorkspaceEntityManager) => { - const { messageExternalIdsAndIdsMap, createdMessages } = - await this.messageService.saveMessagesWithinTransaction( - messagesToSave, - messageChannel.id, - transactionManager, - workspaceId, - ); - - const participantsWithMessageId: (ParticipantWithMessageId & { - shouldCreateContact: boolean; - })[] = messagesToSave.flatMap((message) => { - const messageId = messageExternalIdsAndIdsMap.get( - message.externalId, - ); - - return messageId - ? message.participants.map((participant: Participant) => { - const fromHandle = - message.participants.find((p) => p.role === 'from') - ?.handle || ''; - - const isMessageSentByConnectedAccount = - handleAliases.includes(fromHandle) || - fromHandle === connectedAccount.handle; - - const isParticipantConnectedAccount = - handleAliases.includes(participant.handle) || - participant.handle === connectedAccount.handle; - - const isExcludedByNonProfessionalEmails = - messageChannel.excludeNonProfessionalEmails && - !isWorkEmail(participant.handle); - - const isExcludedByGroupEmails = - messageChannel.excludeGroupEmails && - isGroupEmail(participant.handle); - - const shouldCreateContact = - !!participant.handle && - !isParticipantConnectedAccount && - !isExcludedByNonProfessionalEmails && - !isExcludedByGroupEmails && - (messageChannel.contactAutoCreationPolicy === - MessageChannelContactAutoCreationPolicy.SENT_AND_RECEIVED || - (messageChannel.contactAutoCreationPolicy === - MessageChannelContactAutoCreationPolicy.SENT && - isMessageSentByConnectedAccount)); - - return { - ...participant, - messageId, - shouldCreateContact, - }; - }) - : []; - }); - - await this.messageParticipantService.saveMessageParticipants( - participantsWithMessageId, + const participantsWithMessageId = await workspaceDataSource?.transaction( + async (transactionManager: WorkspaceEntityManager) => { + const { messageExternalIdsAndIdsMap } = + await this.messageService.saveMessagesWithinTransaction( + messagesToSave, + messageChannel.id, transactionManager, + workspaceId, ); - return { participantsWithMessageId, createdMessages }; - }, - ); + const participantsWithMessageId: (ParticipantWithMessageId & { + shouldCreateContact: boolean; + })[] = messagesToSave.flatMap((message) => { + const messageId = messageExternalIdsAndIdsMap.get(message.externalId); - const { participantsWithMessageId } = createdMessagesWithParticipants; + return messageId + ? message.participants.map((participant: Participant) => { + const fromHandle = + message.participants.find((p) => p.role === 'from')?.handle || + ''; + + const isMessageSentByConnectedAccount = + handleAliases.includes(fromHandle) || + fromHandle === connectedAccount.handle; + + const isParticipantConnectedAccount = + handleAliases.includes(participant.handle) || + participant.handle === connectedAccount.handle; + + const isExcludedByNonProfessionalEmails = + messageChannel.excludeNonProfessionalEmails && + !isWorkEmail(participant.handle); + + const isExcludedByGroupEmails = + messageChannel.excludeGroupEmails && + isGroupEmail(participant.handle); + + const shouldCreateContact = + !!participant.handle && + !isParticipantConnectedAccount && + !isExcludedByNonProfessionalEmails && + !isExcludedByGroupEmails && + (messageChannel.contactAutoCreationPolicy === + MessageChannelContactAutoCreationPolicy.SENT_AND_RECEIVED || + (messageChannel.contactAutoCreationPolicy === + MessageChannelContactAutoCreationPolicy.SENT && + isMessageSentByConnectedAccount)); + + return { + ...participant, + messageId, + shouldCreateContact, + }; + }) + : []; + }); + + await this.messageParticipantService.saveMessageParticipants( + participantsWithMessageId, + transactionManager, + ); + + return participantsWithMessageId; + }, + ); if (messageChannel.isContactAutoCreationEnabled) { const contactsToCreate = participantsWithMessageId.filter( diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-emails.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-emails.util.ts index 182f5c898d6..f80c2154002 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-emails.util.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-emails.util.ts @@ -68,15 +68,20 @@ const filterOutInternals = ( } const primaryHandleDomain = getDomainNameByEmail(primaryHandle); - const isAllHandlesFromSameDomain = message.participants - .filter((participant) => !!participant.handle) - .every( - (participant) => - getDomainNameByEmail(participant.handle) === primaryHandleDomain, - ); - if (isAllHandlesFromSameDomain) { - return false; + try { + const isAllHandlesFromSameDomain = message.participants + .filter((participant) => !!participant.handle) + .every( + (participant) => + getDomainNameByEmail(participant.handle) === primaryHandleDomain, + ); + + if (isAllHandlesFromSameDomain) { + return false; + } + } catch { + return true; } return true;