From fc1fc5807ec0682ad6996362c842e0fe74fe2c63 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 16 Aug 2024 19:44:16 -0700 Subject: [PATCH] Session properties. --- .../sessions/[sessionId]/SessionActivity.tsx | 6 +++- .../[sessionId]/SessionDetailsPage.tsx | 7 ++++- .../hooks/queries/useSessionActivity.ts | 15 ++++++++-- src/declaration.d.ts | 1 - src/lib/clickhouse.ts | 6 ++++ .../[websiteId]/session-data/properties.ts | 4 +-- .../sessions/[sessionId]/activity.ts | 11 ++++++-- src/queries/analytics/events/saveEvent.ts | 4 +-- .../analytics/sessions/getSessionActivity.ts | 28 +++++++++++++------ .../analytics/sessions/saveSessionData.ts | 4 +-- 10 files changed, 64 insertions(+), 22 deletions(-) diff --git a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionActivity.tsx b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionActivity.tsx index 3b0ad008..66393493 100644 --- a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionActivity.tsx +++ b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionActivity.tsx @@ -7,12 +7,16 @@ import styles from './SessionActivity.module.css'; export function SessionActivity({ websiteId, sessionId, + startDate, + endDate, }: { websiteId: string; sessionId: string; + startDate: Date; + endDate: Date; }) { const { formatDate } = useTimezone(); - const { data, isLoading } = useSessionActivity(websiteId, sessionId); + const { data, isLoading } = useSessionActivity(websiteId, sessionId, startDate, endDate); if (isLoading) { return ; diff --git a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionDetailsPage.tsx b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionDetailsPage.tsx index 212f8533..d6a07edc 100644 --- a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionDetailsPage.tsx +++ b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionDetailsPage.tsx @@ -28,7 +28,12 @@ export default function SessionDetailsPage({
- +
diff --git a/src/components/hooks/queries/useSessionActivity.ts b/src/components/hooks/queries/useSessionActivity.ts index e6d7ffa0..16c139ab 100644 --- a/src/components/hooks/queries/useSessionActivity.ts +++ b/src/components/hooks/queries/useSessionActivity.ts @@ -1,12 +1,21 @@ 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(); return useQuery({ - queryKey: ['session:activity', { websiteId, sessionId }], + queryKey: ['session:activity', { websiteId, sessionId, startDate, endDate }], 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), }); } diff --git a/src/declaration.d.ts b/src/declaration.d.ts index d968c14d..986adf27 100644 --- a/src/declaration.d.ts +++ b/src/declaration.d.ts @@ -1,5 +1,4 @@ declare module 'cors'; -declare module 'dateformat'; declare module 'debug'; declare module 'chartjs-adapter-date-fns'; declare module 'md5'; diff --git a/src/lib/clickhouse.ts b/src/lib/clickhouse.ts index 63027d75..474417b9 100644 --- a/src/lib/clickhouse.ts +++ b/src/lib/clickhouse.ts @@ -1,4 +1,5 @@ import { ClickHouseClient, createClient } from '@clickhouse/client'; +import { formatInTimeZone } from 'date-fns-tz'; import debug from 'debug'; import { CLICKHOUSE } from 'lib/db'; import { DEFAULT_PAGE_SIZE, OPERATORS } from './constants'; @@ -48,6 +49,10 @@ function getClient() { 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) { if (timezone) { return `formatDateTime(${data}, '${CLICKHOUSE_DATE_FORMATS[unit]}', '${timezone}')`; @@ -221,6 +226,7 @@ export default { getDateStringSQL, getDateSQL, getFilterQuery, + getUTCString, parseFilters, pagedQuery, findUnique, diff --git a/src/pages/api/websites/[websiteId]/session-data/properties.ts b/src/pages/api/websites/[websiteId]/session-data/properties.ts index 4cd2e1e6..92e182d2 100644 --- a/src/pages/api/websites/[websiteId]/session-data/properties.ts +++ b/src/pages/api/websites/[websiteId]/session-data/properties.ts @@ -6,7 +6,7 @@ import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { getSessionDataProperties } from 'queries'; import * as yup from 'yup'; -export interface EventDataFieldsRequestQuery { +export interface SessionDataFieldsRequestQuery { websiteId: string; startAt: string; endAt: string; @@ -23,7 +23,7 @@ const schema = { }; export default async ( - req: NextApiRequestQueryBody, + req: NextApiRequestQueryBody, res: NextApiResponse, ) => { await useCors(req, res); diff --git a/src/pages/api/websites/[websiteId]/sessions/[sessionId]/activity.ts b/src/pages/api/websites/[websiteId]/sessions/[sessionId]/activity.ts index 8d1b2346..2b0fc084 100644 --- a/src/pages/api/websites/[websiteId]/sessions/[sessionId]/activity.ts +++ b/src/pages/api/websites/[websiteId]/sessions/[sessionId]/activity.ts @@ -9,12 +9,16 @@ import { getSessionActivity } from 'queries'; export interface SessionActivityRequestQuery extends PageParams { websiteId: string; sessionId: string; + startAt: number; + endAt: number; } const schema = { GET: yup.object().shape({ websiteId: 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 useValidate(schema, req, res); - const { websiteId, sessionId } = req.query; + const { websiteId, sessionId, startAt, endAt } = req.query; if (req.method === 'GET') { if (!(await canViewWebsite(req.auth, websiteId))) { 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); } diff --git a/src/queries/analytics/events/saveEvent.ts b/src/queries/analytics/events/saveEvent.ts index 5e21e303..6c0f917b 100644 --- a/src/queries/analytics/events/saveEvent.ts +++ b/src/queries/analytics/events/saveEvent.ts @@ -135,10 +135,10 @@ async function clickhouseQuery(data: { city, ...args } = data; - const { insert } = clickhouse; + const { insert, getUTCString } = clickhouse; const { sendMessage } = kafka; const eventId = uuid(); - const createdAt = new Date().toISOString(); + const createdAt = getUTCString(); const message = { ...args, diff --git a/src/queries/analytics/sessions/getSessionActivity.ts b/src/queries/analytics/sessions/getSessionActivity.ts index 47c5f590..c50a82d9 100644 --- a/src/queries/analytics/sessions/getSessionActivity.ts +++ b/src/queries/analytics/sessions/getSessionActivity.ts @@ -2,32 +2,43 @@ import clickhouse from 'lib/clickhouse'; import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db'; 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({ [PRISMA]: () => relationalQuery(...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({ where: { id: sessionId, websiteId, + createdAt: { gte: startDate, lte: endDate }, }, take: 500, }); } -async function clickhouseQuery(websiteId: string, sessionId: string) { - const { rawQuery, getDateStringSQL } = clickhouse; +async function clickhouseQuery( + websiteId: string, + sessionId: string, + startDate: Date, + endDate: Date, +) { + const { rawQuery } = clickhouse; return rawQuery( ` select - session_id as id, - website_id as websiteId, - ${getDateStringSQL('created_at')} as createdAt, + created_at as createdAt, url_path as urlPath, url_query as urlQuery, referrer_domain as referrerDomain, @@ -38,9 +49,10 @@ async function clickhouseQuery(websiteId: string, sessionId: string) { from website_event where website_id = {websiteId:UUID} and session_id = {sessionId:UUID} + and created_at between {startDate:DateTime64} and {endDate:DateTime64} order by created_at desc limit 500 `, - { websiteId, sessionId }, + { websiteId, sessionId, startDate, endDate }, ); } diff --git a/src/queries/analytics/sessions/saveSessionData.ts b/src/queries/analytics/sessions/saveSessionData.ts index d932f7ed..5259239a 100644 --- a/src/queries/analytics/sessions/saveSessionData.ts +++ b/src/queries/analytics/sessions/saveSessionData.ts @@ -80,9 +80,9 @@ async function clickhouseQuery(data: { }) { const { websiteId, sessionId, sessionData } = data; - const { insert } = clickhouse; + const { insert, getUTCString } = clickhouse; const { sendMessages } = kafka; - const createdAt = new Date().toISOString(); + const createdAt = getUTCString(); const jsonKeys = flattenJSON(sessionData);