refactor session replays to be per visit. sessions persist too long resulting in low-quality recordings
This commit is contained in:
@@ -6,6 +6,8 @@ import prisma from '@/lib/prisma';
|
||||
const FUNCTION_NAME = 'getReplayChunks';
|
||||
|
||||
export interface ReplayChunk {
|
||||
sessionId: string;
|
||||
visitId: string;
|
||||
events: any[];
|
||||
chunkIndex: number;
|
||||
eventCount: number;
|
||||
@@ -13,20 +15,19 @@ export interface ReplayChunk {
|
||||
endedAt: Date;
|
||||
}
|
||||
|
||||
export async function getReplayChunks(
|
||||
websiteId: string,
|
||||
sessionId: string,
|
||||
): Promise<ReplayChunk[]> {
|
||||
export async function getReplayChunks(websiteId: string, visitId: string): Promise<ReplayChunk[]> {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(websiteId, sessionId),
|
||||
[CLICKHOUSE]: () => clickhouseQuery(websiteId, sessionId),
|
||||
[PRISMA]: () => relationalQuery(websiteId, visitId),
|
||||
[CLICKHOUSE]: () => clickhouseQuery(websiteId, visitId),
|
||||
});
|
||||
}
|
||||
|
||||
async function relationalQuery(websiteId: string, sessionId: string): Promise<ReplayChunk[]> {
|
||||
async function relationalQuery(websiteId: string, visitId: string): Promise<ReplayChunk[]> {
|
||||
const { rawQuery } = prisma;
|
||||
|
||||
const chunks: {
|
||||
sessionId: string;
|
||||
visitId: string;
|
||||
events: Buffer;
|
||||
chunkIndex: number;
|
||||
eventCount: number;
|
||||
@@ -35,6 +36,8 @@ async function relationalQuery(websiteId: string, sessionId: string): Promise<Re
|
||||
}[] = await rawQuery(
|
||||
`
|
||||
select
|
||||
session_id as "sessionId",
|
||||
visit_id as "visitId",
|
||||
events,
|
||||
chunk_index as "chunkIndex",
|
||||
event_count as "eventCount",
|
||||
@@ -42,10 +45,10 @@ async function relationalQuery(websiteId: string, sessionId: string): Promise<Re
|
||||
ended_at as "endedAt"
|
||||
from session_replay
|
||||
where website_id = {{websiteId::uuid}}
|
||||
and session_id = {{sessionId::uuid}}
|
||||
and visit_id = {{visitId::uuid}}
|
||||
order by chunk_index asc
|
||||
`,
|
||||
{ websiteId, sessionId },
|
||||
{ websiteId, visitId },
|
||||
FUNCTION_NAME,
|
||||
);
|
||||
|
||||
@@ -55,11 +58,13 @@ async function relationalQuery(websiteId: string, sessionId: string): Promise<Re
|
||||
}));
|
||||
}
|
||||
|
||||
async function clickhouseQuery(websiteId: string, sessionId: string): Promise<ReplayChunk[]> {
|
||||
async function clickhouseQuery(websiteId: string, visitId: string): Promise<ReplayChunk[]> {
|
||||
const { rawQuery } = clickhouse;
|
||||
|
||||
const results = await rawQuery<
|
||||
{
|
||||
sessionId: string;
|
||||
visitId: string;
|
||||
events: string;
|
||||
chunk_index: number;
|
||||
event_count: number;
|
||||
@@ -69,6 +74,8 @@ async function clickhouseQuery(websiteId: string, sessionId: string): Promise<Re
|
||||
>(
|
||||
`
|
||||
select
|
||||
session_id as sessionId,
|
||||
visit_id as visitId,
|
||||
events,
|
||||
chunk_index,
|
||||
event_count,
|
||||
@@ -76,14 +83,16 @@ async function clickhouseQuery(websiteId: string, sessionId: string): Promise<Re
|
||||
ended_at
|
||||
from session_replay
|
||||
where website_id = {websiteId:UUID}
|
||||
and session_id = {sessionId:UUID}
|
||||
and visit_id = {visitId:UUID}
|
||||
order by chunk_index asc
|
||||
`,
|
||||
{ websiteId, sessionId },
|
||||
{ websiteId, visitId },
|
||||
FUNCTION_NAME,
|
||||
);
|
||||
|
||||
return results.map(row => ({
|
||||
sessionId: row.sessionId,
|
||||
visitId: row.visitId,
|
||||
events: JSON.parse(row.events),
|
||||
chunkIndex: row.chunk_index,
|
||||
eventCount: row.event_count,
|
||||
|
||||
@@ -5,14 +5,16 @@ import type { QueryFilters } from '@/lib/types';
|
||||
|
||||
const FUNCTION_NAME = 'getSessionReplays';
|
||||
|
||||
export function getSessionReplays(...args: [websiteId: string, filters: QueryFilters]) {
|
||||
export function getSessionReplays(
|
||||
...args: [websiteId: string, filters: QueryFilters, sessionId?: string]
|
||||
) {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(...args),
|
||||
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
||||
});
|
||||
}
|
||||
|
||||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||
async function relationalQuery(websiteId: string, filters: QueryFilters, sessionId?: string) {
|
||||
const { pagedRawQuery, parseFilters } = prisma;
|
||||
const { search } = filters;
|
||||
const { filterQuery, cohortQuery, queryParams } = parseFilters({
|
||||
@@ -23,14 +25,19 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||
|
||||
const joinQuery =
|
||||
filterQuery || cohortQuery
|
||||
? `join (select *
|
||||
? `join (select distinct website_id, session_id, visit_id
|
||||
from website_event
|
||||
${cohortQuery}
|
||||
where website_id = {{websiteId::uuid}}
|
||||
and created_at between {{startDate}} and {{endDate}}) website_event
|
||||
and created_at between {{startDate}} and {{endDate}}
|
||||
${filterQuery}) website_event
|
||||
on website_event.website_id = sr.website_id
|
||||
and website_event.session_id = sr.session_id`
|
||||
and website_event.session_id = sr.session_id
|
||||
and website_event.visit_id = sr.visit_id`
|
||||
: '';
|
||||
|
||||
const sessionFilter = sessionId ? 'and sr.session_id = {{sessionId::uuid}}' : '';
|
||||
|
||||
const searchQuery = search
|
||||
? `and (session.distinct_id ilike {{search}}
|
||||
or session.city ilike {{search}}
|
||||
@@ -42,7 +49,8 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||
return pagedRawQuery(
|
||||
`
|
||||
select
|
||||
sr.session_id as "id",
|
||||
sr.visit_id as "id",
|
||||
sr.session_id as "sessionId",
|
||||
sr.website_id as "websiteId",
|
||||
session.browser,
|
||||
session.os,
|
||||
@@ -58,13 +66,13 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||
from session_replay sr
|
||||
join session on session.session_id = sr.session_id
|
||||
and session.website_id = sr.website_id
|
||||
${cohortQuery}
|
||||
${joinQuery}
|
||||
where sr.website_id = {{websiteId::uuid}}
|
||||
and sr.created_at between {{startDate}} and {{endDate}}
|
||||
${filterQuery}
|
||||
${sessionFilter}
|
||||
${searchQuery}
|
||||
group by sr.session_id,
|
||||
group by sr.visit_id,
|
||||
sr.session_id,
|
||||
sr.website_id,
|
||||
session.browser,
|
||||
session.os,
|
||||
@@ -73,13 +81,13 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||
session.city
|
||||
order by max(sr.created_at) desc
|
||||
`,
|
||||
queryParams,
|
||||
{ ...queryParams, sessionId },
|
||||
filters,
|
||||
FUNCTION_NAME,
|
||||
);
|
||||
}
|
||||
|
||||
async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
||||
async function clickhouseQuery(websiteId: string, filters: QueryFilters, sessionId?: string) {
|
||||
const { pagedRawQuery, parseFilters } = clickhouse;
|
||||
const { search } = filters;
|
||||
const { queryParams, cohortQuery, filterQuery } = parseFilters({
|
||||
@@ -87,6 +95,8 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
||||
websiteId,
|
||||
});
|
||||
|
||||
const sessionFilter = sessionId ? 'and session_replay.session_id = {sessionId:UUID}' : '';
|
||||
|
||||
const searchQuery = search
|
||||
? `and ((positionCaseInsensitive(distinct_id, {search:String}) > 0)
|
||||
or (positionCaseInsensitive(city, {search:String}) > 0)
|
||||
@@ -98,7 +108,8 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
||||
return pagedRawQuery(
|
||||
`
|
||||
select
|
||||
session_replay.session_id as id,
|
||||
session_replay.visit_id as id,
|
||||
session_replay.session_id as sessionId,
|
||||
session_replay.website_id as websiteId,
|
||||
website_event.browser,
|
||||
website_event.os,
|
||||
@@ -113,22 +124,24 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
||||
max(session_replay.created_at) as createdAt
|
||||
from session_replay
|
||||
join (
|
||||
select *
|
||||
select distinct website_id, session_id, visit_id, browser, os, device, country, city
|
||||
from website_event
|
||||
${cohortQuery}
|
||||
where website_id = {websiteId:UUID}
|
||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
${filterQuery}
|
||||
${searchQuery}
|
||||
) website_event
|
||||
on website_event.session_id = session_replay.session_id
|
||||
and website_event.website_id = session_replay.website_id
|
||||
${cohortQuery}
|
||||
and website_event.visit_id = session_replay.visit_id
|
||||
where session_replay.website_id = {websiteId:UUID}
|
||||
and session_replay.created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
${filterQuery}
|
||||
${searchQuery}
|
||||
group by session_replay.session_id, session_replay.website_id, website_event.browser, website_event.os, website_event.device, website_event.country, website_event.city
|
||||
${sessionFilter}
|
||||
group by session_replay.visit_id, session_replay.session_id, session_replay.website_id, website_event.browser, website_event.os, website_event.device, website_event.country, website_event.city
|
||||
order by max(created_at) desc
|
||||
`,
|
||||
queryParams,
|
||||
{ ...queryParams, sessionId },
|
||||
filters,
|
||||
FUNCTION_NAME,
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ import prisma from '@/lib/prisma';
|
||||
export interface SaveRecordingArgs {
|
||||
websiteId: string;
|
||||
sessionId: string;
|
||||
visitId: string;
|
||||
chunkIndex: number;
|
||||
events: any[];
|
||||
eventCount: number;
|
||||
@@ -24,6 +25,7 @@ export async function saveRecording(args: SaveRecordingArgs) {
|
||||
async function relationalQuery({
|
||||
websiteId,
|
||||
sessionId,
|
||||
visitId,
|
||||
chunkIndex,
|
||||
events,
|
||||
eventCount,
|
||||
@@ -37,6 +39,7 @@ async function relationalQuery({
|
||||
id: uuid(),
|
||||
websiteId,
|
||||
sessionId,
|
||||
visitId,
|
||||
chunkIndex,
|
||||
events: compressed as any,
|
||||
eventCount,
|
||||
@@ -49,6 +52,7 @@ async function relationalQuery({
|
||||
async function clickhouseQuery({
|
||||
websiteId,
|
||||
sessionId,
|
||||
visitId,
|
||||
chunkIndex,
|
||||
events,
|
||||
eventCount,
|
||||
@@ -62,6 +66,7 @@ async function clickhouseQuery({
|
||||
replay_id: uuid(),
|
||||
website_id: websiteId,
|
||||
session_id: sessionId,
|
||||
visit_id: visitId,
|
||||
chunk_index: chunkIndex,
|
||||
events: JSON.stringify(events),
|
||||
event_count: eventCount,
|
||||
|
||||
Reference in New Issue
Block a user