From 49b7f5255f3881c644f1cc5277cc25a1e80500f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Sun, 4 May 2025 14:35:41 +0200 Subject: [PATCH] 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> --- .../src/generated-metadata/graphql.ts | 3 - .../twenty-front/src/generated/graphql.tsx | 5 +- .../settings/security/SettingsSecurity.tsx | 25 +- packages/twenty-server/project.json | 4 +- packages/twenty-server/src/app.module.ts | 2 + .../database/clickHouse/clickHouse.module.ts | 12 + .../clickHouse/clickHouse.service.spec.ts | 264 ++++++++++++++++++ .../database/clickHouse/clickHouse.service.ts | 230 +++++++++++++++ .../001-create-audit-event-table.sql} | 4 +- .../migrations/002-create-pageview-table.sql | 0 .../003-create-external-event-table.sql | 12 + .../migrations/run-migrations.ts | 6 +- .../clickHouse/seeds}/fixtures.ts | 12 +- .../seeds/run-seeds.ts | 4 +- .../typeorm-seeds/core/feature-flags.ts | 15 - .../listeners/entity-events-to-db.listener.ts | 4 +- .../listeners/telemetry.listener.ts | 14 +- .../workspace-query-runner.module.ts | 6 +- .../analytics/analytics.module.ts | 15 - .../analytics/analytics.service.ts | 0 .../analytics/services/analytics.service.ts | 70 ----- .../services/clickhouse.service.spec.ts | 159 ----------- .../analytics/services/clickhouse.service.ts | 60 ---- .../analytics/types/common.type.ts | 2 - .../{analytics => audit}/README.md | 14 +- .../audit.exception.ts} | 6 +- .../engine/core-modules/audit/audit.module.ts | 15 + .../audit.resolver.spec.ts} | 46 +-- .../audit.resolver.ts} | 35 ++- .../dtos/create-analytics.input.ts | 4 +- .../entities/analytics.entity.ts | 0 .../audit/jobs/audit-job.module.ts | 17 ++ .../create-audit-log-from-internal-event.ts | 47 +--- .../services/audit.service.spec.ts} | 46 ++- .../audit/services/audit.service.ts | 68 +++++ .../core-modules/audit/types/common.type.ts | 2 + .../{analytics => audit}/types/events.type.ts | 54 ++-- .../utils/analytics.utils.ts | 16 +- .../utils/events/common/base-schemas.ts | 0 .../utils/events/pageview/pageview.ts | 2 +- .../custom-domain/custom-domain-activated.ts | 2 +- .../custom-domain-deactivated.ts | 2 +- .../events/track/monitoring/monitoring.ts | 2 +- .../object-record/object-record-created.ts | 2 +- .../object-record/object-record-delete.ts | 2 +- .../object-record/object-record-updated.ts | 2 +- .../serverless-function-executed.ts | 2 +- .../utils/events/track/track.ts | 2 +- .../utils/events/track/user/user-signup.ts | 2 +- .../events/track/webhook/webhook-response.ts | 2 +- .../workspace-entity-created.ts | 2 +- .../engine/core-modules/core-engine.module.ts | 6 +- .../controllers/cloudflare.controller.ts | 8 +- .../controllers/cloudflare.spec.ts | 8 +- .../domain-manager/domain-manager.module.ts | 8 +- .../services/custom-domain.service.spec.ts | 10 +- .../enums/feature-flag-key.enum.ts | 3 - .../core-modules/message-queue/jobs.module.ts | 4 +- .../enums/config-variables-group.enum.ts | 2 +- .../engine/core-modules/user/user.module.ts | 4 +- .../engine/core-modules/user/user.resolver.ts | 2 - .../__tests__/workspace.service.spec.ts | 6 +- .../workspace/services/workspace.service.ts | 10 +- .../workspace/workspace.module.ts | 4 +- .../serverless-function.module.ts | 4 +- .../serverless-function.service.ts | 10 +- ...excluded-middleware-operations.constant.ts | 1 + .../metadata-to-repository.mapping.ts | 2 - .../standard-objects/index.ts | 6 +- .../api-key.workspace-entity.ts | 2 - .../blocklist.workspace-entity.ts | 4 +- .../connected-account.workspace-entity.ts | 2 - .../favorite.workspace-entity.ts | 4 +- .../jobs/messaging-message-list-fetch.job.ts | 18 +- .../jobs/messaging-messages-import.job.ts | 9 +- .../messaging-messages-import.service.spec.ts | 5 +- .../messaging-messages-import.service.ts | 11 +- .../message-participant-manager.module.ts | 4 +- ...channel-sync-status-monitoring.cron.job.ts | 8 +- .../monitoring/messaging-monitoring.module.ts | 10 +- .../services/messaging-monitoring.service.ts | 47 ++++ .../services/messaging-telemetry.service.ts | 39 --- .../timeline/jobs/timeline-job.module.ts | 16 +- ...timeline-activity-from-behavioral-event.ts | 1 - .../repositiories/audit-log.repository.ts | 38 --- .../audit-log.workspace-entity.ts | 103 ------- .../behavioral-event.workspace-entity.ts | 92 ------ .../view-field.workspace-entity.ts | 4 +- .../view-filter-group.workspace-entity.ts | 4 +- .../view-filter.workspace-entity.ts | 4 +- .../view-group.workspace-entity.ts | 4 +- .../view-sort.workspace-entity.ts | 4 +- .../standard-objects/view.workspace-entity.ts | 2 - .../modules/webhook/jobs/call-webhook.job.ts | 8 +- .../webhook/jobs/webhook-job.module.ts | 6 +- .../webhook.workspace-entity.ts | 2 - .../workflow-run.workspace-entity.ts | 2 + .../workspace-member.workspace-entity.ts | 16 -- ...it-event-registration.integration-spec.ts} | 22 +- .../audit-logs.integration-spec.ts | 65 ----- ...-context.mock.ts => audit-context.mock.ts} | 4 +- 101 files changed, 948 insertions(+), 1032 deletions(-) create mode 100644 packages/twenty-server/src/database/clickHouse/clickHouse.module.ts create mode 100644 packages/twenty-server/src/database/clickHouse/clickHouse.service.spec.ts create mode 100644 packages/twenty-server/src/database/clickHouse/clickHouse.service.ts rename packages/twenty-server/src/database/{clickhouse/migrations/001-create-events-table.sql => clickHouse/migrations/001-create-audit-event-table.sql} (67%) rename packages/twenty-server/src/database/{clickhouse => clickHouse}/migrations/002-create-pageview-table.sql (100%) create mode 100644 packages/twenty-server/src/database/clickHouse/migrations/003-create-external-event-table.sql rename packages/twenty-server/src/database/{clickhouse => clickHouse}/migrations/run-migrations.ts (94%) rename packages/twenty-server/src/{engine/core-modules/analytics/utils/fixtures => database/clickHouse/seeds}/fixtures.ts (78%) rename packages/twenty-server/src/database/{clickhouse => clickHouse}/seeds/run-seeds.ts (87%) delete mode 100644 packages/twenty-server/src/engine/core-modules/analytics/analytics.module.ts delete mode 100644 packages/twenty-server/src/engine/core-modules/analytics/analytics.service.ts delete mode 100644 packages/twenty-server/src/engine/core-modules/analytics/services/analytics.service.ts delete mode 100644 packages/twenty-server/src/engine/core-modules/analytics/services/clickhouse.service.spec.ts delete mode 100644 packages/twenty-server/src/engine/core-modules/analytics/services/clickhouse.service.ts delete mode 100644 packages/twenty-server/src/engine/core-modules/analytics/types/common.type.ts rename packages/twenty-server/src/engine/core-modules/{analytics => audit}/README.md (87%) rename packages/twenty-server/src/engine/core-modules/{analytics/analytics.exception.ts => audit/audit.exception.ts} (51%) create mode 100644 packages/twenty-server/src/engine/core-modules/audit/audit.module.ts rename packages/twenty-server/src/engine/core-modules/{analytics/analytics.resolver.spec.ts => audit/audit.resolver.spec.ts} (66%) rename packages/twenty-server/src/engine/core-modules/{analytics/analytics.resolver.ts => audit/audit.resolver.ts} (65%) rename packages/twenty-server/src/engine/core-modules/{analytics => audit}/dtos/create-analytics.input.ts (88%) rename packages/twenty-server/src/engine/core-modules/{analytics => audit}/entities/analytics.entity.ts (100%) create mode 100644 packages/twenty-server/src/engine/core-modules/audit/jobs/audit-job.module.ts rename packages/twenty-server/src/{modules/timeline => engine/core-modules/audit}/jobs/create-audit-log-from-internal-event.ts (53%) rename packages/twenty-server/src/engine/core-modules/{analytics/services/analytics.service.spec.ts => audit/services/audit.service.spec.ts} (66%) create mode 100644 packages/twenty-server/src/engine/core-modules/audit/services/audit.service.ts create mode 100644 packages/twenty-server/src/engine/core-modules/audit/types/common.type.ts rename packages/twenty-server/src/engine/core-modules/{analytics => audit}/types/events.type.ts (67%) rename packages/twenty-server/src/engine/core-modules/{analytics => audit}/utils/analytics.utils.ts (68%) rename packages/twenty-server/src/engine/core-modules/{analytics => audit}/utils/events/common/base-schemas.ts (100%) rename packages/twenty-server/src/engine/core-modules/{analytics => audit}/utils/events/pageview/pageview.ts (80%) rename packages/twenty-server/src/engine/core-modules/{analytics => audit}/utils/events/track/custom-domain/custom-domain-activated.ts (82%) rename packages/twenty-server/src/engine/core-modules/{analytics => audit}/utils/events/track/custom-domain/custom-domain-deactivated.ts (83%) rename packages/twenty-server/src/engine/core-modules/{analytics => audit}/utils/events/track/monitoring/monitoring.ts (85%) rename packages/twenty-server/src/engine/core-modules/{analytics => audit}/utils/events/track/object-record/object-record-created.ts (81%) rename packages/twenty-server/src/engine/core-modules/{analytics => audit}/utils/events/track/object-record/object-record-delete.ts (81%) rename packages/twenty-server/src/engine/core-modules/{analytics => audit}/utils/events/track/object-record/object-record-updated.ts (81%) rename packages/twenty-server/src/engine/core-modules/{analytics => audit}/utils/events/track/serverless-function/serverless-function-executed.ts (88%) rename packages/twenty-server/src/engine/core-modules/{analytics => audit}/utils/events/track/track.ts (85%) rename packages/twenty-server/src/engine/core-modules/{analytics => audit}/utils/events/track/user/user-signup.ts (79%) rename packages/twenty-server/src/engine/core-modules/{analytics => audit}/utils/events/track/webhook/webhook-response.ts (86%) rename packages/twenty-server/src/engine/core-modules/{analytics => audit}/utils/events/track/workspace-entity/workspace-entity-created.ts (83%) create mode 100644 packages/twenty-server/src/modules/messaging/monitoring/services/messaging-monitoring.service.ts delete mode 100644 packages/twenty-server/src/modules/messaging/monitoring/services/messaging-telemetry.service.ts delete mode 100644 packages/twenty-server/src/modules/timeline/jobs/upsert-timeline-activity-from-behavioral-event.ts delete mode 100644 packages/twenty-server/src/modules/timeline/repositiories/audit-log.repository.ts delete mode 100644 packages/twenty-server/src/modules/timeline/standard-objects/audit-log.workspace-entity.ts delete mode 100644 packages/twenty-server/src/modules/timeline/standard-objects/behavioral-event.workspace-entity.ts rename packages/twenty-server/test/integration/{analytics/suites/clickhouse-event-registration.integration-spec.ts => audit/suites/clickHouse-audit-event-registration.integration-spec.ts} (75%) delete mode 100644 packages/twenty-server/test/integration/graphql/suites/object-generated/audit-logs.integration-spec.ts rename packages/twenty-server/test/utils/{analytics-context.mock.ts => audit-context.mock.ts} (77%) diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 6da73e01f..c459d1460 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -625,11 +625,8 @@ export type FeatureFlagDto = { export enum FeatureFlagKey { IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled', - IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled', - IsApprovedAccessDomainsEnabled = 'IsApprovedAccessDomainsEnabled', IsCopilotEnabled = 'IsCopilotEnabled', IsCustomDomainEnabled = 'IsCustomDomainEnabled', - IsEventObjectEnabled = 'IsEventObjectEnabled', IsJsonFilterEnabled = 'IsJsonFilterEnabled', IsNewRelationEnabled = 'IsNewRelationEnabled', IsPermissionsV2Enabled = 'IsPermissionsV2Enabled', diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index c2c6b218f..b25e00e97 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -1,5 +1,5 @@ -import { gql } from '@apollo/client'; import * as Apollo from '@apollo/client'; +import { gql } from '@apollo/client'; export type Maybe = T | null; export type InputMaybe = Maybe; export type Exact = { [K in keyof T]: T[K] }; @@ -575,11 +575,8 @@ export type FeatureFlagDto = { export enum FeatureFlagKey { IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled', - IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled', - IsApprovedAccessDomainsEnabled = 'IsApprovedAccessDomainsEnabled', IsCopilotEnabled = 'IsCopilotEnabled', IsCustomDomainEnabled = 'IsCustomDomainEnabled', - IsEventObjectEnabled = 'IsEventObjectEnabled', IsJsonFilterEnabled = 'IsJsonFilterEnabled', IsNewRelationEnabled = 'IsNewRelationEnabled', IsPermissionsV2Enabled = 'IsPermissionsV2Enabled', diff --git a/packages/twenty-front/src/pages/settings/security/SettingsSecurity.tsx b/packages/twenty-front/src/pages/settings/security/SettingsSecurity.tsx index ce8539c12..c193d015a 100644 --- a/packages/twenty-front/src/pages/settings/security/SettingsSecurity.tsx +++ b/packages/twenty-front/src/pages/settings/security/SettingsSecurity.tsx @@ -9,13 +9,11 @@ import { SettingsApprovedAccessDomainsListCard } from '@/settings/security/compo import { ToggleImpersonate } from '@/settings/workspace/components/ToggleImpersonate'; import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useRecoilValue } from 'recoil'; -import { FeatureFlagKey } from '~/generated/graphql'; -import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; +import { Tag } from 'twenty-ui/components'; import { H2Title, IconLock } from 'twenty-ui/display'; import { Section } from 'twenty-ui/layout'; -import { Tag } from 'twenty-ui/components'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const StyledContainer = styled.div` width: 100%; @@ -36,9 +34,6 @@ export const SettingsSecurity = () => { const { t } = useLingui(); const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState); - const IsApprovedAccessDomainsEnabled = useIsFeatureEnabled( - FeatureFlagKey.IsApprovedAccessDomainsEnabled, - ); return ( { /> - {IsApprovedAccessDomainsEnabled && ( - - - - - )} + + + +
({ + createClient: jest.fn().mockReturnValue({ + insert: jest.fn().mockResolvedValue({}), + query: jest.fn().mockResolvedValue({ + json: jest.fn().mockResolvedValue([{ test: 'data' }]), + }), + ping: jest.fn().mockResolvedValue({ success: true }), + close: jest.fn().mockResolvedValue({}), + exec: jest.fn().mockResolvedValue({}), + }), +})); + +describe('ClickHouseService', () => { + let service: ClickHouseService; + let twentyConfigService: TwentyConfigService; + let mockClickHouseClient: jest.Mocked; + + beforeEach(async () => { + jest.clearAllMocks(); + + mockClickHouseClient = { + insert: jest.fn().mockResolvedValue({}), + query: jest.fn().mockResolvedValue({ + json: jest.fn().mockResolvedValue([{ test: 'data' }]), + }), + ping: jest.fn().mockResolvedValue({ success: true }), + close: jest.fn().mockResolvedValue({}), + exec: jest.fn().mockResolvedValue({}), + } as unknown as jest.Mocked; + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + ClickHouseService, + { + provide: TwentyConfigService, + useValue: { + get: jest.fn((key) => { + if (key === 'CLICKHOUSE_URL') return 'http://localhost:8123'; + + return undefined; + }), + }, + }, + ], + }).compile(); + + service = module.get(ClickHouseService); + twentyConfigService = module.get(TwentyConfigService); + + // Set the mock client + (service as any).mainClient = mockClickHouseClient; + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('constructor', () => { + it('should not initialize clickhouse client when clickhouse is disabled', async () => { + jest.spyOn(twentyConfigService, 'get').mockImplementation((key) => { + if (key === 'CLICKHOUSE_URL') return ''; + + return undefined; + }); + + const newModule: TestingModule = await Test.createTestingModule({ + providers: [ + ClickHouseService, + { + provide: TwentyConfigService, + useValue: twentyConfigService, + }, + ], + }).compile(); + + const newService = newModule.get(ClickHouseService); + + expect((newService as any).mainClient).toBeUndefined(); + }); + }); + + describe('insert', () => { + it('should insert data into clickhouse and return success', async () => { + const testData = [{ id: 1, name: 'test' }]; + const result = await service.insert('test_table', testData); + + expect(result).toEqual({ success: true }); + expect(mockClickHouseClient.insert).toHaveBeenCalledWith({ + table: 'test_table', + values: testData, + format: 'JSONEachRow', + }); + }); + + it('should return failure when clickhouse client is not defined', async () => { + (service as any).mainClient = undefined; + + const testData = [{ id: 1, name: 'test' }]; + const result = await service.insert('test_table', testData); + + expect(result).toEqual({ success: false }); + }); + + it('should handle errors and return failure', async () => { + const testError = new Error('Test error'); + + mockClickHouseClient.insert.mockRejectedValueOnce(testError); + + const testData = [{ id: 1, name: 'test' }]; + const result = await service.insert('test_table', testData); + + expect(result).toEqual({ success: false }); + // Since the service uses logger.error instead of exceptionHandlerService.captureExceptions, + // we don't need to assert on exceptionHandlerService + }); + }); + + describe('select', () => { + it('should execute a query and return results', async () => { + const query = 'SELECT * FROM test_table WHERE id = {id:Int32}'; + const params = { id: 1 }; + + mockClickHouseClient.query.mockResolvedValueOnce({ + json: jest.fn().mockResolvedValueOnce([{ id: 1, name: 'test' }]), + } as any); + + const result = await service.select(query, params); + + expect(result).toEqual([{ id: 1, name: 'test' }]); + expect(mockClickHouseClient.query).toHaveBeenCalledWith({ + query, + format: 'JSONEachRow', + query_params: params, + }); + }); + + it('should return empty array when clickhouse client is not defined', async () => { + (service as any).mainClient = undefined; + + const query = 'SELECT * FROM test_table'; + const result = await service.select(query); + + expect(result).toEqual([]); + }); + + it('should handle errors and return empty array', async () => { + const testError = new Error('Test error'); + + mockClickHouseClient.query.mockRejectedValueOnce(testError); + + const query = 'SELECT * FROM test_table'; + const result = await service.select(query); + + expect(result).toEqual([]); + // Since the service uses logger.error instead of exceptionHandlerService.captureExceptions, + // we don't need to assert on exceptionHandlerService + }); + }); + + describe('createDatabase', () => { + it('should create a database and return true', async () => { + const result = await service.createDatabase('test_db'); + + expect(result).toBe(true); + expect(mockClickHouseClient.exec).toHaveBeenCalledWith({ + query: 'CREATE DATABASE IF NOT EXISTS test_db', + }); + }); + + it('should return false when clickhouse client is not defined', async () => { + (service as any).mainClient = undefined; + + const result = await service.createDatabase('test_db'); + + expect(result).toBe(false); + }); + }); + + describe('dropDatabase', () => { + it('should drop a database and return true', async () => { + const result = await service.dropDatabase('test_db'); + + expect(result).toBe(true); + expect(mockClickHouseClient.exec).toHaveBeenCalledWith({ + query: 'DROP DATABASE IF EXISTS test_db', + }); + }); + + it('should return false when clickhouse client is not defined', async () => { + (service as any).mainClient = undefined; + + const result = await service.dropDatabase('test_db'); + + expect(result).toBe(false); + }); + }); + + describe('connectToClient', () => { + it('should connect to a client and return it', async () => { + jest + .spyOn(service, 'connectToClient') + .mockResolvedValueOnce(mockClickHouseClient); + + const result = await service.connectToClient('test-client'); + + expect(result).toBe(mockClickHouseClient); + }); + + it('should reuse an existing client if available', async () => { + // Set up a client in the map + (service as any).clients.set('test-client', mockClickHouseClient); + + const result = await service.connectToClient('test-client'); + + expect(result).toBe(mockClickHouseClient); + }); + }); + + describe('disconnectFromClient', () => { + it('should disconnect from a client', async () => { + // Set up a client in the map + (service as any).clients.set('test-client', mockClickHouseClient); + + await service.disconnectFromClient('test-client'); + + expect(mockClickHouseClient.close).toHaveBeenCalled(); + expect((service as any).clients.has('test-client')).toBe(false); + }); + + it('should do nothing if client does not exist', async () => { + await service.disconnectFromClient('non-existent-client'); + + expect(mockClickHouseClient.close).not.toHaveBeenCalled(); + }); + }); + + describe('lifecycle hooks', () => { + it('should ping server on module init', async () => { + await service.onModuleInit(); + + expect(mockClickHouseClient.ping).toHaveBeenCalled(); + }); + + it('should close all clients on module destroy', async () => { + // Set up a couple of clients + (service as any).clients.set('client1', mockClickHouseClient); + (service as any).clients.set('client2', mockClickHouseClient); + + await service.onModuleDestroy(); + + // One for mainClient, and two for the clients in the map + expect(mockClickHouseClient.close).toHaveBeenCalledTimes(3); + }); + }); +}); diff --git a/packages/twenty-server/src/database/clickHouse/clickHouse.service.ts b/packages/twenty-server/src/database/clickHouse/clickHouse.service.ts new file mode 100644 index 000000000..08ddbdbef --- /dev/null +++ b/packages/twenty-server/src/database/clickHouse/clickHouse.service.ts @@ -0,0 +1,230 @@ +import { + Injectable, + Logger, + OnModuleDestroy, + OnModuleInit, +} from '@nestjs/common'; + +import { ClickHouseClient, createClient } from '@clickhouse/client'; + +import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; + +@Injectable() +export class ClickHouseService implements OnModuleInit, OnModuleDestroy { + private mainClient: ClickHouseClient | undefined; + private clients: Map = new Map(); + private isClientInitializing: Map = new Map(); + private readonly logger = new Logger(ClickHouseService.name); + + constructor(private readonly twentyConfigService: TwentyConfigService) { + if (this.twentyConfigService.get('CLICKHOUSE_URL')) { + this.mainClient = createClient({ + url: this.twentyConfigService.get('CLICKHOUSE_URL'), + compression: { + response: true, + request: true, + }, + clickhouse_settings: { + async_insert: 1, + wait_for_async_insert: 1, + }, + application: 'twenty', + }); + } + } + + public getMainClient(): ClickHouseClient | undefined { + return this.mainClient; + } + + public async connectToClient( + clientId: string, + url?: string, + ): Promise { + if (!this.twentyConfigService.get('CLICKHOUSE_URL')) { + return undefined; + } + + // Wait for a bit before trying again if another initialization is in progress + while (this.isClientInitializing.get(clientId)) { + await new Promise((resolve) => setTimeout(resolve, 10)); + } + + if (this.clients.has(clientId)) { + return this.clients.get(clientId); + } + + this.isClientInitializing.set(clientId, true); + + try { + const clientInstance = await this.createAndInitializeClient(url); + + this.clients.set(clientId, clientInstance); + + return clientInstance; + } catch (err) { + this.logger.error( + `Error connecting to ClickHouse client ${clientId}`, + err, + ); + + return undefined; + } finally { + this.isClientInitializing.delete(clientId); + } + } + + private async createAndInitializeClient( + url?: string, + ): Promise { + const client = createClient({ + url: url ?? this.twentyConfigService.get('CLICKHOUSE_URL'), + compression: { + response: true, + request: true, + }, + clickhouse_settings: { + async_insert: 1, + wait_for_async_insert: 1, + }, + application: 'twenty', + }); + + // Ping to check connection + await client.ping(); + + return client; + } + + public async disconnectFromClient(clientId: string) { + if (!this.clients.has(clientId)) { + return; + } + + const client = this.clients.get(clientId); + + if (client) { + await client.close(); + } + + this.clients.delete(clientId); + } + + async onModuleInit() { + if (this.mainClient) { + // Just ping to verify the connection + try { + await this.mainClient.ping(); + } catch (err) { + this.logger.error('Error connecting to ClickHouse', err); + } + } + } + + async onModuleDestroy() { + // Close main client + if (this.mainClient) { + await this.mainClient.close(); + } + + // Close all other clients + for (const [, client] of this.clients) { + await client.close(); + } + } + + public async insert>( + table: string, + values: T[], + clientId?: string, + ): Promise<{ success: boolean }> { + try { + const client = clientId + ? await this.connectToClient(clientId) + : this.mainClient; + + if (!client) { + return { success: false }; + } + + await client.insert({ + table, + values, + format: 'JSONEachRow', + }); + + return { success: true }; + } catch (err) { + this.logger.error('Error inserting data into ClickHouse', err); + + return { success: false }; + } + } + + // Method to execute a select query + public async select( + query: string, + params?: Record, + clientId?: string, + ): Promise { + try { + const client = clientId + ? await this.connectToClient(clientId) + : this.mainClient; + + if (!client) { + return []; + } + + const resultSet = await client.query({ + query, + format: 'JSONEachRow', + query_params: params, + }); + + const result = await resultSet.json(); + + return Array.isArray(result) ? result : []; + } catch (err) { + this.logger.error('Error executing select query in ClickHouse', err); + + return []; + } + } + + public async createDatabase(databaseName: string): Promise { + try { + if (!this.mainClient) { + return false; + } + + await this.mainClient.exec({ + query: `CREATE DATABASE IF NOT EXISTS ${databaseName}`, + }); + + return true; + } catch (err) { + this.logger.error('Error creating database in ClickHouse', err); + + return false; + } + } + + public async dropDatabase(databaseName: string): Promise { + try { + if (!this.mainClient) { + return false; + } + + await this.mainClient.exec({ + query: `DROP DATABASE IF EXISTS ${databaseName}`, + }); + + return true; + } catch (err) { + this.logger.error('Error dropping database in ClickHouse', err); + + return false; + } + } +} diff --git a/packages/twenty-server/src/database/clickhouse/migrations/001-create-events-table.sql b/packages/twenty-server/src/database/clickHouse/migrations/001-create-audit-event-table.sql similarity index 67% rename from packages/twenty-server/src/database/clickhouse/migrations/001-create-events-table.sql rename to packages/twenty-server/src/database/clickHouse/migrations/001-create-audit-event-table.sql index 1e6228c70..0e28d9aa4 100644 --- a/packages/twenty-server/src/database/clickhouse/migrations/001-create-events-table.sql +++ b/packages/twenty-server/src/database/clickHouse/migrations/001-create-audit-event-table.sql @@ -1,4 +1,4 @@ -CREATE TABLE IF NOT EXISTS events +CREATE TABLE IF NOT EXISTS auditEvent ( `event` LowCardinality(String), `timestamp` DateTime64(3), @@ -7,4 +7,4 @@ CREATE TABLE IF NOT EXISTS events `properties` JSON ) ENGINE = MergeTree - ORDER BY (event, workspaceId, timestamp); \ No newline at end of file + ORDER BY (event, workspaceId, 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 similarity index 100% rename from packages/twenty-server/src/database/clickhouse/migrations/002-create-pageview-table.sql rename to packages/twenty-server/src/database/clickHouse/migrations/002-create-pageview-table.sql 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 new file mode 100644 index 000000000..19cc41020 --- /dev/null +++ b/packages/twenty-server/src/database/clickHouse/migrations/003-create-external-event-table.sql @@ -0,0 +1,12 @@ +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/run-migrations.ts b/packages/twenty-server/src/database/clickHouse/migrations/run-migrations.ts similarity index 94% rename from packages/twenty-server/src/database/clickhouse/migrations/run-migrations.ts rename to packages/twenty-server/src/database/clickHouse/migrations/run-migrations.ts index 1ce772c3d..43036d5a1 100644 --- a/packages/twenty-server/src/database/clickhouse/migrations/run-migrations.ts +++ b/packages/twenty-server/src/database/clickHouse/migrations/run-migrations.ts @@ -36,7 +36,7 @@ async function ensureDatabaseExists() { async function ensureMigrationTable(client: ClickHouseClient) { await client.command({ query: ` - CREATE TABLE IF NOT EXISTS migrations ( + CREATE TABLE IF NOT EXISTS _migration ( filename String, applied_at DateTime DEFAULT now() ) ENGINE = MergeTree() @@ -50,7 +50,7 @@ async function hasMigrationBeenRun( client: ClickHouseClient, ): Promise { const resultSet = await client.query({ - query: `SELECT count() as count FROM migrations WHERE filename = {filename:String}`, + query: `SELECT count() as count FROM _migration WHERE filename = {filename:String}`, query_params: { filename }, format: 'JSON', }); @@ -61,7 +61,7 @@ async function hasMigrationBeenRun( async function recordMigration(filename: string, client: ClickHouseClient) { await client.insert({ - table: 'migrations', + table: '_migration', values: [{ filename }], format: 'JSONEachRow', }); diff --git a/packages/twenty-server/src/engine/core-modules/analytics/utils/fixtures/fixtures.ts b/packages/twenty-server/src/database/clickHouse/seeds/fixtures.ts similarity index 78% rename from packages/twenty-server/src/engine/core-modules/analytics/utils/fixtures/fixtures.ts rename to packages/twenty-server/src/database/clickHouse/seeds/fixtures.ts index d9620939d..dfb3d07e9 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/utils/fixtures/fixtures.ts +++ b/packages/twenty-server/src/database/clickHouse/seeds/fixtures.ts @@ -1,9 +1,9 @@ -import { GenericTrackEvent } from 'src/engine/core-modules/analytics/utils/events/track/track'; -import { OBJECT_RECORD_CREATED_EVENT } from 'src/engine/core-modules/analytics/utils/events/track/object-record/object-record-created'; -import { CUSTOM_DOMAIN_ACTIVATED_EVENT } from 'src/engine/core-modules/analytics/utils/events/track/custom-domain/custom-domain-activated'; -import { CUSTOM_DOMAIN_DEACTIVATED_EVENT } from 'src/engine/core-modules/analytics/utils/events/track/custom-domain/custom-domain-deactivated'; -import { OBJECT_RECORD_UPDATED_EVENT } from 'src/engine/core-modules/analytics/utils/events/track/object-record/object-record-updated'; -import { OBJECT_RECORD_DELETED_EVENT } from 'src/engine/core-modules/analytics/utils/events/track/object-record/object-record-delete'; +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'; 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 similarity index 87% rename from packages/twenty-server/src/database/clickhouse/seeds/run-seeds.ts rename to packages/twenty-server/src/database/clickHouse/seeds/run-seeds.ts index 547ab16e6..f317f814d 100644 --- a/packages/twenty-server/src/database/clickhouse/seeds/run-seeds.ts +++ b/packages/twenty-server/src/database/clickHouse/seeds/run-seeds.ts @@ -2,7 +2,7 @@ import { createClient } from '@clickhouse/client'; import { config } from 'dotenv'; -import { fixtures } from 'src/engine/core-modules/analytics/utils/fixtures/fixtures'; +import { fixtures } from './fixtures'; config({ path: process.env.NODE_ENV === 'test' ? '.env.test' : '.env', @@ -18,7 +18,7 @@ async function seedEvents() { console.log(`⚡ Seeding ${fixtures.length} events...`); await client.insert({ - table: 'events', + table: 'auditEvent', values: fixtures, format: 'JSONEachRow', }); diff --git a/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts b/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts index 8f7cf4ac9..22596748a 100644 --- a/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts +++ b/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts @@ -25,11 +25,6 @@ export const seedFeatureFlags = async ( workspaceId: workspaceId, value: true, }, - { - key: FeatureFlagKey.IsEventObjectEnabled, - workspaceId: workspaceId, - value: false, - }, { key: FeatureFlagKey.IsStripeIntegrationEnabled, workspaceId: workspaceId, @@ -40,21 +35,11 @@ export const seedFeatureFlags = async ( workspaceId: workspaceId, value: true, }, - { - key: FeatureFlagKey.IsAnalyticsV2Enabled, - workspaceId: workspaceId, - value: true, - }, { key: FeatureFlagKey.IsCustomDomainEnabled, workspaceId: workspaceId, value: false, }, - { - key: FeatureFlagKey.IsApprovedAccessDomainsEnabled, - workspaceId: workspaceId, - value: true, - }, { key: FeatureFlagKey.IsUniqueIndexesEnabled, workspaceId: workspaceId, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/entity-events-to-db.listener.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/entity-events-to-db.listener.ts index bfb14d4ea..726e0dbc5 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/entity-events-to-db.listener.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/entity-events-to-db.listener.ts @@ -2,6 +2,7 @@ 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 { CreateAuditLogFromInternalEvent } from 'src/engine/core-modules/audit/jobs/create-audit-log-from-internal-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'; @@ -12,11 +13,10 @@ import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/t 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 { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; +import { SubscriptionsJob } from 'src/engine/subscriptions/subscriptions.job'; 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 { UpsertTimelineActivityFromInternalEvent } from 'src/modules/timeline/jobs/upsert-timeline-activity-from-internal-event.job'; import { CallWebhookJobsJob } from 'src/modules/webhook/jobs/call-webhook-jobs.job'; -import { SubscriptionsJob } from 'src/engine/subscriptions/subscriptions.job'; @Injectable() export class EntityEventsToDbListener { 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 b83967184..055d52f61 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 @@ -1,17 +1,17 @@ import { Injectable } from '@nestjs/common'; -import { AnalyticsService } from 'src/engine/core-modules/analytics/services/analytics.service'; +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 { 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'; -import { OnCustomBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-custom-batch-event.decorator'; -import { USER_SIGNUP_EVENT } from 'src/engine/core-modules/analytics/utils/events/track/user/user-signup'; -import { USER_SIGNUP_EVENT_NAME } from 'src/engine/api/graphql/workspace-query-runner/constants/user-signup-event-name.constants'; @Injectable() export class TelemetryListener { constructor( - private readonly analyticsService: AnalyticsService, + private readonly auditService: AuditService, private readonly telemetryService: TelemetryService, ) {} @@ -21,8 +21,8 @@ export class TelemetryListener { ) { await Promise.all( payload.events.map(async (eventPayload) => { - this.analyticsService - .createAnalyticsContext({ + this.auditService + .createContext({ userId: eventPayload.userId, workspaceId: payload.workspaceId, }) diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module.ts index 277a1de8d..159cbafad 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module.ts @@ -5,15 +5,15 @@ import { WorkspaceQueryBuilderModule } from 'src/engine/api/graphql/workspace-qu import { workspaceQueryRunnerFactories } from 'src/engine/api/graphql/workspace-query-runner/factories'; import { TelemetryListener } from 'src/engine/api/graphql/workspace-query-runner/listeners/telemetry.listener'; import { WorkspaceQueryHookModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.module'; -import { AnalyticsModule } from 'src/engine/core-modules/analytics/analytics.module'; +import { AuditModule } from 'src/engine/core-modules/audit/audit.module'; import { AuthModule } from 'src/engine/core-modules/auth/auth.module'; import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { FileModule } from 'src/engine/core-modules/file/file.module'; import { RecordPositionModule } from 'src/engine/core-modules/record-position/record-position.module'; +import { RecordTransformerModule } from 'src/engine/core-modules/record-transformer/record-transformer.module'; import { TelemetryModule } from 'src/engine/core-modules/telemetry/telemetry.module'; import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module'; -import { RecordTransformerModule } from 'src/engine/core-modules/record-transformer/record-transformer.module'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @@ -27,7 +27,7 @@ import { EntityEventsToDbListener } from './listeners/entity-events-to-db.listen WorkspaceQueryHookModule, ObjectMetadataRepositoryModule.forFeature([WorkspaceMemberWorkspaceEntity]), TypeOrmModule.forFeature([FeatureFlag], 'core'), - AnalyticsModule, + AuditModule, TelemetryModule, FileModule, FeatureFlagModule, diff --git a/packages/twenty-server/src/engine/core-modules/analytics/analytics.module.ts b/packages/twenty-server/src/engine/core-modules/analytics/analytics.module.ts deleted file mode 100644 index 84a751759..000000000 --- a/packages/twenty-server/src/engine/core-modules/analytics/analytics.module.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Module } from '@nestjs/common'; - -import { JwtModule } from 'src/engine/core-modules/jwt/jwt.module'; -import { ClickhouseService } from 'src/engine/core-modules/analytics/services/clickhouse.service'; - -import { AnalyticsResolver } from './analytics.resolver'; - -import { AnalyticsService } from './services/analytics.service'; - -@Module({ - providers: [AnalyticsResolver, AnalyticsService, ClickhouseService], - imports: [JwtModule], - exports: [AnalyticsService], -}) -export class AnalyticsModule {} diff --git a/packages/twenty-server/src/engine/core-modules/analytics/analytics.service.ts b/packages/twenty-server/src/engine/core-modules/analytics/analytics.service.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/twenty-server/src/engine/core-modules/analytics/services/analytics.service.ts b/packages/twenty-server/src/engine/core-modules/analytics/services/analytics.service.ts deleted file mode 100644 index 91ec83c16..000000000 --- a/packages/twenty-server/src/engine/core-modules/analytics/services/analytics.service.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - makePageview, - makeTrackEvent, -} from 'src/engine/core-modules/analytics/utils/analytics.utils'; -import { ClickhouseService } from 'src/engine/core-modules/analytics/services/clickhouse.service'; -import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; -import { - TrackEventName, - TrackEventProperties, -} from 'src/engine/core-modules/analytics/types/events.type'; -import { PageviewProperties } from 'src/engine/core-modules/analytics/utils/events/pageview/pageview'; -import { - AnalyticsException, - AnalyticsExceptionCode, -} from 'src/engine/core-modules/analytics/analytics.exception'; - -@Injectable() -export class AnalyticsService { - constructor( - private readonly twentyConfigService: TwentyConfigService, - private readonly clickhouseService: ClickhouseService, - ) {} - - createAnalyticsContext(context?: { - workspaceId?: string | null | undefined; - userId?: string | null | undefined; - }) { - const userIdAndWorkspaceId = context - ? { - ...(context.userId ? { userId: context.userId } : {}), - ...(context.workspaceId ? { workspaceId: context.workspaceId } : {}), - } - : {}; - - return { - track: ( - event: T, - properties: TrackEventProperties, - ) => - this.preventAnalyticsIfDisabled(() => - this.clickhouseService.pushEvent({ - ...userIdAndWorkspaceId, - ...makeTrackEvent(event, properties), - }), - ), - pageview: (name: string, properties: Partial) => - this.preventAnalyticsIfDisabled(() => - this.clickhouseService.pushEvent({ - ...userIdAndWorkspaceId, - ...makePageview(name, properties), - }), - ), - }; - } - - private preventAnalyticsIfDisabled( - sendEventOrPageviewFunction: () => Promise<{ success: boolean }>, - ) { - if (!this.twentyConfigService.get('ANALYTICS_ENABLED')) { - return { success: true }; - } - try { - return sendEventOrPageviewFunction(); - } catch (err) { - return new AnalyticsException(err, AnalyticsExceptionCode.INVALID_INPUT); - } - } -} diff --git a/packages/twenty-server/src/engine/core-modules/analytics/services/clickhouse.service.spec.ts b/packages/twenty-server/src/engine/core-modules/analytics/services/clickhouse.service.spec.ts deleted file mode 100644 index b98e14d16..000000000 --- a/packages/twenty-server/src/engine/core-modules/analytics/services/clickhouse.service.spec.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; - -import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; -import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; -import { CUSTOM_DOMAIN_ACTIVATED_EVENT } from 'src/engine/core-modules/analytics/utils/events/track/custom-domain/custom-domain-activated'; -import { - makePageview, - makeTrackEvent, -} from 'src/engine/core-modules/analytics/utils/analytics.utils'; - -import { ClickhouseService } from './clickhouse.service'; - -// Mock the createClient function from @clickhouse/client -jest.mock('@clickhouse/client', () => ({ - createClient: jest.fn().mockReturnValue({ - insert: jest.fn().mockResolvedValue({}), - }), -})); - -describe('ClickhouseService', () => { - let service: ClickhouseService; - let twentyConfigService: TwentyConfigService; - let exceptionHandlerService: ExceptionHandlerService; - let mockClickhouseClient: { insert: jest.Mock }; - - const mockPageview = makePageview('Home', { - href: 'https://example.com/test', - locale: 'en-US', - pathname: '/test', - referrer: 'https://example.com', - sessionId: 'test-session-id', - timeZone: 'UTC', - userAgent: 'test-user-agent', - }); - - const mockEvent = makeTrackEvent(CUSTOM_DOMAIN_ACTIVATED_EVENT, {}); - - beforeEach(async () => { - jest.clearAllMocks(); - - mockClickhouseClient = { - insert: jest.fn().mockResolvedValue({}), - }; - - const module: TestingModule = await Test.createTestingModule({ - providers: [ - ClickhouseService, - { - provide: TwentyConfigService, - useValue: { - get: jest.fn((key) => { - if (key === 'ANALYTICS_ENABLED') return true; - if (key === 'CLICKHOUSE_URL') return 'http://localhost:8123'; - - return null; - }), - }, - }, - { - provide: ExceptionHandlerService, - useValue: { - captureExceptions: jest.fn(), - }, - }, - ], - }).compile(); - - service = module.get(ClickhouseService); - twentyConfigService = module.get(TwentyConfigService); - exceptionHandlerService = module.get( - ExceptionHandlerService, - ); - - // Set the mock client - // @ts-expect-error accessing private property for testing - service.clickhouseClient = mockClickhouseClient; - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); - - describe('constructor', () => { - it('should not initialize clickhouse client when analytics is disabled', async () => { - jest.spyOn(twentyConfigService, 'get').mockImplementation((key) => { - if (key === 'ANALYTICS_ENABLED') return false; - }); - - const newModule: TestingModule = await Test.createTestingModule({ - providers: [ - ClickhouseService, - { - provide: TwentyConfigService, - useValue: twentyConfigService, - }, - { - provide: ExceptionHandlerService, - useValue: exceptionHandlerService, - }, - ], - }).compile(); - - const newService = newModule.get(ClickhouseService); - - // @ts-expect-error accessing private property for testing - expect(newService.clickhouseClient).toBeUndefined(); - }); - }); - - describe('pushEvent', () => { - it('should insert event into clickhouse and return success', async () => { - const result = await service.pushEvent(mockEvent); - - expect(result).toEqual({ success: true }); - const { type: _type, ...rest } = mockEvent; - - expect(mockClickhouseClient.insert).toHaveBeenCalledWith({ - table: 'events', - values: [rest], - format: 'JSONEachRow', - }); - }); - - it('should insert pageview into clickhouse and return success', async () => { - const result = await service.pushEvent(mockPageview); - - expect(result).toEqual({ success: true }); - const { type: _type, ...rest } = mockPageview; - - expect(mockClickhouseClient.insert).toHaveBeenCalledWith({ - table: 'pageview', - values: [rest], - format: 'JSONEachRow', - }); - }); - - it('should return success when clickhouse client is not defined', async () => { - // @ts-expect-error accessing private property for testing - service.clickhouseClient = undefined; - - const result = await service.pushEvent(mockEvent); - - expect(result).toEqual({ success: true }); - }); - - it('should handle errors and return failure', async () => { - const testError = new Error('Test error'); - - mockClickhouseClient.insert.mockRejectedValueOnce(testError); - - const result = await service.pushEvent(mockEvent); - - expect(result).toEqual({ success: false }); - expect(exceptionHandlerService.captureExceptions).toHaveBeenCalledWith([ - testError, - ]); - }); - }); -}); diff --git a/packages/twenty-server/src/engine/core-modules/analytics/services/clickhouse.service.ts b/packages/twenty-server/src/engine/core-modules/analytics/services/clickhouse.service.ts deleted file mode 100644 index d4a70ca18..000000000 --- a/packages/twenty-server/src/engine/core-modules/analytics/services/clickhouse.service.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { ClickHouseClient, createClient } from '@clickhouse/client'; - -import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; -import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; -import { - makePageview, - makeTrackEvent, -} from 'src/engine/core-modules/analytics/utils/analytics.utils'; - -@Injectable() -export class ClickhouseService { - private clickhouseClient: ClickHouseClient | undefined; - constructor( - private readonly exceptionHandlerService: ExceptionHandlerService, - private readonly twentyConfigService: TwentyConfigService, - ) { - if (twentyConfigService.get('ANALYTICS_ENABLED')) { - this.clickhouseClient = createClient({ - url: twentyConfigService.get('CLICKHOUSE_URL'), - compression: { - response: true, - request: true, - }, - clickhouse_settings: { - async_insert: 1, - wait_for_async_insert: 1, - }, - }); - } - } - - async pushEvent( - data: ( - | ReturnType - | ReturnType - ) & { userId?: string | null; workspaceId?: string | null }, - ) { - try { - if (!this.clickhouseClient) { - return { success: true }; - } - - const { type, ...rest } = data; - - await this.clickhouseClient.insert({ - table: type === 'page' ? 'pageview' : 'events', - values: [rest], - format: 'JSONEachRow', - }); - - return { success: true }; - } catch (err) { - this.exceptionHandlerService.captureExceptions([err]); - - return { success: false }; - } - } -} diff --git a/packages/twenty-server/src/engine/core-modules/analytics/types/common.type.ts b/packages/twenty-server/src/engine/core-modules/analytics/types/common.type.ts deleted file mode 100644 index cc4a2995a..000000000 --- a/packages/twenty-server/src/engine/core-modules/analytics/types/common.type.ts +++ /dev/null @@ -1,2 +0,0 @@ -export type AnalyticsCommonPropertiesType = 'timestamp' | 'version'; -export type IdentifierType = 'workspaceId' | 'userId'; diff --git a/packages/twenty-server/src/engine/core-modules/analytics/README.md b/packages/twenty-server/src/engine/core-modules/audit/README.md similarity index 87% rename from packages/twenty-server/src/engine/core-modules/analytics/README.md rename to packages/twenty-server/src/engine/core-modules/audit/README.md index 9934e7dc6..33f0f381f 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/README.md +++ b/packages/twenty-server/src/engine/core-modules/audit/README.md @@ -6,20 +6,20 @@ This module provides analytics tracking functionality for the Twenty application ### Tracking Events -The `AnalyticsService` provides a `createAnalyticsContext` 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 a `track` method. The `track` method is used to track events. ```typescript import { Injectable } from '@nestjs/common'; -import { AnalyticsService } from 'src/engine/core-modules/analytics/services/analytics.service'; -import { CUSTOM_DOMAIN_ACTIVATED_EVENT } from 'src/engine/core-modules/analytics/utils/events/track/custom-domain/custom-domain-activated'; +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'; @Injectable() export class MyService { - constructor(private readonly analyticsService: AnalyticsService) {} + constructor(private readonly auditService: AuditService) {} async doSomething() { // Create an analytics context - const analytics = this.analyticsService.createAnalyticsContext({ + const analytics = this.auditService.createContext({ workspaceId: 'workspace-id', userId: 'user-id', }); @@ -87,9 +87,9 @@ export type TrackEventProperties = T extends keyof Tra ## API -### AnalyticsService +### AuditService -#### createAnalyticsContext(context?) +#### createContext(context?) Creates an analytics context with the given user ID and workspace ID. diff --git a/packages/twenty-server/src/engine/core-modules/analytics/analytics.exception.ts b/packages/twenty-server/src/engine/core-modules/audit/audit.exception.ts similarity index 51% rename from packages/twenty-server/src/engine/core-modules/analytics/analytics.exception.ts rename to packages/twenty-server/src/engine/core-modules/audit/audit.exception.ts index 0baeb5ac7..0a4a0e351 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/analytics.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/audit.exception.ts @@ -1,12 +1,12 @@ import { CustomException } from 'src/utils/custom-exception'; -export class AnalyticsException extends CustomException { - constructor(message: string, code: AnalyticsExceptionCode) { +export class AuditException extends CustomException { + constructor(message: string, code: AuditExceptionCode) { super(message, code); } } -export enum AnalyticsExceptionCode { +export enum AuditExceptionCode { INVALID_TYPE = 'INVALID_TYPE', INVALID_INPUT = 'INVALID_INPUT', } diff --git a/packages/twenty-server/src/engine/core-modules/audit/audit.module.ts b/packages/twenty-server/src/engine/core-modules/audit/audit.module.ts new file mode 100644 index 000000000..0d5b5576f --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/audit/audit.module.ts @@ -0,0 +1,15 @@ +import { Module } from '@nestjs/common'; + +import { ClickHouseModule } from 'src/database/clickHouse/clickHouse.module'; +import { JwtModule } from 'src/engine/core-modules/jwt/jwt.module'; + +import { AuditResolver } from './audit.resolver'; + +import { AuditService } from './services/audit.service'; + +@Module({ + providers: [AuditResolver, AuditService], + imports: [JwtModule, ClickHouseModule], + exports: [AuditService], +}) +export class AuditModule {} diff --git a/packages/twenty-server/src/engine/core-modules/analytics/analytics.resolver.spec.ts b/packages/twenty-server/src/engine/core-modules/audit/audit.resolver.spec.ts similarity index 66% rename from packages/twenty-server/src/engine/core-modules/analytics/analytics.resolver.spec.ts rename to packages/twenty-server/src/engine/core-modules/audit/audit.resolver.spec.ts index b9ba87de7..bef091b16 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/analytics.resolver.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/audit.resolver.spec.ts @@ -1,36 +1,36 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { User } from 'src/engine/core-modules/user/user.entity'; import { - AnalyticsException, - AnalyticsExceptionCode, -} from 'src/engine/core-modules/analytics/analytics.exception'; + AuditException, + AuditExceptionCode, +} from 'src/engine/core-modules/audit/audit.exception'; +import { User } from 'src/engine/core-modules/user/user.entity'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { AnalyticsResolver } from './analytics.resolver'; +import { AuditResolver } from './audit.resolver'; -import { AnalyticsService } from './services/analytics.service'; +import { AuditService } from './services/audit.service'; -describe('AnalyticsResolver', () => { - let resolver: AnalyticsResolver; - let analyticsService: jest.Mocked; +describe('AuditResolver', () => { + let resolver: AuditResolver; + let auditService: jest.Mocked; beforeEach(async () => { - analyticsService = { - createAnalyticsContext: jest.fn(), + auditService = { + createContext: jest.fn(), } as any; const module: TestingModule = await Test.createTestingModule({ providers: [ - AnalyticsResolver, + AuditResolver, { - provide: AnalyticsService, - useValue: analyticsService, + provide: AuditService, + useValue: auditService, }, ], }).compile(); - resolver = module.get(AnalyticsResolver); + resolver = module.get(AuditResolver); }); it('should be defined', () => { @@ -40,7 +40,7 @@ describe('AnalyticsResolver', () => { it('should handle a valid pageview input', async () => { const mockPageview = jest.fn().mockResolvedValue('Pageview created'); - analyticsService.createAnalyticsContext.mockReturnValue({ + auditService.createContext.mockReturnValue({ pageview: mockPageview, track: jest.fn(), }); @@ -56,7 +56,7 @@ describe('AnalyticsResolver', () => { { id: 'user-1' } as User, ); - expect(analyticsService.createAnalyticsContext).toHaveBeenCalledWith({ + expect(auditService.createContext).toHaveBeenCalledWith({ workspaceId: 'workspace-1', userId: 'user-1', }); @@ -67,7 +67,7 @@ describe('AnalyticsResolver', () => { it('should handle a valid track input', async () => { const mockTrack = jest.fn().mockResolvedValue('Track created'); - analyticsService.createAnalyticsContext.mockReturnValue({ + auditService.createContext.mockReturnValue({ track: mockTrack, pageview: jest.fn(), }); @@ -83,7 +83,7 @@ describe('AnalyticsResolver', () => { { id: 'user-2' } as User, ); - expect(analyticsService.createAnalyticsContext).toHaveBeenCalledWith({ + expect(auditService.createContext).toHaveBeenCalledWith({ workspaceId: 'workspace-2', userId: 'user-2', }); @@ -91,15 +91,15 @@ describe('AnalyticsResolver', () => { expect(result).toBe('Track created'); }); - it('should throw an AnalyticsException for invalid input', async () => { + it('should throw an AuditException for invalid input', async () => { const invalidInput = { type: 'invalid' }; await expect( resolver.trackAnalytics(invalidInput as any, undefined, undefined), ).rejects.toThrowError( - new AnalyticsException( + new AuditException( 'Invalid analytics input', - AnalyticsExceptionCode.INVALID_TYPE, + AuditExceptionCode.INVALID_TYPE, ), ); }); diff --git a/packages/twenty-server/src/engine/core-modules/analytics/analytics.resolver.ts b/packages/twenty-server/src/engine/core-modules/audit/audit.resolver.ts similarity index 65% rename from packages/twenty-server/src/engine/core-modules/analytics/analytics.resolver.ts rename to packages/twenty-server/src/engine/core-modules/audit/audit.resolver.ts index 4e2fa3ffc..94e3c30bb 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/analytics.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/audit.resolver.ts @@ -1,35 +1,34 @@ import { Args, Mutation, Resolver } from '@nestjs/graphql'; +import { + AuditException, + AuditExceptionCode, +} from 'src/engine/core-modules/audit/audit.exception'; 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'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; -import { - AnalyticsException, - AnalyticsExceptionCode, -} from 'src/engine/core-modules/analytics/analytics.exception'; -import { AnalyticsService } from './services/analytics.service'; import { - CreateAnalyticsInput, CreateAnalyticsInputV2, isPageviewAnalyticsInput, isTrackAnalyticsInput, } from './dtos/create-analytics.input'; import { Analytics } from './entities/analytics.entity'; +import { AuditService } from './services/audit.service'; @Resolver(() => Analytics) -export class AnalyticsResolver { - constructor(private readonly analyticsService: AnalyticsService) {} +export class AuditResolver { + constructor(private readonly auditService: AuditService) {} - // deprecated - @Mutation(() => Analytics) - track( - @Args() _createAnalyticsInput: CreateAnalyticsInput, - @AuthWorkspace() _workspace: Workspace | undefined, - @AuthUser({ allowUndefined: true }) _user: User | undefined, + // preparing for new name + async auditTrack( + @Args() + createAnalyticsInput: CreateAnalyticsInputV2, + @AuthWorkspace() workspace: Workspace | undefined, + @AuthUser({ allowUndefined: true }) user: User | undefined, ) { - return { success: true }; + return this.trackAnalytics(createAnalyticsInput, workspace, user); } @Mutation(() => Analytics) @@ -39,7 +38,7 @@ export class AnalyticsResolver { @AuthWorkspace() workspace: Workspace | undefined, @AuthUser({ allowUndefined: true }) user: User | undefined, ) { - const analyticsContext = this.analyticsService.createAnalyticsContext({ + const analyticsContext = this.auditService.createContext({ workspaceId: workspace?.id, userId: user?.id, }); @@ -58,9 +57,9 @@ export class AnalyticsResolver { ); } - throw new AnalyticsException( + throw new AuditException( 'Invalid analytics input', - AnalyticsExceptionCode.INVALID_TYPE, + AuditExceptionCode.INVALID_TYPE, ); } } diff --git a/packages/twenty-server/src/engine/core-modules/analytics/dtos/create-analytics.input.ts b/packages/twenty-server/src/engine/core-modules/audit/dtos/create-analytics.input.ts similarity index 88% rename from packages/twenty-server/src/engine/core-modules/analytics/dtos/create-analytics.input.ts rename to packages/twenty-server/src/engine/core-modules/audit/dtos/create-analytics.input.ts index c25874e9f..5fb5448f7 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/dtos/create-analytics.input.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/dtos/create-analytics.input.ts @@ -9,8 +9,8 @@ import { } from 'class-validator'; import GraphQLJSON from 'graphql-type-json'; -import { TrackEventName } from 'src/engine/core-modules/analytics/types/events.type'; -import { PageviewProperties } from 'src/engine/core-modules/analytics/utils/events/pageview/pageview'; +import { TrackEventName } from 'src/engine/core-modules/audit/types/events.type'; +import { PageviewProperties } from 'src/engine/core-modules/audit/utils/events/pageview/pageview'; enum AnalyticsType { PAGEVIEW = 'pageview', diff --git a/packages/twenty-server/src/engine/core-modules/analytics/entities/analytics.entity.ts b/packages/twenty-server/src/engine/core-modules/audit/entities/analytics.entity.ts similarity index 100% rename from packages/twenty-server/src/engine/core-modules/analytics/entities/analytics.entity.ts rename to packages/twenty-server/src/engine/core-modules/audit/entities/analytics.entity.ts diff --git a/packages/twenty-server/src/engine/core-modules/audit/jobs/audit-job.module.ts b/packages/twenty-server/src/engine/core-modules/audit/jobs/audit-job.module.ts new file mode 100644 index 000000000..77de13ba6 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/audit/jobs/audit-job.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; + +import { AuditModule } from 'src/engine/core-modules/audit/audit.module'; +import { CreateAuditLogFromInternalEvent } from 'src/engine/core-modules/audit/jobs/create-audit-log-from-internal-event'; +import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module'; +import { TimelineActivityModule } from 'src/modules/timeline/timeline-activity.module'; +import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; + +@Module({ + imports: [ + ObjectMetadataRepositoryModule.forFeature([WorkspaceMemberWorkspaceEntity]), + TimelineActivityModule, + AuditModule, + ], + providers: [CreateAuditLogFromInternalEvent], +}) +export class AuditJobModule {} diff --git a/packages/twenty-server/src/modules/timeline/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 similarity index 53% rename from packages/twenty-server/src/modules/timeline/jobs/create-audit-log-from-internal-event.ts rename to packages/twenty-server/src/engine/core-modules/audit/jobs/create-audit-log-from-internal-event.ts index f39703014..8bc806959 100644 --- a/packages/twenty-server/src/modules/timeline/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,26 +1,22 @@ +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 { 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, + private readonly auditService: AuditService, ) {} @Process(CreateAuditLogFromInternalEvent.name) @@ -28,43 +24,26 @@ export class CreateAuditLogFromInternalEvent { workspaceEventBatch: WorkspaceEventBatch, ): Promise { 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, + // We remove "before" and "after" property for a cleaner/slimmer event payload + const eventProperties = 'diff' in eventData.properties ? { - // we remove "before" and "after" property for a cleaner/slimmer event payload + ...eventData.properties, diff: eventData.properties.diff, } - : eventData.properties, - workspaceMemberId, - workspaceEventBatch.name.split('.')[0], - eventData.objectMetadata.id, - eventData.recordId, - workspaceEventBatch.workspaceId, - ); + : eventData.properties; - const analytics = this.analyticsService.createAnalyticsContext({ + const analytics = this.auditService.createContext({ workspaceId: workspaceEventBatch.workspaceId, userId: eventData.userId, }); if (workspaceEventBatch.name.endsWith('.updated')) { - analytics.track(OBJECT_RECORD_UPDATED_EVENT, eventData.properties); + analytics.track(OBJECT_RECORD_UPDATED_EVENT, eventProperties); } else if (workspaceEventBatch.name.endsWith('.created')) { - analytics.track(OBJECT_RECORD_CREATED_EVENT, eventData.properties); + analytics.track(OBJECT_RECORD_CREATED_EVENT, eventProperties); } else if (workspaceEventBatch.name.endsWith('.deleted')) { - analytics.track(OBJECT_RECORD_DELETED_EVENT, eventData.properties); + analytics.track(OBJECT_RECORD_DELETED_EVENT, eventProperties); } } } diff --git a/packages/twenty-server/src/engine/core-modules/analytics/services/analytics.service.spec.ts b/packages/twenty-server/src/engine/core-modules/audit/services/audit.service.spec.ts similarity index 66% rename from packages/twenty-server/src/engine/core-modules/analytics/services/analytics.service.spec.ts rename to packages/twenty-server/src/engine/core-modules/audit/services/audit.service.spec.ts index 808c92afe..8c17bfa55 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/services/analytics.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/services/audit.service.spec.ts @@ -1,24 +1,24 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { AnalyticsContextMock } from 'test/utils/analytics-context.mock'; +import { AuditContextMock } from 'test/utils/audit-context.mock'; -import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; -import { ClickhouseService } from 'src/engine/core-modules/analytics/services/clickhouse.service'; +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 { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; -import { CUSTOM_DOMAIN_ACTIVATED_EVENT } from 'src/engine/core-modules/analytics/utils/events/track/custom-domain/custom-domain-activated'; +import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; -import { AnalyticsService } from './analytics.service'; +import { AuditService } from './audit.service'; -describe('AnalyticsService', () => { - let service: AnalyticsService; +describe('AuditService', () => { + let service: AuditService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ { - provide: AnalyticsService, + provide: AuditService, useValue: { - createAnalyticsContext: AnalyticsContextMock, + createContext: AuditContextMock, }, }, { @@ -28,7 +28,7 @@ describe('AnalyticsService', () => { }, }, { - provide: ClickhouseService, + provide: ClickHouseService, useValue: { pushEvent: jest.fn(), }, @@ -42,21 +42,21 @@ describe('AnalyticsService', () => { ], }).compile(); - service = module.get(AnalyticsService); + service = module.get(AuditService); }); it('should be defined', () => { expect(service).toBeDefined(); }); - describe('createAnalyticsContext', () => { + describe('createContext', () => { const mockUserIdAndWorkspaceId = { userId: 'test-user-id', workspaceId: 'test-workspace-id', }; it('should create a valid context object', () => { - const context = service.createAnalyticsContext(mockUserIdAndWorkspaceId); + const context = service.createContext(mockUserIdAndWorkspaceId); expect(context).toHaveProperty('track'); expect(context).toHaveProperty('pageview'); @@ -64,15 +64,13 @@ describe('AnalyticsService', () => { it('should call track with correct parameters', async () => { const trackSpy = jest.fn().mockResolvedValue({ success: true }); - const mockContext = AnalyticsContextMock({ + const mockContext = AuditContextMock({ track: trackSpy, }); - jest - .spyOn(service, 'createAnalyticsContext') - .mockReturnValue(mockContext); + jest.spyOn(service, 'createContext').mockReturnValue(mockContext); - const context = service.createAnalyticsContext(mockUserIdAndWorkspaceId); + const context = service.createContext(mockUserIdAndWorkspaceId); await context.track(CUSTOM_DOMAIN_ACTIVATED_EVENT, {}); @@ -81,15 +79,13 @@ describe('AnalyticsService', () => { it('should call pageview with correct parameters', async () => { const pageviewSpy = jest.fn().mockResolvedValue({ success: true }); - const mockContext = AnalyticsContextMock({ + const mockContext = AuditContextMock({ pageview: pageviewSpy, }); - jest - .spyOn(service, 'createAnalyticsContext') - .mockReturnValue(mockContext); + jest.spyOn(service, 'createContext').mockReturnValue(mockContext); - const context = service.createAnalyticsContext(mockUserIdAndWorkspaceId); + const context = service.createContext(mockUserIdAndWorkspaceId); const testPageviewProperties = { href: '/test-url', locale: '', @@ -109,7 +105,7 @@ describe('AnalyticsService', () => { }); it('should return success when track is called', async () => { - const context = service.createAnalyticsContext(mockUserIdAndWorkspaceId); + const context = service.createContext(mockUserIdAndWorkspaceId); const result = await context.track(CUSTOM_DOMAIN_ACTIVATED_EVENT, {}); @@ -117,7 +113,7 @@ describe('AnalyticsService', () => { }); it('should return success when pageview is called', async () => { - const context = service.createAnalyticsContext(mockUserIdAndWorkspaceId); + const context = service.createContext(mockUserIdAndWorkspaceId); const result = await context.pageview('page-view', {}); 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 new file mode 100644 index 000000000..0f06892b4 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/audit/services/audit.service.ts @@ -0,0 +1,68 @@ +import { Injectable } from '@nestjs/common'; + +import { ClickHouseService } from 'src/database/clickHouse/clickHouse.service'; +import { + AuditException, + AuditExceptionCode, +} from 'src/engine/core-modules/audit/audit.exception'; +import { + TrackEventName, + TrackEventProperties, +} from 'src/engine/core-modules/audit/types/events.type'; +import { + makePageview, + makeTrackEvent, +} from 'src/engine/core-modules/audit/utils/analytics.utils'; +import { PageviewProperties } from 'src/engine/core-modules/audit/utils/events/pageview/pageview'; +import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; + +@Injectable() +export class AuditService { + constructor( + private readonly twentyConfigService: TwentyConfigService, + private readonly clickHouseService: ClickHouseService, + ) {} + + createContext(context?: { + workspaceId?: string | null | undefined; + userId?: string | null | undefined; + }) { + const userIdAndWorkspaceId = context + ? { + ...(context.userId ? { userId: context.userId } : {}), + ...(context.workspaceId ? { workspaceId: context.workspaceId } : {}), + } + : {}; + + return { + track: ( + event: T, + properties: TrackEventProperties, + ) => + this.preventIfDisabled(() => + this.clickHouseService.insert('auditEvent', [ + { ...userIdAndWorkspaceId, ...makeTrackEvent(event, properties) }, + ]), + ), + pageview: (name: string, properties: Partial) => + this.preventIfDisabled(() => + this.clickHouseService.insert('pageview', [ + { ...userIdAndWorkspaceId, ...makePageview(name, properties) }, + ]), + ), + }; + } + + private preventIfDisabled( + sendEventOrPageviewFunction: () => Promise<{ success: boolean }>, + ) { + if (!this.twentyConfigService.get('CLICKHOUSE_URL')) { + return { success: true }; + } + try { + return sendEventOrPageviewFunction(); + } catch (err) { + return new AuditException(err, AuditExceptionCode.INVALID_INPUT); + } + } +} diff --git a/packages/twenty-server/src/engine/core-modules/audit/types/common.type.ts b/packages/twenty-server/src/engine/core-modules/audit/types/common.type.ts new file mode 100644 index 000000000..1a812c5ae --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/audit/types/common.type.ts @@ -0,0 +1,2 @@ +export type AuditCommonPropertiesType = 'timestamp' | 'version'; +export type IdentifierType = 'workspaceId' | 'userId'; diff --git a/packages/twenty-server/src/engine/core-modules/analytics/types/events.type.ts b/packages/twenty-server/src/engine/core-modules/audit/types/events.type.ts similarity index 67% rename from packages/twenty-server/src/engine/core-modules/analytics/types/events.type.ts rename to packages/twenty-server/src/engine/core-modules/audit/types/events.type.ts index c6810587a..c23b65281 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/types/events.type.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/types/events.type.ts @@ -1,43 +1,43 @@ -import { - WEBHOOK_RESPONSE_EVENT, - WebhookResponseTrackEvent, -} from 'src/engine/core-modules/analytics/utils/events/track/webhook/webhook-response'; -import { - SERVERLESS_FUNCTION_EXECUTED_EVENT, - ServerlessFunctionExecutedTrackEvent, -} from 'src/engine/core-modules/analytics/utils/events/track/serverless-function/serverless-function-executed'; -import { - CUSTOM_DOMAIN_DEACTIVATED_EVENT, - CustomDomainDeactivatedTrackEvent, -} from 'src/engine/core-modules/analytics/utils/events/track/custom-domain/custom-domain-deactivated'; import { CUSTOM_DOMAIN_ACTIVATED_EVENT, CustomDomainActivatedTrackEvent, -} from 'src/engine/core-modules/analytics/utils/events/track/custom-domain/custom-domain-activated'; +} from 'src/engine/core-modules/audit/utils/events/track/custom-domain/custom-domain-activated'; import { - WORKSPACE_ENTITY_CREATED_EVENT, - WorkspaceEntityCreatedTrackEvent, -} from 'src/engine/core-modules/analytics/utils/events/track/workspace-entity/workspace-entity-created'; -import { - USER_SIGNUP_EVENT, - UserSignupTrackEvent, -} from 'src/engine/core-modules/analytics/utils/events/track/user/user-signup'; + 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/analytics/utils/events/track/monitoring/monitoring'; +} from 'src/engine/core-modules/audit/utils/events/track/monitoring/monitoring'; import { OBJECT_RECORD_CREATED_EVENT, ObjectRecordCreatedTrackEvent, -} from 'src/engine/core-modules/analytics/utils/events/track/object-record/object-record-created'; -import { - OBJECT_RECORD_UPDATED_EVENT, - ObjectRecordUpdatedTrackEvent, -} from 'src/engine/core-modules/analytics/utils/events/track/object-record/object-record-updated'; +} from 'src/engine/core-modules/audit/utils/events/track/object-record/object-record-created'; import { OBJECT_RECORD_DELETED_EVENT, ObjectRecordDeletedTrackEvent, -} from 'src/engine/core-modules/analytics/utils/events/track/object-record/object-record-delete'; +} from 'src/engine/core-modules/audit/utils/events/track/object-record/object-record-delete'; +import { + OBJECT_RECORD_UPDATED_EVENT, + ObjectRecordUpdatedTrackEvent, +} from 'src/engine/core-modules/audit/utils/events/track/object-record/object-record-updated'; +import { + SERVERLESS_FUNCTION_EXECUTED_EVENT, + ServerlessFunctionExecutedTrackEvent, +} from 'src/engine/core-modules/audit/utils/events/track/serverless-function/serverless-function-executed'; +import { + USER_SIGNUP_EVENT, + UserSignupTrackEvent, +} from 'src/engine/core-modules/audit/utils/events/track/user/user-signup'; +import { + WEBHOOK_RESPONSE_EVENT, + WebhookResponseTrackEvent, +} from 'src/engine/core-modules/audit/utils/events/track/webhook/webhook-response'; +import { + WORKSPACE_ENTITY_CREATED_EVENT, + WorkspaceEntityCreatedTrackEvent, +} from 'src/engine/core-modules/audit/utils/events/track/workspace-entity/workspace-entity-created'; // Define all track event names export type TrackEventName = diff --git a/packages/twenty-server/src/engine/core-modules/analytics/utils/analytics.utils.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/analytics.utils.ts similarity index 68% rename from packages/twenty-server/src/engine/core-modules/analytics/utils/analytics.utils.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/analytics.utils.ts index eb63c5a8f..d3e735f3b 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/utils/analytics.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/analytics.utils.ts @@ -1,20 +1,20 @@ import { format } from 'date-fns'; -import { AnalyticsCommonPropertiesType } from 'src/engine/core-modules/analytics/types/common.type'; -import { - PageviewProperties, - pageviewSchema, -} from 'src/engine/core-modules/analytics/utils/events/pageview/pageview'; +import { AuditCommonPropertiesType } from 'src/engine/core-modules/audit/types/common.type'; import { TrackEventName, TrackEventProperties, -} from 'src/engine/core-modules/analytics/types/events.type'; +} from 'src/engine/core-modules/audit/types/events.type'; +import { + PageviewProperties, + pageviewSchema, +} from 'src/engine/core-modules/audit/utils/events/pageview/pageview'; import { eventsRegistry, GenericTrackEvent, -} from 'src/engine/core-modules/analytics/utils/events/track/track'; +} from 'src/engine/core-modules/audit/utils/events/track/track'; -const common = (): Record => ({ +const common = (): Record => ({ timestamp: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), version: '1', }); diff --git a/packages/twenty-server/src/engine/core-modules/analytics/utils/events/common/base-schemas.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/common/base-schemas.ts similarity index 100% rename from packages/twenty-server/src/engine/core-modules/analytics/utils/events/common/base-schemas.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/common/base-schemas.ts diff --git a/packages/twenty-server/src/engine/core-modules/analytics/utils/events/pageview/pageview.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/pageview/pageview.ts similarity index 80% rename from packages/twenty-server/src/engine/core-modules/analytics/utils/events/pageview/pageview.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/pageview/pageview.ts index a8c740aa0..6ae385138 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/utils/events/pageview/pageview.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/pageview/pageview.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { baseEventSchema } from 'src/engine/core-modules/analytics/utils/events/common/base-schemas'; +import { baseEventSchema } from 'src/engine/core-modules/audit/utils/events/common/base-schemas'; export const pageviewSchema = baseEventSchema.extend({ type: z.literal('page'), diff --git a/packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/custom-domain/custom-domain-activated.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/custom-domain/custom-domain-activated.ts similarity index 82% rename from packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/custom-domain/custom-domain-activated.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/track/custom-domain/custom-domain-activated.ts index bb2480b19..e5a4e2b42 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/custom-domain/custom-domain-activated.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/custom-domain/custom-domain-activated.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { registerEvent } from 'src/engine/core-modules/analytics/utils/events/track/track'; +import { registerEvent } from 'src/engine/core-modules/audit/utils/events/track/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/analytics/utils/events/track/custom-domain/custom-domain-deactivated.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/custom-domain/custom-domain-deactivated.ts similarity index 83% rename from packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/custom-domain/custom-domain-deactivated.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/track/custom-domain/custom-domain-deactivated.ts index c4d04d820..c078a036f 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/custom-domain/custom-domain-deactivated.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/custom-domain/custom-domain-deactivated.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { registerEvent } from 'src/engine/core-modules/analytics/utils/events/track/track'; +import { registerEvent } from 'src/engine/core-modules/audit/utils/events/track/track'; export const CUSTOM_DOMAIN_DEACTIVATED_EVENT = 'Custom Domain Deactivated' as const; diff --git a/packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/monitoring/monitoring.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/monitoring/monitoring.ts similarity index 85% rename from packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/monitoring/monitoring.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/track/monitoring/monitoring.ts index 59f340058..27fc5993d 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/monitoring/monitoring.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/monitoring/monitoring.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { registerEvent } from 'src/engine/core-modules/analytics/utils/events/track/track'; +import { registerEvent } from 'src/engine/core-modules/audit/utils/events/track/track'; export const MONITORING_EVENT = 'Monitoring' as const; export const monitoringSchema = z diff --git a/packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/object-record/object-record-created.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/object-record/object-record-created.ts similarity index 81% rename from packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/object-record/object-record-created.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/track/object-record/object-record-created.ts index 55bfe4f30..26e7e4c2e 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/object-record/object-record-created.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/object-record/object-record-created.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { registerEvent } from 'src/engine/core-modules/analytics/utils/events/track/track'; +import { registerEvent } from 'src/engine/core-modules/audit/utils/events/track/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/analytics/utils/events/track/object-record/object-record-delete.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/object-record/object-record-delete.ts similarity index 81% rename from packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/object-record/object-record-delete.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/track/object-record/object-record-delete.ts index 2865d8e1b..2223a2f28 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/object-record/object-record-delete.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/object-record/object-record-delete.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { registerEvent } from 'src/engine/core-modules/analytics/utils/events/track/track'; +import { registerEvent } from 'src/engine/core-modules/audit/utils/events/track/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/analytics/utils/events/track/object-record/object-record-updated.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/object-record/object-record-updated.ts similarity index 81% rename from packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/object-record/object-record-updated.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/track/object-record/object-record-updated.ts index 4d34ffddb..78d4f47d6 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/object-record/object-record-updated.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/object-record/object-record-updated.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { registerEvent } from 'src/engine/core-modules/analytics/utils/events/track/track'; +import { registerEvent } from 'src/engine/core-modules/audit/utils/events/track/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/analytics/utils/events/track/serverless-function/serverless-function-executed.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/serverless-function/serverless-function-executed.ts similarity index 88% rename from packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/serverless-function/serverless-function-executed.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/track/serverless-function/serverless-function-executed.ts index dd0a560bc..9721e18ee 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/serverless-function/serverless-function-executed.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/serverless-function/serverless-function-executed.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { registerEvent } from 'src/engine/core-modules/analytics/utils/events/track/track'; +import { registerEvent } from 'src/engine/core-modules/audit/utils/events/track/track'; export const SERVERLESS_FUNCTION_EXECUTED_EVENT = 'Serverless Function Executed' as const; diff --git a/packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/track.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/track.ts similarity index 85% rename from packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/track.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/track/track.ts index 54eb7cdf7..54612549d 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/track.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/track.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { baseEventSchema } from 'src/engine/core-modules/analytics/utils/events/common/base-schemas'; +import { baseEventSchema } from 'src/engine/core-modules/audit/utils/events/common/base-schemas'; export const genericTrackSchema = baseEventSchema.extend({ type: z.literal('track'), diff --git a/packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/user/user-signup.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/user/user-signup.ts similarity index 79% rename from packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/user/user-signup.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/track/user/user-signup.ts index 53c97caaf..0affd3587 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/user/user-signup.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/user/user-signup.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { registerEvent } from 'src/engine/core-modules/analytics/utils/events/track/track'; +import { registerEvent } from 'src/engine/core-modules/audit/utils/events/track/track'; export const USER_SIGNUP_EVENT = 'User Signup' as const; export const userSignupSchema = z diff --git a/packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/webhook/webhook-response.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/webhook/webhook-response.ts similarity index 86% rename from packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/webhook/webhook-response.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/track/webhook/webhook-response.ts index 81b6d070d..0106799df 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/webhook/webhook-response.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/webhook/webhook-response.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { registerEvent } from 'src/engine/core-modules/analytics/utils/events/track/track'; +import { registerEvent } from 'src/engine/core-modules/audit/utils/events/track/track'; export const WEBHOOK_RESPONSE_EVENT = 'Webhook Response' as const; export const webhookResponseSchema = z diff --git a/packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/workspace-entity/workspace-entity-created.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/workspace-entity/workspace-entity-created.ts similarity index 83% rename from packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/workspace-entity/workspace-entity-created.ts rename to packages/twenty-server/src/engine/core-modules/audit/utils/events/track/workspace-entity/workspace-entity-created.ts index 28b2ac87e..88843a77a 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/utils/events/track/workspace-entity/workspace-entity-created.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/track/workspace-entity/workspace-entity-created.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { registerEvent } from 'src/engine/core-modules/analytics/utils/events/track/track'; +import { registerEvent } from 'src/engine/core-modules/audit/utils/events/track/track'; export const WORKSPACE_ENTITY_CREATED_EVENT = 'Workspace Entity Created' as const; diff --git a/packages/twenty-server/src/engine/core-modules/core-engine.module.ts b/packages/twenty-server/src/engine/core-modules/core-engine.module.ts index d5ef3c18c..32e789491 100644 --- a/packages/twenty-server/src/engine/core-modules/core-engine.module.ts +++ b/packages/twenty-server/src/engine/core-modules/core-engine.module.ts @@ -51,7 +51,7 @@ import { RoleModule } from 'src/engine/metadata-modules/role/role.module'; import { SubscriptionsModule } from 'src/engine/subscriptions/subscriptions.module'; import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/workspace-event-emitter.module'; -import { AnalyticsModule } from './analytics/analytics.module'; +import { AuditModule } from './audit/audit.module'; import { ClientConfigModule } from './client-config/client-config.module'; import { FileModule } from './file/file.module'; @@ -59,7 +59,7 @@ import { FileModule } from './file/file.module'; imports: [ TwentyConfigModule.forRoot(), HealthModule, - AnalyticsModule, + AuditModule, AuthModule, BillingModule, ClientConfigModule, @@ -128,7 +128,7 @@ import { FileModule } from './file/file.module'; SearchModule, ], exports: [ - AnalyticsModule, + AuditModule, AuthModule, FeatureFlagModule, TimelineMessagingModule, 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 b10833f80..64c1118dc 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 @@ -13,8 +13,8 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Request, Response } from 'express'; import { Repository } from 'typeorm'; -import { AnalyticsService } from 'src/engine/core-modules/analytics/services/analytics.service'; -import { CUSTOM_DOMAIN_ACTIVATED_EVENT } from 'src/engine/core-modules/analytics/utils/events/track/custom-domain/custom-domain-activated'; +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 { AuthRestApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-rest-api-exception.filter'; import { DomainManagerException, @@ -36,7 +36,7 @@ export class CloudflareController { private readonly domainManagerService: DomainManagerService, private readonly customDomainService: CustomDomainService, private readonly exceptionHandlerService: ExceptionHandlerService, - private readonly analyticsService: AnalyticsService, + private readonly auditService: AuditService, ) {} @Post(['cloudflare/custom-hostname-webhooks', 'webhooks/cloudflare']) @@ -60,7 +60,7 @@ export class CloudflareController { if (!workspace) return; - const analytics = this.analyticsService.createAnalyticsContext({ + const analytics = this.auditService.createContext({ workspaceId: workspace.id, }); diff --git a/packages/twenty-server/src/engine/core-modules/domain-manager/controllers/cloudflare.spec.ts b/packages/twenty-server/src/engine/core-modules/domain-manager/controllers/cloudflare.spec.ts index 7a12e88f2..4bae95be7 100644 --- a/packages/twenty-server/src/engine/core-modules/domain-manager/controllers/cloudflare.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/domain-manager/controllers/cloudflare.spec.ts @@ -2,9 +2,10 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { Request, Response } from 'express'; +import { AuditContextMock } from 'test/utils/audit-context.mock'; import { Repository } from 'typeorm'; -import { AnalyticsContextMock } from 'test/utils/analytics-context.mock'; +import { AuditService } from 'src/engine/core-modules/audit/services/audit.service'; import { CloudflareController } from 'src/engine/core-modules/domain-manager/controllers/cloudflare.controller'; import { CustomDomainValidRecords } from 'src/engine/core-modules/domain-manager/dtos/custom-domain-valid-records'; import { CustomDomainService } from 'src/engine/core-modules/domain-manager/services/custom-domain.service'; @@ -13,7 +14,6 @@ import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handl import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { AnalyticsService } from 'src/engine/core-modules/analytics/services/analytics.service'; describe('CloudflareController - customHostnameWebhooks', () => { let controller: CloudflareController; @@ -64,9 +64,9 @@ describe('CloudflareController - customHostnameWebhooks', () => { }, }, { - provide: AnalyticsService, + provide: AuditService, useValue: { - createAnalyticsContext: AnalyticsContextMock, + createContext: AuditContextMock, }, }, ], diff --git a/packages/twenty-server/src/engine/core-modules/domain-manager/domain-manager.module.ts b/packages/twenty-server/src/engine/core-modules/domain-manager/domain-manager.module.ts index 61defa358..282522fd2 100644 --- a/packages/twenty-server/src/engine/core-modules/domain-manager/domain-manager.module.ts +++ b/packages/twenty-server/src/engine/core-modules/domain-manager/domain-manager.module.ts @@ -1,14 +1,14 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { AuditModule } from 'src/engine/core-modules/audit/audit.module'; import { CloudflareController } from 'src/engine/core-modules/domain-manager/controllers/cloudflare.controller'; import { CustomDomainService } from 'src/engine/core-modules/domain-manager/services/custom-domain.service'; -import { AnalyticsModule } from 'src/engine/core-modules/analytics/analytics.module'; +import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; @Module({ - imports: [AnalyticsModule, TypeOrmModule.forFeature([Workspace], 'core')], + imports: [AuditModule, TypeOrmModule.forFeature([Workspace], 'core')], providers: [DomainManagerService, CustomDomainService], exports: [DomainManagerService, CustomDomainService], controllers: [CloudflareController], diff --git a/packages/twenty-server/src/engine/core-modules/domain-manager/services/custom-domain.service.spec.ts b/packages/twenty-server/src/engine/core-modules/domain-manager/services/custom-domain.service.spec.ts index 813e00545..5ee6f91f1 100644 --- a/packages/twenty-server/src/engine/core-modules/domain-manager/services/custom-domain.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/domain-manager/services/custom-domain.service.spec.ts @@ -2,12 +2,12 @@ import { Test, TestingModule } from '@nestjs/testing'; import Cloudflare from 'cloudflare'; import { CustomHostnameCreateResponse } from 'cloudflare/resources/custom-hostnames/custom-hostnames'; -import { AnalyticsContextMock } from 'test/utils/analytics-context.mock'; +import { AuditContextMock } from 'test/utils/audit-context.mock'; +import { AuditService } from 'src/engine/core-modules/audit/services/audit.service'; +import { DomainManagerException } from 'src/engine/core-modules/domain-manager/domain-manager.exception'; import { CustomDomainService } from 'src/engine/core-modules/domain-manager/services/custom-domain.service'; import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service'; -import { DomainManagerException } from 'src/engine/core-modules/domain-manager/domain-manager.exception'; -import { AnalyticsService } from 'src/engine/core-modules/analytics/services/analytics.service'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; jest.mock('cloudflare'); @@ -28,9 +28,9 @@ describe('CustomDomainService', () => { }, }, { - provide: AnalyticsService, + provide: AuditService, useValue: { - createAnalyticsContext: AnalyticsContextMock, + createContext: AuditContextMock, }, }, { diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts index d277aedf6..fb3684a2e 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts @@ -1,15 +1,12 @@ export enum FeatureFlagKey { - IsEventObjectEnabled = 'IS_EVENT_OBJECT_ENABLED', IsAirtableIntegrationEnabled = 'IS_AIRTABLE_INTEGRATION_ENABLED', IsPostgreSQLIntegrationEnabled = 'IS_POSTGRESQL_INTEGRATION_ENABLED', IsStripeIntegrationEnabled = 'IS_STRIPE_INTEGRATION_ENABLED', IsCopilotEnabled = 'IS_COPILOT_ENABLED', IsWorkflowEnabled = 'IS_WORKFLOW_ENABLED', - IsAnalyticsV2Enabled = 'IS_ANALYTICS_V2_ENABLED', IsUniqueIndexesEnabled = 'IS_UNIQUE_INDEXES_ENABLED', IsJsonFilterEnabled = 'IS_JSON_FILTER_ENABLED', IsCustomDomainEnabled = 'IS_CUSTOM_DOMAIN_ENABLED', - IsApprovedAccessDomainsEnabled = 'IS_APPROVED_ACCESS_DOMAINS_ENABLED', IsNewRelationEnabled = 'IS_NEW_RELATION_ENABLED', IsPermissionsV2Enabled = 'IS_PERMISSIONS_V2_ENABLED', } diff --git a/packages/twenty-server/src/engine/core-modules/message-queue/jobs.module.ts b/packages/twenty-server/src/engine/core-modules/message-queue/jobs.module.ts index 2fe7d799e..59e2646cf 100644 --- a/packages/twenty-server/src/engine/core-modules/message-queue/jobs.module.ts +++ b/packages/twenty-server/src/engine/core-modules/message-queue/jobs.module.ts @@ -4,6 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace.module'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; +import { AuditJobModule } from 'src/engine/core-modules/audit/jobs/audit-job.module'; import { AuthModule } from 'src/engine/core-modules/auth/auth.module'; import { BillingModule } from 'src/engine/core-modules/billing/billing.module'; import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; @@ -19,6 +20,7 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; +import { SubscriptionsModule } from 'src/engine/subscriptions/subscriptions.module'; import { CleanOnboardingWorkspacesJob } from 'src/engine/workspace-manager/workspace-cleaner/crons/clean-onboarding-workspaces.job'; import { CleanSuspendedWorkspacesJob } from 'src/engine/workspace-manager/workspace-cleaner/crons/clean-suspended-workspaces.job'; import { CleanWorkspaceDeletionWarningUserVarsJob } from 'src/engine/workspace-manager/workspace-cleaner/jobs/clean-workspace-deletion-warning-user-vars.job'; @@ -32,7 +34,6 @@ import { TimelineJobModule } from 'src/modules/timeline/jobs/timeline-job.module import { TimelineActivityModule } from 'src/modules/timeline/timeline-activity.module'; import { WebhookJobModule } from 'src/modules/webhook/jobs/webhook-job.module'; import { WorkflowModule } from 'src/modules/workflow/workflow.module'; -import { SubscriptionsModule } from 'src/engine/subscriptions/subscriptions.module'; @Module({ imports: [ @@ -60,6 +61,7 @@ import { SubscriptionsModule } from 'src/engine/subscriptions/subscriptions.modu FavoriteModule, WorkspaceCleanerModule, SubscriptionsModule, + AuditJobModule, ], providers: [ CleanSuspendedWorkspacesJob, diff --git a/packages/twenty-server/src/engine/core-modules/twenty-config/enums/config-variables-group.enum.ts b/packages/twenty-server/src/engine/core-modules/twenty-config/enums/config-variables-group.enum.ts index d356499e4..5518da47c 100644 --- a/packages/twenty-server/src/engine/core-modules/twenty-config/enums/config-variables-group.enum.ts +++ b/packages/twenty-server/src/engine/core-modules/twenty-config/enums/config-variables-group.enum.ts @@ -16,6 +16,6 @@ export enum ConfigVariablesGroup { ServerlessConfig = 'serverless-config', SSL = 'ssl', SupportChatConfig = 'support-chat-config', - AnalyticsConfig = 'analytics-config', + AnalyticsConfig = 'audit-config', TokensDuration = 'tokens-duration', } diff --git a/packages/twenty-server/src/engine/core-modules/user/user.module.ts b/packages/twenty-server/src/engine/core-modules/user/user.module.ts index 823c6844e..28ac87887 100644 --- a/packages/twenty-server/src/engine/core-modules/user/user.module.ts +++ b/packages/twenty-server/src/engine/core-modules/user/user.module.ts @@ -7,7 +7,7 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { TypeORMService } from 'src/database/typeorm/typeorm.service'; -import { AnalyticsModule } from 'src/engine/core-modules/analytics/analytics.module'; +import { AuditModule } from 'src/engine/core-modules/audit/audit.module'; import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module'; @@ -46,7 +46,7 @@ import { UserService } from './services/user.service'; OnboardingModule, TypeOrmModule.forFeature([KeyValuePair, UserWorkspace], 'core'), UserVarsModule, - AnalyticsModule, + AuditModule, DomainManagerModule, UserRoleModule, FeatureFlagModule, diff --git a/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts b/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts index 62d43d3ef..da764abb7 100644 --- a/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts @@ -20,7 +20,6 @@ import { In, Repository } from 'typeorm'; import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface'; import { SupportDriver } from 'src/engine/core-modules/twenty-config/interfaces/support.interface'; -import { AnalyticsService } from 'src/engine/core-modules/analytics/services/analytics.service'; import { AuthException, AuthExceptionCode, @@ -75,7 +74,6 @@ export class UserResolver { private readonly onboardingService: OnboardingService, private readonly userVarService: UserVarsService, private readonly fileService: FileService, - private readonly analyticsService: AnalyticsService, private readonly domainManagerService: DomainManagerService, @InjectRepository(UserWorkspace, 'core') private readonly userWorkspaceRepository: Repository, diff --git a/packages/twenty-server/src/engine/core-modules/workspace/__tests__/workspace.service.spec.ts b/packages/twenty-server/src/engine/core-modules/workspace/__tests__/workspace.service.spec.ts index 99ea85204..014691b94 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/__tests__/workspace.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/__tests__/workspace.service.spec.ts @@ -3,6 +3,7 @@ import { getRepositoryToken } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; +import { AuditService } from 'src/engine/core-modules/audit/services/audit.service'; import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service'; import { BillingService } from 'src/engine/core-modules/billing/services/billing.service'; import { CustomDomainService } from 'src/engine/core-modules/domain-manager/services/custom-domain.service'; @@ -25,7 +26,6 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service'; import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service'; -import { AnalyticsService } from 'src/engine/core-modules/analytics/services/analytics.service'; describe('WorkspaceService', () => { let service: WorkspaceService; @@ -76,9 +76,9 @@ describe('WorkspaceService', () => { }, }, { - provide: AnalyticsService, + provide: AuditService, useValue: { - createAnalyticsContext: jest.fn(), + createContext: jest.fn(), }, }, ...[ 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 e7beadd34..03ab520df 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 @@ -8,6 +8,9 @@ import { isDefined } from 'twenty-shared/utils'; 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 { 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'; @@ -44,9 +47,6 @@ import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service'; import { DEFAULT_FEATURE_FLAGS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/default-feature-flags'; import { extractVersionMajorMinorPatch } from 'src/utils/version/extract-version-major-minor-patch'; -import { AnalyticsService } from 'src/engine/core-modules/analytics/services/analytics.service'; -import { CUSTOM_DOMAIN_ACTIVATED_EVENT } from 'src/engine/core-modules/analytics/utils/events/track/custom-domain/custom-domain-activated'; -import { CUSTOM_DOMAIN_DEACTIVATED_EVENT } from 'src/engine/core-modules/analytics/utils/events/track/custom-domain/custom-domain-deactivated'; @Injectable() // eslint-disable-next-line @nx/workspace-inject-workspace-repository @@ -70,7 +70,7 @@ export class WorkspaceService extends TypeOrmQueryService { private readonly domainManagerService: DomainManagerService, private readonly exceptionHandlerService: ExceptionHandlerService, private readonly permissionsService: PermissionsService, - private readonly analyticsService: AnalyticsService, + private readonly auditService: AuditService, private readonly customDomainService: CustomDomainService, private readonly workspaceCacheStorageService: WorkspaceCacheStorageService, @InjectMessageQueue(MessageQueue.deleteCascadeQueue) @@ -418,7 +418,7 @@ export class WorkspaceService extends TypeOrmQueryService { workspace.isCustomDomainEnabled = isCustomDomainWorking; await this.workspaceRepository.save(workspace); - const analytics = this.analyticsService.createAnalyticsContext({ + const analytics = this.auditService.createContext({ workspaceId: workspace.id, }); diff --git a/packages/twenty-server/src/engine/core-modules/workspace/workspace.module.ts b/packages/twenty-server/src/engine/core-modules/workspace/workspace.module.ts index e9b5f562a..83502a474 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/workspace.module.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/workspace.module.ts @@ -5,6 +5,7 @@ import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql'; import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; +import { AuditModule } from 'src/engine/core-modules/audit/audit.module'; import { TokenModule } from 'src/engine/core-modules/auth/token/token.module'; import { BillingModule } from 'src/engine/core-modules/billing/billing.module'; import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; @@ -24,7 +25,6 @@ import { RoleModule } from 'src/engine/metadata-modules/role/role.module'; import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module'; import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module'; -import { AnalyticsModule } from 'src/engine/core-modules/analytics/analytics.module'; import { workspaceAutoResolverOpts } from './workspace.auto-resolver-opts'; import { Workspace } from './workspace.entity'; @@ -55,7 +55,7 @@ import { WorkspaceService } from './services/workspace.service'; TypeORMModule, PermissionsModule, WorkspaceCacheStorageModule, - AnalyticsModule, + AuditModule, RoleModule, ], services: [WorkspaceService], diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.module.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.module.ts index 02f2b0acc..f1023efec 100644 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.module.ts @@ -3,7 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; -import { AnalyticsModule } from 'src/engine/core-modules/analytics/analytics.module'; +import { AuditModule } from 'src/engine/core-modules/audit/audit.module'; import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module'; import { FileModule } from 'src/engine/core-modules/file/file.module'; @@ -19,7 +19,7 @@ import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverles TypeOrmModule.forFeature([FeatureFlag], 'core'), FileModule, ThrottlerModule, - AnalyticsModule, + AuditModule, ], providers: [ServerlessFunctionService, ServerlessFunctionResolver], exports: [ServerlessFunctionService], 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 2dae70a4a..410f02005 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 @@ -10,7 +10,8 @@ import { IsNull, Not, Repository } from 'typeorm'; import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception'; import { ServerlessExecuteResult } from 'src/engine/core-modules/serverless/drivers/interfaces/serverless-driver.interface'; -import { AnalyticsService } from 'src/engine/core-modules/analytics/services/analytics.service'; +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 { 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'; @@ -35,7 +36,6 @@ import { ServerlessFunctionException, ServerlessFunctionExceptionCode, } from 'src/engine/metadata-modules/serverless-function/serverless-function.exception'; -import { SERVERLESS_FUNCTION_EXECUTED_EVENT } from 'src/engine/core-modules/analytics/utils/events/track/serverless-function/serverless-function-executed'; @Injectable() export class ServerlessFunctionService { @@ -46,7 +46,7 @@ export class ServerlessFunctionService { private readonly serverlessFunctionRepository: Repository, private readonly throttlerService: ThrottlerService, private readonly twentyConfigService: TwentyConfigService, - private readonly analyticsService: AnalyticsService, + private readonly auditService: AuditService, @InjectMessageQueue(MessageQueue.serverlessFunctionQueue) private readonly messageQueueService: MessageQueueService, ) {} @@ -144,8 +144,8 @@ export class ServerlessFunctionService { version, ); - this.analyticsService - .createAnalyticsContext({ + this.auditService + .createContext({ workspaceId, }) .track(SERVERLESS_FUNCTION_EXECUTED_EVENT, { diff --git a/packages/twenty-server/src/engine/middlewares/constants/excluded-middleware-operations.constant.ts b/packages/twenty-server/src/engine/middlewares/constants/excluded-middleware-operations.constant.ts index 39b3c4040..3265e4e29 100644 --- a/packages/twenty-server/src/engine/middlewares/constants/excluded-middleware-operations.constant.ts +++ b/packages/twenty-server/src/engine/middlewares/constants/excluded-middleware-operations.constant.ts @@ -3,6 +3,7 @@ export const EXCLUDED_MIDDLEWARE_OPERATIONS = [ 'GetWorkspaceFromInviteHash', 'Track', 'TrackAnalytics', + 'AuditTrack', 'CheckUserExists', 'GetLoginTokenFromCredentials', 'GetAuthTokensFromLoginToken', diff --git a/packages/twenty-server/src/engine/object-metadata-repository/metadata-to-repository.mapping.ts b/packages/twenty-server/src/engine/object-metadata-repository/metadata-to-repository.mapping.ts index 6f0bc8dff..533617156 100644 --- a/packages/twenty-server/src/engine/object-metadata-repository/metadata-to-repository.mapping.ts +++ b/packages/twenty-server/src/engine/object-metadata-repository/metadata-to-repository.mapping.ts @@ -1,10 +1,8 @@ import { BlocklistRepository } from 'src/modules/blocklist/repositories/blocklist.repository'; -import { AuditLogRepository } from 'src/modules/timeline/repositiories/audit-log.repository'; import { TimelineActivityRepository } from 'src/modules/timeline/repositiories/timeline-activity.repository'; import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository'; export const metadataToRepositoryMapping = { - AuditLogWorkspaceEntity: AuditLogRepository, BlocklistWorkspaceEntity: BlocklistRepository, TimelineActivityWorkspaceEntity: TimelineActivityRepository, WorkspaceMemberWorkspaceEntity: WorkspaceMemberRepository, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts index b162cfffd..7f553948c 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts @@ -21,8 +21,6 @@ import { OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-obj import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity'; import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/task-target.workspace-entity'; import { TaskWorkspaceEntity } from 'src/modules/task/standard-objects/task.workspace-entity'; -import { AuditLogWorkspaceEntity } from 'src/modules/timeline/standard-objects/audit-log.workspace-entity'; -import { BehavioralEventWorkspaceEntity } from 'src/modules/timeline/standard-objects/behavioral-event.workspace-entity'; import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity'; import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity'; import { ViewFilterGroupWorkspaceEntity } from 'src/modules/view/standard-objects/view-filter-group.workspace-entity'; @@ -31,19 +29,17 @@ import { ViewGroupWorkspaceEntity } from 'src/modules/view/standard-objects/view import { ViewSortWorkspaceEntity } from 'src/modules/view/standard-objects/view-sort.workspace-entity'; import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; import { WebhookWorkspaceEntity } from 'src/modules/webhook/standard-objects/webhook.workspace-entity'; +import { WorkflowAutomatedTriggerWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-automated-trigger.workspace-entity'; import { WorkflowEventListenerWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-event-listener.workspace-entity'; import { WorkflowRunWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity'; import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; -import { WorkflowAutomatedTriggerWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-automated-trigger.workspace-entity'; // TODO: Maybe we should automate this with the DiscoverService of Nest.JS export const standardObjectMetadataDefinitions = [ ApiKeyWorkspaceEntity, - AuditLogWorkspaceEntity, AttachmentWorkspaceEntity, - BehavioralEventWorkspaceEntity, BlocklistWorkspaceEntity, CalendarEventWorkspaceEntity, CalendarChannelWorkspaceEntity, diff --git a/packages/twenty-server/src/modules/api-key/standard-objects/api-key.workspace-entity.ts b/packages/twenty-server/src/modules/api-key/standard-objects/api-key.workspace-entity.ts index 39f911916..a1010382c 100644 --- a/packages/twenty-server/src/modules/api-key/standard-objects/api-key.workspace-entity.ts +++ b/packages/twenty-server/src/modules/api-key/standard-objects/api-key.workspace-entity.ts @@ -4,7 +4,6 @@ import { FieldMetadataType } from 'twenty-shared/types'; 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 { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.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 { API_KEY_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; @@ -21,7 +20,6 @@ import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync labelIdentifierStandardId: API_KEY_STANDARD_FIELD_IDS.name, }) @WorkspaceIsSystem() -@WorkspaceIsNotAuditLogged() export class ApiKeyWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceField({ standardId: API_KEY_STANDARD_FIELD_IDS.name, diff --git a/packages/twenty-server/src/modules/blocklist/standard-objects/blocklist.workspace-entity.ts b/packages/twenty-server/src/modules/blocklist/standard-objects/blocklist.workspace-entity.ts index 3f3e18262..7c12b368c 100644 --- a/packages/twenty-server/src/modules/blocklist/standard-objects/blocklist.workspace-entity.ts +++ b/packages/twenty-server/src/modules/blocklist/standard-objects/blocklist.workspace-entity.ts @@ -1,14 +1,13 @@ import { msg } from '@lingui/core/macro'; import { FieldMetadataType } from 'twenty-shared/types'; +import { RelationOnDeleteAction } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-on-delete-action.interface'; 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 { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.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'; @@ -27,7 +26,6 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta labelIdentifierStandardId: BLOCKLIST_STANDARD_FIELD_IDS.handle, }) @WorkspaceIsSystem() -@WorkspaceIsNotAuditLogged() export class BlocklistWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceField({ standardId: BLOCKLIST_STANDARD_FIELD_IDS.handle, diff --git a/packages/twenty-server/src/modules/connected-account/standard-objects/connected-account.workspace-entity.ts b/packages/twenty-server/src/modules/connected-account/standard-objects/connected-account.workspace-entity.ts index de8992dd1..94bda9f83 100644 --- a/packages/twenty-server/src/modules/connected-account/standard-objects/connected-account.workspace-entity.ts +++ b/packages/twenty-server/src/modules/connected-account/standard-objects/connected-account.workspace-entity.ts @@ -11,7 +11,6 @@ import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/i 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 { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.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'; @@ -33,7 +32,6 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta labelIdentifierStandardId: CONNECTED_ACCOUNT_STANDARD_FIELD_IDS.handle, }) @WorkspaceIsSystem() -@WorkspaceIsNotAuditLogged() export class ConnectedAccountWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceField({ standardId: CONNECTED_ACCOUNT_STANDARD_FIELD_IDS.handle, diff --git a/packages/twenty-server/src/modules/favorite/standard-objects/favorite.workspace-entity.ts b/packages/twenty-server/src/modules/favorite/standard-objects/favorite.workspace-entity.ts index 8f7a4e94a..e98817003 100644 --- a/packages/twenty-server/src/modules/favorite/standard-objects/favorite.workspace-entity.ts +++ b/packages/twenty-server/src/modules/favorite/standard-objects/favorite.workspace-entity.ts @@ -4,12 +4,12 @@ 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/relation-metadata/relation-metadata.entity'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-entity'; import { WorkspaceDynamicRelation } from 'src/engine/twenty-orm/decorators/workspace-dynamic-relation.decorator'; import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; -import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.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'; @@ -28,7 +28,6 @@ import { WorkflowRunWorkspaceEntity } from 'src/modules/workflow/common/standard import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; -import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; @WorkspaceEntity({ standardId: STANDARD_OBJECT_IDS.favorite, @@ -38,7 +37,6 @@ import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-met description: msg`A favorite that can be accessed from the left menu`, icon: STANDARD_OBJECT_ICONS.favorite, }) -@WorkspaceIsNotAuditLogged() @WorkspaceIsSystem() export class FavoriteWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceField({ diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job.ts index f9eafd9aa..7f16834be 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job.ts @@ -19,7 +19,7 @@ import { MessageImportSyncStep, } from 'src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service'; import { MessagingPartialMessageListFetchService } from 'src/modules/messaging/message-import-manager/services/messaging-partial-message-list-fetch.service'; -import { MessagingTelemetryService } from 'src/modules/messaging/monitoring/services/messaging-telemetry.service'; +import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/services/messaging-monitoring.service'; export type MessagingMessageListFetchJobData = { messageChannelId: string; @@ -36,7 +36,7 @@ export class MessagingMessageListFetchJob { constructor( private readonly messagingFullMessageListFetchService: MessagingFullMessageListFetchService, private readonly messagingPartialMessageListFetchService: MessagingPartialMessageListFetchService, - private readonly messagingTelemetryService: MessagingTelemetryService, + private readonly messagingMonitoringService: MessagingMonitoringService, private readonly twentyORMManager: TwentyORMManager, private readonly connectedAccountRefreshTokensService: ConnectedAccountRefreshTokensService, private readonly messageImportErrorHandlerService: MessageImportExceptionHandlerService, @@ -46,7 +46,7 @@ export class MessagingMessageListFetchJob { async handle(data: MessagingMessageListFetchJobData): Promise { const { messageChannelId, workspaceId } = data; - await this.messagingTelemetryService.track({ + await this.messagingMonitoringService.track({ eventName: 'message_list_fetch_job.triggered', messageChannelId, workspaceId, @@ -65,7 +65,7 @@ export class MessagingMessageListFetchJob { }); if (!messageChannel) { - await this.messagingTelemetryService.track({ + await this.messagingMonitoringService.track({ eventName: 'message_list_fetch_job.error.message_channel_not_found', messageChannelId, workspaceId, @@ -94,7 +94,7 @@ export class MessagingMessageListFetchJob { switch (error.code) { case ConnectedAccountRefreshAccessTokenExceptionCode.REFRESH_ACCESS_TOKEN_FAILED: case ConnectedAccountRefreshAccessTokenExceptionCode.REFRESH_TOKEN_NOT_FOUND: - await this.messagingTelemetryService.track({ + await this.messagingMonitoringService.track({ eventName: `refresh_token.error.insufficient_permissions`, workspaceId, connectedAccountId: messageChannel.connectedAccountId, @@ -121,7 +121,7 @@ export class MessagingMessageListFetchJob { `Fetching partial message list for workspace ${workspaceId} and messageChannelId ${messageChannel.id}`, ); - await this.messagingTelemetryService.track({ + await this.messagingMonitoringService.track({ eventName: 'partial_message_list_fetch.started', workspaceId, connectedAccountId: messageChannel.connectedAccount.id, @@ -134,7 +134,7 @@ export class MessagingMessageListFetchJob { workspaceId, ); - await this.messagingTelemetryService.track({ + await this.messagingMonitoringService.track({ eventName: 'partial_message_list_fetch.completed', workspaceId, connectedAccountId: messageChannel.connectedAccount.id, @@ -148,7 +148,7 @@ export class MessagingMessageListFetchJob { `Fetching full message list for workspace ${workspaceId} and account ${messageChannel.connectedAccount.id}`, ); - await this.messagingTelemetryService.track({ + await this.messagingMonitoringService.track({ eventName: 'full_message_list_fetch.started', workspaceId, connectedAccountId: messageChannel.connectedAccount.id, @@ -160,7 +160,7 @@ export class MessagingMessageListFetchJob { workspaceId, ); - await this.messagingTelemetryService.track({ + await this.messagingMonitoringService.track({ eventName: 'full_message_list_fetch.completed', workspaceId, connectedAccountId: messageChannel.connectedAccount.id, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-messages-import.job.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-messages-import.job.ts index 449d8a388..ce8bd9f7e 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-messages-import.job.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-messages-import.job.ts @@ -10,8 +10,7 @@ import { MessageChannelWorkspaceEntity, } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { MessagingMessagesImportService } from 'src/modules/messaging/message-import-manager/services/messaging-messages-import.service'; -import { MessagingTelemetryService } from 'src/modules/messaging/monitoring/services/messaging-telemetry.service'; - +import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/services/messaging-monitoring.service'; export type MessagingMessagesImportJobData = { messageChannelId: string; workspaceId: string; @@ -24,7 +23,7 @@ export type MessagingMessagesImportJobData = { export class MessagingMessagesImportJob { constructor( private readonly messagingMessagesImportService: MessagingMessagesImportService, - private readonly messagingTelemetryService: MessagingTelemetryService, + private readonly messagingMonitoringService: MessagingMonitoringService, private readonly twentyORMManager: TwentyORMManager, ) {} @@ -32,7 +31,7 @@ export class MessagingMessagesImportJob { async handle(data: MessagingMessagesImportJobData): Promise { const { messageChannelId, workspaceId } = data; - await this.messagingTelemetryService.track({ + await this.messagingMonitoringService.track({ eventName: 'messages_import.triggered', workspaceId, messageChannelId, @@ -51,7 +50,7 @@ export class MessagingMessagesImportJob { }); if (!messageChannel) { - await this.messagingTelemetryService.track({ + await this.messagingMonitoringService.track({ eventName: 'messages_import.error.message_channel_not_found', messageChannelId, workspaceId, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.spec.ts index b65de9665..fb1365b37 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.spec.ts @@ -20,8 +20,7 @@ import { MessagingGetMessagesService } from 'src/modules/messaging/message-impor import { MessageImportExceptionHandlerService } from 'src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service'; import { MessagingMessagesImportService } from 'src/modules/messaging/message-import-manager/services/messaging-messages-import.service'; import { MessagingSaveMessagesAndEnqueueContactCreationService } from 'src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service'; -import { MessagingTelemetryService } from 'src/modules/messaging/monitoring/services/messaging-telemetry.service'; - +import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/services/messaging-monitoring.service'; describe('MessagingMessagesImportService', () => { let service: MessagingMessagesImportService; let messageChannelSyncStatusService: MessageChannelSyncStatusService; @@ -78,7 +77,7 @@ describe('MessagingMessagesImportService', () => { }, }, { - provide: MessagingTelemetryService, + provide: MessagingMonitoringService, useValue: { track: jest.fn().mockResolvedValue(undefined), }, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts index 671d0d267..e0101082e 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts @@ -26,8 +26,7 @@ import { } from 'src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service'; import { MessagingSaveMessagesAndEnqueueContactCreationService } from 'src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service'; import { filterEmails } from 'src/modules/messaging/message-import-manager/utils/filter-emails.util'; -import { MessagingTelemetryService } from 'src/modules/messaging/monitoring/services/messaging-telemetry.service'; - +import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/services/messaging-monitoring.service'; @Injectable() export class MessagingMessagesImportService { private readonly logger = new Logger(MessagingMessagesImportService.name); @@ -38,7 +37,7 @@ export class MessagingMessagesImportService { private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService, private readonly saveMessagesAndEnqueueContactCreationService: MessagingSaveMessagesAndEnqueueContactCreationService, private readonly connectedAccountRefreshTokensService: ConnectedAccountRefreshTokensService, - private readonly messagingTelemetryService: MessagingTelemetryService, + private readonly messagingMonitoringService: MessagingMonitoringService, @InjectObjectMetadataRepository(BlocklistWorkspaceEntity) private readonly blocklistRepository: BlocklistRepository, private readonly emailAliasManagerService: EmailAliasManagerService, @@ -62,7 +61,7 @@ export class MessagingMessagesImportService { return; } - await this.messagingTelemetryService.track({ + await this.messagingMonitoringService.track({ eventName: 'messages_import.started', workspaceId, connectedAccountId: messageChannel.connectedAccountId, @@ -87,7 +86,7 @@ export class MessagingMessagesImportService { switch (error.code) { case ConnectedAccountRefreshAccessTokenExceptionCode.REFRESH_ACCESS_TOKEN_FAILED: case ConnectedAccountRefreshAccessTokenExceptionCode.REFRESH_TOKEN_NOT_FOUND: - await this.messagingTelemetryService.track({ + await this.messagingMonitoringService.track({ eventName: `refresh_token.error.insufficient_permissions`, workspaceId, connectedAccountId: messageChannel.connectedAccountId, @@ -208,7 +207,7 @@ export class MessagingMessagesImportService { messageChannel: MessageChannelWorkspaceEntity, workspaceId: string, ) { - await this.messagingTelemetryService.track({ + await this.messagingMonitoringService.track({ eventName: 'messages_import.completed', workspaceId, connectedAccountId: messageChannel.connectedAccountId, diff --git a/packages/twenty-server/src/modules/messaging/message-participant-manager/message-participant-manager.module.ts b/packages/twenty-server/src/modules/messaging/message-participant-manager/message-participant-manager.module.ts index ad1262d9a..df9416234 100644 --- a/packages/twenty-server/src/modules/messaging/message-participant-manager/message-participant-manager.module.ts +++ b/packages/twenty-server/src/modules/messaging/message-participant-manager/message-participant-manager.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { AnalyticsModule } from 'src/engine/core-modules/analytics/analytics.module'; +import { AuditModule } from 'src/engine/core-modules/audit/audit.module'; import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; @@ -22,7 +22,7 @@ import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-o @Module({ imports: [ TypeOrmModule.forFeature([FeatureFlag, Workspace], 'core'), - AnalyticsModule, + AuditModule, ContactCreationManagerModule, WorkspaceDataSourceModule, ObjectMetadataRepositoryModule.forFeature([ diff --git a/packages/twenty-server/src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.job.ts b/packages/twenty-server/src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.job.ts index f48f1d603..af06f0e41 100644 --- a/packages/twenty-server/src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.job.ts +++ b/packages/twenty-server/src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.job.ts @@ -13,7 +13,7 @@ import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queu import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { MessagingTelemetryService } from 'src/modules/messaging/monitoring/services/messaging-telemetry.service'; +import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/services/messaging-monitoring.service'; export const MESSAGING_MESSAGE_CHANNEL_SYNC_STATUS_MONITORING_CRON_PATTERN = '2/10 * * * *'; //Every 10 minutes, starting at 2 minutes past the hour @@ -27,7 +27,7 @@ export class MessagingMessageChannelSyncStatusMonitoringCronJob { constructor( @InjectRepository(Workspace, 'core') private readonly workspaceRepository: Repository, - private readonly messagingTelemetryService: MessagingTelemetryService, + private readonly messagingMonitoringService: MessagingMonitoringService, private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly exceptionHandlerService: ExceptionHandlerService, ) {} @@ -40,7 +40,7 @@ export class MessagingMessageChannelSyncStatusMonitoringCronJob { async handle(): Promise { this.logger.log('Starting message channel sync status monitoring...'); - await this.messagingTelemetryService.track({ + await this.messagingMonitoringService.track({ eventName: 'message_channel.monitoring.sync_status.start', message: 'Starting message channel sync status monitoring', }); @@ -66,7 +66,7 @@ export class MessagingMessageChannelSyncStatusMonitoringCronJob { if (!messageChannel.syncStatus) { continue; } - await this.messagingTelemetryService.track({ + await this.messagingMonitoringService.track({ eventName: `message_channel.monitoring.sync_status.${snakeCase( messageChannel.syncStatus, )}`, diff --git a/packages/twenty-server/src/modules/messaging/monitoring/messaging-monitoring.module.ts b/packages/twenty-server/src/modules/messaging/monitoring/messaging-monitoring.module.ts index e0e777712..3371bcc4e 100644 --- a/packages/twenty-server/src/modules/messaging/monitoring/messaging-monitoring.module.ts +++ b/packages/twenty-server/src/modules/messaging/monitoring/messaging-monitoring.module.ts @@ -1,18 +1,18 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { AnalyticsModule } from 'src/engine/core-modules/analytics/analytics.module'; +import { AuditModule } from 'src/engine/core-modules/audit/audit.module'; import { BillingModule } from 'src/engine/core-modules/billing/billing.module'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-common.module'; import { MessagingMessageChannelSyncStatusMonitoringCronCommand } from 'src/modules/messaging/monitoring/crons/commands/messaging-message-channel-sync-status-monitoring.cron.command'; import { MessagingMessageChannelSyncStatusMonitoringCronJob } from 'src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.job'; -import { MessagingTelemetryService } from 'src/modules/messaging/monitoring/services/messaging-telemetry.service'; +import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/services/messaging-monitoring.service'; @Module({ imports: [ - AnalyticsModule, + AuditModule, MessagingCommonModule, BillingModule, TypeOrmModule.forFeature([Workspace], 'core'), @@ -21,8 +21,8 @@ import { MessagingTelemetryService } from 'src/modules/messaging/monitoring/serv providers: [ MessagingMessageChannelSyncStatusMonitoringCronCommand, MessagingMessageChannelSyncStatusMonitoringCronJob, - MessagingTelemetryService, + MessagingMonitoringService, ], - exports: [MessagingTelemetryService], + exports: [MessagingMonitoringService], }) export class MessagingMonitoringModule {} 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 new file mode 100644 index 000000000..223d047e6 --- /dev/null +++ b/packages/twenty-server/src/modules/messaging/monitoring/services/messaging-monitoring.service.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@nestjs/common'; + +import { AuditService } from 'src/engine/core-modules/audit/services/audit.service'; + +type MessagingMonitoringTrackInput = { + eventName: string; + workspaceId?: string; + userId?: string; + connectedAccountId?: string; + messageChannelId?: string; + message?: string; +}; + +@Injectable() +export class MessagingMonitoringService { + constructor(private readonly auditService: AuditService) {} + + public async track({ + eventName, + workspaceId, + userId, + connectedAccountId, + messageChannelId, + message, + }: MessagingMonitoringTrackInput): Promise { + const _eventName = eventName; + const _workspaceId = workspaceId; + const _userId = userId; + const _connectedAccountId = connectedAccountId; + const _messageChannelId = messageChannelId; + const _message = message; + + // TODO: replace once we have Prometheus + /* + await this.auditService + .createContext({ + userId, + workspaceId, + }) + .track(MONITORING_EVENT, { + eventName: `messaging.${eventName}`, + connectedAccountId, + messageChannelId, + message, + }); */ + } +} diff --git a/packages/twenty-server/src/modules/messaging/monitoring/services/messaging-telemetry.service.ts b/packages/twenty-server/src/modules/messaging/monitoring/services/messaging-telemetry.service.ts deleted file mode 100644 index 819bc0fbe..000000000 --- a/packages/twenty-server/src/modules/messaging/monitoring/services/messaging-telemetry.service.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { AnalyticsService } from 'src/engine/core-modules/analytics/services/analytics.service'; -import { MONITORING_EVENT } from 'src/engine/core-modules/analytics/utils/events/track/monitoring/monitoring'; - -type MessagingTelemetryTrackInput = { - eventName: string; - workspaceId?: string; - userId?: string; - connectedAccountId?: string; - messageChannelId?: string; - message?: string; -}; - -@Injectable() -export class MessagingTelemetryService { - constructor(private readonly analyticsService: AnalyticsService) {} - - public async track({ - eventName, - workspaceId, - userId, - connectedAccountId, - messageChannelId, - message, - }: MessagingTelemetryTrackInput): Promise { - await this.analyticsService - .createAnalyticsContext({ - userId, - workspaceId, - }) - .track(MONITORING_EVENT, { - eventName: `messaging.${eventName}`, - connectedAccountId, - messageChannelId, - message, - }); - } -} diff --git a/packages/twenty-server/src/modules/timeline/jobs/timeline-job.module.ts b/packages/twenty-server/src/modules/timeline/jobs/timeline-job.module.ts index d4480a53c..068d43bcb 100644 --- a/packages/twenty-server/src/modules/timeline/jobs/timeline-job.module.ts +++ b/packages/twenty-server/src/modules/timeline/jobs/timeline-job.module.ts @@ -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 {} diff --git a/packages/twenty-server/src/modules/timeline/jobs/upsert-timeline-activity-from-behavioral-event.ts b/packages/twenty-server/src/modules/timeline/jobs/upsert-timeline-activity-from-behavioral-event.ts deleted file mode 100644 index 70b786d12..000000000 --- a/packages/twenty-server/src/modules/timeline/jobs/upsert-timeline-activity-from-behavioral-event.ts +++ /dev/null @@ -1 +0,0 @@ -// TODO diff --git a/packages/twenty-server/src/modules/timeline/repositiories/audit-log.repository.ts b/packages/twenty-server/src/modules/timeline/repositiories/audit-log.repository.ts deleted file mode 100644 index 867f6be55..000000000 --- a/packages/twenty-server/src/modules/timeline/repositiories/audit-log.repository.ts +++ /dev/null @@ -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 { - 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, - ); - } -} diff --git a/packages/twenty-server/src/modules/timeline/standard-objects/audit-log.workspace-entity.ts b/packages/twenty-server/src/modules/timeline/standard-objects/audit-log.workspace-entity.ts deleted file mode 100644 index e22dcac42..000000000 --- a/packages/twenty-server/src/modules/timeline/standard-objects/audit-log.workspace-entity.ts +++ /dev/null @@ -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 | null; - - @WorkspaceJoinColumn('workspaceMember') - workspaceMemberId: string | null; -} diff --git a/packages/twenty-server/src/modules/timeline/standard-objects/behavioral-event.workspace-entity.ts b/packages/twenty-server/src/modules/timeline/standard-objects/behavioral-event.workspace-entity.ts deleted file mode 100644 index b25e52862..000000000 --- a/packages/twenty-server/src/modules/timeline/standard-objects/behavioral-event.workspace-entity.ts +++ /dev/null @@ -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; -} diff --git a/packages/twenty-server/src/modules/view/standard-objects/view-field.workspace-entity.ts b/packages/twenty-server/src/modules/view/standard-objects/view-field.workspace-entity.ts index 6fe5dcf8d..6bd981917 100644 --- a/packages/twenty-server/src/modules/view/standard-objects/view-field.workspace-entity.ts +++ b/packages/twenty-server/src/modules/view/standard-objects/view-field.workspace-entity.ts @@ -7,11 +7,11 @@ import { Relation } from 'typeorm'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; import { AGGREGATE_OPERATIONS } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant'; +import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; 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 { WorkspaceIndex } from 'src/engine/twenty-orm/decorators/workspace-index.decorator'; -import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.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'; @@ -20,7 +20,6 @@ import { VIEW_FIELD_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/work 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 { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; -import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; registerEnumType(AGGREGATE_OPERATIONS, { name: 'AggregateOperations', @@ -34,7 +33,6 @@ registerEnumType(AGGREGATE_OPERATIONS, { description: msg`(System) View Fields`, icon: STANDARD_OBJECT_ICONS.viewField, }) -@WorkspaceIsNotAuditLogged() @WorkspaceIsSystem() @WorkspaceIndex(['fieldMetadataId', 'viewId'], { isUnique: true, diff --git a/packages/twenty-server/src/modules/view/standard-objects/view-filter-group.workspace-entity.ts b/packages/twenty-server/src/modules/view/standard-objects/view-filter-group.workspace-entity.ts index 04a93446c..47573ddec 100644 --- a/packages/twenty-server/src/modules/view/standard-objects/view-filter-group.workspace-entity.ts +++ b/packages/twenty-server/src/modules/view/standard-objects/view-filter-group.workspace-entity.ts @@ -4,10 +4,10 @@ import { Relation } from 'typeorm'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; +import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; 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 { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.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'; @@ -15,7 +15,6 @@ import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-re import { VIEW_FILTER_GROUP_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; -import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; export enum ViewFilterGroupLogicalOperator { AND = 'AND', @@ -31,7 +30,6 @@ export enum ViewFilterGroupLogicalOperator { description: msg`(System) View Filter Groups`, icon: 'IconFilterBolt', }) -@WorkspaceIsNotAuditLogged() @WorkspaceIsSystem() export class ViewFilterGroupWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceRelation({ diff --git a/packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts b/packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts index d82283c52..2f664fb73 100644 --- a/packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts +++ b/packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts @@ -4,10 +4,10 @@ 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/relation-metadata/relation-metadata.entity'; 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 { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.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'; @@ -16,7 +16,6 @@ import { VIEW_FILTER_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/wor 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 { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; -import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; @WorkspaceEntity({ standardId: STANDARD_OBJECT_IDS.viewFilter, @@ -26,7 +25,6 @@ import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-met description: msg`(System) View Filters`, icon: STANDARD_OBJECT_ICONS.viewFilter, }) -@WorkspaceIsNotAuditLogged() @WorkspaceIsSystem() export class ViewFilterWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceField({ diff --git a/packages/twenty-server/src/modules/view/standard-objects/view-group.workspace-entity.ts b/packages/twenty-server/src/modules/view/standard-objects/view-group.workspace-entity.ts index 8d7150ab7..41dfa5414 100644 --- a/packages/twenty-server/src/modules/view/standard-objects/view-group.workspace-entity.ts +++ b/packages/twenty-server/src/modules/view/standard-objects/view-group.workspace-entity.ts @@ -3,10 +3,10 @@ import { FieldMetadataType } from 'twenty-shared/types'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; +import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; 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 { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.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'; @@ -15,7 +15,6 @@ import { VIEW_GROUP_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/work 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 { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; -import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; @WorkspaceEntity({ standardId: STANDARD_OBJECT_IDS.viewGroup, @@ -25,7 +24,6 @@ import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-met description: msg`(System) View Groups`, icon: STANDARD_OBJECT_ICONS.viewGroup, }) -@WorkspaceIsNotAuditLogged() @WorkspaceIsSystem() export class ViewGroupWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceField({ diff --git a/packages/twenty-server/src/modules/view/standard-objects/view-sort.workspace-entity.ts b/packages/twenty-server/src/modules/view/standard-objects/view-sort.workspace-entity.ts index 3f37acf04..006ecdbf7 100644 --- a/packages/twenty-server/src/modules/view/standard-objects/view-sort.workspace-entity.ts +++ b/packages/twenty-server/src/modules/view/standard-objects/view-sort.workspace-entity.ts @@ -4,11 +4,11 @@ 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/relation-metadata/relation-metadata.entity'; 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 { WorkspaceIndex } from 'src/engine/twenty-orm/decorators/workspace-index.decorator'; -import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.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'; @@ -17,7 +17,6 @@ import { VIEW_SORT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/works 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 { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; -import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; @WorkspaceEntity({ standardId: STANDARD_OBJECT_IDS.viewSort, @@ -27,7 +26,6 @@ import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-met description: msg`(System) View Sorts`, icon: STANDARD_OBJECT_ICONS.viewSort, }) -@WorkspaceIsNotAuditLogged() @WorkspaceIsSystem() @WorkspaceIndex(['fieldMetadataId', 'viewId'], { isUnique: true, diff --git a/packages/twenty-server/src/modules/view/standard-objects/view.workspace-entity.ts b/packages/twenty-server/src/modules/view/standard-objects/view.workspace-entity.ts index 8ac2b84df..d3c2118c2 100644 --- a/packages/twenty-server/src/modules/view/standard-objects/view.workspace-entity.ts +++ b/packages/twenty-server/src/modules/view/standard-objects/view.workspace-entity.ts @@ -11,7 +11,6 @@ import { AGGREGATE_OPERATIONS } from 'src/engine/api/graphql/graphql-query-runne 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 { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.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 { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; @@ -43,7 +42,6 @@ registerEnumType(ViewOpenRecordInType, { icon: STANDARD_OBJECT_ICONS.view, labelIdentifierStandardId: VIEW_STANDARD_FIELD_IDS.name, }) -@WorkspaceIsNotAuditLogged() @WorkspaceIsSystem() export class ViewWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceField({ 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 d0fb5d31f..dcc1e1976 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 @@ -3,11 +3,11 @@ import { Logger } from '@nestjs/common'; import crypto from 'crypto'; -import { AnalyticsService } from 'src/engine/core-modules/analytics/services/analytics.service'; +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 { 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 { WEBHOOK_RESPONSE_EVENT } from 'src/engine/core-modules/analytics/utils/events/track/webhook/webhook-response'; export type CallWebhookJobData = { targetUrl: string; @@ -26,7 +26,7 @@ export class CallWebhookJob { private readonly logger = new Logger(CallWebhookJob.name); constructor( private readonly httpService: HttpService, - private readonly analyticsService: AnalyticsService, + private readonly auditService: AuditService, ) {} private generateSignature( @@ -47,7 +47,7 @@ export class CallWebhookJob { webhookId: data.webhookId, eventName: data.eventName, }; - const analytics = this.analyticsService.createAnalyticsContext({ + const analytics = this.auditService.createContext({ workspaceId: data.workspaceId, }); diff --git a/packages/twenty-server/src/modules/webhook/jobs/webhook-job.module.ts b/packages/twenty-server/src/modules/webhook/jobs/webhook-job.module.ts index a3606ce36..03c789f42 100644 --- a/packages/twenty-server/src/modules/webhook/jobs/webhook-job.module.ts +++ b/packages/twenty-server/src/modules/webhook/jobs/webhook-job.module.ts @@ -1,12 +1,12 @@ -import { Module } from '@nestjs/common'; import { HttpModule } from '@nestjs/axios'; +import { Module } from '@nestjs/common'; +import { AuditModule } from 'src/engine/core-modules/audit/audit.module'; import { CallWebhookJobsJob } from 'src/modules/webhook/jobs/call-webhook-jobs.job'; import { CallWebhookJob } from 'src/modules/webhook/jobs/call-webhook.job'; -import { AnalyticsModule } from 'src/engine/core-modules/analytics/analytics.module'; @Module({ - imports: [HttpModule, AnalyticsModule], + imports: [HttpModule, AuditModule], providers: [CallWebhookJobsJob, CallWebhookJob], }) export class WebhookJobModule {} diff --git a/packages/twenty-server/src/modules/webhook/standard-objects/webhook.workspace-entity.ts b/packages/twenty-server/src/modules/webhook/standard-objects/webhook.workspace-entity.ts index 498130eda..b39ab4989 100644 --- a/packages/twenty-server/src/modules/webhook/standard-objects/webhook.workspace-entity.ts +++ b/packages/twenty-server/src/modules/webhook/standard-objects/webhook.workspace-entity.ts @@ -5,7 +5,6 @@ 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 { WorkspaceIsDeprecated } from 'src/engine/twenty-orm/decorators/workspace-is-deprecated.decorator'; -import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.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 { WEBHOOK_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; @@ -21,7 +20,6 @@ import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync icon: STANDARD_OBJECT_ICONS.webhook, labelIdentifierStandardId: WEBHOOK_STANDARD_FIELD_IDS.targetUrl, }) -@WorkspaceIsNotAuditLogged() @WorkspaceIsSystem() export class WebhookWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceField({ diff --git a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts index b355ea73d..044751961 100644 --- a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts +++ b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts @@ -9,6 +9,7 @@ import { ActorMetadata } from 'src/engine/metadata-modules/field-metadata/compos 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 { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.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'; @@ -54,6 +55,7 @@ export type WorkflowRunOutput = { labelIdentifierStandardId: WORKFLOW_RUN_STANDARD_FIELD_IDS.name, icon: STANDARD_OBJECT_ICONS.workflowRun, }) +@WorkspaceIsNotAuditLogged() export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceField({ standardId: WORKFLOW_RUN_STANDARD_FIELD_IDS.name, diff --git a/packages/twenty-server/src/modules/workspace-member/standard-objects/workspace-member.workspace-entity.ts b/packages/twenty-server/src/modules/workspace-member/standard-objects/workspace-member.workspace-entity.ts index f4538e7a8..e5dd84a17 100644 --- a/packages/twenty-server/src/modules/workspace-member/standard-objects/workspace-member.workspace-entity.ts +++ b/packages/twenty-server/src/modules/workspace-member/standard-objects/workspace-member.workspace-entity.ts @@ -15,7 +15,6 @@ import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; import { WorkspaceFieldIndex } from 'src/engine/twenty-orm/decorators/workspace-field-index.decorator'; import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; -import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator'; import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; import { WorkspaceIsSearchable } from 'src/engine/twenty-orm/decorators/workspace-is-searchable.decorator'; import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; @@ -35,7 +34,6 @@ import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/s import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity'; import { MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity'; import { TaskWorkspaceEntity } from 'src/modules/task/standard-objects/task.workspace-entity'; -import { AuditLogWorkspaceEntity } from 'src/modules/timeline/standard-objects/audit-log.workspace-entity'; import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity'; export enum WorkspaceMemberDateFormatEnum { @@ -81,7 +79,6 @@ export const SEARCH_FIELDS_FOR_WORKSPACE_MEMBER: FieldTypeAndNameMetadata[] = [ imageIdentifierStandardId: WORKSPACE_MEMBER_STANDARD_FIELD_IDS.avatarUrl, }) @WorkspaceIsSystem() -@WorkspaceIsNotAuditLogged() @WorkspaceIsSearchable() export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceField({ @@ -347,19 +344,6 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceIsSystem() timelineActivities: Relation; - @WorkspaceRelation({ - standardId: WORKSPACE_MEMBER_STANDARD_FIELD_IDS.auditLogs, - type: RelationType.ONE_TO_MANY, - label: msg`Audit Logs`, - description: msg`Audit Logs linked to the workspace member`, - icon: 'IconTimelineEvent', - inverseSideTarget: () => AuditLogWorkspaceEntity, - onDelete: RelationOnDeleteAction.SET_NULL, - }) - @WorkspaceIsNullable() - @WorkspaceIsSystem() - auditLogs: Relation; - @WorkspaceField({ standardId: WORKSPACE_MEMBER_STANDARD_FIELD_IDS.searchVector, type: FieldMetadataType.TS_VECTOR, diff --git a/packages/twenty-server/test/integration/analytics/suites/clickhouse-event-registration.integration-spec.ts b/packages/twenty-server/test/integration/audit/suites/clickHouse-audit-event-registration.integration-spec.ts similarity index 75% rename from packages/twenty-server/test/integration/analytics/suites/clickhouse-event-registration.integration-spec.ts rename to packages/twenty-server/test/integration/audit/suites/clickHouse-audit-event-registration.integration-spec.ts index b5924d5f3..4266ef6e8 100644 --- a/packages/twenty-server/test/integration/analytics/suites/clickhouse-event-registration.integration-spec.ts +++ b/packages/twenty-server/test/integration/audit/suites/clickHouse-audit-event-registration.integration-spec.ts @@ -1,30 +1,30 @@ import process from 'process'; +import { ClickHouseClient, createClient } from '@clickhouse/client'; import request from 'supertest'; -import { createClient, ClickHouseClient } from '@clickhouse/client'; -import { GenericTrackEvent } from 'src/engine/core-modules/analytics/utils/events/track/track'; -import { OBJECT_RECORD_CREATED_EVENT } from 'src/engine/core-modules/analytics/utils/events/track/object-record/object-record-created'; +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'; describe('ClickHouse Event Registration (integration)', () => { - let clickhouseClient: ClickHouseClient; + let clickHouseClient: ClickHouseClient; beforeAll(async () => { jest.useRealTimers(); - clickhouseClient = createClient({ + clickHouseClient = createClient({ url: process.env.CLICKHOUSE_URL, }); - await clickhouseClient.query({ - query: 'TRUNCATE TABLE events', + await clickHouseClient.query({ + query: 'TRUNCATE TABLE auditEvent', format: 'JSONEachRow', }); }); afterAll(async () => { - if (clickhouseClient) { - await clickhouseClient.close(); + if (clickHouseClient) { + await clickHouseClient.close(); } }); @@ -53,10 +53,10 @@ describe('ClickHouse Event Registration (integration)', () => { expect(response.status).toBe(200); expect(response.body.data.trackAnalytics.success).toBe(true); - const queryResult = await clickhouseClient.query({ + const queryResult = await clickHouseClient.query({ query: ` SELECT * - FROM events + FROM auditEvent WHERE event = '${OBJECT_RECORD_CREATED_EVENT}' AND timestamp >= now() - INTERVAL 1 SECOND `, diff --git a/packages/twenty-server/test/integration/graphql/suites/object-generated/audit-logs.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/object-generated/audit-logs.integration-spec.ts deleted file mode 100644 index 8c4ff4ae0..000000000 --- a/packages/twenty-server/test/integration/graphql/suites/object-generated/audit-logs.integration-spec.ts +++ /dev/null @@ -1,65 +0,0 @@ -import request from 'supertest'; - -const client = request(`http://localhost:${APP_PORT}`); - -describe('auditLogsResolver (e2e)', () => { - it('should find many auditLogs', () => { - const queryData = { - query: ` - query auditLogs { - auditLogs { - edges { - node { - name - properties - context - objectName - objectMetadataId - recordId - id - createdAt - updatedAt - deletedAt - workspaceMemberId - } - } - } - } - `, - }; - - return client - .post('/graphql') - .set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`) - .send(queryData) - .expect(200) - .expect((res) => { - expect(res.body.data).toBeDefined(); - expect(res.body.errors).toBeUndefined(); - }) - .expect((res) => { - const data = res.body.data.auditLogs; - - expect(data).toBeDefined(); - expect(Array.isArray(data.edges)).toBe(true); - - const edges = data.edges; - - if (edges.length > 0) { - const auditLogs = edges[0].node; - - expect(auditLogs).toHaveProperty('name'); - expect(auditLogs).toHaveProperty('properties'); - expect(auditLogs).toHaveProperty('context'); - expect(auditLogs).toHaveProperty('objectName'); - expect(auditLogs).toHaveProperty('objectMetadataId'); - expect(auditLogs).toHaveProperty('recordId'); - expect(auditLogs).toHaveProperty('id'); - expect(auditLogs).toHaveProperty('createdAt'); - expect(auditLogs).toHaveProperty('updatedAt'); - expect(auditLogs).toHaveProperty('deletedAt'); - expect(auditLogs).toHaveProperty('workspaceMemberId'); - } - }); - }); -}); diff --git a/packages/twenty-server/test/utils/analytics-context.mock.ts b/packages/twenty-server/test/utils/audit-context.mock.ts similarity index 77% rename from packages/twenty-server/test/utils/analytics-context.mock.ts rename to packages/twenty-server/test/utils/audit-context.mock.ts index d319a1c59..f14f1b092 100644 --- a/packages/twenty-server/test/utils/analytics-context.mock.ts +++ b/packages/twenty-server/test/utils/audit-context.mock.ts @@ -1,6 +1,6 @@ -import { TrackEventName } from 'src/engine/core-modules/analytics/types/events.type'; +import { TrackEventName } from 'src/engine/core-modules/audit/types/events.type'; -export const AnalyticsContextMock = (params?: { +export const AuditContextMock = (params?: { track?: | (( event: TrackEventName,