mirror of
https://github.com/umami-software/umami.git
synced 2026-02-08 06:37:18 +01:00
Merge branch 'dev' into patch-1
This commit is contained in:
commit
b1dc690e2f
1140 changed files with 52229 additions and 29220 deletions
|
|
@ -1,19 +1,31 @@
|
|||
import clickhouse from '@/lib/clickhouse';
|
||||
import { EVENT_TYPE } from '@/lib/constants';
|
||||
import { CLICKHOUSE, getDatabaseType, POSTGRESQL, PRISMA, runQuery } from '@/lib/db';
|
||||
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
|
||||
import prisma from '@/lib/prisma';
|
||||
import { QueryFilters } from '@/lib/types';
|
||||
|
||||
export interface AttributionParameters {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
model: string;
|
||||
type: string;
|
||||
step: string;
|
||||
currency?: string;
|
||||
}
|
||||
|
||||
export interface AttributionResult {
|
||||
referrer: { name: string; value: number }[];
|
||||
paidAds: { name: string; value: number }[];
|
||||
utm_source: { name: string; value: number }[];
|
||||
utm_medium: { name: string; value: number }[];
|
||||
utm_campaign: { name: string; value: number }[];
|
||||
utm_content: { name: string; value: number }[];
|
||||
utm_term: { name: string; value: number }[];
|
||||
total: { pageviews: number; visitors: number; visits: number };
|
||||
}
|
||||
|
||||
export async function getAttribution(
|
||||
...args: [
|
||||
websiteId: string,
|
||||
criteria: {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
model: string;
|
||||
steps: { type: string; value: string }[];
|
||||
currency: string;
|
||||
},
|
||||
]
|
||||
...args: [websiteId: string, parameters: AttributionParameters, filters: QueryFilters]
|
||||
) {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(...args),
|
||||
|
|
@ -23,30 +35,19 @@ export async function getAttribution(
|
|||
|
||||
async function relationalQuery(
|
||||
websiteId: string,
|
||||
criteria: {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
model: string;
|
||||
steps: { type: string; value: string }[];
|
||||
currency: string;
|
||||
},
|
||||
): Promise<{
|
||||
referrer: { name: string; value: number }[];
|
||||
paidAds: { name: string; value: number }[];
|
||||
utm_source: { name: string; value: number }[];
|
||||
utm_medium: { name: string; value: number }[];
|
||||
utm_campaign: { name: string; value: number }[];
|
||||
utm_content: { name: string; value: number }[];
|
||||
utm_term: { name: string; value: number }[];
|
||||
total: { pageviews: number; visitors: number; visits: number };
|
||||
}> {
|
||||
const { startDate, endDate, model, steps, currency } = criteria;
|
||||
const { rawQuery } = prisma;
|
||||
const conversionStep = steps[0].value;
|
||||
const eventType = steps[0].type === 'url' ? EVENT_TYPE.pageView : EVENT_TYPE.customEvent;
|
||||
const column = steps[0].type === 'url' ? 'url_path' : 'event_name';
|
||||
const db = getDatabaseType();
|
||||
const like = db === POSTGRESQL ? 'ilike' : 'like';
|
||||
parameters: AttributionParameters,
|
||||
filters: QueryFilters,
|
||||
): Promise<AttributionResult> {
|
||||
const { model, type, currency } = parameters;
|
||||
const { rawQuery, parseFilters } = prisma;
|
||||
const eventType = type === 'path' ? EVENT_TYPE.pageView : EVENT_TYPE.customEvent;
|
||||
const column = type === 'path' ? 'url_path' : 'event_name';
|
||||
const { filterQuery, joinSessionQuery, cohortQuery, queryParams } = parseFilters({
|
||||
...filters,
|
||||
...parameters,
|
||||
websiteId,
|
||||
eventType,
|
||||
});
|
||||
|
||||
function getUTMQuery(utmColumn: string) {
|
||||
return `
|
||||
|
|
@ -68,29 +69,40 @@ async function relationalQuery(
|
|||
|
||||
const eventQuery = `WITH events AS (
|
||||
select distinct
|
||||
session_id,
|
||||
max(created_at) max_dt
|
||||
website_event.session_id,
|
||||
max(website_event.created_at) max_dt
|
||||
from website_event
|
||||
where website_id = {{websiteId::uuid}}
|
||||
and created_at between {{startDate}} and {{endDate}}
|
||||
and ${column} = {{conversionStep}}
|
||||
and event_type = {{eventType}}
|
||||
${cohortQuery}
|
||||
${joinSessionQuery}
|
||||
where website_event.website_id = {{websiteId::uuid}}
|
||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||
and website_event.${column} = {{step}}
|
||||
${filterQuery}
|
||||
group by 1),`;
|
||||
|
||||
const revenueEventQuery = `WITH events AS (
|
||||
select
|
||||
session_id,
|
||||
max(created_at) max_dt,
|
||||
sum(revenue) value
|
||||
revenue.session_id,
|
||||
max(revenue.created_at) max_dt,
|
||||
sum(revenue.revenue) value
|
||||
from revenue
|
||||
where website_id = {{websiteId::uuid}}
|
||||
and created_at between {{startDate}} and {{endDate}}
|
||||
and ${column} = {{conversionStep}}
|
||||
and currency ${like} {{currency}}
|
||||
join website_event
|
||||
on website_event.website_id = revenue.website_id
|
||||
and website_event.session_id = revenue.session_id
|
||||
and website_event.event_id = revenue.event_id
|
||||
and website_event.website_id = {{websiteId::uuid}}
|
||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||
${cohortQuery}
|
||||
${joinSessionQuery}
|
||||
where revenue.website_id = {{websiteId::uuid}}
|
||||
and revenue.created_at between {{startDate}} and {{endDate}}
|
||||
and revenue.${column} = {{step}}
|
||||
and revenue.currency = {{currency}}
|
||||
${filterQuery}
|
||||
group by 1),`;
|
||||
|
||||
function getModelQuery(model: string) {
|
||||
return model === 'firstClick'
|
||||
return model === 'first-click'
|
||||
? `\n
|
||||
model AS (select e.session_id,
|
||||
min(we.created_at) created_at
|
||||
|
|
@ -137,7 +149,7 @@ async function relationalQuery(
|
|||
order by 2 desc
|
||||
limit 20
|
||||
`,
|
||||
{ websiteId, startDate, endDate, conversionStep, eventType, currency },
|
||||
queryParams,
|
||||
);
|
||||
|
||||
const paidAdsres = await rawQuery(
|
||||
|
|
@ -170,7 +182,7 @@ async function relationalQuery(
|
|||
FROM results
|
||||
${currency ? '' : `WHERE name != ''`}
|
||||
`,
|
||||
{ websiteId, startDate, endDate, conversionStep, eventType, currency },
|
||||
queryParams,
|
||||
);
|
||||
|
||||
const sourceRes = await rawQuery(
|
||||
|
|
@ -179,7 +191,7 @@ async function relationalQuery(
|
|||
${getModelQuery(model)}
|
||||
${getUTMQuery('utm_source')}
|
||||
`,
|
||||
{ websiteId, startDate, endDate, conversionStep, eventType, currency },
|
||||
queryParams,
|
||||
);
|
||||
|
||||
const mediumRes = await rawQuery(
|
||||
|
|
@ -188,7 +200,7 @@ async function relationalQuery(
|
|||
${getModelQuery(model)}
|
||||
${getUTMQuery('utm_medium')}
|
||||
`,
|
||||
{ websiteId, startDate, endDate, conversionStep, eventType, currency },
|
||||
queryParams,
|
||||
);
|
||||
|
||||
const campaignRes = await rawQuery(
|
||||
|
|
@ -197,7 +209,7 @@ async function relationalQuery(
|
|||
${getModelQuery(model)}
|
||||
${getUTMQuery('utm_campaign')}
|
||||
`,
|
||||
{ websiteId, startDate, endDate, conversionStep, eventType, currency },
|
||||
queryParams,
|
||||
);
|
||||
|
||||
const contentRes = await rawQuery(
|
||||
|
|
@ -206,7 +218,7 @@ async function relationalQuery(
|
|||
${getModelQuery(model)}
|
||||
${getUTMQuery('utm_content')}
|
||||
`,
|
||||
{ websiteId, startDate, endDate, conversionStep, eventType, currency },
|
||||
queryParams,
|
||||
);
|
||||
|
||||
const termRes = await rawQuery(
|
||||
|
|
@ -215,22 +227,24 @@ async function relationalQuery(
|
|||
${getModelQuery(model)}
|
||||
${getUTMQuery('utm_term')}
|
||||
`,
|
||||
{ websiteId, startDate, endDate, conversionStep, eventType, currency },
|
||||
queryParams,
|
||||
);
|
||||
|
||||
const totalRes = await rawQuery(
|
||||
`
|
||||
select
|
||||
count(*) as "pageviews",
|
||||
count(distinct session_id) as "visitors",
|
||||
count(distinct visit_id) as "visits"
|
||||
count(distinct website_event.session_id) as "visitors",
|
||||
count(distinct website_event.visit_id) as "visits"
|
||||
from website_event
|
||||
where website_id = {{websiteId::uuid}}
|
||||
and created_at between {{startDate}} and {{endDate}}
|
||||
and ${column} = {{conversionStep}}
|
||||
and event_type = {{eventType}}
|
||||
${joinSessionQuery}
|
||||
${cohortQuery}
|
||||
where website_event.website_id = {{websiteId::uuid}}
|
||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||
and website_event.${column} = {{step}}
|
||||
${filterQuery}
|
||||
`,
|
||||
{ websiteId, startDate, endDate, conversionStep, eventType, currency },
|
||||
queryParams,
|
||||
).then(result => result?.[0]);
|
||||
|
||||
return {
|
||||
|
|
@ -247,45 +261,64 @@ async function relationalQuery(
|
|||
|
||||
async function clickhouseQuery(
|
||||
websiteId: string,
|
||||
criteria: {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
model: string;
|
||||
steps: { type: string; value: string }[];
|
||||
currency: string;
|
||||
},
|
||||
): Promise<{
|
||||
referrer: { name: string; value: number }[];
|
||||
paidAds: { name: string; value: number }[];
|
||||
utm_source: { name: string; value: number }[];
|
||||
utm_medium: { name: string; value: number }[];
|
||||
utm_campaign: { name: string; value: number }[];
|
||||
utm_content: { name: string; value: number }[];
|
||||
utm_term: { name: string; value: number }[];
|
||||
total: { pageviews: number; visitors: number; visits: number };
|
||||
}> {
|
||||
const { startDate, endDate, model, steps, currency } = criteria;
|
||||
const { rawQuery } = clickhouse;
|
||||
const conversionStep = steps[0].value;
|
||||
const eventType = steps[0].type === 'url' ? EVENT_TYPE.pageView : EVENT_TYPE.customEvent;
|
||||
const column = steps[0].type === 'url' ? 'url_path' : 'event_name';
|
||||
parameters: AttributionParameters,
|
||||
filters: QueryFilters,
|
||||
): Promise<AttributionResult> {
|
||||
const { model, type, currency } = parameters;
|
||||
const { rawQuery, parseFilters } = clickhouse;
|
||||
const eventType = type === 'path' ? EVENT_TYPE.pageView : EVENT_TYPE.customEvent;
|
||||
const column = type === 'path' ? 'url_path' : 'event_name';
|
||||
const { filterQuery, cohortQuery, queryParams } = parseFilters({
|
||||
...filters,
|
||||
...parameters,
|
||||
websiteId,
|
||||
eventType,
|
||||
});
|
||||
|
||||
function getUTMQuery(utmColumn: string) {
|
||||
return `
|
||||
select
|
||||
we.${utmColumn} name,
|
||||
${currency ? 'sum(e.value)' : 'uniqExact(we.session_id)'} value
|
||||
from model m
|
||||
join website_event we
|
||||
on we.created_at = m.created_at
|
||||
and we.session_id = m.session_id
|
||||
${currency ? 'join events e on e.session_id = m.session_id' : ''}
|
||||
where we.website_id = {websiteId:UUID}
|
||||
select
|
||||
we.${utmColumn} name,
|
||||
${currency ? 'sum(e.value)' : 'uniqExact(we.session_id)'} value
|
||||
from model m
|
||||
join website_event we
|
||||
on we.created_at = m.created_at
|
||||
and we.session_id = m.session_id
|
||||
${currency ? 'join events e on e.session_id = m.session_id' : ''}
|
||||
where we.website_id = {websiteId:UUID}
|
||||
and we.created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
${currency ? '' : `and we.${utmColumn} != ''`}
|
||||
group by 1
|
||||
order by 2 desc
|
||||
limit 20
|
||||
`;
|
||||
}
|
||||
|
||||
function getModelQuery(model: string) {
|
||||
if (model === 'first-click') {
|
||||
return `
|
||||
model AS (select e.session_id,
|
||||
min(we.created_at) created_at
|
||||
from events e
|
||||
join website_event we
|
||||
on we.session_id = e.session_id
|
||||
where we.website_id = {websiteId:UUID}
|
||||
and we.created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
${currency ? '' : `and we.${utmColumn} != ''`}
|
||||
group by 1
|
||||
order by 2 desc
|
||||
limit 20`;
|
||||
group by e.session_id)
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
model AS (select e.session_id,
|
||||
max(we.created_at) created_at
|
||||
from events e
|
||||
join website_event we
|
||||
on we.session_id = e.session_id
|
||||
where we.website_id = {websiteId:UUID}
|
||||
and we.created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
and we.created_at < e.max_dt
|
||||
group by e.session_id)
|
||||
`;
|
||||
}
|
||||
|
||||
const eventQuery = `WITH events AS (
|
||||
|
|
@ -293,47 +326,33 @@ async function clickhouseQuery(
|
|||
session_id,
|
||||
max(created_at) max_dt
|
||||
from website_event
|
||||
${cohortQuery}
|
||||
where website_id = {websiteId:UUID}
|
||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
and ${column} = {conversionStep:String}
|
||||
and event_type = {eventType:UInt32}
|
||||
and ${column} = {step:String}
|
||||
${filterQuery}
|
||||
group by 1),`;
|
||||
|
||||
const revenueEventQuery = `WITH events AS (
|
||||
select
|
||||
session_id,
|
||||
max(created_at) max_dt,
|
||||
sum(revenue) as value
|
||||
website_revenue.session_id,
|
||||
max(website_revenue.created_at) max_dt,
|
||||
sum(website_revenue.revenue) as value
|
||||
from website_revenue
|
||||
where website_id = {websiteId:UUID}
|
||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
and ${column} = {conversionStep:String}
|
||||
and currency = {currency:String}
|
||||
join website_event
|
||||
on website_event.website_id = website_revenue.website_id
|
||||
and website_event.session_id = website_revenue.session_id
|
||||
and website_event.event_id = website_revenue.event_id
|
||||
and website_event.website_id = {websiteId:UUID}
|
||||
and website_event.created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
${cohortQuery}
|
||||
where website_revenue.website_id = {websiteId:UUID}
|
||||
and website_revenue.created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
and website_revenue.${column} = {step:String}
|
||||
and website_revenue.currency = {currency:String}
|
||||
${filterQuery}
|
||||
group by 1),`;
|
||||
|
||||
function getModelQuery(model: string) {
|
||||
return model === 'firstClick'
|
||||
? `\n
|
||||
model AS (select e.session_id,
|
||||
min(we.created_at) created_at
|
||||
from events e
|
||||
join website_event we
|
||||
on we.session_id = e.session_id
|
||||
where we.website_id = {websiteId:UUID}
|
||||
and we.created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
group by e.session_id)`
|
||||
: `\n
|
||||
model AS (select e.session_id,
|
||||
max(we.created_at) created_at
|
||||
from events e
|
||||
join website_event we
|
||||
on we.session_id = e.session_id
|
||||
where we.website_id = {websiteId:UUID}
|
||||
and we.created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
and we.created_at < e.max_dt
|
||||
group by e.session_id)`;
|
||||
}
|
||||
|
||||
const referrerRes = await rawQuery<
|
||||
{
|
||||
name: string;
|
||||
|
|
@ -362,7 +381,7 @@ async function clickhouseQuery(
|
|||
order by 2 desc
|
||||
limit 20
|
||||
`,
|
||||
{ websiteId, startDate, endDate, conversionStep, eventType, currency },
|
||||
queryParams,
|
||||
);
|
||||
|
||||
const paidAdsres = await rawQuery<
|
||||
|
|
@ -393,7 +412,7 @@ async function clickhouseQuery(
|
|||
order by 2 desc
|
||||
limit 20
|
||||
`,
|
||||
{ websiteId, startDate, endDate, conversionStep, eventType, currency },
|
||||
queryParams,
|
||||
);
|
||||
|
||||
const sourceRes = await rawQuery<
|
||||
|
|
@ -407,7 +426,7 @@ async function clickhouseQuery(
|
|||
${getModelQuery(model)}
|
||||
${getUTMQuery('utm_source')}
|
||||
`,
|
||||
{ websiteId, startDate, endDate, conversionStep, eventType, currency },
|
||||
queryParams,
|
||||
);
|
||||
|
||||
const mediumRes = await rawQuery<
|
||||
|
|
@ -421,7 +440,7 @@ async function clickhouseQuery(
|
|||
${getModelQuery(model)}
|
||||
${getUTMQuery('utm_medium')}
|
||||
`,
|
||||
{ websiteId, startDate, endDate, conversionStep, eventType, currency },
|
||||
queryParams,
|
||||
);
|
||||
|
||||
const campaignRes = await rawQuery<
|
||||
|
|
@ -435,7 +454,7 @@ async function clickhouseQuery(
|
|||
${getModelQuery(model)}
|
||||
${getUTMQuery('utm_campaign')}
|
||||
`,
|
||||
{ websiteId, startDate, endDate, conversionStep, eventType, currency },
|
||||
queryParams,
|
||||
);
|
||||
|
||||
const contentRes = await rawQuery<
|
||||
|
|
@ -449,7 +468,7 @@ async function clickhouseQuery(
|
|||
${getModelQuery(model)}
|
||||
${getUTMQuery('utm_content')}
|
||||
`,
|
||||
{ websiteId, startDate, endDate, conversionStep, eventType, currency },
|
||||
queryParams,
|
||||
);
|
||||
|
||||
const termRes = await rawQuery<
|
||||
|
|
@ -463,7 +482,7 @@ async function clickhouseQuery(
|
|||
${getModelQuery(model)}
|
||||
${getUTMQuery('utm_term')}
|
||||
`,
|
||||
{ websiteId, startDate, endDate, conversionStep, eventType, currency },
|
||||
queryParams,
|
||||
);
|
||||
|
||||
const totalRes = await rawQuery<{ pageviews: number; visitors: number; visits: number }>(
|
||||
|
|
@ -473,12 +492,13 @@ async function clickhouseQuery(
|
|||
uniqExact(session_id) as "visitors",
|
||||
uniqExact(visit_id) as "visits"
|
||||
from website_event
|
||||
${cohortQuery}
|
||||
where website_id = {websiteId:UUID}
|
||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
and ${column} = {conversionStep:String}
|
||||
and event_type = {eventType:UInt32}
|
||||
and ${column} = {step:String}
|
||||
${filterQuery}
|
||||
`,
|
||||
{ websiteId, startDate, endDate, conversionStep, eventType, currency },
|
||||
queryParams,
|
||||
).then(result => result?.[0]);
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,19 @@ import clickhouse from '@/lib/clickhouse';
|
|||
import { EVENT_TYPE, FILTER_COLUMNS, SESSION_COLUMNS } from '@/lib/constants';
|
||||
import { QueryFilters } from '@/lib/types';
|
||||
|
||||
export async function getInsights(
|
||||
...args: [websiteId: string, fields: { name: string; type?: string }[], filters: QueryFilters]
|
||||
export interface BreakdownParameters {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
fields: string[];
|
||||
}
|
||||
|
||||
export interface BreakdownData {
|
||||
x: string;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export async function getBreakdown(
|
||||
...args: [websiteId: string, parameters: BreakdownParameters, filters: QueryFilters]
|
||||
) {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(...args),
|
||||
|
|
@ -15,23 +26,21 @@ export async function getInsights(
|
|||
|
||||
async function relationalQuery(
|
||||
websiteId: string,
|
||||
fields: { name: string; type?: string }[],
|
||||
parameters: BreakdownParameters,
|
||||
filters: QueryFilters,
|
||||
): Promise<
|
||||
{
|
||||
x: string;
|
||||
y: number;
|
||||
}[]
|
||||
> {
|
||||
): Promise<BreakdownData[]> {
|
||||
const { getTimestampDiffSQL, parseFilters, rawQuery } = prisma;
|
||||
const { filterQuery, cohortQuery, joinSession, params } = await parseFilters(
|
||||
websiteId,
|
||||
const { startDate, endDate, fields } = parameters;
|
||||
const { filterQuery, joinSessionQuery, cohortQuery, queryParams } = parseFilters(
|
||||
{
|
||||
...filters,
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
eventType: EVENT_TYPE.pageView,
|
||||
},
|
||||
{
|
||||
joinSession: !!fields.find(({ name }) => SESSION_COLUMNS.includes(name)),
|
||||
joinSession: !!fields.find((name: string) => SESSION_COLUMNS.includes(name)),
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -53,11 +62,10 @@ async function relationalQuery(
|
|||
min(website_event.created_at) as "min_time",
|
||||
max(website_event.created_at) as "max_time"
|
||||
from website_event
|
||||
${cohortQuery}
|
||||
${joinSession}
|
||||
${cohortQuery}
|
||||
${joinSessionQuery}
|
||||
where website_event.website_id = {{websiteId::uuid}}
|
||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||
and event_type = {{eventType}}
|
||||
${filterQuery}
|
||||
group by ${parseFieldsByName(fields)},
|
||||
website_event.session_id, website_event.visit_id
|
||||
|
|
@ -66,23 +74,22 @@ async function relationalQuery(
|
|||
order by 1 desc, 2 desc
|
||||
limit 500
|
||||
`,
|
||||
params,
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
||||
async function clickhouseQuery(
|
||||
websiteId: string,
|
||||
fields: { name: string; type?: string }[],
|
||||
parameters: BreakdownParameters,
|
||||
filters: QueryFilters,
|
||||
): Promise<
|
||||
{
|
||||
x: string;
|
||||
y: number;
|
||||
}[]
|
||||
> {
|
||||
): Promise<BreakdownData[]> {
|
||||
const { parseFilters, rawQuery } = clickhouse;
|
||||
const { filterQuery, cohortQuery, params } = await parseFilters(websiteId, {
|
||||
const { startDate, endDate, fields } = parameters;
|
||||
const { filterQuery, cohortQuery, queryParams } = parseFilters({
|
||||
...filters,
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
eventType: EVENT_TYPE.pageView,
|
||||
});
|
||||
|
||||
|
|
@ -107,7 +114,6 @@ async function clickhouseQuery(
|
|||
${cohortQuery}
|
||||
where website_id = {websiteId:UUID}
|
||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
and event_type = {eventType:UInt32}
|
||||
${filterQuery}
|
||||
group by ${parseFieldsByName(fields)},
|
||||
session_id, visit_id
|
||||
|
|
@ -116,14 +122,14 @@ async function clickhouseQuery(
|
|||
order by 1 desc, 2 desc
|
||||
limit 500
|
||||
`,
|
||||
params,
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
||||
function parseFields(fields: { name: any }[]) {
|
||||
return fields.map(({ name }) => `${FILTER_COLUMNS[name]} as "${name}"`).join(',');
|
||||
function parseFields(fields: string[]) {
|
||||
return fields.map(name => `${FILTER_COLUMNS[name]} as "${name}"`).join(',');
|
||||
}
|
||||
|
||||
function parseFieldsByName(fields: { name: any }[]) {
|
||||
return `${fields.map(({ name }) => name).join(',')}`;
|
||||
function parseFieldsByName(fields: string[]) {
|
||||
return `${fields.map(name => name).join(',')}`;
|
||||
}
|
||||
|
|
@ -1,36 +1,23 @@
|
|||
import clickhouse from '@/lib/clickhouse';
|
||||
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
|
||||
import prisma from '@/lib/prisma';
|
||||
import { QueryFilters } from '@/lib/types';
|
||||
|
||||
const formatResults = (steps: { type: string; value: string }[]) => (results: unknown) => {
|
||||
return steps.map((step: { type: string; value: string }, i: number) => {
|
||||
const visitors = Number(results[i]?.count) || 0;
|
||||
const previous = Number(results[i - 1]?.count) || 0;
|
||||
const dropped = previous > 0 ? previous - visitors : 0;
|
||||
const dropoff = 1 - visitors / previous;
|
||||
const remaining = visitors / Number(results[0].count);
|
||||
export interface FunnelParameters {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
window: number;
|
||||
steps: { type: string; value: string }[];
|
||||
}
|
||||
|
||||
return {
|
||||
...step,
|
||||
visitors,
|
||||
previous,
|
||||
dropped,
|
||||
dropoff,
|
||||
remaining,
|
||||
};
|
||||
});
|
||||
};
|
||||
export interface FunnelResult {
|
||||
value: string;
|
||||
visitors: number;
|
||||
dropoff: number;
|
||||
}
|
||||
|
||||
export async function getFunnel(
|
||||
...args: [
|
||||
websiteId: string,
|
||||
criteria: {
|
||||
windowMinutes: number;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
steps: { type: string; value: string }[];
|
||||
},
|
||||
]
|
||||
...args: [websiteId: string, parameters: FunnelParameters, filters: QueryFilters]
|
||||
) {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(...args),
|
||||
|
|
@ -40,26 +27,22 @@ export async function getFunnel(
|
|||
|
||||
async function relationalQuery(
|
||||
websiteId: string,
|
||||
criteria: {
|
||||
windowMinutes: number;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
steps: { type: string; value: string }[];
|
||||
},
|
||||
): Promise<
|
||||
{
|
||||
value: string;
|
||||
visitors: number;
|
||||
dropoff: number;
|
||||
}[]
|
||||
> {
|
||||
const { windowMinutes, startDate, endDate, steps } = criteria;
|
||||
const { rawQuery, getAddIntervalQuery } = prisma;
|
||||
const { levelOneQuery, levelQuery, sumQuery, params } = getFunnelQuery(steps, windowMinutes);
|
||||
parameters: FunnelParameters,
|
||||
filters: QueryFilters,
|
||||
): Promise<FunnelResult[]> {
|
||||
const { startDate, endDate, window, steps } = parameters;
|
||||
const { rawQuery, getAddIntervalQuery, parseFilters } = prisma;
|
||||
const { filterQuery, joinSessionQuery, cohortQuery, queryParams } = parseFilters({
|
||||
...filters,
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
});
|
||||
const { levelOneQuery, levelQuery, sumQuery, params } = getFunnelQuery(steps, window);
|
||||
|
||||
function getFunnelQuery(
|
||||
steps: { type: string; value: string }[],
|
||||
windowMinutes: number,
|
||||
window: number,
|
||||
): {
|
||||
levelOneQuery: string;
|
||||
levelQuery: string;
|
||||
|
|
@ -70,7 +53,7 @@ async function relationalQuery(
|
|||
(pv, cv, i) => {
|
||||
const levelNumber = i + 1;
|
||||
const startSum = i > 0 ? 'union ' : '';
|
||||
const isURL = cv.type === 'url';
|
||||
const isURL = cv.type === 'path';
|
||||
const column = isURL ? 'url_path' : 'event_name';
|
||||
|
||||
let operator = '=';
|
||||
|
|
@ -84,11 +67,14 @@ async function relationalQuery(
|
|||
if (levelNumber === 1) {
|
||||
pv.levelOneQuery = `
|
||||
WITH level1 AS (
|
||||
select distinct session_id, created_at
|
||||
select distinct website_event.session_id, website_event.created_at
|
||||
from website_event
|
||||
where website_id = {{websiteId::uuid}}
|
||||
and created_at between {{startDate}} and {{endDate}}
|
||||
${cohortQuery}
|
||||
${joinSessionQuery}
|
||||
where website_event.website_id = {{websiteId::uuid}}
|
||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||
and ${column} ${operator} {{${i}}}
|
||||
${filterQuery}
|
||||
)`;
|
||||
} else {
|
||||
pv.levelQuery += `
|
||||
|
|
@ -100,7 +86,7 @@ async function relationalQuery(
|
|||
where we.website_id = {{websiteId::uuid}}
|
||||
and we.created_at between l.created_at and ${getAddIntervalQuery(
|
||||
`l.created_at `,
|
||||
`${windowMinutes} minute`,
|
||||
`${window} minute`,
|
||||
)}
|
||||
and we.${column} ${operator} {{${i}}}
|
||||
and we.created_at <= {{endDate}}
|
||||
|
|
@ -129,22 +115,16 @@ async function relationalQuery(
|
|||
ORDER BY level;
|
||||
`,
|
||||
{
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
...params,
|
||||
...queryParams,
|
||||
},
|
||||
).then(formatResults(steps));
|
||||
}
|
||||
|
||||
async function clickhouseQuery(
|
||||
websiteId: string,
|
||||
criteria: {
|
||||
windowMinutes: number;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
steps: { type: string; value: string }[];
|
||||
},
|
||||
parameters: FunnelParameters,
|
||||
filters: QueryFilters,
|
||||
): Promise<
|
||||
{
|
||||
value: string;
|
||||
|
|
@ -152,29 +132,35 @@ async function clickhouseQuery(
|
|||
dropoff: number;
|
||||
}[]
|
||||
> {
|
||||
const { windowMinutes, startDate, endDate, steps } = criteria;
|
||||
const { rawQuery } = clickhouse;
|
||||
const { startDate, endDate, window, steps } = parameters;
|
||||
const { rawQuery, parseFilters } = clickhouse;
|
||||
const { levelOneQuery, levelQuery, sumQuery, stepFilterQuery, params } = getFunnelQuery(
|
||||
steps,
|
||||
windowMinutes,
|
||||
window,
|
||||
);
|
||||
const { filterQuery, cohortQuery, queryParams } = parseFilters({
|
||||
...filters,
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
});
|
||||
|
||||
function getFunnelQuery(
|
||||
steps: { type: string; value: string }[],
|
||||
windowMinutes: number,
|
||||
window: number,
|
||||
): {
|
||||
levelOneQuery: string;
|
||||
levelQuery: string;
|
||||
sumQuery: string;
|
||||
stepFilterQuery: string;
|
||||
params: { [key: string]: string };
|
||||
params: Record<string, string>;
|
||||
} {
|
||||
return steps.reduce(
|
||||
(pv, cv, i) => {
|
||||
const levelNumber = i + 1;
|
||||
const startSum = i > 0 ? 'union all ' : '';
|
||||
const startFilter = i > 0 ? 'or' : '';
|
||||
const isURL = cv.type === 'url';
|
||||
const isURL = cv.type === 'path';
|
||||
const column = isURL ? 'url_path' : 'event_name';
|
||||
|
||||
let operator = '=';
|
||||
|
|
@ -203,7 +189,7 @@ async function clickhouseQuery(
|
|||
from level${i} x
|
||||
join level0 y
|
||||
on x.session_id = y.session_id
|
||||
where y.created_at between x.created_at and x.created_at + interval ${windowMinutes} minute
|
||||
where y.created_at between x.created_at and x.created_at + interval ${window} minute
|
||||
and y.${column} ${operator} {param${i}:String}
|
||||
)`;
|
||||
}
|
||||
|
|
@ -229,9 +215,11 @@ async function clickhouseQuery(
|
|||
WITH level0 AS (
|
||||
select distinct session_id, url_path, referrer_path, event_name, created_at
|
||||
from website_event
|
||||
${cohortQuery}
|
||||
where (${stepFilterQuery})
|
||||
and website_id = {websiteId:UUID}
|
||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
${filterQuery}
|
||||
),
|
||||
${levelOneQuery}
|
||||
${levelQuery}
|
||||
|
|
@ -241,10 +229,27 @@ async function clickhouseQuery(
|
|||
) ORDER BY level;
|
||||
`,
|
||||
{
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
...params,
|
||||
...queryParams,
|
||||
},
|
||||
).then(formatResults(steps));
|
||||
}
|
||||
|
||||
const formatResults = (steps: { type: string; value: string }[]) => (results: unknown) => {
|
||||
return steps.map((step: { type: string; value: string }, i: number) => {
|
||||
const visitors = Number(results[i]?.count) || 0;
|
||||
const previous = Number(results[i - 1]?.count) || 0;
|
||||
const dropped = previous > 0 ? previous - visitors : 0;
|
||||
const dropoff = 1 - visitors / previous;
|
||||
const remaining = visitors / Number(results[0].count);
|
||||
|
||||
return {
|
||||
...step,
|
||||
visitors,
|
||||
previous,
|
||||
dropped,
|
||||
dropoff,
|
||||
remaining,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
|
|
|||
105
src/queries/sql/reports/getGoal.ts
Normal file
105
src/queries/sql/reports/getGoal.ts
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import clickhouse from '@/lib/clickhouse';
|
||||
import { EVENT_TYPE } from '@/lib/constants';
|
||||
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
|
||||
import prisma from '@/lib/prisma';
|
||||
import { QueryFilters } from '@/lib/types';
|
||||
|
||||
export interface GoalParameters {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
type: string;
|
||||
value: string;
|
||||
operator?: string;
|
||||
property?: string;
|
||||
}
|
||||
|
||||
export async function getGoal(
|
||||
...args: [websiteId: string, params: GoalParameters, filters: QueryFilters]
|
||||
) {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(...args),
|
||||
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
||||
});
|
||||
}
|
||||
|
||||
async function relationalQuery(
|
||||
websiteId: string,
|
||||
parameters: GoalParameters,
|
||||
filters: QueryFilters,
|
||||
) {
|
||||
const { startDate, endDate, type, value } = parameters;
|
||||
const { rawQuery, parseFilters } = prisma;
|
||||
const eventType = type === 'path' ? EVENT_TYPE.pageView : EVENT_TYPE.customEvent;
|
||||
const column = type === 'path' ? 'url_path' : 'event_name';
|
||||
const { filterQuery, dateQuery, joinSessionQuery, cohortQuery, queryParams } = parseFilters({
|
||||
...filters,
|
||||
websiteId,
|
||||
value,
|
||||
startDate,
|
||||
endDate,
|
||||
eventType,
|
||||
});
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
select count(distinct website_event.session_id) as num,
|
||||
(
|
||||
select count(distinct website_event.session_id)
|
||||
from website_event
|
||||
${cohortQuery}
|
||||
${joinSessionQuery}
|
||||
where website_event.website_id = {{websiteId::uuid}}
|
||||
${dateQuery}
|
||||
${filterQuery}
|
||||
) as total
|
||||
from website_event
|
||||
${cohortQuery}
|
||||
${joinSessionQuery}
|
||||
where website_event.website_id = {{websiteId::uuid}}
|
||||
and ${column} = {{value}}
|
||||
${dateQuery}
|
||||
${filterQuery}
|
||||
`,
|
||||
queryParams,
|
||||
).then(results => results?.[0]);
|
||||
}
|
||||
|
||||
async function clickhouseQuery(
|
||||
websiteId: string,
|
||||
parameters: GoalParameters,
|
||||
filters: QueryFilters,
|
||||
) {
|
||||
const { startDate, endDate, type, value } = parameters;
|
||||
const { rawQuery, parseFilters } = clickhouse;
|
||||
const eventType = type === 'path' ? EVENT_TYPE.pageView : EVENT_TYPE.customEvent;
|
||||
const column = type === 'path' ? 'url_path' : 'event_name';
|
||||
const { filterQuery, dateQuery, cohortQuery, queryParams } = parseFilters({
|
||||
...filters,
|
||||
websiteId,
|
||||
value,
|
||||
startDate,
|
||||
endDate,
|
||||
eventType,
|
||||
});
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
select count(distinct session_id) as num,
|
||||
(
|
||||
select count(distinct session_id)
|
||||
from website_event
|
||||
${cohortQuery}
|
||||
where website_id = {websiteId:UUID}
|
||||
${dateQuery}
|
||||
${filterQuery}
|
||||
) as total
|
||||
from website_event
|
||||
${cohortQuery}
|
||||
where website_id = {websiteId:UUID}
|
||||
and ${column} = {value:String}
|
||||
${dateQuery}
|
||||
${filterQuery}
|
||||
`,
|
||||
queryParams,
|
||||
).then(results => results?.[0]);
|
||||
}
|
||||
|
|
@ -1,375 +0,0 @@
|
|||
import clickhouse from '@/lib/clickhouse';
|
||||
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
|
||||
import prisma from '@/lib/prisma';
|
||||
|
||||
export async function getGoals(
|
||||
...args: [
|
||||
websiteId: string,
|
||||
criteria: {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
goals: { type: string; value: string; goal: number; operator?: string }[];
|
||||
},
|
||||
]
|
||||
) {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(...args),
|
||||
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
||||
});
|
||||
}
|
||||
|
||||
async function relationalQuery(
|
||||
websiteId: string,
|
||||
criteria: {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
goals: { type: string; value: string; goal: number; operator?: string }[];
|
||||
},
|
||||
): Promise<any> {
|
||||
const { startDate, endDate, goals } = criteria;
|
||||
const { rawQuery } = prisma;
|
||||
|
||||
const urls = goals.filter(a => a.type === 'url');
|
||||
const events = goals.filter(a => a.type === 'event');
|
||||
const eventData = goals.filter(a => a.type === 'event-data');
|
||||
|
||||
const hasUrl = urls.length > 0;
|
||||
const hasEvent = events.length > 0;
|
||||
const hasEventData = eventData.length > 0;
|
||||
|
||||
function getParameters(
|
||||
urls: { type: string; value: string; goal: number }[],
|
||||
events: { type: string; value: string; goal: number }[],
|
||||
eventData: {
|
||||
type: string;
|
||||
value: string;
|
||||
goal: number;
|
||||
operator?: string;
|
||||
property?: string;
|
||||
}[],
|
||||
) {
|
||||
const urlParam = urls.reduce((acc, cv, i) => {
|
||||
acc[`${cv.type}${i}`] = cv.value;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const eventParam = events.reduce((acc, cv, i) => {
|
||||
acc[`${cv.type}${i}`] = cv.value;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const eventDataParam = eventData.reduce((acc, cv, i) => {
|
||||
acc[`eventData${i}`] = cv.value;
|
||||
acc[`property${i}`] = cv.property;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return {
|
||||
urls: { ...urlParam, startDate, endDate, websiteId },
|
||||
events: { ...eventParam, startDate, endDate, websiteId },
|
||||
eventData: { ...eventDataParam, startDate, endDate, websiteId },
|
||||
};
|
||||
}
|
||||
|
||||
function getColumns(
|
||||
urls: { type: string; value: string; goal: number }[],
|
||||
events: { type: string; value: string; goal: number }[],
|
||||
eventData: {
|
||||
type: string;
|
||||
value: string;
|
||||
goal: number;
|
||||
operator?: string;
|
||||
property?: string;
|
||||
}[],
|
||||
) {
|
||||
const urlColumns = urls
|
||||
.map((a, i) => `COUNT(CASE WHEN url_path = {{url${i}}} THEN 1 END) AS URL${i},`)
|
||||
.join('\n')
|
||||
.slice(0, -1);
|
||||
const eventColumns = events
|
||||
.map((a, i) => `COUNT(CASE WHEN event_name = {{event${i}}} THEN 1 END) AS EVENT${i},`)
|
||||
.join('\n')
|
||||
.slice(0, -1);
|
||||
const eventDataColumns = eventData
|
||||
.map(
|
||||
(a, i) =>
|
||||
`${
|
||||
a.operator === 'average' ? 'avg' : a.operator
|
||||
}(CASE WHEN event_name = {{eventData${i}}} AND data_key = {{property${i}}} THEN ${
|
||||
a.operator === 'count' ? '1' : 'number_value'
|
||||
} END) AS EVENT_DATA${i},`,
|
||||
)
|
||||
.join('\n')
|
||||
.slice(0, -1);
|
||||
|
||||
return { urls: urlColumns, events: eventColumns, eventData: eventDataColumns };
|
||||
}
|
||||
|
||||
function getWhere(
|
||||
urls: { type: string; value: string; goal: number }[],
|
||||
events: { type: string; value: string; goal: number }[],
|
||||
eventData: {
|
||||
type: string;
|
||||
value: string;
|
||||
goal: number;
|
||||
operator?: string;
|
||||
property?: string;
|
||||
}[],
|
||||
) {
|
||||
const urlWhere = urls.map((a, i) => `{{url${i}}}`).join(',');
|
||||
const eventWhere = events.map((a, i) => `{{event${i}}}`).join(',');
|
||||
const eventDataNameWhere = eventData.map((a, i) => `{{eventData${i}}}`).join(',');
|
||||
const eventDataKeyWhere = eventData.map((a, i) => `{{property${i}}}`).join(',');
|
||||
|
||||
return {
|
||||
urls: `and url_path in (${urlWhere})`,
|
||||
events: `and event_name in (${eventWhere})`,
|
||||
eventData: `and event_name in (${eventDataNameWhere}) and data_key in (${eventDataKeyWhere})`,
|
||||
};
|
||||
}
|
||||
|
||||
const parameters = getParameters(urls, events, eventData);
|
||||
const columns = getColumns(urls, events, eventData);
|
||||
const where = getWhere(urls, events, eventData);
|
||||
|
||||
const urlResults = hasUrl
|
||||
? await rawQuery(
|
||||
`
|
||||
select
|
||||
${columns.urls}
|
||||
from website_event
|
||||
where website_id = {{websiteId::uuid}}
|
||||
${where.urls}
|
||||
and created_at between {{startDate}} and {{endDate}}
|
||||
`,
|
||||
parameters.urls,
|
||||
).then(a => {
|
||||
const results = a[0];
|
||||
|
||||
return Object.keys(results).map((key, i) => ({
|
||||
...urls[i],
|
||||
goal: Number(urls[i].goal),
|
||||
result: Number(results[key]),
|
||||
}));
|
||||
})
|
||||
: [];
|
||||
|
||||
const eventResults = hasEvent
|
||||
? await rawQuery(
|
||||
`
|
||||
select
|
||||
${columns.events}
|
||||
from website_event
|
||||
where website_id = {{websiteId::uuid}}
|
||||
${where.events}
|
||||
and created_at between {{startDate}} and {{endDate}}
|
||||
`,
|
||||
parameters.events,
|
||||
).then(a => {
|
||||
const results = a[0];
|
||||
|
||||
return Object.keys(results).map((key, i) => {
|
||||
return { ...events[i], goal: Number(events[i].goal), result: Number(results[key]) };
|
||||
});
|
||||
})
|
||||
: [];
|
||||
|
||||
const eventDataResults = hasEventData
|
||||
? await rawQuery(
|
||||
`
|
||||
select
|
||||
${columns.eventData}
|
||||
from website_event w
|
||||
join event_data d
|
||||
on d.website_event_id = w.event_id
|
||||
where w.website_id = {{websiteId::uuid}}
|
||||
${where.eventData}
|
||||
and w.created_at between {{startDate}} and {{endDate}}
|
||||
`,
|
||||
parameters.eventData,
|
||||
).then(a => {
|
||||
const results = a[0];
|
||||
|
||||
return Object.keys(results).map((key, i) => {
|
||||
return { ...eventData[i], goal: Number(eventData[i].goal), result: Number(results[key]) };
|
||||
});
|
||||
})
|
||||
: [];
|
||||
|
||||
return [...urlResults, ...eventResults, ...eventDataResults];
|
||||
}
|
||||
|
||||
async function clickhouseQuery(
|
||||
websiteId: string,
|
||||
criteria: {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
goals: { type: string; value: string; goal: number; operator?: string; property?: string }[];
|
||||
},
|
||||
): Promise<{ type: string; value: string; goal: number; result: number }[]> {
|
||||
const { startDate, endDate, goals } = criteria;
|
||||
const { rawQuery } = clickhouse;
|
||||
|
||||
const urls = goals.filter(a => a.type === 'url');
|
||||
const events = goals.filter(a => a.type === 'event');
|
||||
const eventData = goals.filter(a => a.type === 'event-data');
|
||||
|
||||
const hasUrl = urls.length > 0;
|
||||
const hasEvent = events.length > 0;
|
||||
const hasEventData = eventData.length > 0;
|
||||
|
||||
function getParameters(
|
||||
urls: { type: string; value: string; goal: number }[],
|
||||
events: { type: string; value: string; goal: number }[],
|
||||
eventData: {
|
||||
type: string;
|
||||
value: string;
|
||||
goal: number;
|
||||
operator?: string;
|
||||
property?: string;
|
||||
}[],
|
||||
) {
|
||||
const urlParam = urls.reduce((acc, cv, i) => {
|
||||
acc[`${cv.type}${i}`] = cv.value;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const eventParam = events.reduce((acc, cv, i) => {
|
||||
acc[`${cv.type}${i}`] = cv.value;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const eventDataParam = eventData.reduce((acc, cv, i) => {
|
||||
acc[`eventData${i}`] = cv.value;
|
||||
acc[`property${i}`] = cv.property;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return {
|
||||
urls: { ...urlParam, startDate, endDate, websiteId },
|
||||
events: { ...eventParam, startDate, endDate, websiteId },
|
||||
eventData: { ...eventDataParam, startDate, endDate, websiteId },
|
||||
};
|
||||
}
|
||||
|
||||
function getColumns(
|
||||
urls: { type: string; value: string; goal: number }[],
|
||||
events: { type: string; value: string; goal: number }[],
|
||||
eventData: {
|
||||
type: string;
|
||||
value: string;
|
||||
goal: number;
|
||||
operator?: string;
|
||||
property?: string;
|
||||
}[],
|
||||
) {
|
||||
const urlColumns = urls
|
||||
.map((a, i) => `countIf(url_path = {url${i}:String}) AS URL${i},`)
|
||||
.join('\n')
|
||||
.slice(0, -1);
|
||||
const eventColumns = events
|
||||
.map((a, i) => `countIf(event_name = {event${i}:String}) AS EVENT${i},`)
|
||||
.join('\n')
|
||||
.slice(0, -1);
|
||||
const eventDataColumns = eventData
|
||||
.map(
|
||||
(a, i) =>
|
||||
`${a.operator === 'average' ? 'avg' : a.operator}If(${
|
||||
a.operator !== 'count' ? 'number_value, ' : ''
|
||||
}event_name = {eventData${i}:String} AND data_key = {property${i}:String}) AS EVENT_DATA${i},`,
|
||||
)
|
||||
.join('\n')
|
||||
.slice(0, -1);
|
||||
|
||||
return { url: urlColumns, events: eventColumns, eventData: eventDataColumns };
|
||||
}
|
||||
|
||||
function getWhere(
|
||||
urls: { type: string; value: string; goal: number }[],
|
||||
events: { type: string; value: string; goal: number }[],
|
||||
eventData: {
|
||||
type: string;
|
||||
value: string;
|
||||
goal: number;
|
||||
operator?: string;
|
||||
property?: string;
|
||||
}[],
|
||||
) {
|
||||
const urlWhere = urls.map((a, i) => `{url${i}:String}`).join(',');
|
||||
const eventWhere = events.map((a, i) => `{event${i}:String}`).join(',');
|
||||
const eventDataNameWhere = eventData.map((a, i) => `{eventData${i}:String}`).join(',');
|
||||
const eventDataKeyWhere = eventData.map((a, i) => `{property${i}:String}`).join(',');
|
||||
|
||||
return {
|
||||
urls: `and url_path in (${urlWhere})`,
|
||||
events: `and event_name in (${eventWhere})`,
|
||||
eventData: `and event_name in (${eventDataNameWhere}) and data_key in (${eventDataKeyWhere})`,
|
||||
};
|
||||
}
|
||||
|
||||
const parameters = getParameters(urls, events, eventData);
|
||||
const columns = getColumns(urls, events, eventData);
|
||||
const where = getWhere(urls, events, eventData);
|
||||
|
||||
const urlResults = hasUrl
|
||||
? await rawQuery(
|
||||
`
|
||||
select
|
||||
${columns.url}
|
||||
from website_event
|
||||
where website_id = {websiteId:UUID}
|
||||
${where.urls}
|
||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
`,
|
||||
parameters.urls,
|
||||
).then(a => {
|
||||
const results = a[0];
|
||||
|
||||
return Object.keys(results).map((key, i) => {
|
||||
return { ...urls[i], goal: Number(urls[i].goal), result: Number(results[key]) };
|
||||
});
|
||||
})
|
||||
: [];
|
||||
|
||||
const eventResults = hasEvent
|
||||
? await rawQuery(
|
||||
`
|
||||
select
|
||||
${columns.events}
|
||||
from website_event
|
||||
where website_id = {websiteId:UUID}
|
||||
${where.events}
|
||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
`,
|
||||
parameters.events,
|
||||
).then(a => {
|
||||
const results = a[0];
|
||||
|
||||
return Object.keys(results).map((key, i) => {
|
||||
return { ...events[i], goal: Number(events[i].goal), result: Number(results[key]) };
|
||||
});
|
||||
})
|
||||
: [];
|
||||
|
||||
const eventDataResults = hasEventData
|
||||
? await rawQuery(
|
||||
`
|
||||
select
|
||||
${columns.eventData}
|
||||
from event_data
|
||||
where website_id = {websiteId:UUID}
|
||||
${where.eventData}
|
||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
`,
|
||||
parameters.eventData,
|
||||
).then(a => {
|
||||
const results = a[0];
|
||||
|
||||
return Object.keys(results).map((key, i) => {
|
||||
return { ...eventData[i], goal: Number(eventData[i].goal), result: Number(results[key]) };
|
||||
});
|
||||
})
|
||||
: [];
|
||||
|
||||
return [...urlResults, ...eventResults, ...eventDataResults];
|
||||
}
|
||||
|
|
@ -1,8 +1,17 @@
|
|||
import clickhouse from '@/lib/clickhouse';
|
||||
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
|
||||
import prisma from '@/lib/prisma';
|
||||
import { QueryFilters } from '@/lib/types';
|
||||
|
||||
interface JourneyResult {
|
||||
export interface JourneyParameters {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
steps: number;
|
||||
startStep?: string;
|
||||
endStep?: string;
|
||||
}
|
||||
|
||||
export interface JourneyResult {
|
||||
e1: string;
|
||||
e2: string;
|
||||
e3: string;
|
||||
|
|
@ -14,16 +23,7 @@ interface JourneyResult {
|
|||
}
|
||||
|
||||
export async function getJourney(
|
||||
...args: [
|
||||
websiteId: string,
|
||||
filters: {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
steps: number;
|
||||
startStep?: string;
|
||||
endStep?: string;
|
||||
},
|
||||
]
|
||||
...args: [websiteId: string, parameters: JourneyParameters, filters: QueryFilters]
|
||||
) {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(...args),
|
||||
|
|
@ -33,21 +33,22 @@ export async function getJourney(
|
|||
|
||||
async function relationalQuery(
|
||||
websiteId: string,
|
||||
filters: {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
steps: number;
|
||||
startStep?: string;
|
||||
endStep?: string;
|
||||
},
|
||||
parameters: JourneyParameters,
|
||||
filters: QueryFilters,
|
||||
): Promise<JourneyResult[]> {
|
||||
const { startDate, endDate, steps, startStep, endStep } = filters;
|
||||
const { rawQuery } = prisma;
|
||||
const { startDate, endDate, steps, startStep, endStep } = parameters;
|
||||
const { rawQuery, parseFilters } = prisma;
|
||||
const { sequenceQuery, startStepQuery, endStepQuery, params } = getJourneyQuery(
|
||||
steps,
|
||||
startStep,
|
||||
endStep,
|
||||
);
|
||||
const { filterQuery, joinSessionQuery, cohortQuery, queryParams } = parseFilters({
|
||||
...filters,
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
});
|
||||
|
||||
function getJourneyQuery(
|
||||
steps: number,
|
||||
|
|
@ -57,7 +58,7 @@ async function relationalQuery(
|
|||
sequenceQuery: string;
|
||||
startStepQuery: string;
|
||||
endStepQuery: string;
|
||||
params: { [key: string]: string };
|
||||
params: Record<string, string>;
|
||||
} {
|
||||
const params = {};
|
||||
let sequenceQuery = '';
|
||||
|
|
@ -116,13 +117,16 @@ async function relationalQuery(
|
|||
`
|
||||
WITH events AS (
|
||||
select distinct
|
||||
visit_id,
|
||||
referrer_path,
|
||||
coalesce(nullIf(event_name, ''), url_path) "event",
|
||||
row_number() OVER (PARTITION BY visit_id ORDER BY created_at) AS event_number
|
||||
website_event.visit_id,
|
||||
website_event.referrer_path,
|
||||
coalesce(nullIf(website_event.event_name, ''), website_event.url_path) event,
|
||||
row_number() OVER (PARTITION BY visit_id ORDER BY website_event.created_at) AS event_number
|
||||
from website_event
|
||||
where website_id = {{websiteId::uuid}}
|
||||
and created_at between {{startDate}} and {{endDate}}),
|
||||
${cohortQuery}
|
||||
${joinSessionQuery}
|
||||
where website_event.website_id = {{websiteId::uuid}}
|
||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||
${filterQuery}),
|
||||
${sequenceQuery}
|
||||
select *
|
||||
from sequences
|
||||
|
|
@ -133,31 +137,30 @@ async function relationalQuery(
|
|||
limit 100
|
||||
`,
|
||||
{
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
...params,
|
||||
...queryParams,
|
||||
},
|
||||
).then(parseResult);
|
||||
}
|
||||
|
||||
async function clickhouseQuery(
|
||||
websiteId: string,
|
||||
filters: {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
steps: number;
|
||||
startStep?: string;
|
||||
endStep?: string;
|
||||
},
|
||||
parameters: JourneyParameters,
|
||||
filters: QueryFilters,
|
||||
): Promise<JourneyResult[]> {
|
||||
const { startDate, endDate, steps, startStep, endStep } = filters;
|
||||
const { rawQuery } = clickhouse;
|
||||
const { startDate, endDate, steps, startStep, endStep } = parameters;
|
||||
const { rawQuery, parseFilters } = clickhouse;
|
||||
const { sequenceQuery, startStepQuery, endStepQuery, params } = getJourneyQuery(
|
||||
steps,
|
||||
startStep,
|
||||
endStep,
|
||||
);
|
||||
const { filterQuery, cohortQuery, queryParams } = parseFilters({
|
||||
...filters,
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
});
|
||||
|
||||
function getJourneyQuery(
|
||||
steps: number,
|
||||
|
|
@ -167,7 +170,7 @@ async function clickhouseQuery(
|
|||
sequenceQuery: string;
|
||||
startStepQuery: string;
|
||||
endStepQuery: string;
|
||||
params: { [key: string]: string };
|
||||
params: Record<string, string>;
|
||||
} {
|
||||
const params = {};
|
||||
let sequenceQuery = '';
|
||||
|
|
@ -230,7 +233,9 @@ async function clickhouseQuery(
|
|||
coalesce(nullIf(event_name, ''), url_path) "event",
|
||||
row_number() OVER (PARTITION BY visit_id ORDER BY created_at) AS event_number
|
||||
from website_event
|
||||
${cohortQuery}
|
||||
where website_id = {websiteId:UUID}
|
||||
${filterQuery}
|
||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}),
|
||||
${sequenceQuery}
|
||||
select *
|
||||
|
|
@ -242,10 +247,8 @@ async function clickhouseQuery(
|
|||
limit 100
|
||||
`,
|
||||
{
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
...params,
|
||||
...queryParams,
|
||||
},
|
||||
).then(parseResult);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,24 @@
|
|||
import clickhouse from '@/lib/clickhouse';
|
||||
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
|
||||
import prisma from '@/lib/prisma';
|
||||
import { QueryFilters } from '@/lib/types';
|
||||
|
||||
export interface RetentionParameters {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
timezone?: string;
|
||||
}
|
||||
|
||||
export interface RetentionResult {
|
||||
date: string;
|
||||
day: number;
|
||||
visitors: number;
|
||||
returnVisitors: number;
|
||||
percentage: number;
|
||||
}
|
||||
|
||||
export async function getRetention(
|
||||
...args: [
|
||||
websiteId: string,
|
||||
filters: {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
timezone?: string;
|
||||
},
|
||||
]
|
||||
...args: [websiteId: string, parameters: RetentionParameters, filters: QueryFilters]
|
||||
) {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(...args),
|
||||
|
|
@ -20,42 +28,45 @@ export async function getRetention(
|
|||
|
||||
async function relationalQuery(
|
||||
websiteId: string,
|
||||
filters: {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
timezone?: string;
|
||||
},
|
||||
): Promise<
|
||||
{
|
||||
date: string;
|
||||
day: number;
|
||||
visitors: number;
|
||||
returnVisitors: number;
|
||||
percentage: number;
|
||||
}[]
|
||||
> {
|
||||
const { startDate, endDate, timezone = 'UTC' } = filters;
|
||||
const { getDateSQL, getDayDiffQuery, getCastColumnQuery, rawQuery } = prisma;
|
||||
parameters: RetentionParameters,
|
||||
filters: QueryFilters,
|
||||
): Promise<RetentionResult[]> {
|
||||
const { startDate, endDate, timezone } = parameters;
|
||||
const { getDateSQL, getDayDiffQuery, getCastColumnQuery, rawQuery, parseFilters } = prisma;
|
||||
const unit = 'day';
|
||||
|
||||
const { filterQuery, joinSessionQuery, cohortQuery, queryParams } = parseFilters({
|
||||
...filters,
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
timezone,
|
||||
});
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
WITH cohort_items AS (
|
||||
select session_id,
|
||||
${getDateSQL('created_at', unit, timezone)} as cohort_date
|
||||
from session
|
||||
where website_id = {{websiteId::uuid}}
|
||||
and created_at between {{startDate}} and {{endDate}}
|
||||
select
|
||||
min(${getDateSQL('website_event.created_at', unit, timezone)}) as cohort_date,
|
||||
website_event.session_id
|
||||
from website_event
|
||||
${cohortQuery}
|
||||
${joinSessionQuery}
|
||||
where website_event.website_id = {{websiteId::uuid}}
|
||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||
${filterQuery}
|
||||
group by website_event.session_id
|
||||
),
|
||||
user_activities AS (
|
||||
select distinct
|
||||
w.session_id,
|
||||
${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
|
||||
website_event.session_id,
|
||||
${getDayDiffQuery(getDateSQL('created_at', unit, timezone), 'cohort_items.cohort_date')} as day_number
|
||||
from website_event
|
||||
join cohort_items
|
||||
on website_event.session_id = cohort_items.session_id
|
||||
where website_id = {{websiteId::uuid}}
|
||||
and created_at between {{startDate}} and {{endDate}}
|
||||
|
||||
),
|
||||
cohort_size as (
|
||||
select cohort_date,
|
||||
|
|
@ -85,34 +96,27 @@ async function relationalQuery(
|
|||
on c.cohort_date = s.cohort_date
|
||||
where c.day_number <= 31
|
||||
order by 1, 2`,
|
||||
{
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
},
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
||||
async function clickhouseQuery(
|
||||
websiteId: string,
|
||||
filters: {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
timezone?: string;
|
||||
},
|
||||
): Promise<
|
||||
{
|
||||
date: string;
|
||||
day: number;
|
||||
visitors: number;
|
||||
returnVisitors: number;
|
||||
percentage: number;
|
||||
}[]
|
||||
> {
|
||||
const { startDate, endDate, timezone = 'UTC' } = filters;
|
||||
const { getDateSQL, rawQuery } = clickhouse;
|
||||
parameters: RetentionParameters,
|
||||
filters: QueryFilters,
|
||||
): Promise<RetentionResult[]> {
|
||||
const { startDate, endDate, timezone } = parameters;
|
||||
const { getDateSQL, rawQuery, parseFilters } = clickhouse;
|
||||
const unit = 'day';
|
||||
|
||||
const { filterQuery, cohortQuery, queryParams } = parseFilters({
|
||||
...filters,
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
timezone,
|
||||
});
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
WITH cohort_items AS (
|
||||
|
|
@ -120,17 +124,19 @@ async function clickhouseQuery(
|
|||
min(${getDateSQL('created_at', unit, timezone)}) as cohort_date,
|
||||
session_id
|
||||
from website_event
|
||||
${cohortQuery}
|
||||
where website_id = {websiteId:UUID}
|
||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
${filterQuery}
|
||||
group by session_id
|
||||
),
|
||||
user_activities AS (
|
||||
select distinct
|
||||
w.session_id,
|
||||
(${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
|
||||
website_event.session_id,
|
||||
toInt32((${getDateSQL('created_at', unit, timezone)} - cohort_items.cohort_date) / 86400) as day_number
|
||||
from website_event
|
||||
join cohort_items
|
||||
on website_event.session_id = cohort_items.session_id
|
||||
where website_id = {websiteId:UUID}
|
||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
),
|
||||
|
|
@ -162,10 +168,6 @@ async function clickhouseQuery(
|
|||
on c.cohort_date = s.cohort_date
|
||||
where c.day_number <= 31
|
||||
order by 1, 2`,
|
||||
{
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
},
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,24 @@
|
|||
import clickhouse from '@/lib/clickhouse';
|
||||
import { CLICKHOUSE, getDatabaseType, POSTGRESQL, PRISMA, runQuery } from '@/lib/db';
|
||||
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
|
||||
import prisma from '@/lib/prisma';
|
||||
import { QueryFilters } from '@/lib/types';
|
||||
|
||||
export interface RevenuParameters {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
unit: string;
|
||||
timezone: string;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface RevenueResult {
|
||||
chart: { x: string; t: string; y: number }[];
|
||||
country: { name: string; value: number }[];
|
||||
total: { sum: number; count: number; average: number; unique_count: number };
|
||||
}
|
||||
|
||||
export async function getRevenue(
|
||||
...args: [
|
||||
websiteId: string,
|
||||
criteria: {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
unit: string;
|
||||
timezone: string;
|
||||
currency: string;
|
||||
},
|
||||
]
|
||||
...args: [websiteId: string, parameters: RevenuParameters, filters: QueryFilters]
|
||||
) {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(...args),
|
||||
|
|
@ -22,118 +28,116 @@ export async function getRevenue(
|
|||
|
||||
async function relationalQuery(
|
||||
websiteId: string,
|
||||
criteria: {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
unit: string;
|
||||
timezone: string;
|
||||
currency: string;
|
||||
},
|
||||
): Promise<{
|
||||
chart: { x: string; t: string; y: number }[];
|
||||
country: { name: string; value: number }[];
|
||||
total: { sum: number; count: number; unique_count: number };
|
||||
table: {
|
||||
currency: string;
|
||||
sum: number;
|
||||
count: number;
|
||||
unique_count: number;
|
||||
}[];
|
||||
}> {
|
||||
const { startDate, endDate, timezone = 'UTC', unit = 'day', currency } = criteria;
|
||||
const { getDateSQL, rawQuery } = prisma;
|
||||
const db = getDatabaseType();
|
||||
const like = db === POSTGRESQL ? 'ilike' : 'like';
|
||||
parameters: RevenuParameters,
|
||||
filters: QueryFilters,
|
||||
): Promise<RevenueResult> {
|
||||
const { startDate, endDate, unit = 'day', timezone = 'utc', currency } = parameters;
|
||||
const { getDateSQL, rawQuery, parseFilters } = prisma;
|
||||
const { queryParams, filterQuery, cohortQuery, joinSessionQuery } = parseFilters({
|
||||
...filters,
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
currency,
|
||||
});
|
||||
|
||||
const chartRes = await rawQuery(
|
||||
const joinQuery = filterQuery
|
||||
? `join website_event
|
||||
on website_event.website_id = revenue.website_id
|
||||
and website_event.session_id = revenue.session_id
|
||||
and website_event.event_id = revenue.event_id
|
||||
and website_event.website_id = {{websiteId::uuid}}
|
||||
and website_event.created_at between {{startDate}} and {{endDate}}`
|
||||
: '';
|
||||
|
||||
const chart = await rawQuery(
|
||||
`
|
||||
select
|
||||
event_name x,
|
||||
${getDateSQL('created_at', unit, timezone)} t,
|
||||
sum(revenue) y
|
||||
revenue.event_name x,
|
||||
${getDateSQL('revenue.created_at', unit, timezone)} t,
|
||||
sum(revenue.revenue) y
|
||||
from revenue
|
||||
where website_id = {{websiteId::uuid}}
|
||||
and created_at between {{startDate}} and {{endDate}}
|
||||
and currency ${like} {{currency}}
|
||||
${joinQuery}
|
||||
${cohortQuery}
|
||||
${joinSessionQuery}
|
||||
where revenue.website_id = {{websiteId::uuid}}
|
||||
and revenue.created_at between {{startDate}} and {{endDate}}
|
||||
and revenue.currency ilike {{currency}}
|
||||
${filterQuery}
|
||||
group by x, t
|
||||
order by t
|
||||
`,
|
||||
{ websiteId, startDate, endDate, unit, timezone, currency },
|
||||
queryParams,
|
||||
);
|
||||
|
||||
const countryRes = await rawQuery(
|
||||
const country = await rawQuery(
|
||||
`
|
||||
select
|
||||
s.country as name,
|
||||
sum(r.revenue) value
|
||||
from revenue r
|
||||
join session s
|
||||
on s.session_id = r.session_id
|
||||
where r.website_id = {{websiteId::uuid}}
|
||||
and r.created_at between {{startDate}} and {{endDate}}
|
||||
and r.currency ${like} {{currency}}
|
||||
group by s.country
|
||||
session.country as name,
|
||||
sum(revenue) value
|
||||
from revenue
|
||||
${joinQuery}
|
||||
join session
|
||||
on session.website_id = revenue.website_id
|
||||
and session.session_id = revenue.session_id
|
||||
${cohortQuery}
|
||||
where revenue.website_id = {{websiteId::uuid}}
|
||||
and revenue.created_at between {{startDate}} and {{endDate}}
|
||||
and revenue.currency ilike {{currency}}
|
||||
${filterQuery}
|
||||
group by session.country
|
||||
`,
|
||||
{ websiteId, startDate, endDate, currency },
|
||||
queryParams,
|
||||
);
|
||||
|
||||
const totalRes = await rawQuery(
|
||||
const total = await rawQuery(
|
||||
`
|
||||
select
|
||||
sum(revenue) as sum,
|
||||
count(distinct event_id) as count,
|
||||
count(distinct session_id) as unique_count
|
||||
from revenue r
|
||||
where website_id = {{websiteId::uuid}}
|
||||
and created_at between {{startDate}} and {{endDate}}
|
||||
and currency ${like} {{currency}}
|
||||
sum(revenue.revenue) as sum,
|
||||
count(distinct revenue.event_id) as count,
|
||||
count(distinct revenue.session_id) as unique_count
|
||||
from revenue
|
||||
${joinQuery}
|
||||
${cohortQuery}
|
||||
${joinSessionQuery}
|
||||
where revenue.website_id = {{websiteId::uuid}}
|
||||
and revenue.created_at between {{startDate}} and {{endDate}}
|
||||
and revenue.currency ilike {{currency}}
|
||||
${filterQuery}
|
||||
`,
|
||||
{ websiteId, startDate, endDate, currency },
|
||||
queryParams,
|
||||
).then(result => result?.[0]);
|
||||
|
||||
const tableRes = await rawQuery(
|
||||
`
|
||||
select
|
||||
currency,
|
||||
sum(revenue) as sum,
|
||||
count(distinct event_id) as count,
|
||||
count(distinct session_id) as unique_count
|
||||
from revenue r
|
||||
where website_id = {{websiteId::uuid}}
|
||||
and created_at between {{startDate}} and {{endDate}}
|
||||
group by currency
|
||||
order by sum desc
|
||||
`,
|
||||
{ websiteId, startDate, endDate, unit, timezone, currency },
|
||||
);
|
||||
total.average = total.count > 0 ? Number(total.sum) / Number(total.count) : 0;
|
||||
|
||||
return { chart: chartRes, country: countryRes, total: totalRes, table: tableRes };
|
||||
return { chart, country, total };
|
||||
}
|
||||
|
||||
async function clickhouseQuery(
|
||||
websiteId: string,
|
||||
criteria: {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
unit: string;
|
||||
timezone: string;
|
||||
currency: string;
|
||||
},
|
||||
): Promise<{
|
||||
chart: { x: string; t: string; y: number }[];
|
||||
country: { name: string; value: number }[];
|
||||
total: { sum: number; count: number; unique_count: number };
|
||||
table: {
|
||||
currency: string;
|
||||
sum: number;
|
||||
count: number;
|
||||
unique_count: number;
|
||||
}[];
|
||||
}> {
|
||||
const { startDate, endDate, timezone = 'UTC', unit = 'day', currency } = criteria;
|
||||
const { getDateSQL, rawQuery } = clickhouse;
|
||||
parameters: RevenuParameters,
|
||||
filters: QueryFilters,
|
||||
): Promise<RevenueResult> {
|
||||
const { startDate, endDate, unit = 'day', timezone = 'utc', currency } = parameters;
|
||||
const { getDateSQL, rawQuery, parseFilters } = clickhouse;
|
||||
const { filterQuery, cohortQuery, queryParams } = parseFilters({
|
||||
...filters,
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
currency,
|
||||
});
|
||||
|
||||
const chartRes = await rawQuery<
|
||||
const joinQuery = filterQuery
|
||||
? `join website_event
|
||||
on website_event.website_id = website_revenue.website_id
|
||||
and website_event.session_id = website_revenue.session_id
|
||||
and website_event.event_id = website_revenue.event_id
|
||||
and website_event.website_id = {websiteId:UUID}
|
||||
and website_event.created_at between {startDate:DateTime64} and {endDate:DateTime64}`
|
||||
: '';
|
||||
|
||||
const chart = await rawQuery<
|
||||
{
|
||||
x: string;
|
||||
t: string;
|
||||
|
|
@ -142,86 +146,72 @@ async function clickhouseQuery(
|
|||
>(
|
||||
`
|
||||
select
|
||||
event_name x,
|
||||
${getDateSQL('created_at', unit, timezone)} t,
|
||||
sum(revenue) y
|
||||
website_revenue.event_name x,
|
||||
${getDateSQL('website_revenue.created_at', unit, timezone)} t,
|
||||
sum(website_revenue.revenue) y
|
||||
from website_revenue
|
||||
where website_id = {websiteId:UUID}
|
||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
and currency = {currency:String}
|
||||
${joinQuery}
|
||||
${cohortQuery}
|
||||
where website_revenue.website_id = {websiteId:UUID}
|
||||
and website_revenue.created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
and website_revenue.currency = {currency:String}
|
||||
${filterQuery}
|
||||
group by x, t
|
||||
order by t
|
||||
`,
|
||||
{ websiteId, startDate, endDate, unit, timezone, currency },
|
||||
queryParams,
|
||||
);
|
||||
|
||||
const countryRes = await rawQuery<
|
||||
const country = await rawQuery<
|
||||
{
|
||||
name: string;
|
||||
value: number;
|
||||
}[]
|
||||
>(
|
||||
`
|
||||
select
|
||||
s.country as name,
|
||||
sum(w.revenue) as value
|
||||
from website_revenue w
|
||||
join (select distinct website_id, session_id, country
|
||||
from website_event_stats_hourly
|
||||
where website_id = {websiteId:UUID}) s
|
||||
on w.website_id = s.website_id
|
||||
and w.session_id = s.session_id
|
||||
where w.website_id = {websiteId:UUID}
|
||||
and w.created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
and w.currency = {currency:String}
|
||||
group by s.country
|
||||
order by value desc
|
||||
select
|
||||
website_event.country as name,
|
||||
sum(website_revenue.revenue) as value
|
||||
from website_revenue
|
||||
join website_event
|
||||
on website_event.website_id = website_revenue.website_id
|
||||
and website_event.session_id = website_revenue.session_id
|
||||
and website_event.event_id = website_revenue.event_id
|
||||
and website_event.website_id = {websiteId:UUID}
|
||||
and website_event.created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
${cohortQuery}
|
||||
where website_revenue.website_id = {websiteId:UUID}
|
||||
and website_revenue.created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
and website_revenue.currency = {currency:String}
|
||||
${filterQuery}
|
||||
group by website_event.country
|
||||
order by value desc
|
||||
`,
|
||||
{ websiteId, startDate, endDate, currency },
|
||||
queryParams,
|
||||
);
|
||||
|
||||
const totalRes = await rawQuery<{
|
||||
const total = await rawQuery<{
|
||||
sum: number;
|
||||
avg: number;
|
||||
count: number;
|
||||
unique_count: number;
|
||||
}>(
|
||||
`
|
||||
select
|
||||
sum(revenue) as sum,
|
||||
uniqExact(event_id) as count,
|
||||
uniqExact(session_id) as unique_count
|
||||
sum(website_revenue.revenue) as sum,
|
||||
uniqExact(website_revenue.event_id) as count,
|
||||
uniqExact(website_revenue.session_id) as unique_count
|
||||
from website_revenue
|
||||
where website_id = {websiteId:UUID}
|
||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
and currency = {currency:String}
|
||||
${joinQuery}
|
||||
${cohortQuery}
|
||||
where website_revenue.website_id = {websiteId:UUID}
|
||||
and website_revenue.created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
and website_revenue.currency = {currency:String}
|
||||
${filterQuery}
|
||||
`,
|
||||
{ websiteId, startDate, endDate, currency },
|
||||
queryParams,
|
||||
).then(result => result?.[0]);
|
||||
|
||||
const tableRes = await rawQuery<
|
||||
{
|
||||
currency: string;
|
||||
sum: number;
|
||||
avg: number;
|
||||
count: number;
|
||||
unique_count: number;
|
||||
}[]
|
||||
>(
|
||||
`
|
||||
select
|
||||
currency,
|
||||
sum(revenue) as sum,
|
||||
uniqExact(event_id) as count,
|
||||
uniqExact(session_id) as unique_count
|
||||
from website_revenue
|
||||
where website_id = {websiteId:UUID}
|
||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
group by currency
|
||||
order by sum desc
|
||||
`,
|
||||
{ websiteId, startDate, endDate, unit, timezone, currency },
|
||||
);
|
||||
total.average = total.count > 0 ? total.sum / total.count : 0;
|
||||
|
||||
return { chart: chartRes, country: countryRes, total: totalRes, table: tableRes };
|
||||
return { chart, country, total };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,75 +0,0 @@
|
|||
import prisma from '@/lib/prisma';
|
||||
import clickhouse from '@/lib/clickhouse';
|
||||
import { runQuery, CLICKHOUSE, PRISMA, getDatabaseType, POSTGRESQL } from '@/lib/db';
|
||||
|
||||
export async function getRevenueValues(
|
||||
...args: [
|
||||
websiteId: string,
|
||||
criteria: {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
},
|
||||
]
|
||||
) {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(...args),
|
||||
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
||||
});
|
||||
}
|
||||
|
||||
async function relationalQuery(
|
||||
websiteId: string,
|
||||
criteria: {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
},
|
||||
) {
|
||||
const { rawQuery } = prisma;
|
||||
const { startDate, endDate } = criteria;
|
||||
|
||||
const db = getDatabaseType();
|
||||
const like = db === POSTGRESQL ? 'ilike' : 'like';
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
select distinct string_value as currency
|
||||
from event_data
|
||||
where website_id = {{websiteId::uuid}}
|
||||
and created_at between {{startDate}} and {{endDate}}
|
||||
and data_key ${like} '%currency%'
|
||||
order by currency
|
||||
`,
|
||||
{
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async function clickhouseQuery(
|
||||
websiteId: string,
|
||||
criteria: {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
},
|
||||
) {
|
||||
const { rawQuery } = clickhouse;
|
||||
const { startDate, endDate } = criteria;
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
select distinct string_value as currency
|
||||
from event_data
|
||||
where website_id = {websiteId:UUID}
|
||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
and positionCaseInsensitive(data_key, 'currency') > 0
|
||||
order by currency
|
||||
`,
|
||||
{
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -1,16 +1,17 @@
|
|||
import clickhouse from '@/lib/clickhouse';
|
||||
import { EVENT_TYPE } from '@/lib/constants';
|
||||
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
|
||||
import prisma from '@/lib/prisma';
|
||||
import { QueryFilters } from '@/lib/types';
|
||||
|
||||
export interface UTMParameters {
|
||||
column: string;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
}
|
||||
|
||||
export async function getUTM(
|
||||
...args: [
|
||||
websiteId: string,
|
||||
filters: {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
timezone?: string;
|
||||
},
|
||||
]
|
||||
...args: [websiteId: string, parameters: UTMParameters, filters: QueryFilters]
|
||||
) {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(...args),
|
||||
|
|
@ -20,58 +21,64 @@ export async function getUTM(
|
|||
|
||||
async function relationalQuery(
|
||||
websiteId: string,
|
||||
filters: {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
timezone?: string;
|
||||
},
|
||||
parameters: UTMParameters,
|
||||
filters: QueryFilters,
|
||||
) {
|
||||
const { startDate, endDate } = filters;
|
||||
const { rawQuery } = prisma;
|
||||
const { column, startDate, endDate } = parameters;
|
||||
const { parseFilters, rawQuery } = prisma;
|
||||
|
||||
const { filterQuery, joinSessionQuery, cohortQuery, queryParams } = parseFilters({
|
||||
...filters,
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
eventType: EVENT_TYPE.pageView,
|
||||
});
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
select url_query, count(*) as "num"
|
||||
select website_event.${column} utm, count(*) as views
|
||||
from website_event
|
||||
where website_id = {{websiteId::uuid}}
|
||||
and created_at between {{startDate}} and {{endDate}}
|
||||
and coalesce(url_query, '') != ''
|
||||
and event_type = 1
|
||||
${cohortQuery}
|
||||
${joinSessionQuery}
|
||||
where website_event.website_id = {{websiteId::uuid}}
|
||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||
and coalesce(website_event.${column}, '') != ''
|
||||
${filterQuery}
|
||||
group by 1
|
||||
order by 2 desc
|
||||
`,
|
||||
{
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
},
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
||||
async function clickhouseQuery(
|
||||
websiteId: string,
|
||||
filters: {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
timezone?: string;
|
||||
},
|
||||
parameters: UTMParameters,
|
||||
filters: QueryFilters,
|
||||
) {
|
||||
const { startDate, endDate } = filters;
|
||||
const { rawQuery } = clickhouse;
|
||||
const { column, startDate, endDate } = parameters;
|
||||
const { parseFilters, rawQuery } = clickhouse;
|
||||
const { filterQuery, cohortQuery, queryParams } = parseFilters({
|
||||
...filters,
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
eventType: EVENT_TYPE.pageView,
|
||||
});
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
select url_query, count(*) as "num"
|
||||
select ${column} utm, count(*) as views
|
||||
from website_event
|
||||
${cohortQuery}
|
||||
where website_id = {websiteId:UUID}
|
||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||
and url_query != ''
|
||||
and event_type = 1
|
||||
and ${column} != ''
|
||||
${filterQuery}
|
||||
group by 1
|
||||
order by 2 desc
|
||||
`,
|
||||
{
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
},
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue