Made filters work for all reports.

This commit is contained in:
Mike Cao 2025-06-29 23:57:11 -07:00
parent ea83afbc13
commit 8b64029409
46 changed files with 328 additions and 275 deletions

View file

@ -24,7 +24,7 @@ export function WebsiteControls({
{allowDateFilter && <WebsiteDateFilter websiteId={websiteId} allowCompare={allowCompare} />} {allowDateFilter && <WebsiteDateFilter websiteId={websiteId} allowCompare={allowCompare} />}
{allowMonthFilter && <WebsiteMonthSelect websiteId={websiteId} />} {allowMonthFilter && <WebsiteMonthSelect websiteId={websiteId} />}
</Row> </Row>
<FilterBar /> {allowFilter && <FilterBar />}
</Column> </Column>
); );
} }

View file

@ -70,7 +70,7 @@ export function ReportsNav({ websiteId }: { websiteId: string }) {
key={id} key={id}
href={renderUrl( href={renderUrl(
`/websites/${websiteId}/reports${path}`, `/websites/${websiteId}/reports${path}`,
path === '/retention' ? false : null, path === '/retention' ? { date: undefined } : null,
)} )}
> >
<NavMenuItem isSelected={isSelected}> <NavMenuItem isSelected={isSelected}>

View file

@ -48,8 +48,8 @@ export function Attribution({
const metrics = data const metrics = data
? [ ? [
{ {
value: pageviews, value: visitors,
label: formatMessage(labels.views), label: formatMessage(labels.visitors),
formatValue: formatLongNumber, formatValue: formatLongNumber,
}, },
{ {
@ -58,8 +58,8 @@ export function Attribution({
formatValue: formatLongNumber, formatValue: formatLongNumber,
}, },
{ {
value: visitors, value: pageviews,
label: formatMessage(labels.visitors), label: formatMessage(labels.views),
formatValue: formatLongNumber, formatValue: formatLongNumber,
}, },
] ]
@ -83,60 +83,64 @@ export function Attribution({
return ( return (
<LoadingPanel data={data} isLoading={isLoading} error={error}> <LoadingPanel data={data} isLoading={isLoading} error={error}>
<Column gap> {data && (
<MetricsBar> <Column gap>
{metrics?.map(({ label, value, formatValue }) => { <MetricsBar>
return <MetricCard key={label} value={value} label={label} formatValue={formatValue} />; {metrics?.map(({ label, value, formatValue }) => {
})} return (
</MetricsBar> <MetricCard key={label} value={value} label={label} formatValue={formatValue} />
<SectionHeader title={formatMessage(labels.sources)} /> );
<Grid columns="1fr 1fr" gap> })}
<Panel> </MetricsBar>
<ListTable <SectionHeader title={formatMessage(labels.sources)} />
title={formatMessage(labels.referrer)} <Grid columns="1fr 1fr" gap>
metric={formatMessage(currency ? labels.revenue : labels.visitors)} <Panel>
currency={currency} <ListTable
data={percentFilter( title={formatMessage(labels.referrer)}
data?.['referrer']?.map(({ name, value }) => ({ metric={formatMessage(currency ? labels.revenue : labels.visitors)}
x: name, currency={currency}
y: Number(value), data={percentFilter(
})), data?.['referrer']?.map(({ name, value }) => ({
)} x: name,
/> y: Number(value),
</Panel> })),
<Panel> )}
<ListTable />
title={formatMessage(labels.paidAds)} </Panel>
metric={formatMessage(currency ? labels.revenue : labels.visitors)} <Panel>
currency={currency} <ListTable
data={percentFilter( title={formatMessage(labels.paidAds)}
data?.['paidAds']?.map(({ name, value }) => ({ metric={formatMessage(currency ? labels.revenue : labels.visitors)}
x: name, currency={currency}
y: Number(value), data={percentFilter(
})), data?.['paidAds']?.map(({ name, value }) => ({
)} x: name,
/> y: Number(value),
</Panel> })),
</Grid> )}
<SectionHeader title="UTM" /> />
<Grid columns="1fr 1fr" gap> </Panel>
<Panel> </Grid>
<UTMTable data={data?.['utm_source']} title={formatMessage(labels.sources)} /> <SectionHeader title="UTM" />
</Panel> <Grid columns="1fr 1fr" gap>
<Panel> <Panel>
<UTMTable data={data?.['utm_medium']} title={formatMessage(labels.medium)} /> <UTMTable data={data?.['utm_source']} title={formatMessage(labels.sources)} />
</Panel> </Panel>
<Panel> <Panel>
<UTMTable data={data?.['utm_cmapaign']} title={formatMessage(labels.campaigns)} /> <UTMTable data={data?.['utm_medium']} title={formatMessage(labels.medium)} />
</Panel> </Panel>
<Panel> <Panel>
<UTMTable data={data?.['utm_content']} title={formatMessage(labels.content)} /> <UTMTable data={data?.['utm_cmapaign']} title={formatMessage(labels.campaigns)} />
</Panel> </Panel>
<Panel> <Panel>
<UTMTable data={data?.['utm_term']} title={formatMessage(labels.terms)} /> <UTMTable data={data?.['utm_content']} title={formatMessage(labels.content)} />
</Panel> </Panel>
</Grid> <Panel>
</Column> <UTMTable data={data?.['utm_term']} title={formatMessage(labels.terms)} />
</Panel>
</Grid>
</Column>
)}
</LoadingPanel> </LoadingPanel>
); );
} }

View file

@ -11,7 +11,7 @@ export function RevenuePage({ websiteId }: { websiteId: string }) {
return ( return (
<Column gap> <Column gap>
<WebsiteControls websiteId={websiteId} allowCompare={false} /> <WebsiteControls websiteId={websiteId} allowFilter={false} />
<Revenue websiteId={websiteId} startDate={startDate} endDate={endDate} /> <Revenue websiteId={websiteId} startDate={startDate} endDate={endDate} />
</Column> </Column>
); );

View file

@ -26,47 +26,49 @@ export function UTM({ websiteId, startDate, endDate }: UTMProps) {
return ( return (
<LoadingPanel data={data} isLoading={isLoading} error={error}> <LoadingPanel data={data} isLoading={isLoading} error={error}>
<Column gap> {data && (
{UTM_PARAMS.map(param => { <Column gap>
const items = toArray(data?.[param]); {UTM_PARAMS.map(param => {
const chartData = { const items = toArray(data?.[param]);
labels: items.map(({ name }) => name), const chartData = {
datasets: [ labels: items.map(({ name }) => name),
{ datasets: [
data: items.map(({ value }) => value), {
backgroundColor: CHART_COLORS, data: items.map(({ value }) => value),
borderWidth: 0, backgroundColor: CHART_COLORS,
}, borderWidth: 0,
], },
}; ],
const total = items.reduce((sum, { value }) => { };
return +sum + +value; const total = items.reduce((sum, { value }) => {
}, 0); return +sum + +value;
}, 0);
return ( return (
<Panel key={param}> <Panel key={param}>
<Grid columns="1fr 1fr"> <Grid columns="1fr 1fr">
<Column> <Column>
<Heading> <Heading>
<Text transform="capitalize">{param.replace(/^utm_/, '')}</Text> <Text transform="capitalize">{param.replace(/^utm_/, '')}</Text>
</Heading> </Heading>
<ListTable <ListTable
metric={formatMessage(labels.views)} metric={formatMessage(labels.views)}
data={items.map(({ name, value }) => ({ data={items.map(({ name, value }) => ({
x: name, x: name,
y: value, y: value,
z: (value / total) * 100, z: (value / total) * 100,
}))} }))}
/> />
</Column> </Column>
<Column> <Column>
<PieChart type="doughnut" chartData={chartData} /> <PieChart type="doughnut" chartData={chartData} />
</Column> </Column>
</Grid> </Grid>
</Panel> </Panel>
); );
})} })}
</Column> </Column>
)}
</LoadingPanel> </LoadingPanel>
); );
} }

View file

@ -15,6 +15,7 @@ export async function POST(request: Request) {
websiteId, websiteId,
dateRange: { startDate, endDate }, dateRange: { startDate, endDate },
parameters: { model, type, step, currency }, parameters: { model, type, step, currency },
...filters
} = body; } = body;
if (!(await canViewWebsite(auth, websiteId))) { if (!(await canViewWebsite(auth, websiteId))) {
@ -22,6 +23,7 @@ export async function POST(request: Request) {
} }
const data = await getAttribution(websiteId, { const data = await getAttribution(websiteId, {
...filters,
startDate: new Date(startDate), startDate: new Date(startDate),
endDate: new Date(endDate), endDate: new Date(endDate),
model, model,

View file

@ -15,7 +15,7 @@ export async function POST(request: Request) {
websiteId, websiteId,
dateRange: { startDate, endDate }, dateRange: { startDate, endDate },
parameters: { fields }, parameters: { fields },
filters, ...filters
} = body; } = body;
if (!(await canViewWebsite(auth, websiteId))) { if (!(await canViewWebsite(auth, websiteId))) {
@ -23,9 +23,9 @@ export async function POST(request: Request) {
} }
const data = await getBreakdown(websiteId, fields, { const data = await getBreakdown(websiteId, fields, {
...filters,
startDate: new Date(startDate), startDate: new Date(startDate),
endDate: new Date(endDate), endDate: new Date(endDate),
...filters,
}); });
return json(data); return json(data);

View file

@ -15,6 +15,7 @@ export async function POST(request: Request) {
websiteId, websiteId,
dateRange: { startDate, endDate }, dateRange: { startDate, endDate },
parameters: { steps, window }, parameters: { steps, window },
...filters
} = body; } = body;
if (!(await canViewWebsite(auth, websiteId))) { if (!(await canViewWebsite(auth, websiteId))) {
@ -22,6 +23,7 @@ export async function POST(request: Request) {
} }
const data = await getFunnel(websiteId, { const data = await getFunnel(websiteId, {
...filters,
startDate: new Date(startDate), startDate: new Date(startDate),
endDate: new Date(endDate), endDate: new Date(endDate),
steps, steps,

View file

@ -15,6 +15,7 @@ export async function POST(request: Request) {
websiteId, websiteId,
dateRange: { startDate, endDate }, dateRange: { startDate, endDate },
parameters: { type, value, property, operator }, parameters: { type, value, property, operator },
...filters
} = body; } = body;
if (!(await canViewWebsite(auth, websiteId))) { if (!(await canViewWebsite(auth, websiteId))) {
@ -22,12 +23,13 @@ export async function POST(request: Request) {
} }
const data = await getGoal(websiteId, { const data = await getGoal(websiteId, {
...filters,
startDate: new Date(startDate),
endDate: new Date(endDate),
type, type,
value, value,
property, property,
operator, operator,
startDate: new Date(startDate),
endDate: new Date(endDate),
}); });
return json(data); return json(data);

View file

@ -15,6 +15,7 @@ export async function POST(request: Request) {
websiteId, websiteId,
dateRange: { startDate, endDate }, dateRange: { startDate, endDate },
parameters: { steps, startStep, endStep }, parameters: { steps, startStep, endStep },
...filters
} = body; } = body;
if (!(await canViewWebsite(auth, websiteId))) { if (!(await canViewWebsite(auth, websiteId))) {
@ -22,6 +23,7 @@ export async function POST(request: Request) {
} }
const data = await getJourney(websiteId, { const data = await getJourney(websiteId, {
...filters,
startDate: new Date(startDate), startDate: new Date(startDate),
endDate: new Date(endDate), endDate: new Date(endDate),
steps, steps,

View file

@ -14,6 +14,7 @@ export async function POST(request: Request) {
const { const {
websiteId, websiteId,
dateRange: { startDate, endDate, timezone }, dateRange: { startDate, endDate, timezone },
...filters
} = body; } = body;
if (!(await canViewWebsite(auth, websiteId))) { if (!(await canViewWebsite(auth, websiteId))) {
@ -21,6 +22,7 @@ export async function POST(request: Request) {
} }
const data = await getRetention(websiteId, { const data = await getRetention(websiteId, {
...filters,
startDate: new Date(startDate), startDate: new Date(startDate),
endDate: new Date(endDate), endDate: new Date(endDate),
timezone, timezone,

View file

@ -15,6 +15,7 @@ export async function POST(request: Request) {
websiteId, websiteId,
dateRange: { startDate, endDate, unit }, dateRange: { startDate, endDate, unit },
parameters: { currency }, parameters: { currency },
...filters
} = body; } = body;
if (!(await canViewWebsite(auth, websiteId))) { if (!(await canViewWebsite(auth, websiteId))) {
@ -22,6 +23,7 @@ export async function POST(request: Request) {
} }
const data = await getRevenue(websiteId, { const data = await getRevenue(websiteId, {
...filters,
startDate: new Date(startDate), startDate: new Date(startDate),
endDate: new Date(endDate), endDate: new Date(endDate),
unit, unit,

View file

@ -14,6 +14,7 @@ export async function POST(request: Request) {
const { const {
websiteId, websiteId,
dateRange: { startDate, endDate }, dateRange: { startDate, endDate },
...filters
} = body; } = body;
if (!(await canViewWebsite(auth, websiteId))) { if (!(await canViewWebsite(auth, websiteId))) {
@ -21,6 +22,7 @@ export async function POST(request: Request) {
} }
const data = await getUTM(websiteId, { const data = await getUTM(websiteId, {
...filters,
startDate: new Date(startDate), startDate: new Date(startDate),
endDate: new Date(endDate), endDate: new Date(endDate),
}); });

View file

@ -9,7 +9,7 @@ export function useResultQuery<T>(
) { ) {
const { websiteId } = params; const { websiteId } = params;
const { post, useQuery } = useApi(); const { post, useQuery } = useApi();
const filterParams = useFilterParams(websiteId); const filters = useFilterParams(websiteId);
return useQuery<T>({ return useQuery<T>({
queryKey: [ queryKey: [
@ -17,11 +17,11 @@ export function useResultQuery<T>(
{ {
type, type,
websiteId, websiteId,
...filterParams, ...filters,
...params, ...params,
}, },
], ],
queryFn: () => post(`/reports/${type}`, { type, ...filterParams, ...params }), queryFn: () => post(`/reports/${type}`, { type, ...filters, ...params }),
enabled: !!type, enabled: !!type,
...options, ...options,
}); });

View file

@ -21,6 +21,7 @@ export function useFilterParams(websiteId: string) {
city, city,
event, event,
tag, tag,
hostname,
}, },
} = useNavigation(); } = useNavigation();
@ -42,5 +43,6 @@ export function useFilterParams(websiteId: string) {
city, city,
event, event,
tag, tag,
hostname,
}; };
} }

View file

@ -25,7 +25,7 @@ export function usePagedQuery<T = any>({
return { return {
result: data as PageResult<T>, result: data as PageResult<T>,
query, query,
params, filterParams: params,
setParams, setParams,
}; };
} }

View file

@ -1,5 +1,5 @@
import { FloatingTooltip, Column, useTheme } from '@umami/react-zen'; import { FloatingTooltip, Column, useTheme, ColumnProps } from '@umami/react-zen';
import { useState, useMemo, HTMLAttributes } from 'react'; import { useState, useMemo } from 'react';
import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps'; import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps';
import classNames from 'classnames'; import classNames from 'classnames';
import { colord } from 'colord'; import { colord } from 'colord';
@ -16,16 +16,12 @@ import { percentFilter } from '@/lib/filters';
import styles from './WorldMap.module.css'; import styles from './WorldMap.module.css';
import { getThemeColors } from '@/lib/colors'; import { getThemeColors } from '@/lib/colors';
export function WorldMap({ export interface WorldMapProps extends ColumnProps {
websiteId,
data,
className,
...props
}: {
websiteId?: string; websiteId?: string;
data?: any[]; data?: any[];
className?: string; }
} & HTMLAttributes<HTMLDivElement>) {
export function WorldMap({ websiteId, data, className, ...props }: WorldMapProps) {
const [tooltip, setTooltipPopup] = useState(); const [tooltip, setTooltipPopup] = useState();
const { theme } = useTheme(); const { theme } = useTheme();
const { colors } = getThemeColors(theme); const { colors } = getThemeColors(theme);

View file

@ -141,7 +141,7 @@ async function parseFilters(websiteId: string, filters: QueryFilters = {}, optio
return { return {
filterQuery: getFilterQuery(filters, options), filterQuery: getFilterQuery(filters, options),
dateQuery: getDateQuery(filters), dateQuery: getDateQuery(filters),
params: { filterParams: {
...getFilterParams(filters), ...getFilterParams(filters),
websiteId, websiteId,
startDate: maxDate(filters.startDate, new Date(website?.resetAt)), startDate: maxDate(filters.startDate, new Date(website?.resetAt)),

View file

@ -215,7 +215,7 @@ async function parseFilters(
: '', : '',
filterQuery: getFilterQuery(filters, options), filterQuery: getFilterQuery(filters, options),
dateQuery: getDateQuery(filters), dateQuery: getDateQuery(filters),
params: { filterParams: {
...getFilterParams(filters), ...getFilterParams(filters),
websiteId, websiteId,
startDate: maxDate(filters.startDate, website?.resetAt), startDate: maxDate(filters.startDate, website?.resetAt),

View file

@ -23,7 +23,7 @@ export async function getEventDataEvents(
async function relationalQuery(websiteId: string, filters: QueryFilters) { async function relationalQuery(websiteId: string, filters: QueryFilters) {
const { rawQuery, parseFilters } = prisma; const { rawQuery, parseFilters } = prisma;
const { event } = filters; const { event } = filters;
const { params } = await parseFilters(websiteId, filters); const { filterParams } = await parseFilters(websiteId, filters);
if (event) { if (event) {
return rawQuery( return rawQuery(
@ -43,7 +43,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
group by website_event.event_name, event_data.data_key, event_data.data_type, event_data.string_value group by website_event.event_name, event_data.data_key, event_data.data_type, event_data.string_value
order by 1 asc, 2 asc, 3 asc, 5 desc order by 1 asc, 2 asc, 3 asc, 5 desc
`, `,
params, filterParams,
); );
} }
@ -63,7 +63,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
order by 1 asc, 2 asc order by 1 asc, 2 asc
limit 500 limit 500
`, `,
params, filterParams,
); );
} }
@ -73,7 +73,7 @@ async function clickhouseQuery(
): Promise<{ eventName: string; propertyName: string; dataType: number; total: number }[]> { ): Promise<{ eventName: string; propertyName: string; dataType: number; total: number }[]> {
const { rawQuery, parseFilters } = clickhouse; const { rawQuery, parseFilters } = clickhouse;
const { event } = filters; const { event } = filters;
const { params } = await parseFilters(websiteId, filters); const { filterParams } = await parseFilters(websiteId, filters);
if (event) { if (event) {
return rawQuery( return rawQuery(
@ -92,7 +92,7 @@ async function clickhouseQuery(
order by 1 asc, 2 asc, 3 asc, 5 desc order by 1 asc, 2 asc, 3 asc, 5 desc
limit 500 limit 500
`, `,
params, filterParams,
); );
} }
@ -110,6 +110,6 @@ async function clickhouseQuery(
order by 1 asc, 2 asc order by 1 asc, 2 asc
limit 500 limit 500
`, `,
params, filterParams,
); );
} }

View file

@ -1,11 +1,9 @@
import prisma from '@/lib/prisma'; import prisma from '@/lib/prisma';
import clickhouse from '@/lib/clickhouse'; import clickhouse from '@/lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db'; import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
import { QueryFilters, WebsiteEventData } from '@/lib/types'; import { QueryFilters } from '@/lib/types';
export async function getEventDataFields( export async function getEventDataFields(...args: [websiteId: string, filters: QueryFilters]) {
...args: [websiteId: string, filters: QueryFilters]
): Promise<WebsiteEventData[]> {
return runQuery({ return runQuery({
[PRISMA]: () => relationalQuery(...args), [PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args), [CLICKHOUSE]: () => clickhouseQuery(...args),
@ -14,7 +12,7 @@ export async function getEventDataFields(
async function relationalQuery(websiteId: string, filters: QueryFilters) { async function relationalQuery(websiteId: string, filters: QueryFilters) {
const { rawQuery, parseFilters, getDateSQL } = prisma; const { rawQuery, parseFilters, getDateSQL } = prisma;
const { filterQuery, params } = await parseFilters(websiteId, filters); const { filterQuery, filterParams } = await parseFilters(websiteId, filters);
return rawQuery( return rawQuery(
` `
@ -36,7 +34,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
order by 2 desc order by 2 desc
limit 100 limit 100
`, `,
params, filterParams,
); );
} }
@ -45,7 +43,7 @@ async function clickhouseQuery(
filters: QueryFilters, filters: QueryFilters,
): Promise<{ propertyName: string; dataType: number; propertyValue: string; total: number }[]> { ): Promise<{ propertyName: string; dataType: number; propertyValue: string; total: number }[]> {
const { rawQuery, parseFilters } = clickhouse; const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, filters); const { filterQuery, filterParams } = await parseFilters(websiteId, filters);
return rawQuery( return rawQuery(
` `
@ -64,6 +62,6 @@ async function clickhouseQuery(
order by 2 desc order by 2 desc
limit 100 limit 100
`, `,
params, filterParams,
); );
} }

View file

@ -17,7 +17,7 @@ async function relationalQuery(
filters: QueryFilters & { propertyName?: string }, filters: QueryFilters & { propertyName?: string },
) { ) {
const { rawQuery, parseFilters } = prisma; const { rawQuery, parseFilters } = prisma;
const { filterQuery, params } = await parseFilters(websiteId, filters, { const { filterQuery, filterParams } = await parseFilters(websiteId, filters, {
columns: { propertyName: 'data_key' }, columns: { propertyName: 'data_key' },
}); });
@ -36,7 +36,7 @@ async function relationalQuery(
order by 3 desc order by 3 desc
limit 500 limit 500
`, `,
params, filterParams,
); );
} }
@ -45,7 +45,7 @@ async function clickhouseQuery(
filters: QueryFilters & { propertyName?: string }, filters: QueryFilters & { propertyName?: string },
): Promise<{ eventName: string; propertyName: string; total: number }[]> { ): Promise<{ eventName: string; propertyName: string; total: number }[]> {
const { rawQuery, parseFilters } = clickhouse; const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, filters, { const { filterQuery, filterParams } = await parseFilters(websiteId, filters, {
columns: { propertyName: 'data_key' }, columns: { propertyName: 'data_key' },
}); });
@ -63,6 +63,6 @@ async function clickhouseQuery(
order by 1, 3 desc order by 1, 3 desc
limit 500 limit 500
`, `,
params, filterParams,
); );
} }

View file

@ -18,7 +18,7 @@ export async function getEventDataStats(
async function relationalQuery(websiteId: string, filters: QueryFilters) { async function relationalQuery(websiteId: string, filters: QueryFilters) {
const { rawQuery, parseFilters } = prisma; const { rawQuery, parseFilters } = prisma;
const { filterQuery, params } = await parseFilters(websiteId, filters); const { filterQuery, filterParams } = await parseFilters(websiteId, filters);
return rawQuery( return rawQuery(
` `
@ -38,7 +38,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
group by website_event_id, data_key group by website_event_id, data_key
) as t ) as t
`, `,
params, filterParams,
); );
} }
@ -47,7 +47,7 @@ async function clickhouseQuery(
filters: QueryFilters, filters: QueryFilters,
): Promise<{ events: number; properties: number; records: number }[]> { ): Promise<{ events: number; properties: number; records: number }[]> {
const { rawQuery, parseFilters } = clickhouse; const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, filters); const { filterQuery, filterParams } = await parseFilters(websiteId, filters);
return rawQuery( return rawQuery(
` `
@ -67,6 +67,6 @@ async function clickhouseQuery(
group by event_id, data_key group by event_id, data_key
) as t ) as t
`, `,
params, filterParams,
); );
} }

View file

@ -1,7 +1,12 @@
import prisma from '@/lib/prisma'; import prisma from '@/lib/prisma';
import clickhouse from '@/lib/clickhouse'; import clickhouse from '@/lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db'; import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
import { QueryFilters, WebsiteEventData } from '@/lib/types'; import { QueryFilters } from '@/lib/types';
export interface WebsiteEventData {
value: string;
total: number;
}
export async function getEventDataValues( export async function getEventDataValues(
...args: [ ...args: [
@ -20,7 +25,7 @@ async function relationalQuery(
filters: QueryFilters & { eventName?: string; propertyName?: string }, filters: QueryFilters & { eventName?: string; propertyName?: string },
) { ) {
const { rawQuery, parseFilters, getDateSQL } = prisma; const { rawQuery, parseFilters, getDateSQL } = prisma;
const { filterQuery, params } = await parseFilters(websiteId, filters); const { filterQuery, filterParams } = await parseFilters(websiteId, filters);
return rawQuery( return rawQuery(
` `
@ -42,7 +47,7 @@ async function relationalQuery(
order by 2 desc order by 2 desc
limit 100 limit 100
`, `,
params, filterParams,
); );
} }
@ -51,7 +56,7 @@ async function clickhouseQuery(
filters: QueryFilters & { eventName?: string; propertyName?: string }, filters: QueryFilters & { eventName?: string; propertyName?: string },
): Promise<{ value: string; total: number }[]> { ): Promise<{ value: string; total: number }[]> {
const { rawQuery, parseFilters } = clickhouse; const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, filters); const { filterQuery, filterParams } = await parseFilters(websiteId, filters);
return rawQuery( return rawQuery(
` `
@ -70,6 +75,6 @@ async function clickhouseQuery(
order by 2 desc order by 2 desc
limit 100 limit 100
`, `,
params, filterParams,
); );
} }

View file

@ -22,7 +22,7 @@ export async function getEventMetrics(
async function relationalQuery(websiteId: string, filters: QueryFilters) { async function relationalQuery(websiteId: string, filters: QueryFilters) {
const { timezone = 'utc', unit = 'day' } = filters; const { timezone = 'utc', unit = 'day' } = filters;
const { rawQuery, getDateSQL, parseFilters } = prisma; const { rawQuery, getDateSQL, parseFilters } = prisma;
const { filterQuery, joinSession, params } = await parseFilters(websiteId, { const { filterQuery, joinSession, filterParams } = await parseFilters(websiteId, {
...filters, ...filters,
eventType: EVENT_TYPE.customEvent, eventType: EVENT_TYPE.customEvent,
}); });
@ -42,7 +42,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
group by 1, 2 group by 1, 2
order by 2 order by 2
`, `,
params, filterParams,
); );
} }
@ -52,7 +52,7 @@ async function clickhouseQuery(
): Promise<{ x: string; t: string; y: number }[]> { ): Promise<{ x: string; t: string; y: number }[]> {
const { timezone = 'UTC', unit = 'day' } = filters; const { timezone = 'UTC', unit = 'day' } = filters;
const { rawQuery, getDateSQL, parseFilters } = clickhouse; const { rawQuery, getDateSQL, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, { const { filterQuery, filterParams } = await parseFilters(websiteId, {
...filters, ...filters,
eventType: EVENT_TYPE.customEvent, eventType: EVENT_TYPE.customEvent,
}); });
@ -92,5 +92,5 @@ async function clickhouseQuery(
`; `;
} }
return rawQuery(sql, params); return rawQuery(sql, filterParams);
} }

View file

@ -15,7 +15,7 @@ export function getWebsiteEvents(
async function relationalQuery(websiteId: string, filters: QueryFilters, pageParams?: PageParams) { async function relationalQuery(websiteId: string, filters: QueryFilters, pageParams?: PageParams) {
const { pagedRawQuery, parseFilters } = prisma; const { pagedRawQuery, parseFilters } = prisma;
const { search } = pageParams; const { search } = pageParams;
const { filterQuery, params } = await parseFilters(websiteId, { const { filterQuery, filterParams } = await parseFilters(websiteId, {
...filters, ...filters,
}); });
@ -49,14 +49,14 @@ async function relationalQuery(websiteId: string, filters: QueryFilters, pagePar
} }
order by created_at desc order by created_at desc
`, `,
{ ...params, search: `%${search}%` }, { ...filterParams, search: `%${search}%` },
pageParams, pageParams,
); );
} }
async function clickhouseQuery(websiteId: string, filters: QueryFilters, pageParams?: PageParams) { async function clickhouseQuery(websiteId: string, filters: QueryFilters, pageParams?: PageParams) {
const { pagedQuery, parseFilters } = clickhouse; const { pagedQuery, parseFilters } = clickhouse;
const { params, dateQuery, filterQuery } = await parseFilters(websiteId, filters); const { filterParams, dateQuery, filterQuery } = await parseFilters(websiteId, filters);
const { search } = pageParams; const { search } = pageParams;
return pagedQuery( return pagedQuery(
@ -86,7 +86,7 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters, pagePar
} }
order by created_at desc order by created_at desc
`, `,
{ ...params, search }, { ...filterParams, search },
pageParams, pageParams,
); );
} }

View file

@ -12,7 +12,7 @@ export async function getChannelMetrics(...args: [websiteId: string, filters?: Q
async function relationalQuery(websiteId: string, filters: QueryFilters) { async function relationalQuery(websiteId: string, filters: QueryFilters) {
const { rawQuery, parseFilters } = prisma; const { rawQuery, parseFilters } = prisma;
const { params, filterQuery, dateQuery } = await parseFilters(websiteId, filters); const { filterParams, filterQuery, dateQuery } = await parseFilters(websiteId, filters);
return rawQuery( return rawQuery(
` `
@ -27,7 +27,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
group by 1, 2 group by 1, 2
order by visitors desc order by visitors desc
`, `,
params, filterParams,
); );
} }
@ -36,7 +36,7 @@ async function clickhouseQuery(
filters: QueryFilters, filters: QueryFilters,
): Promise<{ x: string; y: number }[]> { ): Promise<{ x: string; y: number }[]> {
const { rawQuery, parseFilters } = clickhouse; const { rawQuery, parseFilters } = clickhouse;
const { params, filterQuery, dateQuery } = await parseFilters(websiteId, filters); const { filterParams, filterQuery, dateQuery } = await parseFilters(websiteId, filters);
const sql = ` const sql = `
select select
@ -51,5 +51,5 @@ async function clickhouseQuery(
order by visitors desc order by visitors desc
`; `;
return rawQuery(sql, params); return rawQuery(sql, filterParams);
} }

View file

@ -12,7 +12,7 @@ export async function getRealtimeActivity(...args: [websiteId: string, filters:
async function relationalQuery(websiteId: string, filters: QueryFilters) { async function relationalQuery(websiteId: string, filters: QueryFilters) {
const { rawQuery, parseFilters } = prisma; const { rawQuery, parseFilters } = prisma;
const { params, filterQuery, dateQuery } = await parseFilters(websiteId, filters); const { filterParams, filterQuery, dateQuery } = await parseFilters(websiteId, filters);
return rawQuery( return rawQuery(
` `
@ -35,13 +35,13 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
order by website_event.created_at desc order by website_event.created_at desc
limit 100 limit 100
`, `,
params, filterParams,
); );
} }
async function clickhouseQuery(websiteId: string, filters: QueryFilters): Promise<{ x: number }> { async function clickhouseQuery(websiteId: string, filters: QueryFilters): Promise<{ x: number }> {
const { rawQuery, parseFilters } = clickhouse; const { rawQuery, parseFilters } = clickhouse;
const { params, filterQuery, dateQuery } = await parseFilters(websiteId, filters); const { filterParams, filterQuery, dateQuery } = await parseFilters(websiteId, filters);
return rawQuery( return rawQuery(
` `
@ -62,6 +62,6 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters): Promis
order by createdAt desc order by createdAt desc
limit 100 limit 100
`, `,
{ ...filters, ...params }, { ...filters, ...filterParams },
); );
} }

View file

@ -12,7 +12,9 @@ export async function getWebsiteDateRange(...args: [websiteId: string]) {
async function relationalQuery(websiteId: string) { async function relationalQuery(websiteId: string) {
const { rawQuery, parseFilters } = prisma; const { rawQuery, parseFilters } = prisma;
const { params } = await parseFilters(websiteId, { startDate: new Date(DEFAULT_RESET_DATE) }); const { filterParams } = await parseFilters(websiteId, {
startDate: new Date(DEFAULT_RESET_DATE),
});
const result = await rawQuery( const result = await rawQuery(
` `
@ -23,7 +25,7 @@ async function relationalQuery(websiteId: string) {
where website_id = {{websiteId::uuid}} where website_id = {{websiteId::uuid}}
and created_at >= {{startDate}} and created_at >= {{startDate}}
`, `,
params, filterParams,
); );
return result[0] ?? null; return result[0] ?? null;
@ -31,7 +33,9 @@ async function relationalQuery(websiteId: string) {
async function clickhouseQuery(websiteId: string) { async function clickhouseQuery(websiteId: string) {
const { rawQuery, parseFilters } = clickhouse; const { rawQuery, parseFilters } = clickhouse;
const { params } = await parseFilters(websiteId, { startDate: new Date(DEFAULT_RESET_DATE) }); const { filterParams } = await parseFilters(websiteId, {
startDate: new Date(DEFAULT_RESET_DATE),
});
const result = await rawQuery( const result = await rawQuery(
` `
@ -42,7 +46,7 @@ async function clickhouseQuery(websiteId: string) {
where website_id = {websiteId:UUID} where website_id = {websiteId:UUID}
and created_at >= {startDate:DateTime64} and created_at >= {startDate:DateTime64}
`, `,
params, filterParams,
); );
return result[0] ?? null; return result[0] ?? null;

View file

@ -23,7 +23,7 @@ async function relationalQuery(
{ pageviews: number; visitors: number; visits: number; bounces: number; totaltime: number }[] { pageviews: number; visitors: number; visits: number; bounces: number; totaltime: number }[]
> { > {
const { getTimestampDiffSQL, parseFilters, rawQuery } = prisma; const { getTimestampDiffSQL, parseFilters, rawQuery } = prisma;
const { filterQuery, joinSession, params } = await parseFilters(websiteId, { const { filterQuery, joinSession, filterParams } = await parseFilters(websiteId, {
...filters, ...filters,
eventType: EVENT_TYPE.pageView, eventType: EVENT_TYPE.pageView,
}); });
@ -52,7 +52,7 @@ async function relationalQuery(
group by 1, 2 group by 1, 2
) as t ) as t
`, `,
params, filterParams,
); );
} }
@ -63,7 +63,7 @@ async function clickhouseQuery(
{ pageviews: number; visitors: number; visits: number; bounces: number; totaltime: number }[] { pageviews: number; visitors: number; visits: number; bounces: number; totaltime: number }[]
> { > {
const { rawQuery, parseFilters } = clickhouse; const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, { const { filterQuery, filterParams } = await parseFilters(websiteId, {
...filters, ...filters,
eventType: EVENT_TYPE.pageView, eventType: EVENT_TYPE.pageView,
}); });
@ -117,5 +117,5 @@ async function clickhouseQuery(
`; `;
} }
return rawQuery(sql, params).then(result => result?.[0]); return rawQuery(sql, filterParams).then(result => result?.[0]);
} }

View file

@ -28,7 +28,7 @@ async function relationalQuery(
) { ) {
const column = FILTER_COLUMNS[type] || type; const column = FILTER_COLUMNS[type] || type;
const { rawQuery, parseFilters } = prisma; const { rawQuery, parseFilters } = prisma;
const { filterQuery, joinSession, params } = await parseFilters( const { filterQuery, joinSession, filterParams } = await parseFilters(
websiteId, websiteId,
{ {
...filters, ...filters,
@ -82,7 +82,7 @@ async function relationalQuery(
limit ${limit} limit ${limit}
offset ${offset} offset ${offset}
`, `,
params, filterParams,
); );
} }
@ -95,7 +95,7 @@ async function clickhouseQuery(
): Promise<{ x: string; y: number }[]> { ): Promise<{ x: string; y: number }[]> {
const column = FILTER_COLUMNS[type] || type; const column = FILTER_COLUMNS[type] || type;
const { rawQuery, parseFilters } = clickhouse; const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, { const { filterQuery, filterParams } = await parseFilters(websiteId, {
...filters, ...filters,
eventType: column === 'event_name' ? EVENT_TYPE.customEvent : EVENT_TYPE.pageView, eventType: column === 'event_name' ? EVENT_TYPE.customEvent : EVENT_TYPE.pageView,
}); });
@ -180,5 +180,5 @@ async function clickhouseQuery(
`; `;
} }
return rawQuery(sql, params); return rawQuery(sql, filterParams);
} }

View file

@ -14,7 +14,7 @@ export async function getPageviewStats(...args: [websiteId: string, filters: Que
async function relationalQuery(websiteId: string, filters: QueryFilters) { async function relationalQuery(websiteId: string, filters: QueryFilters) {
const { timezone = 'utc', unit = 'day' } = filters; const { timezone = 'utc', unit = 'day' } = filters;
const { getDateSQL, parseFilters, rawQuery } = prisma; const { getDateSQL, parseFilters, rawQuery } = prisma;
const { filterQuery, joinSession, params } = await parseFilters(websiteId, { const { filterQuery, joinSession, filterParams } = await parseFilters(websiteId, {
...filters, ...filters,
eventType: EVENT_TYPE.pageView, eventType: EVENT_TYPE.pageView,
}); });
@ -33,7 +33,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
group by 1 group by 1
order by 1 order by 1
`, `,
params, filterParams,
); );
} }
@ -43,7 +43,7 @@ async function clickhouseQuery(
): Promise<{ x: string; y: number }[]> { ): Promise<{ x: string; y: number }[]> {
const { timezone = 'utc', unit = 'day' } = filters; const { timezone = 'utc', unit = 'day' } = filters;
const { parseFilters, rawQuery, getDateSQL } = clickhouse; const { parseFilters, rawQuery, getDateSQL } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, { const { filterQuery, filterParams } = await parseFilters(websiteId, {
...filters, ...filters,
eventType: EVENT_TYPE.pageView, eventType: EVENT_TYPE.pageView,
}); });
@ -88,5 +88,5 @@ async function clickhouseQuery(
`; `;
} }
return rawQuery(sql, params); return rawQuery(sql, filterParams);
} }

View file

@ -1,6 +1,6 @@
import clickhouse from '@/lib/clickhouse'; import clickhouse from '@/lib/clickhouse';
import { EVENT_TYPE } from '@/lib/constants'; 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 prisma from '@/lib/prisma';
export interface AttributionCriteria { export interface AttributionCriteria {
@ -38,8 +38,6 @@ async function relationalQuery(
const { rawQuery } = prisma; const { rawQuery } = prisma;
const eventType = type === 'page' ? EVENT_TYPE.pageView : EVENT_TYPE.customEvent; const eventType = type === 'page' ? EVENT_TYPE.pageView : EVENT_TYPE.customEvent;
const column = type === 'page' ? 'url_path' : 'event_name'; const column = type === 'page' ? 'url_path' : 'event_name';
const db = getDatabaseType();
const like = db === POSTGRESQL ? 'ilike' : 'like';
function getUTMQuery(utmColumn: string) { function getUTMQuery(utmColumn: string) {
return ` return `
@ -79,7 +77,7 @@ async function relationalQuery(
where website_id = {{websiteId::uuid}} where website_id = {{websiteId::uuid}}
and created_at between {{startDate}} and {{endDate}} and created_at between {{startDate}} and {{endDate}}
and ${column} = {{conversionStep}} and ${column} = {{conversionStep}}
and currency ${like} {{currency}} and currency = {{currency}}
group by 1),`; group by 1),`;
function getModelQuery(model: string) { function getModelQuery(model: string) {
@ -243,26 +241,57 @@ async function clickhouseQuery(
criteria: AttributionCriteria, criteria: AttributionCriteria,
): Promise<AttributionResult> { ): Promise<AttributionResult> {
const { startDate, endDate, model, type, step, currency } = criteria; const { startDate, endDate, model, type, step, currency } = criteria;
const { rawQuery } = clickhouse; const { rawQuery, parseFilters } = clickhouse;
const eventType = type === 'page' ? EVENT_TYPE.pageView : EVENT_TYPE.customEvent; const eventType = type === 'page' ? EVENT_TYPE.pageView : EVENT_TYPE.customEvent;
const column = type === 'page' ? 'url_path' : 'event_name'; const column = type === 'page' ? 'url_path' : 'event_name';
const { filterQuery, filterParams } = await parseFilters(websiteId, criteria);
function getUTMQuery(utmColumn: string) { function getUTMQuery(utmColumn: string) {
return ` return `
select select
we.${utmColumn} name, we.${utmColumn} name,
${currency ? 'sum(e.value)' : 'uniqExact(we.session_id)'} value ${currency ? 'sum(e.value)' : 'uniqExact(we.session_id)'} value
from model m from model m
join website_event we join website_event we
on we.created_at = m.created_at on we.created_at = m.created_at
and we.session_id = m.session_id and we.session_id = m.session_id
${currency ? 'join events e on e.session_id = m.session_id' : ''} ${currency ? 'join events e on e.session_id = m.session_id' : ''}
where we.website_id = {websiteId:UUID} 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} and we.created_at between {startDate:DateTime64} and {endDate:DateTime64}
${currency ? '' : `and we.${utmColumn} != ''`} ${filterQuery}
group by 1 group by e.session_id)
order by 2 desc `;
limit 20`; }
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
${filterQuery}
group by e.session_id)
`;
} }
const eventQuery = `WITH events AS ( const eventQuery = `WITH events AS (
@ -288,29 +317,6 @@ async function clickhouseQuery(
and currency = {currency:String} and currency = {currency:String}
group by 1),`; group by 1),`;
function getModelQuery(model: string) {
return model === 'first-click'
? `\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< const referrerRes = await rawQuery<
{ {
name: string; name: string;
@ -339,7 +345,7 @@ async function clickhouseQuery(
order by 2 desc order by 2 desc
limit 20 limit 20
`, `,
{ websiteId, startDate, endDate, conversionStep: step, eventType, currency }, { ...filterParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
); );
const paidAdsres = await rawQuery< const paidAdsres = await rawQuery<
@ -370,7 +376,7 @@ async function clickhouseQuery(
order by 2 desc order by 2 desc
limit 20 limit 20
`, `,
{ websiteId, startDate, endDate, conversionStep: step, eventType, currency }, { ...filterParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
); );
const sourceRes = await rawQuery< const sourceRes = await rawQuery<
@ -384,7 +390,7 @@ async function clickhouseQuery(
${getModelQuery(model)} ${getModelQuery(model)}
${getUTMQuery('utm_source')} ${getUTMQuery('utm_source')}
`, `,
{ websiteId, startDate, endDate, conversionStep: step, eventType, currency }, { ...filterParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
); );
const mediumRes = await rawQuery< const mediumRes = await rawQuery<
@ -398,7 +404,7 @@ async function clickhouseQuery(
${getModelQuery(model)} ${getModelQuery(model)}
${getUTMQuery('utm_medium')} ${getUTMQuery('utm_medium')}
`, `,
{ websiteId, startDate, endDate, conversionStep: step, eventType, currency }, { ...filterParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
); );
const campaignRes = await rawQuery< const campaignRes = await rawQuery<
@ -412,7 +418,7 @@ async function clickhouseQuery(
${getModelQuery(model)} ${getModelQuery(model)}
${getUTMQuery('utm_campaign')} ${getUTMQuery('utm_campaign')}
`, `,
{ websiteId, startDate, endDate, conversionStep: step, eventType, currency }, { ...filterParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
); );
const contentRes = await rawQuery< const contentRes = await rawQuery<
@ -426,7 +432,7 @@ async function clickhouseQuery(
${getModelQuery(model)} ${getModelQuery(model)}
${getUTMQuery('utm_content')} ${getUTMQuery('utm_content')}
`, `,
{ websiteId, startDate, endDate, conversionStep: step, eventType, currency }, { ...filterParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
); );
const termRes = await rawQuery< const termRes = await rawQuery<
@ -440,7 +446,7 @@ async function clickhouseQuery(
${getModelQuery(model)} ${getModelQuery(model)}
${getUTMQuery('utm_term')} ${getUTMQuery('utm_term')}
`, `,
{ websiteId, startDate, endDate, conversionStep: step, eventType, currency }, { ...filterParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
); );
const totalRes = await rawQuery<{ pageviews: number; visitors: number; visits: number }>( const totalRes = await rawQuery<{ pageviews: number; visitors: number; visits: number }>(
@ -454,8 +460,9 @@ async function clickhouseQuery(
and created_at between {startDate:DateTime64} and {endDate:DateTime64} and created_at between {startDate:DateTime64} and {endDate:DateTime64}
and ${column} = {conversionStep:String} and ${column} = {conversionStep:String}
and event_type = {eventType:UInt32} and event_type = {eventType:UInt32}
${filterQuery}
`, `,
{ websiteId, startDate, endDate, conversionStep: step, eventType, currency }, { ...filterParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
).then(result => result?.[0]); ).then(result => result?.[0]);
return { return {

View file

@ -24,7 +24,7 @@ async function relationalQuery(
}[] }[]
> { > {
const { getTimestampDiffSQL, parseFilters, rawQuery } = prisma; const { getTimestampDiffSQL, parseFilters, rawQuery } = prisma;
const { filterQuery, joinSession, params } = await parseFilters( const { filterQuery, joinSession, filterParams } = await parseFilters(
websiteId, websiteId,
{ {
...filters, ...filters,
@ -65,7 +65,7 @@ async function relationalQuery(
order by 1 desc, 2 desc order by 1 desc, 2 desc
limit 500 limit 500
`, `,
params, filterParams,
); );
} }
@ -80,7 +80,7 @@ async function clickhouseQuery(
}[] }[]
> { > {
const { parseFilters, rawQuery } = clickhouse; const { parseFilters, rawQuery } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, { const { filterQuery, filterParams } = await parseFilters(websiteId, {
...filters, ...filters,
eventType: EVENT_TYPE.pageView, eventType: EVENT_TYPE.pageView,
}); });
@ -114,7 +114,7 @@ async function clickhouseQuery(
order by 1 desc, 2 desc order by 1 desc, 2 desc
limit 500 limit 500
`, `,
params, filterParams,
); );
} }

View file

@ -121,11 +121,12 @@ async function clickhouseQuery(
}[] }[]
> { > {
const { windowMinutes, startDate, endDate, steps } = criteria; const { windowMinutes, startDate, endDate, steps } = criteria;
const { rawQuery } = clickhouse; const { rawQuery, parseFilters } = clickhouse;
const { levelOneQuery, levelQuery, sumQuery, stepFilterQuery, params } = getFunnelQuery( const { levelOneQuery, levelQuery, sumQuery, stepFilterQuery, params } = getFunnelQuery(
steps, steps,
windowMinutes, windowMinutes,
); );
const { filterQuery, filterParams: filterParams } = await parseFilters(websiteId, criteria);
function getFunnelQuery( function getFunnelQuery(
steps: { type: string; value: string }[], steps: { type: string; value: string }[],
@ -200,6 +201,7 @@ async function clickhouseQuery(
where (${stepFilterQuery}) where (${stepFilterQuery})
and website_id = {websiteId:UUID} and website_id = {websiteId:UUID}
and created_at between {startDate:DateTime64} and {endDate:DateTime64} and created_at between {startDate:DateTime64} and {endDate:DateTime64}
${filterQuery}
), ),
${levelOneQuery} ${levelOneQuery}
${levelQuery} ${levelQuery}
@ -213,6 +215,7 @@ async function clickhouseQuery(
startDate, startDate,
endDate, endDate,
...params, ...params,
...filterParams,
}, },
).then(formatResults(steps)); ).then(formatResults(steps));
} }

View file

@ -21,7 +21,7 @@ export async function getGoal(...args: [websiteId: string, criteria: GoalCriteri
async function relationalQuery(websiteId: string, criteria: GoalCriteria) { async function relationalQuery(websiteId: string, criteria: GoalCriteria) {
const { type, value } = criteria; const { type, value } = criteria;
const { rawQuery, parseFilters } = prisma; const { rawQuery, parseFilters } = prisma;
const { filterQuery, dateQuery, params } = await parseFilters(websiteId, criteria); const { filterQuery, dateQuery, filterParams } = await parseFilters(websiteId, criteria);
const isPage = type === 'page'; const isPage = type === 'page';
const column = isPage ? 'url_path' : 'event_name'; const column = isPage ? 'url_path' : 'event_name';
const eventType = isPage ? 1 : 2; const eventType = isPage ? 1 : 2;
@ -43,14 +43,14 @@ async function relationalQuery(websiteId: string, criteria: GoalCriteria) {
${dateQuery} ${dateQuery}
${filterQuery} ${filterQuery}
`, `,
{ ...params, value }, { ...filterParams, value },
); );
} }
async function clickhouseQuery(websiteId: string, criteria: GoalCriteria) { async function clickhouseQuery(websiteId: string, criteria: GoalCriteria) {
const { type, value } = criteria; const { type, value } = criteria;
const { rawQuery, parseFilters } = clickhouse; const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, dateQuery, params } = await parseFilters(websiteId, criteria); const { filterQuery, dateQuery, filterParams } = await parseFilters(websiteId, criteria);
const isPage = type === 'page'; const isPage = type === 'page';
const column = isPage ? 'url_path' : 'event_name'; const column = isPage ? 'url_path' : 'event_name';
const eventType = isPage ? 1 : 2; const eventType = isPage ? 1 : 2;
@ -71,6 +71,6 @@ async function clickhouseQuery(websiteId: string, criteria: GoalCriteria) {
${dateQuery} ${dateQuery}
${filterQuery} ${filterQuery}
`, `,
{ ...params, value }, { ...filterParams, value },
).then(results => results?.[0]); ).then(results => results?.[0]);
} }

View file

@ -108,7 +108,7 @@ async function relationalQuery(
sequenceQuery, sequenceQuery,
startStepQuery, startStepQuery,
endStepQuery, endStepQuery,
params, filterParams: params,
}; };
} }
@ -152,7 +152,7 @@ async function clickhouseQuery(
}, },
): Promise<JourneyResult[]> { ): Promise<JourneyResult[]> {
const { startDate, endDate, steps, startStep, endStep } = filters; const { startDate, endDate, steps, startStep, endStep } = filters;
const { rawQuery } = clickhouse; const { rawQuery, parseFilters } = clickhouse;
const { sequenceQuery, startStepQuery, endStepQuery, params } = getJourneyQuery( const { sequenceQuery, startStepQuery, endStepQuery, params } = getJourneyQuery(
steps, steps,
startStep, startStep,
@ -218,10 +218,12 @@ async function clickhouseQuery(
sequenceQuery, sequenceQuery,
startStepQuery, startStepQuery,
endStepQuery, endStepQuery,
params, filterParams: params,
}; };
} }
const { filterQuery, filterParams: filterParams } = await parseFilters(websiteId, filters);
return rawQuery( return rawQuery(
` `
WITH events AS ( WITH events AS (
@ -231,6 +233,7 @@ async function clickhouseQuery(
row_number() OVER (PARTITION BY visit_id ORDER BY created_at) AS event_number row_number() OVER (PARTITION BY visit_id ORDER BY created_at) AS event_number
from umami.website_event from umami.website_event
where website_id = {websiteId:UUID} where website_id = {websiteId:UUID}
${filterQuery}
and created_at between {startDate:DateTime64} and {endDate:DateTime64}), and created_at between {startDate:DateTime64} and {endDate:DateTime64}),
${sequenceQuery} ${sequenceQuery}
select * select *
@ -246,6 +249,7 @@ async function clickhouseQuery(
startDate, startDate,
endDate, endDate,
...params, ...params,
...filterParams,
}, },
).then(parseResult); ).then(parseResult);
} }

View file

@ -28,9 +28,11 @@ async function relationalQuery(
criteria: RetentionCriteria, criteria: RetentionCriteria,
): Promise<RetentionResult[]> { ): Promise<RetentionResult[]> {
const { startDate, endDate, timezone } = criteria; const { startDate, endDate, timezone } = criteria;
const { getDateSQL, getDayDiffQuery, getCastColumnQuery, rawQuery } = prisma; const { getDateSQL, getDayDiffQuery, getCastColumnQuery, rawQuery, parseFilters } = prisma;
const unit = 'day'; const unit = 'day';
const { filterQuery, filterParams } = await parseFilters(websiteId, criteria);
return rawQuery( return rawQuery(
` `
WITH cohort_items AS ( WITH cohort_items AS (
@ -49,6 +51,7 @@ async function relationalQuery(
on w.session_id = c.session_id on w.session_id = c.session_id
where website_id = {{websiteId::uuid}} where website_id = {{websiteId::uuid}}
and created_at between {{startDate}} and {{endDate}} and created_at between {{startDate}} and {{endDate}}
${filterQuery}
), ),
cohort_size as ( cohort_size as (
select cohort_date, select cohort_date,
@ -82,6 +85,7 @@ async function relationalQuery(
websiteId, websiteId,
startDate, startDate,
endDate, endDate,
...filterParams,
}, },
); );
} }
@ -91,9 +95,11 @@ async function clickhouseQuery(
criteria: RetentionCriteria, criteria: RetentionCriteria,
): Promise<RetentionResult[]> { ): Promise<RetentionResult[]> {
const { startDate, endDate, timezone } = criteria; const { startDate, endDate, timezone } = criteria;
const { getDateSQL, rawQuery } = clickhouse; const { getDateSQL, rawQuery, parseFilters } = clickhouse;
const unit = 'day'; const unit = 'day';
const { filterQuery, filterParams } = await parseFilters(websiteId, criteria);
return rawQuery( return rawQuery(
` `
WITH cohort_items AS ( WITH cohort_items AS (
@ -114,6 +120,7 @@ async function clickhouseQuery(
on w.session_id = c.session_id on w.session_id = c.session_id
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}
), ),
cohort_size as ( cohort_size as (
select cohort_date, select cohort_date,
@ -147,6 +154,7 @@ async function clickhouseQuery(
websiteId, websiteId,
startDate, startDate,
endDate, endDate,
...filterParams,
}, },
); );
} }

View file

@ -16,7 +16,8 @@ export async function getUTM(...args: [websiteId: string, criteria: UTMCriteria]
async function relationalQuery(websiteId: string, criteria: UTMCriteria) { async function relationalQuery(websiteId: string, criteria: UTMCriteria) {
const { startDate, endDate } = criteria; const { startDate, endDate } = criteria;
const { rawQuery } = prisma; const { rawQuery, parseFilters } = prisma;
const { filterQuery, filterParams } = await parseFilters(websiteId, criteria);
return rawQuery( return rawQuery(
` `
@ -26,9 +27,11 @@ async function relationalQuery(websiteId: string, criteria: UTMCriteria) {
and created_at between {{startDate}} and {{endDate}} and created_at between {{startDate}} and {{endDate}}
and coalesce(url_query, '') != '' and coalesce(url_query, '') != ''
and event_type = 1 and event_type = 1
${filterQuery}
group by 1 group by 1
`, `,
{ {
...filterParams,
websiteId, websiteId,
startDate, startDate,
endDate, endDate,
@ -38,7 +41,8 @@ async function relationalQuery(websiteId: string, criteria: UTMCriteria) {
async function clickhouseQuery(websiteId: string, criteria: UTMCriteria) { async function clickhouseQuery(websiteId: string, criteria: UTMCriteria) {
const { startDate, endDate } = criteria; const { startDate, endDate } = criteria;
const { rawQuery } = clickhouse; const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, filterParams } = await parseFilters(websiteId, criteria);
return rawQuery( return rawQuery(
` `
@ -48,9 +52,11 @@ async function clickhouseQuery(websiteId: string, criteria: UTMCriteria) {
and created_at between {startDate:DateTime64} and {endDate:DateTime64} and created_at between {startDate:DateTime64} and {endDate:DateTime64}
and url_query != '' and url_query != ''
and event_type = 1 and event_type = 1
${filterQuery}
group by 1 group by 1
`, `,
{ {
...filterParams,
websiteId, websiteId,
startDate, startDate,
endDate, endDate,

View file

@ -1,11 +1,11 @@
import prisma from '@/lib/prisma'; import prisma from '@/lib/prisma';
import clickhouse from '@/lib/clickhouse'; import clickhouse from '@/lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db'; import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
import { QueryFilters, WebsiteEventData } from '@/lib/types'; import { QueryFilters } from '@/lib/types';
export async function getSessionDataProperties( export async function getSessionDataProperties(
...args: [websiteId: string, filters: QueryFilters & { propertyName?: string }] ...args: [websiteId: string, filters: QueryFilters & { propertyName?: string }]
): Promise<WebsiteEventData[]> { ) {
return runQuery({ return runQuery({
[PRISMA]: () => relationalQuery(...args), [PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args), [CLICKHOUSE]: () => clickhouseQuery(...args),
@ -17,7 +17,7 @@ async function relationalQuery(
filters: QueryFilters & { propertyName?: string }, filters: QueryFilters & { propertyName?: string },
) { ) {
const { rawQuery, parseFilters } = prisma; const { rawQuery, parseFilters } = prisma;
const { filterQuery, params } = await parseFilters(websiteId, filters, { const { filterQuery, filterParams } = await parseFilters(websiteId, filters, {
columns: { propertyName: 'data_key' }, columns: { propertyName: 'data_key' },
}); });
@ -36,7 +36,7 @@ async function relationalQuery(
order by 2 desc order by 2 desc
limit 500 limit 500
`, `,
params, filterParams,
); );
} }
@ -45,7 +45,7 @@ async function clickhouseQuery(
filters: QueryFilters & { propertyName?: string }, filters: QueryFilters & { propertyName?: string },
): Promise<{ propertyName: string; total: number }[]> { ): Promise<{ propertyName: string; total: number }[]> {
const { rawQuery, parseFilters } = clickhouse; const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, filters, { const { filterQuery, filterParams } = await parseFilters(websiteId, filters, {
columns: { propertyName: 'data_key' }, columns: { propertyName: 'data_key' },
}); });
@ -65,6 +65,6 @@ async function clickhouseQuery(
order by 2 desc order by 2 desc
limit 500 limit 500
`, `,
params, filterParams,
); );
} }

View file

@ -17,7 +17,7 @@ async function relationalQuery(
filters: QueryFilters & { propertyName?: string }, filters: QueryFilters & { propertyName?: string },
) { ) {
const { rawQuery, parseFilters, getDateSQL } = prisma; const { rawQuery, parseFilters, getDateSQL } = prisma;
const { filterQuery, params } = await parseFilters(websiteId, filters); const { filterQuery, filterParams } = await parseFilters(websiteId, filters);
return rawQuery( return rawQuery(
` `
@ -39,7 +39,7 @@ async function relationalQuery(
order by 2 desc order by 2 desc
limit 100 limit 100
`, `,
params, filterParams,
); );
} }
@ -48,7 +48,7 @@ async function clickhouseQuery(
filters: QueryFilters & { propertyName?: string }, filters: QueryFilters & { propertyName?: string },
): Promise<{ propertyName: string; dataType: number; propertyValue: string; total: number }[]> { ): Promise<{ propertyName: string; dataType: number; propertyValue: string; total: number }[]> {
const { rawQuery, parseFilters } = clickhouse; const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, filters); const { filterQuery, filterParams } = await parseFilters(websiteId, filters);
return rawQuery( return rawQuery(
` `
@ -68,6 +68,6 @@ async function clickhouseQuery(
order by 2 desc order by 2 desc
limit 100 limit 100
`, `,
params, filterParams,
); );
} }

View file

@ -28,7 +28,7 @@ async function relationalQuery(
) { ) {
const column = FILTER_COLUMNS[type] || type; const column = FILTER_COLUMNS[type] || type;
const { parseFilters, rawQuery } = prisma; const { parseFilters, rawQuery } = prisma;
const { filterQuery, joinSession, params } = await parseFilters( const { filterQuery, joinSession, filterParams } = await parseFilters(
websiteId, websiteId,
{ {
...filters, ...filters,
@ -58,7 +58,7 @@ async function relationalQuery(
limit ${limit} limit ${limit}
offset ${offset} offset ${offset}
`, `,
params, filterParams,
); );
} }
@ -71,7 +71,7 @@ async function clickhouseQuery(
): Promise<{ x: string; y: number }[]> { ): Promise<{ x: string; y: number }[]> {
const column = FILTER_COLUMNS[type] || type; const column = FILTER_COLUMNS[type] || type;
const { parseFilters, rawQuery } = clickhouse; const { parseFilters, rawQuery } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, { const { filterQuery, filterParams } = await parseFilters(websiteId, {
...filters, ...filters,
eventType: EVENT_TYPE.pageView, eventType: EVENT_TYPE.pageView,
}); });
@ -115,5 +115,5 @@ async function clickhouseQuery(
`; `;
} }
return rawQuery(sql, params); return rawQuery(sql, filterParams);
} }

View file

@ -14,7 +14,7 @@ export async function getSessionStats(...args: [websiteId: string, filters: Quer
async function relationalQuery(websiteId: string, filters: QueryFilters) { async function relationalQuery(websiteId: string, filters: QueryFilters) {
const { timezone = 'utc', unit = 'day' } = filters; const { timezone = 'utc', unit = 'day' } = filters;
const { getDateSQL, parseFilters, rawQuery } = prisma; const { getDateSQL, parseFilters, rawQuery } = prisma;
const { filterQuery, joinSession, params } = await parseFilters(websiteId, { const { filterQuery, joinSession, filterParams } = await parseFilters(websiteId, {
...filters, ...filters,
eventType: EVENT_TYPE.pageView, eventType: EVENT_TYPE.pageView,
}); });
@ -33,7 +33,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
group by 1 group by 1
order by 1 order by 1
`, `,
params, filterParams,
); );
} }
@ -43,7 +43,7 @@ async function clickhouseQuery(
): Promise<{ x: string; y: number }[]> { ): Promise<{ x: string; y: number }[]> {
const { timezone = 'utc', unit = 'day' } = filters; const { timezone = 'utc', unit = 'day' } = filters;
const { parseFilters, rawQuery, getDateSQL } = clickhouse; const { parseFilters, rawQuery, getDateSQL } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, { const { filterQuery, filterParams } = await parseFilters(websiteId, {
...filters, ...filters,
eventType: EVENT_TYPE.pageView, eventType: EVENT_TYPE.pageView,
}); });
@ -88,5 +88,5 @@ async function clickhouseQuery(
`; `;
} }
return rawQuery(sql, params); return rawQuery(sql, filterParams);
} }

View file

@ -21,7 +21,7 @@ async function relationalQuery(
{ pageviews: number; visitors: number; visits: number; countries: number; events: number }[] { pageviews: number; visitors: number; visits: number; countries: number; events: number }[]
> { > {
const { parseFilters, rawQuery } = prisma; const { parseFilters, rawQuery } = prisma;
const { filterQuery, params } = await parseFilters(websiteId, { const { filterQuery, filterParams } = await parseFilters(websiteId, {
...filters, ...filters,
}); });
@ -39,7 +39,7 @@ async function relationalQuery(
and website_event.created_at between {{startDate}} and {{endDate}} and website_event.created_at between {{startDate}} and {{endDate}}
${filterQuery} ${filterQuery}
`, `,
params, filterParams,
); );
} }
@ -50,7 +50,7 @@ async function clickhouseQuery(
{ pageviews: number; visitors: number; visits: number; countries: number; events: number }[] { pageviews: number; visitors: number; visits: number; countries: number; events: number }[]
> { > {
const { rawQuery, parseFilters } = clickhouse; const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, { const { filterQuery, filterParams } = await parseFilters(websiteId, {
...filters, ...filters,
}); });
@ -67,6 +67,6 @@ async function clickhouseQuery(
and created_at between {startDate:DateTime64} and {endDate:DateTime64} and created_at between {startDate:DateTime64} and {endDate:DateTime64}
${filterQuery} ${filterQuery}
`, `,
params, filterParams,
); );
} }

View file

@ -15,7 +15,7 @@ export async function getWebsiteSessions(
async function relationalQuery(websiteId: string, filters: QueryFilters, pageParams: PageParams) { async function relationalQuery(websiteId: string, filters: QueryFilters, pageParams: PageParams) {
const { pagedRawQuery, parseFilters } = prisma; const { pagedRawQuery, parseFilters } = prisma;
const { search } = pageParams; const { search } = pageParams;
const { filterQuery, params } = await parseFilters(websiteId, { const { filterQuery, filterParams } = await parseFilters(websiteId, {
...filters, ...filters,
}); });
@ -68,14 +68,14 @@ async function relationalQuery(websiteId: string, filters: QueryFilters, pagePar
session.city session.city
order by max(website_event.created_at) desc order by max(website_event.created_at) desc
`, `,
{ ...params, search: `%${search}%` }, { ...filterParams, search: `%${search}%` },
pageParams, pageParams,
); );
} }
async function clickhouseQuery(websiteId: string, filters: QueryFilters, pageParams?: PageParams) { async function clickhouseQuery(websiteId: string, filters: QueryFilters, pageParams?: PageParams) {
const { pagedQuery, parseFilters, getDateStringSQL } = clickhouse; const { pagedQuery, parseFilters, getDateStringSQL } = clickhouse;
const { params, dateQuery, filterQuery } = await parseFilters(websiteId, filters); const { filterParams, dateQuery, filterQuery } = await parseFilters(websiteId, filters);
const { search } = pageParams; const { search } = pageParams;
return pagedQuery( return pagedQuery(
@ -113,7 +113,7 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters, pagePar
group by session_id, website_id, hostname, browser, os, device, screen, language, country, region, city group by session_id, website_id, hostname, browser, os, device, screen, language, country, region, city
order by lastAt desc order by lastAt desc
`, `,
{ ...params, search }, { ...filterParams, search },
pageParams, pageParams,
); );
} }

View file

@ -15,7 +15,7 @@ export async function getWebsiteSessionsWeekly(
async function relationalQuery(websiteId: string, filters: QueryFilters) { async function relationalQuery(websiteId: string, filters: QueryFilters) {
const { timezone = 'utc' } = filters; const { timezone = 'utc' } = filters;
const { rawQuery, getDateWeeklySQL, parseFilters } = prisma; const { rawQuery, getDateWeeklySQL, parseFilters } = prisma;
const { params } = await parseFilters(websiteId, filters); const { filterParams } = await parseFilters(websiteId, filters);
return rawQuery( return rawQuery(
` `
@ -28,14 +28,14 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
group by time group by time
order by 2 order by 2
`, `,
params, filterParams,
).then(formatResults); ).then(formatResults);
} }
async function clickhouseQuery(websiteId: string, filters: QueryFilters) { async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
const { timezone = 'utc' } = filters; const { timezone = 'utc' } = filters;
const { rawQuery, parseFilters } = clickhouse; const { rawQuery, parseFilters } = clickhouse;
const { params } = await parseFilters(websiteId, filters); const { filterParams } = await parseFilters(websiteId, filters);
return rawQuery( return rawQuery(
` `
@ -48,7 +48,7 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
group by time group by time
order by time order by time
`, `,
params, filterParams,
).then(formatResults); ).then(formatResults);
} }