Skip to content

Commit 48c22a6

Browse files
committed
ampper
1 parent e39782a commit 48c22a6

File tree

5 files changed

+376
-86
lines changed

5 files changed

+376
-86
lines changed

packages/mapper/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
1-
// Entry point for analytics event mapping adapters
2-
31
export * from './src';

packages/mapper/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
"module": "index.ts",
44
"type": "module",
55
"private": true,
6+
"scripts": {
7+
"test": "bun run src/test.ts"
8+
},
69
"devDependencies": {
710
"@types/bun": "latest"
811
},
@@ -11,6 +14,7 @@
1114
},
1215
"dependencies": {
1316
"csv-parse": "^6.1.0",
14-
"zod": "4"
17+
"zod": "4",
18+
"@databuddy/db": "workspace:*"
1519
}
1620
}

packages/mapper/src/adapters/umami.ts

Lines changed: 128 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -63,85 +63,132 @@ function getOrCreateAnonId(original: string): string {
6363
return anonIdMap.get(original) || '';
6464
}
6565

66+
function formatBrowserName(browser: string): string {
67+
if (!browser) {
68+
return '';
69+
}
70+
71+
// Replace hyphens with spaces and capitalize each word
72+
return browser
73+
.replace(/-/g, ' ')
74+
.split(' ')
75+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
76+
.join(' ');
77+
}
78+
79+
function determineEventType(
80+
row: UmamiCsvRow,
81+
isLastInSession = false
82+
): 'screen_view' | 'page_exit' {
83+
if (isLastInSession) {
84+
return 'page_exit';
85+
}
86+
return 'screen_view';
87+
}
88+
6689
export const umamiAdapter = (
67-
clientId: string
68-
): AnalyticsEventAdapter<UmamiCsvRow> => ({
69-
mapRowToEvent(row: UmamiCsvRow): AnalyticsEvent {
70-
return {
71-
id: crypto.randomUUID(),
72-
client_id: clientId,
73-
event_name: 'screen_view',
74-
anonymous_id: getOrCreateAnonId(row.distinct_id),
75-
time: new Date(row.created_at).getTime(),
76-
session_id: getOrCreateSessionId(row.session_id),
77-
event_type: 'track',
78-
event_id: row.event_id,
79-
session_start_time: undefined,
80-
timestamp: undefined,
81-
referrer:
82-
row.referrer_domain && row.referrer_domain.trim() !== ''
83-
? row.referrer_domain
84-
: 'direct',
85-
url: row.url_path,
86-
path: row.url_path,
87-
title: row.page_title || '',
88-
ip: '',
89-
user_agent: '',
90-
browser_name: row.browser || '',
91-
browser_version: undefined,
92-
os_name: row.os || '',
93-
os_version: undefined,
94-
device_type: row.device || '',
95-
device_brand: undefined,
96-
device_model: undefined,
97-
country: row.country || '',
98-
region: row.region || '',
99-
city: row.city || '',
100-
screen_resolution: row.screen || '',
101-
viewport_size: undefined,
102-
language: row.language || '',
103-
timezone: undefined,
104-
connection_type: undefined,
105-
rtt: undefined,
106-
downlink: undefined,
107-
time_on_page: undefined,
108-
scroll_depth: undefined,
109-
interaction_count: undefined,
110-
exit_intent: 0,
111-
page_count: 1,
112-
is_bounce: 0,
113-
has_exit_intent: undefined,
114-
page_size: undefined,
115-
utm_source: row.utm_source || '',
116-
utm_medium: row.utm_medium || '',
117-
utm_campaign: row.utm_campaign || '',
118-
utm_term: row.utm_term || '',
119-
utm_content: row.utm_content || '',
120-
load_time: undefined,
121-
dom_ready_time: undefined,
122-
dom_interactive: undefined,
123-
ttfb: undefined,
124-
connection_time: undefined,
125-
request_time: undefined,
126-
render_time: undefined,
127-
redirect_time: undefined,
128-
domain_lookup_time: undefined,
129-
fcp: undefined,
130-
lcp: undefined,
131-
cls: undefined,
132-
fid: undefined,
133-
inp: undefined,
134-
href: undefined,
135-
text: undefined,
136-
value: undefined,
137-
error_message: undefined,
138-
error_filename: undefined,
139-
error_lineno: undefined,
140-
error_colno: undefined,
141-
error_stack: undefined,
142-
error_type: undefined,
143-
properties: '',
144-
created_at: new Date(row.created_at).getTime(),
145-
};
146-
},
147-
});
90+
clientId: string,
91+
rows?: UmamiCsvRow[]
92+
): AnalyticsEventAdapter<UmamiCsvRow> => {
93+
// Pre-analyze sessions for page exit detection if rows are provided
94+
let isLastInSessionMap: Map<string, boolean> | undefined;
95+
96+
if (rows && rows.length > 0) {
97+
isLastInSessionMap = analyzeSessionsForPageExits(rows);
98+
}
99+
100+
function analyzeSessionsForPageExits(
101+
sessionRows: UmamiCsvRow[]
102+
): Map<string, boolean> {
103+
const sessionGroups = new Map<string, UmamiCsvRow[]>();
104+
105+
for (const row of sessionRows) {
106+
if (!sessionGroups.has(row.session_id)) {
107+
sessionGroups.set(row.session_id, []);
108+
}
109+
sessionGroups.get(row.session_id)?.push(row);
110+
}
111+
112+
const lastInSessionMap = new Map<string, boolean>();
113+
114+
for (const [sessionId, sessionEvents] of sessionGroups) {
115+
if (sessionEvents.length >= 2) {
116+
sessionEvents.sort(
117+
(a, b) =>
118+
new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
119+
);
120+
121+
const lastEvent = sessionEvents.at(-1);
122+
lastInSessionMap.set(lastEvent.event_id, true);
123+
}
124+
}
125+
126+
return lastInSessionMap;
127+
}
128+
129+
return {
130+
mapRowToEvent(row: UmamiCsvRow): AnalyticsEvent {
131+
const isLastInSession = isLastInSessionMap?.get(row.event_id);
132+
133+
return {
134+
id: crypto.randomUUID(),
135+
client_id: clientId,
136+
event_name: determineEventType(row, isLastInSession),
137+
anonymous_id: getOrCreateAnonId(row.distinct_id),
138+
time: new Date(row.created_at).getTime(),
139+
session_id: getOrCreateSessionId(row.session_id),
140+
event_type: 'track',
141+
event_id: row.event_id,
142+
session_start_time: undefined,
143+
timestamp: undefined,
144+
referrer:
145+
row.referrer_domain && row.referrer_domain.trim() !== ''
146+
? row.referrer_domain
147+
: 'direct',
148+
url: row.url_path,
149+
path: row.url_path,
150+
title: row.page_title || '',
151+
ip: '',
152+
user_agent: '',
153+
browser_name: formatBrowserName(row.browser || ''),
154+
browser_version: undefined,
155+
os_name: row.os || '',
156+
os_version: undefined,
157+
device_type: row.device || '',
158+
device_brand: undefined,
159+
device_model: undefined,
160+
country: row.country || '',
161+
region: row.region || '',
162+
city: row.city || '',
163+
screen_resolution: row.screen || '',
164+
viewport_size: undefined,
165+
language: row.language || '',
166+
timezone: undefined,
167+
connection_type: undefined,
168+
rtt: undefined,
169+
downlink: undefined,
170+
time_on_page: undefined,
171+
scroll_depth: undefined,
172+
interaction_count: undefined,
173+
page_count: 1,
174+
page_size: undefined,
175+
utm_source: row.utm_source || '',
176+
utm_medium: row.utm_medium || '',
177+
utm_campaign: row.utm_campaign || '',
178+
utm_term: row.utm_term || '',
179+
utm_content: row.utm_content || '',
180+
load_time: undefined,
181+
dom_ready_time: undefined,
182+
dom_interactive: undefined,
183+
ttfb: undefined,
184+
connection_time: undefined,
185+
request_time: undefined,
186+
render_time: undefined,
187+
redirect_time: undefined,
188+
domain_lookup_time: undefined,
189+
properties: '',
190+
created_at: new Date(row.created_at).getTime(),
191+
};
192+
},
193+
};
194+
};

0 commit comments

Comments
 (0)