mirror of
https://github.com/umami-software/umami.git
synced 2026-02-18 19:45:35 +01:00
Merge pull request #3 from autodity/feature/tracking-batch-data
feat: add batch data for tracking payload
This commit is contained in:
commit
5765c0bf4a
6 changed files with 68 additions and 14 deletions
|
|
@ -52,6 +52,12 @@ export interface DynamicData {
|
||||||
[key: string]: number | string | number[] | string[];
|
[key: string]: number | string | number[] | string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface JsonKeyDynamicData {
|
||||||
|
key: string;
|
||||||
|
value: any;
|
||||||
|
dataType: DynamicDataType;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Auth {
|
export interface Auth {
|
||||||
user?: {
|
user?: {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ export interface CollectRequestBody {
|
||||||
payload: {
|
payload: {
|
||||||
website: string;
|
website: string;
|
||||||
data?: { [key: string]: any };
|
data?: { [key: string]: any };
|
||||||
|
batchData?: Array<{ [key: string]: any }>;
|
||||||
hostname?: string;
|
hostname?: string;
|
||||||
ip?: string;
|
ip?: string;
|
||||||
language?: string;
|
language?: string;
|
||||||
|
|
@ -61,7 +62,8 @@ const schema = {
|
||||||
payload: yup
|
payload: yup
|
||||||
.object()
|
.object()
|
||||||
.shape({
|
.shape({
|
||||||
data: yup.object(),
|
data: yup.object().optional(),
|
||||||
|
batchData: yup.array().of(yup.object()).optional(),
|
||||||
hostname: yup.string().matches(HOSTNAME_REGEX).max(100),
|
hostname: yup.string().matches(HOSTNAME_REGEX).max(100),
|
||||||
ip: yup.string().matches(IP_REGEX),
|
ip: yup.string().matches(IP_REGEX),
|
||||||
language: yup.string().max(35),
|
language: yup.string().max(35),
|
||||||
|
|
@ -90,13 +92,16 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
await useValidate(schema, req, res);
|
await useValidate(schema, req, res);
|
||||||
|
if (req.body.payload.batchData && req.body.payload.data) {
|
||||||
|
return badRequest(res, 'cannot send both data and batchData.');
|
||||||
|
}
|
||||||
|
|
||||||
if (hasBlockedIp(req)) {
|
if (hasBlockedIp(req)) {
|
||||||
return forbidden(res);
|
return forbidden(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { type, payload } = req.body;
|
const { type, payload } = req.body;
|
||||||
const { url, referrer, name: eventName, data, title } = payload;
|
const { url, referrer, name: eventName, data, title, batchData } = payload;
|
||||||
const pageTitle = safeDecodeURI(title);
|
const pageTitle = safeDecodeURI(title);
|
||||||
|
|
||||||
await useSession(req, res);
|
await useSession(req, res);
|
||||||
|
|
@ -141,6 +146,7 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => {
|
||||||
pageTitle,
|
pageTitle,
|
||||||
eventName,
|
eventName,
|
||||||
eventData: data,
|
eventData: data,
|
||||||
|
eventBatchData: batchData,
|
||||||
...session,
|
...session,
|
||||||
sessionId: session.id,
|
sessionId: session.id,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ export async function saveEvent(args: {
|
||||||
pageTitle?: string;
|
pageTitle?: string;
|
||||||
eventName?: string;
|
eventName?: string;
|
||||||
eventData?: any;
|
eventData?: any;
|
||||||
|
eventBatchData?: any[];
|
||||||
hostname?: string;
|
hostname?: string;
|
||||||
browser?: string;
|
browser?: string;
|
||||||
os?: string;
|
os?: string;
|
||||||
|
|
@ -47,6 +48,7 @@ async function relationalQuery(data: {
|
||||||
pageTitle?: string;
|
pageTitle?: string;
|
||||||
eventName?: string;
|
eventName?: string;
|
||||||
eventData?: any;
|
eventData?: any;
|
||||||
|
eventBatchData?: Array<any>;
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
websiteId,
|
websiteId,
|
||||||
|
|
@ -60,6 +62,7 @@ async function relationalQuery(data: {
|
||||||
eventName,
|
eventName,
|
||||||
eventData,
|
eventData,
|
||||||
pageTitle,
|
pageTitle,
|
||||||
|
eventBatchData,
|
||||||
} = data;
|
} = data;
|
||||||
const websiteEventId = uuid();
|
const websiteEventId = uuid();
|
||||||
|
|
||||||
|
|
@ -80,7 +83,7 @@ async function relationalQuery(data: {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (eventData) {
|
if (eventData || eventBatchData) {
|
||||||
await saveEventData({
|
await saveEventData({
|
||||||
websiteId,
|
websiteId,
|
||||||
sessionId,
|
sessionId,
|
||||||
|
|
@ -89,6 +92,7 @@ async function relationalQuery(data: {
|
||||||
urlPath: urlPath?.substring(0, URL_LENGTH),
|
urlPath: urlPath?.substring(0, URL_LENGTH),
|
||||||
eventName: eventName?.substring(0, EVENT_NAME_LENGTH),
|
eventName: eventName?.substring(0, EVENT_NAME_LENGTH),
|
||||||
eventData,
|
eventData,
|
||||||
|
eventBatchData,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,6 +111,7 @@ async function clickhouseQuery(data: {
|
||||||
pageTitle?: string;
|
pageTitle?: string;
|
||||||
eventName?: string;
|
eventName?: string;
|
||||||
eventData?: any;
|
eventData?: any;
|
||||||
|
eventBatchData?: any[];
|
||||||
hostname?: string;
|
hostname?: string;
|
||||||
browser?: string;
|
browser?: string;
|
||||||
os?: string;
|
os?: string;
|
||||||
|
|
@ -130,6 +135,7 @@ async function clickhouseQuery(data: {
|
||||||
pageTitle,
|
pageTitle,
|
||||||
eventName,
|
eventName,
|
||||||
eventData,
|
eventData,
|
||||||
|
eventBatchData,
|
||||||
country,
|
country,
|
||||||
subdivision1,
|
subdivision1,
|
||||||
subdivision2,
|
subdivision2,
|
||||||
|
|
@ -173,7 +179,7 @@ async function clickhouseQuery(data: {
|
||||||
await insert('website_event', [message]);
|
await insert('website_event', [message]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventData) {
|
if (eventData || eventBatchData) {
|
||||||
await saveEventData({
|
await saveEventData({
|
||||||
websiteId,
|
websiteId,
|
||||||
sessionId,
|
sessionId,
|
||||||
|
|
@ -182,6 +188,7 @@ async function clickhouseQuery(data: {
|
||||||
urlPath: urlPath?.substring(0, URL_LENGTH),
|
urlPath: urlPath?.substring(0, URL_LENGTH),
|
||||||
eventName: eventName?.substring(0, EVENT_NAME_LENGTH),
|
eventName: eventName?.substring(0, EVENT_NAME_LENGTH),
|
||||||
eventData,
|
eventData,
|
||||||
|
eventBatchData,
|
||||||
createdAt,
|
createdAt,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { flattenDynamicData, flattenJSON, getStringValue } from 'lib/data';
|
||||||
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
|
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
|
||||||
import kafka from 'lib/kafka';
|
import kafka from 'lib/kafka';
|
||||||
import prisma from 'lib/prisma';
|
import prisma from 'lib/prisma';
|
||||||
import { DynamicData } from 'lib/types';
|
import { DynamicData, JsonKeyDynamicData } from 'lib/types';
|
||||||
|
|
||||||
export async function saveEventData(data: {
|
export async function saveEventData(data: {
|
||||||
websiteId: string;
|
websiteId: string;
|
||||||
|
|
@ -15,7 +15,8 @@ export async function saveEventData(data: {
|
||||||
visitId?: string;
|
visitId?: string;
|
||||||
urlPath?: string;
|
urlPath?: string;
|
||||||
eventName?: string;
|
eventName?: string;
|
||||||
eventData: DynamicData;
|
eventData?: DynamicData;
|
||||||
|
eventBatchData?: Array<DynamicData>;
|
||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
}) {
|
}) {
|
||||||
return runQuery({
|
return runQuery({
|
||||||
|
|
@ -27,11 +28,17 @@ export async function saveEventData(data: {
|
||||||
async function relationalQuery(data: {
|
async function relationalQuery(data: {
|
||||||
websiteId: string;
|
websiteId: string;
|
||||||
eventId: string;
|
eventId: string;
|
||||||
eventData: DynamicData;
|
eventData?: DynamicData;
|
||||||
|
eventBatchData?: Array<DynamicData>;
|
||||||
}): Promise<Prisma.BatchPayload> {
|
}): Promise<Prisma.BatchPayload> {
|
||||||
const { websiteId, eventId, eventData } = data;
|
const { websiteId, eventId, eventData, eventBatchData } = data;
|
||||||
|
|
||||||
const jsonKeys = flattenJSON(eventData);
|
let jsonKeys: Array<JsonKeyDynamicData> = [];
|
||||||
|
if (eventData) {
|
||||||
|
jsonKeys = flattenJSON(eventData);
|
||||||
|
} else if (eventBatchData) {
|
||||||
|
jsonKeys = eventBatchData.flatMap(d => flattenJSON(d));
|
||||||
|
}
|
||||||
|
|
||||||
// id, websiteEventId, eventStringValue
|
// id, websiteEventId, eventStringValue
|
||||||
const flattenedData = jsonKeys.map(a => ({
|
const flattenedData = jsonKeys.map(a => ({
|
||||||
|
|
@ -57,15 +64,31 @@ async function clickhouseQuery(data: {
|
||||||
visitId?: string;
|
visitId?: string;
|
||||||
urlPath?: string;
|
urlPath?: string;
|
||||||
eventName?: string;
|
eventName?: string;
|
||||||
eventData: DynamicData;
|
eventData?: DynamicData;
|
||||||
|
eventBatchData?: Array<DynamicData>;
|
||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
}) {
|
}) {
|
||||||
const { websiteId, sessionId, visitId, eventId, urlPath, eventName, eventData, createdAt } = data;
|
const {
|
||||||
|
websiteId,
|
||||||
|
sessionId,
|
||||||
|
visitId,
|
||||||
|
eventId,
|
||||||
|
urlPath,
|
||||||
|
eventName,
|
||||||
|
eventData,
|
||||||
|
eventBatchData,
|
||||||
|
createdAt,
|
||||||
|
} = data;
|
||||||
|
|
||||||
const { sendMessages, sendMessage } = kafka;
|
const { sendMessages, sendMessage } = kafka;
|
||||||
const { insert, getUTCString } = clickhouse;
|
const { insert, getUTCString } = clickhouse;
|
||||||
|
|
||||||
const jsonKeys = flattenJSON(eventData);
|
let jsonKeys: Array<JsonKeyDynamicData> = [];
|
||||||
|
if (eventData) {
|
||||||
|
jsonKeys = flattenJSON(eventData);
|
||||||
|
} else if (eventBatchData) {
|
||||||
|
jsonKeys = eventBatchData.flatMap(d => flattenJSON(d));
|
||||||
|
}
|
||||||
|
|
||||||
const messages = jsonKeys.map(({ key, value, dataType }) => {
|
const messages = jsonKeys.map(({ key, value, dataType }) => {
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
3
src/tracker/index.d.ts
vendored
3
src/tracker/index.d.ts
vendored
|
|
@ -75,6 +75,7 @@ export type EventProperties = {
|
||||||
*/
|
*/
|
||||||
name: string;
|
name: string;
|
||||||
data?: EventData;
|
data?: EventData;
|
||||||
|
batchData?: EventData[];
|
||||||
} & WithRequired<TrackedProperties, 'website'>;
|
} & WithRequired<TrackedProperties, 'website'>;
|
||||||
export type PageViewProperties = WithRequired<TrackedProperties, 'website'>;
|
export type PageViewProperties = WithRequired<TrackedProperties, 'website'>;
|
||||||
export type CustomEventFunction = (
|
export type CustomEventFunction = (
|
||||||
|
|
@ -125,7 +126,7 @@ export type UmamiTracker = {
|
||||||
* umami.track('signup-button', { name: 'newsletter', id: 123 });
|
* umami.track('signup-button', { name: 'newsletter', id: 123 });
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
(eventName: string, obj: EventData): Promise<string>;
|
(eventName: string, obj: EventData | Array<EventData>): Promise<string>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks a page view with custom properties
|
* Tracks a page view with custom properties
|
||||||
|
|
|
||||||
|
|
@ -234,10 +234,21 @@
|
||||||
|
|
||||||
const track = (obj, data) => {
|
const track = (obj, data) => {
|
||||||
if (typeof obj === 'string') {
|
if (typeof obj === 'string') {
|
||||||
|
let singleData = undefined;
|
||||||
|
let batchData = undefined;
|
||||||
|
if (typeof data === 'object') {
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
batchData = data;
|
||||||
|
} else {
|
||||||
|
singleData = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return send({
|
return send({
|
||||||
...getPayload(),
|
...getPayload(),
|
||||||
name: obj,
|
name: obj,
|
||||||
data: typeof data === 'object' ? data : undefined,
|
data: singleData,
|
||||||
|
batchData,
|
||||||
});
|
});
|
||||||
} else if (typeof obj === 'object') {
|
} else if (typeof obj === 'object') {
|
||||||
return send(obj);
|
return send(obj);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue