Skip to content

Commit

Permalink
3889 activate settingsaccountsemailsinboxsettings (#3962)
Browse files Browse the repository at this point in the history
* update email visibility in settings

* improve styling

* Add contact auto creation toggle to inbox settings

* re
move soonpill

* update Icon

* create job

* Add logic to create contacts and companies for message participants without personId and workspaceMemberId

* add listener

* wip

* wip

* refactoring

* improve structure

* Add isContactAutoCreationEnabled method to MessageChannelService

* wip

* wip

* clean

* add job

* fix bug

* contact creation is working

* wip

* working

* improve code

* improve typing

* resolve conflicts

* fix

* create company repository

* move util

* wip

* fix
  • Loading branch information
bosiraphael authored Feb 14, 2024
1 parent 0b2ffb0 commit 94ad0e3
Show file tree
Hide file tree
Showing 27 changed files with 608 additions and 165 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import styled from '@emotion/styled';

import { MessageChannel } from '@/accounts/types/MessageChannel';
import { SettingsAccountsInboxSettingsCardMedia } from '@/settings/accounts/components/SettingsAccountsInboxSettingsCardMedia';
import { IconSend } from '@/ui/display/icon';
import { IconUser } from '@/ui/display/icon';
import { H2Title } from '@/ui/display/typography/components/H2Title';
import { Toggle } from '@/ui/input/components/Toggle';
import { Card } from '@/ui/layout/card/components/Card';
Expand Down Expand Up @@ -43,7 +43,7 @@ export const SettingsAccountsInboxSettingsContactAutoCreateSection = ({
<Card>
<StyledCardContent>
<SettingsAccountsInboxSettingsCardMedia>
<IconSend size={theme.icon.size.sm} stroke={theme.icon.stroke.lg} />
<IconUser size={theme.icon.size.sm} stroke={theme.icon.stroke.lg} />
</SettingsAccountsInboxSettingsCardMedia>
<StyledTitle>Auto-creation</StyledTitle>
<Toggle
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import styled from '@emotion/styled';
import { SoonPill } from 'tsup.ui.index';

import { SettingsAccountsInboxSettingsCardMedia } from '@/settings/accounts/components/SettingsAccountsInboxSettingsCardMedia';
import { H2Title } from '@/ui/display/typography/components/H2Title';
Expand All @@ -23,7 +22,11 @@ const StyledCardContent = styled(CardContent)`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(4)};
opacity: 0.56;
cursor: pointer;
&:hover {
background: ${({ theme }) => theme.background.transparent.lighter};
}
`;

const StyledCardMedia = styled(SettingsAccountsInboxSettingsCardMedia)`
Expand Down Expand Up @@ -61,16 +64,6 @@ const StyledRadio = styled(Radio)`
margin-left: auto;
`;

const StyledSoonPill = styled(SoonPill)`
position: absolute;
right: 0;
top: 0;
`;

const StyledSection = styled(Section)`
position: relative;
`;

const inboxSettingsVisibilityOptions = [
{
title: 'Everything',
Expand Down Expand Up @@ -108,12 +101,11 @@ export const SettingsAccountsInboxSettingsVisibilitySection = ({
onChange,
value = InboxSettingsVisibilityValue.Everything,
}: SettingsAccountsInboxSettingsVisibilitySectionProps) => (
<StyledSection>
<Section>
<H2Title
title="Email visibility"
description="Define what will be visible to other users in your workspace"
/>
<StyledSoonPill />
<Card>
{inboxSettingsVisibilityOptions.map(
(
Expand All @@ -123,6 +115,7 @@ export const SettingsAccountsInboxSettingsVisibilitySection = ({
<StyledCardContent
key={optionValue}
divider={index < inboxSettingsVisibilityOptions.length - 1}
onClick={() => onChange(optionValue)}
>
<StyledCardMedia>
<StyledMetadataSkeleton isActive={visibleElements.metadata} />
Expand All @@ -137,11 +130,10 @@ export const SettingsAccountsInboxSettingsVisibilitySection = ({
value={optionValue}
onCheckedChange={() => onChange(optionValue)}
checked={value === optionValue}
disabled={true}
/>
</StyledCardContent>
),
)}
</Card>
</StyledSection>
</Section>
);
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useNavigate, useParams } from 'react-router-dom';
import { MessageChannel } from '@/accounts/types/MessageChannel';
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { SettingsAccountsInboxSettingsContactAutoCreateSection } from '@/settings/accounts/components/SettingsAccountsInboxSettingsContactAutoCreationSection';
import {
InboxSettingsVisibilityValue,
SettingsAccountsInboxSettingsVisibilitySection,
Expand Down Expand Up @@ -36,6 +37,15 @@ export const SettingsAccountsEmailsInboxSettings = () => {
});
};

const handleContactAutoCreationToggle = (value: boolean) => {
updateOneRecord({
idToUpdate: messageChannelId,
updateOneRecordInput: {
isContactAutoCreationEnabled: value,
},
});
};

useEffect(() => {
if (!loading && !messageChannel) navigate(AppPath.NotFound);
}, [loading, messageChannel, navigate]);
Expand All @@ -61,11 +71,10 @@ export const SettingsAccountsEmailsInboxSettings = () => {
value={messageChannel?.visibility}
onChange={handleVisibilityChange}
/>
{/* TODO : Add this section when the backend will be ready to auto create contacts */}
{/* <SettingsAccountsInboxSettingsContactAutoCreateSection
<SettingsAccountsInboxSettingsContactAutoCreateSection
messageChannel={messageChannel}
onToggle={handleContactAutoCreationToggle}
/> */}
/>
</SettingsPageContainer>
</SubMenuTopBarContainer>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ export class GoogleGmailService {
);

await manager.query(
`INSERT INTO ${dataSourceMetadata.schema}."messageChannel" ("visibility", "handle", "connectedAccountId", "type") VALUES ($1, $2, $3, $4)`,
['share_everything', handle, connectedAccountId, 'email'],
`INSERT INTO ${dataSourceMetadata.schema}."messageChannel" ("visibility", "handle", "connectedAccountId", "type", "isContactAutoCreationEnabled") VALUES ($1, $2, $3, $4, $5)`,
['share_everything', handle, connectedAccountId, 'email', true],
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
import { FetchAllWorkspacesMessagesJob } from 'src/workspace/messaging/commands/crons/fetch-all-workspaces-messages.job';
import { ConnectedAccountModule } from 'src/workspace/messaging/repositories/connected-account/connected-account.module';
import { MatchMessageParticipantJob } from 'src/workspace/messaging/jobs/match-message-participant.job';
import { CreateCompaniesAndContactsAfterSyncJob } from 'src/workspace/messaging/jobs/create-companies-and-contacts-after-sync.job';
import { CreateCompaniesAndContactsModule } from 'src/workspace/messaging/services/create-companies-and-contacts/create-companies-and-contacts.module';
import { MessageChannelModule } from 'src/workspace/messaging/repositories/message-channel/message-channel.module';
import { MessageParticipantModule } from 'src/workspace/messaging/repositories/message-participant/message-participant.module';

@Module({
Expand All @@ -36,6 +39,8 @@ import { MessageParticipantModule } from 'src/workspace/messaging/repositories/m
TypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
ConnectedAccountModule,
MessageParticipantModule,
CreateCompaniesAndContactsModule,
MessageChannelModule,
],
providers: [
{
Expand Down Expand Up @@ -67,6 +72,10 @@ import { MessageParticipantModule } from 'src/workspace/messaging/repositories/m
provide: MatchMessageParticipantJob.name,
useClass: MatchMessageParticipantJob,
},
{
provide: CreateCompaniesAndContactsAfterSyncJob.name,
useClass: CreateCompaniesAndContactsAfterSyncJob,
},
],
})
export class JobsModule {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Injectable, Logger } from '@nestjs/common';

import { CreateContactsAndCompaniesAfterSyncJobData } from 'packages/twenty-server/dist/src/workspace/messaging/jobs/create-contacts-and-companies-after-sync.job';

import { MessageQueueJob } from 'src/integrations/message-queue/interfaces/message-queue-job.interface';

import { CreateCompaniesAndContactsService } from 'src/workspace/messaging/services/create-companies-and-contacts/create-companies-and-contacts.service';
import { MessageChannelService } from 'src/workspace/messaging/repositories/message-channel/message-channel.service';
import { MessageParticipantService } from 'src/workspace/messaging/repositories/message-participant/message-participant.service';

export type CreateCompaniesAndContactsAfterSyncJobData = {
workspaceId: string;
messageChannelId: string;
};

@Injectable()
export class CreateCompaniesAndContactsAfterSyncJob
implements MessageQueueJob<CreateCompaniesAndContactsAfterSyncJobData>
{
private readonly logger = new Logger(
CreateCompaniesAndContactsAfterSyncJob.name,
);
constructor(
private readonly createCompaniesAndContactsService: CreateCompaniesAndContactsService,
private readonly messageChannelService: MessageChannelService,
private readonly messageParticipantService: MessageParticipantService,
) {}

async handle(
data: CreateContactsAndCompaniesAfterSyncJobData,
): Promise<void> {
this.logger.log(
`create contacts and companies after sync for workspace ${data.workspaceId} and messageChannel ${data.messageChannelId}`,
);
const { workspaceId, messageChannelId } = data;

const isContactAutoCreationEnabled =
await this.messageChannelService.getIsContactAutoCreationEnabledByMessageChannelId(
messageChannelId,
workspaceId,
);

if (!isContactAutoCreationEnabled) {
return;
}

const messageParticipantsWithoutPersonIdAndWorkspaceMemberId =
await this.messageParticipantService.getByMessageChannelIdWithoutPersonIdAndWorkspaceMemberId(
messageChannelId,
workspaceId,
);

await this.createCompaniesAndContactsService.createCompaniesAndContacts(
messageParticipantsWithoutPersonIdAndWorkspaceMemberId,
workspaceId,
);

await this.messageParticipantService.updateMessageParticipantsAfterPeopleCreation(
messageParticipantsWithoutPersonIdAndWorkspaceMemberId,
workspaceId,
);

this.logger.log(
`create contacts and companies after sync for workspace ${data.workspaceId} and messageChannel ${data.messageChannelId} done`,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Injectable, Inject } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';

import { CreateContactsAndCompaniesAfterSyncJobData } from 'packages/twenty-server/dist/src/workspace/messaging/jobs/create-contacts-and-companies-after-sync.job';

import { ObjectRecordUpdateEvent } from 'src/integrations/event-emitter/types/object-record-update.event';
import { MessageQueue } from 'src/integrations/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/integrations/message-queue/services/message-queue.service';
import { MessageChannelObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel.object-metadata';
import { objectRecordChangedProperties as objectRecordUpdateEventChangedProperties } from 'src/integrations/event-emitter/utils/object-record-changed-properties.util';
import { CreateCompaniesAndContactsAfterSyncJob } from 'src/workspace/messaging/jobs/create-companies-and-contacts-after-sync.job';

@Injectable()
export class IsContactAutoCreationEnabledListener {
constructor(
@Inject(MessageQueue.messagingQueue)
private readonly messageQueueService: MessageQueueService,
) {}

@OnEvent('messageChannel.updated')
handleUpdatedEvent(
payload: ObjectRecordUpdateEvent<MessageChannelObjectMetadata>,
) {
if (
objectRecordUpdateEventChangedProperties(
payload.previousRecord,
payload.updatedRecord,
).includes('isContactAutoCreationEnabled') &&
payload.updatedRecord.isContactAutoCreationEnabled
) {
this.messageQueueService.add<CreateContactsAndCompaniesAfterSyncJobData>(
CreateCompaniesAndContactsAfterSyncJob.name,
{
workspaceId: payload.workspaceId,
messageChannelId: payload.updatedRecord.id,
},
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ import { GmailRefreshAccessTokenService } from 'src/workspace/messaging/services
import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module';
import { MessageParticipantModule } from 'src/workspace/messaging/repositories/message-participant/message-participant.module';
import { MessagingWorkspaceMemberListener } from 'src/workspace/messaging/listeners/messaging-workspace-member.listener';
import { IsContactAutoCreationEnabledListener } from 'src/workspace/messaging/listeners/is-contact-auto-creation-enabled-listener';
import { MessagingMessageChannelListener } from 'src/workspace/messaging/listeners/messaging-message-channel.listener';
import { MessageService } from 'src/workspace/messaging/repositories/message/message.service';
import { WorkspaceMemberModule } from 'src/workspace/messaging/repositories/workspace-member/workspace-member.module';
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
import { CreateCompaniesAndContactsModule } from 'src/workspace/messaging/services/create-companies-and-contacts/create-companies-and-contacts.module';
import { CompanyModule } from 'src/workspace/messaging/repositories/company/company.module';
import { PersonModule } from 'src/workspace/messaging/repositories/person/person.module';
@Module({
imports: [
EnvironmentModule,
Expand All @@ -32,8 +36,11 @@ import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
MessageModule,
MessageThreadModule,
MessageParticipantModule,
CreateCompaniesAndContactsModule,
WorkspaceMemberModule,
TypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
CompanyModule,
PersonModule,
],
providers: [
GmailFullSyncService,
Expand All @@ -45,6 +52,7 @@ import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
CreateCompanyService,
MessagingPersonListener,
MessagingWorkspaceMemberListener,
IsContactAutoCreationEnabledListener,
MessagingMessageChannelListener,
MessageService,
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';

import { CompanyService } from 'src/workspace/messaging/repositories/company/company.service';
import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module';

// TODO: Move outside of the messaging module
@Module({
imports: [WorkspaceDataSourceModule],
providers: [CompanyService],
exports: [CompanyService],
})
export class CompanyModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Injectable } from '@nestjs/common';

import { EntityManager } from 'typeorm';

import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';

// TODO: Move outside of the messaging module
@Injectable()
export class CompanyService {
constructor(
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
) {}

public async getExistingCompaniesByDomainNames(
domainNames: string[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<{ id: string; domainName: string }[]> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);

const existingCompanies =
await this.workspaceDataSourceService.executeRawQuery(
`SELECT id, "domainName" FROM ${dataSourceSchema}.company WHERE "domainName" = ANY($1)`,
[domainNames],
workspaceId,
transactionManager,
);

return existingCompanies;
}

public async createCompany(
id: string,
name: string,
domainName: string,
city: string,
workspaceId: string,
transactionManager?: EntityManager,
): Promise<void> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);

await this.workspaceDataSourceService.executeRawQuery(
`INSERT INTO ${dataSourceSchema}.company (id, name, "domainName", address)
VALUES ($1, $2, $3, $4)`,
[id, name, domainName, city],
workspaceId,
transactionManager,
);
}
}
Loading

0 comments on commit 94ad0e3

Please sign in to comment.