refactor Property components to be resused for both session/event data. Implement session-data screens + filtering. Remove all event-data specific compoenents/queries

This commit is contained in:
Francis Cao committed 2026-04-29 13:11:03 -07:00
1 parent 9da332c489
commit 680a1ca1a2
71 files changed
+2814 -958

No files matched your search

@@ -29,13 +29,13 @@ async function relationalQuery(
eventFilters: EventPropertyFilter[] = [],
): Promise<EventDataSeriesPoint[]> {
const { timezone = 'utc', unit = 'day' } = filters;
const { rawQuery, getDateSQL, parseFilters, getEventPropertyFilterQuery } = prisma;
const { rawQuery, getDateSQL, parseFilters, getPropertyFilterQuery } = prisma;
const { filterQuery, cohortQuery, joinSessionQuery, queryParams } = parseFilters({
...filters,
websiteId,
timezone,
});
const { sql: epfSQL, params: epfParams } = getEventPropertyFilterQuery(eventFilters, timezone);
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(eventFilters, 'event', timezone);
return rawQuery(
`
@@ -57,11 +57,11 @@ async function relationalQuery(
and event_data.data_key = {{propertyName}}
and event_data.data_type = ${DATA_TYPE.array}
${filterQuery}
${epfSQL}
${pfSQL}
group by 1, 2
order by 2
`,
{ ...queryParams, eventName, propertyName, ...epfParams },
{ ...queryParams, eventName, propertyName, ...pfParams },
FUNCTION_NAME,
);
}
@@ -74,9 +74,9 @@ async function clickhouseQuery(
eventFilters: EventPropertyFilter[] = [],
): Promise<EventDataSeriesPoint[]> {
const { timezone = 'UTC', unit = 'day' } = filters;
const { rawQuery, getDateSQL, parseFilters, getEventPropertyFilterQuery } = clickhouse;
const { rawQuery, getDateSQL, parseFilters, getPropertyFilterQuery } = clickhouse;
const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, websiteId, timezone });
const { sql: epfSQL, params: epfParams } = getEventPropertyFilterQuery(eventFilters, timezone);
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(eventFilters, 'event', timezone);
return rawQuery(
`
@@ -101,11 +101,11 @@ async function clickhouseQuery(
and event_data.data_key = {propertyName:String}
and event_data.data_type = ${DATA_TYPE.array}
${filterQuery}
${epfSQL}
${pfSQL}
group by x, t
order by t
`,
{ ...queryParams, eventName, propertyName, ...epfParams },
{ ...queryParams, eventName, propertyName, ...pfParams },
FUNCTION_NAME,
);
}
@@ -29,13 +29,13 @@ async function relationalQuery(
eventFilters: EventPropertyFilter[] = [],
) : Promise<EventDataDateSeriesPoint[]> {
const { timezone = 'utc' } = filters;
const { rawQuery, parseFilters, getDateStringSQL, getEventPropertyFilterQuery } = prisma;
const { rawQuery, parseFilters, getDateStringSQL, getPropertyFilterQuery } = prisma;
const { filterQuery, cohortQuery, joinSessionQuery, queryParams } = parseFilters({
...filters,
websiteId,
timezone,
});
const { sql: epfSQL, params: epfParams } = getEventPropertyFilterQuery(eventFilters, timezone);
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(eventFilters, 'event', timezone);
return rawQuery(
`
@@ -55,11 +55,11 @@ async function relationalQuery(
and event_data.data_key = {{propertyName}}
and event_data.data_type = ${DATA_TYPE.date}
${filterQuery}
${epfSQL}
${pfSQL}
group by 1
order by 1
`,
{ ...queryParams, eventName, propertyName, ...epfParams },
{ ...queryParams, eventName, propertyName, ...pfParams },
FUNCTION_NAME,
);
}
@@ -72,9 +72,9 @@ async function clickhouseQuery(
eventFilters: EventPropertyFilter[] = [],
): Promise<EventDataDateSeriesPoint[]> {
const { timezone = 'UTC' } = filters;
const { rawQuery, parseFilters, getDateStringSQL, getEventPropertyFilterQuery } = clickhouse;
const { rawQuery, parseFilters, getDateStringSQL, getPropertyFilterQuery } = clickhouse;
const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, websiteId, timezone });
const { sql: epfSQL, params: epfParams } = getEventPropertyFilterQuery(eventFilters, timezone);
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(eventFilters, 'event', timezone);
return rawQuery(
`
@@ -98,11 +98,11 @@ async function clickhouseQuery(
and event_data.data_key = {propertyName:String}
and event_data.data_type = ${DATA_TYPE.date}
${filterQuery}
${epfSQL}
${pfSQL}
group by t
order by t
`,
{ ...queryParams, eventName, propertyName, ...epfParams },
{ ...queryParams, eventName, propertyName, ...pfParams },
FUNCTION_NAME,
);
}
@@ -30,13 +30,13 @@ async function relationalQuery(
eventFilters: EventPropertyFilter[] = [],
) {
const { timezone = 'utc', unit = 'day' } = filters;
const { rawQuery, getDateSQL, parseFilters, getEventPropertyFilterQuery } = prisma;
const { rawQuery, getDateSQL, parseFilters, getPropertyFilterQuery } = prisma;
const { filterQuery, cohortQuery, joinSessionQuery, queryParams } = parseFilters({
...filters,
websiteId,
timezone,
});
const { sql: epfSQL, params: epfParams } = getEventPropertyFilterQuery(eventFilters, timezone);
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(eventFilters, 'event', timezone);
const aggSql =
metric === 'avg' ? 'avg(cast(event_data.number_value as decimal))' :
metric === 'count' ? 'count(*)' :
@@ -60,11 +60,11 @@ async function relationalQuery(
and event_data.data_key = {{propertyName}}
and event_data.data_type = 2
${filterQuery}
${epfSQL}
${pfSQL}
group by 1
order by 1
`,
{ ...queryParams, eventName, propertyName, ...epfParams },
{ ...queryParams, eventName, propertyName, ...pfParams },
FUNCTION_NAME,
);
}
@@ -78,9 +78,9 @@ async function clickhouseQuery(
eventFilters: EventPropertyFilter[] = [],
): Promise<{ t: string; y: number }[]> {
const { timezone = 'UTC', unit = 'day' } = filters;
const { rawQuery, getDateSQL, parseFilters, getEventPropertyFilterQuery } = clickhouse;
const { rawQuery, getDateSQL, parseFilters, getPropertyFilterQuery } = clickhouse;
const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, websiteId, timezone });
const { sql: epfSQL, params: epfParams } = getEventPropertyFilterQuery(eventFilters, timezone);
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(eventFilters, 'event', timezone);
const aggSql =
metric === 'avg' ? 'avg(event_data.number_value)' :
metric === 'count' ? 'count()' :
@@ -108,11 +108,11 @@ async function clickhouseQuery(
and event_data.data_key = {propertyName:String}
and event_data.data_type = 2
${filterQuery}
${epfSQL}
${pfSQL}
group by t
order by t
`,
{ ...queryParams, eventName, propertyName, ...epfParams },
{ ...queryParams, eventName, propertyName, ...pfParams },
FUNCTION_NAME,
);
}
@@ -28,13 +28,13 @@ async function relationalQuery(
eventFilters: EventPropertyFilter[] = [],
) {
const { timezone = 'utc' } = filters;
const { rawQuery, parseFilters, getEventPropertyFilterQuery } = prisma;
const { rawQuery, parseFilters, getPropertyFilterQuery } = prisma;
const { filterQuery, cohortQuery, joinSessionQuery, queryParams } = parseFilters({
...filters,
websiteId,
timezone,
});
const { sql: epfSQL, params: epfParams } = getEventPropertyFilterQuery(eventFilters, timezone);
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(eventFilters, 'event', timezone);
return rawQuery(
`
@@ -57,9 +57,9 @@ async function relationalQuery(
and event_data.data_key = {{propertyName}}
and event_data.data_type = 2
${filterQuery}
${epfSQL}
${pfSQL}
`,
{ ...queryParams, eventName, propertyName, ...epfParams },
{ ...queryParams, eventName, propertyName, ...pfParams },
FUNCTION_NAME,
);
}
@@ -72,9 +72,9 @@ async function clickhouseQuery(
eventFilters: EventPropertyFilter[] = [],
): Promise<EventDataNumericStats[]> {
const { timezone = 'UTC' } = filters;
const { rawQuery, parseFilters, getEventPropertyFilterQuery } = clickhouse;
const { rawQuery, parseFilters, getPropertyFilterQuery } = clickhouse;
const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, websiteId, timezone });
const { sql: epfSQL, params: epfParams } = getEventPropertyFilterQuery(eventFilters, timezone);
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(eventFilters, 'event', timezone);
return rawQuery(
`
@@ -101,9 +101,9 @@ async function clickhouseQuery(
and event_data.data_key = {propertyName:String}
and event_data.data_type = 2
${filterQuery}
${epfSQL}
${pfSQL}
`,
{ ...queryParams, eventName, propertyName, ...epfParams },
{ ...queryParams, eventName, propertyName, ...pfParams },
FUNCTION_NAME,
);
}
+12 -12
View File
@@ -22,7 +22,7 @@ export async function getEventDataPivot(
async function relationalQuery(websiteId: string, eventName: string, filters: QueryFilters, eventFilters: EventPropertyFilter[] = []) {
const { timezone = 'utc' } = filters;
const { rawQuery, parseFilters, getEventPropertyFilterQuery } = prisma;
const { rawQuery, parseFilters, getPropertyFilterQuery } = prisma;
const { page = 1, pageSize } = filters;
const size = +pageSize || DEFAULT_PAGE_SIZE;
const offset = +size * (+page - 1);
@@ -32,7 +32,7 @@ async function relationalQuery(websiteId: string, eventName: string, filters: Qu
websiteId,
timezone,
});
const { sql: epfSQL, params: epfParams } = getEventPropertyFilterQuery(eventFilters, timezone);
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(eventFilters, 'event', timezone);
const countResult = await rawQuery(
`
@@ -47,9 +47,9 @@ async function relationalQuery(websiteId: string, eventName: string, filters: Qu
and website_event.created_at between {{startDate}} and {{endDate}}
and website_event.event_name = {{eventName}}
${filterQuery}
${epfSQL}
${pfSQL}
`,
{ ...queryParams, eventName, ...epfParams },
{ ...queryParams, eventName, ...pfParams },
);
const count = countResult[0].num;
@@ -68,7 +68,7 @@ async function relationalQuery(websiteId: string, eventName: string, filters: Qu
and website_event.created_at between {{startDate}} and {{endDate}}
and website_event.event_name = {{eventName}}
${filterQuery}
${epfSQL}
${pfSQL}
group by website_event.event_id
order by max(website_event.created_at) desc
limit ${size} offset ${offset}
@@ -97,7 +97,7 @@ async function relationalQuery(websiteId: string, eventName: string, filters: Qu
and event_data.created_at between {{startDate}} and {{endDate}}
order by website_event.created_at desc
`,
{ ...queryParams, eventName, ...epfParams },
{ ...queryParams, eventName, ...pfParams },
FUNCTION_NAME,
);
@@ -120,13 +120,13 @@ async function relationalQuery(websiteId: string, eventName: string, filters: Qu
async function clickhouseQuery(websiteId: string, eventName: string, filters: QueryFilters, eventFilters: EventPropertyFilter[] = []) {
const { timezone = 'UTC' } = filters;
const { rawQuery, parseFilters, getEventPropertyFilterQuery } = clickhouse;
const { rawQuery, parseFilters, getPropertyFilterQuery } = clickhouse;
const { page = 1, pageSize } = filters;
const size = +pageSize || DEFAULT_PAGE_SIZE;
const offset = +size * (+page - 1);
const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, websiteId, timezone });
const { sql: epfSQL, params: epfParams } = getEventPropertyFilterQuery(eventFilters, timezone);
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(eventFilters, 'event', timezone);
const count = await rawQuery(
`
@@ -146,9 +146,9 @@ async function clickhouseQuery(websiteId: string, eventName: string, filters: Qu
where event_data_pivot.website_id = {websiteId:UUID}
and event_data_pivot.created_at between {startDate:DateTime64} and {endDate:DateTime64}
${filterQuery}
${epfSQL}
${pfSQL}
`,
{ ...queryParams, eventName, ...epfParams },
{ ...queryParams, eventName, ...pfParams },
).then((res: any) => res[0].num);
const data = await rawQuery(
@@ -176,7 +176,7 @@ async function clickhouseQuery(websiteId: string, eventName: string, filters: Qu
where event_data_pivot.website_id = {websiteId:UUID}
and event_data_pivot.created_at between {startDate:DateTime64} and {endDate:DateTime64}
${filterQuery}
${epfSQL}
${pfSQL}
group by
event_data_pivot.event_id,
event_data_pivot.session_id,
@@ -186,7 +186,7 @@ async function clickhouseQuery(websiteId: string, eventName: string, filters: Qu
order by event_data_pivot.created_at desc
limit ${size} offset ${offset}
`,
{ ...queryParams, eventName, ...epfParams },
{ ...queryParams, eventName, ...pfParams },
FUNCTION_NAME,
);
@@ -29,13 +29,13 @@ async function relationalQuery(
eventFilters: EventPropertyFilter[] = [],
): Promise<EventDataSeriesPoint[]> {
const { timezone = 'utc', unit = 'day' } = filters;
const { rawQuery, getDateSQL, parseFilters, getEventPropertyFilterQuery } = prisma;
const { rawQuery, getDateSQL, parseFilters, getPropertyFilterQuery } = prisma;
const { filterQuery, cohortQuery, joinSessionQuery, queryParams } = parseFilters({
...filters,
websiteId,
timezone,
});
const { sql: epfSQL, params: epfParams } = getEventPropertyFilterQuery(eventFilters, timezone);
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(eventFilters, 'event', timezone);
return rawQuery(
`
@@ -56,11 +56,11 @@ async function relationalQuery(
and event_data.data_key = {{propertyName}}
and event_data.data_type in (${DATA_TYPE.string}, ${DATA_TYPE.boolean})
${filterQuery}
${epfSQL}
${pfSQL}
group by 1, 2
order by 2
`,
{ ...queryParams, eventName, propertyName, ...epfParams },
{ ...queryParams, eventName, propertyName, ...pfParams },
FUNCTION_NAME,
);
}
@@ -73,9 +73,9 @@ async function clickhouseQuery(
eventFilters: EventPropertyFilter[] = [],
): Promise<EventDataSeriesPoint[]> {
const { timezone = 'UTC', unit = 'day' } = filters;
const { rawQuery, getDateSQL, parseFilters, getEventPropertyFilterQuery } = clickhouse;
const { rawQuery, getDateSQL, parseFilters, getPropertyFilterQuery } = clickhouse;
const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, websiteId, timezone });
const { sql: epfSQL, params: epfParams } = getEventPropertyFilterQuery(eventFilters, timezone);
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(eventFilters, 'event', timezone);
return rawQuery(
`
@@ -100,11 +100,11 @@ async function clickhouseQuery(
and event_data.data_key = {propertyName:String}
and event_data.data_type in (${DATA_TYPE.string}, ${DATA_TYPE.boolean})
${filterQuery}
${epfSQL}
${pfSQL}
group by x, t
order by t
`,
{ ...queryParams, eventName, propertyName, ...epfParams },
{ ...queryParams, eventName, propertyName, ...pfParams },
FUNCTION_NAME,
);
}
@@ -76,6 +76,7 @@ async function relationalQuery(
where event_data.website_id = {{websiteId::uuid}}
and event_data.created_at between {{startDate}} and {{endDate}}
and event_data.data_key = {{propertyName}}
${dataType ? `and event_data.data_type = ${dataType}` : ''}
${filterQuery}
group by value
order by 2 desc
@@ -146,6 +147,7 @@ async function clickhouseQuery(
where event_data.website_id = {websiteId:UUID}
and event_data.created_at between {startDate:DateTime64} and {endDate:DateTime64}
and event_data.data_key = {propertyName:String}
${dataType ? `and event_data.data_type = ${dataType}` : ''}
${filterQuery}
group by value
order by 2 desc
+7
View File
@@ -41,7 +41,14 @@ export * from './reports/getUTM';
export * from './sessions/createSession';
export * from './sessions/getSessionActivity';
export * from './sessions/getSessionData';
export * from './sessions/getSessionDataArraySeries';
export * from './sessions/getSessionDataDateSeries';
export * from './sessions/getSessionDataProperties';
export * from './sessions/getSessionDataPropertySeries';
export * from './sessions/getSessionDataNumericSeries';
export * from './sessions/getSessionDataNumericStats';
export * from './sessions/getSessionDataPivot';
export * from './sessions/getSessionDataStats';
export * from './sessions/getSessionDataValues';
export * from './sessions/getSessionExpandedMetrics';
export * from './sessions/getSessionMetrics';
@@ -0,0 +1,99 @@
import clickhouse from '@/lib/clickhouse';
import { DATA_TYPE } from '@/lib/constants';
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
import prisma from '@/lib/prisma';
import type { EventDataSeriesPoint, PropertyFilter, QueryFilters } from '@/lib/types';
const FUNCTION_NAME = 'getSessionDataArraySeries';
export async function getSessionDataArraySeries(
...args: [
websiteId: string,
propertyName: string,
filters: QueryFilters,
propertyFilters?: PropertyFilter[],
]
): Promise<EventDataSeriesPoint[]> {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
});
}
async function relationalQuery(
websiteId: string,
propertyName: string,
filters: QueryFilters,
propertyFilters: PropertyFilter[] = [],
): Promise<EventDataSeriesPoint[]> {
const { timezone = 'utc', unit = 'day' } = filters;
const { rawQuery, getDateSQL, parseFilters, getPropertyFilterQuery } = prisma;
const { filterQuery, cohortQuery, joinSessionQuery, queryParams } = parseFilters({
...filters,
websiteId,
timezone,
});
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(propertyFilters, 'session', timezone);
return rawQuery(
`
select
array_item.value as x,
${getDateSQL('session_data.created_at', unit, timezone)} as t,
count(distinct session_data.session_id) as y
from website_event
${cohortQuery}
${joinSessionQuery}
join session_data
on session_data.session_id = website_event.session_id
and session_data.website_id = website_event.website_id
cross join lateral jsonb_array_elements_text(coalesce(session_data.string_value, '[]')::jsonb) as array_item(value)
where website_event.website_id = {{websiteId::uuid}}
and website_event.created_at between {{startDate}} and {{endDate}}
and session_data.data_key = {{propertyName}}
and session_data.data_type = ${DATA_TYPE.array}
${filterQuery}
${pfSQL}
group by 1, 2
order by 2
`,
{ ...queryParams, propertyName, ...pfParams },
FUNCTION_NAME,
);
}
async function clickhouseQuery(
websiteId: string,
propertyName: string,
filters: QueryFilters,
propertyFilters: PropertyFilter[] = [],
): Promise<EventDataSeriesPoint[]> {
const { timezone = 'UTC', unit = 'day' } = filters;
const { rawQuery, getDateSQL, parseFilters, getPropertyFilterQuery } = clickhouse;
const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, websiteId, timezone });
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(propertyFilters, 'session', timezone);
return rawQuery(
`
select
arrayJoin(JSONExtract(ifNull(session_data.string_value, '[]'), 'Array(String)')) as x,
${getDateSQL('session_data.created_at', unit, timezone)} as t,
uniq(session_data.session_id) as y
from website_event
${cohortQuery}
join session_data final
on session_data.session_id = website_event.session_id
and session_data.website_id = {websiteId:UUID}
where website_event.website_id = {websiteId:UUID}
and website_event.created_at between {startDate:DateTime64} and {endDate:DateTime64}
and session_data.data_key = {propertyName:String}
and session_data.data_type = ${DATA_TYPE.array}
${filterQuery}
${pfSQL}
group by x, t
order by t
`,
{ ...queryParams, propertyName, ...pfParams },
FUNCTION_NAME,
);
}
@@ -0,0 +1,96 @@
import clickhouse from '@/lib/clickhouse';
import { DATA_TYPE } from '@/lib/constants';
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
import prisma from '@/lib/prisma';
import type { EventDataDateSeriesPoint, PropertyFilter, QueryFilters } from '@/lib/types';
const FUNCTION_NAME = 'getSessionDataDateSeries';
export async function getSessionDataDateSeries(
...args: [
websiteId: string,
propertyName: string,
filters: QueryFilters,
propertyFilters?: PropertyFilter[],
]
): Promise<EventDataDateSeriesPoint[]> {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
});
}
async function relationalQuery(
websiteId: string,
propertyName: string,
filters: QueryFilters,
propertyFilters: PropertyFilter[] = [],
): Promise<EventDataDateSeriesPoint[]> {
const { timezone = 'utc' } = filters;
const { rawQuery, parseFilters, getDateStringSQL, getPropertyFilterQuery } = prisma;
const { filterQuery, cohortQuery, joinSessionQuery, queryParams } = parseFilters({
...filters,
websiteId,
timezone,
});
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(propertyFilters, 'session', timezone);
return rawQuery(
`
select
${getDateStringSQL('session_data.date_value', 'second', timezone)} as t,
count(distinct session_data.session_id) as y
from website_event
${cohortQuery}
${joinSessionQuery}
join session_data
on session_data.session_id = website_event.session_id
and session_data.website_id = website_event.website_id
where website_event.website_id = {{websiteId::uuid}}
and website_event.created_at between {{startDate}} and {{endDate}}
and session_data.data_key = {{propertyName}}
and session_data.data_type = ${DATA_TYPE.date}
${filterQuery}
${pfSQL}
group by 1
order by 1
`,
{ ...queryParams, propertyName, ...pfParams },
FUNCTION_NAME,
);
}
async function clickhouseQuery(
websiteId: string,
propertyName: string,
filters: QueryFilters,
propertyFilters: PropertyFilter[] = [],
): Promise<EventDataDateSeriesPoint[]> {
const { timezone = 'UTC' } = filters;
const { rawQuery, parseFilters, getDateStringSQL, getPropertyFilterQuery } = clickhouse;
const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, websiteId, timezone });
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(propertyFilters, 'session', timezone);
return rawQuery(
`
select
${getDateStringSQL('session_data.date_value', 'second', timezone)} as t,
uniq(session_data.session_id) as y
from website_event
${cohortQuery}
join session_data final
on session_data.session_id = website_event.session_id
and session_data.website_id = {websiteId:UUID}
where website_event.website_id = {websiteId:UUID}
and website_event.created_at between {startDate:DateTime64} and {endDate:DateTime64}
and session_data.data_key = {propertyName:String}
and session_data.data_type = ${DATA_TYPE.date}
${filterQuery}
${pfSQL}
group by t
order by t
`,
{ ...queryParams, propertyName, ...pfParams },
FUNCTION_NAME,
);
}
@@ -0,0 +1,119 @@
import clickhouse from '@/lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
import prisma from '@/lib/prisma';
import type { PropertyFilter, QueryFilters } from '@/lib/types';
const FUNCTION_NAME = 'getSessionDataNumericSeries';
export async function getSessionDataNumericSeries(
...args: [
websiteId: string,
propertyName: string,
metric: 'sum' | 'avg' | 'count',
filters: QueryFilters,
propertyFilters?: PropertyFilter[],
]
) {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
});
}
async function relationalQuery(
websiteId: string,
propertyName: string,
metric: 'sum' | 'avg' | 'count',
filters: QueryFilters,
propertyFilters: PropertyFilter[] = [],
) {
const { timezone = 'utc', unit = 'day' } = filters;
const { rawQuery, getDateSQL, parseFilters, getPropertyFilterQuery } = prisma;
const { filterQuery, cohortQuery, joinSessionQuery, queryParams } = parseFilters({
...filters,
websiteId,
timezone,
});
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(propertyFilters, 'session', timezone);
const aggSql =
metric === 'avg'
? 'avg(cast(session_data.number_value as decimal))'
: metric === 'count'
? 'count(distinct session_data.session_id)'
: 'sum(cast(session_data.number_value as decimal))';
return rawQuery(
`
with filtered_sessions as (
select distinct website_event.session_id, website_event.website_id
from website_event
${cohortQuery}
${joinSessionQuery}
where website_event.website_id = {{websiteId::uuid}}
and website_event.created_at between {{startDate}} and {{endDate}}
${filterQuery}
${pfSQL}
)
select
${getDateSQL('session_data.created_at', unit, timezone)} t,
${aggSql} y
from session_data
join filtered_sessions
on filtered_sessions.session_id = session_data.session_id
and filtered_sessions.website_id = session_data.website_id
where session_data.website_id = {{websiteId::uuid}}
and session_data.data_key = {{propertyName}}
and session_data.data_type = 2
group by 1
order by 1
`,
{ ...queryParams, propertyName, ...pfParams },
FUNCTION_NAME,
);
}
async function clickhouseQuery(
websiteId: string,
propertyName: string,
metric: 'sum' | 'avg' | 'count',
filters: QueryFilters,
propertyFilters: PropertyFilter[] = [],
): Promise<{ t: string; y: number }[]> {
const { timezone = 'UTC', unit = 'day' } = filters;
const { rawQuery, getDateSQL, parseFilters, getPropertyFilterQuery } = clickhouse;
const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, websiteId, timezone });
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(propertyFilters, 'session', timezone);
const aggSql =
metric === 'avg'
? 'avg(session_data.number_value)'
: metric === 'count'
? 'uniq(session_data.session_id)'
: 'sum(session_data.number_value)';
return rawQuery(
`
with filtered_sessions as (
select distinct website_event.session_id
from website_event
${cohortQuery}
where website_event.website_id = {websiteId:UUID}
and website_event.created_at between {startDate:DateTime64} and {endDate:DateTime64}
${filterQuery}
${pfSQL}
)
select
${getDateSQL('session_data.created_at', unit, timezone)} as t,
${aggSql} as y
from session_data final
join filtered_sessions
on filtered_sessions.session_id = session_data.session_id
where session_data.website_id = {websiteId:UUID}
and session_data.data_key = {propertyName:String}
and session_data.data_type = 2
group by t
order by t
`,
{ ...queryParams, propertyName, ...pfParams },
FUNCTION_NAME,
);
}
@@ -0,0 +1,106 @@
import clickhouse from '@/lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
import prisma from '@/lib/prisma';
import type { EventDataNumericStats, PropertyFilter, QueryFilters } from '@/lib/types';
const FUNCTION_NAME = 'getSessionDataNumericStats';
export async function getSessionDataNumericStats(
...args: [
websiteId: string,
propertyName: string,
filters: QueryFilters,
propertyFilters?: PropertyFilter[],
]
): Promise<EventDataNumericStats> {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
}).then(results => results?.[0]);
}
async function relationalQuery(
websiteId: string,
propertyName: string,
filters: QueryFilters,
propertyFilters: PropertyFilter[] = [],
) {
const { timezone = 'utc' } = filters;
const { rawQuery, parseFilters, getPropertyFilterQuery } = prisma;
const { filterQuery, cohortQuery, joinSessionQuery, queryParams } = parseFilters({
...filters,
websiteId,
timezone,
});
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(propertyFilters, 'session', timezone);
return rawQuery(
`
with filtered_sessions as (
select distinct website_event.session_id, website_event.website_id
from website_event
${cohortQuery}
${joinSessionQuery}
where website_event.website_id = {{websiteId::uuid}}
and website_event.created_at between {{startDate}} and {{endDate}}
${filterQuery}
${pfSQL}
)
select
coalesce(sum(cast(session_data.number_value as decimal)), 0) as "total",
coalesce(avg(cast(session_data.number_value as decimal)), 0) as "average",
coalesce(percentile_cont(0.5) within group (order by session_data.number_value), 0) as "median",
coalesce(max(session_data.number_value), 0) as "max",
coalesce(min(session_data.number_value), 0) as "min"
from session_data
join filtered_sessions
on filtered_sessions.session_id = session_data.session_id
and filtered_sessions.website_id = session_data.website_id
where session_data.website_id = {{websiteId::uuid}}
and session_data.data_key = {{propertyName}}
and session_data.data_type = 2
`,
{ ...queryParams, propertyName, ...pfParams },
FUNCTION_NAME,
);
}
async function clickhouseQuery(
websiteId: string,
propertyName: string,
filters: QueryFilters,
propertyFilters: PropertyFilter[] = [],
): Promise<EventDataNumericStats[]> {
const { timezone = 'UTC' } = filters;
const { rawQuery, parseFilters, getPropertyFilterQuery } = clickhouse;
const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, websiteId, timezone });
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(propertyFilters, 'session', timezone);
return rawQuery(
`
with filtered_sessions as (
select distinct website_event.session_id
from website_event
${cohortQuery}
where website_event.website_id = {websiteId:UUID}
and website_event.created_at between {startDate:DateTime64} and {endDate:DateTime64}
${filterQuery}
${pfSQL}
)
select
if(count() = 0, 0, sum(session_data.number_value)) as total,
if(count() = 0, 0, avg(session_data.number_value)) as average,
if(count() = 0, 0, median(session_data.number_value)) as median,
if(count() = 0, 0, max(session_data.number_value)) as max,
if(count() = 0, 0, min(session_data.number_value)) as min
from session_data final
join filtered_sessions
on filtered_sessions.session_id = session_data.session_id
where session_data.website_id = {websiteId:UUID}
and session_data.data_key = {propertyName:String}
and session_data.data_type = 2
`,
{ ...queryParams, propertyName, ...pfParams },
FUNCTION_NAME,
);
}
@@ -0,0 +1,215 @@
import clickhouse from '@/lib/clickhouse';
import { DEFAULT_PAGE_SIZE } from '@/lib/constants';
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
import prisma from '@/lib/prisma';
import type { PageResult, PropertyFilter, QueryFilters, SessionDataPivotRow } from '@/lib/types';
const FUNCTION_NAME = 'getSessionDataPivot';
export async function getSessionDataPivot(
...args: [
websiteId: string,
propertyName: string,
filters: QueryFilters,
propertyFilters?: PropertyFilter[],
]
): Promise<PageResult<SessionDataPivotRow[]>> {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
});
}
async function relationalQuery(
websiteId: string,
propertyName: string,
filters: QueryFilters,
propertyFilters: PropertyFilter[] = [],
): Promise<PageResult<SessionDataPivotRow[]>> {
const { timezone = 'utc' } = filters;
const { rawQuery, parseFilters, getPropertyFilterQuery } = prisma;
const { page = 1, pageSize } = filters;
const size = +pageSize || DEFAULT_PAGE_SIZE;
const offset = +size * (+page - 1);
const { filterQuery, cohortQuery, joinSessionQuery, queryParams } = parseFilters({
...filters,
websiteId,
timezone,
});
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(
propertyFilters,
'session',
timezone,
);
const countResult = await rawQuery(
`
with filtered_sessions as (
select distinct website_event.session_id
from website_event
${cohortQuery}
${joinSessionQuery}
where website_event.website_id = {{websiteId::uuid}}
and website_event.created_at between {{startDate}} and {{endDate}}
${filterQuery}
${pfSQL}
),
selected_property_sessions as (
select distinct session_data.session_id
from session_data
join filtered_sessions
on filtered_sessions.session_id = session_data.session_id
where session_data.website_id = {{websiteId::uuid}}
and session_data.data_key = {{propertyName}}
)
select count(*) as num
from selected_property_sessions
`,
{ ...queryParams, websiteId, propertyName, ...pfParams },
);
const count = countResult[0].num;
const rows = await rawQuery(
`
with filtered_sessions as (
select distinct website_event.session_id
from website_event
${cohortQuery}
${joinSessionQuery}
where website_event.website_id = {{websiteId::uuid}}
and website_event.created_at between {{startDate}} and {{endDate}}
${filterQuery}
${pfSQL}
),
selected_property_sessions as (
select distinct session_data.session_id
from session_data
join filtered_sessions
on filtered_sessions.session_id = session_data.session_id
where session_data.website_id = {{websiteId::uuid}}
and session_data.data_key = {{propertyName}}
),
paged_sessions as (
select session_data.session_id
from session_data
join selected_property_sessions
on selected_property_sessions.session_id = session_data.session_id
where session_data.website_id = {{websiteId::uuid}}
group by session_data.session_id
order by max(session_data.created_at) desc
limit ${size} offset ${offset}
)
select
session_data.session_id as "sessionId",
coalesce(max(session_data.distinct_id), '') as "distinctId",
max(session_data.created_at) as "createdAt",
array_agg(session_data.data_key order by session_data.data_key asc) as "propertyKeys",
array_agg(
coalesce(
case when session_data.data_type = 1 then session_data.string_value end,
case when session_data.data_type = 2 then cast(session_data.number_value as varchar) end,
case when session_data.data_type = 3 then session_data.string_value end,
case when session_data.data_type = 4 then cast(session_data.date_value as varchar) end,
case when session_data.data_type = 5 then session_data.string_value end,
''
)
order by session_data.data_key asc
) as "propertyValues"
from session_data
join paged_sessions on paged_sessions.session_id = session_data.session_id
where session_data.website_id = {{websiteId::uuid}}
group by session_data.session_id
order by max(session_data.created_at) desc
`,
{ ...queryParams, websiteId, propertyName, ...pfParams },
FUNCTION_NAME,
);
return { data: rows, count, page: +page, pageSize: size };
}
async function clickhouseQuery(
websiteId: string,
propertyName: string,
filters: QueryFilters,
propertyFilters: PropertyFilter[] = [],
): Promise<PageResult<SessionDataPivotRow[]>> {
const { timezone = 'UTC' } = filters;
const { rawQuery, parseFilters, getPropertyFilterQuery } = clickhouse;
const { page = 1, pageSize } = filters;
const size = +pageSize || DEFAULT_PAGE_SIZE;
const offset = +size * (+page - 1);
const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, websiteId, timezone });
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(
propertyFilters,
'session',
timezone,
);
const count = await rawQuery(
`
with filtered_sessions as (
select distinct website_event.session_id
from website_event
${cohortQuery}
where website_event.website_id = {websiteId:UUID}
and website_event.created_at between {startDate:DateTime64} and {endDate:DateTime64}
${filterQuery}
${pfSQL}
),
selected_property_sessions as (
select distinct session_data.session_id
from session_data final
join filtered_sessions on filtered_sessions.session_id = session_data.session_id
where session_data.website_id = {websiteId:UUID}
and session_data.data_key = {propertyName:String}
)
select count() as num
from selected_property_sessions
`,
{ ...queryParams, websiteId, propertyName, ...pfParams },
).then((res: any) => res[0].num);
const data = await rawQuery(
`
with filtered_sessions as (
select distinct website_event.session_id
from website_event
${cohortQuery}
where website_event.website_id = {websiteId:UUID}
and website_event.created_at between {startDate:DateTime64} and {endDate:DateTime64}
${filterQuery}
${pfSQL}
),
selected_property_sessions as (
select distinct session_data.session_id
from session_data final
join filtered_sessions on filtered_sessions.session_id = session_data.session_id
where session_data.website_id = {websiteId:UUID}
and session_data.data_key = {propertyName:String}
)
select
session_data_pivot.session_id as sessionId,
session_data_pivot.distinct_id as distinctId,
maxMerge(session_data_pivot.created_at) as createdAt,
groupArrayMerge(session_data_pivot.property_keys) as propertyKeys,
groupArrayMerge(session_data_pivot.property_values) as propertyValues
from umami.session_data_pivot session_data_pivot
join selected_property_sessions
on selected_property_sessions.session_id = session_data_pivot.session_id
where session_data_pivot.website_id = {websiteId:UUID}
group by
session_data_pivot.session_id,
session_data_pivot.distinct_id
order by createdAt desc
limit ${size} offset ${offset}
`,
{ ...queryParams, websiteId, propertyName, ...pfParams },
FUNCTION_NAME,
);
return { data, count, page: +page, pageSize: size };
}
@@ -1,12 +1,17 @@
import clickhouse from '@/lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
import prisma from '@/lib/prisma';
import type { QueryFilters } from '@/lib/types';
import type { PropertyFilter, QueryFilters } from '@/lib/types';
const FUNCTION_NAME = 'getSessionDataProperties';
export async function getSessionDataProperties(
...args: [websiteId: string, filters: QueryFilters]
...args: [
websiteId: string,
filters: QueryFilters,
propertyFilters?: PropertyFilter[],
selectedPropertyName?: string,
]
) {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
@@ -14,32 +19,58 @@ export async function getSessionDataProperties(
});
}
async function relationalQuery(websiteId: string, filters: QueryFilters) {
const { rawQuery, parseFilters } = prisma;
async function relationalQuery(
websiteId: string,
filters: QueryFilters,
propertyFilters: PropertyFilter[] = [],
selectedPropertyName?: string,
) {
const { timezone = 'utc' } = filters;
const { rawQuery, parseFilters, getPropertyFilterQuery } = prisma;
const { filterQuery, joinSessionQuery, cohortQuery, queryParams } = parseFilters({
...filters,
websiteId,
timezone,
});
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(
propertyFilters,
'session',
timezone,
);
return rawQuery(
`
with filtered_sessions as (
select distinct website_event.session_id, website_event.website_id
from website_event
${cohortQuery}
${joinSessionQuery}
where website_event.website_id = {{websiteId::uuid}}
and website_event.created_at between {{startDate}} and {{endDate}}
${filterQuery}
${pfSQL}
),
selected_property_sessions as (
select distinct session_data.session_id, session_data.website_id
from session_data
join filtered_sessions
on filtered_sessions.session_id = session_data.session_id
and filtered_sessions.website_id = session_data.website_id
${selectedPropertyName ? 'where session_data.data_key = {{selectedPropertyName}}' : ''}
)
select
data_key as "propertyName",
data_type as "dataType",
count(distinct session_data.session_id) as "total"
from website_event
${cohortQuery}
${joinSessionQuery}
from selected_property_sessions
join session_data
on session_data.session_id = website_event.session_id
and session_data.website_id = website_event.website_id
where website_event.website_id = {{websiteId::uuid}}
and website_event.created_at between {{startDate}} and {{endDate}}
${filterQuery}
group by 1
order by 2 desc
on session_data.session_id = selected_property_sessions.session_id
and session_data.website_id = selected_property_sessions.website_id
group by 1, 2
order by 3 desc, 1 asc
limit 500
`,
queryParams,
{ ...queryParams, websiteId, selectedPropertyName, ...pfParams },
FUNCTION_NAME,
);
}
@@ -47,29 +78,52 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
async function clickhouseQuery(
websiteId: string,
filters: QueryFilters,
): Promise<{ propertyName: string; total: number }[]> {
const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, websiteId });
propertyFilters: PropertyFilter[] = [],
selectedPropertyName?: string,
): Promise<{ propertyName: string; dataType: number; total: number }[]> {
const { timezone = 'UTC' } = filters;
const { rawQuery, parseFilters, getPropertyFilterQuery } = clickhouse;
const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, websiteId, timezone });
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(
propertyFilters,
'session',
timezone,
);
return rawQuery(
`
with filtered_sessions as (
select distinct website_event.session_id, website_event.website_id
from website_event
${cohortQuery}
where website_event.website_id = {websiteId:UUID}
and website_event.created_at between {startDate:DateTime64} and {endDate:DateTime64}
${filterQuery}
${pfSQL}
),
selected_property_sessions as (
select distinct session_data.session_id, session_data.website_id
from session_data final
join filtered_sessions
on filtered_sessions.session_id = session_data.session_id
and filtered_sessions.website_id = session_data.website_id
${selectedPropertyName ? 'where session_data.data_key = {selectedPropertyName:String}' : ''}
)
select
data_key as propertyName,
data_type as dataType,
count(distinct session_data.session_id) as total
from website_event
${cohortQuery}
from selected_property_sessions
join session_data final
on session_data.session_id = website_event.session_id
and session_data.website_id = {websiteId:UUID}
where website_event.website_id = {websiteId:UUID}
and website_event.created_at between {startDate:DateTime64} and {endDate:DateTime64}
on session_data.session_id = selected_property_sessions.session_id
and session_data.website_id = selected_property_sessions.website_id
where session_data.website_id = {websiteId:UUID}
and session_data.data_key != ''
${filterQuery}
group by 1
order by 2 desc
group by 1, 2
order by 3 desc, 1 asc
limit 500
`,
queryParams,
{ ...queryParams, websiteId, selectedPropertyName, ...pfParams },
FUNCTION_NAME,
);
}
@@ -0,0 +1,98 @@
import clickhouse from '@/lib/clickhouse';
import { DATA_TYPE } from '@/lib/constants';
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
import prisma from '@/lib/prisma';
import type { EventDataSeriesPoint, PropertyFilter, QueryFilters } from '@/lib/types';
const FUNCTION_NAME = 'getSessionDataPropertySeries';
export async function getSessionDataPropertySeries(
...args: [
websiteId: string,
propertyName: string,
filters: QueryFilters,
propertyFilters?: PropertyFilter[],
]
): Promise<EventDataSeriesPoint[]> {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
});
}
async function relationalQuery(
websiteId: string,
propertyName: string,
filters: QueryFilters,
propertyFilters: PropertyFilter[] = [],
): Promise<EventDataSeriesPoint[]> {
const { timezone = 'utc', unit = 'day' } = filters;
const { rawQuery, getDateSQL, parseFilters, getPropertyFilterQuery } = prisma;
const { filterQuery, cohortQuery, joinSessionQuery, queryParams } = parseFilters({
...filters,
websiteId,
timezone,
});
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(propertyFilters, 'session', timezone);
return rawQuery(
`
select
session_data.string_value as x,
${getDateSQL('session_data.created_at', unit, timezone)} t,
count(distinct session_data.session_id) y
from website_event
${cohortQuery}
${joinSessionQuery}
join session_data
on session_data.session_id = website_event.session_id
and session_data.website_id = website_event.website_id
where website_event.website_id = {{websiteId::uuid}}
and website_event.created_at between {{startDate}} and {{endDate}}
and session_data.data_key = {{propertyName}}
and session_data.data_type in (${DATA_TYPE.string}, ${DATA_TYPE.boolean})
${filterQuery}
${pfSQL}
group by 1, 2
order by 2
`,
{ ...queryParams, propertyName, ...pfParams },
FUNCTION_NAME,
);
}
async function clickhouseQuery(
websiteId: string,
propertyName: string,
filters: QueryFilters,
propertyFilters: PropertyFilter[] = [],
): Promise<EventDataSeriesPoint[]> {
const { timezone = 'UTC', unit = 'day' } = filters;
const { rawQuery, getDateSQL, parseFilters, getPropertyFilterQuery } = clickhouse;
const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, websiteId, timezone });
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(propertyFilters, 'session', timezone);
return rawQuery(
`
select
session_data.string_value as x,
${getDateSQL('session_data.created_at', unit, timezone)} as t,
uniq(session_data.session_id) as y
from website_event
${cohortQuery}
join session_data final
on session_data.session_id = website_event.session_id
and session_data.website_id = {websiteId:UUID}
where website_event.website_id = {websiteId:UUID}
and website_event.created_at between {startDate:DateTime64} and {endDate:DateTime64}
and session_data.data_key = {propertyName:String}
and session_data.data_type in (${DATA_TYPE.string}, ${DATA_TYPE.boolean})
${filterQuery}
${pfSQL}
group by x, t
order by t
`,
{ ...queryParams, propertyName, ...pfParams },
FUNCTION_NAME,
);
}
@@ -0,0 +1,185 @@
import clickhouse from '@/lib/clickhouse';
import { DATA_TYPE } from '@/lib/constants';
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
import prisma from '@/lib/prisma';
import type { PropertyFilter, PropertyLeaderboardRow, QueryFilters } from '@/lib/types';
const FUNCTION_NAME = 'getSessionDataStats';
export async function getSessionDataStats(
...args: [websiteId: string, propertyName: string, filters: QueryFilters, propertyFilters?: PropertyFilter[]]
): Promise<PropertyLeaderboardRow[]> {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
});
}
async function relationalQuery(
websiteId: string,
propertyName: string,
filters: QueryFilters,
propertyFilters: PropertyFilter[] = [],
) {
const { timezone = 'utc' } = filters;
const { rawQuery, parseFilters, getPropertyFilterQuery, getTimestampDiffSQL } = prisma;
const { filterQuery, cohortQuery, joinSessionQuery, queryParams } = parseFilters({
...filters,
websiteId,
timezone,
});
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(propertyFilters, 'session', timezone);
return rawQuery(
`
with filtered_sessions as (
select distinct website_event.session_id, website_event.website_id
from website_event
${cohortQuery}
${joinSessionQuery}
where website_event.website_id = {{websiteId::uuid}}
and website_event.created_at between {{startDate}} and {{endDate}}
${filterQuery}
${pfSQL}
),
session_rollup as (
select
website_event.session_id,
website_event.visit_id,
count(*) as activity,
sum(case when website_event.event_type = 1 then 1 else 0 end) as views,
sum(case when website_event.event_type = 2 then 1 else 0 end) as events,
min(website_event.created_at) as min_time,
max(website_event.created_at) as max_time
from website_event
join filtered_sessions
on filtered_sessions.session_id = website_event.session_id
and filtered_sessions.website_id = website_event.website_id
where website_event.website_id = {{websiteId::uuid}}
and website_event.created_at between {{startDate}} and {{endDate}}
group by website_event.session_id, website_event.visit_id
),
session_stats as (
select
session_id,
count(*) as visits,
sum(activity) as activity,
sum(views) as views,
sum(events) as events,
sum(${getTimestampDiffSQL('min_time', 'max_time')}) as totaltime
from session_rollup
group by session_id
),
property_values as (
select
session_data.session_id,
session_data.string_value as value
from session_data
join filtered_sessions
on filtered_sessions.session_id = session_data.session_id
and filtered_sessions.website_id = session_data.website_id
where session_data.website_id = {{websiteId::uuid}}
and session_data.data_key = {{propertyName}}
and session_data.data_type = ${DATA_TYPE.string}
and coalesce(session_data.string_value, '') != ''
)
select
property_values.value as label,
coalesce(sum(session_stats.activity), 0) as activity,
count(distinct property_values.session_id) as sessions,
coalesce(sum(session_stats.visits), 0) as visits,
coalesce(sum(session_stats.views), 0) as views,
coalesce(sum(session_stats.events), 0) as events,
coalesce(sum(session_stats.totaltime), 0) as totaltime,
coalesce(sum(case when session_stats.visits = 1 then 1 else 0 end), 0) as "newSessions",
coalesce(sum(case when session_stats.visits > 1 then 1 else 0 end), 0) as "returningSessions"
from property_values
join session_stats on session_stats.session_id = property_values.session_id
group by property_values.value
order by activity desc, property_values.value asc
limit 100
`,
{ ...queryParams, websiteId, propertyName, ...pfParams },
FUNCTION_NAME,
);
}
async function clickhouseQuery(
websiteId: string,
propertyName: string,
filters: QueryFilters,
propertyFilters: PropertyFilter[] = [],
) {
const { timezone = 'UTC' } = filters;
const { rawQuery, parseFilters, getPropertyFilterQuery } = clickhouse;
const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, websiteId, timezone });
const { sql: pfSQL, params: pfParams } = getPropertyFilterQuery(propertyFilters, 'session', timezone);
return rawQuery(
`
with filtered_sessions as (
select distinct website_event.session_id
from website_event
${cohortQuery}
where website_event.website_id = {websiteId:UUID}
and website_event.created_at between {startDate:DateTime64} and {endDate:DateTime64}
${filterQuery}
${pfSQL}
),
session_rollup as (
select
website_event.session_id,
website_event.visit_id,
count() as activity,
sumIf(1, website_event.event_type = 1) as views,
sumIf(1, website_event.event_type = 2) as events,
min(website_event.created_at) as min_time,
max(website_event.created_at) as max_time
from website_event
join filtered_sessions on filtered_sessions.session_id = website_event.session_id
where website_event.website_id = {websiteId:UUID}
and website_event.created_at between {startDate:DateTime64} and {endDate:DateTime64}
group by website_event.session_id, website_event.visit_id
),
session_stats as (
select
session_id,
count() as visits,
sum(activity) as activity,
sum(views) as views,
sum(events) as events,
sum(max_time - min_time) as totaltime
from session_rollup
group by session_id
),
property_values as (
select
session_data.session_id,
session_data.string_value as value
from session_data final
join filtered_sessions on filtered_sessions.session_id = session_data.session_id
where session_data.website_id = {websiteId:UUID}
and session_data.data_key = {propertyName:String}
and session_data.data_type = ${DATA_TYPE.string}
and ifNull(session_data.string_value, '') != ''
)
select
property_values.value as label,
ifNull(sum(session_stats.activity), 0) as activity,
uniq(property_values.session_id) as sessions,
ifNull(sum(session_stats.visits), 0) as visits,
ifNull(sum(session_stats.views), 0) as views,
ifNull(sum(session_stats.events), 0) as events,
ifNull(sum(session_stats.totaltime), 0) as totaltime,
ifNull(sum(if(session_stats.visits = 1, 1, 0)), 0) as newSessions,
ifNull(sum(if(session_stats.visits > 1, 1, 0)), 0) as returningSessions
from property_values
join session_stats on session_stats.session_id = property_values.session_id
group by property_values.value
order by activity desc, property_values.value asc
limit 100
`,
{ ...queryParams, websiteId, propertyName, ...pfParams },
FUNCTION_NAME,
);
}
@@ -1,4 +1,5 @@
import clickhouse from '@/lib/clickhouse';
import { DATA_TYPE } from '@/lib/constants';
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
import prisma from '@/lib/prisma';
import type { QueryFilters } from '@/lib/types';
@@ -6,7 +7,7 @@ import type { QueryFilters } from '@/lib/types';
const FUNCTION_NAME = 'getSessionDataValues';
export async function getSessionDataValues(
...args: [websiteId: string, filters: QueryFilters & { propertyName?: string }]
...args: [websiteId: string, filters: QueryFilters & { propertyName?: string; dataType?: number }]
) {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
@@ -16,14 +17,42 @@ export async function getSessionDataValues(
async function relationalQuery(
websiteId: string,
filters: QueryFilters & { propertyName?: string },
filters: QueryFilters & { propertyName?: string; dataType?: number },
) {
const { rawQuery, parseFilters, getDateSQL } = prisma;
const { dataType } = filters;
const { filterQuery, joinSessionQuery, cohortQuery, queryParams } = parseFilters({
...filters,
websiteId,
});
if (dataType === DATA_TYPE.array) {
return rawQuery(
`
select
array_item.value as "value",
count(distinct session_data.session_id) as "total"
from website_event
${cohortQuery}
${joinSessionQuery}
join session_data
on session_data.session_id = website_event.session_id
and session_data.website_id = website_event.website_id
cross join lateral jsonb_array_elements_text(coalesce(session_data.string_value, '[]')::jsonb) as array_item(value)
where website_event.website_id = {{websiteId::uuid}}
and website_event.created_at between {{startDate}} and {{endDate}}
and session_data.data_key = {{propertyName}}
and session_data.data_type = ${DATA_TYPE.array}
${filterQuery}
group by array_item.value
order by 2 desc
limit 100
`,
queryParams,
FUNCTION_NAME,
);
}
return rawQuery(
`
select
@@ -42,6 +71,7 @@ async function relationalQuery(
where website_event.website_id = {{websiteId::uuid}}
and website_event.created_at between {{startDate}} and {{endDate}}
and session_data.data_key = {{propertyName}}
${dataType ? `and session_data.data_type = ${dataType}` : ''}
${filterQuery}
group by value
order by 2 desc
@@ -54,11 +84,37 @@ async function relationalQuery(
async function clickhouseQuery(
websiteId: string,
filters: QueryFilters & { propertyName?: string },
filters: QueryFilters & { propertyName?: string; dataType?: number },
): Promise<{ propertyName: string; dataType: number; propertyValue: string; total: number }[]> {
const { rawQuery, parseFilters } = clickhouse;
const { dataType } = filters;
const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, websiteId });
if (dataType === DATA_TYPE.array) {
return rawQuery(
`
select
arrayJoin(JSONExtract(ifNull(session_data.string_value, '[]'), 'Array(String)')) as "value",
uniq(session_data.session_id) as "total"
from website_event
${cohortQuery}
join session_data final
on session_data.session_id = website_event.session_id
and session_data.website_id = {websiteId:UUID}
where website_event.website_id = {websiteId:UUID}
and website_event.created_at between {startDate:DateTime64} and {endDate:DateTime64}
and session_data.data_key = {propertyName:String}
and session_data.data_type = ${DATA_TYPE.array}
${filterQuery}
group by value
order by 2 desc
limit 100
`,
queryParams,
FUNCTION_NAME,
);
}
return rawQuery(
`
select
@@ -74,6 +130,7 @@ async function clickhouseQuery(
where website_event.website_id = {websiteId:UUID}
and website_event.created_at between {startDate:DateTime64} and {endDate:DateTime64}
and session_data.data_key = {propertyName:String}
${dataType ? `and session_data.data_type = ${dataType}` : ''}
${filterQuery}
group by value
order by 2 desc