Fix empty user id clickhouse (#18238)

- Fixes:
- Make Workspace User select work; previously, it didn't work as we were
not fetching the workspace users correctly
  - Send Object Events with valid record id and object id

## Audit logs demo


https://github.com/user-attachments/assets/92437037-d253-4810-a138-7c709550755d
This commit is contained in:
Baptiste Devessier
2026-02-27 16:58:44 +01:00
committed by GitHub
parent 86fbf69e95
commit cfad24da48
29 changed files with 146 additions and 171 deletions
@@ -7,4 +7,5 @@ CREATE TABLE IF NOT EXISTS workspaceEvent
`properties` JSON
)
ENGINE = MergeTree
ORDER BY (workspaceId, event, userId, timestamp);
ORDER BY (workspaceId, timestamp, event, userId)
TTL timestamp + INTERVAL 3 YEAR DELETE;
@@ -1,10 +1,11 @@
CREATE TABLE IF NOT EXISTS pageview
(
`name` LowCardinality(String) NOT NULL,
`timestamp` DateTime64(3) NOT NULL,
`name` LowCardinality(String) NOT NULL,
`timestamp` DateTime64(3) NOT NULL,
`userId` String DEFAULT '',
`workspaceId` String DEFAULT '',
`properties` JSON
)
ENGINE = MergeTree
ORDER BY (workspaceId, name, userId, timestamp);
ORDER BY (workspaceId, timestamp, name, userId)
TTL timestamp + INTERVAL 3 YEAR DELETE;
@@ -1,13 +1,14 @@
CREATE TABLE IF NOT EXISTS objectEvent
(
`event` LowCardinality(String) NOT NULL,
`timestamp` DateTime64(3) NOT NULL,
`userId` String DEFAULT '',
`workspaceId` String NOT NULL,
`recordId` String NOT NULL,
`objectMetadataId` String NOT NULL,
`properties` JSON,
`isCustom` Boolean DEFAULT FALSE,
`event` LowCardinality(String) NOT NULL,
`timestamp` DateTime64(3) NOT NULL,
`userId` String DEFAULT '',
`workspaceId` String NOT NULL,
`recordId` String NOT NULL,
`objectMetadataId` String NOT NULL,
`properties` JSON,
`isCustom` Boolean DEFAULT FALSE
)
ENGINE = MergeTree
ORDER BY (workspaceId, event, userId, timestamp);
ORDER BY (workspaceId, timestamp, event, userId)
TTL timestamp + INTERVAL 3 YEAR DELETE;
@@ -1,26 +0,0 @@
-- Optimize workspaceEvent table for time-series queries
-- ClickHouse doesn't allow changing ORDER BY, so we create a new table and migrate data
-- Step 1: Create new table with optimized structure
CREATE TABLE IF NOT EXISTS workspaceEvent_v2
(
`event` LowCardinality(String) NOT NULL,
`timestamp` DateTime64(3) NOT NULL,
`userWorkspaceId` String DEFAULT '',
`workspaceId` String NOT NULL,
`properties` JSON
)
ENGINE = MergeTree
ORDER BY (workspaceId, timestamp, event, userWorkspaceId)
TTL timestamp + INTERVAL 3 YEAR DELETE;
-- Step 2: Migrate existing data (userWorkspaceId will be empty for historical data)
INSERT INTO workspaceEvent_v2
SELECT event, timestamp, '' as userWorkspaceId, workspaceId, properties
FROM workspaceEvent;
-- Step 3: Atomic swap (EXCHANGE is atomic and instant)
EXCHANGE TABLES workspaceEvent AND workspaceEvent_v2;
-- Step 4: Drop old table (now named workspaceEvent_v2 after swap)
DROP TABLE IF EXISTS workspaceEvent_v2;
@@ -1,26 +0,0 @@
-- Optimize pageview table for time-series queries
-- ClickHouse doesn't allow changing ORDER BY, so we create a new table and migrate data
-- Step 1: Create new table with optimized structure
CREATE TABLE IF NOT EXISTS pageview_v2
(
`name` LowCardinality(String) NOT NULL,
`timestamp` DateTime64(3) NOT NULL,
`userWorkspaceId` String DEFAULT '',
`workspaceId` String DEFAULT '',
`properties` JSON
)
ENGINE = MergeTree
ORDER BY (workspaceId, timestamp, name, userWorkspaceId)
TTL timestamp + INTERVAL 3 YEAR DELETE;
-- Step 2: Migrate existing data (userWorkspaceId will be empty for historical data)
INSERT INTO pageview_v2
SELECT name, timestamp, '' as userWorkspaceId, workspaceId, properties
FROM pageview;
-- Step 3: Atomic swap
EXCHANGE TABLES pageview AND pageview_v2;
-- Step 4: Drop old table
DROP TABLE IF EXISTS pageview_v2;
@@ -1,29 +0,0 @@
-- Optimize objectEvent table for time-series queries and object filtering
-- ClickHouse doesn't allow changing ORDER BY, so we create a new table and migrate data
-- Step 1: Create new table with optimized structure
CREATE TABLE IF NOT EXISTS objectEvent_v2
(
`event` LowCardinality(String) NOT NULL,
`timestamp` DateTime64(3) NOT NULL,
`userWorkspaceId` String DEFAULT '',
`workspaceId` String NOT NULL,
`recordId` String NOT NULL,
`objectMetadataId` String NOT NULL,
`properties` JSON,
`isCustom` Boolean DEFAULT FALSE
)
ENGINE = MergeTree
ORDER BY (workspaceId, timestamp, objectMetadataId, recordId, event, userWorkspaceId)
TTL timestamp + INTERVAL 3 YEAR DELETE;
-- Step 2: Migrate existing data (userWorkspaceId will be empty for historical data)
INSERT INTO objectEvent_v2
SELECT event, timestamp, '' as userWorkspaceId, workspaceId, recordId, objectMetadataId, properties, isCustom
FROM objectEvent;
-- Step 3: Atomic swap
EXCHANGE TABLES objectEvent AND objectEvent_v2;
-- Step 4: Drop old table
DROP TABLE IF EXISTS objectEvent_v2;
@@ -5,13 +5,19 @@ import { CUSTOM_DOMAIN_ACTIVATED_EVENT } from 'src/engine/core-modules/audit/uti
import { CUSTOM_DOMAIN_DEACTIVATED_EVENT } from 'src/engine/core-modules/audit/utils/events/workspace-event/custom-domain/custom-domain-deactivated';
import { type GenericTrackEvent } from 'src/engine/core-modules/audit/utils/events/workspace-event/track';
export const fixtures: Array<GenericTrackEvent> = [
export type ObjectEventFixture = GenericTrackEvent & {
recordId: string;
objectMetadataId: string;
isCustom?: boolean;
};
export const workspaceEventFixtures: Array<GenericTrackEvent> = [
{
type: 'track',
event: CUSTOM_DOMAIN_ACTIVATED_EVENT,
timestamp: '2024-10-24T15:55:35.177',
version: '1',
userWorkspaceId: '20202020-3957-45c9-be39-337dc4d9100a',
userId: '20202020-3957-45c9-be39-337dc4d9100a',
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
properties: {},
},
@@ -20,35 +26,44 @@ export const fixtures: Array<GenericTrackEvent> = [
event: CUSTOM_DOMAIN_DEACTIVATED_EVENT,
timestamp: '2024-10-24T15:55:35.177',
version: '1',
userWorkspaceId: '20202020-3957-45c9-be39-337dc4d9100a',
userId: '20202020-3957-45c9-be39-337dc4d9100a',
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
properties: {},
},
];
export const objectEventFixtures: Array<ObjectEventFixture> = [
{
type: 'track',
event: OBJECT_RECORD_CREATED_EVENT,
timestamp: '2024-10-24T15:55:35.177',
version: '1',
userWorkspaceId: '20202020-3957-45c9-be39-337dc4d9100a',
userId: '20202020-3957-45c9-be39-337dc4d9100a',
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
properties: {},
recordId: '20202020-c21e-4ec2-873b-de4264d89025',
objectMetadataId: '20202020-1f76-4e46-b33b-58a70e007ba0',
},
{
type: 'track',
event: OBJECT_RECORD_UPDATED_EVENT,
timestamp: '2024-10-24T15:55:35.177',
version: '1',
userWorkspaceId: '20202020-3957-45c9-be39-337dc4d9100a',
userId: '20202020-3957-45c9-be39-337dc4d9100a',
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
properties: {},
recordId: '20202020-c21e-4ec2-873b-de4264d89025',
objectMetadataId: '20202020-1f76-4e46-b33b-58a70e007ba0',
},
{
type: 'track',
event: OBJECT_RECORD_DELETED_EVENT,
timestamp: '2024-10-24T15:55:35.177',
version: '1',
userWorkspaceId: '20202020-3957-45c9-be39-337dc4d9100a',
userId: '20202020-3957-45c9-be39-337dc4d9100a',
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
properties: {},
recordId: '20202020-c21e-4ec2-873b-de4264d89025',
objectMetadataId: '20202020-1f76-4e46-b33b-58a70e007ba0',
},
];
@@ -2,7 +2,7 @@
import { createClient } from '@clickhouse/client';
import { config } from 'dotenv';
import { fixtures } from './fixtures';
import { objectEventFixtures, workspaceEventFixtures } from './fixtures';
config({
path: process.env.NODE_ENV === 'test' ? '.env.test' : '.env',
@@ -15,11 +15,21 @@ const client = createClient({
async function seedEvents() {
try {
console.log(`⚡ Seeding ${fixtures.length} events...`);
console.log(
`⚡ Seeding ${workspaceEventFixtures.length} workspace events...`,
);
await client.insert({
table: 'workspaceEvent',
values: fixtures,
values: workspaceEventFixtures,
format: 'JSONEachRow',
});
console.log(`⚡ Seeding ${objectEventFixtures.length} object events...`);
await client.insert({
table: 'objectEvent',
values: objectEventFixtures,
format: 'JSONEachRow',
});