From b5d56a2a29d894d39d51ab614f871dcaa00ac46a Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Fri, 22 May 2026 11:09:57 -0700 Subject: [PATCH] remove unused replay snapshot path before merge --- .../[websiteId]/replays/[replayId]/route.ts | 110 +----------------- .../sql/heatmap/extractHeatmapEvents.ts | 55 ++++----- src/queries/sql/replays/getReplayChunks.ts | 43 +------ 3 files changed, 31 insertions(+), 177 deletions(-) diff --git a/src/app/api/websites/[websiteId]/replays/[replayId]/route.ts b/src/app/api/websites/[websiteId]/replays/[replayId]/route.ts index f65780e34..b3b749228 100644 --- a/src/app/api/websites/[websiteId]/replays/[replayId]/route.ts +++ b/src/app/api/websites/[websiteId]/replays/[replayId]/route.ts @@ -3,11 +3,6 @@ import { json, unauthorized } from '@/lib/response'; import { canViewWebsite } from '@/permissions'; import { getReplayChunks } from '@/queries/sql'; -const RRWEB_TYPE_FULL_SNAPSHOT = 2; -const RRWEB_TYPE_META = 4; -const SNAPSHOT_WINDOW_CHUNK_LIMIT = 6; -const SNAPSHOT_WINDOW_MAX_CHUNKS = 96; - function getEventTimestamp(event: any): number | null { const timestamp = Number(event?.timestamp); @@ -24,102 +19,6 @@ function parseOptionalInteger(value: string | null): number | undefined { return Number.isInteger(parsed) ? parsed : undefined; } -function trimChunksToCheckpoint( - chunks: Awaited>, - { endChunkIndex, endEventIndex }: { endChunkIndex?: number; endEventIndex?: number }, -) { - let lastMetaChunkIndex: number | null = null; - let checkpointStartChunkIndex: number | null = null; - let hasMeta = false; - - for (const chunk of chunks) { - if (endChunkIndex !== undefined && chunk.chunkIndex > endChunkIndex) { - break; - } - - for (let chunkEventIndex = 0; chunkEventIndex < chunk.events.length; chunkEventIndex++) { - if ( - chunk.chunkIndex === endChunkIndex && - endEventIndex !== undefined && - chunkEventIndex > endEventIndex - ) { - break; - } - - const event = chunk.events[chunkEventIndex]; - - if (event?.type === RRWEB_TYPE_META) { - lastMetaChunkIndex = chunk.chunkIndex; - hasMeta = true; - } - - if (event?.type === RRWEB_TYPE_FULL_SNAPSHOT) { - checkpointStartChunkIndex = lastMetaChunkIndex ?? chunk.chunkIndex; - } - } - } - - if (checkpointStartChunkIndex === null) { - return { chunks, foundCheckpoint: false, hasMeta }; - } - - return { - chunks: chunks.filter(chunk => chunk.chunkIndex >= checkpointStartChunkIndex), - foundCheckpoint: true, - hasMeta, - }; -} - -async function getReplaySnapshotChunks( - websiteId: string, - replayId: string, - { endAt, endChunkIndex, endEventIndex }: { endAt?: Date; endChunkIndex: number; endEventIndex?: number }, -) { - let limit = SNAPSHOT_WINDOW_CHUNK_LIMIT; - - while (limit <= SNAPSHOT_WINDOW_MAX_CHUNKS) { - const descChunks = await getReplayChunks(websiteId, replayId, { - endAt, - endChunkIndex, - limit, - order: 'desc', - }); - - const chunks = [...descChunks].reverse(); - const { chunks: trimmedChunks, foundCheckpoint, hasMeta } = trimChunksToCheckpoint(chunks, { - endChunkIndex, - endEventIndex, - }); - - if (foundCheckpoint || descChunks.length < limit) { - if (hasMeta || trimmedChunks.length === 0) { - return trimmedChunks; - } - - const initialChunks = await getReplayChunks(websiteId, replayId, { - limit: 1, - order: 'asc', - }); - - if (!initialChunks.length) { - return trimmedChunks; - } - - const initialChunk = initialChunks[0]; - - if (trimmedChunks.some(chunk => chunk.chunkIndex === initialChunk.chunkIndex)) { - return trimmedChunks; - } - - return [initialChunk, ...trimmedChunks]; - } - - limit *= 2; - } - - return getReplayChunks(websiteId, replayId, { endAt, endChunkIndex }); -} - function mergeReplayEvents( chunks: Awaited>, { @@ -191,14 +90,7 @@ export async function GET( return unauthorized(); } - const chunks = - endChunkIndex !== undefined - ? await getReplaySnapshotChunks(websiteId, replayId, { - endAt, - endChunkIndex, - endEventIndex, - }) - : await getReplayChunks(websiteId, replayId, { endAt, endChunkIndex }); + const chunks = await getReplayChunks(websiteId, replayId, { endAt, endChunkIndex }); const allEvents = mergeReplayEvents(chunks, { until, endChunkIndex, endEventIndex }); const sessionId = chunks.length > 0 ? chunks[0].sessionId : null; const startedAt = chunks.length > 0 ? chunks[0].startedAt : null; diff --git a/src/queries/sql/heatmap/extractHeatmapEvents.ts b/src/queries/sql/heatmap/extractHeatmapEvents.ts index 461ba82e8..cb8d65c79 100644 --- a/src/queries/sql/heatmap/extractHeatmapEvents.ts +++ b/src/queries/sql/heatmap/extractHeatmapEvents.ts @@ -3,16 +3,15 @@ import { HEATMAP_EVENT_TYPE } from '@/lib/constants'; const RRWEB_TYPE_INCREMENTAL = 3; const RRWEB_TYPE_META = 4; const RRWEB_TYPE_CUSTOM = 5; +const RRWEB_SOURCE_MOUSE_INTERACTION = 2; const RRWEB_SOURCE_VIEWPORT_RESIZE = 4; +const RRWEB_MOUSE_CLICK = 2; export interface ExtractedHeatmapEvent { eventType: number; nodeId: number | null; x: number | null; y: number | null; - pageX: number | null; - pageY: number | null; - pageW: number | null; viewportW: number | null; viewportH: number | null; pageH: number | null; @@ -77,9 +76,6 @@ export function extractHeatmapEvents( nodeId: null, x: null, y: null, - pageX: null, - pageY: null, - pageW: typeof p.pageW === 'number' ? p.pageW : null, viewportW: typeof p.viewportW === 'number' ? p.viewportW : viewportW, viewportH: typeof p.viewportH === 'number' ? p.viewportH : viewportH, pageH: typeof p.pageH === 'number' ? p.pageH : null, @@ -94,31 +90,6 @@ export function extractHeatmapEvents( replayTimeMs, }); } - - if (ev.data.tag === 'heatmap-click' && ev.data.payload) { - const p = ev.data.payload; - const path = safePathname(p.url) ?? urlPath; - if (path === null) continue; - out.push({ - eventType: HEATMAP_EVENT_TYPE.click, - nodeId: null, - x: typeof p.x === 'number' ? Math.round(p.x) : null, - y: typeof p.y === 'number' ? Math.round(p.y) : null, - pageX: typeof p.pageX === 'number' ? Math.round(p.pageX) : null, - pageY: typeof p.pageY === 'number' ? Math.round(p.pageY) : null, - pageW: typeof p.pageW === 'number' ? Math.round(p.pageW) : null, - viewportW: typeof p.viewportW === 'number' ? Math.round(p.viewportW) : viewportW, - viewportH: typeof p.viewportH === 'number' ? Math.round(p.viewportH) : viewportH, - pageH: typeof p.pageH === 'number' ? Math.round(p.pageH) : null, - scrollPct: null, - urlPath: path, - createdAt: new Date(replayTimeMs ?? Date.now()), - replayChunkIndex: chunkIndex ?? null, - replayEventIndex: eventIndex, - replayTimeMs, - }); - } - continue; } @@ -131,6 +102,28 @@ export function extractHeatmapEvents( if (typeof d.height === 'number') viewportH = d.height; continue; } + + if ( + d.source === RRWEB_SOURCE_MOUSE_INTERACTION && + d.type === RRWEB_MOUSE_CLICK && + urlPath !== null + ) { + out.push({ + eventType: HEATMAP_EVENT_TYPE.click, + nodeId: typeof d.id === 'number' ? d.id : null, + x: typeof d.x === 'number' ? Math.round(d.x) : null, + y: typeof d.y === 'number' ? Math.round(d.y) : null, + viewportW, + viewportH, + pageH: null, + scrollPct: null, + urlPath, + createdAt: new Date(replayTimeMs ?? Date.now()), + replayChunkIndex: chunkIndex ?? null, + replayEventIndex: eventIndex, + replayTimeMs, + }); + } } return out; diff --git a/src/queries/sql/replays/getReplayChunks.ts b/src/queries/sql/replays/getReplayChunks.ts index a53fb5b63..182c0703a 100644 --- a/src/queries/sql/replays/getReplayChunks.ts +++ b/src/queries/sql/replays/getReplayChunks.ts @@ -18,9 +18,6 @@ export interface ReplayChunk { interface GetReplayChunksOptions { endAt?: Date; endChunkIndex?: number; - startChunkIndex?: number; - limit?: number; - order?: 'asc' | 'desc'; } export async function getReplayChunks( @@ -37,7 +34,7 @@ export async function getReplayChunks( async function relationalQuery( websiteId: string, visitId: string, - { endAt, endChunkIndex, startChunkIndex, limit, order = 'asc' }: GetReplayChunksOptions, + { endAt, endChunkIndex }: GetReplayChunksOptions, ): Promise { const { rawQuery } = prisma; const endAtFilter = endAt @@ -51,18 +48,6 @@ async function relationalQuery( and chunk_index <= {{endChunkIndex}} ` : ''; - const startChunkFilter = - startChunkIndex !== undefined - ? ` - and chunk_index >= {{startChunkIndex}} - ` - : ''; - const limitClause = - limit !== undefined - ? ` - limit ${limit} - ` - : ''; const chunks: { sessionId: string; @@ -86,12 +71,10 @@ async function relationalQuery( where website_id = {{websiteId::uuid}} and visit_id = {{visitId::uuid}} ${endAtFilter} - ${startChunkFilter} ${endChunkFilter} - order by chunk_index ${order} - ${limitClause} + order by chunk_index asc `, - { websiteId, visitId, endAt, endChunkIndex, startChunkIndex }, + { websiteId, visitId, endAt, endChunkIndex }, FUNCTION_NAME, ); @@ -104,7 +87,7 @@ async function relationalQuery( async function clickhouseQuery( websiteId: string, visitId: string, - { endAt, endChunkIndex, startChunkIndex, limit, order = 'asc' }: GetReplayChunksOptions, + { endAt, endChunkIndex }: GetReplayChunksOptions, ): Promise { const { rawQuery } = clickhouse; const endAtFilter = endAt @@ -118,18 +101,6 @@ async function clickhouseQuery( and chunk_index <= {endChunkIndex:UInt32} ` : ''; - const startChunkFilter = - startChunkIndex !== undefined - ? ` - and chunk_index >= {startChunkIndex:UInt32} - ` - : ''; - const limitClause = - limit !== undefined - ? ` - limit ${limit} - ` - : ''; const results = await rawQuery< { @@ -155,12 +126,10 @@ async function clickhouseQuery( prewhere website_id = {websiteId:UUID} and visit_id = {visitId:UUID} ${endAtFilter} - ${startChunkFilter} ${endChunkFilter} - order by chunk_index ${order} - ${limitClause} + order by chunk_index asc `, - { websiteId, visitId, endAt, endChunkIndex, startChunkIndex }, + { websiteId, visitId, endAt, endChunkIndex }, FUNCTION_NAME, );