Heatmaps bug fixes, update schema to match overlay, use react-zen component to match ui/ux, polish
This commit is contained in:
@@ -18,6 +18,13 @@ export interface ExtractedHeatmapEvent {
|
||||
scrollPct: number | null;
|
||||
urlPath: string;
|
||||
createdAt: Date;
|
||||
replayChunkIndex: number | null;
|
||||
replayEventIndex: number | null;
|
||||
replayTimeMs: number | null;
|
||||
}
|
||||
|
||||
interface ExtractHeatmapEventOptions {
|
||||
chunkIndex?: number;
|
||||
}
|
||||
|
||||
function safePathname(href: unknown): string | null {
|
||||
@@ -29,7 +36,10 @@ function safePathname(href: unknown): string | null {
|
||||
}
|
||||
}
|
||||
|
||||
export function extractHeatmapEvents(events: any[]): ExtractedHeatmapEvent[] {
|
||||
export function extractHeatmapEvents(
|
||||
events: any[],
|
||||
{ chunkIndex }: ExtractHeatmapEventOptions = {},
|
||||
): ExtractedHeatmapEvent[] {
|
||||
if (!Array.isArray(events) || events.length === 0) return [];
|
||||
|
||||
let urlPath: string | null = null;
|
||||
@@ -37,8 +47,10 @@ export function extractHeatmapEvents(events: any[]): ExtractedHeatmapEvent[] {
|
||||
let viewportH: number | null = null;
|
||||
const out: ExtractedHeatmapEvent[] = [];
|
||||
|
||||
for (const ev of events) {
|
||||
for (const [eventIndex, ev] of events.entries()) {
|
||||
if (!ev || typeof ev !== 'object') continue;
|
||||
const replayTimeMs =
|
||||
typeof ev.timestamp === 'number' && Number.isFinite(ev.timestamp) ? ev.timestamp : null;
|
||||
|
||||
if (ev.type === RRWEB_TYPE_META && ev.data) {
|
||||
const path = safePathname(ev.data.href);
|
||||
@@ -72,7 +84,10 @@ export function extractHeatmapEvents(events: any[]): ExtractedHeatmapEvent[] {
|
||||
? Math.max(0, Math.min(100, Math.round(p.scrollPct)))
|
||||
: null,
|
||||
urlPath: path,
|
||||
createdAt: new Date(typeof ev.timestamp === 'number' ? ev.timestamp : Date.now()),
|
||||
createdAt: new Date(replayTimeMs ?? Date.now()),
|
||||
replayChunkIndex: chunkIndex ?? null,
|
||||
replayEventIndex: eventIndex,
|
||||
replayTimeMs,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
@@ -103,7 +118,10 @@ export function extractHeatmapEvents(events: any[]): ExtractedHeatmapEvent[] {
|
||||
pageH: null,
|
||||
scrollPct: null,
|
||||
urlPath,
|
||||
createdAt: new Date(typeof ev.timestamp === 'number' ? ev.timestamp : Date.now()),
|
||||
createdAt: new Date(replayTimeMs ?? Date.now()),
|
||||
replayChunkIndex: chunkIndex ?? null,
|
||||
replayEventIndex: eventIndex,
|
||||
replayTimeMs,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,9 @@ export interface HeatmapEventRow {
|
||||
pageH: number | null;
|
||||
scrollPct: number | null;
|
||||
createdAt: Date;
|
||||
replayChunkIndex: number | null;
|
||||
replayEventIndex: number | null;
|
||||
replayTimeMs: number | null;
|
||||
}
|
||||
|
||||
export async function saveHeatmapEvents(rows: HeatmapEventRow[]) {
|
||||
@@ -46,7 +49,10 @@ async function relationalQuery(rows: HeatmapEventRow[]) {
|
||||
pageH: r.pageH,
|
||||
scrollPct: r.scrollPct,
|
||||
createdAt: r.createdAt,
|
||||
})),
|
||||
replayChunkIndex: r.replayChunkIndex,
|
||||
replayEventIndex: r.replayEventIndex,
|
||||
replayTimeMs: r.replayTimeMs,
|
||||
})) as any,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -69,6 +75,9 @@ async function clickhouseQuery(rows: HeatmapEventRow[]) {
|
||||
page_h: r.pageH,
|
||||
scroll_pct: r.scrollPct,
|
||||
created_at: getUTCString(r.createdAt),
|
||||
replay_chunk_index: r.replayChunkIndex,
|
||||
replay_event_index: r.replayEventIndex,
|
||||
replay_time_ms: r.replayTimeMs,
|
||||
}));
|
||||
|
||||
if (kafka.enabled) {
|
||||
|
||||
@@ -15,15 +15,39 @@ export interface ReplayChunk {
|
||||
endedAt: Date;
|
||||
}
|
||||
|
||||
export async function getReplayChunks(websiteId: string, visitId: string): Promise<ReplayChunk[]> {
|
||||
interface GetReplayChunksOptions {
|
||||
endAt?: Date;
|
||||
endChunkIndex?: number;
|
||||
}
|
||||
|
||||
export async function getReplayChunks(
|
||||
websiteId: string,
|
||||
visitId: string,
|
||||
options: GetReplayChunksOptions = {},
|
||||
): Promise<ReplayChunk[]> {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(websiteId, visitId),
|
||||
[CLICKHOUSE]: () => clickhouseQuery(websiteId, visitId),
|
||||
[PRISMA]: () => relationalQuery(websiteId, visitId, options),
|
||||
[CLICKHOUSE]: () => clickhouseQuery(websiteId, visitId, options),
|
||||
});
|
||||
}
|
||||
|
||||
async function relationalQuery(websiteId: string, visitId: string): Promise<ReplayChunk[]> {
|
||||
async function relationalQuery(
|
||||
websiteId: string,
|
||||
visitId: string,
|
||||
{ endAt, endChunkIndex }: GetReplayChunksOptions,
|
||||
): Promise<ReplayChunk[]> {
|
||||
const { rawQuery } = prisma;
|
||||
const endAtFilter = endAt
|
||||
? `
|
||||
and started_at <= {{endAt}}
|
||||
`
|
||||
: '';
|
||||
const endChunkFilter =
|
||||
endChunkIndex !== undefined
|
||||
? `
|
||||
and chunk_index <= {{endChunkIndex}}
|
||||
`
|
||||
: '';
|
||||
|
||||
const chunks: {
|
||||
sessionId: string;
|
||||
@@ -46,9 +70,11 @@ async function relationalQuery(websiteId: string, visitId: string): Promise<Repl
|
||||
from session_replay
|
||||
where website_id = {{websiteId::uuid}}
|
||||
and visit_id = {{visitId::uuid}}
|
||||
${endAtFilter}
|
||||
${endChunkFilter}
|
||||
order by chunk_index asc
|
||||
`,
|
||||
{ websiteId, visitId },
|
||||
{ websiteId, visitId, endAt, endChunkIndex },
|
||||
FUNCTION_NAME,
|
||||
);
|
||||
|
||||
@@ -58,8 +84,23 @@ async function relationalQuery(websiteId: string, visitId: string): Promise<Repl
|
||||
}));
|
||||
}
|
||||
|
||||
async function clickhouseQuery(websiteId: string, visitId: string): Promise<ReplayChunk[]> {
|
||||
async function clickhouseQuery(
|
||||
websiteId: string,
|
||||
visitId: string,
|
||||
{ endAt, endChunkIndex }: GetReplayChunksOptions,
|
||||
): Promise<ReplayChunk[]> {
|
||||
const { rawQuery } = clickhouse;
|
||||
const endAtFilter = endAt
|
||||
? `
|
||||
and started_at <= {endAt:DateTime64}
|
||||
`
|
||||
: '';
|
||||
const endChunkFilter =
|
||||
endChunkIndex !== undefined
|
||||
? `
|
||||
and chunk_index <= {endChunkIndex:UInt32}
|
||||
`
|
||||
: '';
|
||||
|
||||
const results = await rawQuery<
|
||||
{
|
||||
@@ -82,11 +123,13 @@ async function clickhouseQuery(websiteId: string, visitId: string): Promise<Repl
|
||||
started_at,
|
||||
ended_at
|
||||
from session_replay
|
||||
where website_id = {websiteId:UUID}
|
||||
prewhere website_id = {websiteId:UUID}
|
||||
and visit_id = {visitId:UUID}
|
||||
${endAtFilter}
|
||||
${endChunkFilter}
|
||||
order by chunk_index asc
|
||||
`,
|
||||
{ websiteId, visitId },
|
||||
{ websiteId, visitId, endAt, endChunkIndex },
|
||||
FUNCTION_NAME,
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user