Files
twenty_crm/packages/twenty-server/src/modules/timeline/services/timeline-activity.service.ts
Jérémy M 8b5f79ddbf fix: multiple twenty orm issues & show an example of use (#5439)
This PR is fixing some issues and adding enhancement in TwentyORM:

- [x] Composite fields in nested relations are not formatted properly
- [x] Passing operators like `Any` in `where` condition is breaking the
query
- [x] Ability to auto load workspace-entities based on a regex path

I've also introduced an example of use for `CalendarEventService`:


https://github.com/twentyhq/twenty/pull/5439/files#diff-3a7dffc0dea57345d10e70c648e911f98fe237248bcea124dafa9c8deb1db748R15
2024-05-20 11:01:47 +02:00

174 lines
5.6 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import { ObjectRecordBaseEvent } from 'src/engine/integrations/event-emitter/types/object-record.base.event';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { TimelineActivityRepository } from 'src/modules/timeline/repositiories/timeline-activity.repository';
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
type TransformedEvent = ObjectRecordBaseEvent & {
objectName?: string;
linkedRecordCachedName?: string;
linkedRecordId?: string;
linkedObjectMetadataId?: string;
};
@Injectable()
export class TimelineActivityService {
constructor(
@InjectObjectMetadataRepository(TimelineActivityWorkspaceEntity)
private readonly timelineActivityRepository: TimelineActivityRepository,
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
) {}
async upsertEvent(event: ObjectRecordBaseEvent) {
const events = await this.transformEvent(event);
if (!events || events.length === 0) return;
for (const event of events) {
return await this.timelineActivityRepository.upsertOne(
event.name,
event.properties,
event.objectName ?? event.objectMetadata.nameSingular,
event.recordId,
event.workspaceId,
event.workspaceMemberId,
event.linkedRecordCachedName,
event.linkedRecordId,
event.linkedObjectMetadataId,
);
}
}
private async transformEvent(
event: ObjectRecordBaseEvent,
): Promise<TransformedEvent[]> {
if (
['activity', 'messageParticipant', 'activityTarget'].includes(
event.objectMetadata.nameSingular,
)
) {
return await this.handleLinkedObjects(event);
}
return [event];
}
private async handleLinkedObjects(event: ObjectRecordBaseEvent) {
const dataSourceSchema = this.workspaceDataSourceService.getSchemaName(
event.workspaceId,
);
switch (event.objectMetadata.nameSingular) {
case 'activityTarget':
return this.processActivityTarget(event, dataSourceSchema);
case 'activity':
return this.processActivity(event, dataSourceSchema);
default:
return [];
}
}
private async processActivity(
event: ObjectRecordBaseEvent,
dataSourceSchema: string,
) {
const activityTargets =
await this.workspaceDataSourceService.executeRawQuery(
`SELECT * FROM ${dataSourceSchema}."activityTarget"
WHERE "activityId" = $1`,
[event.recordId],
event.workspaceId,
);
const activity = await this.workspaceDataSourceService.executeRawQuery(
`SELECT * FROM ${dataSourceSchema}."activity"
WHERE "id" = $1`,
[event.recordId],
event.workspaceId,
);
if (activityTargets.length === 0) return;
if (activity.length === 0) return;
return activityTargets
.map((activityTarget) => {
const targetColumn: string[] = Object.entries(activityTarget)
.map(([columnName, columnValue]: [string, string]) => {
if (columnName === 'activityId' || !columnName.endsWith('Id'))
return;
if (columnValue === null) return;
return columnName;
})
.filter((column): column is string => column !== undefined);
if (targetColumn.length === 0) return;
return {
...event,
name: activity[0].type.toLowerCase() + '.' + event.name.split('.')[1],
objectName: targetColumn[0].replace(/Id$/, ''),
recordId: activityTarget[targetColumn[0]],
linkedRecordCachedName: activity[0].title,
linkedRecordId: activity[0].id,
linkedObjectMetadataId: event.objectMetadata.id,
} as TransformedEvent;
})
.filter((event): event is TransformedEvent => event !== undefined);
}
private async processActivityTarget(
event: ObjectRecordBaseEvent,
dataSourceSchema: string,
) {
const activityTarget =
await this.workspaceDataSourceService.executeRawQuery(
`SELECT * FROM ${dataSourceSchema}."activityTarget"
WHERE "id" = $1`,
[event.recordId],
event.workspaceId,
);
if (activityTarget.length === 0) return;
const activity = await this.workspaceDataSourceService.executeRawQuery(
`SELECT * FROM ${dataSourceSchema}."activity"
WHERE "id" = $1`,
[activityTarget[0].activityId],
event.workspaceId,
);
if (activity.length === 0) return;
const activityObjectMetadataId = event.objectMetadata.fields.find(
(field) => field.name === 'activity',
)?.toRelationMetadata?.fromObjectMetadataId;
const targetColumn: string[] = Object.entries(activityTarget[0])
.map(([columnName, columnValue]: [string, string]) => {
if (columnName === 'activityId' || !columnName.endsWith('Id')) return;
if (columnValue === null) return;
return columnName;
})
.filter((column): column is string => column !== undefined);
if (targetColumn.length === 0) return;
return [
{
...event,
name: activity[0].type.toLowerCase() + '.' + event.name.split('.')[1],
properties: {},
objectName: targetColumn[0].replace(/Id$/, ''),
recordId: activityTarget[0][targetColumn[0]],
linkedRecordCachedName: activity[0].title,
linkedRecordId: activity[0].id,
linkedObjectMetadataId: activityObjectMetadataId,
},
] as TransformedEvent[];
}
}