mirror of
https://github.com/kremalicious/umami.git
synced 2025-02-01 12:29:35 +01:00
Updated session and events queries. Added sessions page.
This commit is contained in:
parent
082a751ffe
commit
db36c37d32
@ -46,9 +46,14 @@ export function WebsiteHeader({
|
|||||||
path: '/reports',
|
path: '/reports',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: formatMessage(labels.eventData),
|
label: formatMessage(labels.sessions),
|
||||||
|
icon: <Icons.User />,
|
||||||
|
path: '/sessions',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: formatMessage(labels.events),
|
||||||
icon: <Icons.Nodes />,
|
icon: <Icons.Nodes />,
|
||||||
path: '/event-data',
|
path: '/events',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -144,7 +144,10 @@ export function RealtimeLog({ data }: { data: RealtimeData }) {
|
|||||||
const { events, visitors } = data;
|
const { events, visitors } = data;
|
||||||
|
|
||||||
let logs = [
|
let logs = [
|
||||||
...events.map(e => ({ __type: e.eventName ? TYPE_EVENT : TYPE_PAGEVIEW, ...e })),
|
...events.map(e => ({
|
||||||
|
__type: e.eventName ? TYPE_EVENT : TYPE_PAGEVIEW,
|
||||||
|
...e,
|
||||||
|
})),
|
||||||
...visitors.map(v => ({ __type: TYPE_SESSION, ...v })),
|
...visitors.map(v => ({ __type: TYPE_SESSION, ...v })),
|
||||||
].sort(thenby.firstBy('timestamp', -1));
|
].sort(thenby.firstBy('timestamp', -1));
|
||||||
|
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
import { useSessions } from 'components/hooks';
|
||||||
|
import SessionsTable from './SessionsTable';
|
||||||
|
import DataTable from 'components/common/DataTable';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export default function SessionsDataTable({
|
||||||
|
websiteId,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
websiteId?: string;
|
||||||
|
teamId?: string;
|
||||||
|
children?: ReactNode;
|
||||||
|
}) {
|
||||||
|
const queryResult = useSessions(websiteId);
|
||||||
|
|
||||||
|
if (queryResult?.result?.data?.length === 0) {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataTable queryResult={queryResult} allowSearch={false}>
|
||||||
|
{({ data }) => <SessionsTable data={data} showDomain={!websiteId} />}
|
||||||
|
</DataTable>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
'use client';
|
||||||
|
import WebsiteHeader from '../WebsiteHeader';
|
||||||
|
import SessionsDataTable from './SessionsDataTable';
|
||||||
|
|
||||||
|
export function SessionsPage({ websiteId }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WebsiteHeader websiteId={websiteId} />
|
||||||
|
<SessionsDataTable websiteId={websiteId} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SessionsPage;
|
@ -0,0 +1,21 @@
|
|||||||
|
import { GridColumn, GridTable, useBreakpoint } from 'react-basics';
|
||||||
|
import { useMessages } from 'components/hooks';
|
||||||
|
|
||||||
|
export function SessionsTable({ data = [] }: { data: any[]; showDomain?: boolean }) {
|
||||||
|
const { formatMessage, labels } = useMessages();
|
||||||
|
const breakpoint = useBreakpoint();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GridTable data={data} cardMode={['xs', 'sm', 'md'].includes(breakpoint)}>
|
||||||
|
<GridColumn name="id" label="ID" />
|
||||||
|
<GridColumn name="country" label={formatMessage(labels.country)} />
|
||||||
|
<GridColumn name="city" label={formatMessage(labels.city)} />
|
||||||
|
<GridColumn name="browser" label={formatMessage(labels.browser)} />
|
||||||
|
<GridColumn name="os" label={formatMessage(labels.os)} />
|
||||||
|
<GridColumn name="device" label={formatMessage(labels.device)} />
|
||||||
|
<GridColumn name="createdAt" label={formatMessage(labels.created)} />
|
||||||
|
</GridTable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SessionsTable;
|
10
src/app/(main)/websites/[websiteId]/sessions/page.tsx
Normal file
10
src/app/(main)/websites/[websiteId]/sessions/page.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import SessionsPage from './SessionsPage';
|
||||||
|
import { Metadata } from 'next';
|
||||||
|
|
||||||
|
export default function ({ params: { websiteId } }) {
|
||||||
|
return <SessionsPage websiteId={websiteId} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Sessions',
|
||||||
|
};
|
28
src/app/api/scripts/telemetry/route.ts
Normal file
28
src/app/api/scripts/telemetry/route.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { CURRENT_VERSION, TELEMETRY_PIXEL } from 'lib/constants';
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
if (
|
||||||
|
process.env.NODE_ENV !== 'production' &&
|
||||||
|
process.env.DISABLE_TELEMETRY &&
|
||||||
|
process.env.PRIVATE_MODE
|
||||||
|
) {
|
||||||
|
const script = `
|
||||||
|
(()=>{const i=document.createElement('img');
|
||||||
|
i.setAttribute('src','${TELEMETRY_PIXEL}?v=${CURRENT_VERSION}');
|
||||||
|
i.setAttribute('style','width:0;height:0;position:absolute;pointer-events:none;');
|
||||||
|
document.body.appendChild(i);})();
|
||||||
|
`;
|
||||||
|
|
||||||
|
return new Response(script.replace(/\s\s+/g, ''), {
|
||||||
|
headers: {
|
||||||
|
'content-type': 'text/javascript',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response('/* telemetry disabled */', {
|
||||||
|
headers: {
|
||||||
|
'content-type': 'text/javascript',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
@ -5,6 +5,7 @@ export * from './queries/useLogin';
|
|||||||
export * from './queries/useRealtime';
|
export * from './queries/useRealtime';
|
||||||
export * from './queries/useReport';
|
export * from './queries/useReport';
|
||||||
export * from './queries/useReports';
|
export * from './queries/useReports';
|
||||||
|
export * from './queries/useSessions';
|
||||||
export * from './queries/useShareToken';
|
export * from './queries/useShareToken';
|
||||||
export * from './queries/useTeam';
|
export * from './queries/useTeam';
|
||||||
export * from './queries/useTeams';
|
export * from './queries/useTeams';
|
||||||
|
20
src/components/hooks/queries/useSessions.ts
Normal file
20
src/components/hooks/queries/useSessions.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { useApi } from './useApi';
|
||||||
|
import { useFilterQuery } from './useFilterQuery';
|
||||||
|
import useModified from '../useModified';
|
||||||
|
|
||||||
|
export function useSessions(websiteId: string, params?: { [key: string]: string | number }) {
|
||||||
|
const { get } = useApi();
|
||||||
|
const { modified } = useModified(`websites`);
|
||||||
|
|
||||||
|
return useFilterQuery({
|
||||||
|
queryKey: ['sessions', { websiteId, modified, ...params }],
|
||||||
|
queryFn: (data: any) => {
|
||||||
|
return get(`/websites/${websiteId}/sessions`, {
|
||||||
|
...data,
|
||||||
|
...params,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSessions;
|
@ -2,8 +2,8 @@ import { ClickHouseClient, createClient } from '@clickhouse/client';
|
|||||||
import dateFormat from 'dateformat';
|
import dateFormat from 'dateformat';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import { CLICKHOUSE } from 'lib/db';
|
import { CLICKHOUSE } from 'lib/db';
|
||||||
import { QueryFilters, QueryOptions } from './types';
|
import { PageParams, QueryFilters, QueryOptions } from './types';
|
||||||
import { OPERATORS } from './constants';
|
import { DEFAULT_PAGE_SIZE, OPERATORS } from './constants';
|
||||||
import { fetchWebsite } from './load';
|
import { fetchWebsite } from './load';
|
||||||
import { maxDate } from './date';
|
import { maxDate } from './date';
|
||||||
import { filtersToArray } from './params';
|
import { filtersToArray } from './params';
|
||||||
@ -47,11 +47,11 @@ function getClient() {
|
|||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDateStringQuery(data: any, unit: string | number) {
|
function getDateStringSQL(data: any, unit: string | number) {
|
||||||
return `formatDateTime(${data}, '${CLICKHOUSE_DATE_FORMATS[unit]}')`;
|
return `formatDateTime(${data}, '${CLICKHOUSE_DATE_FORMATS[unit]}')`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDateQuery(field: string, unit: string, timezone?: string) {
|
function getDateSQL(field: string, unit: string, timezone?: string) {
|
||||||
if (timezone) {
|
if (timezone) {
|
||||||
return `date_trunc('${unit}', ${field}, '${timezone}')`;
|
return `date_trunc('${unit}', ${field}, '${timezone}')`;
|
||||||
}
|
}
|
||||||
@ -95,6 +95,20 @@ function getFilterQuery(filters: QueryFilters = {}, options: QueryOptions = {})
|
|||||||
return query.join('\n');
|
return query.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDateQuery(filters: QueryFilters = {}) {
|
||||||
|
const { startDate, endDate } = filters;
|
||||||
|
|
||||||
|
if (startDate) {
|
||||||
|
if (endDate) {
|
||||||
|
return `and created_at between {startDate:DateTime64} and {endDate:DateTime64}`;
|
||||||
|
} else {
|
||||||
|
return `and created_at >= {startDate:DateTime64}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
function getFilterParams(filters: QueryFilters = {}) {
|
function getFilterParams(filters: QueryFilters = {}) {
|
||||||
return filtersToArray(filters).reduce((obj, { name, value }) => {
|
return filtersToArray(filters).reduce((obj, { name, value }) => {
|
||||||
if (name && value !== undefined) {
|
if (name && value !== undefined) {
|
||||||
@ -110,6 +124,7 @@ async function parseFilters(websiteId: string, filters: QueryFilters = {}, optio
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
filterQuery: getFilterQuery(filters, options),
|
filterQuery: getFilterQuery(filters, options),
|
||||||
|
dateQuery: getDateQuery(filters),
|
||||||
params: {
|
params: {
|
||||||
...getFilterParams(filters),
|
...getFilterParams(filters),
|
||||||
websiteId,
|
websiteId,
|
||||||
@ -119,6 +134,32 @@ async function parseFilters(websiteId: string, filters: QueryFilters = {}, optio
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function pagedQuery(
|
||||||
|
query: string,
|
||||||
|
queryParams: { [key: string]: any },
|
||||||
|
pageParams: PageParams = {},
|
||||||
|
) {
|
||||||
|
const { page = 1, pageSize, orderBy, sortDescending = false } = pageParams;
|
||||||
|
const size = +pageSize || DEFAULT_PAGE_SIZE;
|
||||||
|
const offset = +size * (page - 1);
|
||||||
|
const direction = sortDescending ? 'desc' : 'asc';
|
||||||
|
|
||||||
|
const statements = [
|
||||||
|
orderBy && `order by ${orderBy} ${direction}`,
|
||||||
|
+size > 0 && `limit ${+size} offset ${offset}`,
|
||||||
|
]
|
||||||
|
.filter(n => n)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
const count = await rawQuery(`select count(*) as num from (${query}) t`, queryParams).then(
|
||||||
|
res => res[0].num,
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = await rawQuery(`${query}${statements}`, queryParams);
|
||||||
|
|
||||||
|
return { data, count, page: +page, pageSize: size, orderBy };
|
||||||
|
}
|
||||||
|
|
||||||
async function rawQuery<T = unknown>(
|
async function rawQuery<T = unknown>(
|
||||||
query: string,
|
query: string,
|
||||||
params: Record<string, unknown> = {},
|
params: Record<string, unknown> = {},
|
||||||
@ -170,11 +211,12 @@ export default {
|
|||||||
client: clickhouse,
|
client: clickhouse,
|
||||||
log,
|
log,
|
||||||
connect,
|
connect,
|
||||||
getDateStringQuery,
|
getDateStringSQL,
|
||||||
getDateQuery,
|
getDateSQL,
|
||||||
getDateFormat,
|
getDateFormat,
|
||||||
getFilterQuery,
|
getFilterQuery,
|
||||||
parseFilters,
|
parseFilters,
|
||||||
|
pagedQuery,
|
||||||
findUnique,
|
findUnique,
|
||||||
findFirst,
|
findFirst,
|
||||||
rawQuery,
|
rawQuery,
|
||||||
|
@ -60,7 +60,7 @@ function getCastColumnQuery(field: string, type: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDateQuery(field: string, unit: string, timezone?: string): string {
|
function getDateSQL(field: string, unit: string, timezone?: string): string {
|
||||||
const db = getDatabaseType();
|
const db = getDatabaseType();
|
||||||
|
|
||||||
if (db === POSTGRESQL) {
|
if (db === POSTGRESQL) {
|
||||||
@ -81,7 +81,19 @@ function getDateQuery(field: string, unit: string, timezone?: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTimestampDiffQuery(field1: string, field2: string): string {
|
export function getTimestampSQL(field: string) {
|
||||||
|
const db = getDatabaseType();
|
||||||
|
|
||||||
|
if (db === POSTGRESQL) {
|
||||||
|
return `floor(extract(epoch from ${field}))`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (db === MYSQL) {
|
||||||
|
return `UNIX_TIMESTAMP(${field})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTimestampDiffSQL(field1: string, field2: string): string {
|
||||||
const db = getDatabaseType();
|
const db = getDatabaseType();
|
||||||
|
|
||||||
if (db === POSTGRESQL) {
|
if (db === POSTGRESQL) {
|
||||||
@ -93,7 +105,7 @@ function getTimestampDiffQuery(field1: string, field2: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSearchQuery(column: string): string {
|
function getSearchSQL(column: string): string {
|
||||||
const db = getDatabaseType();
|
const db = getDatabaseType();
|
||||||
const like = db === POSTGRESQL ? 'ilike' : 'like';
|
const like = db === POSTGRESQL ? 'ilike' : 'like';
|
||||||
|
|
||||||
@ -137,6 +149,20 @@ function getFilterQuery(filters: QueryFilters = {}, options: QueryOptions = {}):
|
|||||||
return query.join('\n');
|
return query.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDateQuery(filters: QueryFilters = {}) {
|
||||||
|
const { startDate, endDate } = filters;
|
||||||
|
|
||||||
|
if (startDate) {
|
||||||
|
if (endDate) {
|
||||||
|
return `and website_event.created_at between {{startDate}} and {{endDate}}`;
|
||||||
|
} else {
|
||||||
|
return `and website_event.created_at >= {{startDate}}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
function getFilterParams(filters: QueryFilters = {}) {
|
function getFilterParams(filters: QueryFilters = {}) {
|
||||||
return filtersToArray(filters).reduce((obj, { name, operator, value }) => {
|
return filtersToArray(filters).reduce((obj, { name, operator, value }) => {
|
||||||
obj[name] = [OPERATORS.contains, OPERATORS.doesNotContain].includes(operator)
|
obj[name] = [OPERATORS.contains, OPERATORS.doesNotContain].includes(operator)
|
||||||
@ -161,6 +187,7 @@ async function parseFilters(
|
|||||||
? `inner join session on website_event.session_id = session.session_id`
|
? `inner join session on website_event.session_id = session.session_id`
|
||||||
: '',
|
: '',
|
||||||
filterQuery: getFilterQuery(filters, options),
|
filterQuery: getFilterQuery(filters, options),
|
||||||
|
dateQuery: getDateQuery(filters),
|
||||||
params: {
|
params: {
|
||||||
...getFilterParams(filters),
|
...getFilterParams(filters),
|
||||||
websiteId,
|
websiteId,
|
||||||
@ -191,8 +218,8 @@ async function rawQuery(sql: string, data: object): Promise<any> {
|
|||||||
return prisma.rawQuery(query, params);
|
return prisma.rawQuery(query, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pagedQuery<T>(model: string, criteria: T, filters: PageParams) {
|
async function pagedQuery<T>(model: string, criteria: T, pageParams: PageParams) {
|
||||||
const { page = 1, pageSize, orderBy, sortDescending = false } = filters || {};
|
const { page = 1, pageSize, orderBy, sortDescending = false } = pageParams || {};
|
||||||
const size = +pageSize || DEFAULT_PAGE_SIZE;
|
const size = +pageSize || DEFAULT_PAGE_SIZE;
|
||||||
|
|
||||||
const data = await prisma.client[model].findMany({
|
const data = await prisma.client[model].findMany({
|
||||||
@ -256,11 +283,11 @@ export default {
|
|||||||
getAddIntervalQuery,
|
getAddIntervalQuery,
|
||||||
getCastColumnQuery,
|
getCastColumnQuery,
|
||||||
getDayDiffQuery,
|
getDayDiffQuery,
|
||||||
getDateQuery,
|
getDateSQL,
|
||||||
getFilterQuery,
|
getFilterQuery,
|
||||||
getSearchParameters,
|
getSearchParameters,
|
||||||
getTimestampDiffQuery,
|
getTimestampDiffSQL,
|
||||||
getSearchQuery,
|
getSearchSQL,
|
||||||
getQueryMode,
|
getQueryMode,
|
||||||
pagedQuery,
|
pagedQuery,
|
||||||
parseFilters,
|
parseFilters,
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
import { ok } from 'next-basics';
|
|
||||||
import { CURRENT_VERSION, TELEMETRY_PIXEL } from 'lib/constants';
|
|
||||||
import { NextApiRequest, NextApiResponse } from 'next';
|
|
||||||
|
|
||||||
export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
||||||
if (process.env.NODE_ENV === 'production') {
|
|
||||||
res.setHeader('content-type', 'text/javascript');
|
|
||||||
|
|
||||||
if (process.env.DISABLE_TELEMETRY || process.env.PRIVATE_MODE) {
|
|
||||||
return res.send('/* telemetry disabled */');
|
|
||||||
}
|
|
||||||
|
|
||||||
const script = `
|
|
||||||
(()=>{const i=document.createElement('img');
|
|
||||||
i.setAttribute('src','${TELEMETRY_PIXEL}?v=${CURRENT_VERSION}');
|
|
||||||
i.setAttribute('style','width:0;height:0;position:absolute;pointer-events:none;');
|
|
||||||
document.body.appendChild(i);})();
|
|
||||||
`;
|
|
||||||
|
|
||||||
return res.send(script.replace(/\s\s+/g, ''));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ok(res);
|
|
||||||
}
|
|
42
src/pages/api/websites/[websiteId]/sessions.ts
Normal file
42
src/pages/api/websites/[websiteId]/sessions.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import * as yup from 'yup';
|
||||||
|
import { canViewWebsite } from 'lib/auth';
|
||||||
|
import { useAuth, useCors, useValidate } from 'lib/middleware';
|
||||||
|
import { NextApiRequestQueryBody, PageParams } from 'lib/types';
|
||||||
|
import { NextApiResponse } from 'next';
|
||||||
|
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||||
|
import { pageInfo } from 'lib/schema';
|
||||||
|
import { getSessions } from 'queries';
|
||||||
|
|
||||||
|
export interface ReportsRequestQuery extends PageParams {
|
||||||
|
websiteId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
GET: yup.object().shape({
|
||||||
|
websiteId: yup.string().uuid().required(),
|
||||||
|
...pageInfo,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async (
|
||||||
|
req: NextApiRequestQueryBody<ReportsRequestQuery, any>,
|
||||||
|
res: NextApiResponse,
|
||||||
|
) => {
|
||||||
|
await useCors(req, res);
|
||||||
|
await useAuth(req, res);
|
||||||
|
await useValidate(schema, req, res);
|
||||||
|
|
||||||
|
const { websiteId } = req.query;
|
||||||
|
|
||||||
|
if (req.method === 'GET') {
|
||||||
|
if (!(await canViewWebsite(req.auth, websiteId))) {
|
||||||
|
return unauthorized(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await getSessions(websiteId, {}, req.query);
|
||||||
|
|
||||||
|
return ok(res, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return methodNotAllowed(res);
|
||||||
|
};
|
@ -15,7 +15,7 @@ export async function getEventMetrics(
|
|||||||
|
|
||||||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
const { timezone = 'utc', unit = 'day' } = filters;
|
const { timezone = 'utc', unit = 'day' } = filters;
|
||||||
const { rawQuery, getDateQuery, parseFilters } = prisma;
|
const { rawQuery, getDateSQL, parseFilters } = prisma;
|
||||||
const { filterQuery, joinSession, params } = await parseFilters(websiteId, {
|
const { filterQuery, joinSession, params } = await parseFilters(websiteId, {
|
||||||
...filters,
|
...filters,
|
||||||
eventType: EVENT_TYPE.customEvent,
|
eventType: EVENT_TYPE.customEvent,
|
||||||
@ -25,7 +25,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
|||||||
`
|
`
|
||||||
select
|
select
|
||||||
event_name x,
|
event_name x,
|
||||||
${getDateQuery('website_event.created_at', unit, timezone)} t,
|
${getDateSQL('website_event.created_at', unit, timezone)} t,
|
||||||
count(*) y
|
count(*) y
|
||||||
from website_event
|
from website_event
|
||||||
${joinSession}
|
${joinSession}
|
||||||
@ -45,7 +45,7 @@ async function clickhouseQuery(
|
|||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
): Promise<{ x: string; t: string; y: number }[]> {
|
): Promise<{ x: string; t: string; y: number }[]> {
|
||||||
const { timezone = 'UTC', unit = 'day' } = filters;
|
const { timezone = 'UTC', unit = 'day' } = filters;
|
||||||
const { rawQuery, getDateQuery, parseFilters } = clickhouse;
|
const { rawQuery, getDateSQL, parseFilters } = clickhouse;
|
||||||
const { filterQuery, params } = await parseFilters(websiteId, {
|
const { filterQuery, params } = await parseFilters(websiteId, {
|
||||||
...filters,
|
...filters,
|
||||||
eventType: EVENT_TYPE.customEvent,
|
eventType: EVENT_TYPE.customEvent,
|
||||||
@ -55,7 +55,7 @@ async function clickhouseQuery(
|
|||||||
`
|
`
|
||||||
select
|
select
|
||||||
event_name x,
|
event_name x,
|
||||||
${getDateQuery('created_at', unit, timezone)} t,
|
${getDateSQL('created_at', unit, timezone)} t,
|
||||||
count(*) y
|
count(*) y
|
||||||
from website_event
|
from website_event
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
|
@ -1,45 +1,33 @@
|
|||||||
import clickhouse from 'lib/clickhouse';
|
import clickhouse from 'lib/clickhouse';
|
||||||
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
|
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
|
||||||
import prisma from 'lib/prisma';
|
import prisma from 'lib/prisma';
|
||||||
import { QueryFilters } from 'lib/types';
|
import { PageParams, QueryFilters } from 'lib/types';
|
||||||
|
|
||||||
export function getEvents(...args: [websiteId: string, filters: QueryFilters]) {
|
export function getEvents(
|
||||||
|
...args: [websiteId: string, filters: QueryFilters, pageParams?: PageParams]
|
||||||
|
) {
|
||||||
return runQuery({
|
return runQuery({
|
||||||
[PRISMA]: () => relationalQuery(...args),
|
[PRISMA]: () => relationalQuery(...args),
|
||||||
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function relationalQuery(websiteId: string, filters: QueryFilters) {
|
async function relationalQuery(websiteId: string, filters: QueryFilters, pageParams?: PageParams) {
|
||||||
const { startDate } = filters;
|
const { pagedQuery } = prisma;
|
||||||
|
|
||||||
return prisma.client.websiteEvent
|
const where = {
|
||||||
.findMany({
|
...filters,
|
||||||
where: {
|
id: websiteId,
|
||||||
websiteId,
|
};
|
||||||
createdAt: {
|
|
||||||
gte: startDate,
|
return pagedQuery('website_event', { where }, pageParams);
|
||||||
},
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
createdAt: 'desc',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then(a => {
|
|
||||||
return Object.values(a).map(a => {
|
|
||||||
return {
|
|
||||||
...a,
|
|
||||||
timestamp: new Date(a.createdAt).getTime() / 1000,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
async function clickhouseQuery(websiteId: string, filters: QueryFilters, pageParams?: PageParams) {
|
||||||
const { rawQuery } = clickhouse;
|
const { pagedQuery, parseFilters } = clickhouse;
|
||||||
const { startDate } = filters;
|
const { params, dateQuery, filterQuery } = await parseFilters(websiteId, filters);
|
||||||
|
|
||||||
return rawQuery(
|
return pagedQuery(
|
||||||
`
|
`
|
||||||
select
|
select
|
||||||
event_id as id,
|
event_id as id,
|
||||||
@ -48,16 +36,20 @@ function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
|||||||
created_at as createdAt,
|
created_at as createdAt,
|
||||||
toUnixTimestamp(created_at) as timestamp,
|
toUnixTimestamp(created_at) as timestamp,
|
||||||
url_path as urlPath,
|
url_path as urlPath,
|
||||||
|
url_query as urlQuery,
|
||||||
|
referrer_path as referrerPath,
|
||||||
|
referrer_query as referrerQuery,
|
||||||
referrer_domain as referrerDomain,
|
referrer_domain as referrerDomain,
|
||||||
|
page_title as pageTitle,
|
||||||
|
event_type as eventType,
|
||||||
event_name as eventName
|
event_name as eventName
|
||||||
from website_event
|
from website_event
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at >= {startDate:DateTime64}
|
${dateQuery}
|
||||||
|
${filterQuery}
|
||||||
order by created_at desc
|
order by created_at desc
|
||||||
`,
|
`,
|
||||||
{
|
params,
|
||||||
websiteId,
|
pageParams,
|
||||||
startDate,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -19,15 +19,15 @@ export async function getRealtimeData(
|
|||||||
const { startDate, timezone } = criteria;
|
const { startDate, timezone } = criteria;
|
||||||
const filters = { startDate, endDate: new Date(), unit: 'minute', timezone };
|
const filters = { startDate, endDate: new Date(), unit: 'minute', timezone };
|
||||||
const [events, sessions, pageviews, sessionviews] = await Promise.all([
|
const [events, sessions, pageviews, sessionviews] = await Promise.all([
|
||||||
getEvents(websiteId, { startDate }),
|
getEvents(websiteId, { startDate }, { pageSize: 10000 }),
|
||||||
getSessions(websiteId, { startDate }),
|
getSessions(websiteId, { startDate }, { pageSize: 10000 }),
|
||||||
getPageviewStats(websiteId, filters),
|
getPageviewStats(websiteId, filters),
|
||||||
getSessionStats(websiteId, filters),
|
getSessionStats(websiteId, filters),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const uniques = new Set();
|
const uniques = new Set();
|
||||||
|
|
||||||
const sessionStats = sessions.reduce(
|
const sessionStats = sessions.data.reduce(
|
||||||
(obj: { visitors: any; countries: any }, session: { id: any; country: any }) => {
|
(obj: { visitors: any; countries: any }, session: { id: any; country: any }) => {
|
||||||
const { countries, visitors } = obj;
|
const { countries, visitors } = obj;
|
||||||
const { id, country } = session;
|
const { id, country } = session;
|
||||||
@ -49,7 +49,7 @@ export async function getRealtimeData(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const eventStats = events.reduce(
|
const eventStats = events.data.reduce(
|
||||||
(
|
(
|
||||||
obj: { urls: any; referrers: any; events: any },
|
obj: { urls: any; referrers: any; events: any },
|
||||||
event: { urlPath: any; referrerDomain: any },
|
event: { urlPath: any; referrerDomain: any },
|
||||||
@ -81,9 +81,9 @@ export async function getRealtimeData(
|
|||||||
visitors: sessionviews,
|
visitors: sessionviews,
|
||||||
},
|
},
|
||||||
totals: {
|
totals: {
|
||||||
views: events.filter(e => !e.eventName).length,
|
views: events.data.filter(e => !e.eventName).length,
|
||||||
visitors: uniques.size,
|
visitors: uniques.size,
|
||||||
events: events.filter(e => e.eventName).length,
|
events: events.data.filter(e => e.eventName).length,
|
||||||
countries: Object.keys(sessionStats.countries).length,
|
countries: Object.keys(sessionStats.countries).length,
|
||||||
},
|
},
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
|
@ -18,11 +18,11 @@ async function relationalQuery(
|
|||||||
endDate: Date,
|
endDate: Date,
|
||||||
search: string,
|
search: string,
|
||||||
) {
|
) {
|
||||||
const { rawQuery, getSearchQuery } = prisma;
|
const { rawQuery, getSearchSQL } = prisma;
|
||||||
let searchQuery = '';
|
let searchQuery = '';
|
||||||
|
|
||||||
if (search) {
|
if (search) {
|
||||||
searchQuery = getSearchQuery(column);
|
searchQuery = getSearchSQL(column);
|
||||||
}
|
}
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
|
@ -21,7 +21,7 @@ async function relationalQuery(
|
|||||||
): Promise<
|
): Promise<
|
||||||
{ pageviews: number; visitors: number; visits: number; bounces: number; totaltime: number }[]
|
{ pageviews: number; visitors: number; visits: number; bounces: number; totaltime: number }[]
|
||||||
> {
|
> {
|
||||||
const { getTimestampDiffQuery, parseFilters, rawQuery } = prisma;
|
const { getTimestampDiffSQL, parseFilters, rawQuery } = prisma;
|
||||||
const { filterQuery, joinSession, params } = await parseFilters(websiteId, {
|
const { filterQuery, joinSession, params } = await parseFilters(websiteId, {
|
||||||
...filters,
|
...filters,
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
@ -34,7 +34,7 @@ async function relationalQuery(
|
|||||||
count(distinct t.session_id) as "visitors",
|
count(distinct t.session_id) as "visitors",
|
||||||
count(distinct t.visit_id) as "visits",
|
count(distinct t.visit_id) as "visits",
|
||||||
sum(case when t.c = 1 then 1 else 0 end) as "bounces",
|
sum(case when t.c = 1 then 1 else 0 end) as "bounces",
|
||||||
sum(${getTimestampDiffQuery('t.min_time', 't.max_time')}) as "totaltime"
|
sum(${getTimestampDiffSQL('t.min_time', 't.max_time')}) as "totaltime"
|
||||||
from (
|
from (
|
||||||
select
|
select
|
||||||
website_event.session_id,
|
website_event.session_id,
|
||||||
|
@ -42,15 +42,18 @@ async function relationalQuery(
|
|||||||
const aggregrate = type === 'entry' ? 'min' : 'max';
|
const aggregrate = type === 'entry' ? 'min' : 'max';
|
||||||
|
|
||||||
entryExitQuery = `
|
entryExitQuery = `
|
||||||
JOIN (select visit_id,
|
join (
|
||||||
${aggregrate}(created_at) target_created_at
|
select visit_id,
|
||||||
from website_event
|
${aggregrate}(created_at) target_created_at
|
||||||
where website_event.website_id = {{websiteId::uuid}}
|
from website_event
|
||||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
where website_event.website_id = {{websiteId::uuid}}
|
||||||
and event_type = {{eventType}}
|
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||||
group by visit_id) x
|
and event_type = {{eventType}}
|
||||||
ON x.visit_id = website_event.visit_id
|
group by visit_id
|
||||||
and x.target_created_at = website_event.created_at`;
|
) x
|
||||||
|
on x.visit_id = website_event.visit_id
|
||||||
|
and x.target_created_at = website_event.created_at
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
@ -97,15 +100,18 @@ async function clickhouseQuery(
|
|||||||
const aggregrate = type === 'entry' ? 'min' : 'max';
|
const aggregrate = type === 'entry' ? 'min' : 'max';
|
||||||
|
|
||||||
entryExitQuery = `
|
entryExitQuery = `
|
||||||
JOIN (select visit_id,
|
join (
|
||||||
${aggregrate}(created_at) target_created_at
|
select visit_id,
|
||||||
from website_event
|
${aggregrate}(created_at) target_created_at
|
||||||
where website_id = {websiteId:UUID}
|
from website_event
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
where website_id = {websiteId:UUID}
|
||||||
and event_type = {eventType:UInt32}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
group by visit_id) x
|
and event_type = {eventType:UInt32}
|
||||||
ON x.visit_id = website_event.visit_id
|
group by visit_id
|
||||||
and x.target_created_at = website_event.created_at`;
|
) x
|
||||||
|
on x.visit_id = website_event.visit_id
|
||||||
|
and x.target_created_at = website_event.created_at
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
|
@ -13,7 +13,7 @@ export async function getPageviewStats(...args: [websiteId: string, filters: Que
|
|||||||
|
|
||||||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
const { timezone = 'utc', unit = 'day' } = filters;
|
const { timezone = 'utc', unit = 'day' } = filters;
|
||||||
const { getDateQuery, parseFilters, rawQuery } = prisma;
|
const { getDateSQL, parseFilters, rawQuery } = prisma;
|
||||||
const { filterQuery, joinSession, params } = await parseFilters(websiteId, {
|
const { filterQuery, joinSession, params } = await parseFilters(websiteId, {
|
||||||
...filters,
|
...filters,
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
@ -22,7 +22,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
|||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
select
|
select
|
||||||
${getDateQuery('website_event.created_at', unit, timezone)} x,
|
${getDateSQL('website_event.created_at', unit, timezone)} x,
|
||||||
count(*) y
|
count(*) y
|
||||||
from website_event
|
from website_event
|
||||||
${joinSession}
|
${joinSession}
|
||||||
@ -41,7 +41,7 @@ async function clickhouseQuery(
|
|||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
): Promise<{ x: string; y: number }[]> {
|
): Promise<{ x: string; y: number }[]> {
|
||||||
const { timezone = 'UTC', unit = 'day' } = filters;
|
const { timezone = 'UTC', unit = 'day' } = filters;
|
||||||
const { parseFilters, rawQuery, getDateStringQuery, getDateQuery } = clickhouse;
|
const { parseFilters, rawQuery, getDateStringSQL, getDateSQL } = clickhouse;
|
||||||
const { filterQuery, params } = await parseFilters(websiteId, {
|
const { filterQuery, params } = await parseFilters(websiteId, {
|
||||||
...filters,
|
...filters,
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
@ -50,11 +50,11 @@ async function clickhouseQuery(
|
|||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
select
|
select
|
||||||
${getDateStringQuery('g.t', unit)} as x,
|
${getDateStringSQL('g.t', unit)} as x,
|
||||||
g.y as y
|
g.y as y
|
||||||
from (
|
from (
|
||||||
select
|
select
|
||||||
${getDateQuery('created_at', unit, timezone)} as t,
|
${getDateSQL('created_at', unit, timezone)} as t,
|
||||||
count(*) as y
|
count(*) as y
|
||||||
from website_event
|
from website_event
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
|
@ -23,7 +23,7 @@ async function relationalQuery(
|
|||||||
y: number;
|
y: number;
|
||||||
}[]
|
}[]
|
||||||
> {
|
> {
|
||||||
const { getTimestampDiffQuery, parseFilters, rawQuery } = prisma;
|
const { getTimestampDiffSQL, parseFilters, rawQuery } = prisma;
|
||||||
const { filterQuery, joinSession, params } = await parseFilters(
|
const { filterQuery, joinSession, params } = await parseFilters(
|
||||||
websiteId,
|
websiteId,
|
||||||
{
|
{
|
||||||
@ -42,7 +42,7 @@ async function relationalQuery(
|
|||||||
count(distinct t.session_id) as "visitors",
|
count(distinct t.session_id) as "visitors",
|
||||||
count(distinct t.visit_id) as "visits",
|
count(distinct t.visit_id) as "visits",
|
||||||
sum(case when t.c = 1 then 1 else 0 end) as "bounces",
|
sum(case when t.c = 1 then 1 else 0 end) as "bounces",
|
||||||
sum(${getTimestampDiffQuery('t.min_time', 't.max_time')}) as "totaltime",
|
sum(${getTimestampDiffSQL('t.min_time', 't.max_time')}) as "totaltime",
|
||||||
${parseFieldsByName(fields)}
|
${parseFieldsByName(fields)}
|
||||||
from (
|
from (
|
||||||
select
|
select
|
||||||
|
@ -35,14 +35,14 @@ async function relationalQuery(
|
|||||||
}[]
|
}[]
|
||||||
> {
|
> {
|
||||||
const { startDate, endDate, timezone = 'UTC' } = filters;
|
const { startDate, endDate, timezone = 'UTC' } = filters;
|
||||||
const { getDateQuery, getDayDiffQuery, getCastColumnQuery, rawQuery } = prisma;
|
const { getDateSQL, getDayDiffQuery, getCastColumnQuery, rawQuery } = prisma;
|
||||||
const unit = 'day';
|
const unit = 'day';
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
WITH cohort_items AS (
|
WITH cohort_items AS (
|
||||||
select session_id,
|
select session_id,
|
||||||
${getDateQuery('created_at', unit, timezone)} as cohort_date
|
${getDateSQL('created_at', unit, timezone)} as cohort_date
|
||||||
from session
|
from session
|
||||||
where website_id = {{websiteId::uuid}}
|
where website_id = {{websiteId::uuid}}
|
||||||
and created_at between {{startDate}} and {{endDate}}
|
and created_at between {{startDate}} and {{endDate}}
|
||||||
@ -50,10 +50,7 @@ async function relationalQuery(
|
|||||||
user_activities AS (
|
user_activities AS (
|
||||||
select distinct
|
select distinct
|
||||||
w.session_id,
|
w.session_id,
|
||||||
${getDayDiffQuery(
|
${getDayDiffQuery(getDateSQL('created_at', unit, timezone), 'c.cohort_date')} as day_number
|
||||||
getDateQuery('created_at', unit, timezone),
|
|
||||||
'c.cohort_date',
|
|
||||||
)} as day_number
|
|
||||||
from website_event w
|
from website_event w
|
||||||
join cohort_items c
|
join cohort_items c
|
||||||
on w.session_id = c.session_id
|
on w.session_id = c.session_id
|
||||||
@ -115,14 +112,14 @@ async function clickhouseQuery(
|
|||||||
}[]
|
}[]
|
||||||
> {
|
> {
|
||||||
const { startDate, endDate, timezone = 'UTC' } = filters;
|
const { startDate, endDate, timezone = 'UTC' } = filters;
|
||||||
const { getDateQuery, getDateStringQuery, rawQuery } = clickhouse;
|
const { getDateSQL, getDateStringSQL, rawQuery } = clickhouse;
|
||||||
const unit = 'day';
|
const unit = 'day';
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
WITH cohort_items AS (
|
WITH cohort_items AS (
|
||||||
select
|
select
|
||||||
min(${getDateQuery('created_at', unit, timezone)}) as cohort_date,
|
min(${getDateSQL('created_at', unit, timezone)}) as cohort_date,
|
||||||
session_id
|
session_id
|
||||||
from website_event
|
from website_event
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
@ -132,7 +129,7 @@ async function clickhouseQuery(
|
|||||||
user_activities AS (
|
user_activities AS (
|
||||||
select distinct
|
select distinct
|
||||||
w.session_id,
|
w.session_id,
|
||||||
(${getDateQuery('created_at', unit, timezone)} - c.cohort_date) / 86400 as day_number
|
(${getDateSQL('created_at', unit, timezone)} - c.cohort_date) / 86400 as day_number
|
||||||
from website_event w
|
from website_event w
|
||||||
join cohort_items c
|
join cohort_items c
|
||||||
on w.session_id = c.session_id
|
on w.session_id = c.session_id
|
||||||
@ -157,7 +154,7 @@ async function clickhouseQuery(
|
|||||||
group by 1, 2
|
group by 1, 2
|
||||||
)
|
)
|
||||||
select
|
select
|
||||||
${getDateStringQuery('c.cohort_date', unit)} as date,
|
${getDateStringSQL('c.cohort_date', unit)} as date,
|
||||||
c.day_number as day,
|
c.day_number as day,
|
||||||
s.visitors as visitors,
|
s.visitors as visitors,
|
||||||
c.visitors returnVisitors,
|
c.visitors returnVisitors,
|
||||||
|
@ -46,12 +46,12 @@ async function relationalQuery(
|
|||||||
timezone = 'UTC',
|
timezone = 'UTC',
|
||||||
unit = 'day',
|
unit = 'day',
|
||||||
} = criteria;
|
} = criteria;
|
||||||
const { getDateQuery, rawQuery } = prisma;
|
const { getDateSQL, rawQuery } = prisma;
|
||||||
|
|
||||||
const chartRes = await rawQuery(
|
const chartRes = await rawQuery(
|
||||||
`
|
`
|
||||||
select
|
select
|
||||||
${getDateQuery('website_event.created_at', unit, timezone)} time,
|
${getDateSQL('website_event.created_at', unit, timezone)} time,
|
||||||
sum(case when data_key = {{revenueProperty}} then number_value else 0 end) sum,
|
sum(case when data_key = {{revenueProperty}} then number_value else 0 end) sum,
|
||||||
avg(case when data_key = {{revenueProperty}} then number_value else 0 end) avg,
|
avg(case when data_key = {{revenueProperty}} then number_value else 0 end) avg,
|
||||||
count(case when data_key = {{revenueProperty}} then 1 else 0 end) count,
|
count(case when data_key = {{revenueProperty}} then 1 else 0 end) count,
|
||||||
@ -110,7 +110,7 @@ async function clickhouseQuery(
|
|||||||
timezone = 'UTC',
|
timezone = 'UTC',
|
||||||
unit = 'day',
|
unit = 'day',
|
||||||
} = criteria;
|
} = criteria;
|
||||||
const { getDateStringQuery, getDateQuery, rawQuery } = clickhouse;
|
const { getDateStringSQL, getDateSQL, rawQuery } = clickhouse;
|
||||||
|
|
||||||
const chartRes = await rawQuery<{
|
const chartRes = await rawQuery<{
|
||||||
time: string;
|
time: string;
|
||||||
@ -121,14 +121,14 @@ async function clickhouseQuery(
|
|||||||
}>(
|
}>(
|
||||||
`
|
`
|
||||||
select
|
select
|
||||||
${getDateStringQuery('g.time', unit)} as time,
|
${getDateStringSQL('g.time', unit)} as time,
|
||||||
g.sum as sum,
|
g.sum as sum,
|
||||||
g.avg as avg,
|
g.avg as avg,
|
||||||
g.count as count,
|
g.count as count,
|
||||||
g.uniqueCount as uniqueCount
|
g.uniqueCount as uniqueCount
|
||||||
from (
|
from (
|
||||||
select
|
select
|
||||||
${getDateQuery('created_at', unit, timezone)} as time,
|
${getDateSQL('created_at', unit, timezone)} as time,
|
||||||
sumIf(number_value, data_key = {revenueProperty:String}) as sum,
|
sumIf(number_value, data_key = {revenueProperty:String}) as sum,
|
||||||
avgIf(number_value, data_key = {revenueProperty:String}) as avg,
|
avgIf(number_value, data_key = {revenueProperty:String}) as avg,
|
||||||
countIf(data_key = {revenueProperty:String}) as count,
|
countIf(data_key = {revenueProperty:String}) as count,
|
||||||
|
@ -13,7 +13,7 @@ export async function getSessionStats(...args: [websiteId: string, filters: Quer
|
|||||||
|
|
||||||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
const { timezone = 'utc', unit = 'day' } = filters;
|
const { timezone = 'utc', unit = 'day' } = filters;
|
||||||
const { getDateQuery, parseFilters, rawQuery } = prisma;
|
const { getDateSQL, parseFilters, rawQuery } = prisma;
|
||||||
const { filterQuery, joinSession, params } = await parseFilters(websiteId, {
|
const { filterQuery, joinSession, params } = await parseFilters(websiteId, {
|
||||||
...filters,
|
...filters,
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
@ -22,7 +22,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
|||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
select
|
select
|
||||||
${getDateQuery('website_event.created_at', unit, timezone)} x,
|
${getDateSQL('website_event.created_at', unit, timezone)} x,
|
||||||
count(distinct website_event.session_id) y
|
count(distinct website_event.session_id) y
|
||||||
from website_event
|
from website_event
|
||||||
${joinSession}
|
${joinSession}
|
||||||
@ -41,7 +41,7 @@ async function clickhouseQuery(
|
|||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
): Promise<{ x: string; y: number }[]> {
|
): Promise<{ x: string; y: number }[]> {
|
||||||
const { timezone = 'UTC', unit = 'day' } = filters;
|
const { timezone = 'UTC', unit = 'day' } = filters;
|
||||||
const { parseFilters, rawQuery, getDateStringQuery, getDateQuery } = clickhouse;
|
const { parseFilters, rawQuery, getDateStringSQL, getDateSQL } = clickhouse;
|
||||||
const { filterQuery, params } = await parseFilters(websiteId, {
|
const { filterQuery, params } = await parseFilters(websiteId, {
|
||||||
...filters,
|
...filters,
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
@ -50,11 +50,11 @@ async function clickhouseQuery(
|
|||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
select
|
select
|
||||||
${getDateStringQuery('g.t', unit)} as x,
|
${getDateStringSQL('g.t', unit)} as x,
|
||||||
g.y as y
|
g.y as y
|
||||||
from (
|
from (
|
||||||
select
|
select
|
||||||
${getDateQuery('created_at', unit, timezone)} as t,
|
${getDateSQL('created_at', unit, timezone)} as t,
|
||||||
count(distinct session_id) as y
|
count(distinct session_id) as y
|
||||||
from website_event
|
from website_event
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
|
@ -1,45 +1,33 @@
|
|||||||
import prisma from 'lib/prisma';
|
import prisma from 'lib/prisma';
|
||||||
import clickhouse from 'lib/clickhouse';
|
import clickhouse from 'lib/clickhouse';
|
||||||
import { runQuery, PRISMA, CLICKHOUSE } from 'lib/db';
|
import { runQuery, PRISMA, CLICKHOUSE } from 'lib/db';
|
||||||
import { QueryFilters } from 'lib/types';
|
import { PageParams, QueryFilters } from 'lib/types';
|
||||||
|
|
||||||
export async function getSessions(...args: [websiteId: string, filters: QueryFilters]) {
|
export async function getSessions(
|
||||||
|
...args: [websiteId: string, filters?: QueryFilters, pageParams?: PageParams]
|
||||||
|
) {
|
||||||
return runQuery({
|
return runQuery({
|
||||||
[PRISMA]: () => relationalQuery(...args),
|
[PRISMA]: () => relationalQuery(...args),
|
||||||
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
async function relationalQuery(websiteId: string, filters: QueryFilters, pageParams: PageParams) {
|
||||||
const { startDate } = filters;
|
const { pagedQuery } = prisma;
|
||||||
|
|
||||||
return prisma.client.session
|
const where = {
|
||||||
.findMany({
|
...filters,
|
||||||
where: {
|
id: websiteId,
|
||||||
websiteId,
|
};
|
||||||
createdAt: {
|
|
||||||
gte: startDate,
|
return pagedQuery('session', { where }, pageParams);
|
||||||
},
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
createdAt: 'desc',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then(a => {
|
|
||||||
return Object.values(a).map(a => {
|
|
||||||
return {
|
|
||||||
...a,
|
|
||||||
timestamp: new Date(a.createdAt).getTime() / 1000,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
async function clickhouseQuery(websiteId: string, filters: QueryFilters, pageParams?: PageParams) {
|
||||||
const { rawQuery } = clickhouse;
|
const { pagedQuery, parseFilters } = clickhouse;
|
||||||
const { startDate } = filters;
|
const { params, dateQuery, filterQuery } = await parseFilters(websiteId, filters);
|
||||||
|
|
||||||
return rawQuery(
|
return pagedQuery(
|
||||||
`
|
`
|
||||||
select
|
select
|
||||||
session_id as id,
|
session_id as id,
|
||||||
@ -58,12 +46,11 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
|||||||
city
|
city
|
||||||
from website_event
|
from website_event
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at >= {startDate:DateTime64}
|
${dateQuery}
|
||||||
|
${filterQuery}
|
||||||
order by created_at desc
|
order by created_at desc
|
||||||
`,
|
`,
|
||||||
{
|
params,
|
||||||
websiteId,
|
pageParams,
|
||||||
startDate,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
export * from './admin/report';
|
export * from 'queries/prisma/report';
|
||||||
export * from './admin/team';
|
export * from 'queries/prisma/team';
|
||||||
export * from './admin/teamUser';
|
export * from 'queries/prisma/teamUser';
|
||||||
export * from './admin/user';
|
export * from 'queries/prisma/user';
|
||||||
export * from './admin/website';
|
export * from 'queries/prisma/website';
|
||||||
export * from './analytics/events/getEventMetrics';
|
export * from './analytics/events/getEventMetrics';
|
||||||
export * from './analytics/events/getEventUsage';
|
export * from './analytics/events/getEventUsage';
|
||||||
export * from './analytics/events/getEvents';
|
export * from './analytics/events/getEvents';
|
||||||
|
@ -17,9 +17,9 @@ export async function getReport(reportId: string): Promise<Report> {
|
|||||||
|
|
||||||
export async function getReports(
|
export async function getReports(
|
||||||
criteria: ReportFindManyArgs,
|
criteria: ReportFindManyArgs,
|
||||||
filters: PageParams = {},
|
pageParams: PageParams = {},
|
||||||
): Promise<PageResult<Report[]>> {
|
): Promise<PageResult<Report[]>> {
|
||||||
const { query } = filters;
|
const { query } = pageParams;
|
||||||
|
|
||||||
const where: Prisma.ReportWhereInput = {
|
const where: Prisma.ReportWhereInput = {
|
||||||
...criteria.where,
|
...criteria.where,
|
||||||
@ -45,7 +45,7 @@ export async function getReports(
|
|||||||
]),
|
]),
|
||||||
};
|
};
|
||||||
|
|
||||||
return prisma.pagedQuery('report', { ...criteria, where }, filters);
|
return prisma.pagedQuery('report', { ...criteria, where }, pageParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUserReports(
|
export async function getUserReports(
|
@ -49,9 +49,9 @@ export async function getUserByUsername(username: string, options: GetUserOption
|
|||||||
|
|
||||||
export async function getUsers(
|
export async function getUsers(
|
||||||
criteria: UserFindManyArgs,
|
criteria: UserFindManyArgs,
|
||||||
filters?: PageParams,
|
pageParams?: PageParams,
|
||||||
): Promise<PageResult<User[]>> {
|
): Promise<PageResult<User[]>> {
|
||||||
const { query } = filters;
|
const { query } = pageParams;
|
||||||
|
|
||||||
const where: Prisma.UserWhereInput = {
|
const where: Prisma.UserWhereInput = {
|
||||||
...criteria.where,
|
...criteria.where,
|
||||||
@ -68,7 +68,7 @@ export async function getUsers(
|
|||||||
{
|
{
|
||||||
orderBy: 'createdAt',
|
orderBy: 'createdAt',
|
||||||
sortDescending: true,
|
sortDescending: true,
|
||||||
...filters,
|
...pageParams,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -27,9 +27,9 @@ export async function getSharedWebsite(shareId: string) {
|
|||||||
|
|
||||||
export async function getWebsites(
|
export async function getWebsites(
|
||||||
criteria: WebsiteFindManyArgs,
|
criteria: WebsiteFindManyArgs,
|
||||||
filters: PageParams,
|
pageParams: PageParams,
|
||||||
): Promise<PageResult<Website[]>> {
|
): Promise<PageResult<Website[]>> {
|
||||||
const { query } = filters;
|
const { query } = pageParams;
|
||||||
|
|
||||||
const where: Prisma.WebsiteWhereInput = {
|
const where: Prisma.WebsiteWhereInput = {
|
||||||
...criteria.where,
|
...criteria.where,
|
||||||
@ -42,7 +42,7 @@ export async function getWebsites(
|
|||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
return prisma.pagedQuery('website', { ...criteria, where }, filters);
|
return prisma.pagedQuery('website', { ...criteria, where }, pageParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllWebsites(userId: string) {
|
export async function getAllWebsites(userId: string) {
|
Loading…
Reference in New Issue
Block a user