Merge branch 'dev' into jajaja

# Conflicts:
#	pnpm-lock.yaml
#	postcss.config.js
#	src/app/(main)/websites/[websiteId]/sessions/SessionsDataTable.tsx
#	src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionInfo.tsx
This commit is contained in:
Mike Cao 2025-05-01 03:31:51 -07:00
commit c0ccffeab4
21 changed files with 382 additions and 1281 deletions

View file

@ -14,7 +14,7 @@ export function SessionsDataTable({
const queryResult = useWebsiteSessionsQuery(websiteId);
return (
<DataGrid queryResult={queryResult} allowSearch={false} renderEmpty={() => children}>
<DataGrid queryResult={queryResult} allowSearch={true} renderEmpty={() => children}>
{({ data }) => <SessionsTable data={data} showDomain={!websiteId} />}
</DataGrid>
);

View file

@ -17,6 +17,11 @@ export function SessionInfo({ data }) {
<TextField value={data?.id} allowCopy />
</Box>
<Box>
<Label>{formatMessage(labels.distinctId)}</Label>
<Row>{data?.distinctId}</Row>
</Box>
<Box>
<Label>{formatMessage(labels.lastSeen)}</Label>
<Row>{formatTimezoneDate(data?.lastAt, 'PPPPpp')}</Row>

View file

@ -121,6 +121,7 @@ export async function POST(request: Request) {
country,
region,
city,
distinctId: id,
});
} catch (e: any) {
if (!e.message.toLowerCase().includes('unique constraint')) {
@ -144,7 +145,7 @@ export async function POST(request: Request) {
const base = hostname ? `https://${hostname}` : 'https://localhost';
const currentUrl = new URL(url, base);
let urlPath = currentUrl.pathname;
let urlPath = currentUrl.pathname === '/undefined' ? '' : currentUrl.pathname;
const urlQuery = currentUrl.search.substring(1);
const urlDomain = currentUrl.hostname.replace(/^www./, '');
@ -215,6 +216,7 @@ export async function POST(request: Request) {
region,
city,
tag,
distinctId: id,
createdAt,
});
}
@ -228,6 +230,7 @@ export async function POST(request: Request) {
websiteId,
sessionId,
sessionData: data,
distinctId: id,
createdAt,
});
}

View file

@ -132,6 +132,7 @@ export const labels = defineMessages({
all: { id: 'label.all', defaultMessage: 'All' },
session: { id: 'label.session', defaultMessage: 'Session' },
sessions: { id: 'label.sessions', defaultMessage: 'Sessions' },
distinctId: { id: 'label.distinct-id', defaultMessage: 'Distinct ID' },
pageNotFound: { id: 'message.page-not-found', defaultMessage: 'Page not found' },
activity: { id: 'label.activity', defaultMessage: 'Activity' },
dismiss: { id: 'label.dismiss', defaultMessage: 'Dismiss' },

View file

@ -33,17 +33,7 @@ export const FILTER_REFERRERS = 'filter-referrers';
export const FILTER_PAGES = 'filter-pages';
export const UNIT_TYPES = ['year', 'month', 'hour', 'day', 'minute'];
export const EVENT_COLUMNS = [
'url',
'entry',
'exit',
'referrer',
'title',
'query',
'event',
'tag',
'region',
];
export const EVENT_COLUMNS = ['url', 'entry', 'exit', 'referrer', 'title', 'query', 'event', 'tag'];
export const SESSION_COLUMNS = [
'browser',
@ -53,6 +43,7 @@ export const SESSION_COLUMNS = [
'language',
'country',
'city',
'region',
'host',
];

View file

@ -39,6 +39,7 @@ export async function saveEvent(args: {
region?: string;
city?: string;
tag?: string;
distinctId?: string;
createdAt?: Date;
}) {
return runQuery({
@ -182,6 +183,7 @@ async function clickhouseQuery(data: {
region?: string;
city?: string;
tag?: string;
distinctId?: string;
createdAt?: Date;
}) {
const {
@ -211,6 +213,7 @@ async function clickhouseQuery(data: {
region,
city,
tag,
distinctId,
createdAt,
...args
} = data;
@ -247,6 +250,7 @@ async function clickhouseQuery(data: {
event_type: eventName ? EVENT_TYPE.customEvent : EVENT_TYPE.pageView,
event_name: eventName ? eventName?.substring(0, EVENT_NAME_LENGTH) : null,
tag: tag,
distinct_id: distinctId,
created_at: getUTCString(createdAt),
};

View file

@ -2,7 +2,19 @@ import { Prisma } from '@prisma/client';
import prisma from '@/lib/prisma';
export async function createSession(data: Prisma.SessionCreateInput) {
const { id, websiteId, browser, os, device, screen, language, country, region, city } = data;
const {
id,
websiteId,
browser,
os,
device,
screen,
language,
country,
region,
city,
distinctId,
} = data;
return prisma.client.session.create({
data: {
@ -16,6 +28,7 @@ export async function createSession(data: Prisma.SessionCreateInput) {
country,
region,
city,
distinctId,
},
});
}

View file

@ -15,6 +15,7 @@ async function relationalQuery(websiteId: string, sessionId: string) {
return rawQuery(
`
select id,
distinct_id as "distinctId",
website_id as "websiteId",
hostname,
browser,
@ -33,6 +34,7 @@ async function relationalQuery(websiteId: string, sessionId: string) {
sum(${getTimestampDiffSQL('min_time', 'max_time')}) as "totaltime"
from (select
session.session_id as id,
session.distinct_id,
website_event.visit_id,
session.website_id,
website_event.hostname,
@ -52,8 +54,8 @@ async function relationalQuery(websiteId: string, sessionId: string) {
join website_event on website_event.session_id = session.session_id
where session.website_id = {{websiteId::uuid}}
and session.session_id = {{sessionId::uuid}}
group by session.session_id, visit_id, session.website_id, website_event.hostname, session.browser, session.os, session.device, session.screen, session.language, session.country, session.region, session.city) t
group by id, website_id, hostname, browser, os, device, screen, language, country, region, city;
group by session.session_id, session.distinct_id, visit_id, session.website_id, website_event.hostname, session.browser, session.os, session.device, session.screen, session.language, session.country, session.region, session.city) t
group by id, distinct_id, website_id, hostname, browser, os, device, screen, language, country, region, city;
`,
{ websiteId, sessionId },
).then(result => result?.[0]);
@ -66,6 +68,7 @@ async function clickhouseQuery(websiteId: string, sessionId: string) {
`
select id,
websiteId,
distinctId,
hostname,
browser,
os,
@ -83,6 +86,7 @@ async function clickhouseQuery(websiteId: string, sessionId: string) {
sum(max_time-min_time) as totaltime
from (select
session_id as id,
distinct_id as distinctId,
visit_id,
website_id as websiteId,
hostname,
@ -101,8 +105,8 @@ async function clickhouseQuery(websiteId: string, sessionId: string) {
from website_event_stats_hourly
where website_id = {websiteId:UUID}
and session_id = {sessionId:UUID}
group by session_id, visit_id, website_id, hostname, browser, os, device, screen, language, country, region, city) t
group by id, websiteId, hostname, browser, os, device, screen, language, country, region, city;
group by session_id, distinct_id, visit_id, website_id, hostname, browser, os, device, screen, language, country, region, city) t
group by id, websiteId, distinctId, hostname, browser, os, device, screen, language, country, region, city;
`,
{ websiteId, sessionId },
).then(result => result?.[0]);

View file

@ -1,5 +1,5 @@
import clickhouse from '@/lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
import { CLICKHOUSE, getDatabaseType, POSTGRESQL, PRISMA, runQuery } from '@/lib/db';
import prisma from '@/lib/prisma';
import { PageParams, QueryFilters } from '@/lib/types';
@ -14,10 +14,14 @@ export async function getWebsiteSessions(
async function relationalQuery(websiteId: string, filters: QueryFilters, pageParams: PageParams) {
const { pagedRawQuery, parseFilters } = prisma;
const { search } = pageParams;
const { filterQuery, params } = await parseFilters(websiteId, {
...filters,
});
const db = getDatabaseType();
const like = db === POSTGRESQL ? 'ilike' : 'like';
return pagedRawQuery(
`
with sessions as (
@ -43,6 +47,15 @@ async function relationalQuery(websiteId: string, filters: QueryFilters, pagePar
where website_event.website_id = {{websiteId::uuid}}
and website_event.created_at between {{startDate}} and {{endDate}}
${filterQuery}
${
search
? `and (distinct_id ${like} {{search}}
or city ${like} {{search}}
or browser ${like} {{search}}
or os ${like} {{search}}
or device ${like} {{search}})`
: ''
}
group by session.session_id,
session.website_id,
website_event.hostname,
@ -58,7 +71,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters, pagePar
limit 1000)
select * from sessions
`,
params,
{ ...params, search: `%${search}%` },
pageParams,
);
}
@ -66,6 +79,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters, pagePar
async function clickhouseQuery(websiteId: string, filters: QueryFilters, pageParams?: PageParams) {
const { pagedQuery, parseFilters, getDateStringSQL } = clickhouse;
const { params, dateQuery, filterQuery } = await parseFilters(websiteId, filters);
const { search } = pageParams;
return pagedQuery(
`
@ -91,12 +105,21 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters, pagePar
where website_id = {websiteId:UUID}
${dateQuery}
${filterQuery}
${
search
? `and ((positionCaseInsensitive(distinct_id, {search:String}) > 0)
or (positionCaseInsensitive(city, {search:String}) > 0)
or (positionCaseInsensitive(browser, {search:String}) > 0)
or (positionCaseInsensitive(os, {search:String}) > 0)
or (positionCaseInsensitive(device, {search:String}) > 0))`
: ''
}
group by session_id, website_id, hostname, browser, os, device, screen, language, country, region, city
order by lastAt desc
limit 1000)
select * from sessions
`,
params,
{ ...params, search },
pageParams,
);
}

View file

@ -11,6 +11,7 @@ export async function saveSessionData(data: {
websiteId: string;
sessionId: string;
sessionData: DynamicData;
distinctId?: string;
createdAt?: Date;
}) {
return runQuery({
@ -23,10 +24,11 @@ export async function relationalQuery(data: {
websiteId: string;
sessionId: string;
sessionData: DynamicData;
distinctId?: string;
createdAt?: Date;
}) {
const { client } = prisma;
const { websiteId, sessionId, sessionData, createdAt } = data;
const { websiteId, sessionId, sessionData, distinctId, createdAt } = data;
const jsonKeys = flattenJSON(sessionData);
@ -39,6 +41,7 @@ export async function relationalQuery(data: {
numberValue: a.dataType === DATA_TYPE.number ? a.value : null,
dateValue: a.dataType === DATA_TYPE.date ? new Date(a.value) : null,
dataType: a.dataType,
distinctId,
createdAt,
}));
@ -80,9 +83,10 @@ async function clickhouseQuery(data: {
websiteId: string;
sessionId: string;
sessionData: DynamicData;
distinctId?: string;
createdAt?: Date;
}) {
const { websiteId, sessionId, sessionData, createdAt } = data;
const { websiteId, sessionId, sessionData, distinctId, createdAt } = data;
const { insert, getUTCString } = clickhouse;
const { sendMessage } = kafka;
@ -98,6 +102,7 @@ async function clickhouseQuery(data: {
string_value: getStringValue(value, dataType),
number_value: dataType === DATA_TYPE.number ? value : null,
date_value: dataType === DATA_TYPE.date ? getUTCString(value) : null,
distinct_id: distinctId,
created_at: getUTCString(createdAt),
};
});