Merge 334297f194
into 586529a5ca
commit
f361dc5f53
|
@ -6,6 +6,7 @@ import classNames from 'classnames';
|
|||
import useApi from 'hooks/useApi';
|
||||
import { percentFilter } from 'lib/filters';
|
||||
import useDateRange from 'hooks/useDateRange';
|
||||
import useTimezone from 'hooks/useTimezone';
|
||||
import usePageQuery from 'hooks/usePageQuery';
|
||||
import ErrorMessage from 'components/common/ErrorMessage';
|
||||
import DataTable from './DataTable';
|
||||
|
@ -26,7 +27,8 @@ export function MetricsTable({
|
|||
delay = null,
|
||||
...props
|
||||
}) {
|
||||
const [{ startDate, endDate, modified }] = useDateRange(websiteId);
|
||||
const [{ startDate, endDate, modified, unit }] = useDateRange(websiteId);
|
||||
const [timezone] = useTimezone();
|
||||
const {
|
||||
resolveUrl,
|
||||
router,
|
||||
|
@ -53,6 +55,9 @@ export function MetricsTable({
|
|||
country,
|
||||
region,
|
||||
city,
|
||||
unit,
|
||||
timezone,
|
||||
includeEventData: type === 'event',
|
||||
}),
|
||||
{ onSuccess: onDataLoad, retryDelay: delay || DEFAULT_ANIMATION_DURATION },
|
||||
);
|
||||
|
@ -69,7 +74,7 @@ export function MetricsTable({
|
|||
return items.sort(firstBy('y', -1).thenBy('x'));
|
||||
}
|
||||
return [];
|
||||
}, [data, error, dataFilter, filterOptions]);
|
||||
}, [data, dataFilter, filterOptions, limit]);
|
||||
const { dir } = useLocale();
|
||||
|
||||
return (
|
||||
|
|
|
@ -104,6 +104,7 @@ CREATE TABLE umami.event_data
|
|||
event_string_value Nullable(String),
|
||||
event_numeric_value Nullable(Decimal64(4)), --922337203685477.5625
|
||||
event_date_value Nullable(DateTime('UTC')),
|
||||
event_bool_value Nullable(Boolean),
|
||||
event_data_type UInt32,
|
||||
created_at DateTime('UTC')
|
||||
)
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE `event_data` ADD COLUMN `event_bool_value` BOOLEAN NULL;
|
|
@ -14,7 +14,7 @@ model User {
|
|||
password String @db.VarChar(60)
|
||||
role String @map("role") @db.VarChar(50)
|
||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamp(0)
|
||||
updatedAt DateTime? @map("updated_at") @updatedAt @db.Timestamp(0)
|
||||
updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamp(0)
|
||||
deletedAt DateTime? @map("deleted_at") @db.Timestamp(0)
|
||||
|
||||
website Website[]
|
||||
|
@ -53,7 +53,7 @@ model Website {
|
|||
resetAt DateTime? @map("reset_at") @db.Timestamp(0)
|
||||
userId String? @map("user_id") @db.VarChar(36)
|
||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamp(0)
|
||||
updatedAt DateTime? @map("updated_at") @updatedAt @db.Timestamp(0)
|
||||
updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamp(0)
|
||||
deletedAt DateTime? @map("deleted_at") @db.Timestamp(0)
|
||||
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
|
@ -99,6 +99,7 @@ model EventData {
|
|||
eventStringValue String? @map("event_string_value") @db.VarChar(500)
|
||||
eventNumericValue Decimal? @map("event_numeric_value") @db.Decimal(19, 4)
|
||||
eventDateValue DateTime? @map("event_date_value") @db.Timestamp(0)
|
||||
eventBoolValue Boolean? @map("event_bool_value")
|
||||
eventDataType Int @map("event_data_type") @db.UnsignedInt
|
||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamp(0)
|
||||
|
||||
|
@ -117,7 +118,7 @@ model Team {
|
|||
name String @db.VarChar(50)
|
||||
accessCode String? @unique @map("access_code") @db.VarChar(50)
|
||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamp(0)
|
||||
updatedAt DateTime? @map("updated_at") @updatedAt @db.Timestamp(0)
|
||||
updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamp(0)
|
||||
|
||||
teamUser TeamUser[]
|
||||
teamWebsite TeamWebsite[]
|
||||
|
@ -132,7 +133,7 @@ model TeamUser {
|
|||
userId String @map("user_id") @db.VarChar(36)
|
||||
role String @map("role") @db.VarChar(50)
|
||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamp(0)
|
||||
updatedAt DateTime? @map("updated_at") @updatedAt @db.Timestamp(0)
|
||||
updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamp(0)
|
||||
|
||||
team Team @relation(fields: [teamId], references: [id])
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "event_data" ADD COLUMN "event_bool_value" BOOLEAN;
|
|
@ -14,7 +14,7 @@ model User {
|
|||
password String @db.VarChar(60)
|
||||
role String @map("role") @db.VarChar(50)
|
||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||
updatedAt DateTime? @map("updated_at") @updatedAt @db.Timestamptz(6)
|
||||
updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6)
|
||||
deletedAt DateTime? @map("deleted_at") @db.Timestamptz(6)
|
||||
|
||||
website Website[]
|
||||
|
@ -53,7 +53,7 @@ model Website {
|
|||
resetAt DateTime? @map("reset_at") @db.Timestamptz(6)
|
||||
userId String? @map("user_id") @db.Uuid
|
||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||
updatedAt DateTime? @map("updated_at") @updatedAt @db.Timestamptz(6)
|
||||
updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6)
|
||||
deletedAt DateTime? @map("deleted_at") @db.Timestamptz(6)
|
||||
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
|
@ -99,6 +99,7 @@ model EventData {
|
|||
eventStringValue String? @map("event_string_value") @db.VarChar(500)
|
||||
eventNumericValue Decimal? @map("event_numeric_value") @db.Decimal(19, 4)
|
||||
eventDateValue DateTime? @map("event_date_value") @db.Timestamptz(6)
|
||||
eventBoolValue Boolean? @map("event_bool_value") @db.Boolean
|
||||
eventDataType Int @map("event_data_type") @db.Integer
|
||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||
|
||||
|
@ -116,7 +117,7 @@ model Team {
|
|||
name String @db.VarChar(50)
|
||||
accessCode String? @unique @map("access_code") @db.VarChar(50)
|
||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||
updatedAt DateTime? @map("updated_at") @updatedAt @db.Timestamptz(6)
|
||||
updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6)
|
||||
|
||||
teamUser TeamUser[]
|
||||
teamWebsite TeamWebsite[]
|
||||
|
@ -131,7 +132,7 @@ model TeamUser {
|
|||
userId String @map("user_id") @db.Uuid
|
||||
role String @map("role") @db.VarChar(50)
|
||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||
updatedAt DateTime? @map("updated_at") @updatedAt @db.Timestamptz(6)
|
||||
updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6)
|
||||
|
||||
team Team @relation(fields: [teamId], references: [id])
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
|
|
@ -69,6 +69,7 @@ export interface WebsiteActive {
|
|||
export interface WebsiteMetric {
|
||||
x: string;
|
||||
y: number;
|
||||
d?: unknown;
|
||||
}
|
||||
|
||||
export interface WebsiteMetricFilter {
|
||||
|
@ -90,6 +91,7 @@ export interface WebsiteEventMetric {
|
|||
x: string;
|
||||
t: string;
|
||||
y: number;
|
||||
d?: unknown;
|
||||
}
|
||||
|
||||
export interface WebsiteEventDataMetric {
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { NextApiResponse } from 'next';
|
||||
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||
import { methodNotAllowed, ok, unauthorized, badRequest } from 'next-basics';
|
||||
import { WebsiteMetric, NextApiRequestQueryBody } from 'lib/types';
|
||||
import { canViewWebsite } from 'lib/auth';
|
||||
import { useAuth, useCors } from 'lib/middleware';
|
||||
import { SESSION_COLUMNS, EVENT_COLUMNS, FILTER_COLUMNS } from 'lib/constants';
|
||||
import { getPageviewMetrics, getSessionMetrics } from 'queries';
|
||||
import { getEventMetrics, getPageviewMetrics, getSessionMetrics } from 'queries';
|
||||
import moment from 'moment-timezone';
|
||||
const unitTypes = ['year', 'month', 'hour', 'day'];
|
||||
|
||||
export interface WebsiteMetricsRequestQuery {
|
||||
id: string;
|
||||
|
@ -47,6 +49,9 @@ export default async (
|
|||
country,
|
||||
region,
|
||||
city,
|
||||
includeEventData,
|
||||
timezone,
|
||||
unit,
|
||||
} = req.query;
|
||||
|
||||
if (req.method === 'GET') {
|
||||
|
@ -54,6 +59,15 @@ export default async (
|
|||
return unauthorized(res);
|
||||
}
|
||||
|
||||
if (
|
||||
typeof timezone !== 'string' ||
|
||||
typeof unit !== 'string' ||
|
||||
!moment.tz.zone(String(timezone)) ||
|
||||
!unitTypes.includes(String(unit))
|
||||
) {
|
||||
return badRequest(res);
|
||||
}
|
||||
|
||||
const startDate = new Date(+startAt);
|
||||
const endDate = new Date(+endAt);
|
||||
|
||||
|
@ -114,6 +128,17 @@ export default async (
|
|||
|
||||
filters[type] = undefined;
|
||||
|
||||
if (type === 'event' && includeEventData) {
|
||||
const data = await getEventMetrics(websiteId, {
|
||||
startDate,
|
||||
endDate,
|
||||
timezone,
|
||||
unit,
|
||||
filters: { url: filters.url, eventName: undefined },
|
||||
});
|
||||
|
||||
return ok(res, data);
|
||||
}
|
||||
const data = await getPageviewMetrics(websiteId, {
|
||||
startDate,
|
||||
endDate,
|
||||
|
|
|
@ -52,18 +52,38 @@ async function relationalQuery(
|
|||
const filterQuery = getFilterQuery(filters, params);
|
||||
|
||||
return rawQuery(
|
||||
`select
|
||||
event_name x,
|
||||
${getDateQuery('created_at', unit, timezone)} t,
|
||||
`with event_data as (
|
||||
select d.website_event_id,
|
||||
jsonb_object_agg(
|
||||
d.event_key,
|
||||
case
|
||||
d.event_data_type
|
||||
when 1 then to_jsonb(d.event_string_value) -- string
|
||||
when 2 then to_jsonb(d.event_numeric_value) -- number
|
||||
when 3 then to_jsonb(d.event_bool_value) -- boolean
|
||||
when 4 then to_jsonb(d.event_date_value) -- date
|
||||
when 5 then d.event_string_value::jsonb -- array
|
||||
end
|
||||
) filter (
|
||||
where d.event_key is not null
|
||||
) as event_data
|
||||
from event_data d
|
||||
group by d.website_event_id
|
||||
)
|
||||
select
|
||||
w.event_name x,
|
||||
d.event_data d,
|
||||
${getDateQuery('w.created_at', unit, timezone)} t,
|
||||
count(*) y
|
||||
from website_event
|
||||
from website_event w
|
||||
left join event_data d on w.event_id = d.website_event_id
|
||||
where website_id = $1${toUuid()}
|
||||
and created_at >= $2
|
||||
and created_at between $3 and $4
|
||||
and event_type = ${EVENT_TYPE.customEvent}
|
||||
${filterQuery}
|
||||
group by 1, 2
|
||||
order by 2`,
|
||||
group by 1, 2, 3
|
||||
order by 3`,
|
||||
params,
|
||||
);
|
||||
}
|
||||
|
@ -93,17 +113,37 @@ async function clickhouseQuery(
|
|||
const params = { websiteId };
|
||||
|
||||
return rawQuery(
|
||||
`select
|
||||
event_name x,
|
||||
${getDateQuery('created_at', unit, timezone)} t,
|
||||
`with event_data as (
|
||||
select d.website_event_id,
|
||||
jsonb_object_agg(
|
||||
d.event_key,
|
||||
case
|
||||
d.event_data_type
|
||||
when 1 then to_jsonb(d.event_string_value) -- string
|
||||
when 2 then to_jsonb(d.event_numeric_value) -- number
|
||||
when 3 then to_jsonb(d.event_bool_value) -- boolean
|
||||
when 4 then to_jsonb(d.event_date_value) -- date
|
||||
when 5 then d.event_string_value::jsonb -- array
|
||||
end
|
||||
) filter (
|
||||
where d.event_key is not null
|
||||
) as event_data
|
||||
from event_data d
|
||||
group by d.website_event_id
|
||||
)
|
||||
select
|
||||
w.event_name x,
|
||||
d.event_data d,
|
||||
${getDateQuery('w.created_at', unit, timezone)} t,
|
||||
count(*) y
|
||||
from website_event
|
||||
from website_event w
|
||||
left join event_data d on w.event_id = d.website_event_id
|
||||
where website_id = {websiteId:UUID}
|
||||
and event_type = ${EVENT_TYPE.customEvent}
|
||||
and created_at >= ${getDateFormat(resetDate)}
|
||||
and ${getBetweenDates('created_at', startDate, endDate)}
|
||||
and w.event_type = ${EVENT_TYPE.customEvent}
|
||||
and w.created_at >= ${getDateFormat(resetDate)}
|
||||
and ${getBetweenDates('w.created_at', startDate, endDate)}
|
||||
${getFilterQuery(filters, params)}
|
||||
group by x, t
|
||||
group by x, d, t
|
||||
order by t`,
|
||||
params,
|
||||
);
|
||||
|
|
|
@ -38,13 +38,12 @@ async function relationalQuery(data: {
|
|||
websiteId,
|
||||
eventKey: a.key,
|
||||
eventStringValue:
|
||||
a.eventDataType === EVENT_DATA_TYPE.string ||
|
||||
a.eventDataType === EVENT_DATA_TYPE.boolean ||
|
||||
a.eventDataType === EVENT_DATA_TYPE.array
|
||||
a.eventDataType === EVENT_DATA_TYPE.string || a.eventDataType === EVENT_DATA_TYPE.array
|
||||
? a.value
|
||||
: null,
|
||||
eventNumericValue: a.eventDataType === EVENT_DATA_TYPE.number ? a.value : null,
|
||||
eventDateValue: a.eventDataType === EVENT_DATA_TYPE.date ? new Date(a.value) : null,
|
||||
eventBoolValue: a.eventDataType === EVENT_DATA_TYPE.boolean ? a.value : null,
|
||||
eventDataType: a.eventDataType,
|
||||
}));
|
||||
|
||||
|
@ -76,13 +75,12 @@ async function clickhouseQuery(data: {
|
|||
event_name: eventName,
|
||||
event_key: a.key,
|
||||
event_string_value:
|
||||
a.eventDataType === EVENT_DATA_TYPE.string ||
|
||||
a.eventDataType === EVENT_DATA_TYPE.boolean ||
|
||||
a.eventDataType === EVENT_DATA_TYPE.array
|
||||
a.eventDataType === EVENT_DATA_TYPE.string || a.eventDataType === EVENT_DATA_TYPE.array
|
||||
? a.value
|
||||
: null,
|
||||
event_numeric_value: a.eventDataType === EVENT_DATA_TYPE.number ? a.value : null,
|
||||
event_date_value: a.eventDataType === EVENT_DATA_TYPE.date ? getDateFormat(a.value) : null,
|
||||
event_bool_value: a.eventDataType === EVENT_DATA_TYPE.boolean ? a.value : null,
|
||||
event_data_type: a.eventDataType,
|
||||
created_at: createdAt,
|
||||
}));
|
||||
|
|
|
@ -102,7 +102,7 @@ async function clickhouseQuery(
|
|||
where website_id = {websiteId:UUID}
|
||||
and event_type = {eventType:UInt32}
|
||||
and created_at >= ${getDateFormat(resetDate)}
|
||||
and ${getBetweenDates('created_at', startDate, endDate)}
|
||||
and ${getBetweenDates('created_at', startDate, endDate)}
|
||||
${excludeDomain}
|
||||
${filterQuery}
|
||||
group by x
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
select *
|
||||
from event_data -- WHERE event_data.website_event_id = '91bac2f4-4039-4b2a-a2ab-8c86f71ef95d'
|
|
@ -0,0 +1,30 @@
|
|||
WITH event_data_query AS (
|
||||
SELECT d.website_event_id,
|
||||
jsonb_object_agg(
|
||||
d.event_key,
|
||||
CASE
|
||||
d.event_data_type
|
||||
when 1 then to_jsonb(d.event_string_value) -- string
|
||||
when 2 then to_jsonb(d.event_numeric_value) -- number
|
||||
when 3 then to_jsonb(d.event_bool_value) -- boolean
|
||||
when 4 then to_jsonb(d.event_date_value) -- date
|
||||
when 5 then d.event_string_value::jsonb -- array
|
||||
end
|
||||
) filter (
|
||||
where d.event_key is not null
|
||||
) as event_data
|
||||
FROM event_data d
|
||||
GROUP BY d.website_event_id
|
||||
)
|
||||
select e.event_name x,
|
||||
to_char(
|
||||
date_trunc('hour', e.created_at),
|
||||
'YYYY-MM-DD HH24:00:00'
|
||||
) c,
|
||||
edq.event_data::jsonb->'data.target' t,
|
||||
edq.event_data::jsonb->'data.release' r
|
||||
from website_event e
|
||||
LEFT JOIN event_data_query edq ON e.event_id = edq.website_event_id
|
||||
where e.event_name = 'Outbound click'
|
||||
or e.event_name = 'NewSong PRO click'
|
||||
order by c desc
|
Loading…
Reference in New Issue