diff --git a/packages/twenty-server/nest-cli.json b/packages/twenty-server/nest-cli.json index 5b2c40a65..047cfbae8 100644 --- a/packages/twenty-server/nest-cli.json +++ b/packages/twenty-server/nest-cli.json @@ -27,7 +27,7 @@ "outDir": "dist/assets" }, { - "include": "**/database/clickhouse/migrations/*.sql", + "include": "**/database/clickHouse/migrations/*.sql", "outDir": "dist/src" } ], diff --git a/packages/twenty-server/package.json b/packages/twenty-server/package.json index dd13be41d..c593e1516 100644 --- a/packages/twenty-server/package.json +++ b/packages/twenty-server/package.json @@ -11,7 +11,7 @@ "worker:prod": "node dist/src/queue-worker/queue-worker", "database:init:prod": "npx ts-node ./scripts/setup-db.ts && yarn database:migrate:prod", "database:migrate:prod": "npx -y typeorm migration:run -d dist/src/database/typeorm/metadata/metadata.datasource && npx -y typeorm migration:run -d dist/src/database/typeorm/core/core.datasource", - "clickhouse:migrate:prod": "node dist/src/database/clickhouse/migrations/run-migrations.js", + "clickhouse:migrate:prod": "node dist/src/database/clickHouse/migrations/run-migrations.js", "typeorm": "../../node_modules/typeorm/.bin/typeorm" }, "dependencies": { diff --git a/packages/twenty-server/src/database/clickHouse/migrations/001-create-audit-event-table.sql b/packages/twenty-server/src/database/clickHouse/migrations/001-create-audit-event-table.sql deleted file mode 100644 index 0e28d9aa4..000000000 --- a/packages/twenty-server/src/database/clickHouse/migrations/001-create-audit-event-table.sql +++ /dev/null @@ -1,10 +0,0 @@ -CREATE TABLE IF NOT EXISTS auditEvent -( - `event` LowCardinality(String), - `timestamp` DateTime64(3), - `userId` String DEFAULT '', - `workspaceId` String DEFAULT '', - `properties` JSON -) - ENGINE = MergeTree - ORDER BY (event, workspaceId, userId, timestamp); \ No newline at end of file diff --git a/packages/twenty-server/src/database/clickHouse/migrations/001-create-workspace-event-table.sql b/packages/twenty-server/src/database/clickHouse/migrations/001-create-workspace-event-table.sql new file mode 100644 index 000000000..f73fd11e0 --- /dev/null +++ b/packages/twenty-server/src/database/clickHouse/migrations/001-create-workspace-event-table.sql @@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS workspaceEvent +( + `event` LowCardinality(String) NOT NULL, + `timestamp` DateTime64(3) NOT NULL, + `userId` String DEFAULT '', + `workspaceId` String NOT NULL, + `properties` JSON +) + ENGINE = MergeTree + ORDER BY (workspaceId, event, userId, timestamp); \ No newline at end of file diff --git a/packages/twenty-server/src/database/clickHouse/migrations/002-create-pageview-table.sql b/packages/twenty-server/src/database/clickHouse/migrations/002-create-pageview-table.sql index 6ed0959af..3b77e2f49 100644 --- a/packages/twenty-server/src/database/clickHouse/migrations/002-create-pageview-table.sql +++ b/packages/twenty-server/src/database/clickHouse/migrations/002-create-pageview-table.sql @@ -1,10 +1,10 @@ CREATE TABLE IF NOT EXISTS pageview ( - `name` LowCardinality(String), - `timestamp` DateTime64(3), - `properties` JSON, + `name` LowCardinality(String) NOT NULL, + `timestamp` DateTime64(3) NOT NULL, `userId` String DEFAULT '', - `workspaceId` String DEFAULT '' + `workspaceId` String DEFAULT '', + `properties` JSON ) ENGINE = MergeTree - ORDER BY (name, workspaceId, userId, timestamp); + ORDER BY (workspaceId, name, userId, timestamp); \ No newline at end of file diff --git a/packages/twenty-server/src/database/clickHouse/migrations/003-create-external-event-table.sql b/packages/twenty-server/src/database/clickHouse/migrations/003-create-external-event-table.sql deleted file mode 100644 index 19cc41020..000000000 --- a/packages/twenty-server/src/database/clickHouse/migrations/003-create-external-event-table.sql +++ /dev/null @@ -1,12 +0,0 @@ -CREATE TABLE IF NOT EXISTS externalEvent -( - `event` LowCardinality(String) NOT NULL, - `timestamp` DateTime64(3) NOT NULL, - `userId` String DEFAULT '', - `workspaceId` String NOT NULL, - `objectId` String NOT NULL, - `objectType` LowCardinality(String), -- TBC if it should really be a LowCardinality given custom objects - `properties` JSON -) - ENGINE = MergeTree - ORDER BY (event, workspaceId, userId, timestamp); \ No newline at end of file diff --git a/packages/twenty-server/src/database/clickHouse/migrations/003-create-object-event-table.sql b/packages/twenty-server/src/database/clickHouse/migrations/003-create-object-event-table.sql new file mode 100644 index 000000000..d20ae2864 --- /dev/null +++ b/packages/twenty-server/src/database/clickHouse/migrations/003-create-object-event-table.sql @@ -0,0 +1,13 @@ +CREATE TABLE IF NOT EXISTS objectEvent +( + `event` LowCardinality(String) NOT NULL, + `timestamp` DateTime64(3) NOT NULL, + `userId` String DEFAULT '', + `workspaceId` String NOT NULL, + `recordId` String NOT NULL, + `objectMetadataId` String NOT NULL, + `properties` JSON, + `isCustom` Boolean DEFAULT FALSE, +) + ENGINE = MergeTree + ORDER BY (workspaceId, event, userId, timestamp); \ No newline at end of file diff --git a/packages/twenty-server/src/database/clickHouse/migrations/run-migrations.ts b/packages/twenty-server/src/database/clickHouse/migrations/run-migrations.ts index 43036d5a1..a6f918613 100644 --- a/packages/twenty-server/src/database/clickHouse/migrations/run-migrations.ts +++ b/packages/twenty-server/src/database/clickHouse/migrations/run-migrations.ts @@ -10,7 +10,7 @@ config({ override: true, }); -const clickhouseUrl = () => { +const clickHouseUrl = () => { const url = process.env.CLICKHOUSE_URL; if (url) return url; @@ -21,7 +21,7 @@ const clickhouseUrl = () => { }; async function ensureDatabaseExists() { - const [url, database] = clickhouseUrl().split(/\/(?=[^/]*$)/); + const [url, database] = clickHouseUrl().split(/\/(?=[^/]*$)/); const client = createClient({ url, }); @@ -74,7 +74,7 @@ async function runMigrations() { await ensureDatabaseExists(); const client = createClient({ - url: clickhouseUrl(), + url: clickHouseUrl(), clickhouse_settings: { allow_experimental_json_type: 1, }, diff --git a/packages/twenty-server/src/database/clickHouse/seeds/fixtures.ts b/packages/twenty-server/src/database/clickHouse/seeds/fixtures.ts index dfb3d07e9..94a98d226 100644 --- a/packages/twenty-server/src/database/clickHouse/seeds/fixtures.ts +++ b/packages/twenty-server/src/database/clickHouse/seeds/fixtures.ts @@ -1,9 +1,9 @@ -import { CUSTOM_DOMAIN_ACTIVATED_EVENT } from 'src/engine/core-modules/audit/utils/events/track/custom-domain/custom-domain-activated'; -import { CUSTOM_DOMAIN_DEACTIVATED_EVENT } from 'src/engine/core-modules/audit/utils/events/track/custom-domain/custom-domain-deactivated'; -import { OBJECT_RECORD_CREATED_EVENT } from 'src/engine/core-modules/audit/utils/events/track/object-record/object-record-created'; -import { OBJECT_RECORD_DELETED_EVENT } from 'src/engine/core-modules/audit/utils/events/track/object-record/object-record-delete'; -import { OBJECT_RECORD_UPDATED_EVENT } from 'src/engine/core-modules/audit/utils/events/track/object-record/object-record-updated'; -import { GenericTrackEvent } from 'src/engine/core-modules/audit/utils/events/track/track'; +import { OBJECT_RECORD_CREATED_EVENT } from 'src/engine/core-modules/audit/utils/events/object-event/object-record-created'; +import { OBJECT_RECORD_DELETED_EVENT } from 'src/engine/core-modules/audit/utils/events/object-event/object-record-delete'; +import { OBJECT_RECORD_UPDATED_EVENT } from 'src/engine/core-modules/audit/utils/events/object-event/object-record-updated'; +import { CUSTOM_DOMAIN_ACTIVATED_EVENT } from 'src/engine/core-modules/audit/utils/events/workspace-event/custom-domain/custom-domain-activated'; +import { CUSTOM_DOMAIN_DEACTIVATED_EVENT } from 'src/engine/core-modules/audit/utils/events/workspace-event/custom-domain/custom-domain-deactivated'; +import { GenericTrackEvent } from 'src/engine/core-modules/audit/utils/events/workspace-event/track'; export const fixtures: Array = [ { diff --git a/packages/twenty-server/src/database/clickHouse/seeds/run-seeds.ts b/packages/twenty-server/src/database/clickHouse/seeds/run-seeds.ts index f317f814d..797d58de4 100644 --- a/packages/twenty-server/src/database/clickHouse/seeds/run-seeds.ts +++ b/packages/twenty-server/src/database/clickHouse/seeds/run-seeds.ts @@ -18,7 +18,7 @@ async function seedEvents() { console.log(`⚡ Seeding ${fixtures.length} events...`); await client.insert({ - table: 'auditEvent', + table: 'workspaceEvent', values: fixtures, format: 'JSONEachRow', }); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/telemetry.listener.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/telemetry.listener.ts index 055d52f61..ab65ae17e 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/telemetry.listener.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/telemetry.listener.ts @@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common'; import { OnCustomBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-custom-batch-event.decorator'; import { USER_SIGNUP_EVENT_NAME } from 'src/engine/api/graphql/workspace-query-runner/constants/user-signup-event-name.constants'; import { AuditService } from 'src/engine/core-modules/audit/services/audit.service'; -import { USER_SIGNUP_EVENT } from 'src/engine/core-modules/audit/utils/events/track/user/user-signup'; +import { USER_SIGNUP_EVENT } from 'src/engine/core-modules/audit/utils/events/workspace-event/user/user-signup'; import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event'; import { TelemetryService } from 'src/engine/core-modules/telemetry/telemetry.service'; import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event.type'; @@ -26,7 +26,7 @@ export class TelemetryListener { userId: eventPayload.userId, workspaceId: payload.workspaceId, }) - .track(USER_SIGNUP_EVENT, {}); + .insertWorkspaceEvent(USER_SIGNUP_EVENT, {}); this.telemetryService.create( { diff --git a/packages/twenty-server/src/engine/core-modules/audit/README.md b/packages/twenty-server/src/engine/core-modules/audit/README.md index 33f0f381f..17dd3f7bf 100644 --- a/packages/twenty-server/src/engine/core-modules/audit/README.md +++ b/packages/twenty-server/src/engine/core-modules/audit/README.md @@ -6,7 +6,11 @@ This module provides analytics tracking functionality for the Twenty application ### Tracking Events -The `AuditService` provides a `createContext` method that returns an object with a `track` method. The `track` method is used to track events. +The `AuditService` provides a `createContext` method that returns an object with three methods: + +- `insertWorkspaceEvent`: For tracking workspace-level events +- `createObjectEvent`: For tracking object-level events that include record and metadata IDs +- `createPageviewEvent`: For tracking page views ```typescript import { Injectable } from '@nestjs/common'; @@ -24,10 +28,22 @@ export class MyService { userId: 'user-id', }); - // Track an event - // The event name will be autocompleted - // The properties will be type-checked based on the event name - analytics.track(CUSTOM_DOMAIN_ACTIVATED_EVENT, {}); + // Track a workspace event + analytics.insertWorkspaceEvent(CUSTOM_DOMAIN_ACTIVATED_EVENT, {}); + + // Track an object event + analytics.createObjectEvent(OBJECT_RECORD_CREATED_EVENT, { + recordId: 'record-id', + objectMetadataId: 'object-metadata-id', + // other properties + }); + + // Track a pageview + analytics.createPageviewEvent('page-name', { + href: '/path', + locale: 'en-US', + // other properties + }); } } ``` @@ -97,8 +113,9 @@ Creates an analytics context with the given user ID and workspace ID. Returns an object with the following methods: -- `track(event: T, properties: TrackEventProperties)`: Tracks an event with the given name and properties -- `pageview(name: string, properties: Partial)`: Tracks a pageview with the given name and properties +- `insertWorkspaceEvent(event: T, properties: TrackEventProperties)`: Tracks a workspace-level event +- `createObjectEvent(event: T, properties: TrackEventProperties & { recordId: string; objectMetadataId: string })`: Tracks an object-level event +- `createPageviewEvent(name: string, properties: Partial)`: Tracks a pageview ### Types @@ -128,16 +145,4 @@ This approach makes it easier to add new events without having to modify a compl #### PageviewProperties -A type that defines the structure of pageview properties: - -```typescript -type PageviewProperties = { - href: string; - locale: string; - pathname: string; - referrer: string; - sessionId: string; - timeZone: string; - userAgent: string; -}; -``` +Properties for pageview events, including href, locale, pathname, referrer, sessionId, timeZone, and userAgent. diff --git a/packages/twenty-server/src/engine/core-modules/audit/audit.resolver.spec.ts b/packages/twenty-server/src/engine/core-modules/audit/audit.resolver.spec.ts index bef091b16..ec10a694b 100644 --- a/packages/twenty-server/src/engine/core-modules/audit/audit.resolver.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/audit.resolver.spec.ts @@ -38,11 +38,14 @@ describe('AuditResolver', () => { }); it('should handle a valid pageview input', async () => { - const mockPageview = jest.fn().mockResolvedValue('Pageview created'); + const mockInsertPageviewEvent = jest + .fn() + .mockResolvedValue('Pageview created'); auditService.createContext.mockReturnValue({ - pageview: mockPageview, - track: jest.fn(), + createPageviewEvent: mockInsertPageviewEvent, + insertWorkspaceEvent: jest.fn(), + createObjectEvent: jest.fn(), }); const input = { @@ -60,16 +63,19 @@ describe('AuditResolver', () => { workspaceId: 'workspace-1', userId: 'user-1', }); - expect(mockPageview).toHaveBeenCalledWith('Test Page', {}); + expect(mockInsertPageviewEvent).toHaveBeenCalledWith('Test Page', {}); expect(result).toBe('Pageview created'); }); it('should handle a valid track input', async () => { - const mockTrack = jest.fn().mockResolvedValue('Track created'); + const mockInsertWorkspaceEvent = jest + .fn() + .mockResolvedValue('Track created'); auditService.createContext.mockReturnValue({ - track: mockTrack, - pageview: jest.fn(), + insertWorkspaceEvent: mockInsertWorkspaceEvent, + createObjectEvent: jest.fn(), + createPageviewEvent: jest.fn(), }); const input = { @@ -87,10 +93,54 @@ describe('AuditResolver', () => { workspaceId: 'workspace-2', userId: 'user-2', }); - expect(mockTrack).toHaveBeenCalledWith('Custom Domain Activated', {}); + expect(mockInsertWorkspaceEvent).toHaveBeenCalledWith( + 'Custom Domain Activated', + {}, + ); expect(result).toBe('Track created'); }); + it('should handle object event creation', async () => { + const mockInsertObjectEvent = jest + .fn() + .mockResolvedValue('Object event created'); + + auditService.createContext.mockReturnValue({ + insertWorkspaceEvent: jest.fn(), + createObjectEvent: mockInsertObjectEvent, + createPageviewEvent: jest.fn(), + }); + + const input = { + event: 'Object Record Created' as const, + recordId: 'test-record-id', + objectMetadataId: 'test-object-metadata-id', + properties: { additionalData: 'test-data' }, + }; + + const result = await resolver.createObjectEvent( + input, + { id: 'workspace-3' } as Workspace, + { id: 'user-3' } as User, + ); + + expect(auditService.createContext).toHaveBeenCalledWith({ + workspaceId: 'workspace-3', + userId: 'user-3', + }); + + expect(mockInsertObjectEvent).toHaveBeenCalledWith( + 'Object Record Created', + { + additionalData: 'test-data', + recordId: 'test-record-id', + objectMetadataId: 'test-object-metadata-id', + isCustom: true, + }, + ); + expect(result).toBe('Object event created'); + }); + it('should throw an AuditException for invalid input', async () => { const invalidInput = { type: 'invalid' }; @@ -103,4 +153,18 @@ describe('AuditResolver', () => { ), ); }); + + it('should throw an AuditException when workspace is missing for createObjectEvent', async () => { + const input = { + event: 'Object Record Created' as const, + recordId: 'test-record-id', + objectMetadataId: 'test-object-metadata-id', + }; + + await expect( + resolver.createObjectEvent(input, undefined, undefined), + ).rejects.toThrowError( + new AuditException('Missing workspace', AuditExceptionCode.INVALID_INPUT), + ); + }); }); diff --git a/packages/twenty-server/src/engine/core-modules/audit/audit.resolver.ts b/packages/twenty-server/src/engine/core-modules/audit/audit.resolver.ts index 94e3c30bb..8868416ee 100644 --- a/packages/twenty-server/src/engine/core-modules/audit/audit.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/audit.resolver.ts @@ -4,6 +4,7 @@ import { AuditException, AuditExceptionCode, } from 'src/engine/core-modules/audit/audit.exception'; +import { CreateObjectEventInput } from 'src/engine/core-modules/audit/dtos/create-object-event.input'; import { User } from 'src/engine/core-modules/user/user.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator'; @@ -22,7 +23,7 @@ export class AuditResolver { constructor(private readonly auditService: AuditService) {} // preparing for new name - async auditTrack( + async createPageview( @Args() createAnalyticsInput: CreateAnalyticsInputV2, @AuthWorkspace() workspace: Workspace | undefined, @@ -31,6 +32,33 @@ export class AuditResolver { return this.trackAnalytics(createAnalyticsInput, workspace, user); } + @Mutation(() => Analytics) + async createObjectEvent( + @Args() + createObjectEventInput: CreateObjectEventInput, + @AuthWorkspace() workspace: Workspace | undefined, + @AuthUser({ allowUndefined: true }) user: User | undefined, + ) { + if (!workspace) { + throw new AuditException( + 'Missing workspace', + AuditExceptionCode.INVALID_INPUT, + ); + } + + const analyticsContext = this.auditService.createContext({ + workspaceId: workspace.id, + userId: user?.id, + }); + + return analyticsContext.createObjectEvent(createObjectEventInput.event, { + ...createObjectEventInput.properties, + recordId: createObjectEventInput.recordId, + objectMetadataId: createObjectEventInput.objectMetadataId, + isCustom: true, + }); + } + @Mutation(() => Analytics) async trackAnalytics( @Args() @@ -44,14 +72,16 @@ export class AuditResolver { }); if (isPageviewAnalyticsInput(createAnalyticsInput)) { - return analyticsContext.pageview( + return analyticsContext.createPageviewEvent( createAnalyticsInput.name, createAnalyticsInput.properties ?? {}, ); } if (isTrackAnalyticsInput(createAnalyticsInput)) { - return analyticsContext.track( + // For track events, we need to determine if it's a workspace or object event + // Since we don't have recordId and objectMetadataId in the input, we use insertWorkspaceEvent + return analyticsContext.insertWorkspaceEvent( createAnalyticsInput.event, createAnalyticsInput.properties ?? {}, ); diff --git a/packages/twenty-server/src/engine/core-modules/audit/dtos/create-object-event.input.ts b/packages/twenty-server/src/engine/core-modules/audit/dtos/create-object-event.input.ts new file mode 100644 index 000000000..5595d5324 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/audit/dtos/create-object-event.input.ts @@ -0,0 +1,29 @@ +import { ArgsType, Field } from '@nestjs/graphql'; + +import { IsNotEmpty, IsObject, IsOptional, IsString } from 'class-validator'; +import GraphQLJSON from 'graphql-type-json'; + +import { TrackEventName } from 'src/engine/core-modules/audit/types/events.type'; + +@ArgsType() +export class CreateObjectEventInput { + @Field(() => String) + @IsNotEmpty() + @IsString() + event: TrackEventName; + + @Field(() => String) + @IsNotEmpty() + @IsString() + recordId: string; + + @Field(() => String) + @IsNotEmpty() + @IsString() + objectMetadataId: string; + + @Field(() => GraphQLJSON, { nullable: true }) + @IsObject() + @IsOptional() + properties?: Record; +} diff --git a/packages/twenty-server/src/engine/core-modules/audit/jobs/create-audit-log-from-internal-event.ts b/packages/twenty-server/src/engine/core-modules/audit/jobs/create-audit-log-from-internal-event.ts index 8bc806959..5812129d9 100644 --- a/packages/twenty-server/src/engine/core-modules/audit/jobs/create-audit-log-from-internal-event.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/jobs/create-audit-log-from-internal-event.ts @@ -1,23 +1,16 @@ import { AuditService } from 'src/engine/core-modules/audit/services/audit.service'; -import { OBJECT_RECORD_CREATED_EVENT } from 'src/engine/core-modules/audit/utils/events/track/object-record/object-record-created'; -import { OBJECT_RECORD_DELETED_EVENT } from 'src/engine/core-modules/audit/utils/events/track/object-record/object-record-delete'; -import { OBJECT_RECORD_UPDATED_EVENT } from 'src/engine/core-modules/audit/utils/events/track/object-record/object-record-updated'; +import { OBJECT_RECORD_CREATED_EVENT } from 'src/engine/core-modules/audit/utils/events/object-event/object-record-created'; +import { OBJECT_RECORD_DELETED_EVENT } from 'src/engine/core-modules/audit/utils/events/object-event/object-record-delete'; +import { OBJECT_RECORD_UPDATED_EVENT } from 'src/engine/core-modules/audit/utils/events/object-event/object-record-updated'; 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 { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository'; -import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @Processor(MessageQueue.entityEventsToDbQueue) export class CreateAuditLogFromInternalEvent { - constructor( - @InjectObjectMetadataRepository(WorkspaceMemberWorkspaceEntity) - private readonly workspaceMemberService: WorkspaceMemberRepository, - private readonly auditService: AuditService, - ) {} + constructor(private readonly auditService: AuditService) {} @Process(CreateAuditLogFromInternalEvent.name) async handle( @@ -38,12 +31,25 @@ export class CreateAuditLogFromInternalEvent { userId: eventData.userId, }); + // Since these are object record events, we use createObjectEvent if (workspaceEventBatch.name.endsWith('.updated')) { - analytics.track(OBJECT_RECORD_UPDATED_EVENT, eventProperties); + analytics.createObjectEvent(OBJECT_RECORD_UPDATED_EVENT, { + ...eventProperties, + recordId: eventData.recordId, + objectMetadataId: eventData.objectMetadata.id, + }); } else if (workspaceEventBatch.name.endsWith('.created')) { - analytics.track(OBJECT_RECORD_CREATED_EVENT, eventProperties); + analytics.createObjectEvent(OBJECT_RECORD_CREATED_EVENT, { + ...eventProperties, + recordId: eventData.recordId, + objectMetadataId: eventData.objectMetadata.id, + }); } else if (workspaceEventBatch.name.endsWith('.deleted')) { - analytics.track(OBJECT_RECORD_DELETED_EVENT, eventProperties); + analytics.createObjectEvent(OBJECT_RECORD_DELETED_EVENT, { + ...eventProperties, + recordId: eventData.recordId, + objectMetadataId: eventData.objectMetadata.id, + }); } } } diff --git a/packages/twenty-server/src/engine/core-modules/audit/services/audit.service.spec.ts b/packages/twenty-server/src/engine/core-modules/audit/services/audit.service.spec.ts index 8c17bfa55..0d0391999 100644 --- a/packages/twenty-server/src/engine/core-modules/audit/services/audit.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/services/audit.service.spec.ts @@ -3,7 +3,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AuditContextMock } from 'test/utils/audit-context.mock'; import { ClickHouseService } from 'src/database/clickHouse/clickHouse.service'; -import { CUSTOM_DOMAIN_ACTIVATED_EVENT } from 'src/engine/core-modules/audit/utils/events/track/custom-domain/custom-domain-activated'; +import { CUSTOM_DOMAIN_ACTIVATED_EVENT } from 'src/engine/core-modules/audit/utils/events/workspace-event/custom-domain/custom-domain-activated'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; @@ -58,29 +58,37 @@ describe('AuditService', () => { it('should create a valid context object', () => { const context = service.createContext(mockUserIdAndWorkspaceId); - expect(context).toHaveProperty('track'); - expect(context).toHaveProperty('pageview'); + expect(context).toHaveProperty('insertWorkspaceEvent'); + expect(context).toHaveProperty('createObjectEvent'); + expect(context).toHaveProperty('createPageviewEvent'); }); - it('should call track with correct parameters', async () => { - const trackSpy = jest.fn().mockResolvedValue({ success: true }); + it('should call insertWorkspaceEvent with correct parameters', async () => { + const insertWorkspaceEventSpy = jest + .fn() + .mockResolvedValue({ success: true }); const mockContext = AuditContextMock({ - track: trackSpy, + insertWorkspaceEvent: insertWorkspaceEventSpy, }); jest.spyOn(service, 'createContext').mockReturnValue(mockContext); const context = service.createContext(mockUserIdAndWorkspaceId); - await context.track(CUSTOM_DOMAIN_ACTIVATED_EVENT, {}); + await context.insertWorkspaceEvent(CUSTOM_DOMAIN_ACTIVATED_EVENT, {}); - expect(trackSpy).toHaveBeenCalledWith(CUSTOM_DOMAIN_ACTIVATED_EVENT, {}); + expect(insertWorkspaceEventSpy).toHaveBeenCalledWith( + CUSTOM_DOMAIN_ACTIVATED_EVENT, + {}, + ); }); - it('should call pageview with correct parameters', async () => { - const pageviewSpy = jest.fn().mockResolvedValue({ success: true }); + it('should call createPageviewEvent with correct parameters', async () => { + const createPageviewEventSpy = jest + .fn() + .mockResolvedValue({ success: true }); const mockContext = AuditContextMock({ - pageview: pageviewSpy, + createPageviewEvent: createPageviewEventSpy, }); jest.spyOn(service, 'createContext').mockReturnValue(mockContext); @@ -96,26 +104,43 @@ describe('AuditService', () => { userAgent: '', }; - await context.pageview('page-view', testPageviewProperties); + await context.createPageviewEvent('page-view', testPageviewProperties); - expect(pageviewSpy).toHaveBeenCalledWith( + expect(createPageviewEventSpy).toHaveBeenCalledWith( 'page-view', testPageviewProperties, ); }); - it('should return success when track is called', async () => { + it('should return success when insertWorkspaceEvent is called', async () => { const context = service.createContext(mockUserIdAndWorkspaceId); - const result = await context.track(CUSTOM_DOMAIN_ACTIVATED_EVENT, {}); + const result = await context.insertWorkspaceEvent( + CUSTOM_DOMAIN_ACTIVATED_EVENT, + {}, + ); expect(result).toEqual({ success: true }); }); - it('should return success when pageview is called', async () => { + it('should return success when createPageviewEvent is called', async () => { const context = service.createContext(mockUserIdAndWorkspaceId); - const result = await context.pageview('page-view', {}); + const result = await context.createPageviewEvent('page-view', {}); + + expect(result).toEqual({ success: true }); + }); + + it('should return success when createObjectEvent is called', async () => { + const context = service.createContext(mockUserIdAndWorkspaceId); + + const result = await context.createObjectEvent( + CUSTOM_DOMAIN_ACTIVATED_EVENT, + { + recordId: 'test-record-id', + objectMetadataId: 'test-object-metadata-id', + }, + ); expect(result).toEqual({ success: true }); }); diff --git a/packages/twenty-server/src/engine/core-modules/audit/services/audit.service.ts b/packages/twenty-server/src/engine/core-modules/audit/services/audit.service.ts index 0f06892b4..01fa173f5 100644 --- a/packages/twenty-server/src/engine/core-modules/audit/services/audit.service.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/services/audit.service.ts @@ -35,16 +35,31 @@ export class AuditService { : {}; return { - track: ( + insertWorkspaceEvent: ( event: T, properties: TrackEventProperties, ) => this.preventIfDisabled(() => - this.clickHouseService.insert('auditEvent', [ + this.clickHouseService.insert('workspaceEvent', [ { ...userIdAndWorkspaceId, ...makeTrackEvent(event, properties) }, ]), ), - pageview: (name: string, properties: Partial) => + createObjectEvent: ( + event: T, + properties: TrackEventProperties & { + recordId: string; + objectMetadataId: string; + }, + ) => + this.preventIfDisabled(() => + this.clickHouseService.insert('objectEvent', [ + { ...userIdAndWorkspaceId, ...makeTrackEvent(event, properties) }, + ]), + ), + createPageviewEvent: ( + name: string, + properties: Partial, + ) => this.preventIfDisabled(() => this.clickHouseService.insert('pageview', [ { ...userIdAndWorkspaceId, ...makePageview(name, properties) }, diff --git a/packages/twenty-server/src/engine/core-modules/audit/types/events.type.ts b/packages/twenty-server/src/engine/core-modules/audit/types/events.type.ts index c23b65281..d1ba22cdd 100644 --- a/packages/twenty-server/src/engine/core-modules/audit/types/events.type.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/types/events.type.ts @@ -1,43 +1,43 @@ -import { - CUSTOM_DOMAIN_ACTIVATED_EVENT, - CustomDomainActivatedTrackEvent, -} from 'src/engine/core-modules/audit/utils/events/track/custom-domain/custom-domain-activated'; -import { - CUSTOM_DOMAIN_DEACTIVATED_EVENT, - CustomDomainDeactivatedTrackEvent, -} from 'src/engine/core-modules/audit/utils/events/track/custom-domain/custom-domain-deactivated'; -import { - MONITORING_EVENT, - MonitoringTrackEvent, -} from 'src/engine/core-modules/audit/utils/events/track/monitoring/monitoring'; import { OBJECT_RECORD_CREATED_EVENT, ObjectRecordCreatedTrackEvent, -} from 'src/engine/core-modules/audit/utils/events/track/object-record/object-record-created'; +} from 'src/engine/core-modules/audit/utils/events/object-event/object-record-created'; import { OBJECT_RECORD_DELETED_EVENT, ObjectRecordDeletedTrackEvent, -} from 'src/engine/core-modules/audit/utils/events/track/object-record/object-record-delete'; +} from 'src/engine/core-modules/audit/utils/events/object-event/object-record-delete'; import { OBJECT_RECORD_UPDATED_EVENT, ObjectRecordUpdatedTrackEvent, -} from 'src/engine/core-modules/audit/utils/events/track/object-record/object-record-updated'; +} from 'src/engine/core-modules/audit/utils/events/object-event/object-record-updated'; +import { + CUSTOM_DOMAIN_ACTIVATED_EVENT, + CustomDomainActivatedTrackEvent, +} from 'src/engine/core-modules/audit/utils/events/workspace-event/custom-domain/custom-domain-activated'; +import { + CUSTOM_DOMAIN_DEACTIVATED_EVENT, + CustomDomainDeactivatedTrackEvent, +} from 'src/engine/core-modules/audit/utils/events/workspace-event/custom-domain/custom-domain-deactivated'; +import { + MONITORING_EVENT, + MonitoringTrackEvent, +} from 'src/engine/core-modules/audit/utils/events/workspace-event/monitoring/monitoring'; import { SERVERLESS_FUNCTION_EXECUTED_EVENT, ServerlessFunctionExecutedTrackEvent, -} from 'src/engine/core-modules/audit/utils/events/track/serverless-function/serverless-function-executed'; +} from 'src/engine/core-modules/audit/utils/events/workspace-event/serverless-function/serverless-function-executed'; import { USER_SIGNUP_EVENT, UserSignupTrackEvent, -} from 'src/engine/core-modules/audit/utils/events/track/user/user-signup'; +} from 'src/engine/core-modules/audit/utils/events/workspace-event/user/user-signup'; import { WEBHOOK_RESPONSE_EVENT, WebhookResponseTrackEvent, -} from 'src/engine/core-modules/audit/utils/events/track/webhook/webhook-response'; +} from 'src/engine/core-modules/audit/utils/events/workspace-event/webhook/webhook-response'; import { WORKSPACE_ENTITY_CREATED_EVENT, WorkspaceEntityCreatedTrackEvent, -} from 'src/engine/core-modules/audit/utils/events/track/workspace-entity/workspace-entity-created'; +} from 'src/engine/core-modules/audit/utils/events/workspace-event/workspace-entity/workspace-entity-created'; // Define all track event names export type TrackEventName = diff --git a/packages/twenty-server/src/engine/core-modules/audit/utils/analytics.utils.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/analytics.utils.ts index d3e735f3b..7a1b7aab2 100644 --- a/packages/twenty-server/src/engine/core-modules/audit/utils/analytics.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/analytics.utils.ts @@ -12,7 +12,7 @@ import { import { eventsRegistry, GenericTrackEvent, -} from 'src/engine/core-modules/audit/utils/events/track/track'; +} from 'src/engine/core-modules/audit/utils/events/workspace-event/track'; const common = (): Record => ({ timestamp: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), diff --git a/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/object-record/object-record-created.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/object-event/object-record-created.ts similarity index 93% rename from packages/twenty-server/src/engine/core-modules/audit/utils/events/track/object-record/object-record-created.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/object-event/object-record-created.ts index 26e7e4c2e..00b077c55 100644 --- a/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/object-record/object-record-created.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/object-event/object-record-created.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { registerEvent } from 'src/engine/core-modules/audit/utils/events/track/track'; +import { registerEvent } from 'src/engine/core-modules/audit/utils/events/workspace-event/track'; export const OBJECT_RECORD_CREATED_EVENT = 'Object Record Created' as const; export const objectRecordCreatedSchema = z.object({ diff --git a/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/object-record/object-record-delete.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/object-event/object-record-delete.ts similarity index 93% rename from packages/twenty-server/src/engine/core-modules/audit/utils/events/track/object-record/object-record-delete.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/object-event/object-record-delete.ts index 2223a2f28..958098693 100644 --- a/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/object-record/object-record-delete.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/object-event/object-record-delete.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { registerEvent } from 'src/engine/core-modules/audit/utils/events/track/track'; +import { registerEvent } from 'src/engine/core-modules/audit/utils/events/workspace-event/track'; export const OBJECT_RECORD_DELETED_EVENT = 'Object Record Deleted' as const; export const objectRecordDeletedSchema = z.object({ diff --git a/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/object-record/object-record-updated.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/object-event/object-record-updated.ts similarity index 93% rename from packages/twenty-server/src/engine/core-modules/audit/utils/events/track/object-record/object-record-updated.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/object-event/object-record-updated.ts index 78d4f47d6..01c0f12bb 100644 --- a/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/object-record/object-record-updated.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/object-event/object-record-updated.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { registerEvent } from 'src/engine/core-modules/audit/utils/events/track/track'; +import { registerEvent } from 'src/engine/core-modules/audit/utils/events/workspace-event/track'; export const OBJECT_RECORD_UPDATED_EVENT = 'Object Record Updated' as const; export const objectRecordUpdatedSchema = z.object({ diff --git a/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/custom-domain/custom-domain-activated.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/custom-domain/custom-domain-activated.ts similarity index 93% rename from packages/twenty-server/src/engine/core-modules/audit/utils/events/track/custom-domain/custom-domain-activated.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/custom-domain/custom-domain-activated.ts index e5a4e2b42..15841e839 100644 --- a/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/custom-domain/custom-domain-activated.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/custom-domain/custom-domain-activated.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { registerEvent } from 'src/engine/core-modules/audit/utils/events/track/track'; +import { registerEvent } from 'src/engine/core-modules/audit/utils/events/workspace-event/track'; export const CUSTOM_DOMAIN_ACTIVATED_EVENT = 'Custom Domain Activated' as const; export const customDomainActivatedSchema = z diff --git a/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/custom-domain/custom-domain-deactivated.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/custom-domain/custom-domain-deactivated.ts similarity index 93% rename from packages/twenty-server/src/engine/core-modules/audit/utils/events/track/custom-domain/custom-domain-deactivated.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/custom-domain/custom-domain-deactivated.ts index c078a036f..a5015f63d 100644 --- a/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/custom-domain/custom-domain-deactivated.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/custom-domain/custom-domain-deactivated.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { registerEvent } from 'src/engine/core-modules/audit/utils/events/track/track'; +import { registerEvent } from 'src/engine/core-modules/audit/utils/events/workspace-event/track'; export const CUSTOM_DOMAIN_DEACTIVATED_EVENT = 'Custom Domain Deactivated' as const; diff --git a/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/monitoring/monitoring.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/monitoring/monitoring.ts similarity index 94% rename from packages/twenty-server/src/engine/core-modules/audit/utils/events/track/monitoring/monitoring.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/monitoring/monitoring.ts index 27fc5993d..bc8f29cdf 100644 --- a/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/monitoring/monitoring.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/monitoring/monitoring.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { registerEvent } from 'src/engine/core-modules/audit/utils/events/track/track'; +import { registerEvent } from 'src/engine/core-modules/audit/utils/events/workspace-event/track'; export const MONITORING_EVENT = 'Monitoring' as const; export const monitoringSchema = z diff --git a/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/serverless-function/serverless-function-executed.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/serverless-function/serverless-function-executed.ts similarity index 95% rename from packages/twenty-server/src/engine/core-modules/audit/utils/events/track/serverless-function/serverless-function-executed.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/serverless-function/serverless-function-executed.ts index 9721e18ee..50b084c99 100644 --- a/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/serverless-function/serverless-function-executed.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/serverless-function/serverless-function-executed.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { registerEvent } from 'src/engine/core-modules/audit/utils/events/track/track'; +import { registerEvent } from 'src/engine/core-modules/audit/utils/events/workspace-event/track'; export const SERVERLESS_FUNCTION_EXECUTED_EVENT = 'Serverless Function Executed' as const; diff --git a/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/track.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/track.ts similarity index 100% rename from packages/twenty-server/src/engine/core-modules/audit/utils/events/track/track.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/track.ts diff --git a/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/user/user-signup.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/user/user-signup.ts similarity index 92% rename from packages/twenty-server/src/engine/core-modules/audit/utils/events/track/user/user-signup.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/user/user-signup.ts index 0affd3587..7453bf2a0 100644 --- a/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/user/user-signup.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/user/user-signup.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { registerEvent } from 'src/engine/core-modules/audit/utils/events/track/track'; +import { registerEvent } from 'src/engine/core-modules/audit/utils/events/workspace-event/track'; export const USER_SIGNUP_EVENT = 'User Signup' as const; export const userSignupSchema = z diff --git a/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/webhook/webhook-response.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/webhook/webhook-response.ts similarity index 94% rename from packages/twenty-server/src/engine/core-modules/audit/utils/events/track/webhook/webhook-response.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/webhook/webhook-response.ts index 0106799df..5806eda94 100644 --- a/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/webhook/webhook-response.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/webhook/webhook-response.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { registerEvent } from 'src/engine/core-modules/audit/utils/events/track/track'; +import { registerEvent } from 'src/engine/core-modules/audit/utils/events/workspace-event/track'; export const WEBHOOK_RESPONSE_EVENT = 'Webhook Response' as const; export const webhookResponseSchema = z diff --git a/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/workspace-entity/workspace-entity-created.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/workspace-entity/workspace-entity-created.ts similarity index 94% rename from packages/twenty-server/src/engine/core-modules/audit/utils/events/track/workspace-entity/workspace-entity-created.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/workspace-entity/workspace-entity-created.ts index 88843a77a..5d921ab95 100644 --- a/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/workspace-entity/workspace-entity-created.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/workspace-entity/workspace-entity-created.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { registerEvent } from 'src/engine/core-modules/audit/utils/events/track/track'; +import { registerEvent } from 'src/engine/core-modules/audit/utils/events/workspace-event/track'; export const WORKSPACE_ENTITY_CREATED_EVENT = 'Workspace Entity Created' as const; diff --git a/packages/twenty-server/src/engine/core-modules/domain-manager/controllers/cloudflare.controller.ts b/packages/twenty-server/src/engine/core-modules/domain-manager/controllers/cloudflare.controller.ts index 64c1118dc..7d461455b 100644 --- a/packages/twenty-server/src/engine/core-modules/domain-manager/controllers/cloudflare.controller.ts +++ b/packages/twenty-server/src/engine/core-modules/domain-manager/controllers/cloudflare.controller.ts @@ -14,7 +14,7 @@ import { Request, Response } from 'express'; import { Repository } from 'typeorm'; import { AuditService } from 'src/engine/core-modules/audit/services/audit.service'; -import { CUSTOM_DOMAIN_ACTIVATED_EVENT } from 'src/engine/core-modules/audit/utils/events/track/custom-domain/custom-domain-activated'; +import { CUSTOM_DOMAIN_ACTIVATED_EVENT } from 'src/engine/core-modules/audit/utils/events/workspace-event/custom-domain/custom-domain-activated'; import { AuthRestApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-rest-api-exception.filter'; import { DomainManagerException, @@ -91,7 +91,7 @@ export class CloudflareController { ...workspaceUpdated, }); - await analytics.track(CUSTOM_DOMAIN_ACTIVATED_EVENT, {}); + await analytics.insertWorkspaceEvent(CUSTOM_DOMAIN_ACTIVATED_EVENT, {}); } return res.status(200).send(); diff --git a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts index 03ab520df..fd649cf34 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts @@ -9,8 +9,8 @@ import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; import { Repository } from 'typeorm'; import { AuditService } from 'src/engine/core-modules/audit/services/audit.service'; -import { CUSTOM_DOMAIN_ACTIVATED_EVENT } from 'src/engine/core-modules/audit/utils/events/track/custom-domain/custom-domain-activated'; -import { CUSTOM_DOMAIN_DEACTIVATED_EVENT } from 'src/engine/core-modules/audit/utils/events/track/custom-domain/custom-domain-deactivated'; +import { CUSTOM_DOMAIN_ACTIVATED_EVENT } from 'src/engine/core-modules/audit/utils/events/workspace-event/custom-domain/custom-domain-activated'; +import { CUSTOM_DOMAIN_DEACTIVATED_EVENT } from 'src/engine/core-modules/audit/utils/events/workspace-event/custom-domain/custom-domain-deactivated'; import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum'; import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service'; import { BillingService } from 'src/engine/core-modules/billing/services/billing.service'; @@ -422,7 +422,7 @@ export class WorkspaceService extends TypeOrmQueryService { workspaceId: workspace.id, }); - analytics.track( + analytics.insertWorkspaceEvent( workspace.isCustomDomainEnabled ? CUSTOM_DOMAIN_ACTIVATED_EVENT : CUSTOM_DOMAIN_DEACTIVATED_EVENT, diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.service.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.service.ts index 410f02005..55fcefd8d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.service.ts @@ -11,7 +11,7 @@ import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/i import { ServerlessExecuteResult } from 'src/engine/core-modules/serverless/drivers/interfaces/serverless-driver.interface'; import { AuditService } from 'src/engine/core-modules/audit/services/audit.service'; -import { SERVERLESS_FUNCTION_EXECUTED_EVENT } from 'src/engine/core-modules/audit/utils/events/track/serverless-function/serverless-function-executed'; +import { SERVERLESS_FUNCTION_EXECUTED_EVENT } from 'src/engine/core-modules/audit/utils/events/workspace-event/serverless-function/serverless-function-executed'; import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service'; import { readFileContent } from 'src/engine/core-modules/file-storage/utils/read-file-content'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; @@ -148,7 +148,7 @@ export class ServerlessFunctionService { .createContext({ workspaceId, }) - .track(SERVERLESS_FUNCTION_EXECUTED_EVENT, { + .insertWorkspaceEvent(SERVERLESS_FUNCTION_EXECUTED_EVENT, { duration: resultServerlessFunction.duration, status: resultServerlessFunction.status, ...(resultServerlessFunction.error && { diff --git a/packages/twenty-server/src/modules/messaging/monitoring/services/messaging-monitoring.service.ts b/packages/twenty-server/src/modules/messaging/monitoring/services/messaging-monitoring.service.ts index 223d047e6..6740c679d 100644 --- a/packages/twenty-server/src/modules/messaging/monitoring/services/messaging-monitoring.service.ts +++ b/packages/twenty-server/src/modules/messaging/monitoring/services/messaging-monitoring.service.ts @@ -37,7 +37,7 @@ export class MessagingMonitoringService { userId, workspaceId, }) - .track(MONITORING_EVENT, { + .insertWorkspaceEvent(MONITORING_EVENT, { eventName: `messaging.${eventName}`, connectedAccountId, messageChannelId, diff --git a/packages/twenty-server/src/modules/webhook/jobs/call-webhook.job.ts b/packages/twenty-server/src/modules/webhook/jobs/call-webhook.job.ts index dcc1e1976..dbc0976f6 100644 --- a/packages/twenty-server/src/modules/webhook/jobs/call-webhook.job.ts +++ b/packages/twenty-server/src/modules/webhook/jobs/call-webhook.job.ts @@ -4,7 +4,7 @@ import { Logger } from '@nestjs/common'; import crypto from 'crypto'; import { AuditService } from 'src/engine/core-modules/audit/services/audit.service'; -import { WEBHOOK_RESPONSE_EVENT } from 'src/engine/core-modules/audit/utils/events/track/webhook/webhook-response'; +import { WEBHOOK_RESPONSE_EVENT } from 'src/engine/core-modules/audit/utils/events/workspace-event/webhook/webhook-response'; 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'; @@ -78,13 +78,13 @@ export class CallWebhookJob { const success = response.status >= 200 && response.status < 300; - analytics.track(WEBHOOK_RESPONSE_EVENT, { + analytics.insertWorkspaceEvent(WEBHOOK_RESPONSE_EVENT, { status: response.status, success, ...commonPayload, }); } catch (err) { - analytics.track(WEBHOOK_RESPONSE_EVENT, { + analytics.insertWorkspaceEvent(WEBHOOK_RESPONSE_EVENT, { success: false, ...commonPayload, ...(err.response && { status: err.response.status }), diff --git a/packages/twenty-server/test/integration/audit/suites/clickHouse-audit-event-registration.integration-spec.ts b/packages/twenty-server/test/integration/audit/suites/clickHouse-workspace-event-registration.integration-spec.ts similarity index 81% rename from packages/twenty-server/test/integration/audit/suites/clickHouse-audit-event-registration.integration-spec.ts rename to packages/twenty-server/test/integration/audit/suites/clickHouse-workspace-event-registration.integration-spec.ts index 4266ef6e8..a8efb228d 100644 --- a/packages/twenty-server/test/integration/audit/suites/clickHouse-audit-event-registration.integration-spec.ts +++ b/packages/twenty-server/test/integration/audit/suites/clickHouse-workspace-event-registration.integration-spec.ts @@ -3,8 +3,8 @@ import process from 'process'; import { ClickHouseClient, createClient } from '@clickhouse/client'; import request from 'supertest'; -import { OBJECT_RECORD_CREATED_EVENT } from 'src/engine/core-modules/audit/utils/events/track/object-record/object-record-created'; -import { GenericTrackEvent } from 'src/engine/core-modules/audit/utils/events/track/track'; +import { CUSTOM_DOMAIN_ACTIVATED_EVENT } from 'src/engine/core-modules/audit/utils/events/workspace-event/custom-domain/custom-domain-activated'; +import { GenericTrackEvent } from 'src/engine/core-modules/audit/utils/events/workspace-event/track'; describe('ClickHouse Event Registration (integration)', () => { let clickHouseClient: ClickHouseClient; @@ -17,7 +17,7 @@ describe('ClickHouse Event Registration (integration)', () => { }); await clickHouseClient.query({ - query: 'TRUNCATE TABLE auditEvent', + query: 'TRUNCATE TABLE workspaceEvent', format: 'JSONEachRow', }); }); @@ -39,7 +39,7 @@ describe('ClickHouse Event Registration (integration)', () => { const variables = { type: 'TRACK', - event: OBJECT_RECORD_CREATED_EVENT, + event: CUSTOM_DOMAIN_ACTIVATED_EVENT, properties: {}, }; @@ -56,8 +56,8 @@ describe('ClickHouse Event Registration (integration)', () => { const queryResult = await clickHouseClient.query({ query: ` SELECT * - FROM auditEvent - WHERE event = '${OBJECT_RECORD_CREATED_EVENT}' AND timestamp >= now() - INTERVAL 1 SECOND + FROM workspaceEvent + WHERE event = '${CUSTOM_DOMAIN_ACTIVATED_EVENT}' AND timestamp >= now() - INTERVAL 1 SECOND `, format: 'JSONEachRow', diff --git a/packages/twenty-server/test/utils/audit-context.mock.ts b/packages/twenty-server/test/utils/audit-context.mock.ts index f14f1b092..42f2f5712 100644 --- a/packages/twenty-server/test/utils/audit-context.mock.ts +++ b/packages/twenty-server/test/utils/audit-context.mock.ts @@ -1,19 +1,31 @@ import { TrackEventName } from 'src/engine/core-modules/audit/types/events.type'; export const AuditContextMock = (params?: { - track?: + insertWorkspaceEvent?: | (( event: TrackEventName, properties: any, ) => Promise<{ success: boolean }>) | jest.Mock; - pageview?: + createObjectEvent?: + | (( + event: TrackEventName, + properties: any, + ) => Promise<{ success: boolean }>) + | jest.Mock; + createPageviewEvent?: | ((name: string, properties: any) => Promise<{ success: boolean }>) | jest.Mock; }) => { return { - track: params?.track ?? jest.fn().mockResolvedValue({ success: true }), - pageview: - params?.pageview ?? jest.fn().mockResolvedValue({ success: true }), + insertWorkspaceEvent: + params?.insertWorkspaceEvent ?? + jest.fn().mockResolvedValue({ success: true }), + createObjectEvent: + params?.createObjectEvent ?? + jest.fn().mockResolvedValue({ success: true }), + createPageviewEvent: + params?.createPageviewEvent ?? + jest.fn().mockResolvedValue({ success: true }), }; };