bugfix: escape destroyed objects on workers (#9719)

# This PR

- Fixes #9358 

@FelixMalfait please check this workaround

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
P A C - 先生
2025-01-23 17:29:54 +02:00
committed by GitHub
parent bbb0c9a761
commit bbd3af108b
11 changed files with 134 additions and 80 deletions

View File

@ -1,17 +1,21 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { OnDatabaseBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-batch-event.decorator';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event'; import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event';
import { ObjectRecordDestroyEvent } from 'src/engine/core-modules/event-emitter/types/object-record-destroy.event';
import { ObjectRecordEvent } from 'src/engine/core-modules/event-emitter/types/object-record-event.event';
import { ObjectRecordNonDestructiveEvent } from 'src/engine/core-modules/event-emitter/types/object-record-non-destructive-event';
import { ObjectRecordRestoreEvent } from 'src/engine/core-modules/event-emitter/types/object-record-restore.event';
import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event'; import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event';
import { ObjectRecordBaseEvent } from 'src/engine/core-modules/event-emitter/types/object-record.base.event';
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; 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'; 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'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event.type'; import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event.type';
import { CreateAuditLogFromInternalEvent } from 'src/modules/timeline/jobs/create-audit-log-from-internal-event'; import { CreateAuditLogFromInternalEvent } from 'src/modules/timeline/jobs/create-audit-log-from-internal-event';
import { UpsertTimelineActivityFromInternalEvent } from 'src/modules/timeline/jobs/upsert-timeline-activity-from-internal-event.job'; import { UpsertTimelineActivityFromInternalEvent } from 'src/modules/timeline/jobs/upsert-timeline-activity-from-internal-event.job';
import { OnDatabaseBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-batch-event.decorator';
import { CallWebhookJobsJob } from 'src/modules/webhook/jobs/call-webhook-jobs.job'; import { CallWebhookJobsJob } from 'src/modules/webhook/jobs/call-webhook-jobs.job';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
@Injectable() @Injectable()
export class EntityEventsToDbListener { export class EntityEventsToDbListener {
@ -24,47 +28,66 @@ export class EntityEventsToDbListener {
@OnDatabaseBatchEvent('*', DatabaseEventAction.CREATED) @OnDatabaseBatchEvent('*', DatabaseEventAction.CREATED)
async handleCreate(batchEvent: WorkspaceEventBatch<ObjectRecordCreateEvent>) { async handleCreate(batchEvent: WorkspaceEventBatch<ObjectRecordCreateEvent>) {
return this.handle(batchEvent); return this.handleEvent(batchEvent, DatabaseEventAction.CREATED);
} }
@OnDatabaseBatchEvent('*', DatabaseEventAction.UPDATED) @OnDatabaseBatchEvent('*', DatabaseEventAction.UPDATED)
async handleUpdate(batchEvent: WorkspaceEventBatch<ObjectRecordUpdateEvent>) { async handleUpdate(batchEvent: WorkspaceEventBatch<ObjectRecordUpdateEvent>) {
return this.handle(batchEvent); return this.handleEvent(batchEvent, DatabaseEventAction.UPDATED);
} }
@OnDatabaseBatchEvent('*', DatabaseEventAction.DELETED) @OnDatabaseBatchEvent('*', DatabaseEventAction.DELETED)
async handleDelete(batchEvent: WorkspaceEventBatch<ObjectRecordUpdateEvent>) { async handleDelete(batchEvent: WorkspaceEventBatch<ObjectRecordDeleteEvent>) {
return this.handle(batchEvent); return this.handleEvent(batchEvent, DatabaseEventAction.DELETED);
}
@OnDatabaseBatchEvent('*', DatabaseEventAction.RESTORED)
async handleRestore(
batchEvent: WorkspaceEventBatch<ObjectRecordRestoreEvent>,
) {
return this.handleEvent(batchEvent, DatabaseEventAction.RESTORED);
} }
@OnDatabaseBatchEvent('*', DatabaseEventAction.DESTROYED) @OnDatabaseBatchEvent('*', DatabaseEventAction.DESTROYED)
async handleDestroy( async handleDestroy(
batchEvent: WorkspaceEventBatch<ObjectRecordUpdateEvent>, batchEvent: WorkspaceEventBatch<ObjectRecordDestroyEvent>,
) { ) {
return this.handle(batchEvent); return this.handleEvent(batchEvent, DatabaseEventAction.DESTROYED);
} }
private async handle(batchEvent: WorkspaceEventBatch<ObjectRecordBaseEvent>) { private async handleEvent<T extends ObjectRecordEvent>(
batchEvent: WorkspaceEventBatch<T>,
action: DatabaseEventAction,
) {
const filteredEvents = batchEvent.events.filter( const filteredEvents = batchEvent.events.filter(
(event) => event.objectMetadata?.isAuditLogged, (event) => event.objectMetadata?.isAuditLogged,
); );
await this.entityEventsToDbQueueService.add< await Promise.all([
WorkspaceEventBatch<ObjectRecordBaseEvent> this.webhookQueueService.add<WorkspaceEventBatch<T>>(
>(CreateAuditLogFromInternalEvent.name, { CallWebhookJobsJob.name,
...batchEvent, batchEvent,
events: filteredEvents, {
}); retryLimit: 3,
},
await this.entityEventsToDbQueueService.add< ),
WorkspaceEventBatch<ObjectRecordBaseEvent> this.entityEventsToDbQueueService.add<WorkspaceEventBatch<T>>(
>(UpsertTimelineActivityFromInternalEvent.name, { CreateAuditLogFromInternalEvent.name,
...batchEvent, {
events: filteredEvents, ...batchEvent,
}); events: filteredEvents,
},
await this.webhookQueueService.add< ),
WorkspaceEventBatch<ObjectRecordBaseEvent> ...(action !== DatabaseEventAction.DESTROYED
>(CallWebhookJobsJob.name, batchEvent, { retryLimit: 3 }); ? [
this.entityEventsToDbQueueService.add<
WorkspaceEventBatch<ObjectRecordNonDestructiveEvent>
>(UpsertTimelineActivityFromInternalEvent.name, {
...batchEvent,
events: filteredEvents,
}),
]
: []),
]);
} }
} }

View File

@ -17,6 +17,7 @@ import { MicrosoftAPIsService } from 'src/engine/core-modules/auth/services/micr
// import { OAuthService } from 'src/engine/core-modules/auth/services/oauth.service'; // import { OAuthService } from 'src/engine/core-modules/auth/services/oauth.service';
import { ResetPasswordService } from 'src/engine/core-modules/auth/services/reset-password.service'; import { ResetPasswordService } from 'src/engine/core-modules/auth/services/reset-password.service';
import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-up.service'; import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-up.service';
import { SocialSsoService } from 'src/engine/core-modules/auth/services/social-sso.service';
import { SamlAuthStrategy } from 'src/engine/core-modules/auth/strategies/saml.auth.strategy'; import { SamlAuthStrategy } from 'src/engine/core-modules/auth/strategies/saml.auth.strategy';
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service'; import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service'; import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
@ -43,7 +44,6 @@ import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-s
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module'; import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module';
import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module'; import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module';
import { SocialSsoService } from 'src/engine/core-modules/auth/services/social-sso.service';
import { AuthResolver } from './auth.resolver'; import { AuthResolver } from './auth.resolver';

View File

@ -0,0 +1,12 @@
import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event';
import { ObjectRecordDestroyEvent } from 'src/engine/core-modules/event-emitter/types/object-record-destroy.event';
import { ObjectRecordRestoreEvent } from 'src/engine/core-modules/event-emitter/types/object-record-restore.event';
import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event';
export type ObjectRecordEvent<T = object> =
| ObjectRecordUpdateEvent<T>
| ObjectRecordDeleteEvent<T>
| ObjectRecordCreateEvent<T>
| ObjectRecordDestroyEvent<T>
| ObjectRecordRestoreEvent<T>;

View File

@ -0,0 +1,10 @@
import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event';
import { ObjectRecordRestoreEvent } from 'src/engine/core-modules/event-emitter/types/object-record-restore.event';
import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event';
export type ObjectRecordNonDestructiveEvent =
| ObjectRecordCreateEvent
| ObjectRecordUpdateEvent
| ObjectRecordDeleteEvent
| ObjectRecordRestoreEvent;

View File

@ -1,9 +1,9 @@
import { ObjectRecordBaseEvent } from 'src/engine/core-modules/event-emitter/types/object-record.base.event'; import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
export class ObjectRecordRestoreEvent< export class ObjectRecordRestoreEvent<
T = object, T = object,
> extends ObjectRecordBaseEvent<T> { > extends ObjectRecordCreateEvent<T> {
properties: { properties: {
before: T; after: T;
}; };
} }

View File

@ -1,13 +1,13 @@
import { ObjectRecordBaseEvent } from 'src/engine/core-modules/event-emitter/types/object-record.base.event';
import { ObjectRecordDiff } from 'src/engine/core-modules/event-emitter/types/object-record-diff'; import { ObjectRecordDiff } from 'src/engine/core-modules/event-emitter/types/object-record-diff';
import { ObjectRecordBaseEvent } from 'src/engine/core-modules/event-emitter/types/object-record.base.event';
export class ObjectRecordUpdateEvent< export class ObjectRecordUpdateEvent<
T = object, T = object,
> extends ObjectRecordBaseEvent<T> { > extends ObjectRecordBaseEvent<T> {
properties: { properties: {
updatedFields?: string[]; updatedFields?: string[];
diff?: Partial<ObjectRecordDiff<T>>;
before: T; before: T;
after: T; after: T;
diff?: Partial<ObjectRecordDiff<T>>;
}; };
} }

View File

@ -1,5 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { OnDatabaseBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-batch-event.decorator';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event'; import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event'; import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event';
import { objectRecordChangedProperties as objectRecordUpdateEventChangedProperties } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-properties.util'; import { objectRecordChangedProperties as objectRecordUpdateEventChangedProperties } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-properties.util';
@ -16,8 +18,6 @@ import {
MessageParticipantUnmatchParticipantJobData, MessageParticipantUnmatchParticipantJobData,
} from 'src/modules/messaging/message-participant-manager/jobs/message-participant-unmatch-participant.job'; } from 'src/modules/messaging/message-participant-manager/jobs/message-participant-unmatch-participant.job';
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity'; import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
import { OnDatabaseBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-batch-event.decorator';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
@Injectable() @Injectable()
export class MessageParticipantPersonListener { export class MessageParticipantPersonListener {

View File

@ -1,4 +1,4 @@
import { ObjectRecordBaseEvent } from 'src/engine/core-modules/event-emitter/types/object-record.base.event'; import { ObjectRecordEvent } from 'src/engine/core-modules/event-emitter/types/object-record-event.event';
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
@ -20,7 +20,7 @@ export class CreateAuditLogFromInternalEvent {
@Process(CreateAuditLogFromInternalEvent.name) @Process(CreateAuditLogFromInternalEvent.name)
async handle( async handle(
workspaceEventBatch: WorkspaceEventBatch<ObjectRecordBaseEvent>, workspaceEventBatch: WorkspaceEventBatch<ObjectRecordEvent>,
): Promise<void> { ): Promise<void> {
for (const eventData of workspaceEventBatch.events) { for (const eventData of workspaceEventBatch.events) {
let workspaceMemberId: string | null = null; let workspaceMemberId: string | null = null;
@ -34,16 +34,14 @@ export class CreateAuditLogFromInternalEvent {
workspaceMemberId = workspaceMember.id; workspaceMemberId = workspaceMember.id;
} }
if (eventData.properties.diff) {
// we remove "before" and "after" property for a cleaner/slimmer event payload
eventData.properties = {
diff: eventData.properties.diff,
};
}
await this.auditLogRepository.insert( await this.auditLogRepository.insert(
workspaceEventBatch.name, workspaceEventBatch.name,
eventData.properties, 'diff' in eventData.properties
? {
// we remove "before" and "after" property for a cleaner/slimmer event payload
diff: eventData.properties.diff,
}
: eventData.properties,
workspaceMemberId, workspaceMemberId,
workspaceEventBatch.name.split('.')[0], workspaceEventBatch.name.split('.')[0],
eventData.objectMetadata.id, eventData.objectMetadata.id,

View File

@ -1,4 +1,4 @@
import { ObjectRecordBaseEvent } from 'src/engine/core-modules/event-emitter/types/object-record.base.event'; import { ObjectRecordNonDestructiveEvent } from 'src/engine/core-modules/event-emitter/types/object-record-non-destructive-event';
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
@ -7,7 +7,6 @@ import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/wo
import { TimelineActivityService } from 'src/modules/timeline/services/timeline-activity.service'; import { TimelineActivityService } from 'src/modules/timeline/services/timeline-activity.service';
import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository'; 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 { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
@Processor(MessageQueue.entityEventsToDbQueue) @Processor(MessageQueue.entityEventsToDbQueue)
export class UpsertTimelineActivityFromInternalEvent { export class UpsertTimelineActivityFromInternalEvent {
@ -19,9 +18,7 @@ export class UpsertTimelineActivityFromInternalEvent {
@Process(UpsertTimelineActivityFromInternalEvent.name) @Process(UpsertTimelineActivityFromInternalEvent.name)
async handle( async handle(
workspaceEventBatch: WorkspaceEventBatch< workspaceEventBatch: WorkspaceEventBatch<ObjectRecordNonDestructiveEvent>,
ObjectRecordBaseEvent<TimelineActivityWorkspaceEntity>
>,
): Promise<void> { ): Promise<void> {
for (const eventData of workspaceEventBatch.events) { for (const eventData of workspaceEventBatch.events) {
if (eventData.userId) { if (eventData.userId) {
@ -33,13 +30,6 @@ export class UpsertTimelineActivityFromInternalEvent {
eventData.workspaceMemberId = workspaceMember.id; eventData.workspaceMemberId = workspaceMember.id;
} }
if (eventData.properties.diff) {
// we remove "before" and "after" property for a cleaner/slimmer event payload
eventData.properties = {
diff: eventData.properties.diff,
};
}
// Temporary // Temporary
// We ignore every that is not a LinkedObject or a Business Object // We ignore every that is not a LinkedObject or a Business Object
if ( if (
@ -51,7 +41,16 @@ export class UpsertTimelineActivityFromInternalEvent {
} }
await this.timelineActivityService.upsertEvent({ await this.timelineActivityService.upsertEvent({
event: eventData, event:
// we remove "before" and "after" property for a cleaner/slimmer event payload
'diff' in eventData.properties && eventData.properties.diff
? {
...eventData,
properties: {
diff: eventData.properties.diff,
},
}
: eventData,
eventName: workspaceEventBatch.name, eventName: workspaceEventBatch.name,
workspaceId: workspaceEventBatch.workspaceId, workspaceId: workspaceEventBatch.workspaceId,
}); });

View File

@ -1,19 +1,20 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { ObjectRecordNonDestructiveEvent } from 'src/engine/core-modules/event-emitter/types/object-record-non-destructive-event';
import { ObjectRecordBaseEvent } from 'src/engine/core-modules/event-emitter/types/object-record.base.event'; import { ObjectRecordBaseEvent } from 'src/engine/core-modules/event-emitter/types/object-record.base.event';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { TimelineActivityRepository } from 'src/modules/timeline/repositiories/timeline-activity.repository'; import { TimelineActivityRepository } from 'src/modules/timeline/repositiories/timeline-activity.repository';
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity'; import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
type TimelineActivity = type TimelineActivity = Omit<ObjectRecordNonDestructiveEvent, 'properties'> & {
ObjectRecordBaseEvent<TimelineActivityWorkspaceEntity> & { name: string;
name: string; objectName?: string;
objectName?: string; linkedRecordCachedName?: string;
linkedRecordCachedName?: string; linkedRecordId?: string;
linkedRecordId?: string; linkedObjectMetadataId?: string;
linkedObjectMetadataId?: string; properties: Record<string, any>; // more relaxed conditions than for internal events
}; };
@Injectable() @Injectable()
export class TimelineActivityService { export class TimelineActivityService {
@ -33,7 +34,7 @@ export class TimelineActivityService {
eventName, eventName,
workspaceId, workspaceId,
}: { }: {
event: ObjectRecordBaseEvent<TimelineActivityWorkspaceEntity>; event: ObjectRecordBaseEvent;
eventName: string; eventName: string;
workspaceId: string; workspaceId: string;
}) { }) {
@ -65,7 +66,7 @@ export class TimelineActivityService {
workspaceId, workspaceId,
eventName, eventName,
}: { }: {
event: ObjectRecordBaseEvent<TimelineActivityWorkspaceEntity>; event: ObjectRecordBaseEvent;
workspaceId: string; workspaceId: string;
eventName: string; eventName: string;
}): Promise<TimelineActivity[] | undefined> { }): Promise<TimelineActivity[] | undefined> {
@ -78,7 +79,10 @@ export class TimelineActivityService {
// 2 timelines, one for the linked object and one for the task/note // 2 timelines, one for the linked object and one for the task/note
if (linkedTimelineActivities && linkedTimelineActivities?.length > 0) if (linkedTimelineActivities && linkedTimelineActivities?.length > 0)
return [...linkedTimelineActivities, { ...event, name: eventName }]; return [
...linkedTimelineActivities,
{ ...event, name: eventName },
] satisfies TimelineActivity[];
} }
if ( if (
@ -93,7 +97,7 @@ export class TimelineActivityService {
}); });
} }
return [{ ...event, name: eventName }]; return [{ ...event, name: eventName }] satisfies TimelineActivity[];
} }
private async getLinkedTimelineActivities({ private async getLinkedTimelineActivities({
@ -101,7 +105,7 @@ export class TimelineActivityService {
workspaceId, workspaceId,
eventName, eventName,
}: { }: {
event: ObjectRecordBaseEvent<TimelineActivityWorkspaceEntity>; event: ObjectRecordBaseEvent;
workspaceId: string; workspaceId: string;
eventName: string; eventName: string;
}): Promise<TimelineActivity[] | undefined> { }): Promise<TimelineActivity[] | undefined> {
@ -146,7 +150,7 @@ export class TimelineActivityService {
eventName, eventName,
workspaceId, workspaceId,
}: { }: {
event: ObjectRecordBaseEvent<TimelineActivityWorkspaceEntity>; event: ObjectRecordBaseEvent;
dataSourceSchema: string; dataSourceSchema: string;
activityType: string; activityType: string;
eventName: string; eventName: string;
@ -195,7 +199,7 @@ export class TimelineActivityService {
linkedRecordCachedName: activity[0].title, linkedRecordCachedName: activity[0].title,
linkedRecordId: activity[0].id, linkedRecordId: activity[0].id,
linkedObjectMetadataId: event.objectMetadata.id, linkedObjectMetadataId: event.objectMetadata.id,
} as TimelineActivity; } satisfies TimelineActivity;
}) })
.filter((event): event is TimelineActivity => event !== undefined); .filter((event): event is TimelineActivity => event !== undefined);
} }
@ -207,7 +211,7 @@ export class TimelineActivityService {
eventName, eventName,
workspaceId, workspaceId,
}: { }: {
event: ObjectRecordBaseEvent<TimelineActivityWorkspaceEntity>; event: ObjectRecordBaseEvent;
dataSourceSchema: string; dataSourceSchema: string;
activityType: string; activityType: string;
eventName: string; eventName: string;
@ -258,7 +262,7 @@ export class TimelineActivityService {
linkedRecordCachedName: activity[0].title, linkedRecordCachedName: activity[0].title,
linkedRecordId: activity[0].id, linkedRecordId: activity[0].id,
linkedObjectMetadataId: activityObjectMetadataId, linkedObjectMetadataId: activityObjectMetadataId,
} as TimelineActivity, } satisfies TimelineActivity,
]; ];
} }
} }

View File

@ -2,19 +2,19 @@ import { Logger } from '@nestjs/common';
import { ArrayContains } from 'typeorm'; import { ArrayContains } from 'typeorm';
import { ObjectRecordEvent } from 'src/engine/core-modules/event-emitter/types/object-record-event.event';
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; 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'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { WebhookWorkspaceEntity } from 'src/modules/webhook/standard-objects/webhook.workspace-entity';
import { ObjectRecordBaseEvent } from 'src/engine/core-modules/event-emitter/types/object-record.base.event';
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event.type'; import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event.type';
import { import {
CallWebhookJob, CallWebhookJob,
CallWebhookJobData, CallWebhookJobData,
} from 'src/modules/webhook/jobs/call-webhook.job'; } from 'src/modules/webhook/jobs/call-webhook.job';
import { WebhookWorkspaceEntity } from 'src/modules/webhook/standard-objects/webhook.workspace-entity';
import { removeSecretFromWebhookRecord } from 'src/utils/remove-secret-from-webhook-record'; import { removeSecretFromWebhookRecord } from 'src/utils/remove-secret-from-webhook-record';
@Processor(MessageQueue.webhookQueue) @Processor(MessageQueue.webhookQueue)
@ -29,7 +29,7 @@ export class CallWebhookJobsJob {
@Process(CallWebhookJobsJob.name) @Process(CallWebhookJobsJob.name)
async handle( async handle(
workspaceEventBatch: WorkspaceEventBatch<ObjectRecordBaseEvent>, workspaceEventBatch: WorkspaceEventBatch<ObjectRecordEvent>,
): Promise<void> { ): Promise<void> {
// If you change that function, double check it does not break Zapier // If you change that function, double check it does not break Zapier
// trigger in packages/twenty-zapier/src/triggers/trigger_record.ts // trigger in packages/twenty-zapier/src/triggers/trigger_record.ts
@ -60,8 +60,16 @@ export class CallWebhookJobsJob {
nameSingular: eventData.objectMetadata.nameSingular, nameSingular: eventData.objectMetadata.nameSingular,
}; };
const workspaceId = workspaceEventBatch.workspaceId; const workspaceId = workspaceEventBatch.workspaceId;
const record = eventData.properties.after || eventData.properties.before; const record =
const updatedFields = eventData.properties.updatedFields; 'after' in eventData.properties
? eventData.properties.after
: 'before' in eventData.properties
? eventData.properties.before
: {};
const updatedFields =
'updatedFields' in eventData.properties
? eventData.properties.updatedFields
: undefined;
const isWebhookEvent = nameSingular === 'webhook'; const isWebhookEvent = nameSingular === 'webhook';
const sanitizedRecord = removeSecretFromWebhookRecord( const sanitizedRecord = removeSecretFromWebhookRecord(