From 133585d536e2b1a7e8a7db4712e73b2c1a305aef Mon Sep 17 00:00:00 2001 From: Kuchizu Date: Wed, 1 Jul 2026 12:40:23 +0300 Subject: [PATCH 1/2] Trim event title to a maximum length in grouper --- workers/grouper/src/index.ts | 14 +++++++++++++- workers/grouper/tests/index.test.ts | 14 ++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/workers/grouper/src/index.ts b/workers/grouper/src/index.ts index 8203e8d3..a0837eff 100644 --- a/workers/grouper/src/index.ts +++ b/workers/grouper/src/index.ts @@ -59,10 +59,15 @@ const DB_DUPLICATE_KEY_ERROR = '11000'; const DAILY_METRICS_RETENTION_DAYS = 90; /** - * Maximum length for backtrace code line or title + * Maximum length for backtrace code line */ const MAX_CODE_LINE_LENGTH = 140; +/** + * Maximum length for event title + */ +const MAX_TITLE_LENGTH = 1000; + /** * Worker for handling Javascript events */ @@ -209,6 +214,13 @@ export default class GrouperWorker extends Worker { }; } + /** + * Trim title before hashing so hash and stored title stay consistent + */ + if (typeof task.payload.title === 'string') { + task.payload.title = rightTrim(task.payload.title, MAX_TITLE_LENGTH); + } + let uniqueEventHash = await session.measureStep('hash', () => this.getUniqueEventHash(task)); let existedEvent: GroupedEventDBScheme; let repetitionId = null; diff --git a/workers/grouper/tests/index.test.ts b/workers/grouper/tests/index.test.ts index c75351a9..18a7dd36 100644 --- a/workers/grouper/tests/index.test.ts +++ b/workers/grouper/tests/index.test.ts @@ -173,6 +173,20 @@ describe('GrouperWorker', () => { expect(await eventsCollection.find().count()).toBe(1); }); + test('Should trim event title to the maximum length before saving', async () => { + const longTitle = 'A'.repeat(5000); + + await worker.handle(generateTask({ title: longTitle })); + + const savedEvent = await eventsCollection.findOne({}); + + /** + * 1000 chars + ellipsis + */ + expect(savedEvent.payload.title.length).toBe(1001); + expect(savedEvent.payload.title.endsWith('…')).toBe(true); + }); + test('Should increment total events count on each processing', async () => { await worker.handle(generateTask()); await worker.handle(generateTask()); From b9fd948486e971a7a88bdc983f8457d1d2e51a02 Mon Sep 17 00:00:00 2001 From: Kuchizu Date: Wed, 1 Jul 2026 21:11:06 +0300 Subject: [PATCH 2/2] refactor(grouper): move title trimming into DataFilter --- workers/grouper/src/data-filter.ts | 20 ++++++++++++++++++++ workers/grouper/src/index.ts | 9 +-------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/workers/grouper/src/data-filter.ts b/workers/grouper/src/data-filter.ts index 2345b8e5..d88b24e3 100644 --- a/workers/grouper/src/data-filter.ts +++ b/workers/grouper/src/data-filter.ts @@ -1,11 +1,17 @@ import type { EventAddons, EventData } from '@hawk.so/types'; import { unsafeFields } from '../../../lib/utils/unsafeFields'; +import { rightTrim } from '../../../lib/utils/string'; /** * Maximum depth for object traversal to prevent excessive memory allocations */ const MAX_TRAVERSAL_DEPTH = 20; +/** + * Maximum length for event title + */ +const MAX_TITLE_LENGTH = 1000; + /** * Recursively iterate through object and call function on each key * @@ -142,6 +148,20 @@ export default class DataFilter { }); } + /** + * Trim event title to the maximum allowed length. + * It mutates the original object. + * + * Should be called before hashing so the hash and the stored title stay consistent. + * + * @param event - event to process + */ + public trimEventTitle(event: EventData): void { + if (typeof event.title === 'string') { + event.title = rightTrim(event.title, MAX_TITLE_LENGTH); + } + } + /** * Recursively iterates object and applies filtering to its entries * diff --git a/workers/grouper/src/index.ts b/workers/grouper/src/index.ts index a0837eff..845c5590 100644 --- a/workers/grouper/src/index.ts +++ b/workers/grouper/src/index.ts @@ -63,11 +63,6 @@ const DAILY_METRICS_RETENTION_DAYS = 90; */ const MAX_CODE_LINE_LENGTH = 140; -/** - * Maximum length for event title - */ -const MAX_TITLE_LENGTH = 1000; - /** * Worker for handling Javascript events */ @@ -217,9 +212,7 @@ export default class GrouperWorker extends Worker { /** * Trim title before hashing so hash and stored title stay consistent */ - if (typeof task.payload.title === 'string') { - task.payload.title = rightTrim(task.payload.title, MAX_TITLE_LENGTH); - } + this.dataFilter.trimEventTitle(task.payload); let uniqueEventHash = await session.measureStep('hash', () => this.getUniqueEventHash(task)); let existedEvent: GroupedEventDBScheme;