mirror of
https://github.com/umami-software/umami.git
synced 2025-12-06 01:18:00 +01:00
Refactor filter handling for queries.
This commit is contained in:
parent
5b300f1ff5
commit
ee6c68d27c
107 changed files with 731 additions and 835 deletions
4
.github/workflows/cd-manual.yml
vendored
4
.github/workflows/cd-manual.yml
vendored
|
|
@ -15,7 +15,7 @@ jobs:
|
|||
|
||||
strategy:
|
||||
matrix:
|
||||
db-type: [postgresql, mysql]
|
||||
db-type: [postgresql]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
|
@ -55,4 +55,4 @@ jobs:
|
|||
buildArgs: DATABASE_TYPE=${{ matrix.db-type }}
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
|
|
|||
4
.github/workflows/cd.yml
vendored
4
.github/workflows/cd.yml
vendored
|
|
@ -10,7 +10,7 @@ jobs:
|
|||
|
||||
strategy:
|
||||
matrix:
|
||||
db-type: [postgresql, mysql]
|
||||
db-type: [postgresql]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
|
@ -47,4 +47,4 @@ jobs:
|
|||
buildArgs: DATABASE_TYPE=${{ matrix.db-type }}
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { Button, Grid, Column, Heading } from '@umami/react-zen';
|
||||
import Link from 'next/link';
|
||||
import Script from 'next/script';
|
||||
import { Panel } from '@/components/common/Panel';
|
||||
import { PageBody } from '@/components/common/PageBody';
|
||||
import { EventsChart } from '@/components/metrics/EventsChart';
|
||||
import { WebsiteChart } from '@/app/(main)/websites/[websiteId]/WebsiteChart';
|
||||
|
|
@ -115,87 +116,91 @@ export function TestConsolePage({ websiteId }: { websiteId: string }) {
|
|||
src={`${process.env.basePath || ''}/script.js`}
|
||||
data-cache="true"
|
||||
/>
|
||||
<Grid columns="1fr 1fr 1fr" gap>
|
||||
<Column gap>
|
||||
<Heading>Page links</Heading>
|
||||
<div>
|
||||
<Link href={`/console/${websiteId}?page=1`}>page one</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Link href={`/console/${websiteId}?page=2 `}>page two</Link>
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://www.google.com" data-umami-event="external-link-direct">
|
||||
external link (direct)
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
href="https://www.google.com"
|
||||
data-umami-event="external-link-tab"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
external link (tab)
|
||||
</a>
|
||||
</div>
|
||||
</Column>
|
||||
<Column gap>
|
||||
<Heading>Click events</Heading>
|
||||
<Button id="send-event-button" data-umami-event="button-click" variant="primary">
|
||||
Send event
|
||||
</Button>
|
||||
<Button
|
||||
id="send-event-data-button"
|
||||
data-umami-event="button-click"
|
||||
data-umami-event-name="bob"
|
||||
data-umami-event-id="123"
|
||||
variant="primary"
|
||||
>
|
||||
Send event with data
|
||||
</Button>
|
||||
<Button
|
||||
id="generate-revenue-button"
|
||||
data-umami-event="checkout-cart"
|
||||
data-umami-event-revenue={(Math.random() * 10000).toFixed(2).toString()}
|
||||
data-umami-event-currency="USD"
|
||||
variant="primary"
|
||||
>
|
||||
Generate revenue data
|
||||
</Button>
|
||||
<Button
|
||||
id="button-with-div-button"
|
||||
data-umami-event="button-click"
|
||||
data-umami-event-name={'bob'}
|
||||
data-umami-event-id="123"
|
||||
variant="primary"
|
||||
>
|
||||
<div>Button with div</div>
|
||||
</Button>
|
||||
<div data-umami-event="div-click">DIV with attribute</div>
|
||||
<div data-umami-event="div-click-one">
|
||||
<div data-umami-event="div-click-two">
|
||||
<div data-umami-event="div-click-three">Nested DIV</div>
|
||||
<Panel>
|
||||
<Grid columns="1fr 1fr 1fr" gap>
|
||||
<Column gap>
|
||||
<Heading>Page links</Heading>
|
||||
<div>
|
||||
<Link href={`/console/${websiteId}?page=1`}>page one</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Column>
|
||||
<Column gap>
|
||||
<Heading>Javascript events</Heading>
|
||||
<Button id="manual-button" variant="primary" onClick={handleRunScript}>
|
||||
Run script
|
||||
</Button>
|
||||
<Button id="manual-button" variant="primary" onClick={handleRunIdentify}>
|
||||
Run identify
|
||||
</Button>
|
||||
<Button id="manual-button" variant="primary" onClick={handleRunRevenue}>
|
||||
Revenue script
|
||||
</Button>
|
||||
</Column>
|
||||
</Grid>
|
||||
<div>
|
||||
<Link href={`/console/${websiteId}?page=2 `}>page two</Link>
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://www.google.com" data-umami-event="external-link-direct">
|
||||
external link (direct)
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
href="https://www.google.com"
|
||||
data-umami-event="external-link-tab"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
external link (tab)
|
||||
</a>
|
||||
</div>
|
||||
</Column>
|
||||
<Column gap>
|
||||
<Heading>Click events</Heading>
|
||||
<Button id="send-event-button" data-umami-event="button-click" variant="primary">
|
||||
Send event
|
||||
</Button>
|
||||
<Button
|
||||
id="send-event-data-button"
|
||||
data-umami-event="button-click"
|
||||
data-umami-event-name="bob"
|
||||
data-umami-event-id="123"
|
||||
variant="primary"
|
||||
>
|
||||
Send event with data
|
||||
</Button>
|
||||
<Button
|
||||
id="generate-revenue-button"
|
||||
data-umami-event="checkout-cart"
|
||||
data-umami-event-revenue={(Math.random() * 10000).toFixed(2).toString()}
|
||||
data-umami-event-currency="USD"
|
||||
variant="primary"
|
||||
>
|
||||
Generate revenue data
|
||||
</Button>
|
||||
<Button
|
||||
id="button-with-div-button"
|
||||
data-umami-event="button-click"
|
||||
data-umami-event-name={'bob'}
|
||||
data-umami-event-id="123"
|
||||
variant="primary"
|
||||
>
|
||||
<div>Button with div</div>
|
||||
</Button>
|
||||
<div data-umami-event="div-click">DIV with attribute</div>
|
||||
<div data-umami-event="div-click-one">
|
||||
<div data-umami-event="div-click-two">
|
||||
<div data-umami-event="div-click-three">Nested DIV</div>
|
||||
</div>
|
||||
</div>
|
||||
</Column>
|
||||
<Column gap>
|
||||
<Heading>Javascript events</Heading>
|
||||
<Button id="manual-button" variant="primary" onClick={handleRunScript}>
|
||||
Run script
|
||||
</Button>
|
||||
<Button id="manual-button" variant="primary" onClick={handleRunIdentify}>
|
||||
Run identify
|
||||
</Button>
|
||||
<Button id="manual-button" variant="primary" onClick={handleRunRevenue}>
|
||||
Revenue script
|
||||
</Button>
|
||||
</Column>
|
||||
</Grid>
|
||||
</Panel>
|
||||
<Heading>Pageviews</Heading>
|
||||
<WebsiteChart websiteId={websiteId} />
|
||||
<Heading>Events</Heading>
|
||||
<EventsChart websiteId={websiteId} />
|
||||
<Panel>
|
||||
<EventsChart websiteId={websiteId} />
|
||||
</Panel>
|
||||
</Column>
|
||||
</PageBody>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export function PasswordEditForm({ onSave, onClose }) {
|
|||
});
|
||||
};
|
||||
|
||||
const samePassword = (value: string, values: { [key: string]: any }) => {
|
||||
const samePassword = (value: string, values: Record<string, any>) => {
|
||||
if (value !== values.newPassword) {
|
||||
return formatMessage(messages.noMatchPassword);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { WebsitesTable } from '@/app/(main)/settings/websites/WebsitesTable';
|
||||
import { DataGrid } from '@/components/common/DataGrid';
|
||||
import { useWebsites } from '@/components/hooks';
|
||||
import { useWebsitesQuery } from '@/components/hooks';
|
||||
|
||||
export function UserWebsites({ userId }) {
|
||||
const queryResult = useWebsites({ userId });
|
||||
const queryResult = useWebsitesQuery({ userId });
|
||||
|
||||
return (
|
||||
<DataGrid queryResult={queryResult}>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { WebsitesTable } from '@/app/(main)/settings/websites/WebsitesTable';
|
||||
import { DataGrid } from '@/components/common/DataGrid';
|
||||
import { useWebsites } from '@/components/hooks';
|
||||
import { useWebsitesQuery } from '@/components/hooks';
|
||||
|
||||
export function WebsitesDataTable({
|
||||
teamId,
|
||||
|
|
@ -16,10 +16,10 @@ export function WebsitesDataTable({
|
|||
showActions?: boolean;
|
||||
children?: ReactNode;
|
||||
}) {
|
||||
const queryResult = useWebsites({ teamId });
|
||||
const queryResult = useWebsitesQuery({ teamId });
|
||||
|
||||
return (
|
||||
<DataGrid queryResult={queryResult} renderEmpty={() => children}>
|
||||
<DataGrid queryResult={queryResult} renderEmpty={() => children} allowSearch allowPaging>
|
||||
{({ data }) => (
|
||||
<WebsitesTable
|
||||
teamId={teamId}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export function WebsitesTable({
|
|||
return (
|
||||
<DataTable data={data}>
|
||||
<DataColumn id="name" label={formatMessage(labels.name)}>
|
||||
{(row: any) => <Link href={renderUrl(`/websites/${row.id}`)}>{row.name}</Link>}
|
||||
{(row: any) => <Link href={renderUrl(`/websites/${row.id}`, false)}>{row.name}</Link>}
|
||||
</DataColumn>
|
||||
<DataColumn id="domain" label={formatMessage(labels.domain)} />
|
||||
{showActions && (
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { LoadingPanel } from '@/components/common/LoadingPanel';
|
|||
import { PageviewsChart } from '@/components/metrics/PageviewsChart';
|
||||
import { useWebsitePageviewsQuery } from '@/components/hooks/queries/useWebsitePageviewsQuery';
|
||||
import { useDateRange } from '@/components/hooks';
|
||||
import { Panel } from '@/components/common/Panel';
|
||||
|
||||
export function WebsiteChart({
|
||||
websiteId,
|
||||
|
|
@ -48,16 +47,14 @@ export function WebsiteChart({
|
|||
}, [data, startDate, endDate, unit]);
|
||||
|
||||
return (
|
||||
<Panel height="520px">
|
||||
<LoadingPanel data={data} isFetching={isFetching} isLoading={isLoading} error={error}>
|
||||
<PageviewsChart
|
||||
key={value}
|
||||
data={chartData}
|
||||
minDate={startDate}
|
||||
maxDate={endDate}
|
||||
unit={unit}
|
||||
/>
|
||||
</LoadingPanel>
|
||||
</Panel>
|
||||
<LoadingPanel data={data} isFetching={isFetching} isLoading={isLoading} error={error}>
|
||||
<PageviewsChart
|
||||
key={value}
|
||||
data={chartData}
|
||||
minDate={startDate}
|
||||
maxDate={endDate}
|
||||
unit={unit}
|
||||
/>
|
||||
</LoadingPanel>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
'use client';
|
||||
import { Column } from '@umami/react-zen';
|
||||
import { useNavigation } from '@/components/hooks';
|
||||
import { Panel } from '@/components/common/Panel';
|
||||
import { WebsiteChart } from './WebsiteChart';
|
||||
import { WebsiteExpandedView } from './WebsiteExpandedView';
|
||||
import { WebsiteMetricsBar } from './WebsiteMetricsBar';
|
||||
|
|
@ -16,8 +17,10 @@ export function WebsiteDetailsPage({ websiteId }: { websiteId: string }) {
|
|||
return (
|
||||
<Column gap>
|
||||
<WebsiteControls websiteId={websiteId} allowCompare={true} />
|
||||
<WebsiteMetricsBar websiteId={websiteId} showFilter={true} showChange={true} />
|
||||
<WebsiteChart websiteId={websiteId} compareMode={compare} />
|
||||
<WebsiteMetricsBar websiteId={websiteId} showChange={true} />
|
||||
<Panel>
|
||||
<WebsiteChart websiteId={websiteId} compareMode={compare} />
|
||||
</Panel>
|
||||
{!view && !compare && <WebsiteTableView websiteId={websiteId} />}
|
||||
{view && !compare && <WebsiteExpandedView websiteId={websiteId} />}
|
||||
{compare && <WebsiteCompareTables websiteId={websiteId} />}
|
||||
|
|
|
|||
|
|
@ -11,13 +11,12 @@ import {
|
|||
import { Fragment } from 'react';
|
||||
import { More, Share, Edit } from '@/components/icons';
|
||||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
import { InputItem } from '@/lib/types';
|
||||
|
||||
export function WebsiteMenu({ websiteId }: { websiteId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { router, updateParams, renderUrl } = useNavigation();
|
||||
|
||||
const menuItems: InputItem[] = [
|
||||
const menuItems = [
|
||||
{ id: 'share', label: formatMessage(labels.share), icon: <Share /> },
|
||||
{ id: 'edit', label: formatMessage(labels.edit), icon: <Edit />, seperator: true },
|
||||
];
|
||||
|
|
|
|||
|
|
@ -3,26 +3,18 @@ import { MetricCard } from '@/components/metrics/MetricCard';
|
|||
import { MetricsBar } from '@/components/metrics/MetricsBar';
|
||||
import { formatShortTime, formatLongNumber } from '@/lib/format';
|
||||
import { useWebsiteStatsQuery } from '@/components/hooks/queries/useWebsiteStatsQuery';
|
||||
import { useWebsites } from '@/store/websites';
|
||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||
|
||||
export function WebsiteMetricsBar({
|
||||
websiteId,
|
||||
showChange = false,
|
||||
compareMode = false,
|
||||
}: {
|
||||
websiteId: string;
|
||||
showChange?: boolean;
|
||||
compareMode?: boolean;
|
||||
showFilter?: boolean;
|
||||
}) {
|
||||
const { dateRange } = useDateRange(websiteId);
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const dateCompare = useWebsites(state => state[websiteId]?.dateCompare);
|
||||
const { data, isLoading, isFetching, error } = useWebsiteStatsQuery(
|
||||
websiteId,
|
||||
compareMode && dateCompare,
|
||||
);
|
||||
const { data, isLoading, isFetching, error } = useWebsiteStatsQuery(websiteId);
|
||||
const isAllTime = dateRange.value === 'all';
|
||||
|
||||
const { pageviews, visitors, visits, bounces, totaltime, previous } = data || {};
|
||||
|
|
@ -87,8 +79,7 @@ export function WebsiteMetricsBar({
|
|||
change={change}
|
||||
formatValue={formatValue}
|
||||
reverseColors={reverseColors}
|
||||
showChange={!isAllTime && (compareMode || showChange)}
|
||||
showPrevious={!isAllTime && compareMode}
|
||||
showChange={!isAllTime}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { LoadingPanel } from '@/components/common/LoadingPanel';
|
|||
import { Panel } from '@/components/common/Panel';
|
||||
|
||||
export function GoalsPage({ websiteId }: { websiteId: string }) {
|
||||
const { result, query } = useReportsQuery({ websiteId, type: 'goal' });
|
||||
const { data, isLoading, error } = useReportsQuery({ websiteId, type: 'goal' });
|
||||
const {
|
||||
dateRange: { startDate, endDate },
|
||||
} = useDateRange(websiteId);
|
||||
|
|
@ -20,14 +20,16 @@ export function GoalsPage({ websiteId }: { websiteId: string }) {
|
|||
<SectionHeader>
|
||||
<GoalAddButton websiteId={websiteId} />
|
||||
</SectionHeader>
|
||||
<LoadingPanel data={result?.data} isLoading={query?.isLoading} error={query?.error}>
|
||||
<Grid columns="1fr 1fr" gap>
|
||||
{result?.data?.map((report: any) => (
|
||||
<Panel key={report.id}>
|
||||
<Goal {...report} startDate={startDate} endDate={endDate} />
|
||||
</Panel>
|
||||
))}
|
||||
</Grid>
|
||||
<LoadingPanel data={data} isLoading={isLoading} error={error}>
|
||||
{data && (
|
||||
<Grid columns="1fr 1fr" gap>
|
||||
{data['data'].map((report: any) => (
|
||||
<Panel key={report.id}>
|
||||
<Goal {...report} startDate={startDate} endDate={endDate} />
|
||||
</Panel>
|
||||
))}
|
||||
</Grid>
|
||||
)}
|
||||
</LoadingPanel>
|
||||
</Column>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export function UTM({ websiteId, startDate, endDate }: UTMProps) {
|
|||
);
|
||||
}
|
||||
|
||||
function toArray(data: { [key: string]: number } = {}) {
|
||||
function toArray(data: Record<string, number> = {}) {
|
||||
return Object.keys(data)
|
||||
.map(key => {
|
||||
return { name: key, value: data[key] };
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export function SessionsDataTable({
|
|||
const queryResult = useWebsiteSessionsQuery(websiteId);
|
||||
|
||||
return (
|
||||
<DataGrid queryResult={queryResult} allowSearch={true} renderEmpty={() => children}>
|
||||
<DataGrid queryResult={queryResult} renderEmpty={() => children} allowPaging>
|
||||
{({ data }) => <SessionsTable data={data} showDomain={!websiteId} />}
|
||||
</DataGrid>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { Icon, TextField, Column, Row, Label, Text } from '@umami/react-zen';
|
||||
import { useFormat, useLocale, useMessages, useRegionNames, useTimezone } from '@/components/hooks';
|
||||
import { useFormat, useLocale, useMessages, useRegionNames } from '@/components/hooks';
|
||||
import { TypeIcon } from '@/components/common/TypeIcon';
|
||||
import { Location, KeyRound, Calendar } from '@/components/icons';
|
||||
import { DateDistance } from '@/components/common/DateDistance';
|
||||
|
||||
export function SessionInfo({ data }) {
|
||||
const { locale } = useLocale();
|
||||
const { formatTimezoneDate } = useTimezone();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { formatValue } = useFormat();
|
||||
const { getRegionName } = useRegionNames(locale);
|
||||
|
|
@ -22,11 +22,11 @@ export function SessionInfo({ data }) {
|
|||
</Info>
|
||||
|
||||
<Info label={formatMessage(labels.lastSeen)} icon={<Calendar />}>
|
||||
{formatTimezoneDate(data?.lastAt, 'PPPPpp')}
|
||||
<DateDistance date={new Date(data.lastAt)} />
|
||||
</Info>
|
||||
|
||||
<Info label={formatMessage(labels.firstSeen)} icon={<Calendar />}>
|
||||
{formatTimezoneDate(data?.firstAt, 'PPPPpp')}
|
||||
<DateDistance date={new Date(data.firstAt)} />
|
||||
</Info>
|
||||
|
||||
<Info
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { getRealtimeData } from '@/queries';
|
|||
import { canViewWebsite } from '@/lib/auth';
|
||||
import { startOfMinute, subMinutes } from 'date-fns';
|
||||
import { REALTIME_RANGE } from '@/lib/constants';
|
||||
import { parseRequest } from '@/lib/request';
|
||||
import { parseRequest, getQueryFilters } from '@/lib/request';
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
|
|
@ -16,15 +16,19 @@ export async function GET(
|
|||
}
|
||||
|
||||
const { websiteId } = await params;
|
||||
const { timezone } = query;
|
||||
|
||||
if (!(await canViewWebsite(auth, websiteId))) {
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
const startDate = subMinutes(startOfMinute(new Date()), REALTIME_RANGE);
|
||||
const filters = await getQueryFilters({
|
||||
...query,
|
||||
websiteId,
|
||||
startAt: subMinutes(startOfMinute(new Date()), REALTIME_RANGE).getTime(),
|
||||
endAt: Date.now(),
|
||||
});
|
||||
|
||||
const data = await getRealtimeData(websiteId, { startDate, timezone });
|
||||
const data = await getRealtimeData(websiteId, filters);
|
||||
|
||||
return json(data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { canViewWebsite } from '@/lib/auth';
|
||||
import { unauthorized, json } from '@/lib/response';
|
||||
import { parseRequest } from '@/lib/request';
|
||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
||||
import { getGoal } from '@/queries/sql/reports/getGoal';
|
||||
import { reportResultSchema } from '@/lib/schema';
|
||||
|
||||
|
|
@ -15,21 +15,22 @@ export async function POST(request: Request) {
|
|||
websiteId,
|
||||
dateRange: { startDate, endDate },
|
||||
parameters: { type, value, property, operator },
|
||||
...filters
|
||||
} = body;
|
||||
|
||||
if (!(await canViewWebsite(auth, websiteId))) {
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
const filters = await getQueryFilters(body.filters);
|
||||
|
||||
const data = await getGoal(websiteId, {
|
||||
...filters,
|
||||
startDate: new Date(startDate),
|
||||
endDate: new Date(endDate),
|
||||
type,
|
||||
value,
|
||||
property,
|
||||
operator,
|
||||
filters,
|
||||
});
|
||||
|
||||
return json(data);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { z } from 'zod';
|
||||
import { unauthorized, json } from '@/lib/response';
|
||||
import { canViewTeam } from '@/lib/auth';
|
||||
import { parseRequest } from '@/lib/request';
|
||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
||||
import { pagingParams } from '@/lib/schema';
|
||||
import { getTeamWebsites } from '@/queries';
|
||||
|
||||
|
|
@ -20,7 +20,9 @@ export async function GET(request: Request, { params }: { params: Promise<{ team
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const websites = await getTeamWebsites(teamId, query);
|
||||
const filters = await getQueryFilters(query);
|
||||
|
||||
const websites = await getTeamWebsites(teamId, filters);
|
||||
|
||||
return json(websites);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { json, unauthorized } from '@/lib/response';
|
|||
import { getAllUserWebsitesIncludingTeamOwner } from '@/queries/prisma/website';
|
||||
import { getEventUsage } from '@/queries/sql/events/getEventUsage';
|
||||
import { getEventDataUsage } from '@/queries/sql/events/getEventDataUsage';
|
||||
import { parseRequest, getRequestDateRange } from '@/lib/request';
|
||||
import { parseRequest, getQueryFilters } from '@/lib/request';
|
||||
|
||||
export async function GET(request: Request, { params }: { params: Promise<{ userId: string }> }) {
|
||||
const schema = z.object({
|
||||
|
|
@ -22,14 +22,14 @@ export async function GET(request: Request, { params }: { params: Promise<{ user
|
|||
}
|
||||
|
||||
const { userId } = await params;
|
||||
const { startDate, endDate } = await getRequestDateRange(query);
|
||||
const filters = await getQueryFilters(query);
|
||||
|
||||
const websites = await getAllUserWebsitesIncludingTeamOwner(userId);
|
||||
|
||||
const websiteIds = websites.map(a => a.id);
|
||||
|
||||
const websiteEventUsage = await getEventUsage(websiteIds, startDate, endDate);
|
||||
const eventDataUsage = await getEventDataUsage(websiteIds, startDate, endDate);
|
||||
const websiteEventUsage = await getEventUsage(websiteIds, filters);
|
||||
const eventDataUsage = await getEventDataUsage(websiteIds, filters);
|
||||
|
||||
const websiteUsage = websites.map(a => ({
|
||||
websiteId: a.id,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { z } from 'zod';
|
|||
import { unauthorized, json } from '@/lib/response';
|
||||
import { getUserWebsites } from '@/queries/prisma/website';
|
||||
import { pagingParams } from '@/lib/schema';
|
||||
import { parseRequest } from '@/lib/request';
|
||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
||||
|
||||
export async function GET(request: Request, { params }: { params: Promise<{ userId: string }> }) {
|
||||
const schema = z.object({
|
||||
|
|
@ -21,7 +21,9 @@ export async function GET(request: Request, { params }: { params: Promise<{ user
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const websites = await getUserWebsites(userId, query);
|
||||
const filters = await getQueryFilters(query);
|
||||
|
||||
const websites = await getUserWebsites(userId, filters);
|
||||
|
||||
return json(websites);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { z } from 'zod';
|
||||
import { getRequestDateRange, parseRequest } from '@/lib/request';
|
||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
||||
import { unauthorized, json } from '@/lib/response';
|
||||
import { canViewWebsite } from '@/lib/auth';
|
||||
import { getEventDataEvents } from '@/queries/sql/events/getEventDataEvents';
|
||||
|
|
@ -20,16 +20,16 @@ export async function GET(
|
|||
}
|
||||
|
||||
const { websiteId } = await params;
|
||||
const { event } = query;
|
||||
const { startDate, endDate } = await getRequestDateRange(query);
|
||||
|
||||
if (!(await canViewWebsite(auth, websiteId))) {
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
const { event } = query;
|
||||
const filters = await getQueryFilters(query);
|
||||
|
||||
const data = await getEventDataEvents(websiteId, {
|
||||
startDate,
|
||||
endDate,
|
||||
...filters,
|
||||
event,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { z } from 'zod';
|
||||
import { getRequestDateRange, parseRequest } from '@/lib/request';
|
||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
||||
import { unauthorized, json } from '@/lib/response';
|
||||
import { canViewWebsite } from '@/lib/auth';
|
||||
import { getEventDataFields } from '@/queries';
|
||||
|
|
@ -20,16 +20,14 @@ export async function GET(
|
|||
}
|
||||
|
||||
const { websiteId } = await params;
|
||||
const { startDate, endDate } = await getRequestDateRange(query);
|
||||
|
||||
if (!(await canViewWebsite(auth, websiteId))) {
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
const data = await getEventDataFields(websiteId, {
|
||||
startDate,
|
||||
endDate,
|
||||
});
|
||||
const filters = await getQueryFilters(query);
|
||||
|
||||
const data = await getEventDataFields(websiteId, filters);
|
||||
|
||||
return json(data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { z } from 'zod';
|
||||
import { getRequestDateRange, parseRequest } from '@/lib/request';
|
||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
||||
import { unauthorized, json } from '@/lib/response';
|
||||
import { canViewWebsite } from '@/lib/auth';
|
||||
import { getEventDataProperties } from '@/queries';
|
||||
|
|
@ -21,14 +21,15 @@ export async function GET(
|
|||
}
|
||||
|
||||
const { websiteId } = await params;
|
||||
const { propertyName } = query;
|
||||
const { startDate, endDate } = await getRequestDateRange(query);
|
||||
|
||||
if (!(await canViewWebsite(auth, websiteId))) {
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
const data = await getEventDataProperties(websiteId, { startDate, endDate, propertyName });
|
||||
const { propertyName } = query;
|
||||
const filters = await getQueryFilters(query);
|
||||
|
||||
const data = await getEventDataProperties(websiteId, { ...filters, propertyName });
|
||||
|
||||
return json(data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { z } from 'zod';
|
||||
import { getRequestDateRange, parseRequest } from '@/lib/request';
|
||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
||||
import { unauthorized, json } from '@/lib/response';
|
||||
import { canViewWebsite } from '@/lib/auth';
|
||||
import { getEventDataStats } from '@/queries';
|
||||
|
|
@ -21,13 +21,14 @@ export async function GET(
|
|||
}
|
||||
|
||||
const { websiteId } = await params;
|
||||
const { startDate, endDate } = await getRequestDateRange(query);
|
||||
|
||||
if (!(await canViewWebsite(auth, websiteId))) {
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
const data = await getEventDataStats(websiteId, { startDate, endDate });
|
||||
const filters = await getQueryFilters(query);
|
||||
|
||||
const data = await getEventDataStats(websiteId, filters);
|
||||
|
||||
return json(data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { z } from 'zod';
|
||||
import { getRequestDateRange, parseRequest } from '@/lib/request';
|
||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
||||
import { unauthorized, json } from '@/lib/response';
|
||||
import { canViewWebsite } from '@/lib/auth';
|
||||
import { getEventDataValues } from '@/queries';
|
||||
|
|
@ -22,16 +22,16 @@ export async function GET(
|
|||
}
|
||||
|
||||
const { websiteId } = await params;
|
||||
const { eventName, propertyName } = query;
|
||||
const { startDate, endDate } = await getRequestDateRange(query);
|
||||
|
||||
if (!(await canViewWebsite(auth, websiteId))) {
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
const { eventName, propertyName } = query;
|
||||
const filters = await getQueryFilters(query);
|
||||
|
||||
const data = await getEventDataValues(websiteId, {
|
||||
startDate,
|
||||
endDate,
|
||||
...filters,
|
||||
eventName,
|
||||
propertyName,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { z } from 'zod';
|
||||
import { getRequestDateRange, parseRequest } from '@/lib/request';
|
||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
||||
import { unauthorized, json } from '@/lib/response';
|
||||
import { canViewWebsite } from '@/lib/auth';
|
||||
import { pagingParams } from '@/lib/schema';
|
||||
|
|
@ -22,13 +22,14 @@ export async function GET(
|
|||
}
|
||||
|
||||
const { websiteId } = await params;
|
||||
const { startDate, endDate } = await getRequestDateRange(query);
|
||||
|
||||
if (!(await canViewWebsite(auth, websiteId))) {
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
const data = await getWebsiteEvents(websiteId, { ...query, startDate, endDate }, query);
|
||||
const filters = await getQueryFilters(query);
|
||||
|
||||
const data = await getWebsiteEvents(websiteId, filters);
|
||||
|
||||
return json(data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { z } from 'zod';
|
||||
import { parseRequest, getRequestDateRange, getRequestFilters } from '@/lib/request';
|
||||
import { parseRequest, getQueryFilters } from '@/lib/request';
|
||||
import { unauthorized, json } from '@/lib/response';
|
||||
import { canViewWebsite } from '@/lib/auth';
|
||||
import { filterParams, timezoneParam, unitParam } from '@/lib/schema';
|
||||
|
|
@ -24,20 +24,12 @@ export async function GET(
|
|||
}
|
||||
|
||||
const { websiteId } = await params;
|
||||
const { timezone } = query;
|
||||
const { startDate, endDate, unit } = await getRequestDateRange(query);
|
||||
|
||||
if (!(await canViewWebsite(auth, websiteId))) {
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
const filters = {
|
||||
...getRequestFilters(query),
|
||||
startDate,
|
||||
endDate,
|
||||
timezone,
|
||||
unit,
|
||||
};
|
||||
const filters = await getQueryFilters({ ...query, websiteId });
|
||||
|
||||
const data = await getEventMetrics(websiteId, filters);
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
VIDEO_DOMAINS,
|
||||
PAID_AD_PARAMS,
|
||||
} from '@/lib/constants';
|
||||
import { getRequestFilters, getRequestDateRange, parseRequest } from '@/lib/request';
|
||||
import { parseRequest, getQueryFilters } from '@/lib/request';
|
||||
import { json, unauthorized, badRequest } from '@/lib/response';
|
||||
import { getPageviewMetrics, getSessionMetrics, getChannelMetrics } from '@/queries';
|
||||
import { filterParams } from '@/lib/schema';
|
||||
|
|
@ -45,13 +45,8 @@ export async function GET(
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const { startDate, endDate } = await getRequestDateRange(query);
|
||||
const column = FILTER_COLUMNS[type] || type;
|
||||
const filters = {
|
||||
...getRequestFilters(query),
|
||||
startDate,
|
||||
endDate,
|
||||
};
|
||||
const filters = await getQueryFilters({ ...query, websiteId });
|
||||
|
||||
if (search) {
|
||||
filters[type] = {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { z } from 'zod';
|
||||
import { canViewWebsite } from '@/lib/auth';
|
||||
import { getRequestFilters, getRequestDateRange, parseRequest } from '@/lib/request';
|
||||
import { unitParam, timezoneParam, filterParams } from '@/lib/schema';
|
||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
||||
import { dateRangeParams, filterParams } from '@/lib/schema';
|
||||
import { getCompareDate } from '@/lib/date';
|
||||
import { unauthorized, json } from '@/lib/response';
|
||||
import { getPageviewStats, getSessionStats } from '@/queries';
|
||||
|
|
@ -11,11 +11,7 @@ export async function GET(
|
|||
{ params }: { params: Promise<{ websiteId: string }> },
|
||||
) {
|
||||
const schema = z.object({
|
||||
startAt: z.coerce.number().int(),
|
||||
endAt: z.coerce.number().int(),
|
||||
unit: unitParam,
|
||||
timezone: timezoneParam,
|
||||
compare: z.string().optional(),
|
||||
...dateRangeParams,
|
||||
...filterParams,
|
||||
});
|
||||
|
||||
|
|
@ -26,32 +22,23 @@ export async function GET(
|
|||
}
|
||||
|
||||
const { websiteId } = await params;
|
||||
const { timezone, compare } = query;
|
||||
|
||||
if (!(await canViewWebsite(auth, websiteId))) {
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
const { startDate, endDate, unit } = await getRequestDateRange(query);
|
||||
|
||||
const filters = {
|
||||
...getRequestFilters(query),
|
||||
startDate,
|
||||
endDate,
|
||||
timezone,
|
||||
unit,
|
||||
};
|
||||
const filters = await getQueryFilters({ ...query, websiteId });
|
||||
|
||||
const [pageviews, sessions] = await Promise.all([
|
||||
getPageviewStats(websiteId, filters),
|
||||
getSessionStats(websiteId, filters),
|
||||
]);
|
||||
|
||||
if (compare) {
|
||||
if (filters.compare) {
|
||||
const { startDate: compareStartDate, endDate: compareEndDate } = getCompareDate(
|
||||
compare,
|
||||
startDate,
|
||||
endDate,
|
||||
filters.compare,
|
||||
filters.startDate,
|
||||
filters.endDate,
|
||||
);
|
||||
|
||||
const [comparePageviews, compareSessions] = await Promise.all([
|
||||
|
|
@ -70,8 +57,8 @@ export async function GET(
|
|||
return json({
|
||||
pageviews,
|
||||
sessions,
|
||||
startDate,
|
||||
endDate,
|
||||
startDate: filters.startDate,
|
||||
endDate: filters.endDate,
|
||||
compare: {
|
||||
pageviews: comparePageviews,
|
||||
sessions: compareSessions,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { z } from 'zod';
|
||||
import { getRequestDateRange, parseRequest } from '@/lib/request';
|
||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
||||
import { unauthorized, json } from '@/lib/response';
|
||||
import { canViewWebsite } from '@/lib/auth';
|
||||
import { getSessionDataProperties } from '@/queries';
|
||||
|
|
@ -22,13 +22,13 @@ export async function GET(
|
|||
|
||||
const { websiteId } = await params;
|
||||
const { propertyName } = query;
|
||||
const { startDate, endDate } = await getRequestDateRange(query);
|
||||
const filters = await getQueryFilters(query);
|
||||
|
||||
if (!(await canViewWebsite(auth, websiteId))) {
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
const data = await getSessionDataProperties(websiteId, { startDate, endDate, propertyName });
|
||||
const data = await getSessionDataProperties(websiteId, { ...filters, propertyName });
|
||||
|
||||
return json(data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { canViewWebsite } from '@/lib/auth';
|
||||
import { getRequestDateRange, parseRequest } from '@/lib/request';
|
||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
||||
import { json, unauthorized } from '@/lib/response';
|
||||
import { getSessionDataValues } from '@/queries';
|
||||
import { z } from 'zod';
|
||||
|
|
@ -22,15 +22,14 @@ export async function GET(
|
|||
|
||||
const { propertyName } = query;
|
||||
const { websiteId } = await params;
|
||||
const { startDate, endDate } = await getRequestDateRange(query);
|
||||
const filters = await getQueryFilters({ ...query, websiteId });
|
||||
|
||||
if (!(await canViewWebsite(auth, websiteId))) {
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
const data = await getSessionDataValues(websiteId, {
|
||||
startDate,
|
||||
endDate,
|
||||
...filters,
|
||||
propertyName,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { z } from 'zod';
|
||||
import { parseRequest, getRequestDateRange } from '@/lib/request';
|
||||
import { parseRequest, getQueryFilters } from '@/lib/request';
|
||||
import { unauthorized, json } from '@/lib/response';
|
||||
import { canViewWebsite } from '@/lib/auth';
|
||||
import { getSessionActivity } from '@/queries';
|
||||
|
|
@ -20,13 +20,14 @@ export async function GET(
|
|||
}
|
||||
|
||||
const { websiteId, sessionId } = await params;
|
||||
const { startDate, endDate } = await getRequestDateRange(query);
|
||||
|
||||
if (!(await canViewWebsite(auth, websiteId))) {
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
const data = await getSessionActivity(websiteId, sessionId, startDate, endDate);
|
||||
const filters = await getQueryFilters(query);
|
||||
|
||||
const data = await getSessionActivity(websiteId, sessionId, filters);
|
||||
|
||||
return json(data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { z } from 'zod';
|
||||
import { getRequestDateRange, parseRequest } from '@/lib/request';
|
||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
||||
import { unauthorized, json } from '@/lib/response';
|
||||
import { canViewWebsite } from '@/lib/auth';
|
||||
import { pagingParams } from '@/lib/schema';
|
||||
import { dateRangeParams, filterParams, pagingParams } from '@/lib/schema';
|
||||
import { getWebsiteSessions } from '@/queries';
|
||||
|
||||
export async function GET(
|
||||
|
|
@ -10,8 +10,8 @@ export async function GET(
|
|||
{ params }: { params: Promise<{ websiteId: string }> },
|
||||
) {
|
||||
const schema = z.object({
|
||||
startAt: z.coerce.number().int(),
|
||||
endAt: z.coerce.number().int(),
|
||||
...dateRangeParams,
|
||||
...filterParams,
|
||||
...pagingParams,
|
||||
});
|
||||
|
||||
|
|
@ -21,14 +21,15 @@ export async function GET(
|
|||
return error();
|
||||
}
|
||||
|
||||
const { startDate, endDate } = await getRequestDateRange(query);
|
||||
const { websiteId } = await params;
|
||||
|
||||
if (!(await canViewWebsite(auth, websiteId))) {
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
const data = await getWebsiteSessions(websiteId, { startDate, endDate }, query);
|
||||
const filters = await getQueryFilters({ ...query, websiteId });
|
||||
|
||||
const data = await getWebsiteSessions(websiteId, filters);
|
||||
|
||||
return json(data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { z } from 'zod';
|
||||
import { parseRequest, getRequestDateRange, getRequestFilters } from '@/lib/request';
|
||||
import { parseRequest, getQueryFilters } from '@/lib/request';
|
||||
import { unauthorized, json } from '@/lib/response';
|
||||
import { canViewWebsite } from '@/lib/auth';
|
||||
import { filterParams } from '@/lib/schema';
|
||||
|
|
@ -27,15 +27,9 @@ export async function GET(
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const { startDate, endDate } = await getRequestDateRange(query);
|
||||
const filters = await getQueryFilters(query);
|
||||
|
||||
const filters = getRequestFilters(query);
|
||||
|
||||
const metrics = await getWebsiteSessionStats(websiteId, {
|
||||
...filters,
|
||||
startDate,
|
||||
endDate,
|
||||
});
|
||||
const metrics = await getWebsiteSessionStats(websiteId, filters);
|
||||
|
||||
const data = Object.keys(metrics[0]).reduce((obj, key) => {
|
||||
obj[key] = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { z } from 'zod';
|
||||
import { getRequestDateRange, parseRequest } from '@/lib/request';
|
||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
||||
import { unauthorized, json } from '@/lib/response';
|
||||
import { canViewWebsite } from '@/lib/auth';
|
||||
import { pagingParams, timezoneParam } from '@/lib/schema';
|
||||
|
|
@ -23,14 +23,14 @@ export async function GET(
|
|||
}
|
||||
|
||||
const { websiteId } = await params;
|
||||
const { timezone } = query;
|
||||
const { startDate, endDate } = await getRequestDateRange(query);
|
||||
|
||||
if (!(await canViewWebsite(auth, websiteId))) {
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
const data = await getWebsiteSessionsWeekly(websiteId, { startDate, endDate, timezone });
|
||||
const filters = await getQueryFilters(query);
|
||||
|
||||
const data = await getWebsiteSessionsWeekly(websiteId, filters);
|
||||
|
||||
return json(data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { z } from 'zod';
|
||||
import { parseRequest, getRequestDateRange, getRequestFilters } from '@/lib/request';
|
||||
import { parseRequest, getQueryFilters } from '@/lib/request';
|
||||
import { unauthorized, json } from '@/lib/response';
|
||||
import { canViewWebsite } from '@/lib/auth';
|
||||
import { getCompareDate } from '@/lib/date';
|
||||
import { filterParams } from '@/lib/schema';
|
||||
import { getWebsiteStats } from '@/queries';
|
||||
|
||||
|
|
@ -24,32 +23,20 @@ export async function GET(
|
|||
}
|
||||
|
||||
const { websiteId } = await params;
|
||||
const { compare } = query;
|
||||
|
||||
if (!(await canViewWebsite(auth, websiteId))) {
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
const { startDate, endDate } = await getRequestDateRange(query);
|
||||
const { startDate: compareStartDate, endDate: compareEndDate } = getCompareDate(
|
||||
compare,
|
||||
startDate,
|
||||
endDate,
|
||||
);
|
||||
const filters = await getQueryFilters({ ...query, websiteId });
|
||||
|
||||
const filters = getRequestFilters(query);
|
||||
|
||||
const metrics = await getWebsiteStats(websiteId, {
|
||||
...filters,
|
||||
startDate,
|
||||
endDate,
|
||||
});
|
||||
const data = await getWebsiteStats(websiteId, filters);
|
||||
|
||||
const previous = await getWebsiteStats(websiteId, {
|
||||
...filters,
|
||||
startDate: compareStartDate,
|
||||
endDate: compareEndDate,
|
||||
startDate: filters.compareStartDate,
|
||||
endDate: filters.compareEndDate,
|
||||
});
|
||||
|
||||
return json({ ...metrics, previous });
|
||||
return json({ ...data, previous });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { z } from 'zod';
|
|||
import { canViewWebsite } from '@/lib/auth';
|
||||
import { EVENT_COLUMNS, FILTER_COLUMNS, SESSION_COLUMNS } from '@/lib/constants';
|
||||
import { getValues } from '@/queries';
|
||||
import { parseRequest, getRequestDateRange } from '@/lib/request';
|
||||
import { parseRequest, getQueryFilters } from '@/lib/request';
|
||||
import { badRequest, json, unauthorized } from '@/lib/response';
|
||||
|
||||
export async function GET(
|
||||
|
|
@ -23,7 +23,7 @@ export async function GET(
|
|||
}
|
||||
|
||||
const { websiteId } = await params;
|
||||
const { type, search } = query;
|
||||
const { type } = query;
|
||||
|
||||
if (!(await canViewWebsite(auth, websiteId))) {
|
||||
return unauthorized();
|
||||
|
|
@ -33,9 +33,9 @@ export async function GET(
|
|||
return badRequest('Invalid type.');
|
||||
}
|
||||
|
||||
const { startDate, endDate } = await getRequestDateRange(query);
|
||||
const filters = await getQueryFilters(query);
|
||||
|
||||
const values = await getValues(websiteId, FILTER_COLUMNS[type], startDate, endDate, search);
|
||||
const values = await getValues(websiteId, FILTER_COLUMNS[type], { ...filters });
|
||||
|
||||
return json(values.filter(n => n).sort());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { ReactNode, useState } from 'react';
|
||||
import { SearchField, Row, Column } from '@umami/react-zen';
|
||||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
import { Pager } from '@/components/common/Pager';
|
||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||
import { PagedQueryResult } from '@/lib/types';
|
||||
|
||||
const DEFAULT_SEARCH_DELAY = 600;
|
||||
|
||||
export interface DataTableProps {
|
||||
queryResult: PagedQueryResult<any>;
|
||||
queryResult: any;
|
||||
searchDelay?: number;
|
||||
allowSearch?: boolean;
|
||||
allowPaging?: boolean;
|
||||
|
|
@ -20,31 +19,30 @@ export interface DataTableProps {
|
|||
export function DataGrid({
|
||||
queryResult,
|
||||
searchDelay = 600,
|
||||
allowSearch = true,
|
||||
allowPaging = true,
|
||||
allowSearch,
|
||||
allowPaging,
|
||||
autoFocus,
|
||||
children,
|
||||
}: DataTableProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { result, params, setParams, query } = queryResult || {};
|
||||
const { error, isLoading, isFetching } = query || {};
|
||||
const { page, pageSize, count, data } = result || {};
|
||||
const { search } = params || {};
|
||||
const hasData = Boolean(!isLoading && data?.length);
|
||||
const { data, error, isLoading, isFetching, setParams } = queryResult || {};
|
||||
const { router, updateParams } = useNavigation();
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const handleSearch = (search: string) => {
|
||||
setParams({ ...params, search });
|
||||
setSearch(search);
|
||||
setParams(params => ({ ...params, search }));
|
||||
router.push(updateParams({ search }));
|
||||
};
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setParams({ ...params, page });
|
||||
setParams(params => ({ ...params, page }));
|
||||
router.push(updateParams({ page }));
|
||||
};
|
||||
|
||||
return (
|
||||
<Column gap="4" minHeight="300px">
|
||||
{allowSearch && (hasData || search) && (
|
||||
{allowSearch && (data || search) && (
|
||||
<Row width="280px" alignItems="center">
|
||||
<SearchField
|
||||
value={search}
|
||||
|
|
@ -57,11 +55,16 @@ export function DataGrid({
|
|||
)}
|
||||
<LoadingPanel data={data} isLoading={isLoading} isFetching={isFetching} error={error}>
|
||||
<Column>
|
||||
{hasData ? (typeof children === 'function' ? children(result) : children) : null}
|
||||
{data ? (typeof children === 'function' ? children(data) : children) : null}
|
||||
</Column>
|
||||
{allowPaging && hasData && (
|
||||
{allowPaging && data && (
|
||||
<Row marginTop="6">
|
||||
<Pager page={page} pageSize={pageSize} count={count} onPageChange={handlePageChange} />
|
||||
<Pager
|
||||
page={data.page}
|
||||
pageSize={data.pageSize}
|
||||
count={data.count}
|
||||
onPageChange={handlePageChange}
|
||||
/>
|
||||
</Row>
|
||||
)}
|
||||
</LoadingPanel>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export function FilterEditForm({ websiteId, data = [], onChange, onClose }: Filt
|
|||
dateRange: { startDate, endDate },
|
||||
} = useDateRange(websiteId);
|
||||
|
||||
const updateFilter = (name: string, props: { [key: string]: any }) => {
|
||||
const updateFilter = (name: string, props: Record<string, any>) => {
|
||||
setFilters(filters =>
|
||||
filters.map(filter => (filter.name === name ? { ...filter, ...props } : filter)),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export * from './queries/useTeamMembersQuery';
|
|||
export * from './queries/useUserQuery';
|
||||
export * from './queries/useUsersQuery';
|
||||
export * from './queries/useWebsiteQuery';
|
||||
export * from './queries/useWebsites';
|
||||
export * from './queries/useWebsitesQuery';
|
||||
export * from './queries/useWebsiteEventsQuery';
|
||||
export * from './queries/useWebsiteEventsSeriesQuery';
|
||||
export * from './queries/useWebsiteMetricsQuery';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useApi, useModified } from '@/components/hooks';
|
||||
|
||||
export function useDeleteQuery(path: string, params?: { [key: string]: any }) {
|
||||
export function useDeleteQuery(path: string, params?: Record<string, any>) {
|
||||
const { del, useMutation } = useApi();
|
||||
const { mutate, isPending, error } = useMutation({
|
||||
mutationFn: () => del(path, params),
|
||||
|
|
|
|||
|
|
@ -1,8 +1,27 @@
|
|||
import { useTimezone } from '@/components/hooks/useTimezone';
|
||||
import { REALTIME_INTERVAL } from '@/lib/constants';
|
||||
import { RealtimeData } from '@/lib/types';
|
||||
import { useApi } from '../useApi';
|
||||
|
||||
export interface RealtimeData {
|
||||
countries: Record<string, number>;
|
||||
events: any[];
|
||||
pageviews: any[];
|
||||
referrers: Record<string, number>;
|
||||
timestamp: number;
|
||||
series: {
|
||||
views: any[];
|
||||
visitors: any[];
|
||||
};
|
||||
totals: {
|
||||
views: number;
|
||||
visitors: number;
|
||||
events: number;
|
||||
countries: number;
|
||||
};
|
||||
urls: Record<string, number>;
|
||||
visitors: any[];
|
||||
}
|
||||
|
||||
export function useRealtimeQuery(websiteId: string) {
|
||||
const { get, useQuery } = useApi();
|
||||
const { timezone } = useTimezone();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { ReactQueryOptions } from '@/lib/types';
|
|||
|
||||
export function useResultQuery<T = any>(
|
||||
type: string,
|
||||
params?: { [key: string]: any },
|
||||
params?: Record<string, any>,
|
||||
options?: ReactQueryOptions<T>,
|
||||
) {
|
||||
const { websiteId } = params;
|
||||
|
|
@ -21,7 +21,7 @@ export function useResultQuery<T = any>(
|
|||
...params,
|
||||
},
|
||||
],
|
||||
queryFn: () => post(`/reports/${type}`, { type, ...filters, ...params }),
|
||||
queryFn: () => post(`/reports/${type}`, { type, filters, ...params }),
|
||||
enabled: !!type,
|
||||
...options,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useApi } from '../useApi';
|
||||
|
||||
export function useUserQuery(userId: string, options?: { [key: string]: any }) {
|
||||
export function useUserQuery(userId: string, options?: Record<string, any>) {
|
||||
const { get, useQuery } = useApi();
|
||||
return useQuery({
|
||||
queryKey: ['users', userId],
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ import { ReactQueryOptions } from '@/lib/types';
|
|||
|
||||
export function useWebsiteEventsQuery(websiteId: string, options?: ReactQueryOptions<any>) {
|
||||
const { get } = useApi();
|
||||
const filterParams = useFilterParams(websiteId);
|
||||
const queryParams = useFilterParams(websiteId);
|
||||
|
||||
return usePagedQuery({
|
||||
queryKey: ['websites:events', { websiteId, ...filterParams }],
|
||||
queryFn: () => get(`/websites/${websiteId}/events`, { ...filterParams, pageSize: 20 }),
|
||||
queryKey: ['websites:events', { websiteId, ...queryParams }],
|
||||
queryFn: () => get(`/websites/${websiteId}/events`, { ...queryParams, pageSize: 20 }),
|
||||
enabled: !!websiteId,
|
||||
...options,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export function useWebsiteMetricsQuery(
|
|||
options?: ReactQueryOptions<WebsiteMetricsData>,
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const filterParams = useFilterParams(websiteId);
|
||||
const queryParams = useFilterParams(websiteId);
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
return useQuery<WebsiteMetricsData>({
|
||||
|
|
@ -23,13 +23,13 @@ export function useWebsiteMetricsQuery(
|
|||
'websites:metrics',
|
||||
{
|
||||
websiteId,
|
||||
...filterParams,
|
||||
...queryParams,
|
||||
...params,
|
||||
},
|
||||
],
|
||||
queryFn: async () =>
|
||||
get(`/websites/${websiteId}/metrics`, {
|
||||
...filterParams,
|
||||
...queryParams,
|
||||
[searchParams.get('view')]: undefined,
|
||||
...params,
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@ export function useWebsitePageviewsQuery(
|
|||
options?: ReactQueryOptions<WebsitePageviewsData>,
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const filterParams = useFilterParams(websiteId);
|
||||
const queryParams = useFilterParams(websiteId);
|
||||
|
||||
return useQuery<WebsitePageviewsData>({
|
||||
queryKey: ['websites:pageviews', { websiteId, compare, ...filterParams }],
|
||||
queryFn: () => get(`/websites/${websiteId}/pageviews`, { compare, ...filterParams }),
|
||||
queryKey: ['websites:pageviews', { websiteId, compare, ...queryParams }],
|
||||
queryFn: () => get(`/websites/${websiteId}/pageviews`, { compare, ...queryParams }),
|
||||
enabled: !!websiteId,
|
||||
...options,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useApi } from '../useApi';
|
||||
|
||||
export function useWebsiteQuery(websiteId: string, options?: { [key: string]: any }) {
|
||||
export function useWebsiteQuery(websiteId: string, options?: Record<string, any>) {
|
||||
const { get, useQuery } = useApi();
|
||||
|
||||
return useQuery({
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
import { useApi } from '../useApi';
|
||||
import { useFilterParams } from '../useFilterParams';
|
||||
|
||||
export function useWebsiteSessionStatsQuery(
|
||||
websiteId: string,
|
||||
options?: { [key: string]: string },
|
||||
) {
|
||||
export function useWebsiteSessionStatsQuery(websiteId: string, options?: Record<string, string>) {
|
||||
const { get, useQuery } = useApi();
|
||||
const params = useFilterParams(websiteId);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { useFilterParams } from '@/components/hooks/useFilterParams';
|
|||
|
||||
export function useWebsiteSessionsQuery(
|
||||
websiteId: string,
|
||||
params?: { [key: string]: string | number },
|
||||
params?: Record<string, string | number>,
|
||||
) {
|
||||
const { get } = useApi();
|
||||
const { modified } = useModified(`sessions`);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { useFilterParams } from '@/components/hooks/useFilterParams';
|
|||
|
||||
export function useWebsiteSessionsWeeklyQuery(
|
||||
websiteId: string,
|
||||
params?: { [key: string]: string | number },
|
||||
params?: Record<string, string | number>,
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const { modified } = useModified(`sessions`);
|
||||
|
|
|
|||
|
|
@ -19,15 +19,14 @@ export interface WebsiteStatsData {
|
|||
|
||||
export function useWebsiteStatsQuery(
|
||||
websiteId: string,
|
||||
compare?: string,
|
||||
options?: UseQueryOptions<WebsiteStatsData, Error, WebsiteStatsData>,
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const params = useFilterParams(websiteId);
|
||||
const filterParams = useFilterParams(websiteId);
|
||||
|
||||
return useQuery<WebsiteStatsData>({
|
||||
queryKey: ['websites:stats', { websiteId, ...params, compare }],
|
||||
queryFn: () => get(`/websites/${websiteId}/stats`, { ...params, compare }),
|
||||
queryKey: ['websites:stats', { websiteId, ...filterParams }],
|
||||
queryFn: () => get(`/websites/${websiteId}/stats`, { ...filterParams }),
|
||||
enabled: !!websiteId,
|
||||
...options,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ import { useApi } from '../useApi';
|
|||
import { usePagedQuery } from '../usePagedQuery';
|
||||
import { useLoginQuery } from './useLoginQuery';
|
||||
import { useModified } from '../useModified';
|
||||
import { ReactQueryOptions } from '@/lib/types';
|
||||
|
||||
export function useWebsites(
|
||||
export function useWebsitesQuery(
|
||||
{ userId, teamId }: { userId?: string; teamId?: string },
|
||||
params?: { [key: string]: string | number },
|
||||
params?: Record<string, any>,
|
||||
options?: ReactQueryOptions,
|
||||
) {
|
||||
const { get } = useApi();
|
||||
const { user } = useLoginQuery();
|
||||
|
|
@ -13,10 +15,12 @@ export function useWebsites(
|
|||
|
||||
return usePagedQuery({
|
||||
queryKey: ['websites', { userId, teamId, modified, ...params }],
|
||||
queryFn: () => {
|
||||
queryFn: pageParams => {
|
||||
return get(teamId ? `/teams/${teamId}/websites` : `/users/${userId || user.id}/websites`, {
|
||||
...params,
|
||||
...pageParams,
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ export function useFormat() {
|
|||
return languageNames[value?.split('-')[0]] || value;
|
||||
};
|
||||
|
||||
const formatValue = (value: string, type: string, data?: { [key: string]: any }): string => {
|
||||
const formatValue = (value: string, type: string, data?: Record<string, any>): string => {
|
||||
switch (type) {
|
||||
case 'os':
|
||||
return formatOS(value);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export function useMessages(): any {
|
|||
id: string;
|
||||
defaultMessage: string;
|
||||
},
|
||||
values?: { [key: string]: string },
|
||||
values?: Record<string, string>,
|
||||
opts?: any,
|
||||
) => {
|
||||
return descriptor ? intl.formatMessage(descriptor, values, opts) : null;
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@ export function useNavigation() {
|
|||
const [, websiteId] = pathname.match(/\/websites\/([a-f0-9-]+)/) || [];
|
||||
const query = Object.fromEntries(searchParams);
|
||||
|
||||
const updateParams = (params?: { [key: string]: string | number }) => {
|
||||
const updateParams = (params?: Record<string, string | number>) => {
|
||||
return !params ? pathname : buildUrl(pathname, { ...query, ...params });
|
||||
};
|
||||
|
||||
const renderUrl = (path: string, params?: { [key: string]: string | number } | false) => {
|
||||
const renderUrl = (path: string, params?: Record<string, string | number> | false) => {
|
||||
return buildUrl(
|
||||
teamId ? `/teams/${teamId}${path}` : path,
|
||||
params === false ? {} : { ...query, ...params },
|
||||
|
|
|
|||
|
|
@ -1,30 +1,27 @@
|
|||
import { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useState } from 'react';
|
||||
import { PageResult, PageParams, PagedQueryResult } from '@/lib/types';
|
||||
import { useApi } from './useApi';
|
||||
import { useNavigation } from './useNavigation';
|
||||
|
||||
export function usePagedQuery<T = any>({
|
||||
export function usePagedQuery({
|
||||
queryKey,
|
||||
queryFn,
|
||||
...options
|
||||
}: Omit<UseQueryOptions, 'queryFn'> & { queryFn: (params?: object) => any }): PagedQueryResult<T> {
|
||||
}: Omit<UseQueryOptions, 'queryFn'> & { queryFn: (params?: object) => any }) {
|
||||
const { query: queryParams } = useNavigation();
|
||||
const [params, setParams] = useState<PageParams>({
|
||||
search: '',
|
||||
page: queryParams?.page || '1',
|
||||
const [params, setParams] = useState({
|
||||
search: queryParams?.search ?? '',
|
||||
page: queryParams?.page ?? '1',
|
||||
});
|
||||
|
||||
const { useQuery } = useApi();
|
||||
const { data, ...query } = useQuery({
|
||||
queryKey: [{ ...queryKey, ...params }],
|
||||
queryFn: () => queryFn(params as any),
|
||||
...options,
|
||||
});
|
||||
|
||||
return {
|
||||
result: data as PageResult<T>,
|
||||
query,
|
||||
...useQuery({
|
||||
queryKey: [{ ...queryKey, ...params }],
|
||||
queryFn: () => queryFn(params),
|
||||
...options,
|
||||
}),
|
||||
params,
|
||||
setParams,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { useState } from 'react';
|
||||
import { Select, ListItem } from '@umami/react-zen';
|
||||
import { useWebsites, useMessages } from '@/components/hooks';
|
||||
import type { SelectProps } from '@umami/react-zen/Select';
|
||||
import { Select, SelectProps, ListItem } from '@umami/react-zen';
|
||||
import { useWebsitesQuery, useMessages } from '@/components/hooks';
|
||||
|
||||
export function WebsiteSelect({
|
||||
websiteId,
|
||||
|
|
@ -12,14 +11,14 @@ export function WebsiteSelect({
|
|||
}: {
|
||||
websiteId?: string;
|
||||
teamId?: string;
|
||||
variant?: 'primary' | 'secondary' | 'outline' | 'quiet' | 'danger' | 'zero';
|
||||
variant?: 'primary' | 'outline' | 'quiet' | 'danger' | 'zero';
|
||||
onSelect?: (key: any) => void;
|
||||
} & SelectProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const [search, setSearch] = useState('');
|
||||
const [selectedId, setSelectedId] = useState(websiteId);
|
||||
|
||||
const queryResult = useWebsites({ teamId }, { search, pageSize: 5 });
|
||||
const { data, isLoading } = useWebsitesQuery({ teamId }, { search, pageSize: 5 });
|
||||
|
||||
const handleSelect = (value: any) => {
|
||||
setSelectedId(value);
|
||||
|
|
@ -30,15 +29,17 @@ export function WebsiteSelect({
|
|||
setSearch(value);
|
||||
};
|
||||
|
||||
const items = queryResult?.result?.data as any[];
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
{...props}
|
||||
items={items}
|
||||
items={data?.['data'] || []}
|
||||
value={selectedId}
|
||||
placeholder={formatMessage(labels.selectWebsite)}
|
||||
isLoading={queryResult.query.isLoading}
|
||||
isLoading={isLoading}
|
||||
buttonProps={{ variant }}
|
||||
allowSearch={true}
|
||||
searchValue={search}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ export interface MetricCardProps {
|
|||
formatValue?: (n: any) => string;
|
||||
showLabel?: boolean;
|
||||
showChange?: boolean;
|
||||
showPrevious?: boolean;
|
||||
}
|
||||
|
||||
export const MetricCard = ({
|
||||
|
|
@ -24,13 +23,11 @@ export const MetricCard = ({
|
|||
formatValue = formatNumber,
|
||||
showLabel = true,
|
||||
showChange = false,
|
||||
showPrevious = false,
|
||||
}: MetricCardProps) => {
|
||||
const diff = value - change;
|
||||
const pct = ((value - diff) / diff) * 100;
|
||||
const props = useSpring({ x: Number(value) || 0, from: { x: 0 } });
|
||||
const changeProps = useSpring({ x: Number(pct) || 0, from: { x: 0 } });
|
||||
const prevProps = useSpring({ x: Number(diff) || 0, from: { x: 0 } });
|
||||
|
||||
return (
|
||||
<Column
|
||||
|
|
@ -54,9 +51,6 @@ export const MetricCard = ({
|
|||
<AnimatedDiv>{changeProps?.x?.to(x => `${Math.abs(~~x)}%`)}</AnimatedDiv>
|
||||
</ChangeLabel>
|
||||
)}
|
||||
{showPrevious && (
|
||||
<AnimatedDiv title={diff.toString()}>{prevProps?.x?.to(x => formatValue(x))}</AnimatedDiv>
|
||||
)}
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export interface MetricsTableProps extends ListTableProps {
|
|||
allowSearch?: boolean;
|
||||
searchFormattedValues?: boolean;
|
||||
showMore?: boolean;
|
||||
params?: { [key: string]: any };
|
||||
params?: Record<string, any>;
|
||||
onDataLoad?: (data: any) => any;
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
|
|
|
|||
|
|
@ -2,11 +2,9 @@ import { ClickHouseClient, createClient } from '@clickhouse/client';
|
|||
import { formatInTimeZone } from 'date-fns-tz';
|
||||
import debug from 'debug';
|
||||
import { CLICKHOUSE } from '@/lib/db';
|
||||
import { getWebsite } from '@/queries';
|
||||
import { DEFAULT_PAGE_SIZE, OPERATORS } from './constants';
|
||||
import { maxDate } from './date';
|
||||
import { filtersToArray } from './params';
|
||||
import { PageParams, QueryFilters, QueryOptions } from './types';
|
||||
import { QueryFilters, QueryOptions } from './types';
|
||||
|
||||
export const CLICKHOUSE_DATE_FORMATS = {
|
||||
utc: '%Y-%m-%dT%H:%i:%SZ',
|
||||
|
|
@ -89,7 +87,7 @@ function mapFilter(column: string, operator: string, name: string, type: string
|
|||
}
|
||||
}
|
||||
|
||||
function getFilterQuery(filters: QueryFilters = {}, options: QueryOptions = {}) {
|
||||
function getFilterQuery(filters: Record<string, any>, options: QueryOptions = {}) {
|
||||
const query = filtersToArray(filters, options).reduce((arr, { name, column, operator }) => {
|
||||
if (column) {
|
||||
arr.push(`and ${mapFilter(column, operator, name)}`);
|
||||
|
|
@ -105,7 +103,7 @@ function getFilterQuery(filters: QueryFilters = {}, options: QueryOptions = {})
|
|||
return query.join('\n');
|
||||
}
|
||||
|
||||
function getDateQuery(filters: QueryFilters = {}) {
|
||||
function getDateQuery(filters: Record<string, any>) {
|
||||
const { startDate, endDate, timezone } = filters;
|
||||
|
||||
if (startDate) {
|
||||
|
|
@ -125,36 +123,33 @@ function getDateQuery(filters: QueryFilters = {}) {
|
|||
return '';
|
||||
}
|
||||
|
||||
function getFilterParams(filters: QueryFilters = {}) {
|
||||
return filtersToArray(filters).reduce((obj, { name, value }) => {
|
||||
if (name && value !== undefined) {
|
||||
obj[name] = value;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
|
||||
async function parseFilters(websiteId: string, filters: QueryFilters = {}, options?: QueryOptions) {
|
||||
const website = await getWebsite(websiteId);
|
||||
|
||||
function getQueryParams(filters: Record<string, any>) {
|
||||
return {
|
||||
filterQuery: getFilterQuery(filters, options),
|
||||
dateQuery: getDateQuery(filters),
|
||||
filterParams: {
|
||||
...getFilterParams(filters),
|
||||
websiteId,
|
||||
startDate: maxDate(filters.startDate, new Date(website?.resetAt)),
|
||||
},
|
||||
...filters,
|
||||
...filtersToArray(filters).reduce((obj, { name, value }) => {
|
||||
if (name && value !== undefined) {
|
||||
obj[name] = value;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}, {}),
|
||||
};
|
||||
}
|
||||
|
||||
async function pagedQuery(
|
||||
async function parseFilters(filters: Record<string, any>, options?: QueryOptions) {
|
||||
return {
|
||||
filterQuery: getFilterQuery(filters, options),
|
||||
dateQuery: getDateQuery(filters),
|
||||
queryParams: getQueryParams(filters),
|
||||
};
|
||||
}
|
||||
|
||||
async function pagedRawQuery(
|
||||
query: string,
|
||||
queryParams: { [key: string]: any },
|
||||
pageParams: PageParams = {},
|
||||
queryParams: Record<string, any>,
|
||||
filters: QueryFilters,
|
||||
) {
|
||||
const { page = 1, pageSize, orderBy, sortDescending = false } = pageParams;
|
||||
const { page = 1, pageSize, orderBy, sortDescending = false } = filters;
|
||||
const size = +pageSize || DEFAULT_PAGE_SIZE;
|
||||
const offset = +size * (+page - 1);
|
||||
const direction = sortDescending ? 'desc' : 'asc';
|
||||
|
|
@ -236,7 +231,7 @@ export default {
|
|||
getFilterQuery,
|
||||
getUTCString,
|
||||
parseFilters,
|
||||
pagedQuery,
|
||||
pagedRawQuery,
|
||||
findUnique,
|
||||
findFirst,
|
||||
rawQuery,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export const DEFAULT_ANIMATION_DURATION = 300;
|
|||
export const DEFAULT_DATE_RANGE_VALUE = '24hour';
|
||||
export const DEFAULT_WEBSITE_LIMIT = 10;
|
||||
export const DEFAULT_RESET_DATE = '2000-01-01';
|
||||
export const DEFAULT_PAGE_SIZE = 10;
|
||||
export const DEFAULT_PAGE_SIZE = 20;
|
||||
export const DEFAULT_DATE_COMPARE = 'prev';
|
||||
|
||||
export const REALTIME_RANGE = 30;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { DATA_TYPE, DATETIME_REGEX } from './constants';
|
|||
import { DynamicDataType } from './types';
|
||||
|
||||
export function flattenJSON(
|
||||
eventData: { [key: string]: any },
|
||||
eventData: Record<string, any>,
|
||||
keyValues: { key: string; value: any; dataType: DynamicDataType }[] = [],
|
||||
parentKey = '',
|
||||
): { key: string; value: any; dataType: DynamicDataType }[] {
|
||||
|
|
|
|||
|
|
@ -292,9 +292,13 @@ export function getCompareDate(compare: string, startDate: Date, endDate: Date)
|
|||
return { startDate: subYears(startDate, 1), endDate: subYears(endDate, 1) };
|
||||
}
|
||||
|
||||
const diff = differenceInMinutes(endDate, startDate);
|
||||
if (compare === 'prev') {
|
||||
const diff = differenceInMinutes(endDate, startDate);
|
||||
|
||||
return { startDate: subMinutes(startDate, diff), endDate: subMinutes(endDate, diff) };
|
||||
return { startDate: subMinutes(startDate, diff), endDate: subMinutes(endDate, diff) };
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
export function getDayOfWeekAsDate(dayOfWeek: number) {
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ async function getProducer(): Promise<Producer> {
|
|||
|
||||
async function sendMessage(
|
||||
topic: string,
|
||||
message: { [key: string]: string | number } | { [key: string]: string | number }[],
|
||||
message: Record<string, string | number> | Record<string, string | number>[],
|
||||
): Promise<RecordMetadata[]> {
|
||||
try {
|
||||
await connect();
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export function isSearchOperator(operator: any) {
|
|||
return [OPERATORS.contains, OPERATORS.doesNotContain].includes(operator);
|
||||
}
|
||||
|
||||
export function filtersToArray(filters: QueryFilters = {}, options: QueryOptions = {}) {
|
||||
export function filtersToArray(filters: QueryFilters, options: QueryOptions = {}) {
|
||||
return Object.keys(filters).reduce((arr, key) => {
|
||||
const filter = filters[key];
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,9 @@ import debug from 'debug';
|
|||
import { PrismaClient } from '@/generated/prisma/client';
|
||||
import { PrismaPg } from '@prisma/adapter-pg';
|
||||
import { readReplicas } from '@prisma/extension-read-replicas';
|
||||
import { formatInTimeZone } from 'date-fns-tz';
|
||||
import { MYSQL, POSTGRESQL, getDatabaseType } from '@/lib/db';
|
||||
import { SESSION_COLUMNS, OPERATORS, DEFAULT_PAGE_SIZE } from './constants';
|
||||
import { fetchWebsite } from './load';
|
||||
import { maxDate } from './date';
|
||||
import { QueryFilters, QueryOptions, PageParams } from './types';
|
||||
import { QueryOptions, QueryFilters } from './types';
|
||||
import { filtersToArray } from './params';
|
||||
|
||||
const log = debug('umami:prisma');
|
||||
|
|
@ -22,14 +19,6 @@ const PRISMA_LOG_OPTIONS = {
|
|||
],
|
||||
};
|
||||
|
||||
const MYSQL_DATE_FORMATS = {
|
||||
minute: '%Y-%m-%dT%H:%i:00',
|
||||
hour: '%Y-%m-%d %H:00:00',
|
||||
day: '%Y-%m-%d 00:00:00',
|
||||
month: '%Y-%m-01 00:00:00',
|
||||
year: '%Y-01-01 00:00:00',
|
||||
};
|
||||
|
||||
const POSTGRESQL_DATE_FORMATS = {
|
||||
minute: 'YYYY-MM-DD HH24:MI:00',
|
||||
hour: 'YYYY-MM-DD HH24:00:00',
|
||||
|
|
@ -75,59 +64,23 @@ function getCastColumnQuery(field: string, type: string): string {
|
|||
}
|
||||
|
||||
function getDateSQL(field: string, unit: string, timezone?: string): string {
|
||||
const db = getDatabaseType();
|
||||
|
||||
if (db === POSTGRESQL) {
|
||||
if (timezone) {
|
||||
return `to_char(date_trunc('${unit}', ${field} at time zone '${timezone}'), '${POSTGRESQL_DATE_FORMATS[unit]}')`;
|
||||
}
|
||||
return `to_char(date_trunc('${unit}', ${field}), '${POSTGRESQL_DATE_FORMATS[unit]}')`;
|
||||
if (timezone) {
|
||||
return `to_char(date_trunc('${unit}', ${field} at time zone '${timezone}'), '${POSTGRESQL_DATE_FORMATS[unit]}')`;
|
||||
}
|
||||
|
||||
if (db === MYSQL) {
|
||||
if (timezone) {
|
||||
const tz = formatInTimeZone(new Date(), timezone, 'xxx');
|
||||
return `date_format(convert_tz(${field},'+00:00','${tz}'), '${MYSQL_DATE_FORMATS[unit]}')`;
|
||||
}
|
||||
return `date_format(${field}, '${MYSQL_DATE_FORMATS[unit]}')`;
|
||||
}
|
||||
return `to_char(date_trunc('${unit}', ${field}), '${POSTGRESQL_DATE_FORMATS[unit]}')`;
|
||||
}
|
||||
|
||||
function getDateWeeklySQL(field: string, timezone?: string) {
|
||||
const db = getDatabaseType();
|
||||
|
||||
if (db === POSTGRESQL) {
|
||||
return `concat(extract(dow from (${field} at time zone '${timezone}')), ':', to_char((${field} at time zone '${timezone}'), 'HH24'))`;
|
||||
}
|
||||
|
||||
if (db === MYSQL) {
|
||||
const tz = formatInTimeZone(new Date(), timezone, 'xxx');
|
||||
return `date_format(convert_tz(${field},'+00:00','${tz}'), '%w:%H')`;
|
||||
}
|
||||
return `concat(extract(dow from (${field} at time zone '${timezone}')), ':', to_char((${field} at time zone '${timezone}'), 'HH24'))`;
|
||||
}
|
||||
|
||||
export function getTimestampSQL(field: string) {
|
||||
const db = getDatabaseType();
|
||||
|
||||
if (db === POSTGRESQL) {
|
||||
return `floor(extract(epoch from ${field}))`;
|
||||
}
|
||||
|
||||
if (db === MYSQL) {
|
||||
return `UNIX_TIMESTAMP(${field})`;
|
||||
}
|
||||
return `floor(extract(epoch from ${field}))`;
|
||||
}
|
||||
|
||||
function getTimestampDiffSQL(field1: string, field2: string): string {
|
||||
const db = getDatabaseType();
|
||||
|
||||
if (db === POSTGRESQL) {
|
||||
return `floor(extract(epoch from (${field2} - ${field1})))`;
|
||||
}
|
||||
|
||||
if (db === MYSQL) {
|
||||
return `timestampdiff(second, ${field1}, ${field2})`;
|
||||
}
|
||||
return `floor(extract(epoch from (${field2} - ${field1})))`;
|
||||
}
|
||||
|
||||
function getSearchSQL(column: string, param: string = 'search'): string {
|
||||
|
|
@ -156,7 +109,7 @@ function mapFilter(column: string, operator: string, name: string, type: string
|
|||
}
|
||||
}
|
||||
|
||||
function getFilterQuery(filters: QueryFilters = {}, options: QueryOptions = {}): string {
|
||||
function getFilterQuery(filters: Record<string, any>, options: QueryOptions = {}): string {
|
||||
const query = filtersToArray(filters, options).reduce((arr, { name, column, operator }) => {
|
||||
if (column) {
|
||||
arr.push(`and ${mapFilter(column, operator, name)}`);
|
||||
|
|
@ -174,7 +127,7 @@ function getFilterQuery(filters: QueryFilters = {}, options: QueryOptions = {}):
|
|||
return query.join('\n');
|
||||
}
|
||||
|
||||
function getDateQuery(filters: QueryFilters = {}) {
|
||||
function getDateQuery(filters: Record<string, any>) {
|
||||
const { startDate, endDate } = filters;
|
||||
|
||||
if (startDate) {
|
||||
|
|
@ -188,38 +141,32 @@ function getDateQuery(filters: QueryFilters = {}) {
|
|||
return '';
|
||||
}
|
||||
|
||||
function getFilterParams(filters: QueryFilters = {}) {
|
||||
return filtersToArray(filters).reduce((obj, { name, operator, value }) => {
|
||||
obj[name] = [OPERATORS.contains, OPERATORS.doesNotContain].includes(operator)
|
||||
? `%${value}%`
|
||||
: value;
|
||||
function getQueryParams(filters: Record<string, any>) {
|
||||
return {
|
||||
...filters,
|
||||
...filtersToArray(filters).reduce((obj, { name, operator, value }) => {
|
||||
obj[name] = [OPERATORS.contains, OPERATORS.doesNotContain].includes(operator)
|
||||
? `%${value}%`
|
||||
: value;
|
||||
|
||||
return obj;
|
||||
}, {});
|
||||
return obj;
|
||||
}, {}),
|
||||
};
|
||||
}
|
||||
|
||||
async function parseFilters(
|
||||
websiteId: string,
|
||||
filters: QueryFilters = {},
|
||||
options: QueryOptions = {},
|
||||
) {
|
||||
const website = await fetchWebsite(websiteId);
|
||||
async function parseFilters(filters: Record<string, any>, options?: QueryOptions) {
|
||||
const joinSession = Object.keys(filters).find(key =>
|
||||
['referrer', ...SESSION_COLUMNS].includes(key),
|
||||
);
|
||||
|
||||
return {
|
||||
joinSession:
|
||||
joinSessionQuery:
|
||||
options?.joinSession || joinSession
|
||||
? `inner join session on website_event.session_id = session.session_id`
|
||||
: '',
|
||||
filterQuery: getFilterQuery(filters, options),
|
||||
dateQuery: getDateQuery(filters),
|
||||
filterParams: {
|
||||
...getFilterParams(filters),
|
||||
websiteId,
|
||||
startDate: maxDate(filters.startDate, website?.resetAt),
|
||||
},
|
||||
filterQuery: getFilterQuery(filters, options),
|
||||
queryParams: getQueryParams(filters),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -251,8 +198,8 @@ async function rawQuery(sql: string, data: object): Promise<any> {
|
|||
: client.$queryRawUnsafe(query, ...params);
|
||||
}
|
||||
|
||||
async function pagedQuery<T>(model: string, criteria: T, pageParams: PageParams) {
|
||||
const { page = 1, pageSize, orderBy, sortDescending = false } = pageParams || {};
|
||||
async function pagedQuery<T>(model: string, criteria: T, filters?: QueryFilters) {
|
||||
const { page = 1, pageSize, orderBy, sortDescending = false } = filters || {};
|
||||
const size = +pageSize || DEFAULT_PAGE_SIZE;
|
||||
|
||||
const data = await client[model].findMany({
|
||||
|
|
@ -276,10 +223,10 @@ async function pagedQuery<T>(model: string, criteria: T, pageParams: PageParams)
|
|||
|
||||
async function pagedRawQuery(
|
||||
query: string,
|
||||
queryParams: { [key: string]: any },
|
||||
pageParams: PageParams = {},
|
||||
filters: QueryFilters,
|
||||
queryParams: Record<string, any>,
|
||||
) {
|
||||
const { page = 1, pageSize, orderBy, sortDescending = false } = pageParams;
|
||||
const { page = 1, pageSize, orderBy, sortDescending = false } = filters;
|
||||
const size = +pageSize || DEFAULT_PAGE_SIZE;
|
||||
const offset = +size * (+page - 1);
|
||||
const direction = sortDescending ? 'desc' : 'asc';
|
||||
|
|
@ -310,11 +257,11 @@ function getQueryMode(): { mode?: 'default' | 'insensitive' } {
|
|||
return {};
|
||||
}
|
||||
|
||||
function getSearchParameters(query: string, filters: { [key: string]: any }[]) {
|
||||
function getSearchParameters(query: string, filters: Record<string, any>[]) {
|
||||
if (!query) return;
|
||||
|
||||
const mode = getQueryMode();
|
||||
const parseFilter = (filter: { [key: string]: any }) => {
|
||||
const parseFilter = (filter: Record<string, any>) => {
|
||||
const [[key, value]] = Object.entries(filter);
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { z } from 'zod/v4';
|
||||
import { FILTER_COLUMNS } from '@/lib/constants';
|
||||
import { FILTER_COLUMNS, DEFAULT_PAGE_SIZE } from '@/lib/constants';
|
||||
import { badRequest, unauthorized } from '@/lib/response';
|
||||
import { getAllowedUnits, getCompareDate, getMinimumUnit } from '@/lib/date';
|
||||
import { getAllowedUnits, getCompareDate, getMinimumUnit, maxDate } from '@/lib/date';
|
||||
import { checkAuth } from '@/lib/auth';
|
||||
import { fetchWebsite } from '@/lib/load';
|
||||
import { QueryFilters } from '@/lib/types';
|
||||
|
||||
export async function parseRequest(
|
||||
request: Request,
|
||||
|
|
@ -47,8 +49,8 @@ export async function getJsonBody(request: Request) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function getRequestDateRange(query: Record<string, string>) {
|
||||
const { startAt, endAt, unit, compare } = query;
|
||||
export function getRequestDateRange(query: Record<string, string>) {
|
||||
const { startAt, endAt, unit, compare, timezone } = query;
|
||||
|
||||
const startDate = new Date(+startAt);
|
||||
const endDate = new Date(+endAt);
|
||||
|
|
@ -62,8 +64,10 @@ export async function getRequestDateRange(query: Record<string, string>) {
|
|||
return {
|
||||
startDate,
|
||||
endDate,
|
||||
compare,
|
||||
compareStartDate,
|
||||
compareEndDate,
|
||||
timezone,
|
||||
unit: getAllowedUnits(startDate, endDate).includes(unit)
|
||||
? unit
|
||||
: getMinimumUnit(startDate, endDate),
|
||||
|
|
@ -81,3 +85,30 @@ export function getRequestFilters(query: Record<string, any>) {
|
|||
return obj;
|
||||
}, {});
|
||||
}
|
||||
|
||||
export async function getQueryFilters(params: Record<string, any>): Promise<QueryFilters> {
|
||||
const dateRange = getRequestDateRange(params);
|
||||
const filters = getRequestFilters(params);
|
||||
|
||||
const data = {
|
||||
...dateRange,
|
||||
...filters,
|
||||
page: params?.page,
|
||||
pageSize: params?.page ? params?.pageSize || DEFAULT_PAGE_SIZE : undefined,
|
||||
orderBy: params?.orderBy,
|
||||
sortDescending: params?.sortDescending,
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,22 @@ import { z } from 'zod';
|
|||
import { isValidTimezone } from '@/lib/date';
|
||||
import { UNIT_TYPES } from './constants';
|
||||
|
||||
export const timezoneParam = z.string().refine(value => isValidTimezone(value), {
|
||||
message: 'Invalid timezone',
|
||||
});
|
||||
|
||||
export const unitParam = z.string().refine(value => UNIT_TYPES.includes(value), {
|
||||
message: 'Invalid unit',
|
||||
});
|
||||
|
||||
export const dateRangeParams = {
|
||||
startAt: z.coerce.number(),
|
||||
endAt: z.coerce.number(),
|
||||
timezone: timezoneParam.optional(),
|
||||
unit: unitParam.optional(),
|
||||
compare: z.string().optional(),
|
||||
};
|
||||
|
||||
export const filterParams = {
|
||||
path: z.string().optional(),
|
||||
referrer: z.string().optional(),
|
||||
|
|
@ -22,17 +38,13 @@ export const filterParams = {
|
|||
export const pagingParams = {
|
||||
page: z.coerce.number().int().positive().optional(),
|
||||
pageSize: z.coerce.number().int().positive().optional(),
|
||||
orderBy: z.string().optional(),
|
||||
search: z.string().optional(),
|
||||
};
|
||||
|
||||
export const timezoneParam = z.string().refine(value => isValidTimezone(value), {
|
||||
message: 'Invalid timezone',
|
||||
});
|
||||
|
||||
export const unitParam = z.string().refine(value => UNIT_TYPES.includes(value), {
|
||||
message: 'Invalid unit',
|
||||
});
|
||||
export const sortingParams = {
|
||||
orderBy: z.string().optional(),
|
||||
sortDescending: z.string().optional(),
|
||||
};
|
||||
|
||||
export const userRoleParam = z.enum(['admin', 'user', 'view-only']);
|
||||
|
||||
|
|
@ -86,11 +98,11 @@ export const reportParms = {
|
|||
dateRange: z.object({
|
||||
startDate: z.coerce.date(),
|
||||
endDate: z.coerce.date(),
|
||||
num: z.coerce.number().optional(),
|
||||
offset: z.coerce.number().optional(),
|
||||
timezone: timezoneParam.optional(),
|
||||
unit: unitParam.optional(),
|
||||
value: z.string().optional(),
|
||||
compare: z.string().optional(),
|
||||
compareStartDate: z.coerce.date().optional(),
|
||||
compareEndDate: z.coerce.date().optional(),
|
||||
}),
|
||||
};
|
||||
|
||||
|
|
@ -191,7 +203,7 @@ export const reportSchema = z.intersection(reportBaseSchema, reportTypeSchema);
|
|||
export const reportResultSchema = z.intersection(
|
||||
z.object({
|
||||
...reportParms,
|
||||
...filterParams,
|
||||
filters: z.object({ ...filterParams }),
|
||||
}),
|
||||
reportTypeSchema,
|
||||
);
|
||||
|
|
|
|||
119
src/lib/types.ts
119
src/lib/types.ts
|
|
@ -1,45 +1,16 @@
|
|||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { DATA_TYPE, PERMISSIONS, ROLES } from './constants';
|
||||
import { TIME_UNIT } from './date';
|
||||
|
||||
export type ObjectValues<T> = T[keyof T];
|
||||
|
||||
export type ReactQueryOptions<T> = Omit<UseQueryOptions<T, Error, T>, 'queryKey' | 'queryFn'>;
|
||||
export type ReactQueryOptions<T = any> = Omit<UseQueryOptions<T, Error, T>, 'queryKey' | 'queryFn'>;
|
||||
|
||||
export type TimeUnit = ObjectValues<typeof TIME_UNIT>;
|
||||
export type Permission = ObjectValues<typeof PERMISSIONS>;
|
||||
export type Role = ObjectValues<typeof ROLES>;
|
||||
export type DynamicDataType = ObjectValues<typeof DATA_TYPE>;
|
||||
|
||||
export interface PageParams {
|
||||
search?: string;
|
||||
page?: string | number;
|
||||
pageSize?: string;
|
||||
orderBy?: string;
|
||||
sortDescending?: boolean;
|
||||
}
|
||||
|
||||
export interface PageResult<T> {
|
||||
data: T;
|
||||
count: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
orderBy?: string;
|
||||
sortDescending?: boolean;
|
||||
}
|
||||
|
||||
export interface PagedQueryResult<T = any> {
|
||||
result: PageResult<T>;
|
||||
query: any;
|
||||
params: PageParams;
|
||||
setParams: Dispatch<SetStateAction<T | PageParams>>;
|
||||
}
|
||||
|
||||
export interface DynamicData {
|
||||
[key: string]: number | string | number[] | string[];
|
||||
}
|
||||
|
||||
export interface Auth {
|
||||
user?: {
|
||||
id: string;
|
||||
|
|
@ -54,20 +25,35 @@ export interface Auth {
|
|||
}
|
||||
|
||||
export interface DateRange {
|
||||
value: string;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
value?: string;
|
||||
unit?: TimeUnit;
|
||||
num?: number;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
export interface DynamicData {
|
||||
[key: string]: number | string | number[] | string[];
|
||||
}
|
||||
|
||||
export interface QueryOptions {
|
||||
joinSession?: boolean;
|
||||
columns?: Record<string, string>;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface QueryFilters {
|
||||
websiteId?: string;
|
||||
// Date range
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
timezone?: string;
|
||||
compareStartDate?: Date;
|
||||
compareEndDate?: Date;
|
||||
compare?: string;
|
||||
unit?: string;
|
||||
eventType?: number;
|
||||
timezone?: string;
|
||||
// Filters
|
||||
path?: string;
|
||||
referrer?: string;
|
||||
title?: string;
|
||||
|
|
@ -83,54 +69,29 @@ export interface QueryFilters {
|
|||
event?: string;
|
||||
search?: string;
|
||||
tag?: string;
|
||||
eventType?: number;
|
||||
// Paging
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
// Sorting
|
||||
orderBy?: string;
|
||||
sortDescending?: boolean;
|
||||
}
|
||||
|
||||
export interface QueryOptions {
|
||||
joinSession?: boolean;
|
||||
columns?: { [key: string]: string };
|
||||
limit?: number;
|
||||
export interface PageParams {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
orderBy?: string;
|
||||
sortDescending?: boolean;
|
||||
search?: string;
|
||||
}
|
||||
|
||||
export interface RealtimeData {
|
||||
countries: { [key: string]: number };
|
||||
events: any[];
|
||||
pageviews: any[];
|
||||
referrers: { [key: string]: number };
|
||||
timestamp: number;
|
||||
series: {
|
||||
views: any[];
|
||||
visitors: any[];
|
||||
};
|
||||
totals: {
|
||||
views: number;
|
||||
visitors: number;
|
||||
events: number;
|
||||
countries: number;
|
||||
};
|
||||
urls: { [key: string]: number };
|
||||
visitors: any[];
|
||||
}
|
||||
|
||||
export interface SessionData {
|
||||
id: string;
|
||||
websiteId: string;
|
||||
visitId: string;
|
||||
hostname: string;
|
||||
browser: string;
|
||||
os: string;
|
||||
device: string;
|
||||
screen: string;
|
||||
language: string;
|
||||
country: string;
|
||||
region: string;
|
||||
city: string;
|
||||
ip?: string;
|
||||
userAgent?: string;
|
||||
}
|
||||
|
||||
export interface InputItem {
|
||||
id: string;
|
||||
label: string;
|
||||
icon: any;
|
||||
seperator?: boolean;
|
||||
export interface PageResult<T> {
|
||||
data: T;
|
||||
count: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
orderBy?: string;
|
||||
sortDescending?: boolean;
|
||||
search?: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Prisma, Report } from '@/generated/prisma/client';
|
||||
import prisma from '@/lib/prisma';
|
||||
import { PageResult, PageParams } from '@/lib/types';
|
||||
import { PageResult, QueryFilters } from '@/lib/types';
|
||||
import ReportFindManyArgs = Prisma.ReportFindManyArgs;
|
||||
|
||||
async function findReport(criteria: Prisma.ReportFindUniqueArgs): Promise<Report> {
|
||||
|
|
@ -17,9 +17,9 @@ export async function getReport(reportId: string): Promise<Report> {
|
|||
|
||||
export async function getReports(
|
||||
criteria: ReportFindManyArgs,
|
||||
pageParams: PageParams = {},
|
||||
filters: QueryFilters = {},
|
||||
): Promise<PageResult<Report[]>> {
|
||||
const { search } = pageParams;
|
||||
const { search } = filters;
|
||||
|
||||
const where: Prisma.ReportWhereInput = {
|
||||
...criteria.where,
|
||||
|
|
@ -45,12 +45,12 @@ export async function getReports(
|
|||
]),
|
||||
};
|
||||
|
||||
return prisma.pagedQuery('report', { ...criteria, where }, pageParams);
|
||||
return prisma.pagedQuery('report', { ...criteria, where }, filters);
|
||||
}
|
||||
|
||||
export async function getUserReports(
|
||||
userId: string,
|
||||
filters?: PageParams,
|
||||
filters?: QueryFilters,
|
||||
): Promise<PageResult<Report[]>> {
|
||||
return getReports(
|
||||
{
|
||||
|
|
@ -72,7 +72,7 @@ export async function getUserReports(
|
|||
|
||||
export async function getWebsiteReports(
|
||||
websiteId: string,
|
||||
filters: PageParams = {},
|
||||
filters: QueryFilters = {},
|
||||
): Promise<PageResult<Report[]>> {
|
||||
return getReports(
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Prisma, Team } from '@/generated/prisma/client';
|
|||
import { ROLES } from '@/lib/constants';
|
||||
import { uuid } from '@/lib/crypto';
|
||||
import prisma from '@/lib/prisma';
|
||||
import { PageResult, PageParams } from '@/lib/types';
|
||||
import { PageResult, QueryFilters } from '@/lib/types';
|
||||
import TeamFindManyArgs = Prisma.TeamFindManyArgs;
|
||||
|
||||
export async function findTeam(criteria: Prisma.TeamFindUniqueArgs): Promise<Team> {
|
||||
|
|
@ -22,7 +22,7 @@ export async function getTeam(teamId: string, options: { includeMembers?: boolea
|
|||
|
||||
export async function getTeams(
|
||||
criteria: TeamFindManyArgs,
|
||||
filters: PageParams = {},
|
||||
filters: QueryFilters,
|
||||
): Promise<PageResult<Team[]>> {
|
||||
const { getSearchParameters } = prisma;
|
||||
const { search } = filters;
|
||||
|
|
@ -42,7 +42,7 @@ export async function getTeams(
|
|||
);
|
||||
}
|
||||
|
||||
export async function getUserTeams(userId: string, filters: PageParams = {}) {
|
||||
export async function getUserTeams(userId: string, filters: QueryFilters) {
|
||||
return getTeams(
|
||||
{
|
||||
where: {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Prisma, TeamUser } from '@/generated/prisma/client';
|
||||
import { uuid } from '@/lib/crypto';
|
||||
import prisma from '@/lib/prisma';
|
||||
import { PageResult, PageParams } from '@/lib/types';
|
||||
import { PageResult, QueryFilters } from '@/lib/types';
|
||||
import TeamUserFindManyArgs = Prisma.TeamUserFindManyArgs;
|
||||
|
||||
export async function findTeamUser(criteria: Prisma.TeamUserFindUniqueArgs): Promise<TeamUser> {
|
||||
|
|
@ -19,7 +19,7 @@ export async function getTeamUser(teamId: string, userId: string): Promise<TeamU
|
|||
|
||||
export async function getTeamUsers(
|
||||
criteria: TeamUserFindManyArgs,
|
||||
filters?: PageParams,
|
||||
filters?: QueryFilters,
|
||||
): Promise<PageResult<TeamUser[]>> {
|
||||
const { search } = filters;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Prisma, User } from '@/generated/prisma/client';
|
||||
import { ROLES } from '@/lib/constants';
|
||||
import prisma from '@/lib/prisma';
|
||||
import { PageResult, Role, PageParams } from '@/lib/types';
|
||||
import { PageResult, Role, QueryFilters } from '@/lib/types';
|
||||
import { getRandomChars } from '@/lib/crypto';
|
||||
import UserFindManyArgs = Prisma.UserFindManyArgs;
|
||||
|
||||
|
|
@ -49,9 +49,9 @@ export async function getUserByUsername(username: string, options: GetUserOption
|
|||
|
||||
export async function getUsers(
|
||||
criteria: UserFindManyArgs,
|
||||
pageParams?: PageParams,
|
||||
filters: QueryFilters = {},
|
||||
): Promise<PageResult<User[]>> {
|
||||
const { search } = pageParams;
|
||||
const { search } = filters;
|
||||
|
||||
const where: Prisma.UserWhereInput = {
|
||||
...criteria.where,
|
||||
|
|
@ -68,7 +68,7 @@ export async function getUsers(
|
|||
{
|
||||
orderBy: 'createdAt',
|
||||
sortDescending: true,
|
||||
...pageParams,
|
||||
...filters,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Prisma, Website } from '@/generated/prisma/client';
|
||||
import redis from '@/lib/redis';
|
||||
import prisma from '@/lib/prisma';
|
||||
import { PageResult, PageParams } from '@/lib/types';
|
||||
import { PageResult, QueryFilters } from '@/lib/types';
|
||||
import WebsiteFindManyArgs = Prisma.WebsiteFindManyArgs;
|
||||
import { ROLES } from '@/lib/constants';
|
||||
|
||||
|
|
@ -28,9 +28,9 @@ export async function getSharedWebsite(shareId: string) {
|
|||
|
||||
export async function getWebsites(
|
||||
criteria: WebsiteFindManyArgs,
|
||||
pageParams: PageParams,
|
||||
filters: QueryFilters,
|
||||
): Promise<PageResult<Website[]>> {
|
||||
const { search } = pageParams;
|
||||
const { search } = filters;
|
||||
|
||||
const where: Prisma.WebsiteWhereInput = {
|
||||
...criteria.where,
|
||||
|
|
@ -43,7 +43,7 @@ export async function getWebsites(
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
return prisma.pagedQuery('website', { ...criteria, where }, pageParams);
|
||||
return prisma.pagedQuery('website', { ...criteria, where }, filters);
|
||||
}
|
||||
|
||||
export async function getAllWebsites(userId: string) {
|
||||
|
|
@ -90,7 +90,7 @@ export async function getAllUserWebsitesIncludingTeamOwner(userId: string) {
|
|||
|
||||
export async function getUserWebsites(
|
||||
userId: string,
|
||||
filters?: PageParams,
|
||||
filters?: QueryFilters,
|
||||
): Promise<PageResult<Website[]>> {
|
||||
return getWebsites(
|
||||
{
|
||||
|
|
@ -115,7 +115,7 @@ export async function getUserWebsites(
|
|||
|
||||
export async function getTeamWebsites(
|
||||
teamId: string,
|
||||
filters?: PageParams,
|
||||
filters?: QueryFilters,
|
||||
): Promise<PageResult<Website[]>> {
|
||||
return getWebsites(
|
||||
{
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export async function getEventDataEvents(
|
|||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||
const { rawQuery, parseFilters } = prisma;
|
||||
const { event } = filters;
|
||||
const { filterParams } = await parseFilters(websiteId, filters);
|
||||
const { queryParams } = await parseFilters(filters);
|
||||
|
||||
if (event) {
|
||||
return rawQuery(
|
||||
|
|
@ -43,7 +43,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
|||
group by website_event.event_name, event_data.data_key, event_data.data_type, event_data.string_value
|
||||
order by 1 asc, 2 asc, 3 asc, 5 desc
|
||||
`,
|
||||
filterParams,
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
|||
order by 1 asc, 2 asc
|
||||
limit 500
|
||||
`,
|
||||
filterParams,
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ async function clickhouseQuery(
|
|||
): Promise<{ eventName: string; propertyName: string; dataType: number; total: number }[]> {
|
||||
const { rawQuery, parseFilters } = clickhouse;
|
||||
const { event } = filters;
|
||||
const { filterParams } = await parseFilters(websiteId, filters);
|
||||
const { queryParams } = await parseFilters(filters);
|
||||
|
||||
if (event) {
|
||||
return rawQuery(
|
||||
|
|
@ -92,7 +92,7 @@ async function clickhouseQuery(
|
|||
order by 1 asc, 2 asc, 3 asc, 5 desc
|
||||
limit 500
|
||||
`,
|
||||
filterParams,
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -110,6 +110,6 @@ async function clickhouseQuery(
|
|||
order by 1 asc, 2 asc
|
||||
limit 500
|
||||
`,
|
||||
filterParams,
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export async function getEventDataFields(...args: [websiteId: string, filters: Q
|
|||
|
||||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||
const { rawQuery, parseFilters, getDateSQL } = prisma;
|
||||
const { filterQuery, filterParams } = await parseFilters(websiteId, filters);
|
||||
const { filterQuery, queryParams } = await parseFilters(filters);
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
|
|
@ -34,7 +34,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
|||
order by 2 desc
|
||||
limit 100
|
||||
`,
|
||||
filterParams,
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ async function clickhouseQuery(
|
|||
filters: QueryFilters,
|
||||
): Promise<{ propertyName: string; dataType: number; propertyValue: string; total: number }[]> {
|
||||
const { rawQuery, parseFilters } = clickhouse;
|
||||
const { filterQuery, filterParams } = await parseFilters(websiteId, filters);
|
||||
const { filterQuery, queryParams } = await parseFilters(filters);
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
|
|
@ -62,6 +62,6 @@ async function clickhouseQuery(
|
|||
order by 2 desc
|
||||
limit 100
|
||||
`,
|
||||
filterParams,
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import prisma from '@/lib/prisma';
|
||||
import clickhouse from '@/lib/clickhouse';
|
||||
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
|
||||
import { QueryFilters, WebsiteEventData } from '@/lib/types';
|
||||
import { QueryFilters } from '@/lib/types';
|
||||
|
||||
export async function getEventDataProperties(
|
||||
...args: [websiteId: string, filters: QueryFilters & { propertyName?: string }]
|
||||
): Promise<WebsiteEventData[]> {
|
||||
) {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(...args),
|
||||
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
||||
|
|
@ -17,7 +17,7 @@ async function relationalQuery(
|
|||
filters: QueryFilters & { propertyName?: string },
|
||||
) {
|
||||
const { rawQuery, parseFilters } = prisma;
|
||||
const { filterQuery, filterParams } = await parseFilters(websiteId, filters, {
|
||||
const { filterQuery, queryParams } = await parseFilters(filters, {
|
||||
columns: { propertyName: 'data_key' },
|
||||
});
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ async function relationalQuery(
|
|||
order by 3 desc
|
||||
limit 500
|
||||
`,
|
||||
filterParams,
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ async function clickhouseQuery(
|
|||
filters: QueryFilters & { propertyName?: string },
|
||||
): Promise<{ eventName: string; propertyName: string; total: number }[]> {
|
||||
const { rawQuery, parseFilters } = clickhouse;
|
||||
const { filterQuery, filterParams } = await parseFilters(websiteId, filters, {
|
||||
const { filterQuery, queryParams } = await parseFilters(filters, {
|
||||
columns: { propertyName: 'data_key' },
|
||||
});
|
||||
|
||||
|
|
@ -63,6 +63,6 @@ async function clickhouseQuery(
|
|||
order by 1, 3 desc
|
||||
limit 500
|
||||
`,
|
||||
filterParams,
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export async function getEventDataStats(
|
|||
|
||||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||
const { rawQuery, parseFilters } = prisma;
|
||||
const { filterQuery, filterParams } = await parseFilters(websiteId, filters);
|
||||
const { filterQuery, queryParams } = await parseFilters({ ...filters, websiteId });
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
|
|
@ -38,7 +38,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
|||
group by website_event_id, data_key
|
||||
) as t
|
||||
`,
|
||||
filterParams,
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ async function clickhouseQuery(
|
|||
filters: QueryFilters,
|
||||
): Promise<{ events: number; properties: number; records: number }[]> {
|
||||
const { rawQuery, parseFilters } = clickhouse;
|
||||
const { filterQuery, filterParams } = await parseFilters(websiteId, filters);
|
||||
const { filterQuery, queryParams } = await parseFilters({ ...filters, websiteId });
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
|
|
@ -67,6 +67,6 @@ async function clickhouseQuery(
|
|||
group by event_id, data_key
|
||||
) as t
|
||||
`,
|
||||
filterParams,
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import clickhouse from '@/lib/clickhouse';
|
||||
import { CLICKHOUSE, PRISMA, runQuery, notImplemented } from '@/lib/db';
|
||||
import { QueryFilters } from '@/lib/types';
|
||||
|
||||
export function getEventDataUsage(...args: [websiteIds: string[], startDate: Date, endDate: Date]) {
|
||||
export function getEventDataUsage(...args: [websiteIds: string[], filters: QueryFilters]) {
|
||||
return runQuery({
|
||||
[PRISMA]: notImplemented,
|
||||
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
||||
|
|
@ -10,10 +11,10 @@ export function getEventDataUsage(...args: [websiteIds: string[], startDate: Dat
|
|||
|
||||
function clickhouseQuery(
|
||||
websiteIds: string[],
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
filters: QueryFilters,
|
||||
): Promise<{ websiteId: string; count: number }[]> {
|
||||
const { rawQuery } = clickhouse;
|
||||
const { startDate, endDate } = filters;
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ async function relationalQuery(
|
|||
filters: QueryFilters & { eventName?: string; propertyName?: string },
|
||||
) {
|
||||
const { rawQuery, parseFilters, getDateSQL } = prisma;
|
||||
const { filterQuery, filterParams } = await parseFilters(websiteId, filters);
|
||||
const { filterQuery, queryParams } = await parseFilters({ ...filters, websiteId });
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
|
|
@ -47,7 +47,7 @@ async function relationalQuery(
|
|||
order by 2 desc
|
||||
limit 100
|
||||
`,
|
||||
filterParams,
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ async function clickhouseQuery(
|
|||
filters: QueryFilters & { eventName?: string; propertyName?: string },
|
||||
): Promise<{ value: string; total: number }[]> {
|
||||
const { rawQuery, parseFilters } = clickhouse;
|
||||
const { filterQuery, filterParams } = await parseFilters(websiteId, filters);
|
||||
const { filterQuery, queryParams } = await parseFilters({ ...filters, websiteId });
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
|
|
@ -75,6 +75,6 @@ async function clickhouseQuery(
|
|||
order by 2 desc
|
||||
limit 100
|
||||
`,
|
||||
filterParams,
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export async function getEventMetrics(
|
|||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||
const { timezone = 'utc', unit = 'day' } = filters;
|
||||
const { rawQuery, getDateSQL, parseFilters } = prisma;
|
||||
const { filterQuery, joinSession, filterParams } = await parseFilters(websiteId, {
|
||||
const { filterQuery, joinSessionQuery, queryParams } = await parseFilters({
|
||||
...filters,
|
||||
eventType: EVENT_TYPE.customEvent,
|
||||
});
|
||||
|
|
@ -34,7 +34,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
|||
${getDateSQL('website_event.created_at', unit, timezone)} t,
|
||||
count(*) y
|
||||
from website_event
|
||||
${joinSession}
|
||||
${joinSessionQuery}
|
||||
where website_event.website_id = {{websiteId::uuid}}
|
||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||
and event_type = {{eventType}}
|
||||
|
|
@ -42,7 +42,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
|||
group by 1, 2
|
||||
order by 2
|
||||
`,
|
||||
filterParams,
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ async function clickhouseQuery(
|
|||
): Promise<{ x: string; t: string; y: number }[]> {
|
||||
const { timezone = 'UTC', unit = 'day' } = filters;
|
||||
const { rawQuery, getDateSQL, parseFilters } = clickhouse;
|
||||
const { filterQuery, filterParams } = await parseFilters(websiteId, {
|
||||
const { filterQuery, queryParams } = await parseFilters({
|
||||
...filters,
|
||||
eventType: EVENT_TYPE.customEvent,
|
||||
});
|
||||
|
|
@ -92,5 +92,5 @@ async function clickhouseQuery(
|
|||
`;
|
||||
}
|
||||
|
||||
return rawQuery(sql, filterParams);
|
||||
return rawQuery(sql, queryParams);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import clickhouse from '@/lib/clickhouse';
|
||||
import { CLICKHOUSE, PRISMA, runQuery, notImplemented } from '@/lib/db';
|
||||
import { QueryFilters } from '@/lib/types';
|
||||
|
||||
export function getEventUsage(...args: [websiteIds: string[], startDate: Date, endDate: Date]) {
|
||||
export function getEventUsage(...args: [websiteIds: string[], filters: QueryFilters]) {
|
||||
return runQuery({
|
||||
[PRISMA]: notImplemented,
|
||||
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
||||
|
|
@ -10,10 +11,10 @@ export function getEventUsage(...args: [websiteIds: string[], startDate: Date, e
|
|||
|
||||
function clickhouseQuery(
|
||||
websiteIds: string[],
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
filters: QueryFilters,
|
||||
): Promise<{ websiteId: string; count: number }[]> {
|
||||
const { rawQuery } = clickhouse;
|
||||
const { startDate, endDate } = filters;
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
|
|
|
|||
|
|
@ -1,26 +1,27 @@
|
|||
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 { PageParams, QueryFilters } from '@/lib/types';
|
||||
import { QueryFilters } from '@/lib/types';
|
||||
|
||||
export function getWebsiteEvents(
|
||||
...args: [websiteId: string, filters: QueryFilters, pageParams?: PageParams]
|
||||
) {
|
||||
export function getWebsiteEvents(...args: [websiteId: string, filters: QueryFilters]) {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(...args),
|
||||
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
||||
});
|
||||
}
|
||||
|
||||
async function relationalQuery(websiteId: string, filters: QueryFilters, pageParams?: PageParams) {
|
||||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||
const { pagedRawQuery, parseFilters } = prisma;
|
||||
const { search } = pageParams;
|
||||
const { filterQuery, filterParams } = await parseFilters(websiteId, {
|
||||
const { search } = filters;
|
||||
const { filterQuery, queryParams } = await parseFilters({
|
||||
...filters,
|
||||
websiteId,
|
||||
});
|
||||
|
||||
const db = getDatabaseType();
|
||||
const like = db === POSTGRESQL ? 'ilike' : 'like';
|
||||
const searchQuery = filters.search
|
||||
? `and ((event_name ilike {{search}} and event_type = 2)
|
||||
or (url_path ilike {{search}} and event_type = 1))`
|
||||
: '';
|
||||
|
||||
return pagedRawQuery(
|
||||
`
|
||||
|
|
@ -41,25 +42,27 @@ async function relationalQuery(websiteId: string, filters: QueryFilters, pagePar
|
|||
where website_id = {{websiteId::uuid}}
|
||||
and created_at between {{startDate}} and {{endDate}}
|
||||
${filterQuery}
|
||||
${
|
||||
search
|
||||
? `and ((event_name ${like} {{search}} and event_type = 2)
|
||||
or (url_path ${like} {{search}} and event_type = 1))`
|
||||
: ''
|
||||
}
|
||||
${searchQuery}
|
||||
order by created_at desc
|
||||
`,
|
||||
{ ...filterParams, search: `%${search}%` },
|
||||
pageParams,
|
||||
{ ...queryParams, search: `%${search}%` },
|
||||
filters,
|
||||
);
|
||||
}
|
||||
|
||||
async function clickhouseQuery(websiteId: string, filters: QueryFilters, pageParams?: PageParams) {
|
||||
const { pagedQuery, parseFilters } = clickhouse;
|
||||
const { filterParams, dateQuery, filterQuery } = await parseFilters(websiteId, filters);
|
||||
const { search } = pageParams;
|
||||
async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
||||
const { pagedRawQuery, parseFilters } = clickhouse;
|
||||
const { queryParams, dateQuery, filterQuery } = await parseFilters({
|
||||
...filters,
|
||||
websiteId,
|
||||
});
|
||||
|
||||
return pagedQuery(
|
||||
const searchQuery = filters.search
|
||||
? `and ((positionCaseInsensitive(event_name, {search:String}) > 0 and event_type = 2)
|
||||
or (positionCaseInsensitive(url_path, {search:String}) > 0 and event_type = 1))`
|
||||
: '';
|
||||
|
||||
return pagedRawQuery(
|
||||
`
|
||||
select
|
||||
event_id as id,
|
||||
|
|
@ -78,15 +81,10 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters, pagePar
|
|||
where website_id = {websiteId:UUID}
|
||||
${dateQuery}
|
||||
${filterQuery}
|
||||
${
|
||||
search
|
||||
? `and ((positionCaseInsensitive(event_name, {search:String}) > 0 and event_type = 2)
|
||||
or (positionCaseInsensitive(url_path, {search:String}) > 0 and event_type = 1))`
|
||||
: ''
|
||||
}
|
||||
${searchQuery}
|
||||
order by created_at desc
|
||||
`,
|
||||
{ ...filterParams, search },
|
||||
pageParams,
|
||||
queryParams,
|
||||
filters,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export async function getChannelMetrics(...args: [websiteId: string, filters?: Q
|
|||
|
||||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||
const { rawQuery, parseFilters } = prisma;
|
||||
const { filterParams, filterQuery, dateQuery } = await parseFilters(websiteId, filters);
|
||||
const { queryParams, filterQuery, dateQuery } = await parseFilters(filters);
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
|
|
@ -27,7 +27,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
|||
group by 1, 2
|
||||
order by visitors desc
|
||||
`,
|
||||
filterParams,
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ async function clickhouseQuery(
|
|||
filters: QueryFilters,
|
||||
): Promise<{ x: string; y: number }[]> {
|
||||
const { rawQuery, parseFilters } = clickhouse;
|
||||
const { filterParams, filterQuery, dateQuery } = await parseFilters(websiteId, filters);
|
||||
const { queryParams, filterQuery, dateQuery } = await parseFilters(filters);
|
||||
|
||||
const sql = `
|
||||
select
|
||||
|
|
@ -51,5 +51,5 @@ async function clickhouseQuery(
|
|||
order by visitors desc
|
||||
`;
|
||||
|
||||
return rawQuery(sql, filterParams);
|
||||
return rawQuery(sql, queryParams);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export async function getRealtimeActivity(...args: [websiteId: string, filters:
|
|||
|
||||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||
const { rawQuery, parseFilters } = prisma;
|
||||
const { filterParams, filterQuery, dateQuery } = await parseFilters(websiteId, filters);
|
||||
const { queryParams, filterQuery, dateQuery } = await parseFilters(filters);
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
|
|
@ -35,13 +35,13 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
|||
order by website_event.created_at desc
|
||||
limit 100
|
||||
`,
|
||||
filterParams,
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
||||
async function clickhouseQuery(websiteId: string, filters: QueryFilters): Promise<{ x: number }> {
|
||||
const { rawQuery, parseFilters } = clickhouse;
|
||||
const { filterParams, filterQuery, dateQuery } = await parseFilters(websiteId, filters);
|
||||
const { queryParams, filterQuery, dateQuery } = await parseFilters(filters);
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
|
|
@ -62,6 +62,6 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters): Promis
|
|||
order by createdAt desc
|
||||
limit 100
|
||||
`,
|
||||
{ ...filters, ...filterParams },
|
||||
{ ...filters, ...queryParams },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { getPageviewStats, getRealtimeActivity, getSessionStats } from '@/queries';
|
||||
import { QueryFilters } from '@/lib/types';
|
||||
|
||||
function increment(data: object, key: string) {
|
||||
if (key) {
|
||||
|
|
@ -10,12 +11,7 @@ function increment(data: object, key: string) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function getRealtimeData(
|
||||
websiteId: string,
|
||||
criteria: { startDate: Date; timezone: string },
|
||||
) {
|
||||
const { startDate, timezone } = criteria;
|
||||
const filters = { startDate, endDate: new Date(), unit: 'minute', timezone };
|
||||
export async function getRealtimeData(websiteId: string, filters: QueryFilters) {
|
||||
const [activity, pageviews, sessions] = await Promise.all([
|
||||
getRealtimeActivity(websiteId, filters),
|
||||
getPageviewStats(websiteId, filters),
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import prisma from '@/lib/prisma';
|
||||
import clickhouse from '@/lib/clickhouse';
|
||||
import { runQuery, CLICKHOUSE, PRISMA } from '@/lib/db';
|
||||
import { QueryFilters } from '@/lib/types';
|
||||
|
||||
export async function getValues(
|
||||
...args: [websiteId: string, column: string, startDate: Date, endDate: Date, search: string]
|
||||
...args: [websiteId: string, column: string, filters: QueryFilters]
|
||||
) {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(...args),
|
||||
|
|
@ -11,15 +12,11 @@ export async function getValues(
|
|||
});
|
||||
}
|
||||
|
||||
async function relationalQuery(
|
||||
websiteId: string,
|
||||
column: string,
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
search: string,
|
||||
) {
|
||||
async function relationalQuery(websiteId: string, column: string, filters: QueryFilters) {
|
||||
const { rawQuery, getSearchSQL } = prisma;
|
||||
const params = {};
|
||||
const { startDate, endDate, search } = filters;
|
||||
|
||||
let searchQuery = '';
|
||||
|
||||
if (search) {
|
||||
|
|
@ -63,15 +60,11 @@ async function relationalQuery(
|
|||
);
|
||||
}
|
||||
|
||||
async function clickhouseQuery(
|
||||
websiteId: string,
|
||||
column: string,
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
search: string,
|
||||
) {
|
||||
async function clickhouseQuery(websiteId: string, column: string, filters: QueryFilters) {
|
||||
const { rawQuery, getSearchSQL } = clickhouse;
|
||||
const params = {};
|
||||
const { startDate, endDate, search } = filters;
|
||||
|
||||
let searchQuery = '';
|
||||
|
||||
if (search) {
|
||||
|
|
|
|||
|
|
@ -12,8 +12,9 @@ export async function getWebsiteDateRange(...args: [websiteId: string]) {
|
|||
|
||||
async function relationalQuery(websiteId: string) {
|
||||
const { rawQuery, parseFilters } = prisma;
|
||||
const { filterParams } = await parseFilters(websiteId, {
|
||||
const { queryParams } = await parseFilters({
|
||||
startDate: new Date(DEFAULT_RESET_DATE),
|
||||
websiteId,
|
||||
});
|
||||
|
||||
const result = await rawQuery(
|
||||
|
|
@ -25,7 +26,7 @@ async function relationalQuery(websiteId: string) {
|
|||
where website_id = {{websiteId::uuid}}
|
||||
and created_at >= {{startDate}}
|
||||
`,
|
||||
filterParams,
|
||||
queryParams,
|
||||
);
|
||||
|
||||
return result[0] ?? null;
|
||||
|
|
@ -33,8 +34,9 @@ async function relationalQuery(websiteId: string) {
|
|||
|
||||
async function clickhouseQuery(websiteId: string) {
|
||||
const { rawQuery, parseFilters } = clickhouse;
|
||||
const { filterParams } = await parseFilters(websiteId, {
|
||||
const { queryParams } = await parseFilters({
|
||||
startDate: new Date(DEFAULT_RESET_DATE),
|
||||
websiteId,
|
||||
});
|
||||
|
||||
const result = await rawQuery(
|
||||
|
|
@ -46,7 +48,7 @@ async function clickhouseQuery(websiteId: string) {
|
|||
where website_id = {websiteId:UUID}
|
||||
and created_at >= {startDate:DateTime64}
|
||||
`,
|
||||
filterParams,
|
||||
queryParams,
|
||||
);
|
||||
|
||||
return result[0] ?? null;
|
||||
|
|
|
|||
|
|
@ -5,11 +5,17 @@ import prisma from '@/lib/prisma';
|
|||
import { QueryFilters } from '@/lib/types';
|
||||
import { EVENT_COLUMNS } from '@/lib/constants';
|
||||
|
||||
export interface WebsiteStatsData {
|
||||
pageviews: number;
|
||||
visitors: number;
|
||||
visits: number;
|
||||
bounces: number;
|
||||
totaltime: number;
|
||||
}
|
||||
|
||||
export async function getWebsiteStats(
|
||||
...args: [websiteId: string, filters: QueryFilters]
|
||||
): Promise<
|
||||
{ pageviews: number; visitors: number; visits: number; bounces: number; totaltime: number }[]
|
||||
> {
|
||||
): Promise<WebsiteStatsData[]> {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(...args),
|
||||
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
||||
|
|
@ -19,12 +25,11 @@ export async function getWebsiteStats(
|
|||
async function relationalQuery(
|
||||
websiteId: string,
|
||||
filters: QueryFilters,
|
||||
): Promise<
|
||||
{ pageviews: number; visitors: number; visits: number; bounces: number; totaltime: number }[]
|
||||
> {
|
||||
): Promise<WebsiteStatsData[]> {
|
||||
const { getTimestampDiffSQL, parseFilters, rawQuery } = prisma;
|
||||
const { filterQuery, joinSession, filterParams } = await parseFilters(websiteId, {
|
||||
const { filterQuery, joinSessionQuery, queryParams } = await parseFilters({
|
||||
...filters,
|
||||
websiteId,
|
||||
eventType: EVENT_TYPE.pageView,
|
||||
});
|
||||
|
||||
|
|
@ -44,7 +49,7 @@ async function relationalQuery(
|
|||
min(website_event.created_at) as "min_time",
|
||||
max(website_event.created_at) as "max_time"
|
||||
from website_event
|
||||
${joinSession}
|
||||
${joinSessionQuery}
|
||||
where website_event.website_id = {{websiteId::uuid}}
|
||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||
and event_type = {{eventType}}
|
||||
|
|
@ -52,19 +57,18 @@ async function relationalQuery(
|
|||
group by 1, 2
|
||||
) as t
|
||||
`,
|
||||
filterParams,
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
||||
async function clickhouseQuery(
|
||||
websiteId: string,
|
||||
filters: QueryFilters,
|
||||
): Promise<
|
||||
{ pageviews: number; visitors: number; visits: number; bounces: number; totaltime: number }[]
|
||||
> {
|
||||
): Promise<WebsiteStatsData[]> {
|
||||
const { rawQuery, parseFilters } = clickhouse;
|
||||
const { filterQuery, filterParams } = await parseFilters(websiteId, {
|
||||
const { filterQuery, queryParams } = await parseFilters({
|
||||
...filters,
|
||||
websiteId,
|
||||
eventType: EVENT_TYPE.pageView,
|
||||
});
|
||||
|
||||
|
|
@ -117,5 +121,5 @@ async function clickhouseQuery(
|
|||
`;
|
||||
}
|
||||
|
||||
return rawQuery(sql, filterParams).then(result => result?.[0]);
|
||||
return rawQuery(sql, queryParams).then(result => result?.[0]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,8 +28,7 @@ async function relationalQuery(
|
|||
) {
|
||||
const column = FILTER_COLUMNS[type] || type;
|
||||
const { rawQuery, parseFilters } = prisma;
|
||||
const { filterQuery, joinSession, filterParams } = await parseFilters(
|
||||
websiteId,
|
||||
const { filterQuery, joinSessionQuery, queryParams } = await parseFilters(
|
||||
{
|
||||
...filters,
|
||||
eventType: column === 'event_name' ? EVENT_TYPE.customEvent : EVENT_TYPE.pageView,
|
||||
|
|
@ -70,7 +69,7 @@ async function relationalQuery(
|
|||
select ${column} x,
|
||||
${column === 'referrer_domain' ? 'count(distinct website_event.session_id)' : 'count(*)'} as y
|
||||
from website_event
|
||||
${joinSession}
|
||||
${joinSessionQuery}
|
||||
${entryExitQuery}
|
||||
where website_event.website_id = {{websiteId::uuid}}
|
||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||
|
|
@ -82,7 +81,7 @@ async function relationalQuery(
|
|||
limit ${limit}
|
||||
offset ${offset}
|
||||
`,
|
||||
filterParams,
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +94,7 @@ async function clickhouseQuery(
|
|||
): Promise<{ x: string; y: number }[]> {
|
||||
const column = FILTER_COLUMNS[type] || type;
|
||||
const { rawQuery, parseFilters } = clickhouse;
|
||||
const { filterQuery, filterParams } = await parseFilters(websiteId, {
|
||||
const { filterQuery, queryParams } = await parseFilters({
|
||||
...filters,
|
||||
eventType: column === 'event_name' ? EVENT_TYPE.customEvent : EVENT_TYPE.pageView,
|
||||
});
|
||||
|
|
@ -180,5 +179,5 @@ async function clickhouseQuery(
|
|||
`;
|
||||
}
|
||||
|
||||
return rawQuery(sql, filterParams);
|
||||
return rawQuery(sql, queryParams);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,9 @@ export async function getPageviewStats(...args: [websiteId: string, filters: Que
|
|||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||
const { timezone = 'utc', unit = 'day' } = filters;
|
||||
const { getDateSQL, parseFilters, rawQuery } = prisma;
|
||||
const { filterQuery, joinSession, filterParams } = await parseFilters(websiteId, {
|
||||
const { filterQuery, joinSessionQuery, queryParams } = await parseFilters({
|
||||
...filters,
|
||||
websiteId,
|
||||
eventType: EVENT_TYPE.pageView,
|
||||
});
|
||||
|
||||
|
|
@ -25,7 +26,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
|||
${getDateSQL('website_event.created_at', unit, timezone)} x,
|
||||
count(*) y
|
||||
from website_event
|
||||
${joinSession}
|
||||
${joinSessionQuery}
|
||||
where website_event.website_id = {{websiteId::uuid}}
|
||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||
and event_type = {{eventType}}
|
||||
|
|
@ -33,7 +34,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
|||
group by 1
|
||||
order by 1
|
||||
`,
|
||||
filterParams,
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -43,8 +44,9 @@ async function clickhouseQuery(
|
|||
): Promise<{ x: string; y: number }[]> {
|
||||
const { timezone = 'utc', unit = 'day' } = filters;
|
||||
const { parseFilters, rawQuery, getDateSQL } = clickhouse;
|
||||
const { filterQuery, filterParams } = await parseFilters(websiteId, {
|
||||
const { filterQuery, queryParams } = await parseFilters({
|
||||
...filters,
|
||||
websiteId,
|
||||
eventType: EVENT_TYPE.pageView,
|
||||
});
|
||||
|
||||
|
|
@ -88,5 +90,5 @@ async function clickhouseQuery(
|
|||
`;
|
||||
}
|
||||
|
||||
return rawQuery(sql, filterParams);
|
||||
return rawQuery(sql, queryParams);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -244,7 +244,7 @@ async function clickhouseQuery(
|
|||
const { rawQuery, parseFilters } = clickhouse;
|
||||
const eventType = type === 'page' ? EVENT_TYPE.pageView : EVENT_TYPE.customEvent;
|
||||
const column = type === 'page' ? 'url_path' : 'event_name';
|
||||
const { filterQuery, filterParams } = await parseFilters(websiteId, criteria);
|
||||
const { filterQuery, queryParams } = await parseFilters(criteria);
|
||||
|
||||
function getUTMQuery(utmColumn: string) {
|
||||
return `
|
||||
|
|
@ -345,7 +345,7 @@ async function clickhouseQuery(
|
|||
order by 2 desc
|
||||
limit 20
|
||||
`,
|
||||
{ ...filterParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
||||
{ ...queryParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
||||
);
|
||||
|
||||
const paidAdsres = await rawQuery<
|
||||
|
|
@ -376,7 +376,7 @@ async function clickhouseQuery(
|
|||
order by 2 desc
|
||||
limit 20
|
||||
`,
|
||||
{ ...filterParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
||||
{ ...queryParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
||||
);
|
||||
|
||||
const sourceRes = await rawQuery<
|
||||
|
|
@ -390,7 +390,7 @@ async function clickhouseQuery(
|
|||
${getModelQuery(model)}
|
||||
${getUTMQuery('utm_source')}
|
||||
`,
|
||||
{ ...filterParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
||||
{ ...queryParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
||||
);
|
||||
|
||||
const mediumRes = await rawQuery<
|
||||
|
|
@ -404,7 +404,7 @@ async function clickhouseQuery(
|
|||
${getModelQuery(model)}
|
||||
${getUTMQuery('utm_medium')}
|
||||
`,
|
||||
{ ...filterParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
||||
{ ...queryParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
||||
);
|
||||
|
||||
const campaignRes = await rawQuery<
|
||||
|
|
@ -418,7 +418,7 @@ async function clickhouseQuery(
|
|||
${getModelQuery(model)}
|
||||
${getUTMQuery('utm_campaign')}
|
||||
`,
|
||||
{ ...filterParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
||||
{ ...queryParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
||||
);
|
||||
|
||||
const contentRes = await rawQuery<
|
||||
|
|
@ -432,7 +432,7 @@ async function clickhouseQuery(
|
|||
${getModelQuery(model)}
|
||||
${getUTMQuery('utm_content')}
|
||||
`,
|
||||
{ ...filterParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
||||
{ ...queryParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
||||
);
|
||||
|
||||
const termRes = await rawQuery<
|
||||
|
|
@ -446,7 +446,7 @@ async function clickhouseQuery(
|
|||
${getModelQuery(model)}
|
||||
${getUTMQuery('utm_term')}
|
||||
`,
|
||||
{ ...filterParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
||||
{ ...queryParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
||||
);
|
||||
|
||||
const totalRes = await rawQuery<{ pageviews: number; visitors: number; visits: number }>(
|
||||
|
|
@ -462,7 +462,7 @@ async function clickhouseQuery(
|
|||
and event_type = {eventType:UInt32}
|
||||
${filterQuery}
|
||||
`,
|
||||
{ ...filterParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
||||
{ ...queryParams, websiteId, startDate, endDate, conversionStep: step, eventType, currency },
|
||||
).then(result => result?.[0]);
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -24,8 +24,7 @@ async function relationalQuery(
|
|||
}[]
|
||||
> {
|
||||
const { getTimestampDiffSQL, parseFilters, rawQuery } = prisma;
|
||||
const { filterQuery, joinSession, filterParams } = await parseFilters(
|
||||
websiteId,
|
||||
const { filterQuery, joinSessionQuery, queryParams } = await parseFilters(
|
||||
{
|
||||
...filters,
|
||||
eventType: EVENT_TYPE.pageView,
|
||||
|
|
@ -53,7 +52,7 @@ async function relationalQuery(
|
|||
min(website_event.created_at) as "min_time",
|
||||
max(website_event.created_at) as "max_time"
|
||||
from website_event
|
||||
${joinSession}
|
||||
${joinSessionQuery}
|
||||
where website_event.website_id = {{websiteId::uuid}}
|
||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||
and event_type = {{eventType}}
|
||||
|
|
@ -65,7 +64,7 @@ async function relationalQuery(
|
|||
order by 1 desc, 2 desc
|
||||
limit 500
|
||||
`,
|
||||
filterParams,
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -80,7 +79,7 @@ async function clickhouseQuery(
|
|||
}[]
|
||||
> {
|
||||
const { parseFilters, rawQuery } = clickhouse;
|
||||
const { filterQuery, filterParams } = await parseFilters(websiteId, {
|
||||
const { filterQuery, queryParams } = await parseFilters({
|
||||
...filters,
|
||||
eventType: EVENT_TYPE.pageView,
|
||||
});
|
||||
|
|
@ -114,7 +113,7 @@ async function clickhouseQuery(
|
|||
order by 1 desc, 2 desc
|
||||
limit 500
|
||||
`,
|
||||
filterParams,
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ async function clickhouseQuery(
|
|||
steps,
|
||||
windowMinutes,
|
||||
);
|
||||
const { filterQuery, filterParams: filterParams } = await parseFilters(websiteId, criteria);
|
||||
const { filterQuery, queryParams } = await parseFilters(criteria);
|
||||
|
||||
function getFunnelQuery(
|
||||
steps: { type: string; value: string }[],
|
||||
|
|
@ -136,7 +136,7 @@ async function clickhouseQuery(
|
|||
levelQuery: string;
|
||||
sumQuery: string;
|
||||
stepFilterQuery: string;
|
||||
params: { [key: string]: string };
|
||||
params: Record<string, string>;
|
||||
} {
|
||||
return steps.reduce(
|
||||
(pv, cv, i) => {
|
||||
|
|
@ -215,7 +215,7 @@ async function clickhouseQuery(
|
|||
startDate,
|
||||
endDate,
|
||||
...params,
|
||||
...filterParams,
|
||||
...queryParams,
|
||||
},
|
||||
).then(formatResults(steps));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import clickhouse from '@/lib/clickhouse';
|
||||
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
|
||||
import prisma from '@/lib/prisma';
|
||||
import { QueryFilters } from '@/lib/types';
|
||||
|
||||
export interface GoalCriteria {
|
||||
startDate: Date;
|
||||
|
|
@ -9,6 +10,7 @@ export interface GoalCriteria {
|
|||
value: string;
|
||||
operator?: string;
|
||||
property?: string;
|
||||
filters: QueryFilters;
|
||||
}
|
||||
|
||||
export async function getGoal(...args: [websiteId: string, criteria: GoalCriteria]) {
|
||||
|
|
@ -19,9 +21,13 @@ export async function getGoal(...args: [websiteId: string, criteria: GoalCriteri
|
|||
}
|
||||
|
||||
async function relationalQuery(websiteId: string, criteria: GoalCriteria) {
|
||||
const { type, value } = criteria;
|
||||
const { type, value, filters } = criteria;
|
||||
const { rawQuery, parseFilters } = prisma;
|
||||
const { filterQuery, dateQuery, filterParams } = await parseFilters(websiteId, criteria);
|
||||
const { filterQuery, dateQuery, queryParams } = await parseFilters({
|
||||
...filters,
|
||||
websiteId,
|
||||
value,
|
||||
});
|
||||
const isPage = type === 'page';
|
||||
const column = isPage ? 'url_path' : 'event_name';
|
||||
const eventType = isPage ? 1 : 2;
|
||||
|
|
@ -43,14 +49,18 @@ async function relationalQuery(websiteId: string, criteria: GoalCriteria) {
|
|||
${dateQuery}
|
||||
${filterQuery}
|
||||
`,
|
||||
{ ...filterParams, value },
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
||||
async function clickhouseQuery(websiteId: string, criteria: GoalCriteria) {
|
||||
const { type, value } = criteria;
|
||||
const { type, value, filters } = criteria;
|
||||
const { rawQuery, parseFilters } = clickhouse;
|
||||
const { filterQuery, dateQuery, filterParams } = await parseFilters(websiteId, criteria);
|
||||
const { filterQuery, dateQuery, queryParams } = await parseFilters({
|
||||
...filters,
|
||||
websiteId,
|
||||
value,
|
||||
});
|
||||
const isPage = type === 'page';
|
||||
const column = isPage ? 'url_path' : 'event_name';
|
||||
const eventType = isPage ? 1 : 2;
|
||||
|
|
@ -71,6 +81,6 @@ async function clickhouseQuery(websiteId: string, criteria: GoalCriteria) {
|
|||
${dateQuery}
|
||||
${filterQuery}
|
||||
`,
|
||||
{ ...filterParams, value },
|
||||
queryParams,
|
||||
).then(results => results?.[0]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ async function relationalQuery(
|
|||
sequenceQuery: string;
|
||||
startStepQuery: string;
|
||||
endStepQuery: string;
|
||||
params: { [key: string]: string };
|
||||
params: Record<string, string>;
|
||||
} {
|
||||
const params = {};
|
||||
let sequenceQuery = '';
|
||||
|
|
@ -108,7 +108,7 @@ async function relationalQuery(
|
|||
sequenceQuery,
|
||||
startStepQuery,
|
||||
endStepQuery,
|
||||
filterParams: params,
|
||||
params,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -167,7 +167,7 @@ async function clickhouseQuery(
|
|||
sequenceQuery: string;
|
||||
startStepQuery: string;
|
||||
endStepQuery: string;
|
||||
params: { [key: string]: string };
|
||||
params: Record<string, string>;
|
||||
} {
|
||||
const params = {};
|
||||
let sequenceQuery = '';
|
||||
|
|
@ -218,11 +218,11 @@ async function clickhouseQuery(
|
|||
sequenceQuery,
|
||||
startStepQuery,
|
||||
endStepQuery,
|
||||
filterParams: params,
|
||||
params,
|
||||
};
|
||||
}
|
||||
|
||||
const { filterQuery, filterParams: filterParams } = await parseFilters(websiteId, filters);
|
||||
const { filterQuery, queryParams } = await parseFilters(filters);
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
|
|
@ -249,7 +249,7 @@ async function clickhouseQuery(
|
|||
startDate,
|
||||
endDate,
|
||||
...params,
|
||||
...filterParams,
|
||||
...queryParams,
|
||||
},
|
||||
).then(parseResult);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ async function relationalQuery(
|
|||
const { getDateSQL, getDayDiffQuery, getCastColumnQuery, rawQuery, parseFilters } = prisma;
|
||||
const unit = 'day';
|
||||
|
||||
const { filterQuery, filterParams } = await parseFilters(websiteId, criteria);
|
||||
const { filterQuery, queryParams } = await parseFilters(criteria);
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
|
|
@ -85,7 +85,7 @@ async function relationalQuery(
|
|||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
...filterParams,
|
||||
...queryParams,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -98,7 +98,7 @@ async function clickhouseQuery(
|
|||
const { getDateSQL, rawQuery, parseFilters } = clickhouse;
|
||||
const unit = 'day';
|
||||
|
||||
const { filterQuery, filterParams } = await parseFilters(websiteId, criteria);
|
||||
const { filterQuery, queryParams } = await parseFilters(criteria);
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
|
|
@ -154,7 +154,7 @@ async function clickhouseQuery(
|
|||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
...filterParams,
|
||||
...queryParams,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export async function getUTM(...args: [websiteId: string, criteria: UTMCriteria]
|
|||
async function relationalQuery(websiteId: string, criteria: UTMCriteria) {
|
||||
const { startDate, endDate } = criteria;
|
||||
const { rawQuery, parseFilters } = prisma;
|
||||
const { filterQuery, filterParams } = await parseFilters(websiteId, criteria);
|
||||
const { filterQuery, queryParams } = await parseFilters(criteria);
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
|
|
@ -31,7 +31,7 @@ async function relationalQuery(websiteId: string, criteria: UTMCriteria) {
|
|||
group by 1
|
||||
`,
|
||||
{
|
||||
...filterParams,
|
||||
...queryParams,
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
|
|
@ -42,7 +42,7 @@ async function relationalQuery(websiteId: string, criteria: UTMCriteria) {
|
|||
async function clickhouseQuery(websiteId: string, criteria: UTMCriteria) {
|
||||
const { startDate, endDate } = criteria;
|
||||
const { rawQuery, parseFilters } = clickhouse;
|
||||
const { filterQuery, filterParams } = await parseFilters(websiteId, criteria);
|
||||
const { filterQuery, queryParams } = await parseFilters(criteria);
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
|
|
@ -56,7 +56,7 @@ async function clickhouseQuery(websiteId: string, criteria: UTMCriteria) {
|
|||
group by 1
|
||||
`,
|
||||
{
|
||||
...filterParams,
|
||||
...queryParams,
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import clickhouse from '@/lib/clickhouse';
|
||||
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
|
||||
import prisma from '@/lib/prisma';
|
||||
import { QueryFilters } from '@/lib/types';
|
||||
|
||||
export async function getSessionActivity(
|
||||
...args: [websiteId: string, sessionId: string, startDate: Date, endDate: Date]
|
||||
...args: [websiteId: string, sessionId: string, filters: QueryFilters]
|
||||
) {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(...args),
|
||||
|
|
@ -11,12 +12,9 @@ export async function getSessionActivity(
|
|||
});
|
||||
}
|
||||
|
||||
async function relationalQuery(
|
||||
websiteId: string,
|
||||
sessionId: string,
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
) {
|
||||
async function relationalQuery(websiteId: string, sessionId: string, filters: QueryFilters) {
|
||||
const { startDate, endDate } = filters;
|
||||
|
||||
return prisma.client.websiteEvent.findMany({
|
||||
where: {
|
||||
sessionId,
|
||||
|
|
@ -28,13 +26,9 @@ async function relationalQuery(
|
|||
});
|
||||
}
|
||||
|
||||
async function clickhouseQuery(
|
||||
websiteId: string,
|
||||
sessionId: string,
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
) {
|
||||
async function clickhouseQuery(websiteId: string, sessionId: string, filters: QueryFilters) {
|
||||
const { rawQuery } = clickhouse;
|
||||
const { startDate, endDate } = filters;
|
||||
|
||||
return rawQuery(
|
||||
`
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue