mirror of
https://github.com/kremalicious/umami.git
synced 2024-12-24 18:26:20 +01:00
remove event_data. (#1804)
This commit is contained in:
parent
94165ca5ad
commit
82f0bc3d2b
@ -1,46 +0,0 @@
|
|||||||
import EventDataForm from 'components/metrics/EventDataForm';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { Button, Icon, Modal, Icons } from 'react-basics';
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
import styles from './EventDataButton.module.css';
|
|
||||||
|
|
||||||
function EventDataButton({ websiteId }) {
|
|
||||||
const [showEventData, setShowEventData] = useState(false);
|
|
||||||
|
|
||||||
function handleClick() {
|
|
||||||
if (!showEventData) {
|
|
||||||
setShowEventData(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleClose() {
|
|
||||||
setShowEventData(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
tooltip={<FormattedMessage id="label.event-data" defaultMessage="Event" />}
|
|
||||||
tooltipId="button-event"
|
|
||||||
size="sm"
|
|
||||||
onClick={handleClick}
|
|
||||||
className={styles.button}
|
|
||||||
>
|
|
||||||
<Icon>
|
|
||||||
<Icons.More />
|
|
||||||
</Icon>
|
|
||||||
Event Data
|
|
||||||
</Button>
|
|
||||||
{showEventData && (
|
|
||||||
<Modal
|
|
||||||
title={<FormattedMessage id="label.event-data" defaultMessage="Query Event Data" />}
|
|
||||||
onClose={handleClose}
|
|
||||||
>
|
|
||||||
{close => <EventDataForm websiteId={websiteId} onClose={close} />}
|
|
||||||
</Modal>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EventDataButton;
|
|
@ -1,4 +0,0 @@
|
|||||||
.button {
|
|
||||||
width: fit-content;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
@ -1,241 +0,0 @@
|
|||||||
import { useMutation } from '@tanstack/react-query';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import DateFilter from 'components/input/DateFilter';
|
|
||||||
import DataTable from 'components/metrics/DataTable';
|
|
||||||
import FilterTags from 'components/metrics/FilterTags';
|
|
||||||
import useApi from 'hooks/useApi';
|
|
||||||
import useDateRange from 'hooks/useDateRange';
|
|
||||||
import useTimezone from 'hooks/useTimezone';
|
|
||||||
import { useEffect, useState, useRef } from 'react';
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Dropdown,
|
|
||||||
Flexbox,
|
|
||||||
Form,
|
|
||||||
FormButtons,
|
|
||||||
FormInput,
|
|
||||||
FormRow,
|
|
||||||
Item,
|
|
||||||
TextField,
|
|
||||||
} from 'react-basics';
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
import { FormMessage } from '../layout/FormLayout';
|
|
||||||
import styles from './EventDataForm.module.css';
|
|
||||||
|
|
||||||
export const filterOptions = [
|
|
||||||
{ label: 'Count', value: 'count' },
|
|
||||||
{ label: 'Average', value: 'avg' },
|
|
||||||
{ label: 'Minimum', value: 'min' },
|
|
||||||
{ label: 'Maximum', value: 'max' },
|
|
||||||
{ label: 'Sum', value: 'sum' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const dateOptions = [
|
|
||||||
{ label: <FormattedMessage id="label.today" defaultMessage="Today" />, value: '1day' },
|
|
||||||
{
|
|
||||||
label: (
|
|
||||||
<FormattedMessage id="label.last-hours" defaultMessage="Last {x} hours" values={{ x: 24 }} />
|
|
||||||
),
|
|
||||||
value: '24hour',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: <FormattedMessage id="label.yesterday" defaultMessage="Yesterday" />,
|
|
||||||
value: '-1day',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: <FormattedMessage id="label.this-week" defaultMessage="This week" />,
|
|
||||||
value: '1week',
|
|
||||||
divider: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: (
|
|
||||||
<FormattedMessage id="label.last-days" defaultMessage="Last {x} days" values={{ x: 7 }} />
|
|
||||||
),
|
|
||||||
value: '7day',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: <FormattedMessage id="label.this-month" defaultMessage="This month" />,
|
|
||||||
value: '1month',
|
|
||||||
divider: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: (
|
|
||||||
<FormattedMessage id="label.last-days" defaultMessage="Last {x} days" values={{ x: 30 }} />
|
|
||||||
),
|
|
||||||
value: '30day',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: (
|
|
||||||
<FormattedMessage id="label.last-days" defaultMessage="Last {x} days" values={{ x: 90 }} />
|
|
||||||
),
|
|
||||||
value: '90day',
|
|
||||||
},
|
|
||||||
{ label: <FormattedMessage id="label.this-year" defaultMessage="This year" />, value: '1year' },
|
|
||||||
{
|
|
||||||
label: <FormattedMessage id="label.custom-range" defaultMessage="Custom range" />,
|
|
||||||
value: 'custom',
|
|
||||||
divider: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function EventDataForm({ websiteId, onClose, className }) {
|
|
||||||
const { post } = useApi();
|
|
||||||
const [message, setMessage] = useState();
|
|
||||||
const { mutate } = useMutation(data => post(`/websites/${websiteId}/eventdata`, data));
|
|
||||||
const [columns, setColumns] = useState({});
|
|
||||||
const [filters, setFilters] = useState({});
|
|
||||||
const [type, setType] = useState('');
|
|
||||||
const [data, setData] = useState([]);
|
|
||||||
const [dateRange, setDateRange] = useDateRange('report');
|
|
||||||
const { startDate, endDate, value } = dateRange;
|
|
||||||
const [timezone] = useTimezone();
|
|
||||||
const [isValid, setIsValid] = useState(false);
|
|
||||||
const columnRef = useRef(null);
|
|
||||||
const filterRef = useRef(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (Object.keys(columns).length > 0) {
|
|
||||||
setIsValid(true);
|
|
||||||
} else {
|
|
||||||
setIsValid(false);
|
|
||||||
}
|
|
||||||
}, [columns]);
|
|
||||||
|
|
||||||
const handleAddTag = (value, list, setState, ref, clearDropdown) => {
|
|
||||||
setState({ ...list, [`${value.field}`]: value.value });
|
|
||||||
|
|
||||||
ref.current.reset({ field: '', value: '' });
|
|
||||||
|
|
||||||
if (clearDropdown) {
|
|
||||||
setType('');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemoveTag = (value, list, setState) => {
|
|
||||||
const newList = { ...list };
|
|
||||||
|
|
||||||
delete newList[`${value}`];
|
|
||||||
|
|
||||||
setState(newList);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
const params = {
|
|
||||||
website_id: websiteId,
|
|
||||||
startAt: +startDate,
|
|
||||||
endAt: +endDate,
|
|
||||||
timezone,
|
|
||||||
columns,
|
|
||||||
filters,
|
|
||||||
};
|
|
||||||
|
|
||||||
mutate(params, {
|
|
||||||
onSuccess: async data => {
|
|
||||||
setData(data);
|
|
||||||
setMessage(null);
|
|
||||||
},
|
|
||||||
onError: async () => {
|
|
||||||
setMessage(
|
|
||||||
<FormattedMessage id="message.failure" defaultMessage="Something went wrong." />,
|
|
||||||
);
|
|
||||||
setData([]);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Flexbox className={styles.message} alignItems="center" direction="column">
|
|
||||||
<FormMessage>{message}</FormMessage>
|
|
||||||
</Flexbox>
|
|
||||||
<div className={classNames(styles.container, className)}>
|
|
||||||
<div className={styles.form}>
|
|
||||||
<div className={styles.filters}>
|
|
||||||
<FormRow label={<FormattedMessage id="label.date-range" defaultMessage="Date Range" />}>
|
|
||||||
<DateFilter
|
|
||||||
value={value}
|
|
||||||
startDate={startDate}
|
|
||||||
endDate={endDate}
|
|
||||||
onChange={setDateRange}
|
|
||||||
options={dateOptions}
|
|
||||||
/>
|
|
||||||
</FormRow>
|
|
||||||
</div>
|
|
||||||
<div className={styles.filters}>
|
|
||||||
<Form
|
|
||||||
ref={columnRef}
|
|
||||||
onSubmit={value =>
|
|
||||||
handleAddTag({ ...value, value: type }, columns, setColumns, columnRef, true)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<FormRow
|
|
||||||
label={<FormattedMessage id="label.field-name" defaultMessage="Field Name" />}
|
|
||||||
>
|
|
||||||
<FormInput name="field" rules={{ required: 'Required' }}>
|
|
||||||
<TextField />
|
|
||||||
</FormInput>
|
|
||||||
</FormRow>
|
|
||||||
<FormRow label={<FormattedMessage id="label.type" defaultMessage="Type" />}>
|
|
||||||
<FormInput name="value">
|
|
||||||
<Dropdown items={filterOptions} value={type} onChange={setType}>
|
|
||||||
{({ value, label }) => <Item key={value}>{label}</Item>}
|
|
||||||
</Dropdown>
|
|
||||||
</FormInput>
|
|
||||||
</FormRow>
|
|
||||||
<FormButtons className={styles.formButtons}>
|
|
||||||
<Button variant="action" type="submit">
|
|
||||||
<FormattedMessage id="label.add-column" defaultMessage="Add Column" />
|
|
||||||
</Button>
|
|
||||||
</FormButtons>
|
|
||||||
</Form>
|
|
||||||
<FilterTags
|
|
||||||
className={styles.filterTag}
|
|
||||||
params={columns}
|
|
||||||
onClick={value => handleRemoveTag(value, columns, setColumns)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.filters}>
|
|
||||||
<Form
|
|
||||||
ref={filterRef}
|
|
||||||
onSubmit={value => handleAddTag(value, filters, setFilters, filterRef)}
|
|
||||||
>
|
|
||||||
<FormRow
|
|
||||||
label={<FormattedMessage id="label.field-name" defaultMessage="Field Name" />}
|
|
||||||
>
|
|
||||||
<FormInput name="field">
|
|
||||||
<TextField />
|
|
||||||
</FormInput>
|
|
||||||
</FormRow>
|
|
||||||
<FormRow label={<FormattedMessage id="label.value" defaultMessage="Value" />}>
|
|
||||||
<FormInput name="value">
|
|
||||||
<TextField />
|
|
||||||
</FormInput>
|
|
||||||
</FormRow>
|
|
||||||
<FormButtons className={styles.formButtons}>
|
|
||||||
<Button variant="action" type="submit">
|
|
||||||
<FormattedMessage id="label.add-filter" defaultMessage="Add Filter" />
|
|
||||||
</Button>
|
|
||||||
</FormButtons>
|
|
||||||
</Form>
|
|
||||||
<FilterTags
|
|
||||||
className={styles.filterTag}
|
|
||||||
params={filters}
|
|
||||||
onClick={value => handleRemoveTag(value, filters, setFilters)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<DataTable className={styles.table} data={data} title="Results" showPercentage={false} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<FormButtons>
|
|
||||||
<Button variant="action" onClick={handleSubmit} disabled={!isValid}>
|
|
||||||
<FormattedMessage id="label.search" defaultMessage="Search" />
|
|
||||||
</Button>
|
|
||||||
<Button onClick={onClose}>
|
|
||||||
<FormattedMessage id="label.cancel" defaultMessage="Cancel" />
|
|
||||||
</Button>
|
|
||||||
</FormButtons>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form {
|
|
||||||
padding: 20px;
|
|
||||||
border-right: 1px solid var(--base300);
|
|
||||||
width: 425px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filters {
|
|
||||||
padding: 10px 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filters + .filters {
|
|
||||||
border-top: 1px solid var(--base300);
|
|
||||||
min-height: 250px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table {
|
|
||||||
padding: 10px;
|
|
||||||
min-height: 430px;
|
|
||||||
min-width: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.formButtons {
|
|
||||||
justify-content: flex-start;
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown {
|
|
||||||
min-height: 39px;
|
|
||||||
min-width: 240px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filterTag {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin: 10px 5px 5px 5px;
|
|
||||||
}
|
|
@ -25,7 +25,6 @@ CREATE TABLE event
|
|||||||
--event
|
--event
|
||||||
event_type UInt32,
|
event_type UInt32,
|
||||||
event_name String,
|
event_name String,
|
||||||
event_data JSON,
|
|
||||||
created_at DateTime('UTC')
|
created_at DateTime('UTC')
|
||||||
)
|
)
|
||||||
engine = MergeTree
|
engine = MergeTree
|
||||||
@ -55,7 +54,6 @@ CREATE TABLE event_queue (
|
|||||||
--event
|
--event
|
||||||
event_type UInt32,
|
event_type UInt32,
|
||||||
event_name String,
|
event_name String,
|
||||||
event_data String,
|
|
||||||
created_at DateTime('UTC')
|
created_at DateTime('UTC')
|
||||||
)
|
)
|
||||||
ENGINE = Kafka
|
ENGINE = Kafka
|
||||||
@ -86,6 +84,5 @@ SELECT website_id,
|
|||||||
page_title,
|
page_title,
|
||||||
event_type,
|
event_type,
|
||||||
event_name,
|
event_name,
|
||||||
event_data,
|
|
||||||
created_at
|
created_at
|
||||||
FROM event_queue;
|
FROM event_queue;
|
@ -66,7 +66,6 @@ CREATE TABLE `website_event` (
|
|||||||
`page_title` VARCHAR(500) NULL,
|
`page_title` VARCHAR(500) NULL,
|
||||||
`event_type` INTEGER UNSIGNED NOT NULL DEFAULT 1,
|
`event_type` INTEGER UNSIGNED NOT NULL DEFAULT 1,
|
||||||
`event_name` VARCHAR(50) NULL,
|
`event_name` VARCHAR(50) NULL,
|
||||||
`event_data` JSON NULL,
|
|
||||||
|
|
||||||
INDEX `website_event_created_at_idx`(`created_at`),
|
INDEX `website_event_created_at_idx`(`created_at`),
|
||||||
INDEX `website_event_session_id_idx`(`session_id`),
|
INDEX `website_event_session_id_idx`(`session_id`),
|
||||||
|
@ -3,8 +3,8 @@ generator client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
provider = "mysql"
|
provider = "mysql"
|
||||||
url = env("DATABASE_URL")
|
url = env("DATABASE_URL")
|
||||||
relationMode = "prisma"
|
relationMode = "prisma"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,8 +17,8 @@ model User {
|
|||||||
updatedAt DateTime? @map("updated_at") @db.Timestamp(0)
|
updatedAt DateTime? @map("updated_at") @db.Timestamp(0)
|
||||||
deletedAt DateTime? @map("deleted_at") @db.Timestamp(0)
|
deletedAt DateTime? @map("deleted_at") @db.Timestamp(0)
|
||||||
|
|
||||||
teamUser TeamUser[]
|
teamUser TeamUser[]
|
||||||
Website Website[]
|
Website Website[]
|
||||||
teamWebsite TeamWebsite[]
|
teamWebsite TeamWebsite[]
|
||||||
|
|
||||||
@@map("user")
|
@@map("user")
|
||||||
@ -55,7 +55,7 @@ model Website {
|
|||||||
updatedAt DateTime? @map("updated_at") @db.Timestamp(0)
|
updatedAt DateTime? @map("updated_at") @db.Timestamp(0)
|
||||||
deletedAt DateTime? @map("deleted_at") @db.Timestamp(0)
|
deletedAt DateTime? @map("deleted_at") @db.Timestamp(0)
|
||||||
|
|
||||||
user User? @relation(fields: [userId], references: [id])
|
user User? @relation(fields: [userId], references: [id])
|
||||||
teamWebsite TeamWebsite[]
|
teamWebsite TeamWebsite[]
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@ -74,7 +74,6 @@ model WebsiteEvent {
|
|||||||
pageTitle String? @map("page_title") @db.VarChar(500)
|
pageTitle String? @map("page_title") @db.VarChar(500)
|
||||||
eventType Int @default(1) @map("event_type") @db.UnsignedInt
|
eventType Int @default(1) @map("event_type") @db.UnsignedInt
|
||||||
eventName String? @map("event_name") @db.VarChar(50)
|
eventName String? @map("event_name") @db.VarChar(50)
|
||||||
eventData Json? @map("event_data")
|
|
||||||
|
|
||||||
@@index([createdAt])
|
@@index([createdAt])
|
||||||
@@index([sessionId])
|
@@index([sessionId])
|
||||||
@ -92,7 +91,7 @@ model Team {
|
|||||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamp(0)
|
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamp(0)
|
||||||
updatedAt DateTime? @map("updated_at") @db.Timestamp(0)
|
updatedAt DateTime? @map("updated_at") @db.Timestamp(0)
|
||||||
|
|
||||||
teamUsers TeamUser[]
|
teamUsers TeamUser[]
|
||||||
teamWebsite TeamWebsite[]
|
teamWebsite TeamWebsite[]
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@ -123,8 +122,8 @@ model TeamWebsite {
|
|||||||
websiteId String @map("website_id") @db.VarChar(36)
|
websiteId String @map("website_id") @db.VarChar(36)
|
||||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamp(0)
|
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamp(0)
|
||||||
|
|
||||||
team Team @relation(fields: [teamId], references: [id])
|
team Team @relation(fields: [teamId], references: [id])
|
||||||
user User @relation(fields: [userId], references: [id])
|
user User @relation(fields: [userId], references: [id])
|
||||||
website Website @relation(fields: [websiteId], references: [id])
|
website Website @relation(fields: [websiteId], references: [id])
|
||||||
|
|
||||||
@@index([teamId])
|
@@index([teamId])
|
||||||
|
@ -56,7 +56,6 @@ CREATE TABLE "website_event" (
|
|||||||
"referrer" VARCHAR(500),
|
"referrer" VARCHAR(500),
|
||||||
"event_type" INTEGER NOT NULL DEFAULT 1,
|
"event_type" INTEGER NOT NULL DEFAULT 1,
|
||||||
"event_name" VARCHAR(50),
|
"event_name" VARCHAR(50),
|
||||||
"event_data" JSONB,
|
|
||||||
|
|
||||||
CONSTRAINT "website_event_pkey" PRIMARY KEY ("event_id")
|
CONSTRAINT "website_event_pkey" PRIMARY KEY ("event_id")
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `event_data` on the `website_event` table. All the data in the column will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "website_event" DROP COLUMN "event_data";
|
@ -3,8 +3,8 @@ generator client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
provider = "postgresql"
|
provider = "postgresql"
|
||||||
url = env("DATABASE_URL")
|
url = env("DATABASE_URL")
|
||||||
relationMode = "prisma"
|
relationMode = "prisma"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,8 +17,8 @@ model User {
|
|||||||
updatedAt DateTime? @map("updated_at") @db.Timestamptz(6)
|
updatedAt DateTime? @map("updated_at") @db.Timestamptz(6)
|
||||||
deletedAt DateTime? @map("deleted_at") @db.Timestamptz(6)
|
deletedAt DateTime? @map("deleted_at") @db.Timestamptz(6)
|
||||||
|
|
||||||
Website Website[]
|
Website Website[]
|
||||||
teamUser TeamUser[]
|
teamUser TeamUser[]
|
||||||
teamWebsite TeamWebsite[]
|
teamWebsite TeamWebsite[]
|
||||||
|
|
||||||
@@map("user")
|
@@map("user")
|
||||||
@ -55,7 +55,7 @@ model Website {
|
|||||||
updatedAt DateTime? @map("updated_at") @db.Timestamptz(6)
|
updatedAt DateTime? @map("updated_at") @db.Timestamptz(6)
|
||||||
deletedAt DateTime? @map("deleted_at") @db.Timestamptz(6)
|
deletedAt DateTime? @map("deleted_at") @db.Timestamptz(6)
|
||||||
|
|
||||||
user User? @relation(fields: [userId], references: [id])
|
user User? @relation(fields: [userId], references: [id])
|
||||||
teamWebsite TeamWebsite[]
|
teamWebsite TeamWebsite[]
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@ -74,7 +74,6 @@ model WebsiteEvent {
|
|||||||
pageTitle String? @map("page_title") @db.VarChar(500)
|
pageTitle String? @map("page_title") @db.VarChar(500)
|
||||||
eventType Int @default(1) @map("event_type") @db.Integer
|
eventType Int @default(1) @map("event_type") @db.Integer
|
||||||
eventName String? @map("event_name") @db.VarChar(50)
|
eventName String? @map("event_name") @db.VarChar(50)
|
||||||
eventData Json? @map("event_data")
|
|
||||||
|
|
||||||
@@index([createdAt])
|
@@index([createdAt])
|
||||||
@@index([sessionId])
|
@@index([sessionId])
|
||||||
@ -92,7 +91,7 @@ model Team {
|
|||||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||||
updatedAt DateTime? @map("updated_at") @db.Timestamptz(6)
|
updatedAt DateTime? @map("updated_at") @db.Timestamptz(6)
|
||||||
|
|
||||||
teamUser TeamUser[]
|
teamUser TeamUser[]
|
||||||
teamWebsite TeamWebsite[]
|
teamWebsite TeamWebsite[]
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@ -123,8 +122,8 @@ model TeamWebsite {
|
|||||||
websiteId String @map("website_id") @db.Uuid
|
websiteId String @map("website_id") @db.Uuid
|
||||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||||
|
|
||||||
team Team @relation(fields: [teamId], references: [id])
|
team Team @relation(fields: [teamId], references: [id])
|
||||||
user User @relation(fields: [userId], references: [id])
|
user User @relation(fields: [userId], references: [id])
|
||||||
website Website @relation(fields: [websiteId], references: [id])
|
website Website @relation(fields: [websiteId], references: [id])
|
||||||
|
|
||||||
@@index([teamId])
|
@@index([teamId])
|
||||||
|
@ -64,44 +64,6 @@ function getBetweenDates(field, startAt, endAt) {
|
|||||||
return `${field} between ${getDateFormat(startAt)} and ${getDateFormat(endAt)}`;
|
return `${field} between ${getDateFormat(startAt)} and ${getDateFormat(endAt)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getJsonField(column, property) {
|
|
||||||
return `${column}.${property}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getEventDataColumnsQuery(column, columns) {
|
|
||||||
const query = Object.keys(columns).reduce((arr, key) => {
|
|
||||||
const filter = columns[key];
|
|
||||||
|
|
||||||
if (filter === undefined) {
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
arr.push(`${filter}(${getJsonField(column, key)}) as "${filter}(${key})"`);
|
|
||||||
|
|
||||||
return arr;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return query.join(',\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
function getEventDataFilterQuery(column, filters) {
|
|
||||||
const query = Object.keys(filters).reduce((arr, key) => {
|
|
||||||
const filter = filters[key];
|
|
||||||
|
|
||||||
if (filter === undefined) {
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
arr.push(
|
|
||||||
`${getJsonField(column, key)} = ${typeof filter === 'string' ? `'${filter}'` : filter}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return arr;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return query.join('\nand ');
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFilterQuery(filters = {}, params = {}) {
|
function getFilterQuery(filters = {}, params = {}) {
|
||||||
const query = Object.keys(filters).reduce((arr, key) => {
|
const query = Object.keys(filters).reduce((arr, key) => {
|
||||||
const filter = filters[key];
|
const filter = filters[key];
|
||||||
@ -221,8 +183,6 @@ export default {
|
|||||||
getDateQuery,
|
getDateQuery,
|
||||||
getDateFormat,
|
getDateFormat,
|
||||||
getBetweenDates,
|
getBetweenDates,
|
||||||
getEventDataColumnsQuery,
|
|
||||||
getEventDataFilterQuery,
|
|
||||||
getFilterQuery,
|
getFilterQuery,
|
||||||
parseFilters,
|
parseFilters,
|
||||||
findUnique,
|
findUnique,
|
||||||
|
@ -64,64 +64,6 @@ function getTimestampInterval(field: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getJsonField(column: string, property: string, isNumber: boolean): string {
|
|
||||||
const db = getDatabaseType(process.env.DATABASE_URL);
|
|
||||||
|
|
||||||
if (db === POSTGRESQL) {
|
|
||||||
let accessor = `${column} ->> '${property}'`;
|
|
||||||
|
|
||||||
if (isNumber) {
|
|
||||||
accessor = `CAST(${accessor} AS DECIMAL)`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return accessor;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (db === MYSQL) {
|
|
||||||
return `${column} ->> "$.${property}"`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getEventDataColumnsQuery(column, columns): string {
|
|
||||||
const query = Object.keys(columns).reduce((arr, key) => {
|
|
||||||
const filter = columns[key];
|
|
||||||
|
|
||||||
if (filter === undefined) {
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isNumber = ['sum', 'avg', 'min', 'max'].some(a => a === filter);
|
|
||||||
|
|
||||||
arr.push(`${filter}(${getJsonField(column, key, isNumber)}) as "${filter}(${key})"`);
|
|
||||||
|
|
||||||
return arr;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return query.join(',\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
function getEventDataFilterQuery(column, filters): string {
|
|
||||||
const query = Object.keys(filters).reduce((arr, key) => {
|
|
||||||
const filter = filters[key];
|
|
||||||
|
|
||||||
if (filter === undefined) {
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isNumber = filter && typeof filter === 'number';
|
|
||||||
|
|
||||||
arr.push(
|
|
||||||
`${getJsonField(column, key, isNumber)} = ${
|
|
||||||
typeof filter === 'string' ? `'${filter}'` : filter
|
|
||||||
}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return arr;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return query.join('\nand ');
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFilterQuery(filters = {}, params = []): string {
|
function getFilterQuery(filters = {}, params = []): string {
|
||||||
const query = Object.keys(filters).reduce((arr, key) => {
|
const query = Object.keys(filters).reduce((arr, key) => {
|
||||||
const filter = filters[key];
|
const filter = filters[key];
|
||||||
@ -226,8 +168,6 @@ export default {
|
|||||||
getDateQuery,
|
getDateQuery,
|
||||||
getTimestampInterval,
|
getTimestampInterval,
|
||||||
getFilterQuery,
|
getFilterQuery,
|
||||||
getEventDataColumnsQuery,
|
|
||||||
getEventDataFilterQuery,
|
|
||||||
toUuid,
|
toUuid,
|
||||||
parseFilters,
|
parseFilters,
|
||||||
rawQuery,
|
rawQuery,
|
||||||
|
@ -50,10 +50,6 @@ export interface WebsiteActive {
|
|||||||
x: number;
|
x: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WebsiteEventDataMetric {
|
|
||||||
[key: string]: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WebsiteMetric {
|
export interface WebsiteMetric {
|
||||||
x: string;
|
x: string;
|
||||||
y: number;
|
y: number;
|
||||||
|
@ -34,19 +34,9 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => {
|
|||||||
|
|
||||||
const { type, payload } = getJsonBody(req);
|
const { type, payload } = getJsonBody(req);
|
||||||
|
|
||||||
const { referrer, eventName, eventData, pageTitle } = payload;
|
const { referrer, eventName, pageTitle } = payload;
|
||||||
let { url } = payload;
|
let { url } = payload;
|
||||||
|
|
||||||
// Validate eventData is JSON
|
|
||||||
if (eventData && !(typeof eventData === 'object' && !Array.isArray(eventData))) {
|
|
||||||
return badRequest(res, 'Event Data must be in the form of a JSON Object.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate eventData is less than 100kB
|
|
||||||
if (eventData && new TextEncoder().encode(eventData).length / 1024 > 100) {
|
|
||||||
return badRequest(res, 'Event Data exceeds maximum size of 100 kB.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const ignoreIps = process.env.IGNORE_IP;
|
const ignoreIps = process.env.IGNORE_IP;
|
||||||
const ignoreHostnames = process.env.IGNORE_HOSTNAME;
|
const ignoreHostnames = process.env.IGNORE_HOSTNAME;
|
||||||
|
|
||||||
@ -106,7 +96,6 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => {
|
|||||||
referrer,
|
referrer,
|
||||||
pageTitle,
|
pageTitle,
|
||||||
eventName,
|
eventName,
|
||||||
eventData,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return badRequest(res);
|
return badRequest(res);
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
import { WebsiteMetric, NextApiRequestQueryBody } from 'lib/types';
|
|
||||||
import { canViewWebsite } from 'lib/auth';
|
|
||||||
import { useAuth, useCors } from 'lib/middleware';
|
|
||||||
import { NextApiResponse } from 'next';
|
|
||||||
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
|
||||||
import { getEventData } from 'queries';
|
|
||||||
|
|
||||||
export interface WebsiteEventDataRequestQuery {
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WebsiteEventDataRequestBody {
|
|
||||||
startAt: string;
|
|
||||||
endAt: string;
|
|
||||||
eventName: string;
|
|
||||||
columns: { [key: string]: 'count' | 'max' | 'min' | 'avg' | 'sum' };
|
|
||||||
filters?: { [key: string]: any };
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async (
|
|
||||||
req: NextApiRequestQueryBody<WebsiteEventDataRequestQuery, WebsiteEventDataRequestBody>,
|
|
||||||
res: NextApiResponse<WebsiteMetric>,
|
|
||||||
) => {
|
|
||||||
await useCors(req, res);
|
|
||||||
await useAuth(req, res);
|
|
||||||
|
|
||||||
const { id: websiteId } = req.query;
|
|
||||||
|
|
||||||
if (req.method === 'POST') {
|
|
||||||
if (!(await canViewWebsite(req.auth, websiteId))) {
|
|
||||||
return unauthorized(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { startAt, endAt, eventName, columns, filters } = req.body;
|
|
||||||
|
|
||||||
const startDate = new Date(+startAt);
|
|
||||||
const endDate = new Date(+endAt);
|
|
||||||
|
|
||||||
const events = await getEventData(websiteId, {
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
eventName,
|
|
||||||
columns,
|
|
||||||
filters,
|
|
||||||
});
|
|
||||||
|
|
||||||
return ok(res, events);
|
|
||||||
}
|
|
||||||
|
|
||||||
return methodNotAllowed(res);
|
|
||||||
};
|
|
@ -1,7 +1,6 @@
|
|||||||
import { WebsiteStats } from 'lib/types';
|
|
||||||
import { NextApiRequestQueryBody } from 'lib/types';
|
|
||||||
import { canViewWebsite } from 'lib/auth';
|
import { canViewWebsite } from 'lib/auth';
|
||||||
import { useAuth, useCors } from 'lib/middleware';
|
import { useAuth, useCors } from 'lib/middleware';
|
||||||
|
import { NextApiRequestQueryBody, WebsiteStats } from 'lib/types';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||||
import { getWebsiteStats } from 'queries';
|
import { getWebsiteStats } from 'queries';
|
||||||
|
@ -1,93 +0,0 @@
|
|||||||
import clickhouse from 'lib/clickhouse';
|
|
||||||
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
|
|
||||||
import prisma from 'lib/prisma';
|
|
||||||
import cache from 'lib/cache';
|
|
||||||
import { WebsiteMetric } from 'lib/types';
|
|
||||||
import { EVENT_TYPE } from 'lib/constants';
|
|
||||||
|
|
||||||
export async function getEventData(
|
|
||||||
...args: [
|
|
||||||
websiteId: string,
|
|
||||||
data: {
|
|
||||||
startDate: Date;
|
|
||||||
endDate: Date;
|
|
||||||
eventName: string;
|
|
||||||
columns: any;
|
|
||||||
filters: object;
|
|
||||||
},
|
|
||||||
]
|
|
||||||
): Promise<WebsiteMetric[]> {
|
|
||||||
return runQuery({
|
|
||||||
[PRISMA]: () => relationalQuery(...args),
|
|
||||||
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
|
||||||
}).then(results => {
|
|
||||||
return Object.keys(results[0]).map(a => {
|
|
||||||
return { x: a, y: results[0][`${a}`] };
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function relationalQuery(
|
|
||||||
websiteId: string,
|
|
||||||
data: {
|
|
||||||
startDate: Date;
|
|
||||||
endDate: Date;
|
|
||||||
eventName: string;
|
|
||||||
columns: any;
|
|
||||||
filters: object;
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
const { startDate, endDate, eventName, columns, filters } = data;
|
|
||||||
const { toUuid, rawQuery, getEventDataColumnsQuery, getEventDataFilterQuery } = prisma;
|
|
||||||
const params: any = [websiteId, startDate, endDate, eventName];
|
|
||||||
|
|
||||||
return rawQuery(
|
|
||||||
`select
|
|
||||||
${getEventDataColumnsQuery('event_data', columns)}
|
|
||||||
from website_event
|
|
||||||
where website_id = $1${toUuid()}
|
|
||||||
and created_at between $2 and $3
|
|
||||||
and event_type = ${EVENT_TYPE.customEvent}
|
|
||||||
${eventName ? `and eventName = $4` : ''}
|
|
||||||
${
|
|
||||||
Object.keys(filters).length > 0
|
|
||||||
? `and ${getEventDataFilterQuery('event_data', filters)}`
|
|
||||||
: ''
|
|
||||||
}`,
|
|
||||||
params,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function clickhouseQuery(
|
|
||||||
websiteId: string,
|
|
||||||
data: {
|
|
||||||
startDate: Date;
|
|
||||||
endDate: Date;
|
|
||||||
eventName: string;
|
|
||||||
columns: any;
|
|
||||||
filters: object;
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
const { startDate, endDate, eventName, columns, filters } = data;
|
|
||||||
const { rawQuery, getBetweenDates, getEventDataColumnsQuery, getEventDataFilterQuery } =
|
|
||||||
clickhouse;
|
|
||||||
const website = await cache.fetchWebsite(websiteId);
|
|
||||||
const params = { websiteId, revId: website?.revId || 0 };
|
|
||||||
|
|
||||||
return rawQuery(
|
|
||||||
`select
|
|
||||||
${getEventDataColumnsQuery('event_data', columns)}
|
|
||||||
from event
|
|
||||||
where website_id = {websiteId:UUID}
|
|
||||||
and rev_id = {revId:UInt32}
|
|
||||||
and event_type = ${EVENT_TYPE.customEvent}
|
|
||||||
${eventName ? `and eventName = ${eventName}` : ''}
|
|
||||||
and ${getBetweenDates('created_at', startDate, endDate)}
|
|
||||||
${
|
|
||||||
Object.keys(filters).length > 0
|
|
||||||
? `and ${getEventDataFilterQuery('event_data', filters)}`
|
|
||||||
: ''
|
|
||||||
}`,
|
|
||||||
params,
|
|
||||||
);
|
|
||||||
}
|
|
@ -12,7 +12,6 @@ export async function saveEvent(args: {
|
|||||||
referrer?: string;
|
referrer?: string;
|
||||||
pageTitle?: string;
|
pageTitle?: string;
|
||||||
eventName?: string;
|
eventName?: string;
|
||||||
eventData?: any;
|
|
||||||
hostname?: string;
|
hostname?: string;
|
||||||
browser?: string;
|
browser?: string;
|
||||||
os?: string;
|
os?: string;
|
||||||
@ -37,9 +36,8 @@ async function relationalQuery(data: {
|
|||||||
referrer?: string;
|
referrer?: string;
|
||||||
pageTitle?: string;
|
pageTitle?: string;
|
||||||
eventName?: string;
|
eventName?: string;
|
||||||
eventData?: any;
|
|
||||||
}) {
|
}) {
|
||||||
const { websiteId, id: sessionId, url, eventName, eventData, referrer, pageTitle } = data;
|
const { websiteId, id: sessionId, url, eventName, referrer, pageTitle } = data;
|
||||||
|
|
||||||
return prisma.client.websiteEvent.create({
|
return prisma.client.websiteEvent.create({
|
||||||
data: {
|
data: {
|
||||||
@ -51,7 +49,6 @@ async function relationalQuery(data: {
|
|||||||
pageTitle: pageTitle,
|
pageTitle: pageTitle,
|
||||||
eventType: EVENT_TYPE.customEvent,
|
eventType: EVENT_TYPE.customEvent,
|
||||||
eventName: eventName?.substring(0, EVENT_NAME_LENGTH),
|
eventName: eventName?.substring(0, EVENT_NAME_LENGTH),
|
||||||
eventData,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -63,7 +60,6 @@ async function clickhouseQuery(data) {
|
|||||||
url,
|
url,
|
||||||
pageTitle,
|
pageTitle,
|
||||||
eventName,
|
eventName,
|
||||||
eventData,
|
|
||||||
country,
|
country,
|
||||||
subdivision1,
|
subdivision1,
|
||||||
subdivision2,
|
subdivision2,
|
||||||
@ -77,7 +73,6 @@ async function clickhouseQuery(data) {
|
|||||||
website_id: websiteId,
|
website_id: websiteId,
|
||||||
session_id: sessionId,
|
session_id: sessionId,
|
||||||
event_id: uuid(),
|
event_id: uuid(),
|
||||||
rev_id: website?.revId || 0,
|
|
||||||
country: country ? country : null,
|
country: country ? country : null,
|
||||||
subdivision1: subdivision1 ? subdivision1 : null,
|
subdivision1: subdivision1 ? subdivision1 : null,
|
||||||
subdivision2: subdivision2 ? subdivision2 : null,
|
subdivision2: subdivision2 ? subdivision2 : null,
|
||||||
@ -86,7 +81,7 @@ async function clickhouseQuery(data) {
|
|||||||
page_title: pageTitle,
|
page_title: pageTitle,
|
||||||
event_type: EVENT_TYPE.customEvent,
|
event_type: EVENT_TYPE.customEvent,
|
||||||
event_name: eventName?.substring(0, EVENT_NAME_LENGTH),
|
event_name: eventName?.substring(0, EVENT_NAME_LENGTH),
|
||||||
event_data: eventData ? JSON.stringify(eventData) : null,
|
rev_id: website?.revId || 0,
|
||||||
created_at: getDateFormat(new Date()),
|
created_at: getDateFormat(new Date()),
|
||||||
...args,
|
...args,
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,6 @@ export * from './admin/user';
|
|||||||
export * from './admin/website';
|
export * from './admin/website';
|
||||||
export * from './analytics/event/getEventMetrics';
|
export * from './analytics/event/getEventMetrics';
|
||||||
export * from './analytics/event/getEvents';
|
export * from './analytics/event/getEvents';
|
||||||
export * from './analytics/event/getEventData';
|
|
||||||
export * from './analytics/event/saveEvent';
|
export * from './analytics/event/saveEvent';
|
||||||
export * from './analytics/pageview/getPageviewMetrics';
|
export * from './analytics/pageview/getPageviewMetrics';
|
||||||
export * from './analytics/pageview/getPageviews';
|
export * from './analytics/pageview/getPageviews';
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
@ -112,7 +112,6 @@
|
|||||||
|
|
||||||
const trackEvent = (
|
const trackEvent = (
|
||||||
eventName,
|
eventName,
|
||||||
eventData,
|
|
||||||
url = currentUrl,
|
url = currentUrl,
|
||||||
websiteId = website,
|
websiteId = website,
|
||||||
pageTitle = currentPageTitle,
|
pageTitle = currentPageTitle,
|
||||||
@ -124,7 +123,6 @@
|
|||||||
url,
|
url,
|
||||||
pageTitle,
|
pageTitle,
|
||||||
eventName: eventName,
|
eventName: eventName,
|
||||||
eventData: eventData,
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user