Calendar event fixes and improvements (#4690)

* fixes

* saving workspaceMemberId and personId when saving attendees

* add typing

* use Map

* improve saveMessageParticipants

* fix role type

* move logic in a service

* create new service

* use new service in calendar-event-attendee.service

* modify service to include more common logic

* add defaumt value to isOrganizer in calendar-event-attendee.object-metadata

* rename folder

* renaming
This commit is contained in:
bosiraphael
2024-03-29 10:03:00 +01:00
committed by GitHub
parent 1829f4d009
commit 68977dc675
15 changed files with 217 additions and 112 deletions

View File

@ -214,8 +214,7 @@ export class TimelineCalendarEventService {
event.description = '';
event.location = '';
event.conferenceSolution = '';
event.conferenceLink.label = '';
event.conferenceLink.url = '';
event.conferenceLink = { label: '', url: '' };
}
});

View File

@ -11,7 +11,7 @@ import { MessageQueueDriver } from './interfaces/message-queue-driver.interface'
export type PgBossDriverOptions = PgBoss.ConstructorOptions;
const DEFAULT_PG_BOSS_CRON_PATTERN_WHEN_NOT_PROVIDED = '*/10 * * * *';
const DEFAULT_PG_BOSS_CRON_PATTERN_WHEN_NOT_PROVIDED = '*/1 * * * *';
export class PgBossDriver implements MessageQueueDriver {
private pgBoss: PgBoss;

View File

@ -78,38 +78,6 @@ export class CalendarEventAttendeeRepository {
);
}
public async saveCalendarEventAttendees(
calendarEventAttendees: CalendarEventAttendee[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<void> {
if (calendarEventAttendees.length === 0) {
return;
}
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const { flattenedValues, valuesString } =
getFlattenedValuesAndValuesStringForBatchRawQuery(
calendarEventAttendees,
{
calendarEventId: 'uuid',
handle: 'text',
displayName: 'text',
isOrganizer: 'boolean',
responseStatus: `${dataSourceSchema}."calendarEventAttendee_responsestatus_enum"`,
},
);
await this.workspaceDataSourceService.executeRawQuery(
`INSERT INTO ${dataSourceSchema}."calendarEventAttendee" ("calendarEventId", "handle", "displayName", "isOrganizer", "responseStatus") VALUES ${valuesString}`,
flattenedValues,
workspaceId,
transactionManager,
);
}
public async updateCalendarEventAttendees(
calendarEventAttendees: CalendarEventAttendee[],
iCalUIDCalendarEventIdMap: Map<string, string>,

View File

@ -3,12 +3,14 @@ import { Module } from '@nestjs/common';
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
import { CalendarEventAttendeeService } from 'src/modules/calendar/services/calendar-event-attendee/calendar-event-attendee.service';
import { AddPersonIdAndWorkspaceMemberIdModule } from 'src/modules/connected-account/services/add-person-id-and-workspace-member-id/add-person-id-and-workspace-member-id.module';
import { PersonObjectMetadata } from 'src/modules/person/standard-objects/person.object-metadata';
@Module({
imports: [
WorkspaceDataSourceModule,
ObjectMetadataRepositoryModule.forFeature([PersonObjectMetadata]),
AddPersonIdAndWorkspaceMemberIdModule,
],
providers: [CalendarEventAttendeeService],
exports: [CalendarEventAttendeeService],

View File

@ -7,7 +7,11 @@ import { PersonRepository } from 'src/modules/person/repositories/person.reposit
import { PersonObjectMetadata } from 'src/modules/person/standard-objects/person.object-metadata';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { getFlattenedValuesAndValuesStringForBatchRawQuery } from 'src/modules/calendar/utils/getFlattenedValuesAndValuesStringForBatchRawQuery.util';
import { CalendarEventAttendeeWithId } from 'src/modules/calendar/types/calendar-event';
import {
CalendarEventAttendee,
CalendarEventAttendeeWithId,
} from 'src/modules/calendar/types/calendar-event';
import { AddPersonIdAndWorkspaceMemberIdService } from 'src/modules/connected-account/services/add-person-id-and-workspace-member-id/add-person-id-and-workspace-member-id.service';
@Injectable()
export class CalendarEventAttendeeService {
@ -15,6 +19,7 @@ export class CalendarEventAttendeeService {
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
@InjectObjectMetadataRepository(PersonObjectMetadata)
private readonly personRepository: PersonRepository,
private readonly addPersonIdAndWorkspaceMemberIdService: AddPersonIdAndWorkspaceMemberIdService,
) {}
public async updateCalendarEventAttendeesAfterContactCreation(
@ -62,4 +67,45 @@ export class CalendarEventAttendeeService {
transactionManager,
);
}
public async saveCalendarEventAttendees(
calendarEventAttendees: CalendarEventAttendee[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<void> {
if (calendarEventAttendees.length === 0) {
return;
}
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const calendarEventAttendeesToSave =
await this.addPersonIdAndWorkspaceMemberIdService.addPersonIdAndWorkspaceMemberId(
calendarEventAttendees,
workspaceId,
transactionManager,
);
const { flattenedValues, valuesString } =
getFlattenedValuesAndValuesStringForBatchRawQuery(
calendarEventAttendeesToSave,
{
calendarEventId: 'uuid',
handle: 'text',
displayName: 'text',
isOrganizer: 'boolean',
responseStatus: `${dataSourceSchema}."calendarEventAttendee_responsestatus_enum"`,
personId: 'uuid',
workspaceMemberId: 'uuid',
},
);
await this.workspaceDataSourceService.executeRawQuery(
`INSERT INTO ${dataSourceSchema}."calendarEventAttendee" ("calendarEventId", "handle", "displayName", "isOrganizer", "responseStatus", "personId", "workspaceMemberId") VALUES ${valuesString}`,
flattenedValues,
workspaceId,
transactionManager,
);
}
}

View File

@ -4,6 +4,7 @@ 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 { CalendarEventAttendeeModule } from 'src/modules/calendar/services/calendar-event-attendee/calendar-event-attendee.module';
import { GoogleCalendarFullSyncService } from 'src/modules/calendar/services/google-calendar-full-sync.service';
import { CalendarProvidersModule } from 'src/modules/calendar/services/providers/calendar-providers.module';
import { CalendarChannelEventAssociationObjectMetadata } from 'src/modules/calendar/standard-objects/calendar-channel-event-association.object-metadata';
@ -28,6 +29,7 @@ import { WorkspaceMemberObjectMetadata } from 'src/modules/workspace-member/stan
PersonObjectMetadata,
WorkspaceMemberObjectMetadata,
]),
CalendarEventAttendeeModule,
TypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
WorkspaceDataSourceModule,
],

View File

@ -28,6 +28,7 @@ import { CalendarChannelObjectMetadata } from 'src/modules/calendar/standard-obj
import { CalendarChannelEventAssociationObjectMetadata } from 'src/modules/calendar/standard-objects/calendar-channel-event-association.object-metadata';
import { CalendarEventAttendeeObjectMetadata } from 'src/modules/calendar/standard-objects/calendar-event-attendee.object-metadata';
import { BlocklistObjectMetadata } from 'src/modules/connected-account/standard-objects/blocklist.object-metadata';
import { CalendarEventAttendeeService } from 'src/modules/calendar/services/calendar-event-attendee/calendar-event-attendee.service';
@Injectable()
export class GoogleCalendarFullSyncService {
@ -55,6 +56,7 @@ export class GoogleCalendarFullSyncService {
private readonly featureFlagRepository: Repository<FeatureFlagEntity>,
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
private readonly eventEmitter: EventEmitter2,
private readonly calendarEventAttendeesService: CalendarEventAttendeeService,
) {}
public async startGoogleCalendarFullSync(
@ -265,7 +267,7 @@ export class GoogleCalendarFullSyncService {
startTime = Date.now();
await this.calendarEventAttendeesRepository.saveCalendarEventAttendees(
await this.calendarEventAttendeesService.saveCalendarEventAttendees(
attendeesToSave,
workspaceId,
transactionManager,

View File

@ -65,6 +65,7 @@ export class CalendarEventAttendeeObjectMetadata extends BaseObjectMetadata {
label: 'Is Organizer',
description: 'Is Organizer',
icon: 'IconUser',
defaultValue: false,
})
isOrganizer: boolean;

View File

@ -0,0 +1,16 @@
import { Module } from '@nestjs/common';
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
import { AddPersonIdAndWorkspaceMemberIdService } from 'src/modules/connected-account/services/add-person-id-and-workspace-member-id/add-person-id-and-workspace-member-id.service';
import { PersonObjectMetadata } from 'src/modules/person/standard-objects/person.object-metadata';
@Module({
imports: [
WorkspaceDataSourceModule,
ObjectMetadataRepositoryModule.forFeature([PersonObjectMetadata]),
],
providers: [AddPersonIdAndWorkspaceMemberIdService],
exports: [AddPersonIdAndWorkspaceMemberIdService],
})
export class AddPersonIdAndWorkspaceMemberIdModule {}

View File

@ -0,0 +1,90 @@
import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { PersonRepository } from 'src/modules/person/repositories/person.repository';
import { PersonObjectMetadata } from 'src/modules/person/standard-objects/person.object-metadata';
@Injectable()
export class AddPersonIdAndWorkspaceMemberIdService {
constructor(
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
@InjectObjectMetadataRepository(PersonObjectMetadata)
private readonly personRepository: PersonRepository,
) {}
private async getEmailPersonIdMap(
handles: string[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<Map<string, string>> {
const personIds = await this.personRepository.getByEmails(
handles,
workspaceId,
transactionManager,
);
return new Map(personIds.map((person) => [person.email, person.id]));
}
private async getEmailWorkspaceMemberIdMap(
handles: string[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<Map<string, string>> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const workspaceMemberIds: {
id: string;
email: string;
}[] = await this.workspaceDataSourceService.executeRawQuery(
`SELECT "workspaceMember"."id", "connectedAccount"."handle" AS email FROM ${dataSourceSchema}."workspaceMember"
JOIN ${dataSourceSchema}."connectedAccount" ON ${dataSourceSchema}."workspaceMember"."id" = ${dataSourceSchema}."connectedAccount"."accountOwnerId"
WHERE ${dataSourceSchema}."connectedAccount"."handle" = ANY($1)`,
[handles],
workspaceId,
transactionManager,
);
return new Map(
workspaceMemberIds.map((workspaceMember) => [
workspaceMember.email,
workspaceMember.id,
]),
);
}
public async addPersonIdAndWorkspaceMemberId<T extends { handle: string }>(
objects: T[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<
(T & {
personId: string | null;
workspaceMemberId: string | null;
})[]
> {
const handles = objects.map((object) => object.handle);
const personIdMap = await this.getEmailPersonIdMap(
handles,
workspaceId,
transactionManager,
);
const workspaceMemberIdMap = await this.getEmailWorkspaceMemberIdMap(
handles,
workspaceId,
transactionManager,
);
return objects.map((object) => ({
...object,
personId: personIdMap.get(object.handle) || null,
workspaceMemberId: workspaceMemberIdMap.get(object.handle) || null,
}));
}
}

View File

@ -5,10 +5,7 @@ import { EntityManager } from 'typeorm';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { MessageParticipantObjectMetadata } from 'src/modules/messaging/standard-objects/message-participant.object-metadata';
import { ObjectRecord } from 'src/engine/workspace-manager/workspace-sync-metadata/types/object-record';
import {
ParticipantWithId,
ParticipantWithMessageId,
} from 'src/modules/messaging/types/gmail-message';
import { ParticipantWithId } from 'src/modules/messaging/types/gmail-message';
@Injectable()
export class MessageParticipantRepository {
@ -126,63 +123,4 @@ export class MessageParticipantRepository {
return messageParticipants;
}
public async saveMessageParticipants(
participants: ParticipantWithMessageId[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<void> {
if (!participants) return;
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const handles = participants.map((participant) => participant.handle);
const participantPersonIds =
await this.workspaceDataSourceService.executeRawQuery(
`SELECT id, email FROM ${dataSourceSchema}."person" WHERE "email" = ANY($1)`,
[handles],
workspaceId,
transactionManager,
);
const participantWorkspaceMemberIds =
await this.workspaceDataSourceService.executeRawQuery(
`SELECT "workspaceMember"."id", "connectedAccount"."handle" AS email FROM ${dataSourceSchema}."workspaceMember"
JOIN ${dataSourceSchema}."connectedAccount" ON ${dataSourceSchema}."workspaceMember"."id" = ${dataSourceSchema}."connectedAccount"."accountOwnerId"
WHERE ${dataSourceSchema}."connectedAccount"."handle" = ANY($1)`,
[handles],
workspaceId,
transactionManager,
);
const messageParticipantsToSave = participants.map((participant) => [
participant.messageId,
participant.role,
participant.handle,
participant.displayName,
participantPersonIds.find((e) => e.email === participant.handle)?.id,
participantWorkspaceMemberIds.find((e) => e.email === participant.handle)
?.id,
]);
const valuesString = messageParticipantsToSave
.map(
(_, index) =>
`($${index * 6 + 1}, $${index * 6 + 2}, $${index * 6 + 3}, $${
index * 6 + 4
}, $${index * 6 + 5}, $${index * 6 + 6})`,
)
.join(', ');
if (messageParticipantsToSave.length === 0) return;
await this.workspaceDataSourceService.executeRawQuery(
`INSERT INTO ${dataSourceSchema}."messageParticipant" ("messageId", "role", "handle", "displayName", "personId", "workspaceMemberId") VALUES ${valuesString}`,
messageParticipantsToSave.flat(),
workspaceId,
transactionManager,
);
}
}

View File

@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
import { AddPersonIdAndWorkspaceMemberIdModule } from 'src/modules/connected-account/services/add-person-id-and-workspace-member-id/add-person-id-and-workspace-member-id.module';
import { MessageParticipantService } from 'src/modules/messaging/services/message-participant/message-participant.service';
import { PersonObjectMetadata } from 'src/modules/person/standard-objects/person.object-metadata';
@ -9,6 +10,7 @@ import { PersonObjectMetadata } from 'src/modules/person/standard-objects/person
imports: [
WorkspaceDataSourceModule,
ObjectMetadataRepositoryModule.forFeature([PersonObjectMetadata]),
AddPersonIdAndWorkspaceMemberIdModule,
],
providers: [MessageParticipantService],
exports: [MessageParticipantService],

View File

@ -3,11 +3,15 @@ import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { ParticipantWithId } from 'src/modules/messaging/types/gmail-message';
import {
ParticipantWithId,
ParticipantWithMessageId,
} from 'src/modules/messaging/types/gmail-message';
import { PersonRepository } from 'src/modules/person/repositories/person.repository';
import { PersonObjectMetadata } from 'src/modules/person/standard-objects/person.object-metadata';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { getFlattenedValuesAndValuesStringForBatchRawQuery } from 'src/modules/calendar/utils/getFlattenedValuesAndValuesStringForBatchRawQuery.util';
import { AddPersonIdAndWorkspaceMemberIdService } from 'src/modules/connected-account/services/add-person-id-and-workspace-member-id/add-person-id-and-workspace-member-id.service';
@Injectable()
export class MessageParticipantService {
@ -15,6 +19,7 @@ export class MessageParticipantService {
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
@InjectObjectMetadataRepository(PersonObjectMetadata)
private readonly personRepository: PersonRepository,
private readonly addPersonIdAndWorkspaceMemberIdService: AddPersonIdAndWorkspaceMemberIdService,
) {}
public async updateMessageParticipantsAfterPeopleCreation(
@ -62,4 +67,44 @@ export class MessageParticipantService {
transactionManager,
);
}
public async saveMessageParticipants(
participants: ParticipantWithMessageId[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<void> {
if (!participants) return;
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const messageParticipantsToSave =
await this.addPersonIdAndWorkspaceMemberIdService.addPersonIdAndWorkspaceMemberId(
participants,
workspaceId,
transactionManager,
);
const { flattenedValues, valuesString } =
getFlattenedValuesAndValuesStringForBatchRawQuery(
messageParticipantsToSave,
{
messageId: 'uuid',
role: `${dataSourceSchema}."messageParticipant_role_enum"`,
handle: 'text',
displayName: 'text',
personId: 'uuid',
workspaceMemberId: 'uuid',
},
);
if (messageParticipantsToSave.length === 0) return;
await this.workspaceDataSourceService.executeRawQuery(
`INSERT INTO ${dataSourceSchema}."messageParticipant" ("messageId", "role", "handle", "displayName", "personId", "workspaceMemberId") VALUES ${valuesString}`,
flattenedValues,
workspaceId,
transactionManager,
);
}
}

View File

@ -7,15 +7,11 @@ import { MessageParticipantModule } from 'src/modules/messaging/services/message
import { MessageModule } from 'src/modules/messaging/services/message/message.module';
import { SaveMessageAndEmitContactCreationEventService } from 'src/modules/messaging/services/save-message-and-emit-contact-creation-event/save-message-and-emit-contact-creation-event.service';
import { MessageChannelObjectMetadata } from 'src/modules/messaging/standard-objects/message-channel.object-metadata';
import { MessageParticipantObjectMetadata } from 'src/modules/messaging/standard-objects/message-participant.object-metadata';
@Module({
imports: [
MessageModule,
ObjectMetadataRepositoryModule.forFeature([
MessageChannelObjectMetadata,
MessageParticipantObjectMetadata,
]),
ObjectMetadataRepositoryModule.forFeature([MessageChannelObjectMetadata]),
AutoCompaniesAndContactsCreationModule,
MessageParticipantModule,
WorkspaceDataSourceModule,

View File

@ -4,7 +4,6 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
import { EntityManager } from 'typeorm';
import { MessageChannelRepository } from 'src/modules/messaging/repositories/message-channel.repository';
import { MessageParticipantRepository } from 'src/modules/messaging/repositories/message-participant.repository';
import {
GmailMessage,
ParticipantWithMessageId,
@ -15,7 +14,7 @@ import { ObjectRecord } from 'src/engine/workspace-manager/workspace-sync-metada
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { MessageChannelObjectMetadata } from 'src/modules/messaging/standard-objects/message-channel.object-metadata';
import { MessageService } from 'src/modules/messaging/services/message/message.service';
import { MessageParticipantObjectMetadata } from 'src/modules/messaging/standard-objects/message-participant.object-metadata';
import { MessageParticipantService } from 'src/modules/messaging/services/message-participant/message-participant.service';
@Injectable()
export class SaveMessageAndEmitContactCreationEventService {
@ -27,10 +26,9 @@ export class SaveMessageAndEmitContactCreationEventService {
private readonly messageService: MessageService,
@InjectObjectMetadataRepository(MessageChannelObjectMetadata)
private readonly messageChannelRepository: MessageChannelRepository,
@InjectObjectMetadataRepository(MessageParticipantObjectMetadata)
private readonly messageParticipantRepository: MessageParticipantRepository,
private readonly eventEmitter: EventEmitter2,
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
private readonly messageParticipantService: MessageParticipantService,
) {}
public async saveMessagesAndEmitContactCreationEventWithinTransaction(
@ -66,7 +64,7 @@ export class SaveMessageAndEmitContactCreationEventService {
: [];
});
await this.messageParticipantRepository.saveMessageParticipants(
await this.messageParticipantService.saveMessageParticipants(
participantsWithMessageId,
workspaceId,
transactionManager,
@ -172,7 +170,7 @@ export class SaveMessageAndEmitContactCreationEventService {
connectedAccount: ObjectRecord<ConnectedAccountObjectMetadata>,
) {
try {
await this.messageParticipantRepository.saveMessageParticipants(
await this.messageParticipantService.saveMessageParticipants(
participantsWithMessageId,
workspaceId,
);