Heatmaps bug fixes, update schema to match overlay, use react-zen component to match ui/ux, polish

This commit is contained in:
Francis Cao committed 2026-05-14 14:23:41 -07:00
1 parent a22274bbbc
commit cd20b3aafd
18 files changed
+1262 -628

No files matched your search

+1 -1
View File
@@ -116,7 +116,7 @@ export async function POST(request: Request) {
});
try {
const heatmapRows = extractHeatmapEvents(events).map(e => ({
const heatmapRows = extractHeatmapEvents(events, { chunkIndex }).map(e => ({
websiteId,
sessionId,
visitId,
+17 -2
View File
@@ -1,4 +1,4 @@
import { parseRequest, setWebsiteDate } from '@/lib/request';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { reportResultSchema } from '@/lib/schema';
import { canViewWebsite } from '@/permissions';
@@ -17,7 +17,22 @@ export async function POST(request: Request) {
return unauthorized();
}
const parameters = (await setWebsiteDate(websiteId, body.parameters)) as HeatmapParameters;
const filters = await getQueryFilters(
{
...body.filters,
startAt: body.parameters.startDate.getTime(),
endAt: body.parameters.endDate.getTime(),
timezone: body.parameters.timezone,
unit: body.parameters.unit,
},
websiteId,
);
const parameters = {
...filters,
urlPath: body.parameters.urlPath,
mode: body.parameters.mode,
} as HeatmapParameters;
const data = await getHeatmap(websiteId, parameters);
@@ -3,6 +3,72 @@ import { json, unauthorized } from '@/lib/response';
import { canViewWebsite } from '@/permissions';
import { getReplayChunks } from '@/queries/sql';
function getEventTimestamp(event: any): number | null {
const timestamp = Number(event?.timestamp);
return Number.isFinite(timestamp) ? timestamp : null;
}
function parseOptionalInteger(value: string | null): number | undefined {
if (!value) {
return undefined;
}
const parsed = Number(value);
return Number.isInteger(parsed) ? parsed : undefined;
}
function mergeReplayEvents(
chunks: Awaited<ReturnType<typeof getReplayChunks>>,
{
until,
endChunkIndex,
endEventIndex,
}: { until?: number; endChunkIndex?: number; endEventIndex?: number },
) {
const events: any[] = [];
let isSorted = true;
let lastTimestamp = -Infinity;
for (const chunk of chunks) {
if (endChunkIndex !== undefined && chunk.chunkIndex > endChunkIndex) {
continue;
}
for (let chunkEventIndex = 0; chunkEventIndex < chunk.events.length; chunkEventIndex++) {
const event = chunk.events[chunkEventIndex];
const timestamp = getEventTimestamp(event);
if (chunk.chunkIndex === endChunkIndex && endEventIndex !== undefined) {
if (chunkEventIndex > endEventIndex) {
continue;
}
}
if (until !== undefined && timestamp !== null && timestamp > until) {
continue;
}
if (timestamp !== null) {
if (timestamp < lastTimestamp) {
isSorted = false;
} else {
lastTimestamp = timestamp;
}
}
events.push(event);
}
}
if (!isSorted) {
events.sort((a, b) => (getEventTimestamp(a) ?? 0) - (getEventTimestamp(b) ?? 0));
}
return events;
}
export async function GET(
request: Request,
{ params }: { params: Promise<{ websiteId: string; replayId: string }> },
@@ -14,16 +80,18 @@ export async function GET(
}
const { websiteId, replayId } = await params;
const searchParams = new URL(request.url).searchParams;
const until = parseOptionalInteger(searchParams.get('until'));
const endChunkIndex = parseOptionalInteger(searchParams.get('chunkIndex'));
const endEventIndex = parseOptionalInteger(searchParams.get('eventIndex'));
const endAt = until !== undefined ? new Date(until) : undefined;
if (!(await canViewWebsite(auth, websiteId))) {
return unauthorized();
}
const chunks = await getReplayChunks(websiteId, replayId);
const allEvents = chunks
.flatMap(chunk => chunk.events)
.sort((a, b) => a.timestamp - b.timestamp);
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;
const endedAt = chunks.length > 0 ? chunks[chunks.length - 1].endedAt : null;