mirror of
https://github.com/umami-software/umami.git
synced 2026-02-05 13:17:19 +01:00
Renamed folder.
This commit is contained in:
parent
8525188e42
commit
dcf0da7b14
39 changed files with 0 additions and 0 deletions
36
src/queries/sql/sessions/createSession.ts
Normal file
36
src/queries/sql/sessions/createSession.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { Prisma } from '@prisma/client';
|
||||
import prisma from '@/lib/prisma';
|
||||
|
||||
export async function createSession(data: Prisma.SessionCreateInput) {
|
||||
const {
|
||||
id,
|
||||
websiteId,
|
||||
hostname,
|
||||
browser,
|
||||
os,
|
||||
device,
|
||||
screen,
|
||||
language,
|
||||
country,
|
||||
subdivision1,
|
||||
subdivision2,
|
||||
city,
|
||||
} = data;
|
||||
|
||||
return prisma.client.session.create({
|
||||
data: {
|
||||
id,
|
||||
websiteId,
|
||||
hostname,
|
||||
browser,
|
||||
os,
|
||||
device,
|
||||
screen,
|
||||
language,
|
||||
country,
|
||||
subdivision1,
|
||||
subdivision2,
|
||||
city,
|
||||
},
|
||||
});
|
||||
}
|
||||
59
src/queries/sql/sessions/getSessionActivity.ts
Normal file
59
src/queries/sql/sessions/getSessionActivity.ts
Normal 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 },
|
||||
);
|
||||
}
|
||||
56
src/queries/sql/sessions/getSessionData.ts
Normal file
56
src/queries/sql/sessions/getSessionData.ts
Normal 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 },
|
||||
);
|
||||
}
|
||||
65
src/queries/sql/sessions/getSessionDataProperties.ts
Normal file
65
src/queries/sql/sessions/getSessionDataProperties.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
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 getSessionDataProperties(
|
||||
...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
|
||||
data_key as "propertyName",
|
||||
count(*) as "total"
|
||||
from session_data
|
||||
where website_id = {{websiteId::uuid}}
|
||||
and created_at between {{startDate}} and {{endDate}}
|
||||
${filterQuery}
|
||||
group by data_key
|
||||
order by 2 desc
|
||||
limit 500
|
||||
`,
|
||||
params,
|
||||
);
|
||||
}
|
||||
|
||||
async function clickhouseQuery(
|
||||
websiteId: string,
|
||||
filters: QueryFilters & { propertyName?: string },
|
||||
): Promise<{ propertyName: string; total: number }[]> {
|
||||
const { rawQuery, parseFilters } = clickhouse;
|
||||
const { filterQuery, params } = await parseFilters(websiteId, filters, {
|
||||
columns: { propertyName: 'data_key' },
|
||||
});
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
select
|
||||
data_key as propertyName,
|
||||
count(*) as total
|
||||
from session_data final
|
||||
where website_id = {websiteId:UUID}
|
||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
${filterQuery}
|
||||
group by data_key
|
||||
order by 2 desc
|
||||
limit 500
|
||||
`,
|
||||
params,
|
||||
);
|
||||
}
|
||||
69
src/queries/sql/sessions/getSessionDataValues.ts
Normal file
69
src/queries/sql/sessions/getSessionDataValues.ts
Normal 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,
|
||||
);
|
||||
}
|
||||
123
src/queries/sql/sessions/getSessionMetrics.ts
Normal file
123
src/queries/sql/sessions/getSessionMetrics.ts
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
import clickhouse from '@/lib/clickhouse';
|
||||
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(
|
||||
...args: [
|
||||
websiteId: string,
|
||||
type: string,
|
||||
filters: QueryFilters,
|
||||
limit?: number | string,
|
||||
offset?: number | string,
|
||||
]
|
||||
) {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(...args),
|
||||
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
||||
});
|
||||
}
|
||||
|
||||
async function relationalQuery(
|
||||
websiteId: string,
|
||||
type: string,
|
||||
filters: QueryFilters,
|
||||
limit: number | string = 500,
|
||||
offset: number | string = 0,
|
||||
) {
|
||||
const column = FILTER_COLUMNS[type] || type;
|
||||
const { parseFilters, rawQuery } = prisma;
|
||||
const { filterQuery, joinSession, params } = await parseFilters(
|
||||
websiteId,
|
||||
{
|
||||
...filters,
|
||||
eventType: EVENT_TYPE.pageView,
|
||||
},
|
||||
{
|
||||
joinSession: SESSION_COLUMNS.includes(type),
|
||||
},
|
||||
);
|
||||
const includeCountry = column === 'city' || column === 'subdivision1';
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
select
|
||||
${column} x,
|
||||
count(distinct website_event.session_id) y
|
||||
${includeCountry ? ', country' : ''}
|
||||
from website_event
|
||||
${joinSession}
|
||||
where website_event.website_id = {{websiteId::uuid}}
|
||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||
and website_event.event_type = {{eventType}}
|
||||
${filterQuery}
|
||||
group by 1
|
||||
${includeCountry ? ', 3' : ''}
|
||||
order by 2 desc
|
||||
limit ${limit}
|
||||
offset ${offset}
|
||||
`,
|
||||
params,
|
||||
);
|
||||
}
|
||||
|
||||
async function clickhouseQuery(
|
||||
websiteId: string,
|
||||
type: string,
|
||||
filters: QueryFilters,
|
||||
limit: number | string = 500,
|
||||
offset: number | string = 0,
|
||||
): Promise<{ x: string; y: number }[]> {
|
||||
const column = FILTER_COLUMNS[type] || type;
|
||||
const { parseFilters, rawQuery } = clickhouse;
|
||||
const { filterQuery, params } = await parseFilters(websiteId, {
|
||||
...filters,
|
||||
eventType: EVENT_TYPE.pageView,
|
||||
});
|
||||
const includeCountry = column === 'city' || column === 'subdivision1';
|
||||
|
||||
let sql = '';
|
||||
|
||||
if (EVENT_COLUMNS.some(item => Object.keys(filters).includes(item))) {
|
||||
sql = `
|
||||
select
|
||||
${column} x,
|
||||
count(distinct session_id) y
|
||||
${includeCountry ? ', country' : ''}
|
||||
from 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}
|
||||
`;
|
||||
} 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 };
|
||||
});
|
||||
});
|
||||
}
|
||||
92
src/queries/sql/sessions/getSessionStats.ts
Normal file
92
src/queries/sql/sessions/getSessionStats.ts
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
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 { QueryFilters } from '@/lib/types';
|
||||
|
||||
export async function getSessionStats(...args: [websiteId: string, filters: QueryFilters]) {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(...args),
|
||||
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
||||
});
|
||||
}
|
||||
|
||||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||
const { timezone = 'utc', unit = 'day' } = filters;
|
||||
const { getDateSQL, parseFilters, rawQuery } = prisma;
|
||||
const { filterQuery, joinSession, params } = await parseFilters(websiteId, {
|
||||
...filters,
|
||||
eventType: EVENT_TYPE.pageView,
|
||||
});
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
select
|
||||
${getDateSQL('website_event.created_at', unit, timezone)} x,
|
||||
count(distinct website_event.session_id) y
|
||||
from website_event
|
||||
${joinSession}
|
||||
where website_event.website_id = {{websiteId::uuid}}
|
||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||
and event_type = {{eventType}}
|
||||
${filterQuery}
|
||||
group by 1
|
||||
order by 1
|
||||
`,
|
||||
params,
|
||||
);
|
||||
}
|
||||
|
||||
async function clickhouseQuery(
|
||||
websiteId: string,
|
||||
filters: QueryFilters,
|
||||
): Promise<{ x: string; y: number }[]> {
|
||||
const { timezone = 'utc', unit = 'day' } = filters;
|
||||
const { parseFilters, rawQuery, getDateSQL } = clickhouse;
|
||||
const { filterQuery, params } = await parseFilters(websiteId, {
|
||||
...filters,
|
||||
eventType: EVENT_TYPE.pageView,
|
||||
});
|
||||
|
||||
let sql = '';
|
||||
|
||||
if (EVENT_COLUMNS.some(item => Object.keys(filters).includes(item)) || unit === 'minute') {
|
||||
sql = `
|
||||
select
|
||||
g.t as x,
|
||||
g.y as y
|
||||
from (
|
||||
select
|
||||
${getDateSQL('website_event.created_at', unit, timezone)} as t,
|
||||
count(distinct session_id) as y
|
||||
from 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
|
||||
`;
|
||||
} else {
|
||||
sql = `
|
||||
select
|
||||
g.t as x,
|
||||
g.y as y
|
||||
from (
|
||||
select
|
||||
${getDateSQL('website_event.created_at', unit, timezone)} 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);
|
||||
}
|
||||
109
src/queries/sql/sessions/getWebsiteSession.ts
Normal file
109
src/queries/sql/sessions/getWebsiteSession.ts
Normal 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 = 2 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]);
|
||||
}
|
||||
72
src/queries/sql/sessions/getWebsiteSessionStats.ts
Normal file
72
src/queries/sql/sessions/getWebsiteSessionStats.ts
Normal 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,
|
||||
);
|
||||
}
|
||||
102
src/queries/sql/sessions/getWebsiteSessions.ts
Normal file
102
src/queries/sql/sessions/getWebsiteSessions.ts
Normal 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,
|
||||
);
|
||||
}
|
||||
71
src/queries/sql/sessions/getWebsiteSessionsWeekly.ts
Normal file
71
src/queries/sql/sessions/getWebsiteSessionsWeekly.ts
Normal 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, parseFilters } = clickhouse;
|
||||
const { params } = await parseFilters(websiteId, 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
|
||||
`,
|
||||
params,
|
||||
).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;
|
||||
}
|
||||
109
src/queries/sql/sessions/saveSessionData.ts
Normal file
109
src/queries/sql/sessions/saveSessionData.ts
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
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 clickhouse from '@/lib/clickhouse';
|
||||
|
||||
export async function saveSessionData(data: {
|
||||
websiteId: string;
|
||||
sessionId: string;
|
||||
sessionData: DynamicData;
|
||||
}) {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(data),
|
||||
[CLICKHOUSE]: () => clickhouseQuery(data),
|
||||
});
|
||||
}
|
||||
|
||||
export async function relationalQuery(data: {
|
||||
websiteId: string;
|
||||
sessionId: string;
|
||||
sessionData: DynamicData;
|
||||
}) {
|
||||
const { client } = prisma;
|
||||
const { websiteId, sessionId, sessionData } = data;
|
||||
|
||||
const jsonKeys = flattenJSON(sessionData);
|
||||
|
||||
const flattenedData = jsonKeys.map(a => ({
|
||||
id: uuid(),
|
||||
websiteId,
|
||||
sessionId,
|
||||
dataKey: a.key,
|
||||
stringValue: getStringValue(a.value, a.dataType),
|
||||
numberValue: a.dataType === DATA_TYPE.number ? a.value : null,
|
||||
dateValue: a.dataType === DATA_TYPE.date ? new Date(a.value) : null,
|
||||
dataType: a.dataType,
|
||||
}));
|
||||
|
||||
const existing = await client.sessionData.findMany({
|
||||
where: {
|
||||
sessionId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
sessionId: true,
|
||||
dataKey: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const data of flattenedData) {
|
||||
const { sessionId, dataKey, ...props } = data;
|
||||
const record = existing.find(e => e.sessionId === sessionId && e.dataKey === dataKey);
|
||||
|
||||
if (record) {
|
||||
await client.sessionData.update({
|
||||
where: {
|
||||
id: record.id,
|
||||
},
|
||||
data: {
|
||||
...props,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await client.sessionData.create({
|
||||
data,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return flattenedData;
|
||||
}
|
||||
|
||||
async function clickhouseQuery(data: {
|
||||
websiteId: string;
|
||||
sessionId: string;
|
||||
sessionData: DynamicData;
|
||||
}) {
|
||||
const { websiteId, sessionId, sessionData } = data;
|
||||
|
||||
const { insert, getUTCString } = clickhouse;
|
||||
const { sendMessage } = kafka;
|
||||
const createdAt = getUTCString();
|
||||
|
||||
const jsonKeys = flattenJSON(sessionData);
|
||||
|
||||
const messages = jsonKeys.map(({ key, value, dataType }) => {
|
||||
return {
|
||||
website_id: websiteId,
|
||||
session_id: sessionId,
|
||||
data_key: key,
|
||||
data_type: dataType,
|
||||
string_value: getStringValue(value, dataType),
|
||||
number_value: dataType === DATA_TYPE.number ? value : null,
|
||||
date_value: dataType === DATA_TYPE.date ? getUTCString(value) : null,
|
||||
created_at: createdAt,
|
||||
};
|
||||
});
|
||||
|
||||
if (kafka.enabled) {
|
||||
await sendMessage('session_data', messages);
|
||||
} else {
|
||||
await insert('session_data', messages);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue