mirror of
https://github.com/umami-software/umami.git
synced 2026-02-19 12:05:41 +01:00
R
This commit is contained in:
parent
65f3628ed7
commit
fe03e5f85a
12 changed files with 200 additions and 46 deletions
|
|
@ -15,9 +15,11 @@ COPY . .
|
||||||
|
|
||||||
ARG DATABASE_TYPE
|
ARG DATABASE_TYPE
|
||||||
ARG BASE_PATH
|
ARG BASE_PATH
|
||||||
|
ARG DATABASE_URL
|
||||||
|
|
||||||
ENV DATABASE_TYPE=$DATABASE_TYPE
|
ENV DATABASE_TYPE=$DATABASE_TYPE
|
||||||
ENV BASE_PATH=$BASE_PATH
|
ENV BASE_PATH=$BASE_PATH
|
||||||
|
ENV DATABASE_URL=$DATABASE_URL
|
||||||
|
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
---
|
---
|
||||||
services:
|
services:
|
||||||
umami:
|
umami:
|
||||||
image: ghcr.io/umami-software/umami:postgresql-latest
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
DATABASE_URL: postgresql://umami:umami@db:5432/umami
|
||||||
|
image: umami-local:dev
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
environment:
|
environment:
|
||||||
|
|
|
||||||
|
|
@ -2,24 +2,30 @@ import { canViewWebsite } from '@/lib/auth';
|
||||||
import { json, unauthorized } from '@/lib/response';
|
import { json, unauthorized } from '@/lib/response';
|
||||||
import { getActiveVisitors } from '@/queries';
|
import { getActiveVisitors } from '@/queries';
|
||||||
import { parseRequest } from '@/lib/request';
|
import { parseRequest } from '@/lib/request';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
request: Request,
|
request: Request,
|
||||||
{ params }: { params: Promise<{ websiteId: string }> },
|
{ params }: { params: Promise<{ websiteId: string }> },
|
||||||
) {
|
) {
|
||||||
const { auth, error } = await parseRequest(request);
|
const schema = z.object({
|
||||||
|
pathPrefix: z.string().optional(),
|
||||||
|
host: z.string().optional(),
|
||||||
|
});
|
||||||
|
const { auth, query, error } = await parseRequest(request, schema);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return error();
|
return error();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { websiteId } = await params;
|
const { websiteId } = await params;
|
||||||
|
const { pathPrefix, host } = query;
|
||||||
|
|
||||||
if (!(await canViewWebsite(auth, websiteId))) {
|
if (!(await canViewWebsite(auth, websiteId))) {
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await getActiveVisitors(websiteId);
|
const result = await getActiveVisitors(websiteId, pathPrefix, host);
|
||||||
|
|
||||||
return json(result);
|
return json(result);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ export async function GET(
|
||||||
limit: z.coerce.number().optional(),
|
limit: z.coerce.number().optional(),
|
||||||
offset: z.coerce.number().optional(),
|
offset: z.coerce.number().optional(),
|
||||||
search: z.string().optional(),
|
search: z.string().optional(),
|
||||||
|
pathPrefix: z.string().optional(),
|
||||||
...filterParams,
|
...filterParams,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -39,7 +40,7 @@ export async function GET(
|
||||||
}
|
}
|
||||||
|
|
||||||
const { websiteId } = await params;
|
const { websiteId } = await params;
|
||||||
const { type, limit, offset, search } = query;
|
const { type, limit, offset, search, pathPrefix } = query;
|
||||||
|
|
||||||
if (!(await canViewWebsite(auth, websiteId))) {
|
if (!(await canViewWebsite(auth, websiteId))) {
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
|
|
@ -51,6 +52,7 @@ export async function GET(
|
||||||
...getRequestFilters(query),
|
...getRequestFilters(query),
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
|
pathPrefix,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (search) {
|
if (search) {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ export async function GET(
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
startAt: z.coerce.number().int(),
|
startAt: z.coerce.number().int(),
|
||||||
endAt: z.coerce.number().int(),
|
endAt: z.coerce.number().int(),
|
||||||
|
pathPrefix: z.string().optional(),
|
||||||
...filterParams,
|
...filterParams,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -22,6 +23,7 @@ export async function GET(
|
||||||
}
|
}
|
||||||
|
|
||||||
const { websiteId } = await params;
|
const { websiteId } = await params;
|
||||||
|
const { pathPrefix } = query;
|
||||||
|
|
||||||
if (!(await canViewWebsite(auth, websiteId))) {
|
if (!(await canViewWebsite(auth, websiteId))) {
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
|
|
@ -35,6 +37,7 @@ export async function GET(
|
||||||
...filters,
|
...filters,
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
|
pathPrefix,
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = Object.keys(metrics[0]).reduce((obj, key) => {
|
const data = Object.keys(metrics[0]).reduce((obj, key) => {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ export async function GET(
|
||||||
startAt: z.coerce.number().int(),
|
startAt: z.coerce.number().int(),
|
||||||
endAt: z.coerce.number().int(),
|
endAt: z.coerce.number().int(),
|
||||||
compare: z.string().optional(),
|
compare: z.string().optional(),
|
||||||
|
pathPrefix: z.string().optional(),
|
||||||
...filterParams,
|
...filterParams,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -24,7 +25,7 @@ export async function GET(
|
||||||
}
|
}
|
||||||
|
|
||||||
const { websiteId } = await params;
|
const { websiteId } = await params;
|
||||||
const { compare } = query;
|
const { compare, pathPrefix } = query;
|
||||||
|
|
||||||
if (!(await canViewWebsite(auth, websiteId))) {
|
if (!(await canViewWebsite(auth, websiteId))) {
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
|
|
@ -43,12 +44,14 @@ export async function GET(
|
||||||
...filters,
|
...filters,
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
|
pathPrefix,
|
||||||
});
|
});
|
||||||
|
|
||||||
const prevPeriod = await getWebsiteStats(websiteId, {
|
const prevPeriod = await getWebsiteStats(websiteId, {
|
||||||
...filters,
|
...filters,
|
||||||
startDate: compareStartDate,
|
startDate: compareStartDate,
|
||||||
endDate: compareEndDate,
|
endDate: compareEndDate,
|
||||||
|
pathPrefix,
|
||||||
});
|
});
|
||||||
|
|
||||||
const stats = Object.keys(metrics[0]).reduce((obj, key) => {
|
const stats = Object.keys(metrics[0]).reduce((obj, key) => {
|
||||||
|
|
|
||||||
|
|
@ -158,6 +158,7 @@ export interface QueryFilters {
|
||||||
event?: string;
|
event?: string;
|
||||||
search?: string;
|
search?: string;
|
||||||
tag?: string;
|
tag?: string;
|
||||||
|
pathPrefix?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryOptions {
|
export interface QueryOptions {
|
||||||
|
|
|
||||||
|
|
@ -3,42 +3,70 @@ import prisma from '@/lib/prisma';
|
||||||
import clickhouse from '@/lib/clickhouse';
|
import clickhouse from '@/lib/clickhouse';
|
||||||
import { runQuery, CLICKHOUSE, PRISMA } from '@/lib/db';
|
import { runQuery, CLICKHOUSE, PRISMA } from '@/lib/db';
|
||||||
|
|
||||||
export async function getActiveVisitors(...args: [websiteId: string]) {
|
export async function getActiveVisitors(websiteId: string, pathPrefix?: string, host?: string) {
|
||||||
return runQuery({
|
return runQuery({
|
||||||
[PRISMA]: () => relationalQuery(...args),
|
[PRISMA]: () => relationalQuery(websiteId, pathPrefix, host),
|
||||||
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
[CLICKHOUSE]: () => clickhouseQuery(websiteId, pathPrefix, host),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function relationalQuery(websiteId: string) {
|
async function relationalQuery(websiteId: string, pathPrefix?: string, host?: string) {
|
||||||
const { rawQuery } = prisma;
|
const { rawQuery } = prisma;
|
||||||
|
|
||||||
const result = await rawQuery(
|
let sql = `
|
||||||
`
|
|
||||||
select count(distinct session_id) as visitors
|
select count(distinct session_id) as visitors
|
||||||
from website_event
|
from website_event
|
||||||
where website_id = {{websiteId::uuid}}
|
where website_id = {{websiteId::uuid}}
|
||||||
and created_at >= {{startDate}}
|
and created_at >= {{startDate}}
|
||||||
`,
|
`;
|
||||||
{ websiteId, startDate: subMinutes(new Date(), 5) },
|
const params: any = { websiteId, startDate: subMinutes(new Date(), 5) };
|
||||||
);
|
|
||||||
|
|
||||||
|
if (pathPrefix) {
|
||||||
|
sql += `
|
||||||
|
and (
|
||||||
|
url_path LIKE {{pathPrefix}}
|
||||||
|
or url_path LIKE {{pathPrefixWithLang}}
|
||||||
|
)`;
|
||||||
|
params.pathPrefix = `${pathPrefix}%`;
|
||||||
|
params.pathPrefixWithLang = `%/en${pathPrefix}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (host) {
|
||||||
|
sql += ` and hostname LIKE {{host}}`;
|
||||||
|
params.host = `%${host}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await rawQuery(sql, params);
|
||||||
return result[0] ?? null;
|
return result[0] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clickhouseQuery(websiteId: string): Promise<{ x: number }> {
|
async function clickhouseQuery(websiteId: string, pathPrefix?: string, host?: string): Promise<{ x: number }> {
|
||||||
const { rawQuery } = clickhouse;
|
const { rawQuery } = clickhouse;
|
||||||
|
|
||||||
const result = await rawQuery(
|
let sql = `
|
||||||
`
|
|
||||||
select
|
select
|
||||||
count(distinct session_id) as "visitors"
|
count(distinct session_id) as \'visitors\'
|
||||||
from website_event
|
from website_event
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at >= {startDate:DateTime64}
|
and created_at >= {startDate:DateTime64}
|
||||||
`,
|
`;
|
||||||
{ websiteId, startDate: subMinutes(new Date(), 5) },
|
const params: any = { websiteId, startDate: subMinutes(new Date(), 5) };
|
||||||
);
|
|
||||||
|
|
||||||
|
if (pathPrefix) {
|
||||||
|
sql += `
|
||||||
|
and (
|
||||||
|
url_path LIKE {pathPrefix:String}
|
||||||
|
or url_path LIKE {pathPrefixWithLang:String}
|
||||||
|
)`;
|
||||||
|
params.pathPrefix = `${pathPrefix}%`;
|
||||||
|
params.pathPrefixWithLang = `%/en${pathPrefix}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (host) {
|
||||||
|
sql += ` and hostname LIKE {host:String}`;
|
||||||
|
params.host = `%${host}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await rawQuery(sql, params);
|
||||||
return result[0] ?? null;
|
return result[0] ?? null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { QueryFilters } from '@/lib/types';
|
||||||
import { EVENT_COLUMNS } from '@/lib/constants';
|
import { EVENT_COLUMNS } from '@/lib/constants';
|
||||||
|
|
||||||
export async function getWebsiteStats(
|
export async function getWebsiteStats(
|
||||||
...args: [websiteId: string, filters: QueryFilters]
|
...args: [websiteId: string, filters: QueryFilters & { pathPrefix?: string }]
|
||||||
): Promise<
|
): Promise<
|
||||||
{ pageviews: number; visitors: number; visits: number; bounces: number; totaltime: number }[]
|
{ pageviews: number; visitors: number; visits: number; bounces: number; totaltime: number }[]
|
||||||
> {
|
> {
|
||||||
|
|
@ -18,7 +18,7 @@ export async function getWebsiteStats(
|
||||||
|
|
||||||
async function relationalQuery(
|
async function relationalQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
filters: QueryFilters,
|
filters: QueryFilters & { pathPrefix?: string },
|
||||||
): Promise<
|
): Promise<
|
||||||
{ pageviews: number; visitors: number; visits: number; bounces: number; totaltime: number }[]
|
{ pageviews: number; visitors: number; visits: number; bounces: number; totaltime: number }[]
|
||||||
> {
|
> {
|
||||||
|
|
@ -28,8 +28,7 @@ async function relationalQuery(
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
});
|
});
|
||||||
|
|
||||||
return rawQuery(
|
let sql = `
|
||||||
`
|
|
||||||
select
|
select
|
||||||
sum(t.c) as "pageviews",
|
sum(t.c) as "pageviews",
|
||||||
count(distinct t.session_id) as "visitors",
|
count(distinct t.session_id) as "visitors",
|
||||||
|
|
@ -49,16 +48,29 @@ async function relationalQuery(
|
||||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||||
and event_type = {{eventType}}
|
and event_type = {{eventType}}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (filters.pathPrefix) {
|
||||||
|
sql += `
|
||||||
|
and (
|
||||||
|
website_event.url_path LIKE {{pathPrefix}}
|
||||||
|
or website_event.url_path LIKE {{pathPrefixWithLang}}
|
||||||
|
)`;
|
||||||
|
params.pathPrefix = `${filters.pathPrefix}%`;
|
||||||
|
params.pathPrefixWithLang = `%/en${filters.pathPrefix}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
sql += `
|
||||||
group by 1, 2
|
group by 1, 2
|
||||||
) as t
|
) as t
|
||||||
`,
|
`;
|
||||||
params,
|
|
||||||
);
|
return rawQuery(sql, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clickhouseQuery(
|
async function clickhouseQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
filters: QueryFilters,
|
filters: QueryFilters & { pathPrefix?: string },
|
||||||
): Promise<
|
): Promise<
|
||||||
{ pageviews: number; visitors: number; visits: number; bounces: number; totaltime: number }[]
|
{ pageviews: number; visitors: number; visits: number; bounces: number; totaltime: number }[]
|
||||||
> {
|
> {
|
||||||
|
|
@ -90,6 +102,19 @@ async function clickhouseQuery(
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and event_type = {eventType:UInt32}
|
and event_type = {eventType:UInt32}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (filters.pathPrefix) {
|
||||||
|
sql += `
|
||||||
|
and (
|
||||||
|
url_path LIKE {pathPrefix:String}
|
||||||
|
or url_path LIKE {pathPrefixWithLang:String}
|
||||||
|
)`;
|
||||||
|
params.pathPrefix = `${filters.pathPrefix}%`;
|
||||||
|
params.pathPrefixWithLang = `%/en${filters.pathPrefix}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
sql += `
|
||||||
group by session_id, visit_id
|
group by session_id, visit_id
|
||||||
) as t;
|
) as t;
|
||||||
`;
|
`;
|
||||||
|
|
@ -112,6 +137,19 @@ async function clickhouseQuery(
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and event_type = {eventType:UInt32}
|
and event_type = {eventType:UInt32}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (filters.pathPrefix) {
|
||||||
|
sql += `
|
||||||
|
and (
|
||||||
|
url_path LIKE {pathPrefix:String}
|
||||||
|
or url_path LIKE {pathPrefixWithLang:String}
|
||||||
|
)`;
|
||||||
|
params.pathPrefix = `${filters.pathPrefix}%`;
|
||||||
|
params.pathPrefixWithLang = `%/en${filters.pathPrefix}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
sql += `
|
||||||
group by session_id, visit_id
|
group by session_id, visit_id
|
||||||
) as t;
|
) as t;
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,16 @@ async function relationalQuery(
|
||||||
|
|
||||||
let entryExitQuery = '';
|
let entryExitQuery = '';
|
||||||
let excludeDomain = '';
|
let excludeDomain = '';
|
||||||
|
let pathPrefixQuery = '';
|
||||||
|
|
||||||
|
if (filters.pathPrefix) {
|
||||||
|
pathPrefixQuery = `and (
|
||||||
|
website_event.url_path LIKE {{pathPrefix}}
|
||||||
|
or website_event.url_path LIKE {{pathPrefixWithLang}}
|
||||||
|
)`;
|
||||||
|
params.pathPrefix = `${filters.pathPrefix}%`;
|
||||||
|
params.pathPrefixWithLang = `%/en${filters.pathPrefix}%`;
|
||||||
|
}
|
||||||
|
|
||||||
if (column === 'referrer_domain') {
|
if (column === 'referrer_domain') {
|
||||||
excludeDomain = `and website_event.referrer_domain != website_event.hostname
|
excludeDomain = `and website_event.referrer_domain != website_event.hostname
|
||||||
|
|
@ -74,6 +84,7 @@ async function relationalQuery(
|
||||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||||
and event_type = {{eventType}}
|
and event_type = {{eventType}}
|
||||||
${excludeDomain}
|
${excludeDomain}
|
||||||
|
${pathPrefixQuery}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
group by 1
|
group by 1
|
||||||
order by 2 desc
|
order by 2 desc
|
||||||
|
|
@ -100,14 +111,24 @@ async function clickhouseQuery(
|
||||||
|
|
||||||
let sql = '';
|
let sql = '';
|
||||||
let excludeDomain = '';
|
let excludeDomain = '';
|
||||||
|
let pathPrefixQuery = '';
|
||||||
|
|
||||||
if (EVENT_COLUMNS.some(item => Object.keys(filters).includes(item))) {
|
if (filters.pathPrefix) {
|
||||||
let entryExitQuery = '';
|
pathPrefixQuery = `and (
|
||||||
|
url_path LIKE {pathPrefix:String}
|
||||||
|
or url_path LIKE {pathPrefixWithLang:String}
|
||||||
|
)`;
|
||||||
|
params.pathPrefix = `${filters.pathPrefix}%`;
|
||||||
|
params.pathPrefixWithLang = `%/en${filters.pathPrefix}%`;
|
||||||
|
}
|
||||||
|
|
||||||
if (column === 'referrer_domain') {
|
if (column === 'referrer_domain') {
|
||||||
excludeDomain = `and referrer_domain != hostname and referrer_domain != ''`;
|
excludeDomain = `and referrer_domain != hostname and referrer_domain != ''`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (EVENT_COLUMNS.some(item => Object.keys(filters).includes(item))) {
|
||||||
|
let entryExitQuery = '';
|
||||||
|
|
||||||
if (type === 'entry' || type === 'exit') {
|
if (type === 'entry' || type === 'exit') {
|
||||||
const aggregrate = type === 'entry' ? 'min' : 'max';
|
const aggregrate = type === 'entry' ? 'min' : 'max';
|
||||||
|
|
||||||
|
|
@ -132,6 +153,7 @@ async function clickhouseQuery(
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and event_type = {eventType:UInt32}
|
and event_type = {eventType:UInt32}
|
||||||
${excludeDomain}
|
${excludeDomain}
|
||||||
|
${pathPrefixQuery}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
group by x
|
group by x
|
||||||
order by y desc
|
order by y desc
|
||||||
|
|
@ -169,6 +191,7 @@ async function clickhouseQuery(
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and event_type = {eventType:UInt32}
|
and event_type = {eventType:UInt32}
|
||||||
${excludeDomain}
|
${excludeDomain}
|
||||||
|
${pathPrefixQuery}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
${groupByQuery}) as g
|
${groupByQuery}) as g
|
||||||
group by x
|
group by x
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,17 @@ async function relationalQuery(
|
||||||
);
|
);
|
||||||
const includeCountry = column === 'city' || column === 'region';
|
const includeCountry = column === 'city' || column === 'region';
|
||||||
|
|
||||||
|
let pathPrefixQuery = '';
|
||||||
|
|
||||||
|
if (filters.pathPrefix) {
|
||||||
|
pathPrefixQuery = `and (
|
||||||
|
website_event.url_path LIKE {{pathPrefix}}
|
||||||
|
or website_event.url_path LIKE {{pathPrefixWithLang}}
|
||||||
|
)`;
|
||||||
|
params.pathPrefix = `${filters.pathPrefix}%`;
|
||||||
|
params.pathPrefixWithLang = `%/en${filters.pathPrefix}%`;
|
||||||
|
}
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
select
|
select
|
||||||
|
|
@ -51,6 +62,7 @@ async function relationalQuery(
|
||||||
where website_event.website_id = {{websiteId::uuid}}
|
where website_event.website_id = {{websiteId::uuid}}
|
||||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||||
and website_event.event_type = {{eventType}}
|
and website_event.event_type = {{eventType}}
|
||||||
|
${pathPrefixQuery}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
group by 1
|
group by 1
|
||||||
${includeCountry ? ', 3' : ''}
|
${includeCountry ? ', 3' : ''}
|
||||||
|
|
@ -77,6 +89,17 @@ async function clickhouseQuery(
|
||||||
});
|
});
|
||||||
const includeCountry = column === 'city' || column === 'region';
|
const includeCountry = column === 'city' || column === 'region';
|
||||||
|
|
||||||
|
let pathPrefixQuery = '';
|
||||||
|
|
||||||
|
if (filters.pathPrefix) {
|
||||||
|
pathPrefixQuery = `and (
|
||||||
|
url_path LIKE {pathPrefix:String}
|
||||||
|
or url_path LIKE {pathPrefixWithLang:String}
|
||||||
|
)`;
|
||||||
|
params.pathPrefix = `${filters.pathPrefix}%`;
|
||||||
|
params.pathPrefixWithLang = `%/en${filters.pathPrefix}%`;
|
||||||
|
}
|
||||||
|
|
||||||
let sql = '';
|
let sql = '';
|
||||||
|
|
||||||
if (EVENT_COLUMNS.some(item => Object.keys(filters).includes(item))) {
|
if (EVENT_COLUMNS.some(item => Object.keys(filters).includes(item))) {
|
||||||
|
|
@ -89,6 +112,7 @@ async function clickhouseQuery(
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and event_type = {eventType:UInt32}
|
and event_type = {eventType:UInt32}
|
||||||
|
${pathPrefixQuery}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
group by x
|
group by x
|
||||||
${includeCountry ? ', country' : ''}
|
${includeCountry ? ', country' : ''}
|
||||||
|
|
@ -106,6 +130,7 @@ async function clickhouseQuery(
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and event_type = {eventType:UInt32}
|
and event_type = {eventType:UInt32}
|
||||||
|
${pathPrefixQuery}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
group by x
|
group by x
|
||||||
${includeCountry ? ', country' : ''}
|
${includeCountry ? ', country' : ''}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import prisma from '@/lib/prisma';
|
||||||
import { QueryFilters } from '@/lib/types';
|
import { QueryFilters } from '@/lib/types';
|
||||||
|
|
||||||
export async function getWebsiteSessionStats(
|
export async function getWebsiteSessionStats(
|
||||||
...args: [websiteId: string, filters: QueryFilters]
|
...args: [websiteId: string, filters: QueryFilters & { pathPrefix?: string }]
|
||||||
): Promise<
|
): Promise<
|
||||||
{ pageviews: number; visitors: number; visits: number; countries: number; events: number }[]
|
{ pageviews: number; visitors: number; visits: number; countries: number; events: number }[]
|
||||||
> {
|
> {
|
||||||
|
|
@ -16,7 +16,7 @@ export async function getWebsiteSessionStats(
|
||||||
|
|
||||||
async function relationalQuery(
|
async function relationalQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
filters: QueryFilters,
|
filters: QueryFilters & { pathPrefix?: string },
|
||||||
): Promise<
|
): Promise<
|
||||||
{ pageviews: number; visitors: number; visits: number; countries: number; events: number }[]
|
{ pageviews: number; visitors: number; visits: number; countries: number; events: number }[]
|
||||||
> {
|
> {
|
||||||
|
|
@ -25,8 +25,7 @@ async function relationalQuery(
|
||||||
...filters,
|
...filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
return rawQuery(
|
let sql = `
|
||||||
`
|
|
||||||
select
|
select
|
||||||
count(*) as "pageviews",
|
count(*) as "pageviews",
|
||||||
count(distinct website_event.session_id) as "visitors",
|
count(distinct website_event.session_id) as "visitors",
|
||||||
|
|
@ -38,14 +37,24 @@ async function relationalQuery(
|
||||||
where website_event.website_id = {{websiteId::uuid}}
|
where website_event.website_id = {{websiteId::uuid}}
|
||||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
`,
|
`;
|
||||||
params,
|
|
||||||
);
|
if (filters.pathPrefix) {
|
||||||
|
sql += `
|
||||||
|
and (
|
||||||
|
website_event.url_path LIKE {{pathPrefix}}
|
||||||
|
or website_event.url_path LIKE {{pathPrefixWithLang}}
|
||||||
|
)`;
|
||||||
|
params.pathPrefix = `${filters.pathPrefix}%`;
|
||||||
|
params.pathPrefixWithLang = `%/en${filters.pathPrefix}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawQuery(sql, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clickhouseQuery(
|
async function clickhouseQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
filters: QueryFilters,
|
filters: QueryFilters & { pathPrefix?: string },
|
||||||
): Promise<
|
): Promise<
|
||||||
{ pageviews: number; visitors: number; visits: number; countries: number; events: number }[]
|
{ pageviews: number; visitors: number; visits: number; countries: number; events: number }[]
|
||||||
> {
|
> {
|
||||||
|
|
@ -54,8 +63,7 @@ async function clickhouseQuery(
|
||||||
...filters,
|
...filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
return rawQuery(
|
let sql = `
|
||||||
`
|
|
||||||
select
|
select
|
||||||
sum(views) as "pageviews",
|
sum(views) as "pageviews",
|
||||||
uniq(session_id) as "visitors",
|
uniq(session_id) as "visitors",
|
||||||
|
|
@ -66,7 +74,17 @@ async function clickhouseQuery(
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
`,
|
`;
|
||||||
params,
|
|
||||||
);
|
if (filters.pathPrefix) {
|
||||||
|
sql += `
|
||||||
|
and (
|
||||||
|
url_path LIKE {pathPrefix:String}
|
||||||
|
or url_path LIKE {pathPrefixWithLang:String}
|
||||||
|
)`;
|
||||||
|
params.pathPrefix = `${filters.pathPrefix}%`;
|
||||||
|
params.pathPrefixWithLang = `%/en${filters.pathPrefix}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawQuery(sql, params);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue