sync umami

This commit is contained in:
Viet-Tien Ngoc 2024-08-26 13:51:45 +07:00
commit cc4b21a070
600 changed files with 10884 additions and 3381 deletions

View file

@ -22,9 +22,9 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
`
select
website_event.event_name as "eventName",
event_data.data_key as "fieldName",
event_data.data_key as "propertyName",
event_data.data_type as "dataType",
event_data.string_value as "fieldValue",
event_data.string_value as "propertyValue",
count(*) as "total"
from event_data
inner join website_event
@ -43,7 +43,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
`
select
website_event.event_name as "eventName",
event_data.data_key as "fieldName",
event_data.data_key as "propertyName",
event_data.data_type as "dataType",
count(*) as "total"
from event_data
@ -62,7 +62,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
async function clickhouseQuery(
websiteId: string,
filters: QueryFilters,
): Promise<{ eventName: string; fieldName: string; dataType: number; total: number }[]> {
): Promise<{ eventName: string; propertyName: string; dataType: number; total: number }[]> {
const { rawQuery, parseFilters } = clickhouse;
const { event } = filters;
const { params } = await parseFilters(websiteId, filters);
@ -72,9 +72,9 @@ async function clickhouseQuery(
`
select
event_name as eventName,
data_key as fieldName,
data_key as propertyName,
data_type as dataType,
string_value as fieldValue,
string_value as propertyValue,
count(*) as total
from event_data
where website_id = {websiteId:UUID}
@ -85,24 +85,14 @@ async function clickhouseQuery(
limit 500
`,
params,
).then(result => {
return Object.values(result).map((a: any) => {
return {
eventName: a.eventName,
fieldName: a.fieldName,
dataType: Number(a.dataType),
fieldValue: a.fieldValue,
total: Number(a.total),
};
});
});
);
}
return rawQuery(
`
select
event_name as eventName,
data_key as fieldName,
data_key as propertyName,
data_type as dataType,
count(*) as total
from event_data
@ -113,14 +103,5 @@ async function clickhouseQuery(
limit 500
`,
params,
).then(result => {
return Object.values(result).map((a: any) => {
return {
eventName: a.eventName,
fieldName: a.fieldName,
dataType: Number(a.dataType),
total: Number(a.total),
};
});
});
);
}

View file

@ -0,0 +1,68 @@
import prisma from 'lib/prisma';
import clickhouse from 'lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import { QueryFilters, WebsiteEventData } from 'lib/types';
export async function getEventDataProperties(
...args: [websiteId: string, filters: QueryFilters & { propertyName?: string }]
): Promise<WebsiteEventData[]> {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
});
}
async function relationalQuery(
websiteId: string,
filters: QueryFilters & { propertyName?: string },
) {
const { rawQuery, parseFilters } = prisma;
const { filterQuery, params } = await parseFilters(websiteId, filters, {
columns: { propertyName: 'data_key' },
});
return rawQuery(
`
select
website_event.event_name as "eventName",
event_data.data_key as "propertyName",
count(*) as "total"
from event_data
join website_event on website_event.event_id = event_data.website_event_id
where event_data.website_id = {{websiteId::uuid}}
and event_data.created_at between {{startDate}} and {{endDate}}
${filterQuery}
group by website_event.event_name, event_data.data_key
order by 3 desc
limit 500
`,
params,
);
}
async function clickhouseQuery(
websiteId: string,
filters: QueryFilters & { propertyName?: string },
): Promise<{ eventName: string; propertyName: string; total: number }[]> {
const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, filters, {
columns: { propertyName: 'data_key' },
});
return rawQuery(
`
select
event_name as eventName,
data_key as propertyName,
count(*) as total
from event_data
where website_id = {websiteId:UUID}
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
${filterQuery}
group by event_name, data_key
order by 1, 3 desc
limit 500
`,
params,
);
}

View file

@ -7,13 +7,13 @@ export async function getEventDataStats(
...args: [websiteId: string, filters: QueryFilters]
): Promise<{
events: number;
fields: number;
properties: number;
records: number;
}> {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
}).then(results => results[0]);
}).then(results => results?.[0]);
}
async function relationalQuery(websiteId: string, filters: QueryFilters) {
@ -24,7 +24,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
`
select
count(distinct t.website_event_id) as "events",
count(distinct t.data_key) as "fields",
count(distinct t.data_key) as "properties",
sum(t.total) as "records"
from (
select
@ -45,7 +45,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
async function clickhouseQuery(
websiteId: string,
filters: QueryFilters,
): Promise<{ events: number; fields: number; records: number }[]> {
): Promise<{ events: number; properties: number; records: number }[]> {
const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, filters);
@ -53,7 +53,7 @@ async function clickhouseQuery(
`
select
count(distinct t.event_id) as "events",
count(distinct t.data_key) as "fields",
count(distinct t.data_key) as "properties",
sum(t.total) as "records"
from (
select
@ -68,13 +68,5 @@ async function clickhouseQuery(
) as t
`,
params,
).then(result => {
return Object.values(result).map((a: any) => {
return {
events: Number(a.events),
fields: Number(a.fields),
records: Number(a.records),
};
});
});
);
}

View file

@ -30,9 +30,5 @@ function clickhouseQuery(
startDate,
endDate,
},
).then(result => {
return Object.values(result).map((a: any) => {
return { websiteId: a.websiteId, count: Number(a.count) };
});
});
);
}

View file

@ -0,0 +1,75 @@
import prisma from 'lib/prisma';
import clickhouse from 'lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import { QueryFilters, WebsiteEventData } from 'lib/types';
export async function getEventDataValues(
...args: [
websiteId: string,
filters: QueryFilters & { eventName?: string; propertyName?: string },
]
): Promise<WebsiteEventData[]> {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
});
}
async function relationalQuery(
websiteId: string,
filters: QueryFilters & { eventName?: string; propertyName?: string },
) {
const { rawQuery, parseFilters, getDateSQL } = prisma;
const { filterQuery, params } = await parseFilters(websiteId, filters);
return rawQuery(
`
select
case
when data_type = 2 then replace(string_value, '.0000', '')
when data_type = 4 then ${getDateSQL('date_value', 'hour')}
else string_value
end as "value",
count(*) as "total"
from event_data
join website_event on website_event.event_id = event_data.website_event_id
where event_data.website_id = {{websiteId::uuid}}
and event_data.created_at between {{startDate}} and {{endDate}}
and event_data.data_key = {{propertyName}}
and website_event.event_name = {{eventName}}
${filterQuery}
group by value
order by 2 desc
limit 100
`,
params,
);
}
async function clickhouseQuery(
websiteId: string,
filters: QueryFilters & { eventName?: string; propertyName?: string },
): Promise<{ propertyName: string; dataType: number; propertyValue: string; total: number }[]> {
const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, filters);
return rawQuery(
`
select
multiIf(data_type = 2, replaceAll(string_value, '.0000', ''),
data_type = 4, toString(date_trunc('hour', date_value)),
string_value) as "value",
count(*) as "total"
from event_data
where website_id = {websiteId:UUID}
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
and data_key = {propertyName:String}
and event_name = {eventName:String}
${filterQuery}
group by event_name, value
order by 2 desc
limit 100
`,
params,
);
}

View file

@ -1,8 +1,8 @@
import prisma from 'lib/prisma';
import clickhouse from 'lib/clickhouse';
import { runQuery, CLICKHOUSE, PRISMA } from 'lib/db';
import { WebsiteEventMetric, QueryFilters } from 'lib/types';
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 getEventMetrics(
...args: [websiteId: string, filters: QueryFilters]
@ -15,7 +15,7 @@ export async function getEventMetrics(
async function relationalQuery(websiteId: string, filters: QueryFilters) {
const { timezone = 'utc', unit = 'day' } = filters;
const { rawQuery, getDateQuery, parseFilters } = prisma;
const { rawQuery, getDateSQL, parseFilters } = prisma;
const { filterQuery, joinSession, params } = await parseFilters(websiteId, {
...filters,
eventType: EVENT_TYPE.customEvent,
@ -25,7 +25,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
`
select
event_name x,
${getDateQuery('website_event.created_at', unit, timezone)} t,
${getDateSQL('website_event.created_at', unit, timezone)} t,
count(*) y
from website_event
${joinSession}
@ -45,17 +45,19 @@ async function clickhouseQuery(
filters: QueryFilters,
): Promise<{ x: string; t: string; y: number }[]> {
const { timezone = 'UTC', unit = 'day' } = filters;
const { rawQuery, getDateQuery, parseFilters } = clickhouse;
const { rawQuery, getDateSQL, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, {
...filters,
eventType: EVENT_TYPE.customEvent,
});
return rawQuery(
`
let sql = '';
if (filterQuery) {
sql = `
select
event_name x,
${getDateQuery('created_at', unit, timezone)} t,
${getDateSQL('created_at', unit, timezone)} t,
count(*) y
from website_event
where website_id = {websiteId:UUID}
@ -64,9 +66,27 @@ async function clickhouseQuery(
${filterQuery}
group by x, t
order by t
`,
params,
).then(a => {
`;
} 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).then(a => {
return Object.values(a).map(a => {
return { x: a.x, t: a.t, y: Number(a.y) };
});

View file

@ -1,63 +0,0 @@
import clickhouse from 'lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import prisma from 'lib/prisma';
import { QueryFilters } from 'lib/types';
export function getEvents(...args: [websiteId: string, filters: QueryFilters]) {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
});
}
function relationalQuery(websiteId: string, filters: QueryFilters) {
const { startDate } = filters;
return prisma.client.websiteEvent
.findMany({
where: {
websiteId,
createdAt: {
gte: startDate,
},
},
orderBy: {
createdAt: 'desc',
},
})
.then(a => {
return Object.values(a).map(a => {
return {
...a,
timestamp: new Date(a.createdAt).getTime() / 1000,
};
});
});
}
function clickhouseQuery(websiteId: string, filters: QueryFilters) {
const { rawQuery } = clickhouse;
const { startDate } = filters;
return rawQuery(
`
select
event_id as id,
website_id as websiteId,
session_id as sessionId,
created_at as createdAt,
toUnixTimestamp(created_at) as timestamp,
url_path as urlPath,
referrer_domain as referrerDomain,
event_name as eventName
from website_event
where website_id = {websiteId:UUID}
and created_at >= {startDate:DateTime64}
order by created_at desc
`,
{
websiteId,
startDate,
},
);
}

View file

@ -0,0 +1,98 @@
import clickhouse from 'lib/clickhouse';
import { CLICKHOUSE, getDatabaseType, POSTGRESQL, PRISMA, runQuery } from 'lib/db';
import prisma from 'lib/prisma';
import { PageParams, QueryFilters } from 'lib/types';
export function getWebsiteEvents(
...args: [websiteId: string, filters: QueryFilters, pageParams?: PageParams]
) {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
});
}
async function relationalQuery(websiteId: string, filters: QueryFilters, pageParams?: PageParams) {
const { pagedRawQuery, parseFilters } = prisma;
const { query } = pageParams;
const { filterQuery, params } = await parseFilters(websiteId, {
...filters,
});
const db = getDatabaseType();
const like = db === POSTGRESQL ? 'ilike' : 'like';
return pagedRawQuery(
`
with events as (
select
event_id as "id",
website_id as "websiteId",
session_id as "sessionId",
created_at as "createdAt",
url_path as "urlPath",
url_query as "urlQuery",
referrer_path as "referrerPath",
referrer_query as "referrerQuery",
referrer_domain as "referrerDomain",
page_title as "pageTitle",
event_type as "eventType",
event_name as "eventName"
from website_event
where website_id = {{websiteId::uuid}}
and created_at between {{startDate}} and {{endDate}}
${filterQuery}
${
query
? `and ((event_name ${like} {{query}} and event_type = 2)
or (url_path ${like} {{query}} and event_type = 1))`
: ''
}
order by created_at desc
limit 1000)
select * from events
`,
{ ...params, query: `%${query}%` },
pageParams,
);
}
async function clickhouseQuery(websiteId: string, filters: QueryFilters, pageParams?: PageParams) {
const { pagedQuery, parseFilters } = clickhouse;
const { params, dateQuery, filterQuery } = await parseFilters(websiteId, filters);
const { query } = pageParams;
return pagedQuery(
`
with events as (
select
event_id as id,
website_id as websiteId,
session_id as sessionId,
created_at as createdAt,
url_path as urlPath,
url_query as urlQuery,
referrer_path as referrerPath,
referrer_query as referrerQuery,
referrer_domain as referrerDomain,
page_title as pageTitle,
event_type as eventType,
event_name as eventName
from website_event
where website_id = {websiteId:UUID}
${dateQuery}
${filterQuery}
${
query
? `and ((positionCaseInsensitive(event_name, {query:String}) > 0 and event_type = 2)
or (positionCaseInsensitive(url_path, {query:String}) > 0 and event_type = 1))`
: ''
}
order by created_at desc
limit 1000)
select * from events
`,
{ ...params, query },
pageParams,
);
}

View file

@ -1,9 +1,10 @@
import { EVENT_NAME_LENGTH, URL_LENGTH, EVENT_TYPE, PAGE_TITLE_LENGTH } from 'lib/constants';
import clickhouse from 'lib/clickhouse';
import { EVENT_NAME_LENGTH, EVENT_TYPE, PAGE_TITLE_LENGTH, URL_LENGTH } from 'lib/constants';
import { uuid } from 'lib/crypto';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import kafka from 'lib/kafka';
import prisma from 'lib/prisma';
import { uuid } from 'lib/crypto';
import { saveEventData } from 'queries/analytics/eventData/saveEventData';
import { saveEventData } from './saveEventData';
export async function saveEvent(args: {
websiteId: string;
@ -135,9 +136,10 @@ async function clickhouseQuery(data: {
city,
...args
} = data;
const { getDateFormat, sendMessage } = kafka;
const { insert, getUTCString } = clickhouse;
const { sendMessage } = kafka;
const eventId = uuid();
const createdAt = getDateFormat(new Date());
const createdAt = getUTCString();
const message = {
...args,
@ -165,7 +167,11 @@ async function clickhouseQuery(data: {
created_at: createdAt,
};
await sendMessage(message, 'event');
if (kafka.enabled) {
await sendMessage('event', message);
} else {
await insert('website_event', [message]);
}
if (eventData) {
await saveEventData({

View file

@ -1,8 +1,9 @@
import { Prisma } from '@prisma/client';
import clickhouse from 'lib/clickhouse';
import { DATA_TYPE } from 'lib/constants';
import { uuid } from 'lib/crypto';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import { flattenDynamicData, flattenJSON, getStringValue } from 'lib/data';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import kafka from 'lib/kafka';
import prisma from 'lib/prisma';
import { DynamicData } from 'lib/types';
@ -61,7 +62,8 @@ async function clickhouseQuery(data: {
}) {
const { websiteId, sessionId, visitId, eventId, urlPath, eventName, eventData, createdAt } = data;
const { getDateFormat, sendMessages, sendMessage } = kafka;
const { sendMessages, sendMessage } = kafka;
const { insert, getUTCString } = clickhouse;
const jsonKeys = flattenJSON(eventData);
@ -77,12 +79,16 @@ async function clickhouseQuery(data: {
data_type: dataType,
string_value: getStringValue(value, dataType),
number_value: dataType === DATA_TYPE.number ? value : null,
date_value: dataType === DATA_TYPE.date ? getDateFormat(value) : null,
created_at: createdAt ?? getDateFormat(new Date()),
date_value: dataType === DATA_TYPE.date ? getUTCString(value) : null,
created_at: createdAt ?? getUTCString(),
};
});
await sendMessages(messages, 'event_data');
if (kafka.enabled) {
await sendMessages('event_data', messages);
} else {
await insert('event_data', messages);
}
const jsonBlobs = flattenDynamicData(jsonKeys);
const message: { [key: string]: string | number } = {
@ -91,7 +97,7 @@ async function clickhouseQuery(data: {
event_id: eventId,
visit_id: visitId,
event_name: eventName,
created_at: createdAt ?? getDateFormat(new Date()),
created_at: createdAt ?? getUTCString(new Date()),
};
jsonBlobs.blobs.forEach((blob, i) => {
if (i >= 20) return; // 20 is the max number of blobs

View file

@ -18,9 +18,9 @@ async function relationalQuery(websiteId: string) {
select count(distinct session_id) x
from website_event
where website_id = {{websiteId::uuid}}
and created_at >= {{startAt}}
and created_at >= {{startDate}}
`,
{ websiteId, startAt: subMinutes(new Date(), 5) },
{ websiteId, startDate: subMinutes(new Date(), 5) },
);
return result[0] ?? null;
@ -35,14 +35,10 @@ async function clickhouseQuery(websiteId: string): Promise<{ x: number }> {
count(distinct session_id) x
from website_event
where website_id = {websiteId:UUID}
and created_at >= {startAt:DateTime64}
and created_at >= {startDate:DateTime64}
`,
{ websiteId, startAt: subMinutes(new Date(), 5) },
).then(a => {
return Object.values(a).map(a => {
return { x: Number(a.x) };
});
});
{ websiteId, startDate: subMinutes(new Date(), 5) },
);
return result[0] ?? null;
}

View file

@ -0,0 +1,67 @@
import prisma from 'lib/prisma';
import clickhouse from 'lib/clickhouse';
import { runQuery, CLICKHOUSE, PRISMA } from 'lib/db';
import { QueryFilters } from 'lib/types';
export async function getRealtimeActivity(...args: [websiteId: string, filters: QueryFilters]) {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
});
}
async function relationalQuery(websiteId: string, filters: QueryFilters) {
const { rawQuery, parseFilters } = prisma;
const { params, filterQuery, dateQuery } = await parseFilters(websiteId, filters);
return rawQuery(
`
select
website_event.session_id as "sessionId",
website_event.event_name as "eventName",
website_event.created_at as "createdAt",
session.browser,
session.os,
session.device,
session.country,
website_event.url_path as "urlPath",
website_event.referrer_domain as "referrerDomain"
from website_event
inner join session
on session.session_id = website_event.session_id
where website_event.website_id = {{websiteId::uuid}}
${filterQuery}
${dateQuery}
order by website_event.created_at asc
limit 100
`,
params,
);
}
async function clickhouseQuery(websiteId: string, filters: QueryFilters): Promise<{ x: number }> {
const { rawQuery, parseFilters } = clickhouse;
const { params, filterQuery, dateQuery } = await parseFilters(websiteId, filters);
return rawQuery(
`
select
session_id as sessionId,
event_name as eventName,
created_at as createdAt,
browser,
os,
device,
country,
url_path as urlPath,
referrer_domain as referrerDomain
from website_event
where website_id = {websiteId:UUID}
${filterQuery}
${dateQuery}
order by createdAt asc
limit 100
`,
params,
);
}

View file

@ -1,6 +1,4 @@
import { getSessions, getEvents, getPageviewStats, getSessionStats } from 'queries/index';
const MAX_SIZE = 50;
import { getPageviewStats, getRealtimeActivity, getSessionStats } from 'queries/index';
function increment(data: object, key: string) {
if (key) {
@ -18,55 +16,44 @@ export async function getRealtimeData(
) {
const { startDate, timezone } = criteria;
const filters = { startDate, endDate: new Date(), unit: 'minute', timezone };
const [events, sessions, pageviews, sessionviews] = await Promise.all([
getEvents(websiteId, { startDate }),
getSessions(websiteId, { startDate }),
const [activity, pageviews, sessions] = await Promise.all([
getRealtimeActivity(websiteId, filters),
getPageviewStats(websiteId, filters),
getSessionStats(websiteId, filters),
]);
const uniques = new Set();
const sessionStats = sessions.reduce(
(obj: { visitors: any; countries: any }, session: { id: any; country: any }) => {
const { countries, visitors } = obj;
const { id, country } = session;
const { countries, urls, referrers, events } = activity.reduce(
(
obj: { countries: any; urls: any; referrers: any; events: any },
event: {
sessionId: string;
urlPath: string;
referrerDomain: string;
country: string;
eventName: string;
},
) => {
const { countries, urls, referrers, events } = obj;
const { sessionId, urlPath, referrerDomain, country, eventName } = event;
if (!uniques.has(id)) {
uniques.add(id);
if (!uniques.has(sessionId)) {
uniques.add(sessionId);
increment(countries, country);
if (visitors.length < MAX_SIZE) {
visitors.push(session);
}
events.push({ __type: 'session', ...event });
}
increment(urls, urlPath);
increment(referrers, referrerDomain);
events.push({ __type: eventName ? 'event' : 'pageview', ...event });
return obj;
},
{
countries: {},
visitors: [],
},
);
const eventStats = events.reduce(
(
obj: { urls: any; referrers: any; events: any },
event: { urlPath: any; referrerDomain: any },
) => {
const { urls, referrers, events } = obj;
const { urlPath, referrerDomain } = event;
increment(urls, urlPath);
increment(referrers, referrerDomain);
if (events.length < MAX_SIZE) {
events.push(event);
}
return obj;
},
{
urls: {},
referrers: {},
events: [],
@ -74,17 +61,19 @@ export async function getRealtimeData(
);
return {
...sessionStats,
...eventStats,
countries,
urls,
referrers,
events: events.reverse(),
series: {
views: pageviews,
visitors: sessionviews,
visitors: sessions,
},
totals: {
views: events.filter(e => !e.eventName).length,
visitors: uniques.size,
events: events.filter(e => e.eventName).length,
countries: Object.keys(sessionStats.countries).length,
views: pageviews.reduce((sum: number, { y }: { y: number }) => Number(sum) + Number(y), 0),
visitors: sessions.reduce((sum: number, { y }: { y: number }) => Number(sum) + Number(y), 0),
events: activity.filter(e => e.eventName).length,
countries: Object.keys(countries).length,
},
timestamp: Date.now(),
};

View file

@ -18,11 +18,11 @@ async function relationalQuery(
endDate: Date,
search: string,
) {
const { rawQuery, getSearchQuery } = prisma;
const { rawQuery, getSearchSQL } = prisma;
let searchQuery = '';
if (search) {
searchQuery = getSearchQuery(column);
searchQuery = getSearchSQL(column);
}
return rawQuery(

View file

@ -3,6 +3,7 @@ import { EVENT_TYPE } from 'lib/constants';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import prisma from 'lib/prisma';
import { QueryFilters } from 'lib/types';
import { EVENT_COLUMNS } from 'lib/constants';
export async function getWebsiteStats(
...args: [websiteId: string, filters: QueryFilters]
@ -21,7 +22,7 @@ async function relationalQuery(
): Promise<
{ pageviews: number; visitors: number; visits: number; bounces: number; totaltime: number }[]
> {
const { getTimestampDiffQuery, parseFilters, rawQuery } = prisma;
const { getTimestampDiffSQL, parseFilters, rawQuery } = prisma;
const { filterQuery, joinSession, params } = await parseFilters(websiteId, {
...filters,
eventType: EVENT_TYPE.pageView,
@ -34,7 +35,7 @@ async function relationalQuery(
count(distinct t.session_id) as "visitors",
count(distinct t.visit_id) as "visits",
sum(case when t.c = 1 then 1 else 0 end) as "bounces",
sum(${getTimestampDiffQuery('t.min_time', 't.max_time')}) as "totaltime"
sum(${getTimestampDiffSQL('t.min_time', 't.max_time')}) as "totaltime"
from (
select
website_event.session_id,
@ -67,12 +68,14 @@ async function clickhouseQuery(
eventType: EVENT_TYPE.pageView,
});
return rawQuery(
`
select
let sql = '';
if (EVENT_COLUMNS.some(item => Object.keys(filters).includes(item))) {
sql = `
select
sum(t.c) as "pageviews",
count(distinct t.session_id) as "visitors",
count(distinct t.visit_id) as "visits",
uniq(t.session_id) as "visitors",
uniq(t.visit_id) as "visits",
sum(if(t.c = 1, 1, 0)) as "bounces",
sum(max_time-min_time) as "totaltime"
from (
@ -89,17 +92,30 @@ async function clickhouseQuery(
${filterQuery}
group by session_id, visit_id
) as t;
`,
params,
).then(result => {
return Object.values(result).map((a: any) => {
return {
pageviews: Number(a.pageviews),
visitors: Number(a.visitors),
visits: Number(a.visits),
bounces: Number(a.bounces),
totaltime: Number(a.totaltime),
};
});
});
`;
} else {
sql = `
select
sum(t.c) as "pageviews",
uniq(session_id) as "visitors",
uniq(visit_id) as "visits",
sumIf(1, t.c = 1) as "bounces",
sum(max_time-min_time) as "totaltime"
from (select
session_id,
visit_id,
sum(views) c,
min(min_time) min_time,
max(max_time) max_time
from umami.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}
${filterQuery}
group by session_id, visit_id
) as t;
`;
}
return rawQuery(sql, params);
}

View file

@ -1,5 +1,5 @@
import clickhouse from 'lib/clickhouse';
import { EVENT_TYPE, FILTER_COLUMNS, SESSION_COLUMNS } from 'lib/constants';
import { EVENT_COLUMNS, EVENT_TYPE, FILTER_COLUMNS, SESSION_COLUMNS } from 'lib/constants';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import prisma from 'lib/prisma';
import { QueryFilters } from 'lib/types';
@ -42,15 +42,18 @@ async function relationalQuery(
const aggregrate = type === 'entry' ? 'min' : 'max';
entryExitQuery = `
JOIN (select visit_id,
${aggregrate}(created_at) target_created_at
from website_event
where website_event.website_id = {{websiteId::uuid}}
and website_event.created_at between {{startDate}} and {{endDate}}
and event_type = {{eventType}}
group by visit_id) x
ON x.visit_id = website_event.visit_id
and x.target_created_at = website_event.created_at`;
join (
select visit_id,
${aggregrate}(created_at) target_created_at
from website_event
where website_event.website_id = {{websiteId::uuid}}
and website_event.created_at between {{startDate}} and {{endDate}}
and event_type = {{eventType}}
group by visit_id
) x
on x.visit_id = website_event.visit_id
and x.target_created_at = website_event.created_at
`;
}
return rawQuery(
@ -87,29 +90,32 @@ async function clickhouseQuery(
eventType: column === 'event_name' ? EVENT_TYPE.customEvent : EVENT_TYPE.pageView,
});
let entryExitQuery = '';
let excludeDomain = '';
if (column === 'referrer_domain') {
excludeDomain = `and referrer_domain != {websiteDomain:String} and referrer_domain != ''`;
}
let sql = '';
if (type === 'entry' || type === 'exit') {
const aggregrate = type === 'entry' ? 'min' : 'max';
if (EVENT_COLUMNS.some(item => Object.keys(filters).includes(item))) {
let entryExitQuery = '';
entryExitQuery = `
JOIN (select visit_id,
${aggregrate}(created_at) target_created_at
from website_event
where website_id = {websiteId:UUID}
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
and event_type = {eventType:UInt32}
group by visit_id) x
ON x.visit_id = website_event.visit_id
and x.target_created_at = website_event.created_at`;
}
if (column === 'referrer_domain') {
excludeDomain = `and referrer_domain != {websiteDomain:String} and referrer_domain != ''`;
}
return rawQuery(
`
if (type === 'entry' || type === 'exit') {
const aggregrate = type === 'entry' ? 'min' : 'max';
entryExitQuery = `
JOIN (select visit_id,
${aggregrate}(created_at) target_created_at
from website_event
where website_id = {websiteId:UUID}
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
and event_type = {eventType:UInt32}
group by visit_id) x
ON x.visit_id = website_event.visit_id
and x.target_created_at = website_event.created_at`;
}
sql = `
select ${column} x, count(*) y
from website_event
${entryExitQuery}
@ -122,9 +128,48 @@ async function clickhouseQuery(
order by y desc
limit ${limit}
offset ${offset}
`,
params,
).then((result: any) => {
`;
} else {
let groupByQuery = '';
if (column === 'referrer_domain') {
excludeDomain = `and t != {websiteDomain:String} and t != ''`;
}
let columnQuery = `arrayJoin(${column})`;
if (type === 'entry') {
columnQuery = `visit_id x, argMinMerge(entry_url)`;
}
if (type === 'exit') {
columnQuery = `visit_id x, argMaxMerge(exit_url)`;
}
if (type === 'entry' || type === 'exit') {
groupByQuery = 'group by x';
}
sql = `
select g.t as x,
count(*) as y
from (
select ${columnQuery} as t
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}
${excludeDomain}
${filterQuery}
${groupByQuery}) as g
group by x
order by y desc
limit ${limit}
offset ${offset}
`;
}
return rawQuery(sql, params).then((result: any) => {
return Object.values(result).map((a: any) => {
return { x: a.x, y: Number(a.y) };
});

View file

@ -1,7 +1,7 @@
import clickhouse from 'lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import prisma from 'lib/prisma';
import { EVENT_TYPE } from 'lib/constants';
import { EVENT_COLUMNS, EVENT_TYPE } from 'lib/constants';
import { QueryFilters } from 'lib/types';
export async function getPageviewStats(...args: [websiteId: string, filters: QueryFilters]) {
@ -13,7 +13,7 @@ export async function getPageviewStats(...args: [websiteId: string, filters: Que
async function relationalQuery(websiteId: string, filters: QueryFilters) {
const { timezone = 'utc', unit = 'day' } = filters;
const { getDateQuery, parseFilters, rawQuery } = prisma;
const { getDateSQL, parseFilters, rawQuery } = prisma;
const { filterQuery, joinSession, params } = await parseFilters(websiteId, {
...filters,
eventType: EVENT_TYPE.pageView,
@ -22,7 +22,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
return rawQuery(
`
select
${getDateQuery('website_event.created_at', unit, timezone)} x,
${getDateSQL('website_event.created_at', unit, timezone)} x,
count(*) y
from website_event
${joinSession}
@ -31,6 +31,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
and event_type = {{eventType}}
${filterQuery}
group by 1
order by 1
`,
params,
);
@ -40,21 +41,23 @@ async function clickhouseQuery(
websiteId: string,
filters: QueryFilters,
): Promise<{ x: string; y: number }[]> {
const { timezone = 'UTC', unit = 'day' } = filters;
const { parseFilters, rawQuery, getDateStringQuery, getDateQuery } = clickhouse;
const { unit = 'day' } = filters;
const { parseFilters, rawQuery } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, {
...filters,
eventType: EVENT_TYPE.pageView,
});
return rawQuery(
`
let sql = '';
if (EVENT_COLUMNS.some(item => Object.keys(filters).includes(item)) || unit === 'minute') {
sql = `
select
${getDateStringQuery('g.t', unit)} as x,
g.t as x,
g.y as y
from (
select
${getDateQuery('created_at', unit, timezone)} as t,
select
date_trunc('${unit}', created_at) as t,
count(*) as y
from website_event
where website_id = {websiteId:UUID}
@ -64,11 +67,26 @@ async function clickhouseQuery(
group by t
) as g
order by t
`,
params,
).then(result => {
return Object.values(result).map((a: any) => {
return { x: a.x, y: Number(a.y) };
});
});
`;
} else {
sql = `
select
g.t as x,
g.y as y
from (
select
date_trunc('${unit}', created_at) as t,
sum(views)as y
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}
${filterQuery}
group by t
) as g
order by t
`;
}
return rawQuery(sql, params);
}

View file

@ -23,7 +23,7 @@ async function relationalQuery(
y: number;
}[]
> {
const { getTimestampDiffQuery, parseFilters, rawQuery } = prisma;
const { getTimestampDiffSQL, parseFilters, rawQuery } = prisma;
const { filterQuery, joinSession, params } = await parseFilters(
websiteId,
{
@ -42,7 +42,7 @@ async function relationalQuery(
count(distinct t.session_id) as "visitors",
count(distinct t.visit_id) as "visits",
sum(case when t.c = 1 then 1 else 0 end) as "bounces",
sum(${getTimestampDiffQuery('t.min_time', 't.max_time')}) as "totaltime",
sum(${getTimestampDiffSQL('t.min_time', 't.max_time')}) as "totaltime",
${parseFieldsByName(fields)}
from (
select

View file

@ -35,14 +35,14 @@ async function relationalQuery(
}[]
> {
const { startDate, endDate, timezone = 'UTC' } = filters;
const { getDateQuery, getDayDiffQuery, getCastColumnQuery, rawQuery } = prisma;
const { getDateSQL, getDayDiffQuery, getCastColumnQuery, rawQuery } = prisma;
const unit = 'day';
return rawQuery(
`
WITH cohort_items AS (
select session_id,
${getDateQuery('created_at', unit, timezone)} as cohort_date
${getDateSQL('created_at', unit, timezone)} as cohort_date
from session
where website_id = {{websiteId::uuid}}
and created_at between {{startDate}} and {{endDate}}
@ -50,10 +50,7 @@ async function relationalQuery(
user_activities AS (
select distinct
w.session_id,
${getDayDiffQuery(
getDateQuery('created_at', unit, timezone),
'c.cohort_date',
)} as day_number
${getDayDiffQuery(getDateSQL('created_at', unit, timezone), 'c.cohort_date')} as day_number
from website_event w
join cohort_items c
on w.session_id = c.session_id
@ -93,9 +90,7 @@ async function relationalQuery(
startDate,
endDate,
},
).then(results => {
return results.map(i => ({ ...i, percentage: Number(i.percentage) || 0 }));
});
);
}
async function clickhouseQuery(
@ -115,14 +110,14 @@ async function clickhouseQuery(
}[]
> {
const { startDate, endDate, timezone = 'UTC' } = filters;
const { getDateQuery, getDateStringQuery, rawQuery } = clickhouse;
const { getDateSQL, getDateStringSQL, rawQuery } = clickhouse;
const unit = 'day';
return rawQuery(
`
WITH cohort_items AS (
select
min(${getDateQuery('created_at', unit, timezone)}) as cohort_date,
min(${getDateSQL('created_at', unit, timezone)}) as cohort_date,
session_id
from website_event
where website_id = {websiteId:UUID}
@ -132,7 +127,7 @@ async function clickhouseQuery(
user_activities AS (
select distinct
w.session_id,
(${getDateQuery('created_at', unit, timezone)} - c.cohort_date) / 86400 as day_number
(${getDateSQL('created_at', unit, timezone)} - c.cohort_date) / 86400 as day_number
from website_event w
join cohort_items c
on w.session_id = c.session_id
@ -157,7 +152,7 @@ async function clickhouseQuery(
group by 1, 2
)
select
${getDateStringQuery('c.cohort_date', unit)} as date,
${getDateStringSQL('c.cohort_date', unit)} as date,
c.day_number as day,
s.visitors as visitors,
c.visitors returnVisitors,
@ -172,15 +167,5 @@ async function clickhouseQuery(
startDate,
endDate,
},
).then(result => {
return Object.values(result).map((a: any) => {
return {
date: a.date,
day: Number(a.day),
visitors: Number(a.visitors),
returnVisitors: Number(a.returnVisitors),
percentage: Number(a.percentage),
};
});
});
);
}

View file

@ -46,12 +46,12 @@ async function relationalQuery(
timezone = 'UTC',
unit = 'day',
} = criteria;
const { getDateQuery, rawQuery } = prisma;
const { getDateSQL, rawQuery } = prisma;
const chartRes = await rawQuery(
`
select
${getDateQuery('website_event.created_at', unit, timezone)} time,
${getDateSQL('website_event.created_at', unit, timezone)} time,
sum(case when data_key = {{revenueProperty}} then number_value else 0 end) sum,
avg(case when data_key = {{revenueProperty}} then number_value else 0 end) avg,
count(case when data_key = {{revenueProperty}} then 1 else 0 end) count,
@ -110,7 +110,7 @@ async function clickhouseQuery(
timezone = 'UTC',
unit = 'day',
} = criteria;
const { getDateStringQuery, getDateQuery, rawQuery } = clickhouse;
const { getDateStringSQL, getDateSQL, rawQuery } = clickhouse;
const chartRes = await rawQuery<{
time: string;
@ -121,14 +121,14 @@ async function clickhouseQuery(
}>(
`
select
${getDateStringQuery('g.time', unit)} as time,
${getDateStringSQL('g.time', unit)} as time,
g.sum as sum,
g.avg as avg,
g.count as count,
g.uniqueCount as uniqueCount
from (
select
${getDateQuery('created_at', unit, timezone)} as time,
${getDateSQL('created_at', unit, timezone)} as time,
sumIf(number_value, data_key = {revenueProperty:String}) as sum,
avgIf(number_value, data_key = {revenueProperty:String}) as avg,
countIf(data_key = {revenueProperty:String}) as count,
@ -143,17 +143,7 @@ async function clickhouseQuery(
order by time
`,
{ websiteId, startDate, endDate, eventName, revenueProperty, userProperty },
).then(result => {
return Object.values(result).map((a: any) => {
return {
time: a.time,
sum: Number(a.sum),
avg: Number(a.avg),
count: Number(a.count),
uniqueCount: Number(!a.avg ? 0 : a.uniqueCount),
};
});
});
).then(result => result?.[0]);
const totalRes = await rawQuery<{
sum: number;
@ -174,16 +164,7 @@ async function clickhouseQuery(
and data_key in ({revenueProperty:String}, {userProperty:String})
`,
{ websiteId, startDate, endDate, eventName, revenueProperty, userProperty },
).then(results => {
const result = results[0];
return {
sum: Number(result.sum),
avg: Number(result.avg),
count: Number(result.count),
uniqueCount: Number(!result.avg ? 0 : result.uniqueCount),
};
});
);
return { chart: chartRes, total: totalRes };
}

View file

@ -1,9 +0,0 @@
import prisma from 'lib/prisma';
export async function getSession(id: string) {
return prisma.client.session.findUnique({
where: {
id,
},
});
}

View file

@ -0,0 +1,59 @@
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, startDate: Date, endDate: Date]
) {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
});
}
async function relationalQuery(
websiteId: string,
sessionId: string,
startDate: Date,
endDate: Date,
) {
return prisma.client.websiteEvent.findMany({
where: {
sessionId,
websiteId,
createdAt: { gte: startDate, lte: endDate },
},
take: 500,
orderBy: { createdAt: 'desc' },
});
}
async function clickhouseQuery(
websiteId: string,
sessionId: string,
startDate: Date,
endDate: Date,
) {
const { rawQuery } = clickhouse;
return rawQuery(
`
select
created_at as createdAt,
url_path as urlPath,
url_query as urlQuery,
referrer_domain as referrerDomain,
event_id as eventId,
event_type as eventType,
event_name as eventName,
visit_id as visitId
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, startDate, endDate },
);
}

View file

@ -0,0 +1,56 @@
import prisma from 'lib/prisma';
import clickhouse from 'lib/clickhouse';
import { runQuery, PRISMA, CLICKHOUSE } from 'lib/db';
export async function getSessionData(...args: [websiteId: string, sessionId: string]) {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
});
}
async function relationalQuery(websiteId: string, sessionId: string) {
const { rawQuery } = prisma;
return rawQuery(
`
select
website_id as "websiteId",
session_id as "sessionId",
data_key as "dataKey",
data_type as "dataType",
replace(string_value, '.0000', '') as "stringValue",
number_value as "numberValue",
date_value as "dateValue",
created_at as "createdAt"
from session_data
where website_id = {{websiteId::uuid}}
and session_id = {{sessionId::uuid}}
order by data_key asc
`,
{ websiteId, sessionId },
);
}
async function clickhouseQuery(websiteId: string, sessionId: string) {
const { rawQuery } = clickhouse;
return rawQuery(
`
select
website_id as websiteId,
session_id as sessionId,
data_key as dataKey,
data_type as dataType,
replace(string_value, '.0000', '') as stringValue,
number_value as numberValue,
date_value as dateValue,
created_at as createdAt
from session_data final
where website_id = {websiteId:UUID}
and session_id = {sessionId:UUID}
order by data_key asc
`,
{ websiteId, sessionId },
);
}

View file

@ -3,8 +3,8 @@ import clickhouse from 'lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import { QueryFilters, WebsiteEventData } from 'lib/types';
export async function getEventDataFields(
...args: [websiteId: string, filters: QueryFilters & { field?: string }]
export async function getSessionDataProperties(
...args: [websiteId: string, filters: QueryFilters & { propertyName?: string }]
): Promise<WebsiteEventData[]> {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
@ -12,25 +12,26 @@ export async function getEventDataFields(
});
}
async function relationalQuery(websiteId: string, filters: QueryFilters & { field?: string }) {
async function relationalQuery(
websiteId: string,
filters: QueryFilters & { propertyName?: string },
) {
const { rawQuery, parseFilters } = prisma;
const { filterQuery, params } = await parseFilters(websiteId, filters, {
columns: { field: 'data_key' },
columns: { propertyName: 'data_key' },
});
return rawQuery(
`
select
data_key as "fieldName",
data_type as "dataType",
string_value as "fieldValue",
data_key as "propertyName",
count(*) as "total"
from event_data
from session_data
where website_id = {{websiteId::uuid}}
and created_at between {{startDate}} and {{endDate}}
${filterQuery}
group by data_key, data_type, string_value
order by 3 desc, 2 desc, 1 asc
group by data_key
order by 2 desc
limit 500
`,
params,
@ -39,37 +40,26 @@ async function relationalQuery(websiteId: string, filters: QueryFilters & { fiel
async function clickhouseQuery(
websiteId: string,
filters: QueryFilters & { field?: string },
): Promise<{ fieldName: string; dataType: number; fieldValue: string; total: number }[]> {
filters: QueryFilters & { propertyName?: string },
): Promise<{ propertyName: string; total: number }[]> {
const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, filters, {
columns: { field: 'data_key' },
columns: { propertyName: 'data_key' },
});
return rawQuery(
`
select
data_key as fieldName,
data_type as dataType,
string_value as fieldValue,
data_key as propertyName,
count(*) as total
from event_data
from session_data final
where website_id = {websiteId:UUID}
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
${filterQuery}
group by data_key, data_type, string_value
order by 3 desc, 2 desc, 1 asc
group by data_key
order by 2 desc
limit 500
`,
params,
).then(result => {
return Object.values(result).map((a: any) => {
return {
fieldName: a.fieldName,
dataType: Number(a.dataType),
fieldValue: a.fieldValue,
total: Number(a.total),
};
});
});
);
}

View file

@ -0,0 +1,69 @@
import prisma from 'lib/prisma';
import clickhouse from 'lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import { QueryFilters, WebsiteEventData } from 'lib/types';
export async function getSessionDataValues(
...args: [websiteId: string, filters: QueryFilters & { propertyName?: string }]
): Promise<WebsiteEventData[]> {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
});
}
async function relationalQuery(
websiteId: string,
filters: QueryFilters & { propertyName?: string },
) {
const { rawQuery, parseFilters, getDateSQL } = prisma;
const { filterQuery, params } = await parseFilters(websiteId, filters);
return rawQuery(
`
select
case
when data_type = 2 then replace(string_value, '.0000', '')
when data_type = 4 then ${getDateSQL('date_value', 'hour')}
else string_value
end as "value",
count(*) as "total"
from session_data
where website_id = {{websiteId::uuid}}
and created_at between {{startDate}} and {{endDate}}
and data_key = {{propertyName}}
${filterQuery}
group by value
order by 2 desc
limit 100
`,
params,
);
}
async function clickhouseQuery(
websiteId: string,
filters: QueryFilters & { propertyName?: string },
): Promise<{ propertyName: string; dataType: number; propertyValue: string; total: number }[]> {
const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, filters);
return rawQuery(
`
select
multiIf(data_type = 2, replaceAll(string_value, '.0000', ''),
data_type = 4, toString(date_trunc('hour', date_value)),
string_value) as "value",
count(*) as "total"
from session_data final
where website_id = {websiteId:UUID}
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
and data_key = {propertyName:String}
${filterQuery}
group by value
order by 2 desc
limit 100
`,
params,
);
}

View file

@ -1,7 +1,7 @@
import prisma from 'lib/prisma';
import clickhouse from 'lib/clickhouse';
import { runQuery, CLICKHOUSE, PRISMA } from 'lib/db';
import { EVENT_TYPE, FILTER_COLUMNS, SESSION_COLUMNS } from 'lib/constants';
import { EVENT_COLUMNS, EVENT_TYPE, FILTER_COLUMNS, SESSION_COLUMNS } from 'lib/constants';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import prisma from 'lib/prisma';
import { QueryFilters } from 'lib/types';
export async function getSessionMetrics(
@ -71,8 +71,10 @@ async function clickhouseQuery(
});
const includeCountry = column === 'city' || column === 'subdivision1';
return rawQuery(
`
let sql = '';
if (EVENT_COLUMNS.some(item => Object.keys(filters).includes(item))) {
sql = `
select
${column} x,
count(distinct session_id) y
@ -87,9 +89,27 @@ async function clickhouseQuery(
order by y desc
limit ${limit}
offset ${offset}
`,
params,
).then(a => {
`;
} else {
sql = `
select
${column} x,
uniq(session_id) y
${includeCountry ? ', country' : ''}
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}
${filterQuery}
group by x
${includeCountry ? ', country' : ''}
order by y desc
limit ${limit}
offset ${offset}
`;
}
return rawQuery(sql, params).then(a => {
return Object.values(a).map(a => {
return { x: a.x, y: Number(a.y), country: a.country };
});

View file

@ -1,7 +1,7 @@
import clickhouse from 'lib/clickhouse';
import { EVENT_COLUMNS, EVENT_TYPE } from 'lib/constants';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import prisma from 'lib/prisma';
import { EVENT_TYPE } from 'lib/constants';
import { QueryFilters } from 'lib/types';
export async function getSessionStats(...args: [websiteId: string, filters: QueryFilters]) {
@ -13,7 +13,7 @@ export async function getSessionStats(...args: [websiteId: string, filters: Quer
async function relationalQuery(websiteId: string, filters: QueryFilters) {
const { timezone = 'utc', unit = 'day' } = filters;
const { getDateQuery, parseFilters, rawQuery } = prisma;
const { getDateSQL, parseFilters, rawQuery } = prisma;
const { filterQuery, joinSession, params } = await parseFilters(websiteId, {
...filters,
eventType: EVENT_TYPE.pageView,
@ -22,7 +22,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
return rawQuery(
`
select
${getDateQuery('website_event.created_at', unit, timezone)} x,
${getDateSQL('website_event.created_at', unit, timezone)} x,
count(distinct website_event.session_id) y
from website_event
${joinSession}
@ -31,6 +31,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
and event_type = {{eventType}}
${filterQuery}
group by 1
order by 1
`,
params,
);
@ -40,21 +41,23 @@ async function clickhouseQuery(
websiteId: string,
filters: QueryFilters,
): Promise<{ x: string; y: number }[]> {
const { timezone = 'UTC', unit = 'day' } = filters;
const { parseFilters, rawQuery, getDateStringQuery, getDateQuery } = clickhouse;
const { unit = 'day' } = filters;
const { parseFilters, rawQuery } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, {
...filters,
eventType: EVENT_TYPE.pageView,
});
return rawQuery(
`
let sql = '';
if (EVENT_COLUMNS.some(item => Object.keys(filters).includes(item)) || unit === 'minute') {
sql = `
select
${getDateStringQuery('g.t', unit)} as x,
g.t as x,
g.y as y
from (
select
${getDateQuery('created_at', unit, timezone)} as t,
select
date_trunc('${unit}', created_at) as t,
count(distinct session_id) as y
from website_event
where website_id = {websiteId:UUID}
@ -64,11 +67,26 @@ async function clickhouseQuery(
group by t
) as g
order by t
`,
params,
).then(result => {
return Object.values(result).map((a: any) => {
return { x: a.x, y: Number(a.y) };
});
});
`;
} else {
sql = `
select
g.t as x,
g.y as y
from (
select
date_trunc('${unit}', created_at) as t,
uniq(session_id) as y
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}
${filterQuery}
group by t
) as g
order by t
`;
}
return rawQuery(sql, params);
}

View file

@ -1,69 +0,0 @@
import prisma from 'lib/prisma';
import clickhouse from 'lib/clickhouse';
import { runQuery, PRISMA, CLICKHOUSE } from 'lib/db';
import { QueryFilters } from 'lib/types';
export async function getSessions(...args: [websiteId: string, filters: QueryFilters]) {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
});
}
async function relationalQuery(websiteId: string, filters: QueryFilters) {
const { startDate } = filters;
return prisma.client.session
.findMany({
where: {
websiteId,
createdAt: {
gte: startDate,
},
},
orderBy: {
createdAt: 'desc',
},
})
.then(a => {
return Object.values(a).map(a => {
return {
...a,
timestamp: new Date(a.createdAt).getTime() / 1000,
};
});
});
}
async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
const { rawQuery } = clickhouse;
const { startDate } = filters;
return rawQuery(
`
select
session_id as id,
website_id as websiteId,
created_at as createdAt,
toUnixTimestamp(created_at) as timestamp,
hostname,
browser,
os,
device,
screen,
language,
country,
subdivision1,
subdivision2,
city
from website_event
where website_id = {websiteId:UUID}
and created_at >= {startDate:DateTime64}
order by created_at desc
`,
{
websiteId,
startDate,
},
);
}

View file

@ -0,0 +1,109 @@
import prisma from 'lib/prisma';
import clickhouse from 'lib/clickhouse';
import { runQuery, PRISMA, CLICKHOUSE } from 'lib/db';
export async function getWebsiteSession(...args: [websiteId: string, sessionId: string]) {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
});
}
async function relationalQuery(websiteId: string, sessionId: string) {
const { rawQuery, getTimestampDiffSQL } = prisma;
return rawQuery(
`
select id,
website_id as "websiteId",
hostname,
browser,
os,
device,
screen,
language,
country,
subdivision1,
city,
min(min_time) as "firstAt",
max(max_time) as "lastAt",
count(distinct visit_id) as visits,
sum(views) as views,
sum(events) as events,
sum(${getTimestampDiffSQL('min_time', 'max_time')}) as "totaltime"
from (select
session.session_id as id,
website_event.visit_id,
session.website_id,
session.hostname,
session.browser,
session.os,
session.device,
session.screen,
session.language,
session.country,
session.subdivision1,
session.city,
min(website_event.created_at) as min_time,
max(website_event.created_at) as max_time,
sum(case when website_event.event_type = 1 then 1 else 0 end) as views,
sum(case when website_event.event_type = 1 then 1 else 0 end) as events
from session
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, session.hostname, session.browser, session.os, session.device, session.screen, session.language, session.country, session.subdivision1, session.city) t
group by id, website_id, hostname, browser, os, device, screen, language, country, subdivision1, city;
`,
{ websiteId, sessionId },
).then(result => result?.[0]);
}
async function clickhouseQuery(websiteId: string, sessionId: string) {
const { rawQuery, getDateStringSQL } = clickhouse;
return rawQuery(
`
select id,
websiteId,
hostname,
browser,
os,
device,
screen,
language,
country,
subdivision1,
city,
${getDateStringSQL('min(min_time)')} as firstAt,
${getDateStringSQL('max(max_time)')} as lastAt,
uniq(visit_id) visits,
sum(views) as views,
sum(events) as events,
sum(max_time-min_time) as totaltime
from (select
session_id as id,
visit_id,
website_id as websiteId,
hostname,
browser,
os,
device,
screen,
language,
country,
subdivision1,
city,
min(min_time) as min_time,
max(max_time) as max_time,
sum(views) as views,
length(groupArrayArray(event_name)) as events
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, subdivision1, city) t
group by id, websiteId, hostname, browser, os, device, screen, language, country, subdivision1, city;
`,
{ websiteId, sessionId },
).then(result => result?.[0]);
}

View file

@ -0,0 +1,72 @@
import clickhouse from 'lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import prisma from 'lib/prisma';
import { QueryFilters } from 'lib/types';
export async function getWebsiteSessionStats(
...args: [websiteId: string, filters: QueryFilters]
): Promise<
{ pageviews: number; visitors: number; visits: number; countries: number; events: number }[]
> {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
});
}
async function relationalQuery(
websiteId: string,
filters: QueryFilters,
): Promise<
{ pageviews: number; visitors: number; visits: number; countries: number; events: number }[]
> {
const { parseFilters, rawQuery } = prisma;
const { filterQuery, params } = await parseFilters(websiteId, {
...filters,
});
return rawQuery(
`
select
count(*) as "pageviews",
count(distinct website_event.session_id) as "visitors",
count(distinct website_event.visit_id) as "visits",
count(distinct session.country) as "countries",
sum(case when website_event.event_type = 2 then 1 else 0 end) as "events"
from website_event
join session on website_event.session_id = session.session_id
where website_event.website_id = {{websiteId::uuid}}
and website_event.created_at between {{startDate}} and {{endDate}}
${filterQuery}
`,
params,
);
}
async function clickhouseQuery(
websiteId: string,
filters: QueryFilters,
): Promise<
{ pageviews: number; visitors: number; visits: number; countries: number; events: number }[]
> {
const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, {
...filters,
});
return rawQuery(
`
select
sum(views) as "pageviews",
uniq(session_id) as "visitors",
uniq(visit_id) as "visits",
uniq(country) as "countries",
sum(length(event_name)) as "events"
from umami.website_event_stats_hourly "website_event"
where website_id = {websiteId:UUID}
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
${filterQuery}
`,
params,
);
}

View file

@ -0,0 +1,102 @@
import clickhouse from 'lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import prisma from 'lib/prisma';
import { PageParams, QueryFilters } from 'lib/types';
export async function getWebsiteSessions(
...args: [websiteId: string, filters?: QueryFilters, pageParams?: PageParams]
) {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
});
}
async function relationalQuery(websiteId: string, filters: QueryFilters, pageParams: PageParams) {
const { pagedRawQuery, parseFilters } = prisma;
const { filterQuery, params } = await parseFilters(websiteId, {
...filters,
});
return pagedRawQuery(
`
with sessions as (
select
session.session_id as "id",
session.website_id as "websiteId",
session.hostname,
session.browser,
session.os,
session.device,
session.screen,
session.language,
session.country,
session.subdivision1,
session.city,
min(website_event.created_at) as "firstAt",
max(website_event.created_at) as "lastAt",
count(distinct website_event.visit_id) as "visits",
sum(case when website_event.event_type = 1 then 1 else 0 end) as "views",
max(website_event.created_at) as "createdAt"
from website_event
join session on session.session_id = website_event.session_id
where website_event.website_id = {{websiteId::uuid}}
and website_event.created_at between {{startDate}} and {{endDate}}
${filterQuery}
group by session.session_id,
session.website_id,
session.hostname,
session.browser,
session.os,
session.device,
session.screen,
session.language,
session.country,
session.subdivision1,
session.city
order by max(website_event.created_at) desc
limit 1000)
select * from sessions
`,
params,
pageParams,
);
}
async function clickhouseQuery(websiteId: string, filters: QueryFilters, pageParams?: PageParams) {
const { pagedQuery, parseFilters, getDateStringSQL } = clickhouse;
const { params, dateQuery, filterQuery } = await parseFilters(websiteId, filters);
return pagedQuery(
`
with sessions as (
select
session_id as id,
website_id as websiteId,
hostname,
browser,
os,
device,
screen,
language,
country,
subdivision1,
city,
${getDateStringSQL('min(min_time)')} as firstAt,
${getDateStringSQL('max(max_time)')} as lastAt,
uniq(visit_id) as visits,
sumIf(views, event_type = 1) as views,
lastAt as createdAt
from website_event_stats_hourly
where website_id = {websiteId:UUID}
${dateQuery}
${filterQuery}
group by session_id, website_id, hostname, browser, os, device, screen, language, country, subdivision1, city
order by lastAt desc
limit 1000)
select * from sessions
`,
params,
pageParams,
);
}

View file

@ -0,0 +1,71 @@
import prisma from 'lib/prisma';
import clickhouse from 'lib/clickhouse';
import { runQuery, PRISMA, CLICKHOUSE } from 'lib/db';
import { QueryFilters } from 'lib/types';
export async function getWebsiteSessionsWeekly(
...args: [websiteId: string, filters?: QueryFilters]
) {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
});
}
async function relationalQuery(websiteId: string, filters: QueryFilters) {
const { timezone = 'utc' } = filters;
const { rawQuery, getDateWeeklySQL, parseFilters } = prisma;
const { params } = await parseFilters(websiteId, filters);
return rawQuery(
`
select
${getDateWeeklySQL('created_at', timezone)} as time,
count(distinct session_id) as value
from website_event
where website_id = {{websiteId::uuid}}
and created_at between {{startDate}} and {{endDate}}
group by time
order by 2
`,
params,
).then(formatResults);
}
async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
const { timezone = 'utc' } = filters;
const { rawQuery } = clickhouse;
const { startDate, endDate } = filters;
return rawQuery(
`
select
formatDateTime(toDateTime(created_at, '${timezone}'), '%w:%H') as time,
count(distinct session_id) as value
from website_event_stats_hourly
where website_id = {websiteId:UUID}
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
group by time
order by time
`,
{ websiteId, startDate, endDate },
).then(formatResults);
}
function formatResults(data: any) {
const days = [];
for (let i = 0; i < 7; i++) {
days.push([]);
for (let j = 0; j < 24; j++) {
days[i].push(
Number(
data.find(({ time }) => time === `${i}:${j.toString().padStart(2, '0')}`)?.value || 0,
),
);
}
}
return days;
}

View file

@ -1,16 +1,16 @@
import clickhouse from 'lib/clickhouse';
import { DATA_TYPE } from 'lib/constants';
import { uuid } from 'lib/crypto';
import { flattenJSON, getStringValue } from 'lib/data';
import prisma from 'lib/prisma';
import { DynamicData } from 'lib/types';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import kafka from 'lib/kafka';
import prisma from 'lib/prisma';
import { DynamicData } from 'lib/types';
export async function saveSessionData(data: {
websiteId: string;
sessionId: string;
sessionData: DynamicData;
createdAt?: string;
}) {
return runQuery({
[PRISMA]: () => relationalQuery(data),
@ -77,11 +77,12 @@ async function clickhouseQuery(data: {
websiteId: string;
sessionId: string;
sessionData: DynamicData;
createdAt?: string;
}) {
const { websiteId, sessionId, sessionData, createdAt } = data;
const { websiteId, sessionId, sessionData } = data;
const { getDateFormat, sendMessages } = kafka;
const { insert, getUTCString } = clickhouse;
const { sendMessages } = kafka;
const createdAt = getUTCString();
const jsonKeys = flattenJSON(sessionData);
@ -93,12 +94,16 @@ async function clickhouseQuery(data: {
data_type: dataType,
string_value: getStringValue(value, dataType),
number_value: dataType === DATA_TYPE.number ? value : null,
date_value: dataType === DATA_TYPE.date ? getDateFormat(value) : null,
created_at: createdAt ?? getDateFormat(new Date()),
date_value: dataType === DATA_TYPE.date ? getUTCString(value) : null,
created_at: createdAt,
};
});
await sendMessages(messages, 'session_data');
if (kafka.enabled) {
await sendMessages('session_data', messages);
} else {
await insert('session_data', messages);
}
return data;
}