Session properties.

This commit is contained in:
Mike Cao 2024-08-16 19:44:16 -07:00
parent deb9dd60df
commit fc1fc5807e
10 changed files with 64 additions and 22 deletions

View File

@ -7,12 +7,16 @@ import styles from './SessionActivity.module.css';
export function SessionActivity({ export function SessionActivity({
websiteId, websiteId,
sessionId, sessionId,
startDate,
endDate,
}: { }: {
websiteId: string; websiteId: string;
sessionId: string; sessionId: string;
startDate: Date;
endDate: Date;
}) { }) {
const { formatDate } = useTimezone(); const { formatDate } = useTimezone();
const { data, isLoading } = useSessionActivity(websiteId, sessionId); const { data, isLoading } = useSessionActivity(websiteId, sessionId, startDate, endDate);
if (isLoading) { if (isLoading) {
return <Loading position="page" />; return <Loading position="page" />;

View File

@ -28,7 +28,12 @@ export default function SessionDetailsPage({
</div> </div>
<div className={styles.content}> <div className={styles.content}>
<SessionStats data={data} /> <SessionStats data={data} />
<SessionActivity websiteId={websiteId} sessionId={sessionId} /> <SessionActivity
websiteId={websiteId}
sessionId={sessionId}
startDate={data?.firstAt}
endDate={data?.lastAt}
/>
</div> </div>
<div className={styles.data}> <div className={styles.data}>
<SessionData websiteId={websiteId} sessionId={sessionId} /> <SessionData websiteId={websiteId} sessionId={sessionId} />

View File

@ -1,12 +1,21 @@
import { useApi } from './useApi'; import { useApi } from './useApi';
export function useSessionActivity(websiteId: string, sessionId: string) { export function useSessionActivity(
websiteId: string,
sessionId: string,
startDate: Date,
endDate: Date,
) {
const { get, useQuery } = useApi(); const { get, useQuery } = useApi();
return useQuery({ return useQuery({
queryKey: ['session:activity', { websiteId, sessionId }], queryKey: ['session:activity', { websiteId, sessionId, startDate, endDate }],
queryFn: () => { queryFn: () => {
return get(`/websites/${websiteId}/sessions/${sessionId}/activity`); return get(`/websites/${websiteId}/sessions/${sessionId}/activity`, {
startAt: +new Date(startDate),
endAt: +new Date(endDate),
});
}, },
enabled: Boolean(websiteId && sessionId && startDate && endDate),
}); });
} }

View File

@ -1,5 +1,4 @@
declare module 'cors'; declare module 'cors';
declare module 'dateformat';
declare module 'debug'; declare module 'debug';
declare module 'chartjs-adapter-date-fns'; declare module 'chartjs-adapter-date-fns';
declare module 'md5'; declare module 'md5';

View File

@ -1,4 +1,5 @@
import { ClickHouseClient, createClient } from '@clickhouse/client'; import { ClickHouseClient, createClient } from '@clickhouse/client';
import { formatInTimeZone } from 'date-fns-tz';
import debug from 'debug'; import debug from 'debug';
import { CLICKHOUSE } from 'lib/db'; import { CLICKHOUSE } from 'lib/db';
import { DEFAULT_PAGE_SIZE, OPERATORS } from './constants'; import { DEFAULT_PAGE_SIZE, OPERATORS } from './constants';
@ -48,6 +49,10 @@ function getClient() {
return client; return client;
} }
function getUTCString(date?: Date) {
return formatInTimeZone(date || new Date(), 'UTC', 'yyyy-MM-dd HH:mm:ss');
}
function getDateStringSQL(data: any, unit: string = 'utc', timezone?: string) { function getDateStringSQL(data: any, unit: string = 'utc', timezone?: string) {
if (timezone) { if (timezone) {
return `formatDateTime(${data}, '${CLICKHOUSE_DATE_FORMATS[unit]}', '${timezone}')`; return `formatDateTime(${data}, '${CLICKHOUSE_DATE_FORMATS[unit]}', '${timezone}')`;
@ -221,6 +226,7 @@ export default {
getDateStringSQL, getDateStringSQL,
getDateSQL, getDateSQL,
getFilterQuery, getFilterQuery,
getUTCString,
parseFilters, parseFilters,
pagedQuery, pagedQuery,
findUnique, findUnique,

View File

@ -6,7 +6,7 @@ import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getSessionDataProperties } from 'queries'; import { getSessionDataProperties } from 'queries';
import * as yup from 'yup'; import * as yup from 'yup';
export interface EventDataFieldsRequestQuery { export interface SessionDataFieldsRequestQuery {
websiteId: string; websiteId: string;
startAt: string; startAt: string;
endAt: string; endAt: string;
@ -23,7 +23,7 @@ const schema = {
}; };
export default async ( export default async (
req: NextApiRequestQueryBody<EventDataFieldsRequestQuery>, req: NextApiRequestQueryBody<SessionDataFieldsRequestQuery>,
res: NextApiResponse<any>, res: NextApiResponse<any>,
) => { ) => {
await useCors(req, res); await useCors(req, res);

View File

@ -9,12 +9,16 @@ import { getSessionActivity } from 'queries';
export interface SessionActivityRequestQuery extends PageParams { export interface SessionActivityRequestQuery extends PageParams {
websiteId: string; websiteId: string;
sessionId: string; sessionId: string;
startAt: number;
endAt: number;
} }
const schema = { const schema = {
GET: yup.object().shape({ GET: yup.object().shape({
websiteId: yup.string().uuid().required(), websiteId: yup.string().uuid().required(),
sessionId: yup.string().uuid().required(), sessionId: yup.string().uuid().required(),
startAt: yup.number().integer(),
endAt: yup.number().integer(),
}), }),
}; };
@ -26,14 +30,17 @@ export default async (
await useAuth(req, res); await useAuth(req, res);
await useValidate(schema, req, res); await useValidate(schema, req, res);
const { websiteId, sessionId } = req.query; const { websiteId, sessionId, startAt, endAt } = req.query;
if (req.method === 'GET') { if (req.method === 'GET') {
if (!(await canViewWebsite(req.auth, websiteId))) { if (!(await canViewWebsite(req.auth, websiteId))) {
return unauthorized(res); return unauthorized(res);
} }
const data = await getSessionActivity(websiteId, sessionId); const startDate = new Date(+startAt);
const endDate = new Date(+endAt);
const data = await getSessionActivity(websiteId, sessionId, startDate, endDate);
return ok(res, data); return ok(res, data);
} }

View File

@ -135,10 +135,10 @@ async function clickhouseQuery(data: {
city, city,
...args ...args
} = data; } = data;
const { insert } = clickhouse; const { insert, getUTCString } = clickhouse;
const { sendMessage } = kafka; const { sendMessage } = kafka;
const eventId = uuid(); const eventId = uuid();
const createdAt = new Date().toISOString(); const createdAt = getUTCString();
const message = { const message = {
...args, ...args,

View File

@ -2,32 +2,43 @@ import clickhouse from 'lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db'; import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import prisma from 'lib/prisma'; import prisma from 'lib/prisma';
export async function getSessionActivity(...args: [websiteId: string, sessionId: string]) { export async function getSessionActivity(
...args: [websiteId: string, sessionId: string, startDate: Date, endDate: Date]
) {
return runQuery({ return runQuery({
[PRISMA]: () => relationalQuery(...args), [PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args), [CLICKHOUSE]: () => clickhouseQuery(...args),
}); });
} }
async function relationalQuery(websiteId: string, sessionId: string) { async function relationalQuery(
websiteId: string,
sessionId: string,
startDate: Date,
endDate: Date,
) {
return prisma.client.websiteEvent.findMany({ return prisma.client.websiteEvent.findMany({
where: { where: {
id: sessionId, id: sessionId,
websiteId, websiteId,
createdAt: { gte: startDate, lte: endDate },
}, },
take: 500, take: 500,
}); });
} }
async function clickhouseQuery(websiteId: string, sessionId: string) { async function clickhouseQuery(
const { rawQuery, getDateStringSQL } = clickhouse; websiteId: string,
sessionId: string,
startDate: Date,
endDate: Date,
) {
const { rawQuery } = clickhouse;
return rawQuery( return rawQuery(
` `
select select
session_id as id, created_at as createdAt,
website_id as websiteId,
${getDateStringSQL('created_at')} as createdAt,
url_path as urlPath, url_path as urlPath,
url_query as urlQuery, url_query as urlQuery,
referrer_domain as referrerDomain, referrer_domain as referrerDomain,
@ -38,9 +49,10 @@ async function clickhouseQuery(websiteId: string, sessionId: string) {
from website_event from website_event
where website_id = {websiteId:UUID} where website_id = {websiteId:UUID}
and session_id = {sessionId:UUID} and session_id = {sessionId:UUID}
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
order by created_at desc order by created_at desc
limit 500 limit 500
`, `,
{ websiteId, sessionId }, { websiteId, sessionId, startDate, endDate },
); );
} }

View File

@ -80,9 +80,9 @@ async function clickhouseQuery(data: {
}) { }) {
const { websiteId, sessionId, sessionData } = data; const { websiteId, sessionId, sessionData } = data;
const { insert } = clickhouse; const { insert, getUTCString } = clickhouse;
const { sendMessages } = kafka; const { sendMessages } = kafka;
const createdAt = new Date().toISOString(); const createdAt = getUTCString();
const jsonKeys = flattenJSON(sessionData); const jsonKeys = flattenJSON(sessionData);