From a81b144cd66abe0b668e0b545f8c0209054d341a Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 21 Jul 2025 10:49:48 -0700 Subject: [PATCH] fix get event metrics card and create it's own query --- .../[websiteId]/events/series/route.ts | 4 +- .../api/websites/[websiteId]/metrics/route.ts | 15 ++- src/queries/index.ts | 1 + src/queries/sql/events/getEventMetrics.ts | 100 +++++++++--------- src/queries/sql/events/getEventStats.ts | 92 ++++++++++++++++ 5 files changed, 157 insertions(+), 55 deletions(-) create mode 100644 src/queries/sql/events/getEventStats.ts diff --git a/src/app/api/websites/[websiteId]/events/series/route.ts b/src/app/api/websites/[websiteId]/events/series/route.ts index 5b5bc88c..a30741c6 100644 --- a/src/app/api/websites/[websiteId]/events/series/route.ts +++ b/src/app/api/websites/[websiteId]/events/series/route.ts @@ -3,7 +3,7 @@ import { parseRequest, getRequestDateRange, getRequestFilters } from '@/lib/requ import { unauthorized, json } from '@/lib/response'; import { canViewWebsite } from '@/lib/auth'; import { filterParams, timezoneParam, unitParam } from '@/lib/schema'; -import { getEventMetrics } from '@/queries'; +import { getEventStats } from '@/queries'; export async function GET( request: Request, @@ -39,7 +39,7 @@ export async function GET( unit, }; - const data = await getEventMetrics(websiteId, filters); + const data = await getEventStats(websiteId, filters); return json(data); } diff --git a/src/app/api/websites/[websiteId]/metrics/route.ts b/src/app/api/websites/[websiteId]/metrics/route.ts index 5bc4e522..488f85fc 100644 --- a/src/app/api/websites/[websiteId]/metrics/route.ts +++ b/src/app/api/websites/[websiteId]/metrics/route.ts @@ -15,7 +15,12 @@ import { } from '@/lib/constants'; import { getRequestFilters, getRequestDateRange, parseRequest } from '@/lib/request'; import { json, unauthorized, badRequest } from '@/lib/response'; -import { getPageviewMetrics, getSessionMetrics, getChannelMetrics } from '@/queries'; +import { + getPageviewMetrics, + getSessionMetrics, + getEventMetrics, + getChannelMetrics, +} from '@/queries'; import { filterParams } from '@/lib/schema'; export async function GET( @@ -85,7 +90,13 @@ export async function GET( } if (EVENT_COLUMNS.includes(type)) { - const data = await getPageviewMetrics(websiteId, type, filters, limit, offset); + let data; + + if (type === 'event') { + data = await getEventMetrics(websiteId, type, filters, limit, offset); + } else { + data = await getPageviewMetrics(websiteId, type, filters, limit, offset); + } return json(data); } diff --git a/src/queries/index.ts b/src/queries/index.ts index b9495bcd..0d57b294 100644 --- a/src/queries/index.ts +++ b/src/queries/index.ts @@ -11,6 +11,7 @@ export * from '@/queries/sql/events/getEventDataValues'; export * from '@/queries/sql/events/getEventDataStats'; export * from '@/queries/sql/events/getEventDataUsage'; export * from '@/queries/sql/events/getEventMetrics'; +export * from '@/queries/sql/events/getEventStats'; export * from '@/queries/sql/events/getWebsiteEvents'; export * from '@/queries/sql/events/getEventUsage'; export * from '@/queries/sql/events/saveEvent'; diff --git a/src/queries/sql/events/getEventMetrics.ts b/src/queries/sql/events/getEventMetrics.ts index 42dc8862..b028d759 100644 --- a/src/queries/sql/events/getEventMetrics.ts +++ b/src/queries/sql/events/getEventMetrics.ts @@ -1,32 +1,46 @@ import clickhouse from '@/lib/clickhouse'; -import { EVENT_TYPE } from '@/lib/constants'; +import { EVENT_TYPE, FILTER_COLUMNS, SESSION_COLUMNS } from '@/lib/constants'; import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db'; import prisma from '@/lib/prisma'; -import { QueryFilters, WebsiteEventMetric } from '@/lib/types'; +import { QueryFilters } from '@/lib/types'; export async function getEventMetrics( - ...args: [websiteId: string, filters: QueryFilters] -): Promise { + ...args: [ + websiteId: string, + type: string, + filters: QueryFilters, + limit?: number | string, + offset?: number | string, + ] +) { return runQuery({ [PRISMA]: () => relationalQuery(...args), [CLICKHOUSE]: () => clickhouseQuery(...args), }); } -async function relationalQuery(websiteId: string, filters: QueryFilters) { - const { timezone = 'utc', unit = 'day' } = filters; - const { rawQuery, getDateSQL, parseFilters } = prisma; - const { filterQuery, cohortQuery, joinSession, params } = await parseFilters(websiteId, { - ...filters, - eventType: EVENT_TYPE.customEvent, - }); +async function relationalQuery( + websiteId: string, + type: string, + filters: QueryFilters, + limit: number | string = 500, + offset: number | string = 0, +) { + const column = FILTER_COLUMNS[type] || type; + const { rawQuery, parseFilters } = prisma; + const { filterQuery, cohortQuery, joinSession, params } = await parseFilters( + websiteId, + { + ...filters, + eventType: EVENT_TYPE.customEvent, + }, + { joinSession: SESSION_COLUMNS.includes(type) }, + ); return rawQuery( ` - select - event_name x, - ${getDateSQL('website_event.created_at', unit, timezone)} t, - count(*) y + select ${column} x, + count(*) as y from website_event ${cohortQuery} ${joinSession} @@ -34,8 +48,10 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) { and website_event.created_at between {{startDate}} and {{endDate}} and event_type = {{eventType}} ${filterQuery} - group by 1, 2 - order by 2 + group by 1 + order by 2 desc + limit ${limit} + offset ${offset} `, params, ); @@ -43,50 +59,32 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) { async function clickhouseQuery( websiteId: string, + type: string, filters: QueryFilters, -): Promise<{ x: string; t: string; y: number }[]> { - const { timezone = 'UTC', unit = 'day' } = filters; - const { rawQuery, getDateSQL, parseFilters } = clickhouse; + limit: number | string = 500, + offset: number | string = 0, +): Promise<{ x: string; y: number }[]> { + const column = FILTER_COLUMNS[type] || type; + const { rawQuery, parseFilters } = clickhouse; const { filterQuery, cohortQuery, params } = await parseFilters(websiteId, { ...filters, eventType: EVENT_TYPE.customEvent, }); - let sql = ''; - - if (filterQuery || cohortQuery) { - sql = ` - select - event_name x, - ${getDateSQL('created_at', unit, timezone)} t, - count(*) y + return rawQuery( + `select ${column} x, + count(*) as y from website_event ${cohortQuery} where website_id = {websiteId:UUID} and created_at between {startDate:DateTime64} and {endDate:DateTime64} and event_type = {eventType:UInt32} ${filterQuery} - group by x, t - order by t - `; - } else { - sql = ` - select - event_name x, - ${getDateSQL('created_at', unit, timezone)} t, - count(*) y - from ( - select arrayJoin(event_name) as event_name, - created_at - from website_event_stats_hourly website_event - where website_id = {websiteId:UUID} - and created_at between {startDate:DateTime64} and {endDate:DateTime64} - and event_type = {eventType:UInt32} - ) as g - group by x, t - order by t - `; - } - - return rawQuery(sql, params); + group by x + order by y desc + limit ${limit} + offset ${offset} + `, + params, + ); } diff --git a/src/queries/sql/events/getEventStats.ts b/src/queries/sql/events/getEventStats.ts new file mode 100644 index 00000000..56f31363 --- /dev/null +++ b/src/queries/sql/events/getEventStats.ts @@ -0,0 +1,92 @@ +import clickhouse from '@/lib/clickhouse'; +import { EVENT_TYPE } from '@/lib/constants'; +import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db'; +import prisma from '@/lib/prisma'; +import { QueryFilters, WebsiteEventMetric } from '@/lib/types'; + +export async function getEventStats( + ...args: [websiteId: string, filters: QueryFilters] +): Promise { + return runQuery({ + [PRISMA]: () => relationalQuery(...args), + [CLICKHOUSE]: () => clickhouseQuery(...args), + }); +} + +async function relationalQuery(websiteId: string, filters: QueryFilters) { + const { timezone = 'utc', unit = 'day' } = filters; + const { rawQuery, getDateSQL, parseFilters } = prisma; + const { filterQuery, cohortQuery, joinSession, params } = await parseFilters(websiteId, { + ...filters, + eventType: EVENT_TYPE.customEvent, + }); + + return rawQuery( + ` + select + event_name x, + ${getDateSQL('website_event.created_at', unit, timezone)} t, + count(*) y + from website_event + ${cohortQuery} + ${joinSession} + where website_event.website_id = {{websiteId::uuid}} + and website_event.created_at between {{startDate}} and {{endDate}} + and event_type = {{eventType}} + ${filterQuery} + group by 1, 2 + order by 2 + `, + params, + ); +} + +async function clickhouseQuery( + websiteId: string, + filters: QueryFilters, +): Promise<{ x: string; t: string; y: number }[]> { + const { timezone = 'UTC', unit = 'day' } = filters; + const { rawQuery, getDateSQL, parseFilters } = clickhouse; + const { filterQuery, cohortQuery, params } = await parseFilters(websiteId, { + ...filters, + eventType: EVENT_TYPE.customEvent, + }); + + let sql = ''; + + if (filterQuery || cohortQuery) { + sql = ` + select + event_name x, + ${getDateSQL('created_at', unit, timezone)} t, + count(*) y + from website_event + ${cohortQuery} + where website_id = {websiteId:UUID} + and created_at between {startDate:DateTime64} and {endDate:DateTime64} + and event_type = {eventType:UInt32} + ${filterQuery} + group by x, t + order by t + `; + } else { + sql = ` + select + event_name x, + ${getDateSQL('created_at', unit, timezone)} t, + count(*) y + from ( + select arrayJoin(event_name) as event_name, + created_at + from website_event_stats_hourly website_event + where website_id = {websiteId:UUID} + and created_at between {startDate:DateTime64} and {endDate:DateTime64} + and event_type = {eventType:UInt32} + ) as g + group by x, t + order by t + `; + } + + return rawQuery(sql, params); +}