Update what is being audit logged (#11833)

No need to audit log workflow runs as it's already a form of audit log.
Add more audit log for other objects
Rename MessagingTelemetry to MessagingMonitoring
Merge Analytics and Audit in one (Audit)

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
Félix Malfait
2025-05-04 14:35:41 +02:00
committed by GitHub
parent b1994f3707
commit 49b7f5255f
101 changed files with 948 additions and 1032 deletions

View File

@ -1,71 +0,0 @@
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 { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event.type';
import { AuditLogRepository } from 'src/modules/timeline/repositiories/audit-log.repository';
import { AuditLogWorkspaceEntity } from 'src/modules/timeline/standard-objects/audit-log.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 { AnalyticsService } from 'src/engine/core-modules/analytics/services/analytics.service';
import { OBJECT_RECORD_UPDATED_EVENT } from 'src/engine/core-modules/analytics/utils/events/track/object-record/object-record-updated';
import { OBJECT_RECORD_CREATED_EVENT } from 'src/engine/core-modules/analytics/utils/events/track/object-record/object-record-created';
import { OBJECT_RECORD_DELETED_EVENT } from 'src/engine/core-modules/analytics/utils/events/track/object-record/object-record-delete';
@Processor(MessageQueue.entityEventsToDbQueue)
export class CreateAuditLogFromInternalEvent {
constructor(
@InjectObjectMetadataRepository(WorkspaceMemberWorkspaceEntity)
private readonly workspaceMemberService: WorkspaceMemberRepository,
@InjectObjectMetadataRepository(AuditLogWorkspaceEntity)
private readonly auditLogRepository: AuditLogRepository,
private readonly analyticsService: AnalyticsService,
) {}
@Process(CreateAuditLogFromInternalEvent.name)
async handle(
workspaceEventBatch: WorkspaceEventBatch<ObjectRecordEvent>,
): Promise<void> {
for (const eventData of workspaceEventBatch.events) {
let workspaceMemberId: string | null = null;
if (eventData.userId) {
const workspaceMember = await this.workspaceMemberService.getByIdOrFail(
eventData.userId,
workspaceEventBatch.workspaceId,
);
workspaceMemberId = workspaceMember.id;
}
await this.auditLogRepository.insert(
workspaceEventBatch.name,
'diff' in eventData.properties
? {
// we remove "before" and "after" property for a cleaner/slimmer event payload
diff: eventData.properties.diff,
}
: eventData.properties,
workspaceMemberId,
workspaceEventBatch.name.split('.')[0],
eventData.objectMetadata.id,
eventData.recordId,
workspaceEventBatch.workspaceId,
);
const analytics = this.analyticsService.createAnalyticsContext({
workspaceId: workspaceEventBatch.workspaceId,
userId: eventData.userId,
});
if (workspaceEventBatch.name.endsWith('.updated')) {
analytics.track(OBJECT_RECORD_UPDATED_EVENT, eventData.properties);
} else if (workspaceEventBatch.name.endsWith('.created')) {
analytics.track(OBJECT_RECORD_CREATED_EVENT, eventData.properties);
} else if (workspaceEventBatch.name.endsWith('.deleted')) {
analytics.track(OBJECT_RECORD_DELETED_EVENT, eventData.properties);
}
}
}
}

View File

@ -1,25 +1,17 @@
import { Module } from '@nestjs/common';
import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
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 { AuditLogWorkspaceEntity } from 'src/modules/timeline/standard-objects/audit-log.workspace-entity';
import { TimelineActivityModule } from 'src/modules/timeline/timeline-activity.module';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { AnalyticsModule } from 'src/engine/core-modules/analytics/analytics.module';
@Module({
imports: [
ObjectMetadataRepositoryModule.forFeature([
WorkspaceMemberWorkspaceEntity,
AuditLogWorkspaceEntity,
]),
ObjectMetadataRepositoryModule.forFeature([WorkspaceMemberWorkspaceEntity]),
TimelineActivityModule,
AnalyticsModule,
],
providers: [
CreateAuditLogFromInternalEvent,
UpsertTimelineActivityFromInternalEvent,
AuditModule,
],
providers: [UpsertTimelineActivityFromInternalEvent],
})
export class TimelineJobModule {}

View File

@ -1,38 +0,0 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
@Injectable()
export class AuditLogRepository {
constructor(
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
) {}
public async insert(
name: string,
properties: object | null,
workspaceMemberId: string | null,
objectName: string,
objectMetadataId: string,
recordId: string,
workspaceId: string,
): Promise<void> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
await this.workspaceDataSourceService.executeRawQuery(
`INSERT INTO ${dataSourceSchema}."auditLog"
("name", "properties", "workspaceMemberId", "objectName", "objectMetadataId", "recordId")
VALUES ($1, $2, $3, $4, $5, $6)`,
[
name,
properties,
workspaceMemberId,
objectName,
objectMetadataId,
recordId,
],
workspaceId,
);
}
}

View File

@ -1,103 +0,0 @@
import { msg } from '@lingui/core/macro';
import { FieldMetadataType } from 'twenty-shared/types';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-on-delete-action.interface';
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator';
import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator';
import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator';
import { AUDIT_LOGS_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { STANDARD_OBJECT_ICONS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-icons';
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
@WorkspaceEntity({
standardId: STANDARD_OBJECT_IDS.auditLog,
namePlural: 'auditLogs',
labelSingular: msg`Audit Log`,
labelPlural: msg`Audit Logs`,
description: msg`An audit log of actions performed in the system`,
icon: STANDARD_OBJECT_ICONS.auditLog,
labelIdentifierStandardId: AUDIT_LOGS_STANDARD_FIELD_IDS.name,
})
@WorkspaceIsSystem()
export class AuditLogWorkspaceEntity extends BaseWorkspaceEntity {
@WorkspaceField({
standardId: AUDIT_LOGS_STANDARD_FIELD_IDS.name,
type: FieldMetadataType.TEXT,
label: msg`Event name`,
description: msg`Event name/type`,
icon: 'IconAbc',
})
name: string;
@WorkspaceField({
standardId: AUDIT_LOGS_STANDARD_FIELD_IDS.properties,
type: FieldMetadataType.RAW_JSON,
label: msg`Event details`,
description: msg`Json value for event details`,
icon: 'IconListDetails',
})
@WorkspaceIsNullable()
properties: JSON | null;
@WorkspaceField({
standardId: AUDIT_LOGS_STANDARD_FIELD_IDS.context,
type: FieldMetadataType.RAW_JSON,
label: msg`Event context`,
description: msg`Json object to provide context (user, device, workspace, etc.)`,
icon: 'IconListDetails',
})
@WorkspaceIsNullable()
context: JSON | null;
@WorkspaceField({
standardId: AUDIT_LOGS_STANDARD_FIELD_IDS.objectName,
type: FieldMetadataType.TEXT,
label: msg`Object name`,
description: msg`Object name`,
icon: 'IconAbc',
})
objectName: string;
@WorkspaceField({
standardId: AUDIT_LOGS_STANDARD_FIELD_IDS.objectMetadataId,
type: FieldMetadataType.TEXT,
label: msg`Object metadata id`,
description: msg`Object metadata id`,
icon: 'IconAbc',
})
objectMetadataId: string;
@WorkspaceField({
standardId: AUDIT_LOGS_STANDARD_FIELD_IDS.recordId,
type: FieldMetadataType.UUID,
label: msg`Record id`,
description: msg`Record id`,
icon: 'IconAbc',
})
@WorkspaceIsNullable()
recordId: string | null;
@WorkspaceRelation({
standardId: AUDIT_LOGS_STANDARD_FIELD_IDS.workspaceMember,
type: RelationType.MANY_TO_ONE,
label: msg`Workspace Member`,
description: msg`Event workspace member`,
icon: 'IconCircleUser',
inverseSideTarget: () => WorkspaceMemberWorkspaceEntity,
inverseSideFieldKey: 'auditLogs',
onDelete: RelationOnDeleteAction.SET_NULL,
})
@WorkspaceIsNullable()
workspaceMember: Relation<WorkspaceMemberWorkspaceEntity> | null;
@WorkspaceJoinColumn('workspaceMember')
workspaceMemberId: string | null;
}

View File

@ -1,92 +0,0 @@
import { msg } from '@lingui/core/macro';
import { FieldMetadataType } from 'twenty-shared/types';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator';
import { WorkspaceGate } from 'src/engine/twenty-orm/decorators/workspace-gate.decorator';
import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
import { BEHAVIORAL_EVENT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { STANDARD_OBJECT_ICONS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-icons';
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
@WorkspaceEntity({
standardId: STANDARD_OBJECT_IDS.behavioralEvent,
namePlural: 'behavioralEvents',
labelSingular: msg`Behavioral Event`,
labelPlural: msg`Behavioral Events`,
description: msg`An event related to user behavior`,
icon: STANDARD_OBJECT_ICONS.behavioralEvent,
})
@WorkspaceIsSystem()
@WorkspaceGate({
featureFlag: FeatureFlagKey.IsEventObjectEnabled,
})
export class BehavioralEventWorkspaceEntity extends BaseWorkspaceEntity {
/**
*
* Common in Segment, Rudderstack, etc.
* = Track, Screen, Page...
* But doesn't feel that useful.
* Let's try living without it.
*
@WorkspaceField({
standardId: behavioralEventStandardFieldIds.type,
type: FieldMetadataType.TEXT,
label: msg`Event type`,
description: msg`Event type`,
icon: 'IconAbc',
})
type: string;
*/
@WorkspaceField({
standardId: BEHAVIORAL_EVENT_STANDARD_FIELD_IDS.name,
type: FieldMetadataType.TEXT,
label: msg`Event name`,
description: msg`Event name`,
icon: 'IconAbc',
})
name: string;
@WorkspaceField({
standardId: BEHAVIORAL_EVENT_STANDARD_FIELD_IDS.properties,
type: FieldMetadataType.RAW_JSON,
label: msg`Event details`,
description: msg`Json value for event details`,
icon: 'IconListDetails',
})
@WorkspaceIsNullable()
properties: JSON | null;
@WorkspaceField({
standardId: BEHAVIORAL_EVENT_STANDARD_FIELD_IDS.context,
type: FieldMetadataType.RAW_JSON,
label: msg`Event context`,
description: msg`Json object to provide context (user, device, workspace, etc.)`,
icon: 'IconListDetails',
})
@WorkspaceIsNullable()
context: JSON | null;
@WorkspaceField({
standardId: BEHAVIORAL_EVENT_STANDARD_FIELD_IDS.objectName,
type: FieldMetadataType.TEXT,
label: msg`Object name`,
description: msg`If the event is related to a particular object`,
icon: 'IconAbc',
})
objectName: string;
@WorkspaceField({
standardId: BEHAVIORAL_EVENT_STANDARD_FIELD_IDS.recordId,
type: FieldMetadataType.UUID,
label: msg`Object id`,
description: msg`Event name/type`,
icon: 'IconAbc',
})
@WorkspaceIsNullable()
recordId: string | null;
}