New goals page. Upgraded prisma.

This commit is contained in:
Mike Cao 2025-05-31 02:11:18 -07:00
parent 99330a1a4d
commit 49bcbfd7f9
65 changed files with 769 additions and 1195 deletions

View file

@ -0,0 +1,77 @@
import clickhouse from '@/lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
import prisma from '@/lib/prisma';
export interface GoalCriteria {
startDate: Date;
endDate: Date;
type: string;
value: string;
operator?: string;
property?: string;
}
export async function getGoal(...args: [websiteId: string, criteria: GoalCriteria]) {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
});
}
async function relationalQuery(websiteId: string, criteria: GoalCriteria) {
const { type, value } = criteria;
const { rawQuery, parseFilters } = prisma;
const { filterQuery, dateQuery, params } = await parseFilters(websiteId, criteria);
const isPage = type === 'page';
const column = isPage ? 'url_path' : 'event_name';
const eventType = isPage ? 1 : 2;
return rawQuery(
`
select count(*) as num,
(
select count(${isPage ? '*' : 'distinct session_id'})
from website_event
where website_id = {websiteId:UUID}
and event_type = ${eventType}
${dateQuery}
) as total
from website_event
where website_id = {websiteId:UUID}
and event_type = ${eventType}
and ${column} = {value:String}
${dateQuery}
${filterQuery}
`,
{ ...params, value },
);
}
async function clickhouseQuery(websiteId: string, criteria: GoalCriteria) {
const { type, value } = criteria;
const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, dateQuery, params } = await parseFilters(websiteId, criteria);
const isPage = type === 'page';
const column = isPage ? 'url_path' : 'event_name';
const eventType = isPage ? 1 : 2;
return rawQuery(
`
select count(*) as num,
(
select count(${isPage ? '*' : 'distinct session_id'})
from website_event
where website_id = {websiteId:UUID}
and event_type = ${eventType}
${dateQuery}
) as total
from website_event
where website_id = {websiteId:UUID}
and event_type = ${eventType}
and ${column} = {value:String}
${dateQuery}
${filterQuery}
`,
{ ...params, value },
).then(results => results?.[0]);
}

View file

@ -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];
}

View file

@ -1,4 +1,4 @@
import { Prisma } from '@prisma/client';
import { Prisma } from '@/generated/prisma';
import prisma from '@/lib/prisma';
export async function createSession(data: Prisma.SessionCreateInput) {