mirror of
https://github.com/umami-software/umami.git
synced 2026-02-04 04:37:11 +01:00
Refactor part 2: Electric Boogaloo. Standardize way of passing filter parameters.
This commit is contained in:
parent
f26f1b0581
commit
cdf391d5c2
90 changed files with 867 additions and 709 deletions
|
|
@ -3,17 +3,11 @@ import { useUsersQuery } from '@/components/hooks';
|
||||||
import { UsersTable } from './UsersTable';
|
import { UsersTable } from './UsersTable';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
export function UsersDataTable({
|
export function UsersDataTable({ showActions }: { showActions?: boolean; children?: ReactNode }) {
|
||||||
showActions,
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
showActions?: boolean;
|
|
||||||
children?: ReactNode;
|
|
||||||
}) {
|
|
||||||
const queryResult = useUsersQuery();
|
const queryResult = useUsersQuery();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataGrid queryResult={queryResult} renderEmpty={() => children}>
|
<DataGrid queryResult={queryResult} allowSearch={true}>
|
||||||
{({ data }) => <UsersTable data={data} showActions={showActions} />}
|
{({ data }) => <UsersTable data={data} showActions={showActions} />}
|
||||||
</DataGrid>
|
</DataGrid>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ import { TagsTable } from '@/components/metrics/TagsTable';
|
||||||
import { getCompareDate } from '@/lib/date';
|
import { getCompareDate } from '@/lib/date';
|
||||||
import { formatNumber } from '@/lib/format';
|
import { formatNumber } from '@/lib/format';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useWebsites } from '@/store/websites';
|
|
||||||
import { Panel } from '@/components/common/Panel';
|
import { Panel } from '@/components/common/Panel';
|
||||||
import { DateDisplay } from '@/components/common/DateDisplay';
|
import { DateDisplay } from '@/components/common/DateDisplay';
|
||||||
|
|
||||||
|
|
@ -42,8 +41,7 @@ const views = {
|
||||||
|
|
||||||
export function WebsiteCompareTables({ websiteId }: { websiteId: string }) {
|
export function WebsiteCompareTables({ websiteId }: { websiteId: string }) {
|
||||||
const [data, setData] = useState([]);
|
const [data, setData] = useState([]);
|
||||||
const { dateRange } = useDateRange(websiteId);
|
const { dateRange, dateCompare } = useDateRange(websiteId);
|
||||||
const dateCompare = useWebsites(state => state[websiteId]?.dateCompare);
|
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const {
|
const {
|
||||||
updateParams,
|
updateParams,
|
||||||
|
|
|
||||||
|
|
@ -17,43 +17,43 @@ export function WebsiteMetricsBar({
|
||||||
const { data, isLoading, isFetching, error } = useWebsiteStatsQuery(websiteId);
|
const { data, isLoading, isFetching, error } = useWebsiteStatsQuery(websiteId);
|
||||||
const isAllTime = dateRange.value === 'all';
|
const isAllTime = dateRange.value === 'all';
|
||||||
|
|
||||||
const { pageviews, visitors, visits, bounces, totaltime, previous } = data || {};
|
const { pageviews, visitors, visits, bounces, totaltime, comparison } = data || {};
|
||||||
|
|
||||||
const metrics = data
|
const metrics = data
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
value: visitors,
|
value: visitors,
|
||||||
label: formatMessage(labels.visitors),
|
label: formatMessage(labels.visitors),
|
||||||
change: visitors - previous.visitors,
|
change: visitors - comparison.visitors,
|
||||||
formatValue: formatLongNumber,
|
formatValue: formatLongNumber,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: visits,
|
value: visits,
|
||||||
label: formatMessage(labels.visits),
|
label: formatMessage(labels.visits),
|
||||||
change: visits - previous.visits,
|
change: visits - comparison.visits,
|
||||||
formatValue: formatLongNumber,
|
formatValue: formatLongNumber,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: pageviews,
|
value: pageviews,
|
||||||
label: formatMessage(labels.views),
|
label: formatMessage(labels.views),
|
||||||
change: pageviews - previous.pageviews,
|
change: pageviews - comparison.pageviews,
|
||||||
formatValue: formatLongNumber,
|
formatValue: formatLongNumber,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: formatMessage(labels.bounceRate),
|
label: formatMessage(labels.bounceRate),
|
||||||
value: (Math.min(visits, bounces) / visits) * 100,
|
value: (Math.min(visits, bounces) / visits) * 100,
|
||||||
prev: (Math.min(previous.visits, previous.bounces) / previous.visits) * 100,
|
prev: (Math.min(comparison.visits, comparison.bounces) / comparison.visits) * 100,
|
||||||
change:
|
change:
|
||||||
(Math.min(visits, bounces) / visits) * 100 -
|
(Math.min(visits, bounces) / visits) * 100 -
|
||||||
(Math.min(previous.visits, previous.bounces) / previous.visits) * 100,
|
(Math.min(comparison.visits, comparison.bounces) / comparison.visits) * 100,
|
||||||
formatValue: n => Math.round(+n) + '%',
|
formatValue: n => Math.round(+n) + '%',
|
||||||
reverseColors: true,
|
reverseColors: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: formatMessage(labels.visitDuration),
|
label: formatMessage(labels.visitDuration),
|
||||||
value: totaltime / visits,
|
value: totaltime / visits,
|
||||||
prev: previous.totaltime / previous.visits,
|
prev: comparison.totaltime / comparison.visits,
|
||||||
change: totaltime / visits - previous.totaltime / previous.visits,
|
change: totaltime / visits - comparison.totaltime / comparison.visits,
|
||||||
formatValue: n =>
|
formatValue: n =>
|
||||||
`${+n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}`,
|
`${+n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}`,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import { useWebsiteEventsQuery } from '@/components/hooks';
|
import { useState } from 'react';
|
||||||
|
import { useMessages, useWebsiteEventsQuery } from '@/components/hooks';
|
||||||
import { EventsTable } from './EventsTable';
|
import { EventsTable } from './EventsTable';
|
||||||
import { DataGrid } from '@/components/common/DataGrid';
|
import { DataGrid } from '@/components/common/DataGrid';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
import { FilterButtons } from '@/components/common/FilterButtons';
|
||||||
|
|
||||||
export function EventsDataTable({
|
export function EventsDataTable({
|
||||||
websiteId,
|
websiteId,
|
||||||
|
|
@ -10,10 +12,37 @@ export function EventsDataTable({
|
||||||
teamId?: string;
|
teamId?: string;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
|
const { formatMessage, labels } = useMessages();
|
||||||
const queryResult = useWebsiteEventsQuery(websiteId);
|
const queryResult = useWebsiteEventsQuery(websiteId);
|
||||||
|
const [view, setView] = useState('all');
|
||||||
|
|
||||||
|
const buttons = [
|
||||||
|
{
|
||||||
|
id: 'all',
|
||||||
|
label: formatMessage(labels.all),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'page',
|
||||||
|
label: formatMessage(labels.page),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'event',
|
||||||
|
label: formatMessage(labels.event),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const renderActions = () => {
|
||||||
|
return <FilterButtons items={buttons} value={view} onChange={setView} />;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataGrid queryResult={queryResult} allowSearch={true} autoFocus={false}>
|
<DataGrid
|
||||||
|
queryResult={queryResult}
|
||||||
|
allowSearch={true}
|
||||||
|
autoFocus={false}
|
||||||
|
allowPaging={true}
|
||||||
|
renderActions={renderActions}
|
||||||
|
>
|
||||||
{({ data }) => <EventsTable data={data} />}
|
{({ data }) => <EventsTable data={data} />}
|
||||||
</DataGrid>
|
</DataGrid>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { Avatar } from '@/components/common/Avatar';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Bolt, Eye } from '@/components/icons';
|
import { Bolt, Eye } from '@/components/icons';
|
||||||
import { DateDistance } from '@/components/common/DateDistance';
|
import { DateDistance } from '@/components/common/DateDistance';
|
||||||
|
import { TypeIcon } from '@/components/common/TypeIcon';
|
||||||
|
|
||||||
export function EventsTable({ data = [] }) {
|
export function EventsTable({ data = [] }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
@ -16,13 +17,6 @@ export function EventsTable({ data = [] }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataTable data={data}>
|
<DataTable data={data}>
|
||||||
<DataColumn id="session" label={formatMessage(labels.session)} width="100px">
|
|
||||||
{(row: any) => (
|
|
||||||
<Link href={renderUrl(`/websites/${row.websiteId}/sessions/${row.sessionId}`)}>
|
|
||||||
<Avatar seed={row.sessionId} size={64} />
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</DataColumn>
|
|
||||||
<DataColumn id="event" label={formatMessage(labels.event)} width="2fr">
|
<DataColumn id="event" label={formatMessage(labels.event)} width="2fr">
|
||||||
{(row: any) => {
|
{(row: any) => {
|
||||||
return (
|
return (
|
||||||
|
|
@ -34,8 +28,21 @@ export function EventsTable({ data = [] }) {
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</DataColumn>
|
</DataColumn>
|
||||||
<DataColumn id="created" label={formatMessage(labels.created)} width="200px">
|
<DataColumn id="created" width="1fr" align="end">
|
||||||
{(row: any) => <DateDistance date={new Date(row.createdAt)} />}
|
{(row: any) => (
|
||||||
|
<Row alignItems="center" gap>
|
||||||
|
<DateDistance date={new Date(row.createdAt)} />
|
||||||
|
<Link href={renderUrl(`/websites/${row.websiteId}/sessions/${row.sessionId}`)}>
|
||||||
|
<Avatar seed={row.sessionId} size={32} />
|
||||||
|
</Link>
|
||||||
|
<Row alignItems="center" gap="1">
|
||||||
|
<TypeIcon type="country" value={row.country} />
|
||||||
|
<TypeIcon type="browser" value={row.browser} />
|
||||||
|
<TypeIcon type="os" value={row.os} />
|
||||||
|
<TypeIcon type="device" value={row.device} />
|
||||||
|
</Row>
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
</DataColumn>
|
</DataColumn>
|
||||||
</DataTable>
|
</DataTable>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -30,15 +30,11 @@ export function Attribution({
|
||||||
}: AttributionProps) {
|
}: AttributionProps) {
|
||||||
const { data, error, isLoading } = useResultQuery<any>('attribution', {
|
const { data, error, isLoading } = useResultQuery<any>('attribution', {
|
||||||
websiteId,
|
websiteId,
|
||||||
dateRange: {
|
startDate,
|
||||||
startDate,
|
endDate,
|
||||||
endDate,
|
model,
|
||||||
},
|
type,
|
||||||
parameters: {
|
step,
|
||||||
model,
|
|
||||||
type,
|
|
||||||
step,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,10 @@ export interface BreakdownProps {
|
||||||
websiteId: string;
|
websiteId: string;
|
||||||
startDate: Date;
|
startDate: Date;
|
||||||
endDate: Date;
|
endDate: Date;
|
||||||
parameters: {
|
selectedFields: string[];
|
||||||
fields: string[];
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Breakdown({ websiteId, parameters, startDate, endDate }: BreakdownProps) {
|
export function Breakdown({ websiteId, selectedFields = [], startDate, endDate }: BreakdownProps) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { formatValue } = useFormat();
|
const { formatValue } = useFormat();
|
||||||
const { fields } = useFields();
|
const { fields } = useFields();
|
||||||
|
|
@ -20,19 +18,17 @@ export function Breakdown({ websiteId, parameters, startDate, endDate }: Breakdo
|
||||||
'breakdown',
|
'breakdown',
|
||||||
{
|
{
|
||||||
websiteId,
|
websiteId,
|
||||||
dateRange: {
|
startDate,
|
||||||
startDate,
|
endDate,
|
||||||
endDate,
|
fields: selectedFields,
|
||||||
},
|
|
||||||
parameters,
|
|
||||||
},
|
},
|
||||||
{ enabled: !!parameters.fields.length },
|
{ enabled: !!selectedFields.length },
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoadingPanel data={data} isLoading={isLoading} error={error}>
|
<LoadingPanel data={data} isLoading={isLoading} error={error}>
|
||||||
<DataTable data={data}>
|
<DataTable data={data}>
|
||||||
{parameters?.fields.map(field => {
|
{selectedFields.map(field => {
|
||||||
return (
|
return (
|
||||||
<DataColumn key={field} id={field} label={fields.find(f => f.name === field)?.label}>
|
<DataColumn key={field} id={field} label={fields.find(f => f.name === field)?.label}>
|
||||||
{row => {
|
{row => {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export function BreakdownPage({ websiteId }: { websiteId: string }) {
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
startDate={startDate}
|
startDate={startDate}
|
||||||
endDate={endDate}
|
endDate={endDate}
|
||||||
parameters={{ fields }}
|
selectedFields={fields}
|
||||||
/>
|
/>
|
||||||
</Panel>
|
</Panel>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,11 @@ type FunnelResult = {
|
||||||
remaining: number;
|
remaining: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Funnel({ id, name, type, parameters, websiteId, startDate, endDate }) {
|
export function Funnel({ id, name, type, parameters, websiteId }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { data, error, isLoading } = useResultQuery<any>(type, {
|
const { data, error, isLoading } = useResultQuery(type, {
|
||||||
websiteId,
|
websiteId,
|
||||||
dateRange: {
|
...parameters,
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
},
|
|
||||||
parameters,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||||
import { Panel } from '@/components/common/Panel';
|
import { Panel } from '@/components/common/Panel';
|
||||||
|
|
||||||
export function FunnelsPage({ websiteId }: { websiteId: string }) {
|
export function FunnelsPage({ websiteId }: { websiteId: string }) {
|
||||||
const { result, query } = useReportsQuery({ websiteId, type: 'funnel' });
|
const { data, isLoading, error } = useReportsQuery({ websiteId, type: 'funnel' });
|
||||||
const {
|
const {
|
||||||
dateRange: { startDate, endDate },
|
dateRange: { startDate, endDate },
|
||||||
} = useDateRange(websiteId);
|
} = useDateRange(websiteId);
|
||||||
|
|
@ -20,14 +20,16 @@ export function FunnelsPage({ websiteId }: { websiteId: string }) {
|
||||||
<SectionHeader>
|
<SectionHeader>
|
||||||
<FunnelAddButton websiteId={websiteId} />
|
<FunnelAddButton websiteId={websiteId} />
|
||||||
</SectionHeader>
|
</SectionHeader>
|
||||||
<LoadingPanel data={result?.data} isLoading={query?.isLoading} error={query?.error}>
|
<LoadingPanel data={data} isLoading={isLoading} error={error}>
|
||||||
<Grid gap>
|
{data && (
|
||||||
{result?.data?.map((report: any) => (
|
<Grid gap>
|
||||||
<Panel key={report.id}>
|
{data['data']?.map((report: any) => (
|
||||||
<Funnel {...report} startDate={startDate} endDate={endDate} />
|
<Panel key={report.id}>
|
||||||
</Panel>
|
<Funnel {...report} startDate={startDate} endDate={endDate} />
|
||||||
))}
|
</Panel>
|
||||||
</Grid>
|
))}
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
</LoadingPanel>
|
</LoadingPanel>
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,9 @@ export function Goal({ id, name, type, parameters, websiteId, startDate, endDate
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { data, error, isLoading, isFetching } = useResultQuery<GoalData>(type, {
|
const { data, error, isLoading, isFetching } = useResultQuery<GoalData>(type, {
|
||||||
websiteId,
|
websiteId,
|
||||||
dateRange: {
|
startDate,
|
||||||
startDate,
|
endDate,
|
||||||
endDate,
|
...parameters,
|
||||||
},
|
|
||||||
parameters,
|
|
||||||
});
|
});
|
||||||
const isPage = parameters?.type === 'page';
|
const isPage = parameters?.type === 'page';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,28 +22,15 @@ export interface JourneyProps {
|
||||||
endStep?: string;
|
endStep?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Journey({
|
export function Journey({ websiteId, steps, startStep, endStep }: JourneyProps) {
|
||||||
websiteId,
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
steps,
|
|
||||||
startStep,
|
|
||||||
endStep,
|
|
||||||
}: JourneyProps) {
|
|
||||||
const [selectedNode, setSelectedNode] = useState(null);
|
const [selectedNode, setSelectedNode] = useState(null);
|
||||||
const [activeNode, setActiveNode] = useState(null);
|
const [activeNode, setActiveNode] = useState(null);
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { data, error, isLoading } = useResultQuery<any>('journey', {
|
const { data, error, isLoading } = useResultQuery<any>('journey', {
|
||||||
websiteId,
|
websiteId,
|
||||||
dateRange: {
|
steps,
|
||||||
startDate,
|
startStep,
|
||||||
endDate,
|
endStep,
|
||||||
},
|
|
||||||
parameters: {
|
|
||||||
steps,
|
|
||||||
startStep,
|
|
||||||
endStep,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEscapeKey(() => setSelectedNode(null));
|
useEscapeKey(() => setSelectedNode(null));
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { Grid, Row, Column, Text, Icon } from '@umami/react-zen';
|
import { Grid, Row, Column, Text, Icon } from '@umami/react-zen';
|
||||||
import { Users } from '@/components/icons';
|
import { Users } from '@/components/icons';
|
||||||
import { useMessages, useLocale, useResultQuery, useTimezone } from '@/components/hooks';
|
import { useMessages, useLocale, useResultQuery } from '@/components/hooks';
|
||||||
import { formatDate } from '@/lib/date';
|
import { formatDate } from '@/lib/date';
|
||||||
import { formatLongNumber } from '@/lib/format';
|
import { formatLongNumber } from '@/lib/format';
|
||||||
import { Panel } from '@/components/common/Panel';
|
import { Panel } from '@/components/common/Panel';
|
||||||
|
|
@ -19,14 +19,10 @@ export interface RetentionProps {
|
||||||
export function Retention({ websiteId, days = DAYS, startDate, endDate }: RetentionProps) {
|
export function Retention({ websiteId, days = DAYS, startDate, endDate }: RetentionProps) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
const { timezone } = useTimezone();
|
const { data, error, isLoading } = useResultQuery('retention', {
|
||||||
const { data, error, isLoading } = useResultQuery<any>('retention', {
|
|
||||||
websiteId,
|
websiteId,
|
||||||
dateRange: {
|
startDate,
|
||||||
startDate,
|
endDate,
|
||||||
endDate,
|
|
||||||
timezone,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const rows =
|
const rows =
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { endOfMonth, startOfMonth } from 'date-fns';
|
||||||
export function RetentionPage({ websiteId }: { websiteId: string }) {
|
export function RetentionPage({ websiteId }: { websiteId: string }) {
|
||||||
const {
|
const {
|
||||||
dateRange: { startDate },
|
dateRange: { startDate },
|
||||||
} = useDateRange(websiteId);
|
} = useDateRange(websiteId, { ignoreOffset: true });
|
||||||
|
|
||||||
const monthStartDate = startOfMonth(startDate);
|
const monthStartDate = startOfMonth(startDate);
|
||||||
const monthEndDate = endOfMonth(startDate);
|
const monthEndDate = endOfMonth(startDate);
|
||||||
|
|
|
||||||
|
|
@ -32,13 +32,9 @@ export function Revenue({ websiteId, startDate, endDate }: RevenueProps) {
|
||||||
const unit = getMinimumUnit(startDate, endDate);
|
const unit = getMinimumUnit(startDate, endDate);
|
||||||
const { data, error, isLoading } = useResultQuery<any>('revenue', {
|
const { data, error, isLoading } = useResultQuery<any>('revenue', {
|
||||||
websiteId,
|
websiteId,
|
||||||
dateRange: {
|
startDate,
|
||||||
startDate,
|
endDate,
|
||||||
endDate,
|
currency,
|
||||||
},
|
|
||||||
parameters: {
|
|
||||||
currency,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderCountryName = useCallback(
|
const renderCountryName = useCallback(
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,8 @@ export function UTM({ websiteId, startDate, endDate }: UTMProps) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { data, error, isLoading } = useResultQuery<any>('utm', {
|
const { data, error, isLoading } = useResultQuery<any>('utm', {
|
||||||
websiteId,
|
websiteId,
|
||||||
dateRange: {
|
startDate,
|
||||||
startDate,
|
endDate,
|
||||||
endDate,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,15 @@
|
||||||
import { useWebsiteSessionsQuery } from '@/components/hooks';
|
import { useWebsiteSessionsQuery } from '@/components/hooks';
|
||||||
import { SessionsTable } from './SessionsTable';
|
import { SessionsTable } from './SessionsTable';
|
||||||
import { DataGrid } from '@/components/common/DataGrid';
|
import { DataGrid } from '@/components/common/DataGrid';
|
||||||
import { ReactNode } from 'react';
|
|
||||||
|
|
||||||
export function SessionsDataTable({
|
export function SessionsDataTable({ websiteId }: { websiteId?: string; teamId?: string }) {
|
||||||
websiteId,
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
websiteId?: string;
|
|
||||||
teamId?: string;
|
|
||||||
children?: ReactNode;
|
|
||||||
}) {
|
|
||||||
const queryResult = useWebsiteSessionsQuery(websiteId);
|
const queryResult = useWebsiteSessionsQuery(websiteId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataGrid queryResult={queryResult} renderEmpty={() => children} allowPaging>
|
<DataGrid queryResult={queryResult} allowPaging allowSearch>
|
||||||
{({ data }) => <SessionsTable data={data} showDomain={!websiteId} />}
|
{({ data }) => {
|
||||||
|
return <SessionsTable data={data} showDomain={!websiteId} />;
|
||||||
|
}}
|
||||||
</DataGrid>
|
</DataGrid>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export function SessionsTable({ data = [] }: { data: any[]; showDomain?: boolean
|
||||||
<DataColumn id="id" label={formatMessage(labels.session)} width="100px">
|
<DataColumn id="id" label={formatMessage(labels.session)} width="100px">
|
||||||
{(row: any) => (
|
{(row: any) => (
|
||||||
<Link href={`sessions/${row.id}`}>
|
<Link href={`sessions/${row.id}`}>
|
||||||
<Avatar seed={row.id} size={64} />
|
<Avatar seed={row.id} size={48} />
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</DataColumn>
|
</DataColumn>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { canViewWebsite } from '@/lib/auth';
|
import { canViewWebsite } from '@/lib/auth';
|
||||||
import { parseRequest } from '@/lib/request';
|
import { getQueryFilters, parseRequest, setWebsiteDate } from '@/lib/request';
|
||||||
import { json, unauthorized } from '@/lib/response';
|
import { json, unauthorized } from '@/lib/response';
|
||||||
import { reportResultSchema } from '@/lib/schema';
|
import { reportResultSchema } from '@/lib/schema';
|
||||||
import { getAttribution } from '@/queries/sql/reports/getAttribution';
|
import { AttributionParameters, getAttribution } from '@/queries/sql/reports/getAttribution';
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
const { auth, body, error } = await parseRequest(request, reportResultSchema);
|
const { auth, body, error } = await parseRequest(request, reportResultSchema);
|
||||||
|
|
@ -11,26 +11,16 @@ export async function POST(request: Request) {
|
||||||
return error();
|
return error();
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { websiteId } = body;
|
||||||
websiteId,
|
|
||||||
dateRange: { startDate, endDate },
|
|
||||||
parameters: { model, type, step, currency },
|
|
||||||
...filters
|
|
||||||
} = body;
|
|
||||||
|
|
||||||
if (!(await canViewWebsite(auth, websiteId))) {
|
if (!(await canViewWebsite(auth, websiteId))) {
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await getAttribution(websiteId, {
|
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
||||||
...filters,
|
const filters = getQueryFilters(body.filters);
|
||||||
startDate: new Date(startDate),
|
|
||||||
endDate: new Date(endDate),
|
const data = await getAttribution(websiteId, parameters as AttributionParameters, filters);
|
||||||
model,
|
|
||||||
type,
|
|
||||||
step,
|
|
||||||
currency,
|
|
||||||
});
|
|
||||||
|
|
||||||
return json(data);
|
return json(data);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { canViewWebsite } from '@/lib/auth';
|
import { canViewWebsite } from '@/lib/auth';
|
||||||
import { unauthorized, json } from '@/lib/response';
|
import { unauthorized, json } from '@/lib/response';
|
||||||
import { parseRequest } from '@/lib/request';
|
import { getQueryFilters, parseRequest, setWebsiteDate } from '@/lib/request';
|
||||||
import { getBreakdown } from '@/queries';
|
import { BreakdownParameters, getBreakdown } from '@/queries';
|
||||||
import { reportResultSchema } from '@/lib/schema';
|
import { reportResultSchema } from '@/lib/schema';
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
|
|
@ -11,22 +11,16 @@ export async function POST(request: Request) {
|
||||||
return error();
|
return error();
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { websiteId } = body;
|
||||||
websiteId,
|
|
||||||
dateRange: { startDate, endDate },
|
|
||||||
parameters: { fields },
|
|
||||||
...filters
|
|
||||||
} = body;
|
|
||||||
|
|
||||||
if (!(await canViewWebsite(auth, websiteId))) {
|
if (!(await canViewWebsite(auth, websiteId))) {
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await getBreakdown(websiteId, fields, {
|
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
||||||
...filters,
|
const filters = getQueryFilters(body.filters);
|
||||||
startDate: new Date(startDate),
|
|
||||||
endDate: new Date(endDate),
|
const data = await getBreakdown(websiteId, parameters as BreakdownParameters, filters);
|
||||||
});
|
|
||||||
|
|
||||||
return json(data);
|
return json(data);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { canViewWebsite } from '@/lib/auth';
|
import { canViewWebsite } from '@/lib/auth';
|
||||||
import { unauthorized, json } from '@/lib/response';
|
import { unauthorized, json } from '@/lib/response';
|
||||||
import { parseRequest } from '@/lib/request';
|
import { parseRequest, getQueryFilters, setWebsiteDate } from '@/lib/request';
|
||||||
import { getFunnel } from '@/queries';
|
import { FunnelParameters, getFunnel } from '@/queries';
|
||||||
import { reportResultSchema } from '@/lib/schema';
|
import { reportResultSchema } from '@/lib/schema';
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
|
|
@ -11,24 +11,16 @@ export async function POST(request: Request) {
|
||||||
return error();
|
return error();
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { websiteId } = body;
|
||||||
websiteId,
|
|
||||||
dateRange: { startDate, endDate },
|
|
||||||
parameters: { steps, window },
|
|
||||||
...filters
|
|
||||||
} = body;
|
|
||||||
|
|
||||||
if (!(await canViewWebsite(auth, websiteId))) {
|
if (!(await canViewWebsite(auth, websiteId))) {
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await getFunnel(websiteId, {
|
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
||||||
...filters,
|
const filters = getQueryFilters(body.filters);
|
||||||
startDate: new Date(startDate),
|
|
||||||
endDate: new Date(endDate),
|
const data = await getFunnel(websiteId, parameters as FunnelParameters, filters);
|
||||||
steps,
|
|
||||||
windowMinutes: +window,
|
|
||||||
});
|
|
||||||
|
|
||||||
return json(data);
|
return json(data);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { canViewWebsite } from '@/lib/auth';
|
import { canViewWebsite } from '@/lib/auth';
|
||||||
import { unauthorized, json } from '@/lib/response';
|
import { unauthorized, json } from '@/lib/response';
|
||||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
import { getQueryFilters, parseRequest, setWebsiteDate } from '@/lib/request';
|
||||||
import { getGoal } from '@/queries/sql/reports/getGoal';
|
import { getGoal, GoalParameters } from '@/queries/sql/reports/getGoal';
|
||||||
import { reportResultSchema } from '@/lib/schema';
|
import { reportResultSchema } from '@/lib/schema';
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
|
|
@ -11,27 +11,16 @@ export async function POST(request: Request) {
|
||||||
return error();
|
return error();
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { websiteId } = body;
|
||||||
websiteId,
|
|
||||||
dateRange: { startDate, endDate },
|
|
||||||
parameters: { type, value, property, operator },
|
|
||||||
} = body;
|
|
||||||
|
|
||||||
if (!(await canViewWebsite(auth, websiteId))) {
|
if (!(await canViewWebsite(auth, websiteId))) {
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = await getQueryFilters(body.filters);
|
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
||||||
|
const filters = getQueryFilters(body.filters);
|
||||||
|
|
||||||
const data = await getGoal(websiteId, {
|
const data = await getGoal(websiteId, parameters as GoalParameters, filters);
|
||||||
startDate: new Date(startDate),
|
|
||||||
endDate: new Date(endDate),
|
|
||||||
type,
|
|
||||||
value,
|
|
||||||
property,
|
|
||||||
operator,
|
|
||||||
filters,
|
|
||||||
});
|
|
||||||
|
|
||||||
return json(data);
|
return json(data);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { canViewWebsite } from '@/lib/auth';
|
import { canViewWebsite } from '@/lib/auth';
|
||||||
import { unauthorized, json } from '@/lib/response';
|
import { unauthorized, json } from '@/lib/response';
|
||||||
import { parseRequest } from '@/lib/request';
|
import { getQueryFilters, parseRequest, setWebsiteDate } from '@/lib/request';
|
||||||
import { getJourney } from '@/queries';
|
import { getJourney } from '@/queries';
|
||||||
import { reportResultSchema } from '@/lib/schema';
|
import { reportResultSchema } from '@/lib/schema';
|
||||||
|
|
||||||
|
|
@ -11,25 +11,15 @@ export async function POST(request: Request) {
|
||||||
return error();
|
return error();
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { websiteId, parameters, filters } = body;
|
||||||
websiteId,
|
|
||||||
dateRange: { startDate, endDate },
|
|
||||||
parameters: { steps, startStep, endStep },
|
|
||||||
...filters
|
|
||||||
} = body;
|
|
||||||
|
|
||||||
if (!(await canViewWebsite(auth, websiteId))) {
|
if (!(await canViewWebsite(auth, websiteId))) {
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await getJourney(websiteId, {
|
const queryFilters = await setWebsiteDate(websiteId, getQueryFilters(filters));
|
||||||
...filters,
|
|
||||||
startDate: new Date(startDate),
|
const data = await getJourney(websiteId, parameters, queryFilters);
|
||||||
endDate: new Date(endDate),
|
|
||||||
steps,
|
|
||||||
startStep,
|
|
||||||
endStep,
|
|
||||||
});
|
|
||||||
|
|
||||||
return json(data);
|
return json(data);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { canViewWebsite } from '@/lib/auth';
|
import { canViewWebsite } from '@/lib/auth';
|
||||||
import { unauthorized, json } from '@/lib/response';
|
import { unauthorized, json } from '@/lib/response';
|
||||||
import { parseRequest } from '@/lib/request';
|
import { parseRequest, getQueryFilters, setWebsiteDate } from '@/lib/request';
|
||||||
import { getRetention } from '@/queries';
|
import { getRetention, RetentionParameters } from '@/queries';
|
||||||
import { reportResultSchema } from '@/lib/schema';
|
import { reportResultSchema } from '@/lib/schema';
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
|
|
@ -11,22 +11,16 @@ export async function POST(request: Request) {
|
||||||
return error();
|
return error();
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { websiteId } = body;
|
||||||
websiteId,
|
|
||||||
dateRange: { startDate, endDate, timezone },
|
|
||||||
...filters
|
|
||||||
} = body;
|
|
||||||
|
|
||||||
if (!(await canViewWebsite(auth, websiteId))) {
|
if (!(await canViewWebsite(auth, websiteId))) {
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await getRetention(websiteId, {
|
const filters = getQueryFilters(body.filters);
|
||||||
...filters,
|
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
||||||
startDate: new Date(startDate),
|
|
||||||
endDate: new Date(endDate),
|
const data = await getRetention(websiteId, parameters as RetentionParameters, filters);
|
||||||
timezone,
|
|
||||||
});
|
|
||||||
|
|
||||||
return json(data);
|
return json(data);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { canViewWebsite } from '@/lib/auth';
|
import { canViewWebsite } from '@/lib/auth';
|
||||||
import { unauthorized, json } from '@/lib/response';
|
import { unauthorized, json } from '@/lib/response';
|
||||||
import { parseRequest } from '@/lib/request';
|
import { parseRequest, getQueryFilters, setWebsiteDate } from '@/lib/request';
|
||||||
import { reportResultSchema } from '@/lib/schema';
|
import { reportResultSchema } from '@/lib/schema';
|
||||||
import { getRevenue } from '@/queries/sql/reports/getRevenue';
|
import { getRevenue, RevenuParameters } from '@/queries/sql/reports/getRevenue';
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
const { auth, body, error } = await parseRequest(request, reportResultSchema);
|
const { auth, body, error } = await parseRequest(request, reportResultSchema);
|
||||||
|
|
@ -11,24 +11,16 @@ export async function POST(request: Request) {
|
||||||
return error();
|
return error();
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { websiteId } = body;
|
||||||
websiteId,
|
|
||||||
dateRange: { startDate, endDate, unit },
|
|
||||||
parameters: { currency },
|
|
||||||
...filters
|
|
||||||
} = body;
|
|
||||||
|
|
||||||
if (!(await canViewWebsite(auth, websiteId))) {
|
if (!(await canViewWebsite(auth, websiteId))) {
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await getRevenue(websiteId, {
|
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
||||||
...filters,
|
const filters = getQueryFilters(body.filters);
|
||||||
startDate: new Date(startDate),
|
|
||||||
endDate: new Date(endDate),
|
const data = await getRevenue(websiteId, parameters as RevenuParameters, filters);
|
||||||
unit,
|
|
||||||
currency,
|
|
||||||
});
|
|
||||||
|
|
||||||
return json(data);
|
return json(data);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { canViewWebsite } from '@/lib/auth';
|
import { canViewWebsite } from '@/lib/auth';
|
||||||
import { unauthorized, json } from '@/lib/response';
|
import { unauthorized, json } from '@/lib/response';
|
||||||
import { parseRequest } from '@/lib/request';
|
import { getQueryFilters, parseRequest, setWebsiteDate } from '@/lib/request';
|
||||||
import { getUTM } from '@/queries';
|
import { getUTM, UTMParameters } from '@/queries';
|
||||||
import { reportResultSchema } from '@/lib/schema';
|
import { reportResultSchema } from '@/lib/schema';
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
|
|
@ -11,21 +11,16 @@ export async function POST(request: Request) {
|
||||||
return error();
|
return error();
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { websiteId } = body;
|
||||||
websiteId,
|
|
||||||
dateRange: { startDate, endDate },
|
|
||||||
...filters
|
|
||||||
} = body;
|
|
||||||
|
|
||||||
if (!(await canViewWebsite(auth, websiteId))) {
|
if (!(await canViewWebsite(auth, websiteId))) {
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await getUTM(websiteId, {
|
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
||||||
...filters,
|
const filters = getQueryFilters(body.filters);
|
||||||
startDate: new Date(startDate),
|
|
||||||
endDate: new Date(endDate),
|
const data = await getUTM(websiteId, parameters as UTMParameters, filters);
|
||||||
});
|
|
||||||
|
|
||||||
return json(data);
|
return json(data);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { z } from 'zod';
|
||||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
import { getQueryFilters, parseRequest } from '@/lib/request';
|
||||||
import { unauthorized, json } from '@/lib/response';
|
import { unauthorized, json } from '@/lib/response';
|
||||||
import { canViewWebsite } from '@/lib/auth';
|
import { canViewWebsite } from '@/lib/auth';
|
||||||
import { pagingParams } from '@/lib/schema';
|
import { dateRangeParams, pagingParams, filterParams } from '@/lib/schema';
|
||||||
import { getWebsiteEvents } from '@/queries';
|
import { getWebsiteEvents } from '@/queries';
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
|
|
@ -10,8 +10,8 @@ export async function GET(
|
||||||
{ params }: { params: Promise<{ websiteId: string }> },
|
{ params }: { params: Promise<{ websiteId: string }> },
|
||||||
) {
|
) {
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
startAt: z.coerce.number().int(),
|
...dateRangeParams,
|
||||||
endAt: z.coerce.number().int(),
|
...filterParams,
|
||||||
...pagingParams,
|
...pagingParams,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { parseRequest, getQueryFilters } from '@/lib/request';
|
import { parseRequest, getQueryFilters, setWebsiteDate } from '@/lib/request';
|
||||||
import { unauthorized, json } from '@/lib/response';
|
import { unauthorized, json } from '@/lib/response';
|
||||||
import { canViewWebsite } from '@/lib/auth';
|
import { canViewWebsite } from '@/lib/auth';
|
||||||
import { filterParams, timezoneParam, unitParam } from '@/lib/schema';
|
import { filterParams, timezoneParam, unitParam } from '@/lib/schema';
|
||||||
|
|
@ -29,7 +29,7 @@ export async function GET(
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = await getQueryFilters({ ...query, websiteId });
|
const filters = await setWebsiteDate(websiteId, getQueryFilters(query));
|
||||||
|
|
||||||
const data = await getEventMetrics(websiteId, filters);
|
const data = await getEventMetrics(websiteId, filters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@ import { canViewWebsite } from '@/lib/auth';
|
||||||
import {
|
import {
|
||||||
SESSION_COLUMNS,
|
SESSION_COLUMNS,
|
||||||
EVENT_COLUMNS,
|
EVENT_COLUMNS,
|
||||||
FILTER_COLUMNS,
|
|
||||||
OPERATORS,
|
|
||||||
SEARCH_DOMAINS,
|
SEARCH_DOMAINS,
|
||||||
SOCIAL_DOMAINS,
|
SOCIAL_DOMAINS,
|
||||||
EMAIL_DOMAINS,
|
EMAIL_DOMAINS,
|
||||||
|
|
@ -13,7 +11,7 @@ import {
|
||||||
VIDEO_DOMAINS,
|
VIDEO_DOMAINS,
|
||||||
PAID_AD_PARAMS,
|
PAID_AD_PARAMS,
|
||||||
} from '@/lib/constants';
|
} from '@/lib/constants';
|
||||||
import { parseRequest, getQueryFilters } from '@/lib/request';
|
import { parseRequest, getQueryFilters, setWebsiteDate } from '@/lib/request';
|
||||||
import { json, unauthorized, badRequest } from '@/lib/response';
|
import { json, unauthorized, badRequest } from '@/lib/response';
|
||||||
import { getPageviewMetrics, getSessionMetrics, getChannelMetrics } from '@/queries';
|
import { getPageviewMetrics, getSessionMetrics, getChannelMetrics } from '@/queries';
|
||||||
import { filterParams } from '@/lib/schema';
|
import { filterParams } from '@/lib/schema';
|
||||||
|
|
@ -45,20 +43,14 @@ export async function GET(
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const column = FILTER_COLUMNS[type] || type;
|
const filters = await setWebsiteDate(websiteId, getQueryFilters(query));
|
||||||
const filters = await getQueryFilters({ ...query, websiteId });
|
|
||||||
|
|
||||||
if (search) {
|
if (search) {
|
||||||
filters[type] = {
|
filters[type] = `c.${search}`;
|
||||||
name: type,
|
|
||||||
column,
|
|
||||||
operator: OPERATORS.contains,
|
|
||||||
value: search,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SESSION_COLUMNS.includes(type)) {
|
if (SESSION_COLUMNS.includes(type)) {
|
||||||
const data = await getSessionMetrics(websiteId, type, filters, limit, offset);
|
const data = await getSessionMetrics(websiteId, { type, limit, offset }, filters);
|
||||||
|
|
||||||
if (type === 'language') {
|
if (type === 'language') {
|
||||||
const combined = {};
|
const combined = {};
|
||||||
|
|
@ -80,7 +72,7 @@ export async function GET(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (EVENT_COLUMNS.includes(type)) {
|
if (EVENT_COLUMNS.includes(type)) {
|
||||||
const data = await getPageviewMetrics(websiteId, type, filters, limit, offset);
|
const data = await getPageviewMetrics(websiteId, { type, limit, offset }, filters);
|
||||||
|
|
||||||
return json(data);
|
return json(data);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { canViewWebsite } from '@/lib/auth';
|
import { canViewWebsite } from '@/lib/auth';
|
||||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
import { getQueryFilters, parseRequest, setWebsiteDate } from '@/lib/request';
|
||||||
import { dateRangeParams, filterParams } from '@/lib/schema';
|
import { dateRangeParams, filterParams } from '@/lib/schema';
|
||||||
import { getCompareDate } from '@/lib/date';
|
import { getCompareDate } from '@/lib/date';
|
||||||
import { unauthorized, json } from '@/lib/response';
|
import { unauthorized, json } from '@/lib/response';
|
||||||
|
|
@ -27,7 +27,7 @@ export async function GET(
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = await getQueryFilters({ ...query, websiteId });
|
const filters = await setWebsiteDate(websiteId, getQueryFilters(query));
|
||||||
|
|
||||||
const [pageviews, sessions] = await Promise.all([
|
const [pageviews, sessions] = await Promise.all([
|
||||||
getPageviewStats(websiteId, filters),
|
getPageviewStats(websiteId, filters),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { canViewWebsite } from '@/lib/auth';
|
import { canViewWebsite } from '@/lib/auth';
|
||||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
import { getQueryFilters, parseRequest, setWebsiteDate } from '@/lib/request';
|
||||||
import { json, unauthorized } from '@/lib/response';
|
import { json, unauthorized } from '@/lib/response';
|
||||||
import { getSessionDataValues } from '@/queries';
|
import { getSessionDataValues } from '@/queries';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
@ -22,7 +22,7 @@ export async function GET(
|
||||||
|
|
||||||
const { propertyName } = query;
|
const { propertyName } = query;
|
||||||
const { websiteId } = await params;
|
const { websiteId } = await params;
|
||||||
const filters = await getQueryFilters({ ...query, websiteId });
|
const filters = await setWebsiteDate(websiteId, getQueryFilters(query));
|
||||||
|
|
||||||
if (!(await canViewWebsite(auth, websiteId))) {
|
if (!(await canViewWebsite(auth, websiteId))) {
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
import { getQueryFilters, parseRequest, setWebsiteDate } from '@/lib/request';
|
||||||
import { unauthorized, json } from '@/lib/response';
|
import { unauthorized, json } from '@/lib/response';
|
||||||
import { canViewWebsite } from '@/lib/auth';
|
import { canViewWebsite } from '@/lib/auth';
|
||||||
import { dateRangeParams, filterParams, pagingParams } from '@/lib/schema';
|
import { dateRangeParams, filterParams, pagingParams } from '@/lib/schema';
|
||||||
|
|
@ -27,7 +27,7 @@ export async function GET(
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = await getQueryFilters({ ...query, websiteId });
|
const filters = await setWebsiteDate(websiteId, getQueryFilters(query));
|
||||||
|
|
||||||
const data = await getWebsiteSessions(websiteId, filters);
|
const data = await getWebsiteSessions(websiteId, filters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { parseRequest, getQueryFilters } from '@/lib/request';
|
import { parseRequest, getQueryFilters, setWebsiteDate } from '@/lib/request';
|
||||||
import { unauthorized, json } from '@/lib/response';
|
import { unauthorized, json } from '@/lib/response';
|
||||||
import { canViewWebsite } from '@/lib/auth';
|
import { canViewWebsite } from '@/lib/auth';
|
||||||
import { filterParams } from '@/lib/schema';
|
import { filterParams } from '@/lib/schema';
|
||||||
|
|
@ -27,7 +27,7 @@ export async function GET(
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = await getQueryFilters(query);
|
const filters = await setWebsiteDate(websiteId, getQueryFilters(query));
|
||||||
|
|
||||||
const metrics = await getWebsiteSessionStats(websiteId, filters);
|
const metrics = await getWebsiteSessionStats(websiteId, filters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { parseRequest, getQueryFilters } from '@/lib/request';
|
import { parseRequest, getQueryFilters, setWebsiteDate } from '@/lib/request';
|
||||||
import { unauthorized, json } from '@/lib/response';
|
import { unauthorized, json } from '@/lib/response';
|
||||||
import { canViewWebsite } from '@/lib/auth';
|
import { canViewWebsite } from '@/lib/auth';
|
||||||
import { filterParams } from '@/lib/schema';
|
import { filterParams } from '@/lib/schema';
|
||||||
import { getWebsiteStats } from '@/queries';
|
import { getWebsiteStats } from '@/queries';
|
||||||
|
import { getCompareDate } from '@/lib/date';
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
|
@ -28,15 +29,17 @@ export async function GET(
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = await getQueryFilters({ ...query, websiteId });
|
const filters = await setWebsiteDate(websiteId, getQueryFilters(query));
|
||||||
|
|
||||||
const data = await getWebsiteStats(websiteId, filters);
|
const data = await getWebsiteStats(websiteId, filters);
|
||||||
|
|
||||||
const previous = await getWebsiteStats(websiteId, {
|
const { startDate, endDate } = getCompareDate('prev', filters.startDate, filters.endDate);
|
||||||
|
|
||||||
|
const comparison = await getWebsiteStats(websiteId, {
|
||||||
...filters,
|
...filters,
|
||||||
startDate: filters.compareStartDate,
|
startDate,
|
||||||
endDate: filters.compareEndDate,
|
endDate,
|
||||||
});
|
});
|
||||||
|
|
||||||
return json({ ...data, previous });
|
return json({ ...data, comparison });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,13 @@ import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||||
|
|
||||||
const DEFAULT_SEARCH_DELAY = 600;
|
const DEFAULT_SEARCH_DELAY = 600;
|
||||||
|
|
||||||
export interface DataTableProps {
|
export interface DataGridProps {
|
||||||
queryResult: any;
|
queryResult: any;
|
||||||
searchDelay?: number;
|
searchDelay?: number;
|
||||||
allowSearch?: boolean;
|
allowSearch?: boolean;
|
||||||
allowPaging?: boolean;
|
allowPaging?: boolean;
|
||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
renderEmpty?: () => ReactNode;
|
renderActions?: () => ReactNode;
|
||||||
children: ReactNode | ((data: any) => ReactNode);
|
children: ReactNode | ((data: any) => ReactNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -20,12 +20,13 @@ export function DataGrid({
|
||||||
queryResult,
|
queryResult,
|
||||||
searchDelay = 600,
|
searchDelay = 600,
|
||||||
allowSearch,
|
allowSearch,
|
||||||
allowPaging,
|
allowPaging = true,
|
||||||
autoFocus,
|
autoFocus,
|
||||||
|
renderActions,
|
||||||
children,
|
children,
|
||||||
}: DataTableProps) {
|
}: DataGridProps) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { data, error, isLoading, isFetching, setParams } = queryResult || {};
|
const { data, error, isLoading, isFetching, setParams } = queryResult;
|
||||||
const { router, updateParams } = useNavigation();
|
const { router, updateParams } = useNavigation();
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
|
|
||||||
|
|
@ -43,29 +44,33 @@ export function DataGrid({
|
||||||
return (
|
return (
|
||||||
<Column gap="4" minHeight="300px">
|
<Column gap="4" minHeight="300px">
|
||||||
{allowSearch && (data || search) && (
|
{allowSearch && (data || search) && (
|
||||||
<Row width="280px" alignItems="center">
|
<Row alignItems="center" justifyContent="space-between">
|
||||||
<SearchField
|
<SearchField
|
||||||
value={search}
|
value={search}
|
||||||
onSearch={handleSearch}
|
onSearch={handleSearch}
|
||||||
delay={searchDelay || DEFAULT_SEARCH_DELAY}
|
delay={searchDelay || DEFAULT_SEARCH_DELAY}
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
placeholder={formatMessage(labels.search)}
|
placeholder={formatMessage(labels.search)}
|
||||||
|
style={{ width: '280px' }}
|
||||||
/>
|
/>
|
||||||
|
{renderActions?.()}
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
<LoadingPanel data={data} isLoading={isLoading} isFetching={isFetching} error={error}>
|
<LoadingPanel data={data} isLoading={isLoading} isFetching={isFetching} error={error}>
|
||||||
<Column>
|
{data && (
|
||||||
{data ? (typeof children === 'function' ? children(data) : children) : null}
|
<>
|
||||||
</Column>
|
<Column>{typeof children === 'function' ? children(data) : children}</Column>
|
||||||
{allowPaging && data && (
|
{allowPaging && data && (
|
||||||
<Row marginTop="6">
|
<Row marginTop="6">
|
||||||
<Pager
|
<Pager
|
||||||
page={data.page}
|
page={data.page}
|
||||||
pageSize={data.pageSize}
|
pageSize={data.pageSize}
|
||||||
count={data.count}
|
count={data.count}
|
||||||
onPageChange={handlePageChange}
|
onPageChange={handlePageChange}
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</LoadingPanel>
|
</LoadingPanel>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
import { useApi } from '../useApi';
|
import { useApi } from '../useApi';
|
||||||
import { useFilterParams } from '../useFilterParams';
|
import { useFilterParameters } from '../useFilterParameters';
|
||||||
|
import { useDateParameters } from '../useDateParameters';
|
||||||
import { ReactQueryOptions } from '@/lib/types';
|
import { ReactQueryOptions } from '@/lib/types';
|
||||||
|
|
||||||
export function useEventDataEventsQuery(websiteId: string, options?: ReactQueryOptions<any>) {
|
export function useEventDataEventsQuery(websiteId: string, options?: ReactQueryOptions<any>) {
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const params = useFilterParams(websiteId);
|
const date = useDateParameters(websiteId);
|
||||||
|
const filters = useFilterParameters();
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['websites:event-data:events', { websiteId, ...params }],
|
queryKey: ['websites:event-data:events', { websiteId, ...date, ...filters }],
|
||||||
queryFn: () => get(`/websites/${websiteId}/event-data/events`, { ...params }),
|
queryFn: () => get(`/websites/${websiteId}/event-data/events`, { ...date, ...filters }),
|
||||||
enabled: !!websiteId,
|
enabled: !!websiteId,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
import { useApi } from '../useApi';
|
import { useApi } from '../useApi';
|
||||||
import { useFilterParams } from '../useFilterParams';
|
import { useFilterParameters } from '../useFilterParameters';
|
||||||
|
import { useDateParameters } from '../useDateParameters';
|
||||||
import { ReactQueryOptions } from '@/lib/types';
|
import { ReactQueryOptions } from '@/lib/types';
|
||||||
|
|
||||||
export function useEventDataPropertiesQuery(websiteId: string, options?: ReactQueryOptions<any>) {
|
export function useEventDataPropertiesQuery(websiteId: string, options?: ReactQueryOptions<any>) {
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const params = useFilterParams(websiteId);
|
const date = useDateParameters(websiteId);
|
||||||
|
const filters = useFilterParameters();
|
||||||
|
|
||||||
return useQuery<any>({
|
return useQuery<any>({
|
||||||
queryKey: ['websites:event-data:properties', { websiteId, ...params }],
|
queryKey: ['websites:event-data:properties', { websiteId, ...date, ...filters }],
|
||||||
queryFn: () => get(`/websites/${websiteId}/event-data/properties`, { ...params }),
|
queryFn: () => get(`/websites/${websiteId}/event-data/properties`, { ...date, ...filters }),
|
||||||
enabled: !!websiteId,
|
enabled: !!websiteId,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useApi } from '../useApi';
|
import { useApi } from '../useApi';
|
||||||
import { useFilterParams } from '../useFilterParams';
|
import { useFilterParameters } from '../useFilterParameters';
|
||||||
|
import { useDateParameters } from '../useDateParameters';
|
||||||
import { ReactQueryOptions } from '@/lib/types';
|
import { ReactQueryOptions } from '@/lib/types';
|
||||||
|
|
||||||
export function useEventDataQuery(
|
export function useEventDataQuery(
|
||||||
|
|
@ -8,11 +9,12 @@ export function useEventDataQuery(
|
||||||
options?: ReactQueryOptions<any>,
|
options?: ReactQueryOptions<any>,
|
||||||
) {
|
) {
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const params = useFilterParams(websiteId);
|
const date = useDateParameters(websiteId);
|
||||||
|
const params = useFilterParameters();
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['websites:event-data', { websiteId, eventId, ...params }],
|
queryKey: ['websites:event-data', { websiteId, eventId, ...date, ...params }],
|
||||||
queryFn: () => get(`/websites/${websiteId}/event-data/${eventId}`, { ...params }),
|
queryFn: () => get(`/websites/${websiteId}/event-data/${eventId}`, { ...date, ...params }),
|
||||||
enabled: !!(websiteId && eventId),
|
enabled: !!(websiteId && eventId),
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useApi } from '../useApi';
|
import { useApi } from '../useApi';
|
||||||
import { useFilterParams } from '../useFilterParams';
|
import { useFilterParameters } from '../useFilterParameters';
|
||||||
|
import { useDateParameters } from '../useDateParameters';
|
||||||
import { ReactQueryOptions } from '@/lib/types';
|
import { ReactQueryOptions } from '@/lib/types';
|
||||||
|
|
||||||
export function useEventDataValuesQuery(
|
export function useEventDataValuesQuery(
|
||||||
|
|
@ -9,12 +10,21 @@ export function useEventDataValuesQuery(
|
||||||
options?: ReactQueryOptions<any>,
|
options?: ReactQueryOptions<any>,
|
||||||
) {
|
) {
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const params = useFilterParams(websiteId);
|
const date = useDateParameters(websiteId);
|
||||||
|
const filters = useFilterParameters();
|
||||||
|
|
||||||
return useQuery<any>({
|
return useQuery<any>({
|
||||||
queryKey: ['websites:event-data:values', { websiteId, eventName, propertyName, ...params }],
|
queryKey: [
|
||||||
|
'websites:event-data:values',
|
||||||
|
{ websiteId, eventName, propertyName, ...date, ...filters },
|
||||||
|
],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
get(`/websites/${websiteId}/event-data/values`, { ...params, eventName, propertyName }),
|
get(`/websites/${websiteId}/event-data/values`, {
|
||||||
|
...date,
|
||||||
|
...filters,
|
||||||
|
eventName,
|
||||||
|
propertyName,
|
||||||
|
}),
|
||||||
enabled: !!(websiteId && propertyName),
|
enabled: !!(websiteId && propertyName),
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
import { useApi } from '../useApi';
|
import { useApi } from '../useApi';
|
||||||
import { useFilterParams } from '../useFilterParams';
|
import { useFilterParameters } from '../useFilterParameters';
|
||||||
import { ReactQueryOptions } from '@/lib/types';
|
import { ReactQueryOptions } from '@/lib/types';
|
||||||
|
import { useDateParameters } from '@/components/hooks/useDateParameters';
|
||||||
|
|
||||||
export function useResultQuery<T = any>(
|
export function useResultQuery<T = any>(
|
||||||
type: string,
|
type: string,
|
||||||
params?: Record<string, any>,
|
params?: Record<string, any>,
|
||||||
options?: ReactQueryOptions<T>,
|
options?: ReactQueryOptions<T>,
|
||||||
) {
|
) {
|
||||||
const { websiteId } = params;
|
const { websiteId, ...parameters } = params;
|
||||||
const { post, useQuery } = useApi();
|
const { post, useQuery } = useApi();
|
||||||
const filters = useFilterParams(websiteId);
|
const { startDate, endDate, timezone } = useDateParameters(websiteId);
|
||||||
|
const filters = useFilterParameters();
|
||||||
|
|
||||||
return useQuery<T>({
|
return useQuery<T>({
|
||||||
queryKey: [
|
queryKey: [
|
||||||
|
|
@ -17,11 +19,25 @@ export function useResultQuery<T = any>(
|
||||||
{
|
{
|
||||||
type,
|
type,
|
||||||
websiteId,
|
websiteId,
|
||||||
...filters,
|
startDate,
|
||||||
|
endDate,
|
||||||
|
timezone,
|
||||||
...params,
|
...params,
|
||||||
|
...filters,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
queryFn: () => post(`/reports/${type}`, { type, filters, ...params }),
|
queryFn: () =>
|
||||||
|
post(`/reports/${type}`, {
|
||||||
|
websiteId,
|
||||||
|
type,
|
||||||
|
filters,
|
||||||
|
parameters: {
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
timezone,
|
||||||
|
...parameters,
|
||||||
|
},
|
||||||
|
}),
|
||||||
enabled: !!type,
|
enabled: !!type,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
import { useApi } from '../useApi';
|
import { useApi } from '../useApi';
|
||||||
import { useFilterParams } from '../useFilterParams';
|
import { useFilterParameters } from '../useFilterParameters';
|
||||||
|
import { useDateParameters } from '../useDateParameters';
|
||||||
import { ReactQueryOptions } from '@/lib/types';
|
import { ReactQueryOptions } from '@/lib/types';
|
||||||
|
|
||||||
export function useSessionDataPropertiesQuery(websiteId: string, options?: ReactQueryOptions<any>) {
|
export function useSessionDataPropertiesQuery(websiteId: string, options?: ReactQueryOptions<any>) {
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const params = useFilterParams(websiteId);
|
const date = useDateParameters(websiteId);
|
||||||
|
const filters = useFilterParameters();
|
||||||
|
|
||||||
return useQuery<any>({
|
return useQuery<any>({
|
||||||
queryKey: ['websites:session-data:properties', { websiteId, ...params }],
|
queryKey: ['websites:session-data:properties', { websiteId, ...date, ...filters }],
|
||||||
queryFn: () => get(`/websites/${websiteId}/session-data/properties`, { ...params }),
|
queryFn: () => get(`/websites/${websiteId}/session-data/properties`, { ...date, ...filters }),
|
||||||
enabled: !!websiteId,
|
enabled: !!websiteId,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useApi } from '../useApi';
|
import { useApi } from '../useApi';
|
||||||
import { useFilterParams } from '../useFilterParams';
|
import { useFilterParameters } from '../useFilterParameters';
|
||||||
|
import { useDateParameters } from '../useDateParameters';
|
||||||
import { ReactQueryOptions } from '@/lib/types';
|
import { ReactQueryOptions } from '@/lib/types';
|
||||||
|
|
||||||
export function useSessionDataValuesQuery(
|
export function useSessionDataValuesQuery(
|
||||||
|
|
@ -8,11 +9,13 @@ export function useSessionDataValuesQuery(
|
||||||
options?: ReactQueryOptions<any>,
|
options?: ReactQueryOptions<any>,
|
||||||
) {
|
) {
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const params = useFilterParams(websiteId);
|
const date = useDateParameters(websiteId);
|
||||||
|
const filters = useFilterParameters();
|
||||||
|
|
||||||
return useQuery<any>({
|
return useQuery<any>({
|
||||||
queryKey: ['websites:session-data:values', { websiteId, propertyName, ...params }],
|
queryKey: ['websites:session-data:values', { websiteId, propertyName, ...date, ...filters }],
|
||||||
queryFn: () => get(`/websites/${websiteId}/session-data/values`, { ...params, propertyName }),
|
queryFn: () =>
|
||||||
|
get(`/websites/${websiteId}/session-data/values`, { ...date, ...filters, propertyName }),
|
||||||
enabled: !!(websiteId && propertyName),
|
enabled: !!(websiteId && propertyName),
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
import { useApi } from '../useApi';
|
import { useApi } from '../useApi';
|
||||||
import { useFilterParams } from '../useFilterParams';
|
import { useFilterParameters } from '../useFilterParameters';
|
||||||
|
import { useDateParameters } from '../useDateParameters';
|
||||||
import { usePagedQuery } from '../usePagedQuery';
|
import { usePagedQuery } from '../usePagedQuery';
|
||||||
import { ReactQueryOptions } from '@/lib/types';
|
import { ReactQueryOptions } from '@/lib/types';
|
||||||
|
|
||||||
export function useWebsiteEventsQuery(websiteId: string, options?: ReactQueryOptions<any>) {
|
export function useWebsiteEventsQuery(websiteId: string, options?: ReactQueryOptions<any>) {
|
||||||
const { get } = useApi();
|
const { get } = useApi();
|
||||||
const queryParams = useFilterParams(websiteId);
|
const date = useDateParameters(websiteId);
|
||||||
|
const filters = useFilterParameters();
|
||||||
|
|
||||||
return usePagedQuery({
|
return usePagedQuery({
|
||||||
queryKey: ['websites:events', { websiteId, ...queryParams }],
|
queryKey: ['websites:events', { websiteId, ...date, ...filters }],
|
||||||
queryFn: () => get(`/websites/${websiteId}/events`, { ...queryParams, pageSize: 20 }),
|
queryFn: () => get(`/websites/${websiteId}/events`, { ...date, ...filters, pageSize: 20 }),
|
||||||
enabled: !!websiteId,
|
enabled: !!websiteId,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
import { useApi } from '../useApi';
|
import { useApi } from '../useApi';
|
||||||
import { useFilterParams } from '../useFilterParams';
|
import { useFilterParameters } from '../useFilterParameters';
|
||||||
|
import { useDateParameters } from '../useDateParameters';
|
||||||
import { ReactQueryOptions } from '@/lib/types';
|
import { ReactQueryOptions } from '@/lib/types';
|
||||||
|
|
||||||
export function useWebsiteEventsSeriesQuery(websiteId: string, options?: ReactQueryOptions<any>) {
|
export function useWebsiteEventsSeriesQuery(websiteId: string, options?: ReactQueryOptions<any>) {
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const params = useFilterParams(websiteId);
|
const date = useDateParameters(websiteId);
|
||||||
|
const filters = useFilterParameters();
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['websites:events:series', { websiteId, ...params }],
|
queryKey: ['websites:events:series', { websiteId, ...date, ...filters }],
|
||||||
queryFn: () => get(`/websites/${websiteId}/events/series`, { ...params }),
|
queryFn: () => get(`/websites/${websiteId}/events/series`, { ...date, ...filters }),
|
||||||
enabled: !!websiteId,
|
enabled: !!websiteId,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { keepPreviousData } from '@tanstack/react-query';
|
import { keepPreviousData } from '@tanstack/react-query';
|
||||||
import { useApi } from '../useApi';
|
import { useApi } from '../useApi';
|
||||||
import { useFilterParams } from '../useFilterParams';
|
import { useFilterParameters } from '../useFilterParameters';
|
||||||
|
import { useDateParameters } from '../useDateParameters';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
import { ReactQueryOptions } from '@/lib/types';
|
import { ReactQueryOptions } from '@/lib/types';
|
||||||
|
|
||||||
|
|
@ -11,11 +12,12 @@ export type WebsiteMetricsData = {
|
||||||
|
|
||||||
export function useWebsiteMetricsQuery(
|
export function useWebsiteMetricsQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
params: { type: string; limit?: number; search?: string; startAt?: number; endAt?: number },
|
params: { type: string; limit?: number; search?: string },
|
||||||
options?: ReactQueryOptions<WebsiteMetricsData>,
|
options?: ReactQueryOptions<WebsiteMetricsData>,
|
||||||
) {
|
) {
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const queryParams = useFilterParams(websiteId);
|
const date = useDateParameters(websiteId);
|
||||||
|
const filters = useFilterParameters();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
return useQuery<WebsiteMetricsData>({
|
return useQuery<WebsiteMetricsData>({
|
||||||
|
|
@ -23,13 +25,15 @@ export function useWebsiteMetricsQuery(
|
||||||
'websites:metrics',
|
'websites:metrics',
|
||||||
{
|
{
|
||||||
websiteId,
|
websiteId,
|
||||||
...queryParams,
|
...date,
|
||||||
|
...filters,
|
||||||
...params,
|
...params,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
queryFn: async () =>
|
queryFn: async () =>
|
||||||
get(`/websites/${websiteId}/metrics`, {
|
get(`/websites/${websiteId}/metrics`, {
|
||||||
...queryParams,
|
...date,
|
||||||
|
...filters,
|
||||||
[searchParams.get('view')]: undefined,
|
[searchParams.get('view')]: undefined,
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useApi } from '../useApi';
|
import { useApi } from '../useApi';
|
||||||
import { useFilterParams } from '../useFilterParams';
|
import { useFilterParameters } from '../useFilterParameters';
|
||||||
|
import { useDateParameters } from '../useDateParameters';
|
||||||
import { ReactQueryOptions } from '@/lib/types';
|
import { ReactQueryOptions } from '@/lib/types';
|
||||||
|
|
||||||
export interface WebsitePageviewsData {
|
export interface WebsitePageviewsData {
|
||||||
|
|
@ -12,11 +13,12 @@ export function useWebsitePageviewsQuery(
|
||||||
options?: ReactQueryOptions<WebsitePageviewsData>,
|
options?: ReactQueryOptions<WebsitePageviewsData>,
|
||||||
) {
|
) {
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const queryParams = useFilterParams(websiteId);
|
const date = useDateParameters(websiteId);
|
||||||
|
const queryParams = useFilterParameters();
|
||||||
|
|
||||||
return useQuery<WebsitePageviewsData>({
|
return useQuery<WebsitePageviewsData>({
|
||||||
queryKey: ['websites:pageviews', { websiteId, compare, ...queryParams }],
|
queryKey: ['websites:pageviews', { websiteId, compare, ...date, ...queryParams }],
|
||||||
queryFn: () => get(`/websites/${websiteId}/pageviews`, { compare, ...queryParams }),
|
queryFn: () => get(`/websites/${websiteId}/pageviews`, { compare, ...date, ...queryParams }),
|
||||||
enabled: !!websiteId,
|
enabled: !!websiteId,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
import { useApi } from '../useApi';
|
import { useApi } from '../useApi';
|
||||||
import { useFilterParams } from '../useFilterParams';
|
import { useFilterParameters } from '../useFilterParameters';
|
||||||
|
import { useDateParameters } from '../useDateParameters';
|
||||||
|
|
||||||
export function useWebsiteSessionStatsQuery(websiteId: string, options?: Record<string, string>) {
|
export function useWebsiteSessionStatsQuery(websiteId: string, options?: Record<string, string>) {
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const params = useFilterParams(websiteId);
|
const date = useDateParameters(websiteId);
|
||||||
|
const filters = useFilterParameters();
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['sessions:stats', { websiteId, ...params }],
|
queryKey: ['sessions:stats', { websiteId, ...date, ...filters }],
|
||||||
queryFn: () => get(`/websites/${websiteId}/sessions/stats`, { ...params }),
|
queryFn: () => get(`/websites/${websiteId}/sessions/stats`, { ...date, ...filters }),
|
||||||
enabled: !!websiteId,
|
enabled: !!websiteId,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { useApi } from '../useApi';
|
import { useApi } from '../useApi';
|
||||||
import { usePagedQuery } from '../usePagedQuery';
|
import { usePagedQuery } from '../usePagedQuery';
|
||||||
import { useModified } from '../useModified';
|
import { useModified } from '../useModified';
|
||||||
import { useFilterParams } from '@/components/hooks/useFilterParams';
|
import { useFilterParameters } from '../useFilterParameters';
|
||||||
|
import { useDateParameters } from '../useDateParameters';
|
||||||
|
|
||||||
export function useWebsiteSessionsQuery(
|
export function useWebsiteSessionsQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
|
|
@ -9,14 +10,16 @@ export function useWebsiteSessionsQuery(
|
||||||
) {
|
) {
|
||||||
const { get } = useApi();
|
const { get } = useApi();
|
||||||
const { modified } = useModified(`sessions`);
|
const { modified } = useModified(`sessions`);
|
||||||
const filters = useFilterParams(websiteId);
|
const date = useDateParameters(websiteId);
|
||||||
|
const filters = useFilterParameters();
|
||||||
|
|
||||||
return usePagedQuery({
|
return usePagedQuery({
|
||||||
queryKey: ['sessions', { websiteId, modified, ...params, ...filters }],
|
queryKey: ['sessions', { websiteId, modified, ...params, ...date, ...filters }],
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
return get(`/websites/${websiteId}/sessions`, {
|
return get(`/websites/${websiteId}/sessions`, {
|
||||||
...params,
|
...params,
|
||||||
...filters,
|
...filters,
|
||||||
|
...date,
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useApi } from '../useApi';
|
import { useApi } from '../useApi';
|
||||||
import { useModified } from '../useModified';
|
import { useModified } from '../useModified';
|
||||||
import { useFilterParams } from '@/components/hooks/useFilterParams';
|
import { useDateParameters } from '../useDateParameters';
|
||||||
|
import { useFilterParameters } from '@/components/hooks/useFilterParameters';
|
||||||
|
|
||||||
export function useWebsiteSessionsWeeklyQuery(
|
export function useWebsiteSessionsWeeklyQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
|
|
@ -8,13 +9,15 @@ export function useWebsiteSessionsWeeklyQuery(
|
||||||
) {
|
) {
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const { modified } = useModified(`sessions`);
|
const { modified } = useModified(`sessions`);
|
||||||
const filters = useFilterParams(websiteId);
|
const date = useDateParameters(websiteId);
|
||||||
|
const filters = useFilterParameters();
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['sessions', { websiteId, modified, ...params, ...filters }],
|
queryKey: ['sessions', { websiteId, modified, ...params, ...date, ...filters }],
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
return get(`/websites/${websiteId}/sessions/weekly`, {
|
return get(`/websites/${websiteId}/sessions/weekly`, {
|
||||||
...params,
|
...params,
|
||||||
|
...date,
|
||||||
...filters,
|
...filters,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { UseQueryOptions } from '@tanstack/react-query';
|
import { UseQueryOptions } from '@tanstack/react-query';
|
||||||
import { useApi } from '../useApi';
|
import { useApi } from '../useApi';
|
||||||
import { useFilterParams } from '../useFilterParams';
|
import { useFilterParameters } from '../useFilterParameters';
|
||||||
|
import { useDateParameters } from '@/components/hooks/useDateParameters';
|
||||||
|
|
||||||
export interface WebsiteStatsData {
|
export interface WebsiteStatsData {
|
||||||
pageviews: number;
|
pageviews: number;
|
||||||
|
|
@ -8,7 +9,7 @@ export interface WebsiteStatsData {
|
||||||
visits: number;
|
visits: number;
|
||||||
bounces: number;
|
bounces: number;
|
||||||
totaltime: number;
|
totaltime: number;
|
||||||
previous: {
|
comparison: {
|
||||||
pageviews: number;
|
pageviews: number;
|
||||||
visitors: number;
|
visitors: number;
|
||||||
visits: number;
|
visits: number;
|
||||||
|
|
@ -22,11 +23,12 @@ export function useWebsiteStatsQuery(
|
||||||
options?: UseQueryOptions<WebsiteStatsData, Error, WebsiteStatsData>,
|
options?: UseQueryOptions<WebsiteStatsData, Error, WebsiteStatsData>,
|
||||||
) {
|
) {
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const filterParams = useFilterParams(websiteId);
|
const date = useDateParameters(websiteId);
|
||||||
|
const filters = useFilterParameters();
|
||||||
|
|
||||||
return useQuery<WebsiteStatsData>({
|
return useQuery<WebsiteStatsData>({
|
||||||
queryKey: ['websites:stats', { websiteId, ...filterParams }],
|
queryKey: ['websites:stats', { websiteId, ...date, ...filters }],
|
||||||
queryFn: () => get(`/websites/${websiteId}/stats`, { ...filterParams }),
|
queryFn: () => get(`/websites/${websiteId}/stats`, { ...date, ...filters }),
|
||||||
enabled: !!websiteId,
|
enabled: !!websiteId,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
16
src/components/hooks/uesPageParameters.ts
Normal file
16
src/components/hooks/uesPageParameters.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useNavigation } from './useNavigation';
|
||||||
|
|
||||||
|
export function usePageParameters() {
|
||||||
|
const {
|
||||||
|
query: { page, pageSize, search },
|
||||||
|
} = useNavigation();
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
return {
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
search,
|
||||||
|
};
|
||||||
|
}, [page, pageSize, search]);
|
||||||
|
}
|
||||||
18
src/components/hooks/useDateParameters.ts
Normal file
18
src/components/hooks/useDateParameters.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { useDateRange } from './useDateRange';
|
||||||
|
import { useTimezone } from './useTimezone';
|
||||||
|
|
||||||
|
export function useDateParameters(websiteId: string) {
|
||||||
|
const {
|
||||||
|
dateRange: { startDate, endDate, unit },
|
||||||
|
} = useDateRange(websiteId);
|
||||||
|
const { timezone, toUtc } = useTimezone();
|
||||||
|
|
||||||
|
return {
|
||||||
|
startAt: +toUtc(startDate),
|
||||||
|
endAt: +toUtc(endDate),
|
||||||
|
startDate: toUtc(startDate).toISOString(),
|
||||||
|
endDate: toUtc(endDate).toISOString(),
|
||||||
|
unit,
|
||||||
|
timezone,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,11 @@ import { useApi } from './useApi';
|
||||||
import { useNavigation } from './useNavigation';
|
import { useNavigation } from './useNavigation';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
export function useDateRange(websiteId?: string) {
|
export interface UseDateRangeOptions {
|
||||||
|
ignoreOffset?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDateRange(websiteId?: string, options: UseDateRangeOptions = {}) {
|
||||||
const { get } = useApi();
|
const { get } = useApi();
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
const {
|
const {
|
||||||
|
|
@ -16,14 +20,16 @@ export function useDateRange(websiteId?: string) {
|
||||||
} = useNavigation();
|
} = useNavigation();
|
||||||
const websiteConfig = useWebsites(state => state[websiteId]?.dateRange);
|
const websiteConfig = useWebsites(state => state[websiteId]?.dateRange);
|
||||||
const globalConfig = useApp(state => state.dateRangeValue);
|
const globalConfig = useApp(state => state.dateRangeValue);
|
||||||
const dateRangeObject = parseDateRange(
|
const dateValue = date || websiteConfig?.value || globalConfig || DEFAULT_DATE_RANGE_VALUE;
|
||||||
date || websiteConfig?.value || globalConfig || DEFAULT_DATE_RANGE_VALUE,
|
|
||||||
locale,
|
const dateRange = useMemo(() => {
|
||||||
);
|
const dateRangeObject = parseDateRange(dateValue, locale);
|
||||||
const dateRange = useMemo(
|
|
||||||
() => (offset ? getOffsetDateRange(dateRangeObject, +offset) : dateRangeObject),
|
return !options.ignoreOffset && offset
|
||||||
[date, offset, websiteConfig],
|
? getOffsetDateRange(dateRangeObject, +offset)
|
||||||
);
|
: dateRangeObject;
|
||||||
|
}, [date, offset, dateValue, options]);
|
||||||
|
|
||||||
const dateCompare = useWebsites(state => state[websiteId]?.dateCompare || DEFAULT_DATE_COMPARE);
|
const dateCompare = useWebsites(state => state[websiteId]?.dateCompare || DEFAULT_DATE_COMPARE);
|
||||||
|
|
||||||
const saveDateRange = async (value: string) => {
|
const saveDateRange = async (value: string) => {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
|
import { useMemo } from 'react';
|
||||||
import { useNavigation } from './useNavigation';
|
import { useNavigation } from './useNavigation';
|
||||||
import { useDateRange } from './useDateRange';
|
|
||||||
import { useTimezone } from './useTimezone';
|
|
||||||
|
|
||||||
export function useFilterParams(websiteId: string) {
|
export function useFilterParameters() {
|
||||||
const { dateRange } = useDateRange(websiteId);
|
|
||||||
const { startDate, endDate, unit } = dateRange;
|
|
||||||
const { timezone, toUtc } = useTimezone();
|
|
||||||
const {
|
const {
|
||||||
query: {
|
query: {
|
||||||
path,
|
path,
|
||||||
|
|
@ -28,13 +24,25 @@ export function useFilterParams(websiteId: string) {
|
||||||
},
|
},
|
||||||
} = useNavigation();
|
} = useNavigation();
|
||||||
|
|
||||||
return {
|
return useMemo(() => {
|
||||||
// Date range
|
return {
|
||||||
startAt: +toUtc(startDate),
|
path,
|
||||||
endAt: +toUtc(endDate),
|
referrer,
|
||||||
unit,
|
title,
|
||||||
timezone,
|
query,
|
||||||
// Filters
|
host,
|
||||||
|
os,
|
||||||
|
browser,
|
||||||
|
device,
|
||||||
|
country,
|
||||||
|
region,
|
||||||
|
city,
|
||||||
|
event,
|
||||||
|
tag,
|
||||||
|
hostname,
|
||||||
|
search,
|
||||||
|
};
|
||||||
|
}, [
|
||||||
path,
|
path,
|
||||||
referrer,
|
referrer,
|
||||||
title,
|
title,
|
||||||
|
|
@ -49,9 +57,8 @@ export function useFilterParams(websiteId: string) {
|
||||||
event,
|
event,
|
||||||
tag,
|
tag,
|
||||||
hostname,
|
hostname,
|
||||||
// Paging
|
|
||||||
page,
|
page,
|
||||||
pageSize,
|
pageSize,
|
||||||
search,
|
search,
|
||||||
};
|
]);
|
||||||
}
|
}
|
||||||
|
|
@ -46,7 +46,12 @@ export function MetricsTable({
|
||||||
|
|
||||||
const { data, isLoading, isFetching, error } = useWebsiteMetricsQuery(
|
const { data, isLoading, isFetching, error } = useWebsiteMetricsQuery(
|
||||||
websiteId,
|
websiteId,
|
||||||
{ type, limit, search: searchFormattedValues ? undefined : search, ...params },
|
{
|
||||||
|
type,
|
||||||
|
limit,
|
||||||
|
search: searchFormattedValues ? undefined : search,
|
||||||
|
...params,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
retryDelay: delay || DEFAULT_ANIMATION_DURATION,
|
retryDelay: delay || DEFAULT_ANIMATION_DURATION,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,7 @@ function getQueryParams(filters: Record<string, any>) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function parseFilters(filters: Record<string, any>, options?: QueryOptions) {
|
function parseFilters(filters: Record<string, any>, options?: QueryOptions) {
|
||||||
return {
|
return {
|
||||||
filterQuery: getFilterQuery(filters, options),
|
filterQuery: getFilterQuery(filters, options),
|
||||||
dateQuery: getDateQuery(filters),
|
dateQuery: getDateQuery(filters),
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@ const enabled = Boolean(process.env.KAFKA_URL && process.env.KAFKA_BROKER);
|
||||||
function getClient() {
|
function getClient() {
|
||||||
const { username, password } = new URL(process.env.KAFKA_URL);
|
const { username, password } = new URL(process.env.KAFKA_URL);
|
||||||
const brokers = process.env.KAFKA_BROKER.split(',');
|
const brokers = process.env.KAFKA_BROKER.split(',');
|
||||||
const mechanism = process.env.KAFKA_SASL_MECHANISM as 'plain' | 'scram-sha-256' | 'scram-sha-512';
|
const mechanism =
|
||||||
|
(process.env.KAFKA_SASL_MECHANISM as 'plain' | 'scram-sha-256' | 'scram-sha-512') || 'plain';
|
||||||
|
|
||||||
const ssl: { ssl?: tls.ConnectionOptions | boolean; sasl?: SASLOptions } =
|
const ssl: { ssl?: tls.ConnectionOptions | boolean; sasl?: SASLOptions } =
|
||||||
username && password
|
username && password
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ export function filtersToArray(filters: QueryFilters, options: QueryOptions = {}
|
||||||
column: options?.columns?.[key] ?? FILTER_COLUMNS[key],
|
column: options?.columns?.[key] ?? FILTER_COLUMNS[key],
|
||||||
operator,
|
operator,
|
||||||
value,
|
value,
|
||||||
|
prefix: options?.prefix,
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,15 +84,10 @@ function getTimestampDiffSQL(field1: string, field2: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSearchSQL(column: string, param: string = 'search'): string {
|
function getSearchSQL(column: string, param: string = 'search'): string {
|
||||||
const db = getDatabaseType();
|
return `and ${column} ilike {{${param}}}`;
|
||||||
const like = db === POSTGRESQL ? 'ilike' : 'like';
|
|
||||||
|
|
||||||
return `and ${column} ${like} {{${param}}}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapFilter(column: string, operator: string, name: string, type: string = '') {
|
function mapFilter(column: string, operator: string, name: string, type: string = '') {
|
||||||
const db = getDatabaseType();
|
|
||||||
const like = db === POSTGRESQL ? 'ilike' : 'like';
|
|
||||||
const value = `{{${name}${type ? `::${type}` : ''}}}`;
|
const value = `{{${name}${type ? `::${type}` : ''}}}`;
|
||||||
|
|
||||||
switch (operator) {
|
switch (operator) {
|
||||||
|
|
@ -101,28 +96,31 @@ function mapFilter(column: string, operator: string, name: string, type: string
|
||||||
case OPERATORS.notEquals:
|
case OPERATORS.notEquals:
|
||||||
return `${column} != ${value}`;
|
return `${column} != ${value}`;
|
||||||
case OPERATORS.contains:
|
case OPERATORS.contains:
|
||||||
return `${column} ${like} ${value}`;
|
return `${column} ilike ${value}`;
|
||||||
case OPERATORS.doesNotContain:
|
case OPERATORS.doesNotContain:
|
||||||
return `${column} not ${like} ${value}`;
|
return `${column} not ilike ${value}`;
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFilterQuery(filters: Record<string, any>, options: QueryOptions = {}): string {
|
function getFilterQuery(filters: Record<string, any>, options: QueryOptions = {}): string {
|
||||||
const query = filtersToArray(filters, options).reduce((arr, { name, column, operator }) => {
|
const query = filtersToArray(filters, options).reduce(
|
||||||
if (column) {
|
(arr, { name, column, operator, prefix = '' }) => {
|
||||||
arr.push(`and ${mapFilter(column, operator, name)}`);
|
if (column) {
|
||||||
|
arr.push(`and ${mapFilter(`${prefix}${column}`, operator, name)}`);
|
||||||
|
|
||||||
if (name === 'referrer') {
|
if (name === 'referrer') {
|
||||||
arr.push(
|
arr.push(
|
||||||
`and (website_event.referrer_domain != website_event.hostname or website_event.referrer_domain is null)`,
|
`and (website_event.referrer_domain != website_event.hostname or website_event.referrer_domain is null)`,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return arr;
|
return arr;
|
||||||
}, []);
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
return query.join('\n');
|
return query.join('\n');
|
||||||
}
|
}
|
||||||
|
|
@ -154,7 +152,7 @@ function getQueryParams(filters: Record<string, any>) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function parseFilters(filters: Record<string, any>, options?: QueryOptions) {
|
function parseFilters(filters: Record<string, any>, options?: QueryOptions) {
|
||||||
const joinSession = Object.keys(filters).find(key =>
|
const joinSession = Object.keys(filters).find(key =>
|
||||||
['referrer', ...SESSION_COLUMNS].includes(key),
|
['referrer', ...SESSION_COLUMNS].includes(key),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { z } from 'zod/v4';
|
import { z } from 'zod/v4';
|
||||||
import { FILTER_COLUMNS, DEFAULT_PAGE_SIZE } from '@/lib/constants';
|
import { FILTER_COLUMNS, DEFAULT_PAGE_SIZE } from '@/lib/constants';
|
||||||
import { badRequest, unauthorized } from '@/lib/response';
|
import { badRequest, unauthorized } from '@/lib/response';
|
||||||
import { getAllowedUnits, getCompareDate, getMinimumUnit, maxDate } from '@/lib/date';
|
import { getAllowedUnits, getMinimumUnit, maxDate } from '@/lib/date';
|
||||||
import { checkAuth } from '@/lib/auth';
|
import { checkAuth } from '@/lib/auth';
|
||||||
import { fetchWebsite } from '@/lib/load';
|
import { fetchWebsite } from '@/lib/load';
|
||||||
import { QueryFilters } from '@/lib/types';
|
import { QueryFilters } from '@/lib/types';
|
||||||
|
|
@ -50,23 +50,14 @@ export async function getJsonBody(request: Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRequestDateRange(query: Record<string, string>) {
|
export function getRequestDateRange(query: Record<string, string>) {
|
||||||
const { startAt, endAt, unit, compare, timezone } = query;
|
const { startAt, endAt, unit, timezone } = query;
|
||||||
|
|
||||||
const startDate = new Date(+startAt);
|
const startDate = new Date(+startAt);
|
||||||
const endDate = new Date(+endAt);
|
const endDate = new Date(+endAt);
|
||||||
|
|
||||||
const { startDate: compareStartDate, endDate: compareEndDate } = getCompareDate(
|
|
||||||
compare,
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
compare,
|
|
||||||
compareStartDate,
|
|
||||||
compareEndDate,
|
|
||||||
timezone,
|
timezone,
|
||||||
unit: getAllowedUnits(startDate, endDate).includes(unit)
|
unit: getAllowedUnits(startDate, endDate).includes(unit)
|
||||||
? unit
|
? unit
|
||||||
|
|
@ -86,11 +77,21 @@ export function getRequestFilters(query: Record<string, any>) {
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getQueryFilters(params: Record<string, any>): Promise<QueryFilters> {
|
export async function setWebsiteDate(websiteId: string, data: Record<string, any>) {
|
||||||
|
const website = await fetchWebsite(websiteId);
|
||||||
|
|
||||||
|
if (website) {
|
||||||
|
data.startDate = maxDate(data.startDate, new Date(website?.resetAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getQueryFilters(params: Record<string, any>): QueryFilters {
|
||||||
const dateRange = getRequestDateRange(params);
|
const dateRange = getRequestDateRange(params);
|
||||||
const filters = getRequestFilters(params);
|
const filters = getRequestFilters(params);
|
||||||
|
|
||||||
const data = {
|
return {
|
||||||
...dateRange,
|
...dateRange,
|
||||||
...filters,
|
...filters,
|
||||||
page: params?.page,
|
page: params?.page,
|
||||||
|
|
@ -98,17 +99,5 @@ export async function getQueryFilters(params: Record<string, any>): Promise<Quer
|
||||||
orderBy: params?.orderBy,
|
orderBy: params?.orderBy,
|
||||||
sortDescending: params?.sortDescending,
|
sortDescending: params?.sortDescending,
|
||||||
search: params?.search,
|
search: params?.search,
|
||||||
websiteId: undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const { websiteId } = params;
|
|
||||||
|
|
||||||
if (websiteId) {
|
|
||||||
const website = await fetchWebsite(websiteId);
|
|
||||||
|
|
||||||
data.websiteId = websiteId;
|
|
||||||
data.startDate = maxDate(data.startDate, new Date(website?.resetAt));
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,10 @@ export const unitParam = z.string().refine(value => UNIT_TYPES.includes(value),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const dateRangeParams = {
|
export const dateRangeParams = {
|
||||||
startAt: z.coerce.number(),
|
startAt: z.coerce.number().optional(),
|
||||||
endAt: z.coerce.number(),
|
endAt: z.coerce.number().optional(),
|
||||||
|
startDate: z.coerce.date().optional(),
|
||||||
|
endDate: z.coerce.date().optional(),
|
||||||
timezone: timezoneParam.optional(),
|
timezone: timezoneParam.optional(),
|
||||||
unit: unitParam.optional(),
|
unit: unitParam.optional(),
|
||||||
compare: z.string().optional(),
|
compare: z.string().optional(),
|
||||||
|
|
@ -33,12 +35,12 @@ export const filterParams = {
|
||||||
hostname: z.string().optional(),
|
hostname: z.string().optional(),
|
||||||
language: z.string().optional(),
|
language: z.string().optional(),
|
||||||
event: z.string().optional(),
|
event: z.string().optional(),
|
||||||
|
search: z.string().optional(),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pagingParams = {
|
export const pagingParams = {
|
||||||
page: z.coerce.number().int().positive().optional(),
|
page: z.coerce.number().int().positive().optional(),
|
||||||
pageSize: z.coerce.number().int().positive().optional(),
|
pageSize: z.coerce.number().int().positive().optional(),
|
||||||
search: z.string().optional(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sortingParams = {
|
export const sortingParams = {
|
||||||
|
|
@ -93,23 +95,63 @@ export const reportTypeParam = z.enum([
|
||||||
'utm',
|
'utm',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const reportParms = {
|
export const dateRangeSchema = z.object({ ...dateRangeParams }).superRefine((data, ctx) => {
|
||||||
websiteId: z.string().uuid(),
|
const hasTimestamps = data.startAt !== undefined && data.endAt !== undefined;
|
||||||
dateRange: z.object({
|
const hasDates = data.startDate !== undefined && data.endDate !== undefined;
|
||||||
startDate: z.coerce.date(),
|
|
||||||
endDate: z.coerce.date(),
|
if (!hasTimestamps && !hasDates) {
|
||||||
timezone: timezoneParam.optional(),
|
ctx.addIssue({
|
||||||
unit: unitParam.optional(),
|
code: z.ZodIssueCode.custom,
|
||||||
compare: z.string().optional(),
|
message: 'You must provide either startAt & endAt or startDate & endDate.',
|
||||||
compareStartDate: z.coerce.date().optional(),
|
});
|
||||||
compareEndDate: z.coerce.date().optional(),
|
}
|
||||||
}),
|
|
||||||
};
|
if (hasTimestamps && hasDates) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: 'Provide either startAt & endAt or startDate & endDate, not both.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.startAt !== undefined && data.endAt === undefined) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: 'If you provide startAt, you must also provide endAt.',
|
||||||
|
path: ['endAt'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.endAt !== undefined && data.startAt === undefined) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: 'If you provide endAt, you must also provide startAt.',
|
||||||
|
path: ['startAt'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.startDate !== undefined && data.endDate === undefined) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: 'If you provide startDate, you must also provide endDate.',
|
||||||
|
path: ['endDate'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.endDate !== undefined && data.startDate === undefined) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: 'If you provide endDate, you must also provide startDate.',
|
||||||
|
path: ['startDate'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export const goalReportSchema = z.object({
|
export const goalReportSchema = z.object({
|
||||||
type: z.literal('goal'),
|
type: z.literal('goal'),
|
||||||
parameters: z
|
parameters: z
|
||||||
.object({
|
.object({
|
||||||
|
startDate: z.coerce.date(),
|
||||||
|
endDate: z.coerce.date(),
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
value: z.string(),
|
value: z.string(),
|
||||||
operator: z.enum(['count', 'sum', 'average']).optional(),
|
operator: z.enum(['count', 'sum', 'average']).optional(),
|
||||||
|
|
@ -126,6 +168,8 @@ export const goalReportSchema = z.object({
|
||||||
export const funnelReportSchema = z.object({
|
export const funnelReportSchema = z.object({
|
||||||
type: z.literal('funnel'),
|
type: z.literal('funnel'),
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
|
startDate: z.coerce.date(),
|
||||||
|
endDate: z.coerce.date(),
|
||||||
window: z.coerce.number().positive(),
|
window: z.coerce.number().positive(),
|
||||||
steps: z
|
steps: z
|
||||||
.array(
|
.array(
|
||||||
|
|
@ -142,6 +186,8 @@ export const funnelReportSchema = z.object({
|
||||||
export const journeyReportSchema = z.object({
|
export const journeyReportSchema = z.object({
|
||||||
type: z.literal('journey'),
|
type: z.literal('journey'),
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
|
startDate: z.coerce.date(),
|
||||||
|
endDate: z.coerce.date(),
|
||||||
steps: z.coerce.number().min(2).max(7),
|
steps: z.coerce.number().min(2).max(7),
|
||||||
startStep: z.string().optional(),
|
startStep: z.string().optional(),
|
||||||
endStep: z.string().optional(),
|
endStep: z.string().optional(),
|
||||||
|
|
@ -150,15 +196,27 @@ export const journeyReportSchema = z.object({
|
||||||
|
|
||||||
export const retentionReportSchema = z.object({
|
export const retentionReportSchema = z.object({
|
||||||
type: z.literal('retention'),
|
type: z.literal('retention'),
|
||||||
|
parameters: z.object({
|
||||||
|
startDate: z.coerce.date(),
|
||||||
|
endDate: z.coerce.date(),
|
||||||
|
timezone: z.string().optional(),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const utmReportSchema = z.object({
|
export const utmReportSchema = z.object({
|
||||||
type: z.literal('utm'),
|
type: z.literal('utm'),
|
||||||
|
parameters: z.object({
|
||||||
|
startDate: z.coerce.date(),
|
||||||
|
endDate: z.coerce.date(),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const revenueReportSchema = z.object({
|
export const revenueReportSchema = z.object({
|
||||||
type: z.literal('revenue'),
|
type: z.literal('revenue'),
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
|
startDate: z.coerce.date(),
|
||||||
|
endDate: z.coerce.date(),
|
||||||
|
timezone: z.string().optional(),
|
||||||
currency: z.string(),
|
currency: z.string(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
@ -166,6 +224,8 @@ export const revenueReportSchema = z.object({
|
||||||
export const attributionReportSchema = z.object({
|
export const attributionReportSchema = z.object({
|
||||||
type: z.literal('attribution'),
|
type: z.literal('attribution'),
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
|
startDate: z.coerce.date(),
|
||||||
|
endDate: z.coerce.date(),
|
||||||
model: z.enum(['first-click', 'last-click']),
|
model: z.enum(['first-click', 'last-click']),
|
||||||
type: z.enum(['page', 'event']),
|
type: z.enum(['page', 'event']),
|
||||||
step: z.string(),
|
step: z.string(),
|
||||||
|
|
@ -176,6 +236,8 @@ export const attributionReportSchema = z.object({
|
||||||
export const breakdownReportSchema = z.object({
|
export const breakdownReportSchema = z.object({
|
||||||
type: z.literal('breakdown'),
|
type: z.literal('breakdown'),
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
|
startDate: z.coerce.date(),
|
||||||
|
endDate: z.coerce.date(),
|
||||||
fields: z.array(fieldsParam),
|
fields: z.array(fieldsParam),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
@ -202,7 +264,7 @@ export const reportSchema = z.intersection(reportBaseSchema, reportTypeSchema);
|
||||||
|
|
||||||
export const reportResultSchema = z.intersection(
|
export const reportResultSchema = z.intersection(
|
||||||
z.object({
|
z.object({
|
||||||
...reportParms,
|
websiteId: z.string().uuid(),
|
||||||
filters: z.object({ ...filterParams }),
|
filters: z.object({ ...filterParams }),
|
||||||
}),
|
}),
|
||||||
reportTypeSchema,
|
reportTypeSchema,
|
||||||
|
|
|
||||||
|
|
@ -41,19 +41,20 @@ export interface QueryOptions {
|
||||||
joinSession?: boolean;
|
joinSession?: boolean;
|
||||||
columns?: Record<string, string>;
|
columns?: Record<string, string>;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
|
prefix?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryFilters {
|
export interface QueryFilters extends DateParams, FilterParams, SortParams, PageParams {}
|
||||||
websiteId?: string;
|
|
||||||
// Date range
|
export interface DateParams {
|
||||||
startDate?: Date;
|
startDate?: Date;
|
||||||
endDate?: Date;
|
endDate?: Date;
|
||||||
compareStartDate?: Date;
|
|
||||||
compareEndDate?: Date;
|
|
||||||
compare?: string;
|
|
||||||
unit?: string;
|
unit?: string;
|
||||||
timezone?: string;
|
timezone?: string;
|
||||||
// Filters
|
compareDate?: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FilterParams {
|
||||||
path?: string;
|
path?: string;
|
||||||
referrer?: string;
|
referrer?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
|
@ -70,20 +71,16 @@ export interface QueryFilters {
|
||||||
search?: string;
|
search?: string;
|
||||||
tag?: string;
|
tag?: string;
|
||||||
eventType?: number;
|
eventType?: number;
|
||||||
// Paging
|
}
|
||||||
page?: number;
|
|
||||||
pageSize?: number;
|
export interface SortParams {
|
||||||
// Sorting
|
|
||||||
orderBy?: string;
|
orderBy?: string;
|
||||||
sortDescending?: boolean;
|
sortDescending?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PageParams {
|
export interface PageParams {
|
||||||
page: number;
|
page?: number;
|
||||||
pageSize: number;
|
pageSize?: number;
|
||||||
orderBy?: string;
|
|
||||||
sortDescending?: boolean;
|
|
||||||
search?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PageResult<T> {
|
export interface PageResult<T> {
|
||||||
|
|
|
||||||
|
|
@ -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 { queryParams } = await parseFilters(filters);
|
const { queryParams } = parseFilters(filters);
|
||||||
|
|
||||||
if (event) {
|
if (event) {
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
|
|
@ -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 { queryParams } = await parseFilters(filters);
|
const { queryParams } = parseFilters(filters);
|
||||||
|
|
||||||
if (event) {
|
if (event) {
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ export async function getEventDataFields(...args: [websiteId: string, filters: Q
|
||||||
|
|
||||||
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, queryParams } = await parseFilters(filters);
|
const { filterQuery, queryParams } = parseFilters(filters);
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -43,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, queryParams } = await parseFilters(filters);
|
const { filterQuery, queryParams } = parseFilters(filters);
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
|
||||||
|
|
@ -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, queryParams } = await parseFilters(filters, {
|
const { filterQuery, queryParams } = parseFilters(filters, {
|
||||||
columns: { propertyName: 'data_key' },
|
columns: { propertyName: 'data_key' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -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, queryParams } = await parseFilters(filters, {
|
const { filterQuery, queryParams } = parseFilters(filters, {
|
||||||
columns: { propertyName: 'data_key' },
|
columns: { propertyName: 'data_key' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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, queryParams } = await parseFilters({ ...filters, websiteId });
|
const { filterQuery, queryParams } = parseFilters({ ...filters, websiteId });
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -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, queryParams } = await parseFilters({ ...filters, websiteId });
|
const { filterQuery, queryParams } = parseFilters({ ...filters, websiteId });
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
|
||||||
|
|
@ -25,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, queryParams } = await parseFilters({ ...filters, websiteId });
|
const { filterQuery, queryParams } = parseFilters({ ...filters, websiteId });
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -56,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, queryParams } = await parseFilters({ ...filters, websiteId });
|
const { filterQuery, queryParams } = parseFilters({ ...filters, websiteId });
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,9 @@ 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, joinSessionQuery, queryParams } = await parseFilters({
|
const { filterQuery, joinSessionQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
|
websiteId,
|
||||||
eventType: EVENT_TYPE.customEvent,
|
eventType: EVENT_TYPE.customEvent,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -49,11 +50,12 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
async function clickhouseQuery(
|
async function clickhouseQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
): Promise<{ x: string; t: string; y: number }[]> {
|
): Promise<WebsiteEventMetricData[]> {
|
||||||
const { timezone = 'UTC', unit = 'day' } = filters;
|
const { timezone = 'UTC', unit = 'day' } = filters;
|
||||||
const { rawQuery, getDateSQL, parseFilters } = clickhouse;
|
const { rawQuery, getDateSQL, parseFilters } = clickhouse;
|
||||||
const { filterQuery, queryParams } = await parseFilters({
|
const { filterQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
|
websiteId,
|
||||||
eventType: EVENT_TYPE.customEvent,
|
eventType: EVENT_TYPE.customEvent,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ export function getWebsiteEvents(...args: [websiteId: string, filters: QueryFilt
|
||||||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
const { pagedRawQuery, parseFilters } = prisma;
|
const { pagedRawQuery, parseFilters } = prisma;
|
||||||
const { search } = filters;
|
const { search } = filters;
|
||||||
const { filterQuery, queryParams } = await parseFilters({
|
const { filterQuery, dateQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
});
|
});
|
||||||
|
|
@ -40,7 +40,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
event_name as "eventName"
|
event_name as "eventName"
|
||||||
from website_event
|
from website_event
|
||||||
where website_id = {{websiteId::uuid}}
|
where website_id = {{websiteId::uuid}}
|
||||||
and created_at between {{startDate}} and {{endDate}}
|
${dateQuery}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
${searchQuery}
|
${searchQuery}
|
||||||
order by created_at desc
|
order by created_at desc
|
||||||
|
|
@ -52,7 +52,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
|
|
||||||
async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
||||||
const { pagedRawQuery, parseFilters } = clickhouse;
|
const { pagedRawQuery, parseFilters } = clickhouse;
|
||||||
const { queryParams, dateQuery, filterQuery } = await parseFilters({
|
const { queryParams, dateQuery, filterQuery } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
});
|
});
|
||||||
|
|
@ -74,6 +74,10 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
||||||
referrer_path as referrerPath,
|
referrer_path as referrerPath,
|
||||||
referrer_query as referrerQuery,
|
referrer_query as referrerQuery,
|
||||||
referrer_domain as referrerDomain,
|
referrer_domain as referrerDomain,
|
||||||
|
country as country,
|
||||||
|
device as device,
|
||||||
|
os as os,
|
||||||
|
browser as browser,
|
||||||
page_title as pageTitle,
|
page_title as pageTitle,
|
||||||
event_type as eventType,
|
event_type as eventType,
|
||||||
event_name as eventName
|
event_name as eventName
|
||||||
|
|
|
||||||
|
|
@ -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 { queryParams, filterQuery, dateQuery } = await parseFilters(filters);
|
const { queryParams, filterQuery, dateQuery } = parseFilters(filters);
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -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 { queryParams, filterQuery, dateQuery } = await parseFilters(filters);
|
const { queryParams, filterQuery, dateQuery } = parseFilters(filters);
|
||||||
|
|
||||||
const sql = `
|
const sql = `
|
||||||
select
|
select
|
||||||
|
|
|
||||||
|
|
@ -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 { queryParams, filterQuery, dateQuery } = await parseFilters(filters);
|
const { queryParams, filterQuery, dateQuery } = parseFilters(filters);
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -41,7 +41,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
|
|
||||||
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 { queryParams, filterQuery, dateQuery } = await parseFilters(filters);
|
const { queryParams, filterQuery, dateQuery } = parseFilters(filters);
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ 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 { queryParams } = await parseFilters({
|
const { queryParams } = parseFilters({
|
||||||
startDate: new Date(DEFAULT_RESET_DATE),
|
startDate: new Date(DEFAULT_RESET_DATE),
|
||||||
websiteId,
|
websiteId,
|
||||||
});
|
});
|
||||||
|
|
@ -34,7 +34,7 @@ async function relationalQuery(websiteId: string) {
|
||||||
|
|
||||||
async function clickhouseQuery(websiteId: string) {
|
async function clickhouseQuery(websiteId: string) {
|
||||||
const { rawQuery, parseFilters } = clickhouse;
|
const { rawQuery, parseFilters } = clickhouse;
|
||||||
const { queryParams } = await parseFilters({
|
const { queryParams } = parseFilters({
|
||||||
startDate: new Date(DEFAULT_RESET_DATE),
|
startDate: new Date(DEFAULT_RESET_DATE),
|
||||||
websiteId,
|
websiteId,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ async function relationalQuery(
|
||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
): Promise<WebsiteStatsData[]> {
|
): Promise<WebsiteStatsData[]> {
|
||||||
const { getTimestampDiffSQL, parseFilters, rawQuery } = prisma;
|
const { getTimestampDiffSQL, parseFilters, rawQuery } = prisma;
|
||||||
const { filterQuery, joinSessionQuery, queryParams } = await parseFilters({
|
const { filterQuery, joinSessionQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
|
|
@ -66,7 +66,7 @@ async function clickhouseQuery(
|
||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
): Promise<WebsiteStatsData[]> {
|
): Promise<WebsiteStatsData[]> {
|
||||||
const { rawQuery, parseFilters } = clickhouse;
|
const { rawQuery, parseFilters } = clickhouse;
|
||||||
const { filterQuery, queryParams } = await parseFilters({
|
const { filterQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,19 @@ import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
|
||||||
import prisma from '@/lib/prisma';
|
import prisma from '@/lib/prisma';
|
||||||
import { QueryFilters } from '@/lib/types';
|
import { QueryFilters } from '@/lib/types';
|
||||||
|
|
||||||
|
export interface PageviewMetricsParameters {
|
||||||
|
type: string;
|
||||||
|
limit?: number | string;
|
||||||
|
offset?: number | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PageviewMetricsData {
|
||||||
|
x: string;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
export async function getPageviewMetrics(
|
export async function getPageviewMetrics(
|
||||||
...args: [
|
...args: [websiteId: string, parameters: PageviewMetricsParameters, filters: QueryFilters]
|
||||||
websiteId: string,
|
|
||||||
type: string,
|
|
||||||
filters: QueryFilters,
|
|
||||||
limit?: number | string,
|
|
||||||
offset?: number | string,
|
|
||||||
]
|
|
||||||
) {
|
) {
|
||||||
return runQuery({
|
return runQuery({
|
||||||
[PRISMA]: () => relationalQuery(...args),
|
[PRISMA]: () => relationalQuery(...args),
|
||||||
|
|
@ -21,16 +26,16 @@ export async function getPageviewMetrics(
|
||||||
|
|
||||||
async function relationalQuery(
|
async function relationalQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
type: string,
|
parameters: PageviewMetricsParameters,
|
||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
limit: number | string = 500,
|
): Promise<PageviewMetricsData[]> {
|
||||||
offset: number | string = 0,
|
const { type, limit = 500, offset = 0 } = parameters;
|
||||||
) {
|
|
||||||
const column = FILTER_COLUMNS[type] || type;
|
const column = FILTER_COLUMNS[type] || type;
|
||||||
const { rawQuery, parseFilters } = prisma;
|
const { rawQuery, parseFilters } = prisma;
|
||||||
const { filterQuery, joinSessionQuery, queryParams } = await parseFilters(
|
const { filterQuery, joinSessionQuery, queryParams } = parseFilters(
|
||||||
{
|
{
|
||||||
...filters,
|
...filters,
|
||||||
|
websiteId,
|
||||||
eventType: column === 'event_name' ? EVENT_TYPE.customEvent : EVENT_TYPE.pageView,
|
eventType: column === 'event_name' ? EVENT_TYPE.customEvent : EVENT_TYPE.pageView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -87,15 +92,15 @@ async function relationalQuery(
|
||||||
|
|
||||||
async function clickhouseQuery(
|
async function clickhouseQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
type: string,
|
parameters: PageviewMetricsParameters,
|
||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
limit: number | string = 500,
|
): Promise<PageviewMetricsData[]> {
|
||||||
offset: number | string = 0,
|
const { type, limit = 500, offset = 0 } = parameters;
|
||||||
): 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, queryParams } = await parseFilters({
|
const { filterQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
|
websiteId,
|
||||||
eventType: column === 'event_name' ? EVENT_TYPE.customEvent : EVENT_TYPE.pageView,
|
eventType: column === 'event_name' ? EVENT_TYPE.customEvent : EVENT_TYPE.pageView,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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, joinSessionQuery, queryParams } = await parseFilters({
|
const { filterQuery, joinSessionQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
|
|
@ -44,7 +44,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, queryParams } = await parseFilters({
|
const { filterQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@ import clickhouse from '@/lib/clickhouse';
|
||||||
import { EVENT_TYPE } from '@/lib/constants';
|
import { EVENT_TYPE } from '@/lib/constants';
|
||||||
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
|
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
|
||||||
import prisma from '@/lib/prisma';
|
import prisma from '@/lib/prisma';
|
||||||
|
import { QueryFilters } from '@/lib/types';
|
||||||
|
|
||||||
export interface AttributionCriteria {
|
export interface AttributionParameters {
|
||||||
startDate: Date;
|
startDate: Date;
|
||||||
endDate: Date;
|
endDate: Date;
|
||||||
model: string;
|
model: string;
|
||||||
|
|
@ -23,7 +24,9 @@ export interface AttributionResult {
|
||||||
total: { pageviews: number; visitors: number; visits: number };
|
total: { pageviews: number; visitors: number; visits: number };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAttribution(...args: [websiteId: string, criteria: AttributionCriteria]) {
|
export async function getAttribution(
|
||||||
|
...args: [websiteId: string, parameters: AttributionParameters, filters: QueryFilters]
|
||||||
|
) {
|
||||||
return runQuery({
|
return runQuery({
|
||||||
[PRISMA]: () => relationalQuery(...args),
|
[PRISMA]: () => relationalQuery(...args),
|
||||||
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
||||||
|
|
@ -32,12 +35,18 @@ export async function getAttribution(...args: [websiteId: string, criteria: Attr
|
||||||
|
|
||||||
async function relationalQuery(
|
async function relationalQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
criteria: AttributionCriteria,
|
parameters: AttributionParameters,
|
||||||
|
filters: QueryFilters,
|
||||||
): Promise<AttributionResult> {
|
): Promise<AttributionResult> {
|
||||||
const { startDate, endDate, model, type, step, currency } = criteria;
|
const { model, type, currency } = parameters;
|
||||||
const { rawQuery } = prisma;
|
const { rawQuery, parseFilters } = 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 { filterQuery, queryParams } = parseFilters({
|
||||||
|
...filters,
|
||||||
|
...parameters,
|
||||||
|
eventType,
|
||||||
|
});
|
||||||
|
|
||||||
function getUTMQuery(utmColumn: string) {
|
function getUTMQuery(utmColumn: string) {
|
||||||
return `
|
return `
|
||||||
|
|
@ -64,8 +73,9 @@ async function relationalQuery(
|
||||||
from website_event
|
from website_event
|
||||||
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} = {{step}}
|
||||||
and event_type = {{eventType}}
|
and event_type = {{eventType}}
|
||||||
|
${filterQuery}
|
||||||
group by 1),`;
|
group by 1),`;
|
||||||
|
|
||||||
const revenueEventQuery = `WITH events AS (
|
const revenueEventQuery = `WITH events AS (
|
||||||
|
|
@ -76,8 +86,9 @@ async function relationalQuery(
|
||||||
from revenue
|
from revenue
|
||||||
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} = {{step}}
|
||||||
and currency = {{currency}}
|
and currency = {{currency}}
|
||||||
|
${filterQuery}
|
||||||
group by 1),`;
|
group by 1),`;
|
||||||
|
|
||||||
function getModelQuery(model: string) {
|
function getModelQuery(model: string) {
|
||||||
|
|
@ -128,7 +139,7 @@ async function relationalQuery(
|
||||||
order by 2 desc
|
order by 2 desc
|
||||||
limit 20
|
limit 20
|
||||||
`,
|
`,
|
||||||
{ websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
queryParams,
|
||||||
);
|
);
|
||||||
|
|
||||||
const paidAdsres = await rawQuery(
|
const paidAdsres = await rawQuery(
|
||||||
|
|
@ -161,7 +172,7 @@ async function relationalQuery(
|
||||||
FROM results
|
FROM results
|
||||||
${currency ? '' : `WHERE name != ''`}
|
${currency ? '' : `WHERE name != ''`}
|
||||||
`,
|
`,
|
||||||
{ websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
queryParams,
|
||||||
);
|
);
|
||||||
|
|
||||||
const sourceRes = await rawQuery(
|
const sourceRes = await rawQuery(
|
||||||
|
|
@ -170,7 +181,7 @@ async function relationalQuery(
|
||||||
${getModelQuery(model)}
|
${getModelQuery(model)}
|
||||||
${getUTMQuery('utm_source')}
|
${getUTMQuery('utm_source')}
|
||||||
`,
|
`,
|
||||||
{ websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
queryParams,
|
||||||
);
|
);
|
||||||
|
|
||||||
const mediumRes = await rawQuery(
|
const mediumRes = await rawQuery(
|
||||||
|
|
@ -179,7 +190,7 @@ async function relationalQuery(
|
||||||
${getModelQuery(model)}
|
${getModelQuery(model)}
|
||||||
${getUTMQuery('utm_medium')}
|
${getUTMQuery('utm_medium')}
|
||||||
`,
|
`,
|
||||||
{ websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
queryParams,
|
||||||
);
|
);
|
||||||
|
|
||||||
const campaignRes = await rawQuery(
|
const campaignRes = await rawQuery(
|
||||||
|
|
@ -188,7 +199,7 @@ async function relationalQuery(
|
||||||
${getModelQuery(model)}
|
${getModelQuery(model)}
|
||||||
${getUTMQuery('utm_campaign')}
|
${getUTMQuery('utm_campaign')}
|
||||||
`,
|
`,
|
||||||
{ websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
queryParams,
|
||||||
);
|
);
|
||||||
|
|
||||||
const contentRes = await rawQuery(
|
const contentRes = await rawQuery(
|
||||||
|
|
@ -197,7 +208,7 @@ async function relationalQuery(
|
||||||
${getModelQuery(model)}
|
${getModelQuery(model)}
|
||||||
${getUTMQuery('utm_content')}
|
${getUTMQuery('utm_content')}
|
||||||
`,
|
`,
|
||||||
{ websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
queryParams,
|
||||||
);
|
);
|
||||||
|
|
||||||
const termRes = await rawQuery(
|
const termRes = await rawQuery(
|
||||||
|
|
@ -206,7 +217,7 @@ async function relationalQuery(
|
||||||
${getModelQuery(model)}
|
${getModelQuery(model)}
|
||||||
${getUTMQuery('utm_term')}
|
${getUTMQuery('utm_term')}
|
||||||
`,
|
`,
|
||||||
{ websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
queryParams,
|
||||||
);
|
);
|
||||||
|
|
||||||
const totalRes = await rawQuery(
|
const totalRes = await rawQuery(
|
||||||
|
|
@ -218,10 +229,11 @@ async function relationalQuery(
|
||||||
from website_event
|
from website_event
|
||||||
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} = {{step}}
|
||||||
and event_type = {{eventType}}
|
and event_type = {{eventType}}
|
||||||
|
${filterQuery}
|
||||||
`,
|
`,
|
||||||
{ websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
queryParams,
|
||||||
).then(result => result?.[0]);
|
).then(result => result?.[0]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -238,13 +250,19 @@ async function relationalQuery(
|
||||||
|
|
||||||
async function clickhouseQuery(
|
async function clickhouseQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
criteria: AttributionCriteria,
|
parameters: AttributionParameters,
|
||||||
|
filters: QueryFilters,
|
||||||
): Promise<AttributionResult> {
|
): Promise<AttributionResult> {
|
||||||
const { startDate, endDate, model, type, step, currency } = criteria;
|
const { model, type, currency } = parameters;
|
||||||
const { rawQuery, parseFilters } = 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, queryParams } = await parseFilters(criteria);
|
const { filterQuery, queryParams } = parseFilters({
|
||||||
|
...filters,
|
||||||
|
...parameters,
|
||||||
|
websiteId,
|
||||||
|
eventType,
|
||||||
|
});
|
||||||
|
|
||||||
function getUTMQuery(utmColumn: string) {
|
function getUTMQuery(utmColumn: string) {
|
||||||
return `
|
return `
|
||||||
|
|
@ -301,7 +319,7 @@ async function clickhouseQuery(
|
||||||
from website_event
|
from website_event
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and ${column} = {conversionStep:String}
|
and ${column} = {step:String}
|
||||||
and event_type = {eventType:UInt32}
|
and event_type = {eventType:UInt32}
|
||||||
group by 1),`;
|
group by 1),`;
|
||||||
|
|
||||||
|
|
@ -313,7 +331,7 @@ async function clickhouseQuery(
|
||||||
from website_revenue
|
from website_revenue
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and ${column} = {conversionStep:String}
|
and ${column} = {step:String}
|
||||||
and currency = {currency:String}
|
and currency = {currency:String}
|
||||||
group by 1),`;
|
group by 1),`;
|
||||||
|
|
||||||
|
|
@ -345,7 +363,7 @@ async function clickhouseQuery(
|
||||||
order by 2 desc
|
order by 2 desc
|
||||||
limit 20
|
limit 20
|
||||||
`,
|
`,
|
||||||
{ ...queryParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
queryParams,
|
||||||
);
|
);
|
||||||
|
|
||||||
const paidAdsres = await rawQuery<
|
const paidAdsres = await rawQuery<
|
||||||
|
|
@ -376,7 +394,7 @@ async function clickhouseQuery(
|
||||||
order by 2 desc
|
order by 2 desc
|
||||||
limit 20
|
limit 20
|
||||||
`,
|
`,
|
||||||
{ ...queryParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
queryParams,
|
||||||
);
|
);
|
||||||
|
|
||||||
const sourceRes = await rawQuery<
|
const sourceRes = await rawQuery<
|
||||||
|
|
@ -390,7 +408,7 @@ async function clickhouseQuery(
|
||||||
${getModelQuery(model)}
|
${getModelQuery(model)}
|
||||||
${getUTMQuery('utm_source')}
|
${getUTMQuery('utm_source')}
|
||||||
`,
|
`,
|
||||||
{ ...queryParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
queryParams,
|
||||||
);
|
);
|
||||||
|
|
||||||
const mediumRes = await rawQuery<
|
const mediumRes = await rawQuery<
|
||||||
|
|
@ -404,7 +422,7 @@ async function clickhouseQuery(
|
||||||
${getModelQuery(model)}
|
${getModelQuery(model)}
|
||||||
${getUTMQuery('utm_medium')}
|
${getUTMQuery('utm_medium')}
|
||||||
`,
|
`,
|
||||||
{ ...queryParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
queryParams,
|
||||||
);
|
);
|
||||||
|
|
||||||
const campaignRes = await rawQuery<
|
const campaignRes = await rawQuery<
|
||||||
|
|
@ -418,7 +436,7 @@ async function clickhouseQuery(
|
||||||
${getModelQuery(model)}
|
${getModelQuery(model)}
|
||||||
${getUTMQuery('utm_campaign')}
|
${getUTMQuery('utm_campaign')}
|
||||||
`,
|
`,
|
||||||
{ ...queryParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
queryParams,
|
||||||
);
|
);
|
||||||
|
|
||||||
const contentRes = await rawQuery<
|
const contentRes = await rawQuery<
|
||||||
|
|
@ -432,7 +450,7 @@ async function clickhouseQuery(
|
||||||
${getModelQuery(model)}
|
${getModelQuery(model)}
|
||||||
${getUTMQuery('utm_content')}
|
${getUTMQuery('utm_content')}
|
||||||
`,
|
`,
|
||||||
{ ...queryParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
queryParams,
|
||||||
);
|
);
|
||||||
|
|
||||||
const termRes = await rawQuery<
|
const termRes = await rawQuery<
|
||||||
|
|
@ -446,7 +464,7 @@ async function clickhouseQuery(
|
||||||
${getModelQuery(model)}
|
${getModelQuery(model)}
|
||||||
${getUTMQuery('utm_term')}
|
${getUTMQuery('utm_term')}
|
||||||
`,
|
`,
|
||||||
{ ...queryParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
queryParams,
|
||||||
);
|
);
|
||||||
|
|
||||||
const totalRes = await rawQuery<{ pageviews: number; visitors: number; visits: number }>(
|
const totalRes = await rawQuery<{ pageviews: number; visitors: number; visits: number }>(
|
||||||
|
|
@ -458,11 +476,11 @@ async function clickhouseQuery(
|
||||||
from website_event
|
from website_event
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and ${column} = {conversionStep:String}
|
and ${column} = {step:String}
|
||||||
and event_type = {eventType:UInt32}
|
and event_type = {eventType:UInt32}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
`,
|
`,
|
||||||
{ ...queryParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
queryParams,
|
||||||
).then(result => result?.[0]);
|
).then(result => result?.[0]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,19 @@ import clickhouse from '@/lib/clickhouse';
|
||||||
import { EVENT_TYPE, FILTER_COLUMNS, SESSION_COLUMNS } from '@/lib/constants';
|
import { EVENT_TYPE, FILTER_COLUMNS, SESSION_COLUMNS } from '@/lib/constants';
|
||||||
import { QueryFilters } from '@/lib/types';
|
import { QueryFilters } from '@/lib/types';
|
||||||
|
|
||||||
|
export interface BreakdownParameters {
|
||||||
|
startDate: Date;
|
||||||
|
endDate: Date;
|
||||||
|
fields: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BreakdownData {
|
||||||
|
x: string;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
export async function getBreakdown(
|
export async function getBreakdown(
|
||||||
...args: [websiteId: string, fields: string[], filters: QueryFilters]
|
...args: [websiteId: string, parameters: BreakdownParameters, filters: QueryFilters]
|
||||||
) {
|
) {
|
||||||
return runQuery({
|
return runQuery({
|
||||||
[PRISMA]: () => relationalQuery(...args),
|
[PRISMA]: () => relationalQuery(...args),
|
||||||
|
|
@ -15,22 +26,21 @@ export async function getBreakdown(
|
||||||
|
|
||||||
async function relationalQuery(
|
async function relationalQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
fields: string[],
|
parameters: BreakdownParameters,
|
||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
): Promise<
|
): Promise<BreakdownData[]> {
|
||||||
{
|
|
||||||
x: string;
|
|
||||||
y: number;
|
|
||||||
}[]
|
|
||||||
> {
|
|
||||||
const { getTimestampDiffSQL, parseFilters, rawQuery } = prisma;
|
const { getTimestampDiffSQL, parseFilters, rawQuery } = prisma;
|
||||||
const { filterQuery, joinSessionQuery, queryParams } = await parseFilters(
|
const { startDate, endDate, fields } = parameters;
|
||||||
|
const { filterQuery, joinSessionQuery, queryParams } = parseFilters(
|
||||||
{
|
{
|
||||||
...filters,
|
...filters,
|
||||||
|
websiteId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
joinSession: !!fields.find(name => SESSION_COLUMNS.includes(name)),
|
joinSession: !!fields.find((name: string) => SESSION_COLUMNS.includes(name)),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -70,17 +80,16 @@ async function relationalQuery(
|
||||||
|
|
||||||
async function clickhouseQuery(
|
async function clickhouseQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
fields: string[],
|
parameters: BreakdownParameters,
|
||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
): Promise<
|
): Promise<BreakdownData[]> {
|
||||||
{
|
|
||||||
x: string;
|
|
||||||
y: number;
|
|
||||||
}[]
|
|
||||||
> {
|
|
||||||
const { parseFilters, rawQuery } = clickhouse;
|
const { parseFilters, rawQuery } = clickhouse;
|
||||||
const { filterQuery, queryParams } = await parseFilters({
|
const { startDate, endDate, fields } = parameters;
|
||||||
|
const { filterQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
|
websiteId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,24 @@
|
||||||
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 prisma from '@/lib/prisma';
|
import prisma from '@/lib/prisma';
|
||||||
|
import { QueryFilters } from '@/lib/types';
|
||||||
|
|
||||||
export interface FunnelCriteria {
|
export interface FunnelParameters {
|
||||||
windowMinutes: number;
|
|
||||||
startDate: Date;
|
startDate: Date;
|
||||||
endDate: Date;
|
endDate: Date;
|
||||||
|
window: number;
|
||||||
steps: { type: string; value: string }[];
|
steps: { type: string; value: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getFunnel(...args: [websiteId: string, criteria: FunnelCriteria]) {
|
export interface FunnelResult {
|
||||||
|
value: string;
|
||||||
|
visitors: number;
|
||||||
|
dropoff: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFunnel(
|
||||||
|
...args: [websiteId: string, parameters: FunnelParameters, filters: QueryFilters]
|
||||||
|
) {
|
||||||
return runQuery({
|
return runQuery({
|
||||||
[PRISMA]: () => relationalQuery(...args),
|
[PRISMA]: () => relationalQuery(...args),
|
||||||
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
||||||
|
|
@ -18,21 +27,18 @@ export async function getFunnel(...args: [websiteId: string, criteria: FunnelCri
|
||||||
|
|
||||||
async function relationalQuery(
|
async function relationalQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
criteria: FunnelCriteria,
|
parameters: FunnelParameters,
|
||||||
): Promise<
|
filters: QueryFilters,
|
||||||
{
|
): Promise<FunnelResult[]> {
|
||||||
value: string;
|
const { startDate, endDate, window, steps } = parameters;
|
||||||
visitors: number;
|
const { rawQuery, getAddIntervalQuery, parseFilters } = prisma;
|
||||||
dropoff: number;
|
const { levelOneQuery, levelQuery, sumQuery, params } = getFunnelQuery(steps, window);
|
||||||
}[]
|
|
||||||
> {
|
const { filterQuery, queryParams } = parseFilters({ ...filters, websiteId, startDate, endDate });
|
||||||
const { windowMinutes, startDate, endDate, steps } = criteria;
|
|
||||||
const { rawQuery, getAddIntervalQuery } = prisma;
|
|
||||||
const { levelOneQuery, levelQuery, sumQuery, params } = getFunnelQuery(steps, windowMinutes);
|
|
||||||
|
|
||||||
function getFunnelQuery(
|
function getFunnelQuery(
|
||||||
steps: { type: string; value: string }[],
|
steps: { type: string; value: string }[],
|
||||||
windowMinutes: number,
|
window: number,
|
||||||
): {
|
): {
|
||||||
levelOneQuery: string;
|
levelOneQuery: string;
|
||||||
levelQuery: string;
|
levelQuery: string;
|
||||||
|
|
@ -62,6 +68,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} ${operator} {{${i}}}
|
and ${column} ${operator} {{${i}}}
|
||||||
|
${filterQuery}
|
||||||
)`;
|
)`;
|
||||||
} else {
|
} else {
|
||||||
pv.levelQuery += `
|
pv.levelQuery += `
|
||||||
|
|
@ -73,7 +80,7 @@ async function relationalQuery(
|
||||||
where we.website_id = {{websiteId::uuid}}
|
where we.website_id = {{websiteId::uuid}}
|
||||||
and we.created_at between l.created_at and ${getAddIntervalQuery(
|
and we.created_at between l.created_at and ${getAddIntervalQuery(
|
||||||
`l.created_at `,
|
`l.created_at `,
|
||||||
`${windowMinutes} minute`,
|
`${window} minute`,
|
||||||
)}
|
)}
|
||||||
and we.${column} ${operator} {{${i}}}
|
and we.${column} ${operator} {{${i}}}
|
||||||
and we.created_at <= {{endDate}}
|
and we.created_at <= {{endDate}}
|
||||||
|
|
@ -102,17 +109,16 @@ async function relationalQuery(
|
||||||
ORDER BY level;
|
ORDER BY level;
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
websiteId,
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
...params,
|
...params,
|
||||||
|
...queryParams,
|
||||||
},
|
},
|
||||||
).then(formatResults(steps));
|
).then(formatResults(steps));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clickhouseQuery(
|
async function clickhouseQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
criteria: FunnelCriteria,
|
parameters: FunnelParameters,
|
||||||
|
filters: QueryFilters,
|
||||||
): Promise<
|
): Promise<
|
||||||
{
|
{
|
||||||
value: string;
|
value: string;
|
||||||
|
|
@ -120,17 +126,17 @@ async function clickhouseQuery(
|
||||||
dropoff: number;
|
dropoff: number;
|
||||||
}[]
|
}[]
|
||||||
> {
|
> {
|
||||||
const { windowMinutes, startDate, endDate, steps } = criteria;
|
const { startDate, endDate, window, steps } = parameters;
|
||||||
const { rawQuery, parseFilters } = clickhouse;
|
const { rawQuery, parseFilters } = clickhouse;
|
||||||
const { levelOneQuery, levelQuery, sumQuery, stepFilterQuery, params } = getFunnelQuery(
|
const { levelOneQuery, levelQuery, sumQuery, stepFilterQuery, params } = getFunnelQuery(
|
||||||
steps,
|
steps,
|
||||||
windowMinutes,
|
window,
|
||||||
);
|
);
|
||||||
const { filterQuery, queryParams } = await parseFilters(criteria);
|
const { filterQuery, queryParams } = parseFilters({ ...filters, websiteId, startDate, endDate });
|
||||||
|
|
||||||
function getFunnelQuery(
|
function getFunnelQuery(
|
||||||
steps: { type: string; value: string }[],
|
steps: { type: string; value: string }[],
|
||||||
windowMinutes: number,
|
window: number,
|
||||||
): {
|
): {
|
||||||
levelOneQuery: string;
|
levelOneQuery: string;
|
||||||
levelQuery: string;
|
levelQuery: string;
|
||||||
|
|
@ -172,7 +178,7 @@ async function clickhouseQuery(
|
||||||
from level${i} x
|
from level${i} x
|
||||||
join level0 y
|
join level0 y
|
||||||
on x.session_id = y.session_id
|
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}
|
and y.${column} ${operator} {param${i}:String}
|
||||||
)`;
|
)`;
|
||||||
}
|
}
|
||||||
|
|
@ -211,9 +217,6 @@ async function clickhouseQuery(
|
||||||
) ORDER BY level;
|
) ORDER BY level;
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
websiteId,
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
...params,
|
...params,
|
||||||
...queryParams,
|
...queryParams,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -3,30 +3,37 @@ import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
|
||||||
import prisma from '@/lib/prisma';
|
import prisma from '@/lib/prisma';
|
||||||
import { QueryFilters } from '@/lib/types';
|
import { QueryFilters } from '@/lib/types';
|
||||||
|
|
||||||
export interface GoalCriteria {
|
export interface GoalParameters {
|
||||||
startDate: Date;
|
startDate: Date;
|
||||||
endDate: Date;
|
endDate: Date;
|
||||||
type: string;
|
type: string;
|
||||||
value: string;
|
value: string;
|
||||||
operator?: string;
|
operator?: string;
|
||||||
property?: string;
|
property?: string;
|
||||||
filters: QueryFilters;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getGoal(...args: [websiteId: string, criteria: GoalCriteria]) {
|
export async function getGoal(
|
||||||
|
...args: [websiteId: string, params: GoalParameters, filters: QueryFilters]
|
||||||
|
) {
|
||||||
return runQuery({
|
return runQuery({
|
||||||
[PRISMA]: () => relationalQuery(...args),
|
[PRISMA]: () => relationalQuery(...args),
|
||||||
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function relationalQuery(websiteId: string, criteria: GoalCriteria) {
|
async function relationalQuery(
|
||||||
const { type, value, filters } = criteria;
|
websiteId: string,
|
||||||
|
parameters: GoalParameters,
|
||||||
|
filters: QueryFilters,
|
||||||
|
) {
|
||||||
|
const { startDate, endDate, type, value } = parameters;
|
||||||
const { rawQuery, parseFilters } = prisma;
|
const { rawQuery, parseFilters } = prisma;
|
||||||
const { filterQuery, dateQuery, queryParams } = await parseFilters({
|
const { filterQuery, dateQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
value,
|
value,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
});
|
});
|
||||||
const isPage = type === 'page';
|
const isPage = type === 'page';
|
||||||
const column = isPage ? 'url_path' : 'event_name';
|
const column = isPage ? 'url_path' : 'event_name';
|
||||||
|
|
@ -53,13 +60,19 @@ async function relationalQuery(websiteId: string, criteria: GoalCriteria) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clickhouseQuery(websiteId: string, criteria: GoalCriteria) {
|
async function clickhouseQuery(
|
||||||
const { type, value, filters } = criteria;
|
websiteId: string,
|
||||||
|
parameters: GoalParameters,
|
||||||
|
filters: QueryFilters,
|
||||||
|
) {
|
||||||
|
const { startDate, endDate, type, value } = parameters;
|
||||||
const { rawQuery, parseFilters } = clickhouse;
|
const { rawQuery, parseFilters } = clickhouse;
|
||||||
const { filterQuery, dateQuery, queryParams } = await parseFilters({
|
const { filterQuery, dateQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
value,
|
value,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
});
|
});
|
||||||
const isPage = type === 'page';
|
const isPage = type === 'page';
|
||||||
const column = isPage ? 'url_path' : 'event_name';
|
const column = isPage ? 'url_path' : 'event_name';
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,17 @@
|
||||||
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 prisma from '@/lib/prisma';
|
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;
|
e1: string;
|
||||||
e2: string;
|
e2: string;
|
||||||
e3: string;
|
e3: string;
|
||||||
|
|
@ -14,16 +23,7 @@ interface JourneyResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getJourney(
|
export async function getJourney(
|
||||||
...args: [
|
...args: [websiteId: string, parameters: JourneyParameters, filters: QueryFilters]
|
||||||
websiteId: string,
|
|
||||||
filters: {
|
|
||||||
startDate: Date;
|
|
||||||
endDate: Date;
|
|
||||||
steps: number;
|
|
||||||
startStep?: string;
|
|
||||||
endStep?: string;
|
|
||||||
},
|
|
||||||
]
|
|
||||||
) {
|
) {
|
||||||
return runQuery({
|
return runQuery({
|
||||||
[PRISMA]: () => relationalQuery(...args),
|
[PRISMA]: () => relationalQuery(...args),
|
||||||
|
|
@ -33,21 +33,17 @@ export async function getJourney(
|
||||||
|
|
||||||
async function relationalQuery(
|
async function relationalQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
filters: {
|
parameters: JourneyParameters,
|
||||||
startDate: Date;
|
filters: QueryFilters,
|
||||||
endDate: Date;
|
|
||||||
steps: number;
|
|
||||||
startStep?: string;
|
|
||||||
endStep?: string;
|
|
||||||
},
|
|
||||||
): Promise<JourneyResult[]> {
|
): Promise<JourneyResult[]> {
|
||||||
const { startDate, endDate, steps, startStep, endStep } = filters;
|
const { startDate, endDate, steps, startStep, endStep } = parameters;
|
||||||
const { rawQuery } = prisma;
|
const { rawQuery, parseFilters } = prisma;
|
||||||
const { sequenceQuery, startStepQuery, endStepQuery, params } = getJourneyQuery(
|
const { sequenceQuery, startStepQuery, endStepQuery, params } = getJourneyQuery(
|
||||||
steps,
|
steps,
|
||||||
startStep,
|
startStep,
|
||||||
endStep,
|
endStep,
|
||||||
);
|
);
|
||||||
|
const { filterQuery, queryParams } = parseFilters({ ...filters, websiteId, startDate, endDate });
|
||||||
|
|
||||||
function getJourneyQuery(
|
function getJourneyQuery(
|
||||||
steps: number,
|
steps: number,
|
||||||
|
|
@ -123,6 +119,7 @@ async function relationalQuery(
|
||||||
from website_event
|
from website_event
|
||||||
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}
|
||||||
${sequenceQuery}
|
${sequenceQuery}
|
||||||
select *
|
select *
|
||||||
from sequences
|
from sequences
|
||||||
|
|
@ -133,31 +130,25 @@ async function relationalQuery(
|
||||||
limit 100
|
limit 100
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
websiteId,
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
...params,
|
...params,
|
||||||
|
...queryParams,
|
||||||
},
|
},
|
||||||
).then(parseResult);
|
).then(parseResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clickhouseQuery(
|
async function clickhouseQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
filters: {
|
parameters: JourneyParameters,
|
||||||
startDate: Date;
|
filters: QueryFilters,
|
||||||
endDate: Date;
|
|
||||||
steps: number;
|
|
||||||
startStep?: string;
|
|
||||||
endStep?: string;
|
|
||||||
},
|
|
||||||
): Promise<JourneyResult[]> {
|
): Promise<JourneyResult[]> {
|
||||||
const { startDate, endDate, steps, startStep, endStep } = filters;
|
const { startDate, endDate, steps, startStep, endStep } = parameters;
|
||||||
const { rawQuery, parseFilters } = clickhouse;
|
const { rawQuery, parseFilters } = clickhouse;
|
||||||
const { sequenceQuery, startStepQuery, endStepQuery, params } = getJourneyQuery(
|
const { sequenceQuery, startStepQuery, endStepQuery, params } = getJourneyQuery(
|
||||||
steps,
|
steps,
|
||||||
startStep,
|
startStep,
|
||||||
endStep,
|
endStep,
|
||||||
);
|
);
|
||||||
|
const { filterQuery, queryParams } = parseFilters({ ...filters, websiteId, startDate, endDate });
|
||||||
|
|
||||||
function getJourneyQuery(
|
function getJourneyQuery(
|
||||||
steps: number,
|
steps: number,
|
||||||
|
|
@ -222,8 +213,6 @@ async function clickhouseQuery(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { filterQuery, queryParams } = await parseFilters(filters);
|
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
WITH events AS (
|
WITH events AS (
|
||||||
|
|
@ -245,9 +234,6 @@ async function clickhouseQuery(
|
||||||
limit 100
|
limit 100
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
websiteId,
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
...params,
|
...params,
|
||||||
...queryParams,
|
...queryParams,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
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 prisma from '@/lib/prisma';
|
import prisma from '@/lib/prisma';
|
||||||
|
import { QueryFilters } from '@/lib/types';
|
||||||
|
|
||||||
export interface RetentionCriteria {
|
export interface RetentionParameters {
|
||||||
startDate: Date;
|
startDate: Date;
|
||||||
endDate: Date;
|
endDate: Date;
|
||||||
timezone?: string;
|
timezone?: string;
|
||||||
|
|
@ -16,7 +17,9 @@ export interface RetentionResult {
|
||||||
percentage: number;
|
percentage: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRetention(...args: [websiteId: string, criteria: RetentionCriteria]) {
|
export async function getRetention(
|
||||||
|
...args: [websiteId: string, parameters: RetentionParameters, filters: QueryFilters]
|
||||||
|
) {
|
||||||
return runQuery({
|
return runQuery({
|
||||||
[PRISMA]: () => relationalQuery(...args),
|
[PRISMA]: () => relationalQuery(...args),
|
||||||
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
||||||
|
|
@ -25,13 +28,20 @@ export async function getRetention(...args: [websiteId: string, criteria: Retent
|
||||||
|
|
||||||
async function relationalQuery(
|
async function relationalQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
criteria: RetentionCriteria,
|
parameters: RetentionParameters,
|
||||||
|
filters: QueryFilters,
|
||||||
): Promise<RetentionResult[]> {
|
): Promise<RetentionResult[]> {
|
||||||
const { startDate, endDate, timezone } = criteria;
|
const { startDate, endDate, timezone } = parameters;
|
||||||
const { getDateSQL, getDayDiffQuery, getCastColumnQuery, rawQuery, parseFilters } = prisma;
|
const { getDateSQL, getDayDiffQuery, getCastColumnQuery, rawQuery, parseFilters } = prisma;
|
||||||
const unit = 'day';
|
const unit = 'day';
|
||||||
|
|
||||||
const { filterQuery, queryParams } = await parseFilters(criteria);
|
const { filterQuery, queryParams } = parseFilters({
|
||||||
|
...filters,
|
||||||
|
websiteId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
timezone,
|
||||||
|
});
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -81,24 +91,26 @@ async function relationalQuery(
|
||||||
on c.cohort_date = s.cohort_date
|
on c.cohort_date = s.cohort_date
|
||||||
where c.day_number <= 31
|
where c.day_number <= 31
|
||||||
order by 1, 2`,
|
order by 1, 2`,
|
||||||
{
|
queryParams,
|
||||||
websiteId,
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
...queryParams,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clickhouseQuery(
|
async function clickhouseQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
criteria: RetentionCriteria,
|
parameters: RetentionParameters,
|
||||||
|
filters: QueryFilters,
|
||||||
): Promise<RetentionResult[]> {
|
): Promise<RetentionResult[]> {
|
||||||
const { startDate, endDate, timezone } = criteria;
|
const { startDate, endDate, timezone } = parameters;
|
||||||
const { getDateSQL, rawQuery, parseFilters } = clickhouse;
|
const { getDateSQL, rawQuery, parseFilters } = clickhouse;
|
||||||
const unit = 'day';
|
const unit = 'day';
|
||||||
|
|
||||||
const { filterQuery, queryParams } = await parseFilters(criteria);
|
const { filterQuery, queryParams } = parseFilters({
|
||||||
|
...filters,
|
||||||
|
websiteId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
timezone,
|
||||||
|
});
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -150,11 +162,6 @@ async function clickhouseQuery(
|
||||||
on c.cohort_date = s.cohort_date
|
on c.cohort_date = s.cohort_date
|
||||||
where c.day_number <= 31
|
where c.day_number <= 31
|
||||||
order by 1, 2`,
|
order by 1, 2`,
|
||||||
{
|
queryParams,
|
||||||
websiteId,
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
...queryParams,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import clickhouse from '@/lib/clickhouse';
|
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 prisma from '@/lib/prisma';
|
||||||
|
import { QueryFilters } from '@/lib/types';
|
||||||
|
|
||||||
export interface RevenueCriteria {
|
export interface RevenuParameters {
|
||||||
startDate: Date;
|
startDate: Date;
|
||||||
endDate: Date;
|
endDate: Date;
|
||||||
unit: string;
|
unit: string;
|
||||||
|
|
@ -21,7 +22,9 @@ export interface RevenueResult {
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRevenue(...args: [websiteId: string, criteria: RevenueCriteria]) {
|
export async function getRevenue(
|
||||||
|
...args: [websiteId: string, parameters: RevenuParameters, filters: QueryFilters]
|
||||||
|
) {
|
||||||
return runQuery({
|
return runQuery({
|
||||||
[PRISMA]: () => relationalQuery(...args),
|
[PRISMA]: () => relationalQuery(...args),
|
||||||
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
||||||
|
|
@ -30,12 +33,12 @@ export async function getRevenue(...args: [websiteId: string, criteria: RevenueC
|
||||||
|
|
||||||
async function relationalQuery(
|
async function relationalQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
criteria: RevenueCriteria,
|
parameters: RevenuParameters,
|
||||||
|
filters: QueryFilters,
|
||||||
): Promise<RevenueResult> {
|
): Promise<RevenueResult> {
|
||||||
const { startDate, endDate, unit = 'day', currency } = criteria;
|
const { startDate, endDate, currency, unit = 'day' } = parameters;
|
||||||
const { getDateSQL, rawQuery } = prisma;
|
const { getDateSQL, rawQuery, parseFilters } = prisma;
|
||||||
const db = getDatabaseType();
|
const { queryParams } = parseFilters({ ...filters, websiteId, startDate, endDate, currency });
|
||||||
const like = db === POSTGRESQL ? 'ilike' : 'like';
|
|
||||||
|
|
||||||
const chart = await rawQuery(
|
const chart = await rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -50,7 +53,7 @@ async function relationalQuery(
|
||||||
group by x, t
|
group by x, t
|
||||||
order by t
|
order by t
|
||||||
`,
|
`,
|
||||||
{ websiteId, startDate, endDate, unit, currency },
|
queryParams,
|
||||||
);
|
);
|
||||||
|
|
||||||
const country = await rawQuery(
|
const country = await rawQuery(
|
||||||
|
|
@ -63,10 +66,10 @@ async function relationalQuery(
|
||||||
on s.session_id = r.session_id
|
on s.session_id = r.session_id
|
||||||
where r.website_id = {{websiteId::uuid}}
|
where r.website_id = {{websiteId::uuid}}
|
||||||
and r.created_at between {{startDate}} and {{endDate}}
|
and r.created_at between {{startDate}} and {{endDate}}
|
||||||
and r.currency ${like} {{currency}}
|
and r.currency = {{currency}}
|
||||||
group by s.country
|
group by s.country
|
||||||
`,
|
`,
|
||||||
{ websiteId, startDate, endDate, currency },
|
queryParams,
|
||||||
);
|
);
|
||||||
|
|
||||||
const total = await rawQuery(
|
const total = await rawQuery(
|
||||||
|
|
@ -78,9 +81,9 @@ async function relationalQuery(
|
||||||
from revenue r
|
from revenue r
|
||||||
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 currency ${like} {{currency}}
|
and currency = {{currency}}
|
||||||
`,
|
`,
|
||||||
{ websiteId, startDate, endDate, currency },
|
queryParams,
|
||||||
).then(result => result?.[0]);
|
).then(result => result?.[0]);
|
||||||
|
|
||||||
total.average = total.count > 0 ? total.sum / total.count : 0;
|
total.average = total.count > 0 ? total.sum / total.count : 0;
|
||||||
|
|
@ -98,7 +101,7 @@ async function relationalQuery(
|
||||||
group by currency
|
group by currency
|
||||||
order by sum desc
|
order by sum desc
|
||||||
`,
|
`,
|
||||||
{ websiteId, startDate, endDate, unit, currency },
|
queryParams,
|
||||||
);
|
);
|
||||||
|
|
||||||
return { chart, country, table, total };
|
return { chart, country, table, total };
|
||||||
|
|
@ -106,10 +109,18 @@ async function relationalQuery(
|
||||||
|
|
||||||
async function clickhouseQuery(
|
async function clickhouseQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
criteria: RevenueCriteria,
|
parameters: RevenuParameters,
|
||||||
|
filters: QueryFilters,
|
||||||
): Promise<RevenueResult> {
|
): Promise<RevenueResult> {
|
||||||
const { startDate, endDate, unit = 'day', currency } = criteria;
|
const { startDate, endDate, unit = 'day', currency } = parameters;
|
||||||
const { getDateSQL, rawQuery } = clickhouse;
|
const { getDateSQL, rawQuery, parseFilters } = clickhouse;
|
||||||
|
const { queryParams } = parseFilters({
|
||||||
|
...filters,
|
||||||
|
websiteId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
currency,
|
||||||
|
});
|
||||||
|
|
||||||
const chart = await rawQuery<
|
const chart = await rawQuery<
|
||||||
{
|
{
|
||||||
|
|
@ -130,7 +141,7 @@ async function clickhouseQuery(
|
||||||
group by x, t
|
group by x, t
|
||||||
order by t
|
order by t
|
||||||
`,
|
`,
|
||||||
{ websiteId, startDate, endDate, unit, currency },
|
queryParams,
|
||||||
);
|
);
|
||||||
|
|
||||||
const country = await rawQuery<
|
const country = await rawQuery<
|
||||||
|
|
@ -144,9 +155,11 @@ async function clickhouseQuery(
|
||||||
s.country as name,
|
s.country as name,
|
||||||
sum(w.revenue) as value
|
sum(w.revenue) as value
|
||||||
from website_revenue w
|
from website_revenue w
|
||||||
join (select distinct website_id, session_id, country
|
join (
|
||||||
from website_event
|
select distinct website_id, session_id, country
|
||||||
where website_id = {websiteId:UUID}) s
|
from website_event
|
||||||
|
where website_id = {websiteId:UUID}
|
||||||
|
) s
|
||||||
on w.website_id = s.website_id
|
on w.website_id = s.website_id
|
||||||
and w.session_id = s.session_id
|
and w.session_id = s.session_id
|
||||||
where w.website_id = {websiteId:UUID}
|
where w.website_id = {websiteId:UUID}
|
||||||
|
|
@ -154,7 +167,7 @@ async function clickhouseQuery(
|
||||||
and w.currency = {currency:String}
|
and w.currency = {currency:String}
|
||||||
group by s.country
|
group by s.country
|
||||||
`,
|
`,
|
||||||
{ websiteId, startDate, endDate, currency },
|
queryParams,
|
||||||
);
|
);
|
||||||
|
|
||||||
const total = await rawQuery<{
|
const total = await rawQuery<{
|
||||||
|
|
@ -172,7 +185,7 @@ async function clickhouseQuery(
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and currency = {currency:String}
|
and currency = {currency:String}
|
||||||
`,
|
`,
|
||||||
{ websiteId, startDate, endDate, currency },
|
queryParams,
|
||||||
).then(result => result?.[0]);
|
).then(result => result?.[0]);
|
||||||
|
|
||||||
total.average = total.count > 0 ? total.sum / total.count : 0;
|
total.average = total.count > 0 ? total.sum / total.count : 0;
|
||||||
|
|
@ -197,7 +210,7 @@ async function clickhouseQuery(
|
||||||
group by currency
|
group by currency
|
||||||
order by sum desc
|
order by sum desc
|
||||||
`,
|
`,
|
||||||
{ websiteId, startDate, endDate, unit, currency },
|
queryParams,
|
||||||
);
|
);
|
||||||
|
|
||||||
return { chart, country, table, total };
|
return { chart, country, table, total };
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,30 @@
|
||||||
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 prisma from '@/lib/prisma';
|
import prisma from '@/lib/prisma';
|
||||||
|
import { QueryFilters } from '@/lib/types';
|
||||||
|
|
||||||
export interface UTMCriteria {
|
export interface UTMParameters {
|
||||||
startDate: Date;
|
startDate: Date;
|
||||||
endDate: Date;
|
endDate: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUTM(...args: [websiteId: string, criteria: UTMCriteria]) {
|
export async function getUTM(
|
||||||
|
...args: [websiteId: string, parameters: UTMParameters, filters: QueryFilters]
|
||||||
|
) {
|
||||||
return runQuery({
|
return runQuery({
|
||||||
[PRISMA]: () => relationalQuery(...args),
|
[PRISMA]: () => relationalQuery(...args),
|
||||||
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function relationalQuery(websiteId: string, criteria: UTMCriteria) {
|
async function relationalQuery(
|
||||||
const { startDate, endDate } = criteria;
|
websiteId: string,
|
||||||
|
parameters: UTMParameters,
|
||||||
|
filters: QueryFilters,
|
||||||
|
) {
|
||||||
|
const { startDate, endDate } = parameters;
|
||||||
const { rawQuery, parseFilters } = prisma;
|
const { rawQuery, parseFilters } = prisma;
|
||||||
const { filterQuery, queryParams } = await parseFilters(criteria);
|
const { filterQuery, queryParams } = parseFilters({ ...filters, websiteId, startDate, endDate });
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -30,19 +37,18 @@ async function relationalQuery(websiteId: string, criteria: UTMCriteria) {
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
group by 1
|
group by 1
|
||||||
`,
|
`,
|
||||||
{
|
queryParams,
|
||||||
...queryParams,
|
|
||||||
websiteId,
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
},
|
|
||||||
).then(result => parseParameters(result as any[]));
|
).then(result => parseParameters(result as any[]));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clickhouseQuery(websiteId: string, criteria: UTMCriteria) {
|
async function clickhouseQuery(
|
||||||
const { startDate, endDate } = criteria;
|
websiteId: string,
|
||||||
|
parameters: UTMParameters,
|
||||||
|
filters: QueryFilters,
|
||||||
|
) {
|
||||||
|
const { startDate, endDate } = parameters;
|
||||||
const { rawQuery, parseFilters } = clickhouse;
|
const { rawQuery, parseFilters } = clickhouse;
|
||||||
const { filterQuery, queryParams } = await parseFilters(criteria);
|
const { filterQuery, queryParams } = parseFilters({ ...filters, websiteId, startDate, endDate });
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -55,12 +61,7 @@ async function clickhouseQuery(websiteId: string, criteria: UTMCriteria) {
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
group by 1
|
group by 1
|
||||||
`,
|
`,
|
||||||
{
|
queryParams,
|
||||||
...queryParams,
|
|
||||||
websiteId,
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
},
|
|
||||||
).then(result => parseParameters(result as any[]));
|
).then(result => parseParameters(result as any[]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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, queryParams } = await parseFilters(filters, {
|
const { filterQuery, queryParams } = parseFilters(filters, {
|
||||||
columns: { propertyName: 'data_key' },
|
columns: { propertyName: 'data_key' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -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, queryParams } = await parseFilters(filters, {
|
const { filterQuery, queryParams } = parseFilters(filters, {
|
||||||
columns: { propertyName: 'data_key' },
|
columns: { propertyName: 'data_key' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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, queryParams } = await parseFilters(filters);
|
const { filterQuery, queryParams } = parseFilters(filters);
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -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, queryParams } = await parseFilters(filters);
|
const { filterQuery, queryParams } = parseFilters(filters);
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,14 @@ import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
|
||||||
import prisma from '@/lib/prisma';
|
import prisma from '@/lib/prisma';
|
||||||
import { QueryFilters } from '@/lib/types';
|
import { QueryFilters } from '@/lib/types';
|
||||||
|
|
||||||
|
export interface SessionMetricsParameters {
|
||||||
|
type: string;
|
||||||
|
limit: number | string;
|
||||||
|
offset: number | string;
|
||||||
|
}
|
||||||
|
|
||||||
export async function getSessionMetrics(
|
export async function getSessionMetrics(
|
||||||
...args: [
|
...args: [websiteId: string, parameters: SessionMetricsParameters, filters: QueryFilters]
|
||||||
websiteId: string,
|
|
||||||
type: string,
|
|
||||||
filters: QueryFilters,
|
|
||||||
limit?: number | string,
|
|
||||||
offset?: number | string,
|
|
||||||
]
|
|
||||||
) {
|
) {
|
||||||
return runQuery({
|
return runQuery({
|
||||||
[PRISMA]: () => relationalQuery(...args),
|
[PRISMA]: () => relationalQuery(...args),
|
||||||
|
|
@ -21,16 +21,16 @@ export async function getSessionMetrics(
|
||||||
|
|
||||||
async function relationalQuery(
|
async function relationalQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
type: string,
|
parameters: SessionMetricsParameters,
|
||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
limit: number | string = 500,
|
|
||||||
offset: number | string = 0,
|
|
||||||
) {
|
) {
|
||||||
|
const { type, limit = 500, offset = 0 } = parameters;
|
||||||
const column = FILTER_COLUMNS[type] || type;
|
const column = FILTER_COLUMNS[type] || type;
|
||||||
const { parseFilters, rawQuery } = prisma;
|
const { parseFilters, rawQuery } = prisma;
|
||||||
const { filterQuery, joinSessionQuery, queryParams } = await parseFilters(
|
const { filterQuery, joinSessionQuery, queryParams } = parseFilters(
|
||||||
{
|
{
|
||||||
...filters,
|
...filters,
|
||||||
|
websiteId,
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -57,21 +57,21 @@ async function relationalQuery(
|
||||||
limit ${limit}
|
limit ${limit}
|
||||||
offset ${offset}
|
offset ${offset}
|
||||||
`,
|
`,
|
||||||
queryParams,
|
{ ...queryParams, ...parameters },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clickhouseQuery(
|
async function clickhouseQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
type: string,
|
parameters: SessionMetricsParameters,
|
||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
limit: number | string = 500,
|
|
||||||
offset: number | string = 0,
|
|
||||||
): Promise<{ x: string; y: number }[]> {
|
): Promise<{ x: string; y: number }[]> {
|
||||||
|
const { type, limit = 500, offset = 0 } = parameters;
|
||||||
const column = FILTER_COLUMNS[type] || type;
|
const column = FILTER_COLUMNS[type] || type;
|
||||||
const { parseFilters, rawQuery } = clickhouse;
|
const { parseFilters, rawQuery } = clickhouse;
|
||||||
const { filterQuery, queryParams } = await parseFilters({
|
const { filterQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
|
websiteId,
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
});
|
});
|
||||||
const includeCountry = column === 'city' || column === 'region';
|
const includeCountry = column === 'city' || column === 'region';
|
||||||
|
|
@ -114,5 +114,5 @@ async function clickhouseQuery(
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return rawQuery(sql, queryParams);
|
return rawQuery(sql, { ...queryParams, ...parameters });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,9 @@ 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, joinSessionQuery, queryParams } = await parseFilters({
|
const { filterQuery, joinSessionQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
|
websiteId,
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -43,8 +44,9 @@ 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, queryParams } = await parseFilters({
|
const { filterQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
|
websiteId,
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ async function relationalQuery(
|
||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
): Promise<WebsiteSessionStatsData[]> {
|
): Promise<WebsiteSessionStatsData[]> {
|
||||||
const { parseFilters, rawQuery } = prisma;
|
const { parseFilters, rawQuery } = prisma;
|
||||||
const { filterQuery, queryParams } = await parseFilters(filters);
|
const { filterQuery, queryParams } = parseFilters({ ...filters, websiteId });
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -50,7 +50,7 @@ async function clickhouseQuery(
|
||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
): Promise<WebsiteSessionStatsData[]> {
|
): Promise<WebsiteSessionStatsData[]> {
|
||||||
const { rawQuery, parseFilters } = clickhouse;
|
const { rawQuery, parseFilters } = clickhouse;
|
||||||
const { filterQuery, queryParams } = await parseFilters(filters);
|
const { filterQuery, queryParams } = parseFilters({ ...filters, websiteId });
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,14 @@ export async function getWebsiteSessions(...args: [websiteId: string, filters: Q
|
||||||
|
|
||||||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
const { pagedRawQuery, parseFilters } = prisma;
|
const { pagedRawQuery, parseFilters } = prisma;
|
||||||
const { filterQuery, dateQuery, queryParams } = await parseFilters(filters);
|
const { search } = filters;
|
||||||
|
const { filterQuery, dateQuery, queryParams } = parseFilters({
|
||||||
|
...filters,
|
||||||
|
websiteId,
|
||||||
|
search: search ? `%${search}%` : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const searchQuery = search ? `and session.distinct_id ilike {{search}}` : '';
|
||||||
|
|
||||||
return pagedRawQuery(
|
return pagedRawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -38,6 +45,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
where website_event.website_id = {{websiteId::uuid}}
|
where website_event.website_id = {{websiteId::uuid}}
|
||||||
${dateQuery}
|
${dateQuery}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
|
${searchQuery}
|
||||||
group by session.session_id,
|
group by session.session_id,
|
||||||
session.website_id,
|
session.website_id,
|
||||||
website_event.hostname,
|
website_event.hostname,
|
||||||
|
|
@ -58,7 +66,13 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
|
|
||||||
async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
||||||
const { pagedRawQuery, parseFilters, getDateStringSQL } = clickhouse;
|
const { pagedRawQuery, parseFilters, getDateStringSQL } = clickhouse;
|
||||||
const { filterQuery, dateQuery, queryParams } = await parseFilters(filters);
|
const { search } = filters;
|
||||||
|
const { filterQuery, dateQuery, queryParams } = parseFilters({
|
||||||
|
...filters,
|
||||||
|
websiteId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const searchQuery = search ? `and positionCaseInsensitive(distinct_id, {search:String}) > 0` : '';
|
||||||
|
|
||||||
return pagedRawQuery(
|
return pagedRawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -83,6 +97,7 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
${dateQuery}
|
${dateQuery}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
|
${searchQuery}
|
||||||
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
|
||||||
`,
|
`,
|
||||||
|
|
|
||||||
|
|
@ -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 { queryParams } = await parseFilters(filters);
|
const { queryParams } = parseFilters(filters);
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -35,7 +35,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
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 { queryParams } = await parseFilters(filters);
|
const { queryParams } = parseFilters(filters);
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue