mirror of
https://github.com/umami-software/umami.git
synced 2025-12-06 01:18:00 +01:00
Adds a CLI tool to generate realistic analytics data for local development and testing. Creates two demo websites with varying traffic patterns and realistic user behavior distributions.
191 lines
5.3 KiB
TypeScript
191 lines
5.3 KiB
TypeScript
import { uuid, addSeconds, randomInt } from '../utils.js';
|
|
import { getRandomReferrer } from '../distributions/referrers.js';
|
|
import type { SessionData } from './sessions.js';
|
|
|
|
export const EVENT_TYPE = {
|
|
pageView: 1,
|
|
customEvent: 2,
|
|
} as const;
|
|
|
|
export interface PageConfig {
|
|
path: string;
|
|
title: string;
|
|
weight: number;
|
|
avgTimeOnPage: number;
|
|
}
|
|
|
|
export interface CustomEventConfig {
|
|
name: string;
|
|
weight: number;
|
|
pages?: string[];
|
|
data?: Record<string, string[] | number[]>;
|
|
}
|
|
|
|
export interface JourneyConfig {
|
|
pages: string[];
|
|
weight: number;
|
|
}
|
|
|
|
export interface EventData {
|
|
id: string;
|
|
websiteId: string;
|
|
sessionId: string;
|
|
visitId: string;
|
|
eventType: number;
|
|
urlPath: string;
|
|
urlQuery: string | null;
|
|
pageTitle: string | null;
|
|
hostname: string;
|
|
referrerDomain: string | null;
|
|
referrerPath: string | null;
|
|
utmSource: string | null;
|
|
utmMedium: string | null;
|
|
utmCampaign: string | null;
|
|
utmContent: string | null;
|
|
utmTerm: string | null;
|
|
gclid: string | null;
|
|
fbclid: string | null;
|
|
eventName: string | null;
|
|
tag: string | null;
|
|
createdAt: Date;
|
|
}
|
|
|
|
export interface EventDataEntry {
|
|
id: string;
|
|
websiteId: string;
|
|
websiteEventId: string;
|
|
dataKey: string;
|
|
stringValue: string | null;
|
|
numberValue: number | null;
|
|
dateValue: Date | null;
|
|
dataType: number;
|
|
createdAt: Date;
|
|
}
|
|
|
|
export interface SiteConfig {
|
|
hostname: string;
|
|
pages: PageConfig[];
|
|
journeys: JourneyConfig[];
|
|
customEvents: CustomEventConfig[];
|
|
}
|
|
|
|
function getPageTitle(pages: PageConfig[], path: string): string | null {
|
|
const page = pages.find(p => p.path === path);
|
|
return page?.title ?? null;
|
|
}
|
|
|
|
function getPageTimeOnPage(pages: PageConfig[], path: string): number {
|
|
const page = pages.find(p => p.path === path);
|
|
return page?.avgTimeOnPage ?? 30;
|
|
}
|
|
|
|
export function generateEventsForSession(
|
|
session: SessionData,
|
|
siteConfig: SiteConfig,
|
|
journey: string[],
|
|
): { events: EventData[]; eventDataEntries: EventDataEntry[] } {
|
|
const events: EventData[] = [];
|
|
const eventDataEntries: EventDataEntry[] = [];
|
|
const visitId = uuid();
|
|
|
|
let currentTime = session.createdAt;
|
|
const referrer = getRandomReferrer();
|
|
|
|
for (let i = 0; i < journey.length; i++) {
|
|
const pagePath = journey[i];
|
|
const isFirstPage = i === 0;
|
|
|
|
const eventId = uuid();
|
|
const pageTitle = getPageTitle(siteConfig.pages, pagePath);
|
|
|
|
events.push({
|
|
id: eventId,
|
|
websiteId: session.websiteId,
|
|
sessionId: session.id,
|
|
visitId,
|
|
eventType: EVENT_TYPE.pageView,
|
|
urlPath: pagePath,
|
|
urlQuery: null,
|
|
pageTitle,
|
|
hostname: siteConfig.hostname,
|
|
referrerDomain: isFirstPage ? referrer.domain : null,
|
|
referrerPath: isFirstPage ? referrer.path : null,
|
|
utmSource: isFirstPage ? referrer.utmSource : null,
|
|
utmMedium: isFirstPage ? referrer.utmMedium : null,
|
|
utmCampaign: isFirstPage ? referrer.utmCampaign : null,
|
|
utmContent: isFirstPage ? referrer.utmContent : null,
|
|
utmTerm: isFirstPage ? referrer.utmTerm : null,
|
|
gclid: isFirstPage ? referrer.gclid : null,
|
|
fbclid: isFirstPage ? referrer.fbclid : null,
|
|
eventName: null,
|
|
tag: null,
|
|
createdAt: currentTime,
|
|
});
|
|
|
|
// Check for custom events on this page
|
|
for (const customEvent of siteConfig.customEvents) {
|
|
// Check if this event can occur on this page
|
|
if (customEvent.pages && !customEvent.pages.includes(pagePath)) {
|
|
continue;
|
|
}
|
|
|
|
// Random chance based on weight
|
|
if (Math.random() < customEvent.weight) {
|
|
currentTime = addSeconds(currentTime, randomInt(2, 15));
|
|
|
|
const customEventId = uuid();
|
|
events.push({
|
|
id: customEventId,
|
|
websiteId: session.websiteId,
|
|
sessionId: session.id,
|
|
visitId,
|
|
eventType: EVENT_TYPE.customEvent,
|
|
urlPath: pagePath,
|
|
urlQuery: null,
|
|
pageTitle,
|
|
hostname: siteConfig.hostname,
|
|
referrerDomain: null,
|
|
referrerPath: null,
|
|
utmSource: null,
|
|
utmMedium: null,
|
|
utmCampaign: null,
|
|
utmContent: null,
|
|
utmTerm: null,
|
|
gclid: null,
|
|
fbclid: null,
|
|
eventName: customEvent.name,
|
|
tag: null,
|
|
createdAt: currentTime,
|
|
});
|
|
|
|
// Generate event data if configured
|
|
if (customEvent.data) {
|
|
for (const [key, values] of Object.entries(customEvent.data)) {
|
|
const value = values[Math.floor(Math.random() * values.length)];
|
|
const isNumber = typeof value === 'number';
|
|
|
|
eventDataEntries.push({
|
|
id: uuid(),
|
|
websiteId: session.websiteId,
|
|
websiteEventId: customEventId,
|
|
dataKey: key,
|
|
stringValue: isNumber ? null : String(value),
|
|
numberValue: isNumber ? value : null,
|
|
dateValue: null,
|
|
dataType: isNumber ? 2 : 1, // 1 = string, 2 = number
|
|
createdAt: currentTime,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Time spent on page before navigating
|
|
const timeOnPage = getPageTimeOnPage(siteConfig.pages, pagePath);
|
|
const variance = Math.floor(timeOnPage * 0.5);
|
|
const actualTime = timeOnPage + randomInt(-variance, variance);
|
|
currentTime = addSeconds(currentTime, Math.max(5, actualTime));
|
|
}
|
|
|
|
return { events, eventDataEntries };
|
|
}
|