move stitching calculation to server side
This commit is contained in:
@@ -4,24 +4,6 @@ import { useEffect, useRef, useState } from 'react';
|
|||||||
import { useMobile } from '@/components/hooks';
|
import { useMobile } from '@/components/hooks';
|
||||||
import 'rrweb-player/dist/style.css';
|
import 'rrweb-player/dist/style.css';
|
||||||
|
|
||||||
function normalizeEvents(events: any[], maxGapMs = 60_000): any[] {
|
|
||||||
if (events.length < 2) return events;
|
|
||||||
|
|
||||||
const sorted = [...events].sort((a, b) => a.timestamp - b.timestamp);
|
|
||||||
let shift = 0;
|
|
||||||
const result: any[] = [sorted[0]];
|
|
||||||
|
|
||||||
for (let i = 1; i < sorted.length; i++) {
|
|
||||||
const gap = sorted[i].timestamp - sorted[i - 1].timestamp;
|
|
||||||
if (gap > maxGapMs) {
|
|
||||||
shift += gap - 1000;
|
|
||||||
}
|
|
||||||
result.push(shift > 0 ? { ...sorted[i], timestamp: sorted[i].timestamp - shift } : sorted[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ReplayPlayer({ events }: { events: any[] }) {
|
export function ReplayPlayer({ events }: { events: any[] }) {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const playerRef = useRef<any>(null);
|
const playerRef = useRef<any>(null);
|
||||||
@@ -44,7 +26,7 @@ export function ReplayPlayer({ events }: { events: any[] }) {
|
|||||||
playerRef.current = new RRWebPlayer({
|
playerRef.current = new RRWebPlayer({
|
||||||
target: containerRef.current,
|
target: containerRef.current,
|
||||||
props: {
|
props: {
|
||||||
events: normalizeEvents(events),
|
events: events,
|
||||||
width: playerWidth,
|
width: playerWidth,
|
||||||
height: playerHeight,
|
height: playerHeight,
|
||||||
autoPlay: false,
|
autoPlay: false,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { stitchChunkEvents } from '@/lib/replay';
|
||||||
import { parseRequest } from '@/lib/request';
|
import { parseRequest } from '@/lib/request';
|
||||||
import { json, unauthorized } from '@/lib/response';
|
import { json, unauthorized } from '@/lib/response';
|
||||||
import { canViewWebsite } from '@/permissions';
|
import { canViewWebsite } from '@/permissions';
|
||||||
@@ -21,7 +22,7 @@ export async function GET(
|
|||||||
|
|
||||||
const chunks = await getReplayChunks(websiteId, sessionId);
|
const chunks = await getReplayChunks(websiteId, sessionId);
|
||||||
|
|
||||||
const allEvents = chunks.flatMap(chunk => chunk.events);
|
const allEvents = stitchChunkEvents(chunks);
|
||||||
const startedAt = chunks.length > 0 ? chunks[0].startedAt : null;
|
const startedAt = chunks.length > 0 ? chunks[0].startedAt : null;
|
||||||
const endedAt = chunks.length > 0 ? chunks[chunks.length - 1].endedAt : null;
|
const endedAt = chunks.length > 0 ? chunks[chunks.length - 1].endedAt : null;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import type { ReplayChunk } from '@/queries/sql';
|
||||||
|
|
||||||
|
export function stitchChunkEvents(chunks: ReplayChunk[]): any[] {
|
||||||
|
if (!chunks.length) return [];
|
||||||
|
const sorted = [...chunks].sort((a, b) => a.startedAt.getTime() - b.startedAt.getTime());
|
||||||
|
const result: any[] = [];
|
||||||
|
let timeOffset = 0;
|
||||||
|
for (const chunk of sorted) {
|
||||||
|
const chunkStartMs = chunk.startedAt.getTime();
|
||||||
|
const events = [...chunk.events].sort((a, b) => a.timestamp - b.timestamp);
|
||||||
|
for (const event of events) {
|
||||||
|
result.push({ ...event, timestamp: timeOffset + (event.timestamp - chunkStartMs) });
|
||||||
|
}
|
||||||
|
timeOffset += chunk.endedAt.getTime() - chunkStartMs + 1000;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -53,7 +53,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
|||||||
count(sr.replay_id) as "chunkCount",
|
count(sr.replay_id) as "chunkCount",
|
||||||
min(sr.started_at) as "startedAt",
|
min(sr.started_at) as "startedAt",
|
||||||
max(sr.ended_at) as "endedAt",
|
max(sr.ended_at) as "endedAt",
|
||||||
(extract(epoch from max(sr.ended_at) - min(sr.started_at)) * 1000)::bigint as "duration",
|
sum(extract(epoch from sr.ended_at - sr.started_at) * 1000)::bigint as "duration",
|
||||||
max(sr.created_at) as "createdAt"
|
max(sr.created_at) as "createdAt"
|
||||||
from session_replay sr
|
from session_replay sr
|
||||||
join session on session.session_id = sr.session_id
|
join session on session.session_id = sr.session_id
|
||||||
@@ -109,7 +109,7 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
|||||||
count(session_replay.replay_id) as chunkCount,
|
count(session_replay.replay_id) as chunkCount,
|
||||||
min(session_replay.started_at) as startedAt,
|
min(session_replay.started_at) as startedAt,
|
||||||
max(session_replay.ended_at) as endedAt,
|
max(session_replay.ended_at) as endedAt,
|
||||||
toInt64(dateDiff('millisecond', min(session_replay.started_at), max(session_replay.ended_at))) as duration,
|
toInt64(sum(dateDiff('millisecond', session_replay.started_at, session_replay.ended_at))) as duration,
|
||||||
max(session_replay.created_at) as createdAt
|
max(session_replay.created_at) as createdAt
|
||||||
from session_replay
|
from session_replay
|
||||||
join (
|
join (
|
||||||
|
|||||||
Reference in New Issue
Block a user