Skip to content

Commit

Permalink
Add message import granulary on non-pro emails, group emails and rece…
Browse files Browse the repository at this point in the history
…ived contact creation (twentyhq#6156)

1) Remove featureFlag
2) Base contactCreation on messageChannel.autoContactCreationPolicy
4) add excludeProfessionalEmails + excludeGroupEmails logic
  • Loading branch information
charlesBochet authored Jul 8, 2024
1 parent ef849d3 commit 9ba2110
Show file tree
Hide file tree
Showing 17 changed files with 131 additions and 143 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const OnboardingSyncEmailsSettingsCard = ({
value = MessageChannelVisibility.ShareEverything,
}: OnboardingSyncEmailsSettingsCardProps) => (
<SettingsAccountsRadioSettingsCard
name="sync-emails-visiblity"
options={onboardingSyncEmailsOptions}
value={value}
onChange={onChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const SettingsAccountsEventVisibilitySettingsCard = ({
value = CalendarChannelVisibility.ShareEverything,
}: SettingsAccountsEventVisibilitySettingsCardProps) => (
<SettingsAccountsRadioSettingsCard
name="event-visibility"
options={eventSettingsVisibilityOptions}
value={value}
onChange={onChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const SettingsAccountsMessageAutoCreationCard = ({
value = MessageChannelContactAutoCreationPolicy.SENT_AND_RECEIVED,
}: SettingsAccountsMessageAutoCreationCardProps) => (
<SettingsAccountsRadioSettingsCard
name="message-auto-creation"
options={autoCreationOptions}
value={value}
onChange={onChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const SettingsAccountsMessageVisibilityCard = ({
value = MessageChannelVisibility.ShareEverything,
}: SettingsAccountsMessageVisibilityCardProps) => (
<SettingsAccountsRadioSettingsCard
name="message-visibility"
options={inboxSettingsVisibilityOptions}
value={value}
onChange={onChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type SettingsAccountsRadioSettingsCardProps<Option extends { value: string }> =
onChange: (nextValue: Option['value']) => void;
options: Option[];
value: Option['value'];
name: string;
};

const StyledCardContent = styled(CardContent)`
Expand Down Expand Up @@ -49,6 +50,7 @@ export const SettingsAccountsRadioSettingsCard = <
onChange,
options,
value,
name,
}: SettingsAccountsRadioSettingsCardProps<Option>) => (
<Card rounded>
{options.map((option, index) => (
Expand All @@ -63,6 +65,7 @@ export const SettingsAccountsRadioSettingsCard = <
<StyledDescription>{option.description}</StyledDescription>
</div>
<StyledRadio
name={name}
value={option.value}
onCheckedChange={() => onChange(option.value)}
checked={value === option.value}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,15 @@ export const SettingsAccountsRowDropdownMenu = ({
LeftIcon={IconMail}
text="Emails settings"
onClick={() => {
navigate(
`/settings/accounts/emails/${account.messageChannels[0].id}`,
);
navigate(`/settings/accounts/emails`);
closeDropdown();
}}
/>
<MenuItem
LeftIcon={IconCalendarEvent}
text="Calendar settings"
onClick={() => {
navigate(
`/settings/accounts/calendars/${account.calendarChannels[0].id}`,
);
navigate(`/settings/accounts/calendars`);
closeDropdown();
}}
/>
Expand Down
13 changes: 9 additions & 4 deletions packages/twenty-front/src/modules/ui/input/components/Radio.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as React from 'react';
import styled from '@emotion/styled';
import { motion } from 'framer-motion';
import * as React from 'react';
import { RGBA } from 'twenty-ui';

import { v4 } from 'uuid';
import { RadioGroup } from './RadioGroup';

export enum RadioSize {
Expand Down Expand Up @@ -105,6 +106,7 @@ const StyledLabel = styled.label<LabelProps>`
export type RadioProps = {
checked?: boolean;
className?: string;
name?: string;
disabled?: boolean;
label?: string;
labelPosition?: LabelPosition;
Expand All @@ -118,6 +120,7 @@ export type RadioProps = {
export const Radio = ({
checked,
className,
name = 'input-radio',
disabled = false,
label,
labelPosition = LabelPosition.Right,
Expand All @@ -131,12 +134,14 @@ export const Radio = ({
onCheckedChange?.(event.target.checked);
};

const optionId = v4();

return (
<StyledContainer className={className} labelPosition={labelPosition}>
<StyledRadioInput
type="radio"
id="input-radio"
name="input-radio"
id={optionId}
name={name}
data-testid="input-radio"
checked={checked}
value={value || label}
Expand All @@ -149,7 +154,7 @@ export const Radio = ({
/>
{label && (
<StyledLabel
htmlFor="input-radio"
htmlFor={optionId}
labelPosition={labelPosition}
disabled={disabled}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import * as React from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconComponent, Pill } from 'twenty-ui';
Expand Down Expand Up @@ -41,6 +40,7 @@ const StyledHover = styled.span`
padding: ${({ theme }) => theme.spacing(1)};
padding-left: ${({ theme }) => theme.spacing(2)};
padding-right: ${({ theme }) => theme.spacing(2)};
font-weight: ${({ theme }) => theme.font.weight.medium};
&:hover {
background: ${({ theme }) => theme.background.tertiary};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,6 @@ export const seedFeatureFlags = async (
workspaceId: workspaceId,
value: true,
},
{
key: FeatureFlagKeys.IsContactCreationForSentAndReceivedEmailsEnabled,
workspaceId: workspaceId,
value: true,
},
{
key: FeatureFlagKeys.IsMessagingAliasFetchingEnabled,
workspaceId: workspaceId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
import { Field, ObjectType } from '@nestjs/graphql';

import { IDField } from '@ptc-org/nestjs-query-graphql';
import {
Entity,
Unique,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
Entity,
ManyToOne,
PrimaryGeneratedColumn,
Relation,
Unique,
UpdateDateColumn,
} from 'typeorm';
import { IDField } from '@ptc-org/nestjs-query-graphql';

import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';

export enum FeatureFlagKeys {
IsBlocklistEnabled = 'IS_BLOCKLIST_ENABLED',
IsEventObjectEnabled = 'IS_EVENT_OBJECT_ENABLED',
IsAirtableIntegrationEnabled = 'IS_AIRTABLE_INTEGRATION_ENABLED',
IsPostgreSQLIntegrationEnabled = 'IS_POSTGRESQL_INTEGRATION_ENABLED',
IsStripeIntegrationEnabled = 'IS_STRIPE_INTEGRATION_ENABLED',
IsContactCreationForSentAndReceivedEmailsEnabled = 'IS_CONTACT_CREATION_FOR_SENT_AND_RECEIVED_EMAILS_ENABLED',
IsCopilotEnabled = 'IS_COPILOT_ENABLED',
IsMessagingAliasFetchingEnabled = 'IS_MESSAGING_ALIAS_FETCHING_ENABLED',
IsGoogleCalendarSyncV2Enabled = 'IS_GOOGLE_CALENDAR_SYNC_V2_ENABLED',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { InjectDataSource } from '@nestjs/typeorm';
import { Command, CommandRunner, Option } from 'nest-commander';
import { DataSource } from 'typeorm';

import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { standardObjectMetadataDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-objects';
import { StandardObjectFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-object.factory';
import { StandardFieldFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-field.factory';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-entity';
import { StandardFieldFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-field.factory';
import { StandardObjectFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-object.factory';
import { standardObjectMetadataDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-objects';
import { computeStandardFields } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/compute-standard-fields.util';

interface RunCommandOptions {
Expand Down Expand Up @@ -58,7 +58,6 @@ export class AddStandardIdCommand extends CommandRunner {
IS_AIRTABLE_INTEGRATION_ENABLED: true,
IS_POSTGRESQL_INTEGRATION_ENABLED: true,
IS_STRIPE_INTEGRATION_ENABLED: false,
IS_CONTACT_CREATION_FOR_SENT_AND_RECEIVED_EMAILS_ENABLED: true,
IS_COPILOT_ENABLED: false,
IS_MESSAGING_ALIAS_FETCHING_ENABLED: true,
IS_GOOGLE_CALENDAR_SYNC_V2_ENABLED: true,
Expand All @@ -77,7 +76,6 @@ export class AddStandardIdCommand extends CommandRunner {
IS_AIRTABLE_INTEGRATION_ENABLED: true,
IS_POSTGRESQL_INTEGRATION_ENABLED: true,
IS_STRIPE_INTEGRATION_ENABLED: false,
IS_CONTACT_CREATION_FOR_SENT_AND_RECEIVED_EMAILS_ENABLED: true,
IS_COPILOT_ENABLED: false,
IS_MESSAGING_ALIAS_FETCHING_ENABLED: true,
IS_GOOGLE_CALENDAR_SYNC_V2_ENABLED: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';

import { EntityManager } from 'typeorm';
import compact from 'lodash.compact';
import chunk from 'lodash.chunk';
import compact from 'lodash.compact';
import { EntityManager } from 'typeorm';

import { getDomainNameFromHandle } from 'src/modules/calendar-messaging-participant/utils/get-domain-name-from-handle.util';
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 { PersonRepository } from 'src/modules/person/repositories/person.repository';
import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository';
import { isWorkEmail } from 'src/utils/is-work-email';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { getUniqueContactsAndHandles } from 'src/modules/connected-account/auto-companies-and-contacts-creation/utils/get-unique-contacts-and-handles.util';
import { filterOutSelfAndContactsFromCompanyOrWorkspace } from 'src/modules/connected-account/auto-companies-and-contacts-creation/utils/filter-out-contacts-from-company-or-workspace.util';
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 { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
import { InjectWorkspaceDatasource } from 'src/engine/twenty-orm/decorators/inject-workspace-datasource.decorator';
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
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 { 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 { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event-participant.workspace-entity';
import { filterOutSelfAndContactsFromCompanyOrWorkspace } from 'src/modules/connected-account/auto-companies-and-contacts-creation/utils/filter-out-contacts-from-company-or-workspace.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';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { isWorkEmail } from 'src/utils/is-work-email';

@Injectable()
export class CreateCompanyAndContactService {
Expand Down Expand Up @@ -52,9 +52,6 @@ export class CreateCompanyAndContactService {
return [];
}

// TODO: This is a feature that may be implemented in the future
const isContactAutoCreationForNonWorkEmailsEnabled = false;

const workspaceMembers =
await this.workspaceMemberRepository.getAllByWorkspaceId(
workspaceId,
Expand Down Expand Up @@ -89,9 +86,7 @@ export class CreateCompanyAndContactService {
const filteredContactsToCreate = uniqueContacts.filter(
(participant) =>
!alreadyCreatedContactEmails.includes(participant.handle) &&
participant.handle.includes('@') &&
(isContactAutoCreationForNonWorkEmailsEnabled ||
isWorkEmail(participant.handle)),
participant.handle.includes('@'),
);

const filteredContactsToCreateWithCompanyDomainNames =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { InjectRepository } from '@nestjs/typeorm';

import { EntityManager, Repository } from 'typeorm';

import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
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 { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import {
CreateCompanyAndContactJobData,
CreateCompanyAndContactJob,
CreateCompanyAndContactJobData,
} from 'src/modules/connected-account/auto-companies-and-contacts-creation/jobs/create-company-and-contact.job';
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 { MessagingMessageService } from 'src/modules/messaging/common/services/messaging-message.service';
import {
FeatureFlagEntity,
FeatureFlagKeys,
} from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
MessageChannelContactAutoCreationPolicy,
MessageChannelWorkspaceEntity,
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
import { MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity';
import {
GmailMessage,
Participant,
ParticipantWithMessageId,
} from 'src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-message';
import { MessagingMessageService } from 'src/modules/messaging/common/services/messaging-message.service';
import { MessagingMessageParticipantService } from 'src/modules/messaging/common/services/messaging-message-participant.service';
import { InjectMessageQueue } from 'src/engine/integrations/message-queue/decorators/message-queue.decorator';
import { MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity';
import { isGroupEmail } from 'src/utils/is-group-email';
import { isWorkEmail } from 'src/utils/is-work-email';

@Injectable()
export class MessagingSaveMessagesAndEnqueueContactCreationService {
Expand All @@ -51,18 +53,8 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService {
workspaceId,
);

const isContactCreationForSentAndReceivedEmailsEnabledFeatureFlag =
await this.featureFlagRepository.findOneBy({
workspaceId: workspaceId,
key: FeatureFlagKeys.IsContactCreationForSentAndReceivedEmailsEnabled,
value: true,
});

const emailAliases = connectedAccount.emailAliases?.split(',') || [];

const isContactCreationForSentAndReceivedEmailsEnabled =
isContactCreationForSentAndReceivedEmailsEnabledFeatureFlag?.value;

let savedMessageParticipants: MessageParticipantWorkspaceEntity[] = [];

const participantsWithMessageId = await workspaceDataSource?.transaction(
Expand All @@ -87,14 +79,35 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService {
message.participants.find((p) => p.role === 'from')?.handle ||
'';

const isMessageSentByConnectedAccount =
emailAliases.includes(fromHandle);

const isParticipantConnectedAccount = emailAliases.includes(
participant.handle,
);

const isExcludedByNonProfessionalEmails =
messageChannel.excludeNonProfessionalEmails &&
!isWorkEmail(participant.handle);

const isExcludedByGroupEmails =
messageChannel.excludeGroupEmails &&
isGroupEmail(participant.handle);

const shouldCreateContact =
!isParticipantConnectedAccount &&
!isExcludedByNonProfessionalEmails &&
!isExcludedByGroupEmails &&
(messageChannel.contactAutoCreationPolicy ===
MessageChannelContactAutoCreationPolicy.SENT_AND_RECEIVED ||
(messageChannel.contactAutoCreationPolicy ===
MessageChannelContactAutoCreationPolicy.SENT &&
isMessageSentByConnectedAccount));

return {
...participant,
messageId,
shouldCreateContact:
messageChannel.isContactAutoCreationEnabled &&
(isContactCreationForSentAndReceivedEmailsEnabled ||
emailAliases.includes(fromHandle)) &&
!emailAliases.includes(participant.handle),
shouldCreateContact,
};
})
: [];
Expand Down
Loading

0 comments on commit 9ba2110

Please sign in to comment.