Finalize twenty standard app as workspace migration object and fields (#16353)

# Introduction
Related to https://github.com/twentyhq/core-team-issues/issues/1995
In this PR we're fixing the remaining object/fields validation errors
resulting from standard objects and fields now passing a validation that
wasn't when using the sync metadata

## Key Changes
- **Field naming**: Renamed `iCalUID` to `iCalUid` for consistent
camelCase convention across calendar events
- **Enum standardization**: Uppercased enum values for message channels
(email→EMAIL), message participants (from→FROM, to→TO, cc→CC, bcc→BCC),
and message direction (incoming→INCOMING, outgoing→OUTGOING)
- **Label simplification**: Removed example values from workspace member
number format labels for cleaner UI
- **Migration infrastructure**: Added `isSystemBuild` flag throughout
field metadata service pipeline to allow system-level updates of
standard fields that bypass normal restrictions

## Migrating the existing data
We've created an upgrade command that will identify using the existing
object and field standard id field that needs to be updated, even though
the sync metadata still in usage could have fix them ( and the goal is
to deprecate it by the end of the sprint )
We will call the updateOneField for each of them, we're passing by the
field service in order to battle test what are going to be the temporary
way to handle standard migrations when we will start deprecating the
sync metadata but haven't still refactored the v2 workspace migration to
be workspace agnostic

## Twenty eng migration
Tested the whole migration + upgrade on twenty eng
Here are generated workspace migration
Records are handled natively gracefully too

### ICalUid
```json
{
  "status": "success",
  "workspaceMigration": {
    "relatedFlatEntityMapsKeys": [
      "flatFieldMetadataMaps",
      "flatIndexMaps",
      "flatViewFilterMaps",
      "flatViewGroupMaps",
      "flatViewMaps",
      "flatViewFieldMaps",
      "flatObjectMetadataMaps"
    ],
    "actions": [
      {
        "type": "update_field",
        "fieldMetadataId": "",
        "objectMetadataId": "",
        "updates": [
          {
            "from": "iCalUID",
            "to": "iCalUid",
            "property": "name"
          }
        ]
      }
    ],
    "workspaceId": ""
  }
}
```

### Incoming Outgoing
None as already caps in database somehow
```json
{
  "status": "success",
  "workspaceMigration": {
    "relatedFlatEntityMapsKeys": [
      "flatFieldMetadataMaps",
      "flatIndexMaps",
      "flatViewFilterMaps",
      "flatViewGroupMaps",
      "flatViewMaps",
      "flatViewFieldMaps",
      "flatObjectMetadataMaps"
    ],
    "actions": [],
    "workspaceId": ""
  }
}
```

### EMAIL
```json
{
  "status": "success",
  "workspaceMigration": {
    "relatedFlatEntityMapsKeys": [
      "flatFieldMetadataMaps",
      "flatIndexMaps",
      "flatViewFilterMaps",
      "flatViewGroupMaps",
      "flatViewMaps",
      "flatViewFieldMaps",
      "flatObjectMetadataMaps"
    ],
    "actions": [
      {
        "type": "update_field",
        "fieldMetadataId": "",
        "objectMetadataId": "",
        "updates": [
          {
            "from": "'email'",
            "to": "'EMAIL'",
            "property": "defaultValue"
          },
          {
            "from": [
              {
                "color": "green",
                "id": "",
                "label": "Email",
                "position": 0,
                "value": "email"
              },
              {
                "color": "blue",
                "id": "",
                "label": "SMS",
                "position": 1,
                "value": "sms"
              }
            ],
            "to": [
              {
                "color": "green",
                "id": "",
                "label": "Email",
                "position": 0,
                "value": "EMAIL"
              },
              {
                "color": "blue",
                "id": "",
                "label": "SMS",
                "position": 1,
                "value": "SMS"
              }
            ],
            "property": "options"
          }
        ]
      }
    ],
    "workspaceId": "e"
  }
}
```

### MessageParticipantRole
```json
{
  "status": "success",
  "workspaceMigration": {
    "relatedFlatEntityMapsKeys": [
      "flatFieldMetadataMaps",
      "flatIndexMaps",
      "flatViewFilterMaps",
      "flatViewGroupMaps",
      "flatViewMaps",
      "flatViewFieldMaps",
      "flatObjectMetadataMaps"
    ],
    "actions": [
      {
        "type": "update_field",
        "fieldMetadataId": "",
        "objectMetadataId": "",
        "updates": [
          {
            "from": "'from'",
            "to": "'FROM'",
            "property": "defaultValue"
          },
          {
            "from": [
              {
                "color": "green",
                "id": "",
                "label": "From",
                "position": 0,
                "value": "from"
              },
              {
                "color": "blue",
                "id": "",
                "label": "To",
                "position": 1,
                "value": "to"
              },
              {
                "color": "orange",
                "id": "",
                "label": "Cc",
                "position": 2,
                "value": "cc"
              },
              {
                "color": "red",
                "id": "",
                "label": "Bcc",
                "position": 3,
                "value": "bcc"
              }
            ],
            "to": [
              {
                "color": "green",
                "id": "",
                "label": "From",
                "position": 0,
                "value": "FROM"
              },
              {
                "color": "blue",
                "id": "",
                "label": "To",
                "position": 1,
                "value": "TO"
              },
              {
                "color": "orange",
                "id": "",
                "label": "Cc",
                "position": 2,
                "value": "CC"
              },
              {
                "color": "red",
                "id": "",
                "label": "Bcc",
                "position": 3,
                "value": "BCC"
              }
            ],
            "property": "options"
          }
        ]
      }
    ],
    "workspaceId": ""
  }
}
```

### Workspace member number format labels
```json
{
  "status": "success",
  "workspaceMigration": {
    "relatedFlatEntityMapsKeys": [
      "flatFieldMetadataMaps",
      "flatIndexMaps",
      "flatViewFilterMaps",
      "flatViewGroupMaps",
      "flatViewMaps",
      "flatViewFieldMaps",
      "flatObjectMetadataMaps"
    ],
    "actions": [
      {
        "type": "update_field",
        "fieldMetadataId": "",
        "objectMetadataId": "",
        "updates": [
          {
            "from": [
              {
                "color": "turquoise",
                "id": "",
                "label": "System",
                "position": 0,
                "value": "SYSTEM"
              },
              {
                "color": "blue",
                "id": "",
                "label": "Commas and dot (1,234.56)",
                "position": 1,
                "value": "COMMAS_AND_DOT"
              },
              {
                "color": "green",
                "id": "",
                "label": "Spaces and comma (1 234,56)",
                "position": 2,
                "value": "SPACES_AND_COMMA"
              },
              {
                "color": "orange",
                "id": "",
                "label": "Dots and comma (1.234,56)",
                "position": 3,
                "value": "DOTS_AND_COMMA"
              },
              {
                "color": "purple",
                "id": "",
                "label": "Apostrophe and dot (1'234.56)",
                "position": 4,
                "value": "APOSTROPHE_AND_DOT"
              }
            ],
            "to": [
              {
                "color": "turquoise",
                "id": "",
                "label": "System",
                "position": 0,
                "value": "SYSTEM"
              },
              {
                "color": "blue",
                "id": "",
                "label": "Commas and dot",
                "position": 1,
                "value": "COMMAS_AND_DOT"
              },
              {
                "color": "green",
                "id": "",
                "label": "Spaces and comma",
                "position": 2,
                "value": "SPACES_AND_COMMA"
              },
              {
                "color": "orange",
                "id": "",
                "label": "Dots and comma",
                "position": 3,
                "value": "DOTS_AND_COMMA"
              },
              {
                "color": "purple",
                "id": "",
                "label": "Apostrophe and dot",
                "position": 4,
                "value": "APOSTROPHE_AND_DOT"
              }
            ],
            "property": "options"
          }
        ]
      }
    ],
    "workspaceId": ""
  }
}
```
This commit is contained in:
Paul Rastoin
2025-12-09 14:09:48 +01:00
committed by GitHub
parent be91d870f9
commit 4996f3dd28
69 changed files with 807 additions and 193 deletions
@@ -2,6 +2,7 @@ import { Scope } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { And, Any, ILike, In, Not, Or } from 'typeorm';
import { MessageParticipantRole } from 'twenty-shared/types';
import { type ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
@@ -87,7 +88,10 @@ export class BlocklistItemDeleteMessagesJob {
continue;
}
const rolesToDelete: ('from' | 'to')[] = ['from', 'to'];
const rolesToDelete = [
MessageParticipantRole.FROM,
MessageParticipantRole.TO,
] as const;
const messageChannels = await messageChannelRepository.find({
select: {
@@ -47,8 +47,8 @@ export enum MessageChannelVisibility {
}
export enum MessageChannelType {
EMAIL = 'email',
SMS = 'sms',
EMAIL = 'EMAIL',
SMS = 'SMS',
}
export enum MessageChannelContactAutoCreationPolicy {
@@ -1,6 +1,10 @@
import { msg } from '@lingui/core/macro';
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
import {
FieldMetadataType,
MessageParticipantRole,
RelationOnDeleteAction,
} from 'twenty-shared/types';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
@@ -39,14 +43,34 @@ export class MessageParticipantWorkspaceEntity extends BaseWorkspaceEntity {
description: msg`Role`,
icon: 'IconAt',
options: [
{ value: 'from', label: 'From', position: 0, color: 'green' },
{ value: 'to', label: 'To', position: 1, color: 'blue' },
{ value: 'cc', label: 'Cc', position: 2, color: 'orange' },
{ value: 'bcc', label: 'Bcc', position: 3, color: 'red' },
{
value: MessageParticipantRole.FROM,
label: 'From',
position: 0,
color: 'green',
},
{
value: MessageParticipantRole.TO,
label: 'To',
position: 1,
color: 'blue',
},
{
value: MessageParticipantRole.CC,
label: 'Cc',
position: 2,
color: 'orange',
},
{
value: MessageParticipantRole.BCC,
label: 'Bcc',
position: 3,
color: 'red',
},
],
defaultValue: "'from'",
defaultValue: `'${MessageParticipantRole.FROM}'`,
})
role: string;
role: MessageParticipantRole;
@WorkspaceField({
standardId: MESSAGE_PARTICIPANT_STANDARD_FIELD_IDS.handle,
@@ -1,3 +1,5 @@
import { type MessageParticipantRole } from 'twenty-shared/types';
export type GmailMessage = {
historyId: string;
externalId: string;
@@ -13,7 +15,7 @@ export type GmailMessage = {
};
export type Participant = {
role: 'from' | 'to' | 'cc' | 'bcc';
role: MessageParticipantRole;
handle: string;
displayName: string;
};
@@ -1,5 +1,6 @@
import { type gmail_v1 as gmailV1 } from 'googleapis';
import planer from 'planer';
import { MessageParticipantRole } from 'twenty-shared/types';
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { computeMessageDirection } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/compute-message-direction.util';
@@ -43,17 +44,28 @@ export const parseAndFormatGmailMessage = (
const participants = [
...(from
? formatAddressObjectAsParticipants([{ address: from }], 'from')
? formatAddressObjectAsParticipants(
[{ address: from }],
MessageParticipantRole.FROM,
)
: []),
...(toParticipants
? formatAddressObjectAsParticipants(
[{ address: toParticipants, name: '' }],
'to',
MessageParticipantRole.TO,
)
: []),
...(cc
? formatAddressObjectAsParticipants(
[{ address: cc }],
MessageParticipantRole.CC,
)
: []),
...(cc ? formatAddressObjectAsParticipants([{ address: cc }], 'cc') : []),
...(bcc
? formatAddressObjectAsParticipants([{ address: bcc }], 'bcc')
? formatAddressObjectAsParticipants(
[{ address: bcc }],
MessageParticipantRole.BCC,
)
: []),
];
@@ -2,6 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
import { type ImapFlow } from 'imapflow';
import { Address, type Email as ParsedMail } from 'postal-mime';
import { MessageParticipantRole } from 'twenty-shared/types';
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { computeMessageDirection } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/compute-message-direction.util';
@@ -203,11 +204,11 @@ export class ImapGetMessagesService {
private extractParticipants(parsed: ParsedMail) {
const addressFields = [
{ field: parsed.from, role: 'from' as const },
{ field: parsed.to, role: 'to' as const },
{ field: parsed.cc, role: 'cc' as const },
{ field: parsed.bcc, role: 'bcc' as const },
];
{ field: parsed.from, role: MessageParticipantRole.FROM },
{ field: parsed.to, role: MessageParticipantRole.TO },
{ field: parsed.cc, role: MessageParticipantRole.CC },
{ field: parsed.bcc, role: MessageParticipantRole.BCC },
] as const;
return addressFields.flatMap(({ field, role }) =>
formatAddressObjectAsParticipants(this.extractAddresses(field), role),
@@ -2,7 +2,10 @@ import { Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Test, type TestingModule } from '@nestjs/testing';
import { ConnectedAccountProvider } from 'twenty-shared/types';
import {
ConnectedAccountProvider,
MessageParticipantRole,
} from 'twenty-shared/types';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { GoogleOAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/drivers/google/google-oauth2-client-manager.service';
@@ -97,12 +100,12 @@ describe('Microsoft get messages service', () => {
{
displayName: 'John l',
handle: 'john.l@outlook.fr',
role: 'from',
role: MessageParticipantRole.FROM,
},
{
displayName: 'Walker',
handle: 'walker@felixacme.onmicrosoft.com',
role: 'to',
role: MessageParticipantRole.TO,
},
],
attachments: [],
@@ -130,22 +133,22 @@ describe('Microsoft get messages service', () => {
{
displayName: 'Microsoft',
handle: 'microsoft-noreply@microsoft.com',
role: 'from',
role: MessageParticipantRole.FROM,
},
{
displayName: 'Walker',
handle: 'walker@felixacme.onmicrosoft.com',
role: 'to',
role: MessageParticipantRole.TO,
},
{
displayName: 'Antoine',
handle: 'antoine@gmail.com',
role: 'cc',
role: MessageParticipantRole.CC,
},
{
displayName: 'Cyril@acme2.com',
handle: 'cyril@acme2.com',
role: 'cc',
role: MessageParticipantRole.CC,
},
],
attachments: [],
@@ -184,7 +187,7 @@ describe('Microsoft get messages service', () => {
displayName: responseExample.body.sender.emailAddress.name,
handle:
responseExample.body.sender.emailAddress.address.toLowerCase(),
role: 'from',
role: MessageParticipantRole.FROM,
},
],
attachments: [],
@@ -2,6 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
import { type EmailAddress } from 'addressparser';
import { isDefined } from 'twenty-shared/utils';
import { MessageParticipantRole } from 'twenty-shared/types';
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum';
@@ -109,16 +110,28 @@ export class MicrosoftGetMessagesService {
const participants = [
...(safeParseFrom
? formatAddressObjectAsParticipants(safeParseFrom, 'from')
? formatAddressObjectAsParticipants(
safeParseFrom,
MessageParticipantRole.FROM,
)
: []),
...(safeParseTo
? formatAddressObjectAsParticipants(safeParseTo, 'to')
? formatAddressObjectAsParticipants(
safeParseTo,
MessageParticipantRole.TO,
)
: []),
...(safeParseCc
? formatAddressObjectAsParticipants(safeParseCc, 'cc')
? formatAddressObjectAsParticipants(
safeParseCc,
MessageParticipantRole.CC,
)
: []),
...(safeParseBcc
? formatAddressObjectAsParticipants(safeParseBcc, 'bcc')
? formatAddressObjectAsParticipants(
safeParseBcc,
MessageParticipantRole.BCC,
)
: []),
];
@@ -2,6 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
import chunk from 'lodash.chunk';
import { isDefined } from 'twenty-shared/utils';
import { MessageParticipantRole } from 'twenty-shared/types';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity';
@@ -55,7 +56,7 @@ export class MessagingDeleteGroupEmailMessagesService {
'message.messageParticipants',
'participant',
'participant.role = :role',
{ role: 'from' },
{ role: MessageParticipantRole.FROM },
)
.where('mcma.messageChannelId = :messageChannelId', {
messageChannelId,
@@ -1,7 +1,7 @@
import { Test, type TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { FieldActorSource } from 'twenty-shared/types';
import { FieldActorSource, MessageParticipantRole } from 'twenty-shared/types';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
@@ -56,8 +56,16 @@ describe('MessagingSaveMessagesAndEnqueueContactCreationService', () => {
messageThreadExternalId: 'thread-1',
direction: MessageDirection.OUTGOING,
participants: [
{ role: 'from', handle: 'test@example.com', displayName: 'Test User' },
{ role: 'to', handle: 'contact@company.com', displayName: 'Contact' },
{
role: MessageParticipantRole.FROM,
handle: 'test@example.com',
displayName: 'Test User',
},
{
role: MessageParticipantRole.TO,
handle: 'contact@company.com',
displayName: 'Contact',
},
],
},
{
@@ -70,11 +78,23 @@ describe('MessagingSaveMessagesAndEnqueueContactCreationService', () => {
messageThreadExternalId: 'thread-1',
direction: MessageDirection.INCOMING,
participants: [
{ role: 'from', handle: 'contact@company.com', displayName: 'Contact' },
{ role: 'to', handle: 'test@example.com', displayName: 'Test User' },
{ role: 'to', handle: 'personal@gmail.com', displayName: 'Personal' },
{
role: 'to',
role: MessageParticipantRole.FROM,
handle: 'contact@company.com',
displayName: 'Contact',
},
{
role: MessageParticipantRole.TO,
handle: 'test@example.com',
displayName: 'Test User',
},
{
role: MessageParticipantRole.TO,
handle: 'personal@gmail.com',
displayName: 'Personal',
},
{
role: MessageParticipantRole.TO,
handle: 'team@lists.company.com',
displayName: 'Group email',
},
@@ -199,7 +219,7 @@ describe('MessagingSaveMessagesAndEnqueueContactCreationService', () => {
...mockMessages[1],
participants: [
{
role: 'from',
role: MessageParticipantRole.FROM,
handle: 'tim@apple.com',
displayName: 'participant email',
},
@@ -221,7 +241,7 @@ describe('MessagingSaveMessagesAndEnqueueContactCreationService', () => {
{
handle: 'tim@apple.com',
displayName: 'participant email',
role: 'from',
role: MessageParticipantRole.FROM,
shouldCreateContact: true,
messageId: 'db-message-id-2',
},
@@ -237,7 +257,7 @@ describe('MessagingSaveMessagesAndEnqueueContactCreationService', () => {
...mockMessages[0],
participants: [
{
role: 'from',
role: MessageParticipantRole.FROM,
handle: 'test@gmail.com',
displayName: 'participant personal email',
},
@@ -265,7 +285,7 @@ describe('MessagingSaveMessagesAndEnqueueContactCreationService', () => {
...mockMessages[0],
participants: [
{
role: 'from',
role: MessageParticipantRole.FROM,
handle: 'connected@account.com',
displayName: 'participant that is the Connected Account',
},
@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { FieldActorSource } from 'twenty-shared/types';
import { FieldActorSource, MessageParticipantRole } from 'twenty-shared/types';
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
@@ -66,8 +66,9 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService {
return messageId
? message.participants.map((participant: Participant) => {
const fromHandle =
message.participants.find((p) => p.role === 'from')?.handle ||
'';
message.participants.find(
(p) => p.role === MessageParticipantRole.FROM,
)?.handle || '';
const isMessageSentByConnectedAccount =
handleAliases.includes(fromHandle) ||
@@ -1,3 +1,6 @@
import { MessageParticipantRole } from 'twenty-shared/types';
import { MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum';
import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message';
export const messagingGetMessagesServiceGetMessages = [
@@ -8,15 +11,15 @@ export const messagingGetMessagesServiceGetMessages = [
text: 'Just a test',
headerMessageId: '<d45b9f1c@PR0P264MB2911.FRAP264.PROD.OUTLOOK.COM>',
messageThreadExternalId: 'AAQkAGZlMDQ1NjU5Lk=',
direction: 'OUTGOING',
direction: MessageDirection.OUTGOING,
participants: [
{
role: 'from',
role: MessageParticipantRole.FROM,
handle: 'from@acme.com',
displayName: 'From',
},
{
role: 'to',
role: MessageParticipantRole.TO,
handle: 'to@acme.com',
displayName: 'To',
},
@@ -30,15 +33,15 @@ export const messagingGetMessagesServiceGetMessages = [
text: 'Just a test',
headerMessageId: '<d45b9f1c@PR0P264MB2911.FRAP264.PROD.OUTLOOK.COM>',
messageThreadExternalId: 'AAQkAGZlMDQ1NjU5Lk=',
direction: 'OUTGOING',
direction: MessageDirection.OUTGOING,
participants: [
{
role: 'from',
role: MessageParticipantRole.FROM,
handle: 'from@acme.com',
displayName: 'From',
},
{
role: 'to',
role: MessageParticipantRole.TO,
handle: 'to@external.com',
displayName: 'To',
},
@@ -52,19 +55,19 @@ export const messagingGetMessagesServiceGetMessages = [
text: 'Learn with interactive sandboxes, curated collections, and bookmarks',
headerMessageId: '<AC70000@mails.microsoft.com>',
messageThreadExternalId: 'AAQkAGZlMDQ1NNc=',
direction: 'INCOMING',
direction: MessageDirection.INCOMING,
participants: [
{
role: 'from',
role: MessageParticipantRole.FROM,
handle: 'learn@mails.microsoft.com',
displayName: 'Microsoft Learn',
},
{
role: 'to',
role: MessageParticipantRole.TO,
handle: 'to@gmail.com',
displayName: 'To',
},
],
attachments: [],
},
] as MessageWithParticipants[];
] satisfies MessageWithParticipants[];
@@ -1,3 +1,5 @@
import { MessageParticipantRole } from 'twenty-shared/types';
import { MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum';
import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message';
import { messagingGetMessagesServiceGetMessages } from 'src/modules/messaging/message-import-manager/utils/__mocks__/messages';
@@ -64,7 +66,7 @@ describe('filterEmails', () => {
direction: MessageDirection.INCOMING,
participants: [
{
role: 'from',
role: MessageParticipantRole.FROM,
handle: 'noreply@example.com',
displayName: 'No Reply',
},
@@ -81,7 +83,7 @@ describe('filterEmails', () => {
direction: MessageDirection.INCOMING,
participants: [
{
role: 'from',
role: MessageParticipantRole.FROM,
handle: 'support@company.com',
displayName: 'Support Team',
},
@@ -98,7 +100,7 @@ describe('filterEmails', () => {
direction: MessageDirection.INCOMING,
participants: [
{
role: 'from',
role: MessageParticipantRole.FROM,
handle: 'john@example.com',
displayName: 'John Doe',
},
@@ -126,7 +128,7 @@ describe('filterEmails', () => {
direction: MessageDirection.INCOMING,
participants: [
{
role: 'from',
role: MessageParticipantRole.FROM,
handle: 'noreply@example.com',
displayName: 'No Reply',
},
@@ -174,7 +176,7 @@ describe('filterEmails', () => {
direction: MessageDirection.INCOMING,
participants: [
{
role: 'from',
role: MessageParticipantRole.FROM,
handle: 'john.doe@example.com',
displayName: 'John Doe',
},
@@ -1,3 +1,5 @@
import { MessageParticipantRole } from 'twenty-shared/types';
import { formatAddressObjectAsParticipants } from 'src/modules/messaging/message-import-manager/utils/format-address-object-as-participants.util';
describe('formatAddressObjectAsParticipants', () => {
@@ -7,16 +9,19 @@ describe('formatAddressObjectAsParticipants', () => {
{ name: 'Jane Smith', address: 'jane.smith@example.com ' },
];
const result = formatAddressObjectAsParticipants(addresses, 'from');
const result = formatAddressObjectAsParticipants(
addresses,
MessageParticipantRole.FROM,
);
expect(result).toEqual([
{
role: 'from',
role: MessageParticipantRole.FROM,
handle: 'john.doe@example.com',
displayName: 'John Doe',
},
{
role: 'from',
role: MessageParticipantRole.FROM,
handle: 'jane.smith@example.com',
displayName: 'Jane Smith',
},
@@ -29,7 +34,10 @@ describe('formatAddressObjectAsParticipants', () => {
address: 'john.doe',
};
const result = formatAddressObjectAsParticipants([addressObject], 'to');
const result = formatAddressObjectAsParticipants(
[addressObject],
MessageParticipantRole.TO,
);
expect(result).toEqual([]);
});
@@ -40,7 +48,10 @@ describe('formatAddressObjectAsParticipants', () => {
address: '',
};
const result = formatAddressObjectAsParticipants([addressObject], 'to');
const result = formatAddressObjectAsParticipants(
[addressObject],
MessageParticipantRole.TO,
);
expect(result).toEqual([]);
});
@@ -51,11 +62,14 @@ describe('formatAddressObjectAsParticipants', () => {
address: 'John.Doe@example.com',
};
const result = formatAddressObjectAsParticipants([addressObject], 'to');
const result = formatAddressObjectAsParticipants(
[addressObject],
MessageParticipantRole.TO,
);
expect(result).toEqual([
{
role: 'to',
role: MessageParticipantRole.TO,
handle: 'john.doe@example.com',
displayName: 'John Doe',
},
@@ -1,3 +1,5 @@
import { MessageParticipantRole } from 'twenty-shared/types';
import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message';
import { isGroupEmail } from 'src/modules/messaging/message-import-manager/utils/is-group-email';
@@ -8,7 +10,7 @@ export const filterOutGroupEmails = (messages: MessageWithParticipants[]) => {
}
const fromParticipant = message.participants.find(
(participant) => participant.role === 'from',
(participant) => participant.role === MessageParticipantRole.FROM,
);
if (!fromParticipant || !fromParticipant.handle) {
@@ -1,4 +1,5 @@
import { isDefined } from 'twenty-shared/utils';
import { type MessageParticipantRole } from 'twenty-shared/types';
import { type Participant } from 'src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-message.type';
import { type EmailAddress } from 'src/modules/messaging/message-import-manager/types/email-address';
@@ -9,7 +10,7 @@ const removeSpacesAndLowerCase = (email: string): string => {
export const formatAddressObjectAsParticipants = (
addressObjects: EmailAddress[],
role: 'from' | 'to' | 'cc' | 'bcc',
role: MessageParticipantRole,
): Participant[] => {
const participants = addressObjects.map((addressObject) => {
const address = addressObject.address;