@litemetrics/node 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/dist/index.cjs +548 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +548 -55
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -153,6 +153,13 @@ CREATE TABLE IF NOT EXISTS ${EVENTS_TABLE} (
|
|
|
153
153
|
title Nullable(String),
|
|
154
154
|
event_name Nullable(String),
|
|
155
155
|
properties Nullable(String),
|
|
156
|
+
event_source LowCardinality(Nullable(String)),
|
|
157
|
+
event_subtype LowCardinality(Nullable(String)),
|
|
158
|
+
page_path Nullable(String),
|
|
159
|
+
target_url_path Nullable(String),
|
|
160
|
+
element_selector Nullable(String),
|
|
161
|
+
element_text Nullable(String),
|
|
162
|
+
scroll_depth_pct Nullable(UInt8),
|
|
156
163
|
user_id Nullable(String),
|
|
157
164
|
traits Nullable(String),
|
|
158
165
|
country LowCardinality(Nullable(String)),
|
|
@@ -184,6 +191,7 @@ CREATE TABLE IF NOT EXISTS ${SITES_TABLE} (
|
|
|
184
191
|
name String,
|
|
185
192
|
domain Nullable(String),
|
|
186
193
|
allowed_origins Nullable(String),
|
|
194
|
+
conversion_events Nullable(String),
|
|
187
195
|
created_at DateTime64(3),
|
|
188
196
|
updated_at DateTime64(3),
|
|
189
197
|
version UInt64,
|
|
@@ -196,6 +204,39 @@ function toCHDateTime(d) {
|
|
|
196
204
|
const iso = typeof d === "string" ? d : d.toISOString();
|
|
197
205
|
return iso.replace("T", " ").replace("Z", "");
|
|
198
206
|
}
|
|
207
|
+
function buildFilterConditions(filters) {
|
|
208
|
+
if (!filters) return { conditions: [], params: {} };
|
|
209
|
+
const map = {
|
|
210
|
+
"geo.country": "country",
|
|
211
|
+
"geo.city": "city",
|
|
212
|
+
"geo.region": "region",
|
|
213
|
+
"language": "language",
|
|
214
|
+
"device.type": "device_type",
|
|
215
|
+
"device.browser": "browser",
|
|
216
|
+
"device.os": "os",
|
|
217
|
+
"utm.source": "utm_source",
|
|
218
|
+
"utm.medium": "utm_medium",
|
|
219
|
+
"utm.campaign": "utm_campaign",
|
|
220
|
+
"utm.term": "utm_term",
|
|
221
|
+
"utm.content": "utm_content",
|
|
222
|
+
"referrer": "referrer",
|
|
223
|
+
"event_source": "event_source",
|
|
224
|
+
"event_subtype": "event_subtype",
|
|
225
|
+
"page_path": "page_path",
|
|
226
|
+
"target_url_path": "target_url_path",
|
|
227
|
+
"event_name": "event_name",
|
|
228
|
+
"type": "type"
|
|
229
|
+
};
|
|
230
|
+
const conditions = [];
|
|
231
|
+
const params = {};
|
|
232
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
233
|
+
if (!value || !map[key]) continue;
|
|
234
|
+
const paramKey = `f_${key.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
235
|
+
conditions.push(`${map[key]} = {${paramKey}:String}`);
|
|
236
|
+
params[paramKey] = value;
|
|
237
|
+
}
|
|
238
|
+
return { conditions, params };
|
|
239
|
+
}
|
|
199
240
|
var ClickHouseAdapter = class {
|
|
200
241
|
client;
|
|
201
242
|
constructor(url) {
|
|
@@ -209,6 +250,16 @@ var ClickHouseAdapter = class {
|
|
|
209
250
|
async init() {
|
|
210
251
|
await this.client.command({ query: CREATE_EVENTS_TABLE });
|
|
211
252
|
await this.client.command({ query: CREATE_SITES_TABLE });
|
|
253
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS event_source LowCardinality(Nullable(String))` });
|
|
254
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS event_subtype LowCardinality(Nullable(String))` });
|
|
255
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS page_path Nullable(String)` });
|
|
256
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS target_url_path Nullable(String)` });
|
|
257
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS element_selector Nullable(String)` });
|
|
258
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS element_text Nullable(String)` });
|
|
259
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS scroll_depth_pct Nullable(UInt8)` });
|
|
260
|
+
await this.client.command({
|
|
261
|
+
query: `ALTER TABLE ${SITES_TABLE} ADD COLUMN IF NOT EXISTS conversion_events Nullable(String)`
|
|
262
|
+
});
|
|
212
263
|
}
|
|
213
264
|
async close() {
|
|
214
265
|
await this.client.close();
|
|
@@ -227,6 +278,13 @@ var ClickHouseAdapter = class {
|
|
|
227
278
|
title: e.title ?? null,
|
|
228
279
|
event_name: e.name ?? null,
|
|
229
280
|
properties: e.properties ? JSON.stringify(e.properties) : null,
|
|
281
|
+
event_source: e.eventSource ?? null,
|
|
282
|
+
event_subtype: e.eventSubtype ?? null,
|
|
283
|
+
page_path: e.pagePath ?? null,
|
|
284
|
+
target_url_path: e.targetUrlPath ?? null,
|
|
285
|
+
element_selector: e.elementSelector ?? null,
|
|
286
|
+
element_text: e.elementText ?? null,
|
|
287
|
+
scroll_depth_pct: e.scrollDepthPct ?? null,
|
|
230
288
|
user_id: e.userId ?? null,
|
|
231
289
|
traits: e.traits ? JSON.stringify(e.traits) : null,
|
|
232
290
|
country: e.geo?.country ?? null,
|
|
@@ -263,6 +321,8 @@ var ClickHouseAdapter = class {
|
|
|
263
321
|
to: toCHDateTime(dateRange.to),
|
|
264
322
|
limit
|
|
265
323
|
};
|
|
324
|
+
const filter = buildFilterConditions(q.filters);
|
|
325
|
+
const filterSql = filter.conditions.length > 0 ? ` AND ${filter.conditions.join(" AND ")}` : "";
|
|
266
326
|
let data = [];
|
|
267
327
|
let total = 0;
|
|
268
328
|
switch (q.metric) {
|
|
@@ -272,8 +332,8 @@ var ClickHouseAdapter = class {
|
|
|
272
332
|
WHERE site_id = {siteId:String}
|
|
273
333
|
AND timestamp >= {from:String}
|
|
274
334
|
AND timestamp <= {to:String}
|
|
275
|
-
AND type = 'pageview'`,
|
|
276
|
-
params
|
|
335
|
+
AND type = 'pageview'${filterSql}`,
|
|
336
|
+
{ ...params, ...filter.params }
|
|
277
337
|
);
|
|
278
338
|
total = Number(rows[0]?.value ?? 0);
|
|
279
339
|
data = [{ key: "pageviews", value: total }];
|
|
@@ -284,8 +344,8 @@ var ClickHouseAdapter = class {
|
|
|
284
344
|
`SELECT uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
285
345
|
WHERE site_id = {siteId:String}
|
|
286
346
|
AND timestamp >= {from:String}
|
|
287
|
-
AND timestamp <= {to:String}`,
|
|
288
|
-
params
|
|
347
|
+
AND timestamp <= {to:String}${filterSql}`,
|
|
348
|
+
{ ...params, ...filter.params }
|
|
289
349
|
);
|
|
290
350
|
total = Number(rows[0]?.value ?? 0);
|
|
291
351
|
data = [{ key: "visitors", value: total }];
|
|
@@ -296,8 +356,8 @@ var ClickHouseAdapter = class {
|
|
|
296
356
|
`SELECT uniq(session_id) AS value FROM ${EVENTS_TABLE}
|
|
297
357
|
WHERE site_id = {siteId:String}
|
|
298
358
|
AND timestamp >= {from:String}
|
|
299
|
-
AND timestamp <= {to:String}`,
|
|
300
|
-
params
|
|
359
|
+
AND timestamp <= {to:String}${filterSql}`,
|
|
360
|
+
{ ...params, ...filter.params }
|
|
301
361
|
);
|
|
302
362
|
total = Number(rows[0]?.value ?? 0);
|
|
303
363
|
data = [{ key: "sessions", value: total }];
|
|
@@ -309,13 +369,33 @@ var ClickHouseAdapter = class {
|
|
|
309
369
|
WHERE site_id = {siteId:String}
|
|
310
370
|
AND timestamp >= {from:String}
|
|
311
371
|
AND timestamp <= {to:String}
|
|
312
|
-
AND type = 'event'`,
|
|
313
|
-
params
|
|
372
|
+
AND type = 'event'${filterSql}`,
|
|
373
|
+
{ ...params, ...filter.params }
|
|
314
374
|
);
|
|
315
375
|
total = Number(rows[0]?.value ?? 0);
|
|
316
376
|
data = [{ key: "events", value: total }];
|
|
317
377
|
break;
|
|
318
378
|
}
|
|
379
|
+
case "conversions": {
|
|
380
|
+
const conversionEvents = q.conversionEvents ?? [];
|
|
381
|
+
if (conversionEvents.length === 0) {
|
|
382
|
+
total = 0;
|
|
383
|
+
data = [{ key: "conversions", value: 0 }];
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
const rows = await this.queryRows(
|
|
387
|
+
`SELECT count() AS value FROM ${EVENTS_TABLE}
|
|
388
|
+
WHERE site_id = {siteId:String}
|
|
389
|
+
AND timestamp >= {from:String}
|
|
390
|
+
AND timestamp <= {to:String}
|
|
391
|
+
AND type = 'event'
|
|
392
|
+
AND event_name IN {eventNames:Array(String)}${filterSql}`,
|
|
393
|
+
{ ...params, eventNames: conversionEvents, ...filter.params }
|
|
394
|
+
);
|
|
395
|
+
total = Number(rows[0]?.value ?? 0);
|
|
396
|
+
data = [{ key: "conversions", value: total }];
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
319
399
|
case "top_pages": {
|
|
320
400
|
const rows = await this.queryRows(
|
|
321
401
|
`SELECT url AS key, count() AS value FROM ${EVENTS_TABLE}
|
|
@@ -323,11 +403,11 @@ var ClickHouseAdapter = class {
|
|
|
323
403
|
AND timestamp >= {from:String}
|
|
324
404
|
AND timestamp <= {to:String}
|
|
325
405
|
AND type = 'pageview'
|
|
326
|
-
AND url IS NOT NULL
|
|
406
|
+
AND url IS NOT NULL${filterSql}
|
|
327
407
|
GROUP BY url
|
|
328
408
|
ORDER BY value DESC
|
|
329
409
|
LIMIT {limit:UInt32}`,
|
|
330
|
-
params
|
|
410
|
+
{ ...params, ...filter.params }
|
|
331
411
|
);
|
|
332
412
|
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
333
413
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
@@ -341,11 +421,11 @@ var ClickHouseAdapter = class {
|
|
|
341
421
|
AND timestamp <= {to:String}
|
|
342
422
|
AND type = 'pageview'
|
|
343
423
|
AND referrer IS NOT NULL
|
|
344
|
-
AND referrer != ''
|
|
424
|
+
AND referrer != ''${filterSql}
|
|
345
425
|
GROUP BY referrer
|
|
346
426
|
ORDER BY value DESC
|
|
347
427
|
LIMIT {limit:UInt32}`,
|
|
348
|
-
params
|
|
428
|
+
{ ...params, ...filter.params }
|
|
349
429
|
);
|
|
350
430
|
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
351
431
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
@@ -357,11 +437,11 @@ var ClickHouseAdapter = class {
|
|
|
357
437
|
WHERE site_id = {siteId:String}
|
|
358
438
|
AND timestamp >= {from:String}
|
|
359
439
|
AND timestamp <= {to:String}
|
|
360
|
-
AND country IS NOT NULL
|
|
440
|
+
AND country IS NOT NULL${filterSql}
|
|
361
441
|
GROUP BY country
|
|
362
442
|
ORDER BY value DESC
|
|
363
443
|
LIMIT {limit:UInt32}`,
|
|
364
|
-
params
|
|
444
|
+
{ ...params, ...filter.params }
|
|
365
445
|
);
|
|
366
446
|
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
367
447
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
@@ -373,11 +453,11 @@ var ClickHouseAdapter = class {
|
|
|
373
453
|
WHERE site_id = {siteId:String}
|
|
374
454
|
AND timestamp >= {from:String}
|
|
375
455
|
AND timestamp <= {to:String}
|
|
376
|
-
AND city IS NOT NULL
|
|
456
|
+
AND city IS NOT NULL${filterSql}
|
|
377
457
|
GROUP BY city
|
|
378
458
|
ORDER BY value DESC
|
|
379
459
|
LIMIT {limit:UInt32}`,
|
|
380
|
-
params
|
|
460
|
+
{ ...params, ...filter.params }
|
|
381
461
|
);
|
|
382
462
|
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
383
463
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
@@ -391,10 +471,132 @@ var ClickHouseAdapter = class {
|
|
|
391
471
|
AND timestamp <= {to:String}
|
|
392
472
|
AND type = 'event'
|
|
393
473
|
AND event_name IS NOT NULL
|
|
474
|
+
${filterSql}
|
|
394
475
|
GROUP BY event_name
|
|
395
476
|
ORDER BY value DESC
|
|
396
477
|
LIMIT {limit:UInt32}`,
|
|
397
|
-
params
|
|
478
|
+
{ ...params, ...filter.params }
|
|
479
|
+
);
|
|
480
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
481
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
case "top_conversions": {
|
|
485
|
+
const conversionEvents = q.conversionEvents ?? [];
|
|
486
|
+
if (conversionEvents.length === 0) {
|
|
487
|
+
total = 0;
|
|
488
|
+
data = [];
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
const rows = await this.queryRows(
|
|
492
|
+
`SELECT event_name AS key, count() AS value FROM ${EVENTS_TABLE}
|
|
493
|
+
WHERE site_id = {siteId:String}
|
|
494
|
+
AND timestamp >= {from:String}
|
|
495
|
+
AND timestamp <= {to:String}
|
|
496
|
+
AND type = 'event'
|
|
497
|
+
AND event_name IN {eventNames:Array(String)}
|
|
498
|
+
${filterSql}
|
|
499
|
+
GROUP BY event_name
|
|
500
|
+
ORDER BY value DESC
|
|
501
|
+
LIMIT {limit:UInt32}`,
|
|
502
|
+
{ ...params, eventNames: conversionEvents, ...filter.params }
|
|
503
|
+
);
|
|
504
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
505
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
case "top_exit_pages": {
|
|
509
|
+
const rows = await this.queryRows(
|
|
510
|
+
`SELECT url AS key, count() AS value FROM (
|
|
511
|
+
SELECT session_id, argMax(url, timestamp) AS url
|
|
512
|
+
FROM ${EVENTS_TABLE}
|
|
513
|
+
WHERE site_id = {siteId:String}
|
|
514
|
+
AND timestamp >= {from:String}
|
|
515
|
+
AND timestamp <= {to:String}
|
|
516
|
+
AND type = 'pageview'
|
|
517
|
+
AND url IS NOT NULL${filterSql}
|
|
518
|
+
GROUP BY session_id
|
|
519
|
+
)
|
|
520
|
+
GROUP BY url
|
|
521
|
+
ORDER BY value DESC
|
|
522
|
+
LIMIT {limit:UInt32}`,
|
|
523
|
+
{ ...params, ...filter.params }
|
|
524
|
+
);
|
|
525
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
526
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
case "top_transitions": {
|
|
530
|
+
const rows = await this.queryRows(
|
|
531
|
+
`SELECT concat(prev_url, ' \u2192 ', url) AS key, count() AS value FROM (
|
|
532
|
+
SELECT session_id, url,
|
|
533
|
+
lag(url) OVER (PARTITION BY session_id ORDER BY timestamp) AS prev_url
|
|
534
|
+
FROM ${EVENTS_TABLE}
|
|
535
|
+
WHERE site_id = {siteId:String}
|
|
536
|
+
AND timestamp >= {from:String}
|
|
537
|
+
AND timestamp <= {to:String}
|
|
538
|
+
AND type = 'pageview'
|
|
539
|
+
AND url IS NOT NULL${filterSql}
|
|
540
|
+
)
|
|
541
|
+
WHERE prev_url IS NOT NULL
|
|
542
|
+
GROUP BY key
|
|
543
|
+
ORDER BY value DESC
|
|
544
|
+
LIMIT {limit:UInt32}`,
|
|
545
|
+
{ ...params, ...filter.params }
|
|
546
|
+
);
|
|
547
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
548
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
case "top_scroll_pages": {
|
|
552
|
+
const rows = await this.queryRows(
|
|
553
|
+
`SELECT page_path AS key, count() AS value FROM ${EVENTS_TABLE}
|
|
554
|
+
WHERE site_id = {siteId:String}
|
|
555
|
+
AND timestamp >= {from:String}
|
|
556
|
+
AND timestamp <= {to:String}
|
|
557
|
+
AND type = 'event'
|
|
558
|
+
AND event_subtype = 'scroll_depth'
|
|
559
|
+
AND page_path IS NOT NULL${filterSql}
|
|
560
|
+
GROUP BY page_path
|
|
561
|
+
ORDER BY value DESC
|
|
562
|
+
LIMIT {limit:UInt32}`,
|
|
563
|
+
{ ...params, ...filter.params }
|
|
564
|
+
);
|
|
565
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
566
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
case "top_button_clicks": {
|
|
570
|
+
const rows = await this.queryRows(
|
|
571
|
+
`SELECT ifNull(element_text, element_selector) AS key, count() AS value FROM ${EVENTS_TABLE}
|
|
572
|
+
WHERE site_id = {siteId:String}
|
|
573
|
+
AND timestamp >= {from:String}
|
|
574
|
+
AND timestamp <= {to:String}
|
|
575
|
+
AND type = 'event'
|
|
576
|
+
AND event_subtype = 'button_click'
|
|
577
|
+
AND (element_text IS NOT NULL OR element_selector IS NOT NULL)${filterSql}
|
|
578
|
+
GROUP BY key
|
|
579
|
+
ORDER BY value DESC
|
|
580
|
+
LIMIT {limit:UInt32}`,
|
|
581
|
+
{ ...params, ...filter.params }
|
|
582
|
+
);
|
|
583
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
584
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
case "top_link_targets": {
|
|
588
|
+
const rows = await this.queryRows(
|
|
589
|
+
`SELECT target_url_path AS key, count() AS value FROM ${EVENTS_TABLE}
|
|
590
|
+
WHERE site_id = {siteId:String}
|
|
591
|
+
AND timestamp >= {from:String}
|
|
592
|
+
AND timestamp <= {to:String}
|
|
593
|
+
AND type = 'event'
|
|
594
|
+
AND event_subtype IN ('link_click','outbound_click')
|
|
595
|
+
AND target_url_path IS NOT NULL${filterSql}
|
|
596
|
+
GROUP BY target_url_path
|
|
597
|
+
ORDER BY value DESC
|
|
598
|
+
LIMIT {limit:UInt32}`,
|
|
599
|
+
{ ...params, ...filter.params }
|
|
398
600
|
);
|
|
399
601
|
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
400
602
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
@@ -407,10 +609,11 @@ var ClickHouseAdapter = class {
|
|
|
407
609
|
AND timestamp >= {from:String}
|
|
408
610
|
AND timestamp <= {to:String}
|
|
409
611
|
AND device_type IS NOT NULL
|
|
612
|
+
${filterSql}
|
|
410
613
|
GROUP BY device_type
|
|
411
614
|
ORDER BY value DESC
|
|
412
615
|
LIMIT {limit:UInt32}`,
|
|
413
|
-
params
|
|
616
|
+
{ ...params, ...filter.params }
|
|
414
617
|
);
|
|
415
618
|
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
416
619
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
@@ -423,10 +626,11 @@ var ClickHouseAdapter = class {
|
|
|
423
626
|
AND timestamp >= {from:String}
|
|
424
627
|
AND timestamp <= {to:String}
|
|
425
628
|
AND browser IS NOT NULL
|
|
629
|
+
${filterSql}
|
|
426
630
|
GROUP BY browser
|
|
427
631
|
ORDER BY value DESC
|
|
428
632
|
LIMIT {limit:UInt32}`,
|
|
429
|
-
params
|
|
633
|
+
{ ...params, ...filter.params }
|
|
430
634
|
);
|
|
431
635
|
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
432
636
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
@@ -439,10 +643,11 @@ var ClickHouseAdapter = class {
|
|
|
439
643
|
AND timestamp >= {from:String}
|
|
440
644
|
AND timestamp <= {to:String}
|
|
441
645
|
AND os IS NOT NULL
|
|
646
|
+
${filterSql}
|
|
442
647
|
GROUP BY os
|
|
443
648
|
ORDER BY value DESC
|
|
444
649
|
LIMIT {limit:UInt32}`,
|
|
445
|
-
params
|
|
650
|
+
{ ...params, ...filter.params }
|
|
446
651
|
);
|
|
447
652
|
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
448
653
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
@@ -450,7 +655,7 @@ var ClickHouseAdapter = class {
|
|
|
450
655
|
}
|
|
451
656
|
}
|
|
452
657
|
const result = { metric: q.metric, period, data, total };
|
|
453
|
-
if (q.compare && ["pageviews", "visitors", "sessions", "events"].includes(q.metric)) {
|
|
658
|
+
if (q.compare && ["pageviews", "visitors", "sessions", "events", "conversions"].includes(q.metric)) {
|
|
454
659
|
const prevRange = previousPeriodRange(dateRange);
|
|
455
660
|
const prevResult = await this.query({
|
|
456
661
|
...q,
|
|
@@ -480,7 +685,12 @@ var ClickHouseAdapter = class {
|
|
|
480
685
|
const granularity = params.granularity ?? autoGranularity(period);
|
|
481
686
|
const bucketFn = this.granularityToClickHouseFunc(granularity);
|
|
482
687
|
const dateFormat = granularityToDateFormat(granularity);
|
|
688
|
+
const filter = buildFilterConditions(params.filters);
|
|
689
|
+
const filterSql = filter.conditions.length > 0 ? ` AND ${filter.conditions.join(" AND ")}` : "";
|
|
483
690
|
const typeFilter = params.metric === "pageviews" ? `AND type = 'pageview'` : "";
|
|
691
|
+
const eventsFilter = params.metric === "events" ? `AND type = 'event'` : "";
|
|
692
|
+
const conversionsFilter = params.metric === "conversions" ? `AND type = 'event' AND event_name IN {eventNames:Array(String)}` : "";
|
|
693
|
+
const extraFilters = [typeFilter, eventsFilter, conversionsFilter, filterSql].filter(Boolean).join(" ");
|
|
484
694
|
let sql;
|
|
485
695
|
if (params.metric === "visitors" || params.metric === "sessions") {
|
|
486
696
|
const field = params.metric === "visitors" ? "visitor_id" : "session_id";
|
|
@@ -490,7 +700,7 @@ var ClickHouseAdapter = class {
|
|
|
490
700
|
WHERE site_id = {siteId:String}
|
|
491
701
|
AND timestamp >= {from:String}
|
|
492
702
|
AND timestamp <= {to:String}
|
|
493
|
-
${
|
|
703
|
+
${extraFilters}
|
|
494
704
|
GROUP BY bucket
|
|
495
705
|
ORDER BY bucket ASC
|
|
496
706
|
`;
|
|
@@ -501,7 +711,7 @@ var ClickHouseAdapter = class {
|
|
|
501
711
|
WHERE site_id = {siteId:String}
|
|
502
712
|
AND timestamp >= {from:String}
|
|
503
713
|
AND timestamp <= {to:String}
|
|
504
|
-
${
|
|
714
|
+
${extraFilters}
|
|
505
715
|
GROUP BY bucket
|
|
506
716
|
ORDER BY bucket ASC
|
|
507
717
|
`;
|
|
@@ -509,7 +719,9 @@ var ClickHouseAdapter = class {
|
|
|
509
719
|
const rows = await this.queryRows(sql, {
|
|
510
720
|
siteId: params.siteId,
|
|
511
721
|
from: toCHDateTime(dateRange.from),
|
|
512
|
-
to: toCHDateTime(dateRange.to)
|
|
722
|
+
to: toCHDateTime(dateRange.to),
|
|
723
|
+
eventNames: params.conversionEvents ?? [],
|
|
724
|
+
...filter.params
|
|
513
725
|
});
|
|
514
726
|
const mappedRows = rows.map((r) => ({
|
|
515
727
|
_id: this.convertClickHouseBucket(r.bucket, granularity),
|
|
@@ -627,6 +839,14 @@ var ClickHouseAdapter = class {
|
|
|
627
839
|
conditions.push(`event_name = {eventName:String}`);
|
|
628
840
|
queryParams.eventName = params.eventName;
|
|
629
841
|
}
|
|
842
|
+
if (params.eventSource) {
|
|
843
|
+
conditions.push(`event_source = {eventSource:String}`);
|
|
844
|
+
queryParams.eventSource = params.eventSource;
|
|
845
|
+
}
|
|
846
|
+
if (params.eventNames && params.eventNames.length > 0) {
|
|
847
|
+
conditions.push(`event_name IN {eventNames:Array(String)}`);
|
|
848
|
+
queryParams.eventNames = params.eventNames;
|
|
849
|
+
}
|
|
630
850
|
if (params.visitorId) {
|
|
631
851
|
conditions.push(`visitor_id = {visitorId:String}`);
|
|
632
852
|
queryParams.visitorId = params.visitorId;
|
|
@@ -649,7 +869,9 @@ var ClickHouseAdapter = class {
|
|
|
649
869
|
const [events, countRows] = await Promise.all([
|
|
650
870
|
this.queryRows(
|
|
651
871
|
`SELECT event_id, type, timestamp, session_id, visitor_id, url, referrer, title,
|
|
652
|
-
event_name, properties,
|
|
872
|
+
event_name, properties, event_source, event_subtype, page_path, target_url_path,
|
|
873
|
+
element_selector, element_text, scroll_depth_pct,
|
|
874
|
+
user_id, traits, country, city, region,
|
|
653
875
|
device_type, browser, os, language,
|
|
654
876
|
utm_source, utm_medium, utm_campaign, utm_term, utm_content
|
|
655
877
|
FROM ${EVENTS_TABLE}
|
|
@@ -694,13 +916,22 @@ var ClickHouseAdapter = class {
|
|
|
694
916
|
countIf(type = 'pageview') AS totalPageviews,
|
|
695
917
|
uniq(session_id) AS totalSessions,
|
|
696
918
|
anyLast(url) AS lastUrl,
|
|
919
|
+
anyLast(referrer) AS referrer,
|
|
697
920
|
anyLast(device_type) AS device_type,
|
|
698
921
|
anyLast(browser) AS browser,
|
|
699
922
|
anyLast(os) AS os,
|
|
700
923
|
anyLast(country) AS country,
|
|
701
924
|
anyLast(city) AS city,
|
|
702
925
|
anyLast(region) AS region,
|
|
703
|
-
anyLast(language) AS language
|
|
926
|
+
anyLast(language) AS language,
|
|
927
|
+
anyLast(timezone) AS timezone,
|
|
928
|
+
anyLast(screen_width) AS screen_width,
|
|
929
|
+
anyLast(screen_height) AS screen_height,
|
|
930
|
+
anyLast(utm_source) AS utm_source,
|
|
931
|
+
anyLast(utm_medium) AS utm_medium,
|
|
932
|
+
anyLast(utm_campaign) AS utm_campaign,
|
|
933
|
+
anyLast(utm_term) AS utm_term,
|
|
934
|
+
anyLast(utm_content) AS utm_content
|
|
704
935
|
FROM ${EVENTS_TABLE}
|
|
705
936
|
WHERE ${where}
|
|
706
937
|
GROUP BY visitor_id
|
|
@@ -728,9 +959,19 @@ var ClickHouseAdapter = class {
|
|
|
728
959
|
totalPageviews: Number(u.totalPageviews),
|
|
729
960
|
totalSessions: Number(u.totalSessions),
|
|
730
961
|
lastUrl: u.lastUrl ? String(u.lastUrl) : void 0,
|
|
962
|
+
referrer: u.referrer ? String(u.referrer) : void 0,
|
|
731
963
|
device: u.device_type ? { type: String(u.device_type), browser: String(u.browser ?? ""), os: String(u.os ?? "") } : void 0,
|
|
732
964
|
geo: u.country ? { country: String(u.country), city: u.city ? String(u.city) : void 0, region: u.region ? String(u.region) : void 0 } : void 0,
|
|
733
|
-
language: u.language ? String(u.language) : void 0
|
|
965
|
+
language: u.language ? String(u.language) : void 0,
|
|
966
|
+
timezone: u.timezone ? String(u.timezone) : void 0,
|
|
967
|
+
screen: u.screen_width || u.screen_height ? { width: Number(u.screen_width ?? 0), height: Number(u.screen_height ?? 0) } : void 0,
|
|
968
|
+
utm: u.utm_source ? {
|
|
969
|
+
source: String(u.utm_source),
|
|
970
|
+
medium: u.utm_medium ? String(u.utm_medium) : void 0,
|
|
971
|
+
campaign: u.utm_campaign ? String(u.utm_campaign) : void 0,
|
|
972
|
+
term: u.utm_term ? String(u.utm_term) : void 0,
|
|
973
|
+
content: u.utm_content ? String(u.utm_content) : void 0
|
|
974
|
+
} : void 0
|
|
734
975
|
}));
|
|
735
976
|
return {
|
|
736
977
|
users,
|
|
@@ -758,6 +999,7 @@ var ClickHouseAdapter = class {
|
|
|
758
999
|
name: data.name,
|
|
759
1000
|
domain: data.domain,
|
|
760
1001
|
allowedOrigins: data.allowedOrigins,
|
|
1002
|
+
conversionEvents: data.conversionEvents,
|
|
761
1003
|
createdAt: nowISO,
|
|
762
1004
|
updatedAt: nowISO
|
|
763
1005
|
};
|
|
@@ -769,6 +1011,7 @@ var ClickHouseAdapter = class {
|
|
|
769
1011
|
name: site.name,
|
|
770
1012
|
domain: site.domain ?? null,
|
|
771
1013
|
allowed_origins: site.allowedOrigins ? JSON.stringify(site.allowedOrigins) : null,
|
|
1014
|
+
conversion_events: site.conversionEvents ? JSON.stringify(site.conversionEvents) : null,
|
|
772
1015
|
created_at: nowCH,
|
|
773
1016
|
updated_at: nowCH,
|
|
774
1017
|
version: 1,
|
|
@@ -780,7 +1023,7 @@ var ClickHouseAdapter = class {
|
|
|
780
1023
|
}
|
|
781
1024
|
async getSite(siteId) {
|
|
782
1025
|
const rows = await this.queryRows(
|
|
783
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, created_at, updated_at
|
|
1026
|
+
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, updated_at
|
|
784
1027
|
FROM ${SITES_TABLE} FINAL
|
|
785
1028
|
WHERE site_id = {siteId:String} AND is_deleted = 0`,
|
|
786
1029
|
{ siteId }
|
|
@@ -789,7 +1032,7 @@ var ClickHouseAdapter = class {
|
|
|
789
1032
|
}
|
|
790
1033
|
async getSiteBySecret(secretKey) {
|
|
791
1034
|
const rows = await this.queryRows(
|
|
792
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, created_at, updated_at
|
|
1035
|
+
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, updated_at
|
|
793
1036
|
FROM ${SITES_TABLE} FINAL
|
|
794
1037
|
WHERE secret_key = {secretKey:String} AND is_deleted = 0`,
|
|
795
1038
|
{ secretKey }
|
|
@@ -798,7 +1041,7 @@ var ClickHouseAdapter = class {
|
|
|
798
1041
|
}
|
|
799
1042
|
async listSites() {
|
|
800
1043
|
const rows = await this.queryRows(
|
|
801
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, created_at, updated_at
|
|
1044
|
+
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, updated_at
|
|
802
1045
|
FROM ${SITES_TABLE} FINAL
|
|
803
1046
|
WHERE is_deleted = 0
|
|
804
1047
|
ORDER BY created_at DESC`,
|
|
@@ -808,7 +1051,7 @@ var ClickHouseAdapter = class {
|
|
|
808
1051
|
}
|
|
809
1052
|
async updateSite(siteId, data) {
|
|
810
1053
|
const currentRows = await this.queryRows(
|
|
811
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, created_at, updated_at, version
|
|
1054
|
+
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, updated_at, version
|
|
812
1055
|
FROM ${SITES_TABLE} FINAL
|
|
813
1056
|
WHERE site_id = {siteId:String} AND is_deleted = 0`,
|
|
814
1057
|
{ siteId }
|
|
@@ -822,6 +1065,7 @@ var ClickHouseAdapter = class {
|
|
|
822
1065
|
const newName = data.name !== void 0 ? data.name : String(current.name);
|
|
823
1066
|
const newDomain = data.domain !== void 0 ? data.domain || null : current.domain ? String(current.domain) : null;
|
|
824
1067
|
const newOrigins = data.allowedOrigins !== void 0 ? data.allowedOrigins.length > 0 ? JSON.stringify(data.allowedOrigins) : null : current.allowed_origins ? String(current.allowed_origins) : null;
|
|
1068
|
+
const newConversions = data.conversionEvents !== void 0 ? data.conversionEvents.length > 0 ? JSON.stringify(data.conversionEvents) : null : current.conversion_events ? String(current.conversion_events) : null;
|
|
825
1069
|
await this.client.insert({
|
|
826
1070
|
table: SITES_TABLE,
|
|
827
1071
|
values: [{
|
|
@@ -830,6 +1074,7 @@ var ClickHouseAdapter = class {
|
|
|
830
1074
|
name: newName,
|
|
831
1075
|
domain: newDomain,
|
|
832
1076
|
allowed_origins: newOrigins,
|
|
1077
|
+
conversion_events: newConversions,
|
|
833
1078
|
created_at: toCHDateTime(String(current.created_at)),
|
|
834
1079
|
updated_at: nowCH,
|
|
835
1080
|
version: newVersion,
|
|
@@ -843,13 +1088,14 @@ var ClickHouseAdapter = class {
|
|
|
843
1088
|
name: newName,
|
|
844
1089
|
domain: newDomain ?? void 0,
|
|
845
1090
|
allowedOrigins: newOrigins ? JSON.parse(newOrigins) : void 0,
|
|
1091
|
+
conversionEvents: newConversions ? JSON.parse(newConversions) : void 0,
|
|
846
1092
|
createdAt: String(current.created_at),
|
|
847
1093
|
updatedAt: nowISO
|
|
848
1094
|
};
|
|
849
1095
|
}
|
|
850
1096
|
async deleteSite(siteId) {
|
|
851
1097
|
const currentRows = await this.queryRows(
|
|
852
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, created_at, version
|
|
1098
|
+
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, version
|
|
853
1099
|
FROM ${SITES_TABLE} FINAL
|
|
854
1100
|
WHERE site_id = {siteId:String} AND is_deleted = 0`,
|
|
855
1101
|
{ siteId }
|
|
@@ -865,6 +1111,7 @@ var ClickHouseAdapter = class {
|
|
|
865
1111
|
name: String(current.name),
|
|
866
1112
|
domain: current.domain ? String(current.domain) : null,
|
|
867
1113
|
allowed_origins: current.allowed_origins ? String(current.allowed_origins) : null,
|
|
1114
|
+
conversion_events: current.conversion_events ? String(current.conversion_events) : null,
|
|
868
1115
|
created_at: toCHDateTime(String(current.created_at)),
|
|
869
1116
|
updated_at: nowCH,
|
|
870
1117
|
version: Number(current.version) + 1,
|
|
@@ -876,7 +1123,7 @@ var ClickHouseAdapter = class {
|
|
|
876
1123
|
}
|
|
877
1124
|
async regenerateSecret(siteId) {
|
|
878
1125
|
const currentRows = await this.queryRows(
|
|
879
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, created_at, version
|
|
1126
|
+
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, version
|
|
880
1127
|
FROM ${SITES_TABLE} FINAL
|
|
881
1128
|
WHERE site_id = {siteId:String} AND is_deleted = 0`,
|
|
882
1129
|
{ siteId }
|
|
@@ -895,6 +1142,7 @@ var ClickHouseAdapter = class {
|
|
|
895
1142
|
name: String(current.name),
|
|
896
1143
|
domain: current.domain ? String(current.domain) : null,
|
|
897
1144
|
allowed_origins: current.allowed_origins ? String(current.allowed_origins) : null,
|
|
1145
|
+
conversion_events: current.conversion_events ? String(current.conversion_events) : null,
|
|
898
1146
|
created_at: toCHDateTime(String(current.created_at)),
|
|
899
1147
|
updated_at: nowCH,
|
|
900
1148
|
version: Number(current.version) + 1,
|
|
@@ -908,6 +1156,7 @@ var ClickHouseAdapter = class {
|
|
|
908
1156
|
name: String(current.name),
|
|
909
1157
|
domain: current.domain ? String(current.domain) : void 0,
|
|
910
1158
|
allowedOrigins: current.allowed_origins ? JSON.parse(String(current.allowed_origins)) : void 0,
|
|
1159
|
+
conversionEvents: current.conversion_events ? JSON.parse(String(current.conversion_events)) : void 0,
|
|
911
1160
|
createdAt: String(current.created_at),
|
|
912
1161
|
updatedAt: nowISO
|
|
913
1162
|
};
|
|
@@ -928,6 +1177,7 @@ var ClickHouseAdapter = class {
|
|
|
928
1177
|
name: String(row.name),
|
|
929
1178
|
domain: row.domain ? String(row.domain) : void 0,
|
|
930
1179
|
allowedOrigins: row.allowed_origins ? JSON.parse(String(row.allowed_origins)) : void 0,
|
|
1180
|
+
conversionEvents: row.conversion_events ? JSON.parse(String(row.conversion_events)) : void 0,
|
|
931
1181
|
createdAt: new Date(String(row.created_at)).toISOString(),
|
|
932
1182
|
updatedAt: new Date(String(row.updated_at)).toISOString()
|
|
933
1183
|
};
|
|
@@ -944,6 +1194,13 @@ var ClickHouseAdapter = class {
|
|
|
944
1194
|
title: row.title ? String(row.title) : void 0,
|
|
945
1195
|
name: row.event_name ? String(row.event_name) : void 0,
|
|
946
1196
|
properties: this.parseJSON(row.properties),
|
|
1197
|
+
eventSource: row.event_source ? String(row.event_source) : void 0,
|
|
1198
|
+
eventSubtype: row.event_subtype ? String(row.event_subtype) : void 0,
|
|
1199
|
+
pagePath: row.page_path ? String(row.page_path) : void 0,
|
|
1200
|
+
targetUrlPath: row.target_url_path ? String(row.target_url_path) : void 0,
|
|
1201
|
+
elementSelector: row.element_selector ? String(row.element_selector) : void 0,
|
|
1202
|
+
elementText: row.element_text ? String(row.element_text) : void 0,
|
|
1203
|
+
scrollDepthPct: row.scroll_depth_pct !== null && row.scroll_depth_pct !== void 0 ? Number(row.scroll_depth_pct) : void 0,
|
|
947
1204
|
userId: row.user_id ? String(row.user_id) : void 0,
|
|
948
1205
|
traits: this.parseJSON(row.traits),
|
|
949
1206
|
geo: row.country ? {
|
|
@@ -980,6 +1237,36 @@ var ClickHouseAdapter = class {
|
|
|
980
1237
|
import { MongoClient } from "mongodb";
|
|
981
1238
|
var EVENTS_COLLECTION = "litemetrics_events";
|
|
982
1239
|
var SITES_COLLECTION = "litemetrics_sites";
|
|
1240
|
+
function buildFilterMatch(filters) {
|
|
1241
|
+
if (!filters) return {};
|
|
1242
|
+
const map = {
|
|
1243
|
+
"geo.country": "country",
|
|
1244
|
+
"geo.city": "city",
|
|
1245
|
+
"geo.region": "region",
|
|
1246
|
+
"language": "language",
|
|
1247
|
+
"device.type": "device_type",
|
|
1248
|
+
"device.browser": "browser",
|
|
1249
|
+
"device.os": "os",
|
|
1250
|
+
"utm.source": "utm_source",
|
|
1251
|
+
"utm.medium": "utm_medium",
|
|
1252
|
+
"utm.campaign": "utm_campaign",
|
|
1253
|
+
"utm.term": "utm_term",
|
|
1254
|
+
"utm.content": "utm_content",
|
|
1255
|
+
"referrer": "referrer",
|
|
1256
|
+
"event_source": "event_source",
|
|
1257
|
+
"event_subtype": "event_subtype",
|
|
1258
|
+
"page_path": "page_path",
|
|
1259
|
+
"target_url_path": "target_url_path",
|
|
1260
|
+
"event_name": "event_name",
|
|
1261
|
+
"type": "type"
|
|
1262
|
+
};
|
|
1263
|
+
const match = {};
|
|
1264
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
1265
|
+
if (!value || !map[key]) continue;
|
|
1266
|
+
match[map[key]] = value;
|
|
1267
|
+
}
|
|
1268
|
+
return match;
|
|
1269
|
+
}
|
|
983
1270
|
var MongoDBAdapter = class {
|
|
984
1271
|
client;
|
|
985
1272
|
db;
|
|
@@ -1015,6 +1302,13 @@ var MongoDBAdapter = class {
|
|
|
1015
1302
|
title: e.title ?? null,
|
|
1016
1303
|
event_name: e.name ?? null,
|
|
1017
1304
|
properties: e.properties ?? null,
|
|
1305
|
+
event_source: e.eventSource ?? null,
|
|
1306
|
+
event_subtype: e.eventSubtype ?? null,
|
|
1307
|
+
page_path: e.pagePath ?? null,
|
|
1308
|
+
target_url_path: e.targetUrlPath ?? null,
|
|
1309
|
+
element_selector: e.elementSelector ?? null,
|
|
1310
|
+
element_text: e.elementText ?? null,
|
|
1311
|
+
scroll_depth_pct: e.scrollDepthPct ?? null,
|
|
1018
1312
|
user_id: e.userId ?? null,
|
|
1019
1313
|
traits: e.traits ?? null,
|
|
1020
1314
|
country: e.geo?.country ?? null,
|
|
@@ -1045,12 +1339,13 @@ var MongoDBAdapter = class {
|
|
|
1045
1339
|
site_id: siteId,
|
|
1046
1340
|
timestamp: { $gte: new Date(dateRange.from), $lte: new Date(dateRange.to) }
|
|
1047
1341
|
};
|
|
1342
|
+
const filterMatch = buildFilterMatch(q.filters);
|
|
1048
1343
|
let data = [];
|
|
1049
1344
|
let total = 0;
|
|
1050
1345
|
switch (q.metric) {
|
|
1051
1346
|
case "pageviews": {
|
|
1052
1347
|
const [result2] = await this.collection.aggregate([
|
|
1053
|
-
{ $match: { ...baseMatch, type: "pageview" } },
|
|
1348
|
+
{ $match: { ...baseMatch, ...filterMatch, type: "pageview" } },
|
|
1054
1349
|
{ $count: "count" }
|
|
1055
1350
|
]).toArray();
|
|
1056
1351
|
total = result2?.count ?? 0;
|
|
@@ -1059,7 +1354,7 @@ var MongoDBAdapter = class {
|
|
|
1059
1354
|
}
|
|
1060
1355
|
case "visitors": {
|
|
1061
1356
|
const [result2] = await this.collection.aggregate([
|
|
1062
|
-
{ $match: baseMatch },
|
|
1357
|
+
{ $match: { ...baseMatch, ...filterMatch } },
|
|
1063
1358
|
{ $group: { _id: "$visitor_id" } },
|
|
1064
1359
|
{ $count: "count" }
|
|
1065
1360
|
]).toArray();
|
|
@@ -1069,7 +1364,7 @@ var MongoDBAdapter = class {
|
|
|
1069
1364
|
}
|
|
1070
1365
|
case "sessions": {
|
|
1071
1366
|
const [result2] = await this.collection.aggregate([
|
|
1072
|
-
{ $match: baseMatch },
|
|
1367
|
+
{ $match: { ...baseMatch, ...filterMatch } },
|
|
1073
1368
|
{ $group: { _id: "$session_id" } },
|
|
1074
1369
|
{ $count: "count" }
|
|
1075
1370
|
]).toArray();
|
|
@@ -1079,16 +1374,31 @@ var MongoDBAdapter = class {
|
|
|
1079
1374
|
}
|
|
1080
1375
|
case "events": {
|
|
1081
1376
|
const [result2] = await this.collection.aggregate([
|
|
1082
|
-
{ $match: { ...baseMatch, type: "event" } },
|
|
1377
|
+
{ $match: { ...baseMatch, ...filterMatch, type: "event" } },
|
|
1083
1378
|
{ $count: "count" }
|
|
1084
1379
|
]).toArray();
|
|
1085
1380
|
total = result2?.count ?? 0;
|
|
1086
1381
|
data = [{ key: "events", value: total }];
|
|
1087
1382
|
break;
|
|
1088
1383
|
}
|
|
1384
|
+
case "conversions": {
|
|
1385
|
+
const conversionEvents = q.conversionEvents ?? [];
|
|
1386
|
+
if (conversionEvents.length === 0) {
|
|
1387
|
+
total = 0;
|
|
1388
|
+
data = [{ key: "conversions", value: 0 }];
|
|
1389
|
+
break;
|
|
1390
|
+
}
|
|
1391
|
+
const [result2] = await this.collection.aggregate([
|
|
1392
|
+
{ $match: { ...baseMatch, ...filterMatch, type: "event", event_name: { $in: conversionEvents } } },
|
|
1393
|
+
{ $count: "count" }
|
|
1394
|
+
]).toArray();
|
|
1395
|
+
total = result2?.count ?? 0;
|
|
1396
|
+
data = [{ key: "conversions", value: total }];
|
|
1397
|
+
break;
|
|
1398
|
+
}
|
|
1089
1399
|
case "top_pages": {
|
|
1090
1400
|
const rows = await this.collection.aggregate([
|
|
1091
|
-
{ $match: { ...baseMatch, type: "pageview", url: { $ne: null } } },
|
|
1401
|
+
{ $match: { ...baseMatch, ...filterMatch, type: "pageview", url: { $ne: null } } },
|
|
1092
1402
|
{ $group: { _id: "$url", value: { $sum: 1 } } },
|
|
1093
1403
|
{ $sort: { value: -1 } },
|
|
1094
1404
|
{ $limit: limit }
|
|
@@ -1099,7 +1409,7 @@ var MongoDBAdapter = class {
|
|
|
1099
1409
|
}
|
|
1100
1410
|
case "top_referrers": {
|
|
1101
1411
|
const rows = await this.collection.aggregate([
|
|
1102
|
-
{ $match: { ...baseMatch, type: "pageview", referrer: { $nin: [null, ""] } } },
|
|
1412
|
+
{ $match: { ...baseMatch, ...filterMatch, type: "pageview", referrer: { $nin: [null, ""] } } },
|
|
1103
1413
|
{ $group: { _id: "$referrer", value: { $sum: 1 } } },
|
|
1104
1414
|
{ $sort: { value: -1 } },
|
|
1105
1415
|
{ $limit: limit }
|
|
@@ -1110,7 +1420,7 @@ var MongoDBAdapter = class {
|
|
|
1110
1420
|
}
|
|
1111
1421
|
case "top_countries": {
|
|
1112
1422
|
const rows = await this.collection.aggregate([
|
|
1113
|
-
{ $match: { ...baseMatch, country: { $ne: null } } },
|
|
1423
|
+
{ $match: { ...baseMatch, ...filterMatch, country: { $ne: null } } },
|
|
1114
1424
|
{ $group: { _id: "$country", value: { $addToSet: "$visitor_id" } } },
|
|
1115
1425
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1116
1426
|
{ $sort: { value: -1 } },
|
|
@@ -1122,7 +1432,7 @@ var MongoDBAdapter = class {
|
|
|
1122
1432
|
}
|
|
1123
1433
|
case "top_cities": {
|
|
1124
1434
|
const rows = await this.collection.aggregate([
|
|
1125
|
-
{ $match: { ...baseMatch, city: { $ne: null } } },
|
|
1435
|
+
{ $match: { ...baseMatch, ...filterMatch, city: { $ne: null } } },
|
|
1126
1436
|
{ $group: { _id: "$city", value: { $addToSet: "$visitor_id" } } },
|
|
1127
1437
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1128
1438
|
{ $sort: { value: -1 } },
|
|
@@ -1134,7 +1444,7 @@ var MongoDBAdapter = class {
|
|
|
1134
1444
|
}
|
|
1135
1445
|
case "top_events": {
|
|
1136
1446
|
const rows = await this.collection.aggregate([
|
|
1137
|
-
{ $match: { ...baseMatch, type: "event", event_name: { $ne: null } } },
|
|
1447
|
+
{ $match: { ...baseMatch, ...filterMatch, type: "event", event_name: { $ne: null } } },
|
|
1138
1448
|
{ $group: { _id: "$event_name", value: { $sum: 1 } } },
|
|
1139
1449
|
{ $sort: { value: -1 } },
|
|
1140
1450
|
{ $limit: limit }
|
|
@@ -1143,9 +1453,109 @@ var MongoDBAdapter = class {
|
|
|
1143
1453
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1144
1454
|
break;
|
|
1145
1455
|
}
|
|
1456
|
+
case "top_conversions": {
|
|
1457
|
+
const conversionEvents = q.conversionEvents ?? [];
|
|
1458
|
+
if (conversionEvents.length === 0) {
|
|
1459
|
+
total = 0;
|
|
1460
|
+
data = [];
|
|
1461
|
+
break;
|
|
1462
|
+
}
|
|
1463
|
+
const rows = await this.collection.aggregate([
|
|
1464
|
+
{ $match: { ...baseMatch, ...filterMatch, type: "event", event_name: { $in: conversionEvents } } },
|
|
1465
|
+
{ $group: { _id: "$event_name", value: { $sum: 1 } } },
|
|
1466
|
+
{ $sort: { value: -1 } },
|
|
1467
|
+
{ $limit: limit }
|
|
1468
|
+
]).toArray();
|
|
1469
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
1470
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1471
|
+
break;
|
|
1472
|
+
}
|
|
1473
|
+
case "top_exit_pages": {
|
|
1474
|
+
const rows = await this.collection.aggregate([
|
|
1475
|
+
{ $match: { ...baseMatch, ...filterMatch, type: "pageview", url: { $ne: null } } },
|
|
1476
|
+
{ $sort: { timestamp: 1 } },
|
|
1477
|
+
{ $group: { _id: "$session_id", url: { $last: "$url" } } },
|
|
1478
|
+
{ $group: { _id: "$url", value: { $sum: 1 } } },
|
|
1479
|
+
{ $sort: { value: -1 } },
|
|
1480
|
+
{ $limit: limit }
|
|
1481
|
+
]).toArray();
|
|
1482
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
1483
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1484
|
+
break;
|
|
1485
|
+
}
|
|
1486
|
+
case "top_transitions": {
|
|
1487
|
+
const rows = await this.collection.aggregate([
|
|
1488
|
+
{ $match: { ...baseMatch, ...filterMatch, type: "pageview", url: { $ne: null } } },
|
|
1489
|
+
{
|
|
1490
|
+
$setWindowFields: {
|
|
1491
|
+
partitionBy: "$session_id",
|
|
1492
|
+
sortBy: { timestamp: 1 },
|
|
1493
|
+
output: {
|
|
1494
|
+
prev_url: { $shift: { output: "$url", by: -1 } }
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
},
|
|
1498
|
+
{ $match: { prev_url: { $ne: null } } },
|
|
1499
|
+
{ $group: { _id: { $concat: ["$prev_url", " \u2192 ", "$url"] }, value: { $sum: 1 } } },
|
|
1500
|
+
{ $sort: { value: -1 } },
|
|
1501
|
+
{ $limit: limit }
|
|
1502
|
+
]).toArray();
|
|
1503
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
1504
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1505
|
+
break;
|
|
1506
|
+
}
|
|
1507
|
+
case "top_scroll_pages": {
|
|
1508
|
+
const rows = await this.collection.aggregate([
|
|
1509
|
+
{ $match: { ...baseMatch, ...filterMatch, type: "event", event_subtype: "scroll_depth", page_path: { $ne: null } } },
|
|
1510
|
+
{ $group: { _id: "$page_path", value: { $sum: 1 } } },
|
|
1511
|
+
{ $sort: { value: -1 } },
|
|
1512
|
+
{ $limit: limit }
|
|
1513
|
+
]).toArray();
|
|
1514
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
1515
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1516
|
+
break;
|
|
1517
|
+
}
|
|
1518
|
+
case "top_button_clicks": {
|
|
1519
|
+
const rows = await this.collection.aggregate([
|
|
1520
|
+
{
|
|
1521
|
+
$match: {
|
|
1522
|
+
...baseMatch,
|
|
1523
|
+
...filterMatch,
|
|
1524
|
+
type: "event",
|
|
1525
|
+
event_subtype: "button_click",
|
|
1526
|
+
$or: [{ element_text: { $ne: null } }, { element_selector: { $ne: null } }]
|
|
1527
|
+
}
|
|
1528
|
+
},
|
|
1529
|
+
{ $group: { _id: { $ifNull: ["$element_text", "$element_selector"] }, value: { $sum: 1 } } },
|
|
1530
|
+
{ $sort: { value: -1 } },
|
|
1531
|
+
{ $limit: limit }
|
|
1532
|
+
]).toArray();
|
|
1533
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
1534
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1535
|
+
break;
|
|
1536
|
+
}
|
|
1537
|
+
case "top_link_targets": {
|
|
1538
|
+
const rows = await this.collection.aggregate([
|
|
1539
|
+
{
|
|
1540
|
+
$match: {
|
|
1541
|
+
...baseMatch,
|
|
1542
|
+
...filterMatch,
|
|
1543
|
+
type: "event",
|
|
1544
|
+
event_subtype: { $in: ["link_click", "outbound_click"] },
|
|
1545
|
+
target_url_path: { $ne: null }
|
|
1546
|
+
}
|
|
1547
|
+
},
|
|
1548
|
+
{ $group: { _id: "$target_url_path", value: { $sum: 1 } } },
|
|
1549
|
+
{ $sort: { value: -1 } },
|
|
1550
|
+
{ $limit: limit }
|
|
1551
|
+
]).toArray();
|
|
1552
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
1553
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1554
|
+
break;
|
|
1555
|
+
}
|
|
1146
1556
|
case "top_devices": {
|
|
1147
1557
|
const rows = await this.collection.aggregate([
|
|
1148
|
-
{ $match: { ...baseMatch, device_type: { $ne: null } } },
|
|
1558
|
+
{ $match: { ...baseMatch, ...filterMatch, device_type: { $ne: null } } },
|
|
1149
1559
|
{ $group: { _id: "$device_type", value: { $addToSet: "$visitor_id" } } },
|
|
1150
1560
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1151
1561
|
{ $sort: { value: -1 } },
|
|
@@ -1157,7 +1567,7 @@ var MongoDBAdapter = class {
|
|
|
1157
1567
|
}
|
|
1158
1568
|
case "top_browsers": {
|
|
1159
1569
|
const rows = await this.collection.aggregate([
|
|
1160
|
-
{ $match: { ...baseMatch, browser: { $ne: null } } },
|
|
1570
|
+
{ $match: { ...baseMatch, ...filterMatch, browser: { $ne: null } } },
|
|
1161
1571
|
{ $group: { _id: "$browser", value: { $addToSet: "$visitor_id" } } },
|
|
1162
1572
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1163
1573
|
{ $sort: { value: -1 } },
|
|
@@ -1169,7 +1579,7 @@ var MongoDBAdapter = class {
|
|
|
1169
1579
|
}
|
|
1170
1580
|
case "top_os": {
|
|
1171
1581
|
const rows = await this.collection.aggregate([
|
|
1172
|
-
{ $match: { ...baseMatch, os: { $ne: null } } },
|
|
1582
|
+
{ $match: { ...baseMatch, ...filterMatch, os: { $ne: null } } },
|
|
1173
1583
|
{ $group: { _id: "$os", value: { $addToSet: "$visitor_id" } } },
|
|
1174
1584
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1175
1585
|
{ $sort: { value: -1 } },
|
|
@@ -1181,7 +1591,7 @@ var MongoDBAdapter = class {
|
|
|
1181
1591
|
}
|
|
1182
1592
|
}
|
|
1183
1593
|
const result = { metric: q.metric, period, data, total };
|
|
1184
|
-
if (q.compare && ["pageviews", "visitors", "sessions", "events"].includes(q.metric)) {
|
|
1594
|
+
if (q.compare && ["pageviews", "visitors", "sessions", "events", "conversions"].includes(q.metric)) {
|
|
1185
1595
|
const prevRange = previousPeriodRange(dateRange);
|
|
1186
1596
|
const prevResult = await this.query({
|
|
1187
1597
|
...q,
|
|
@@ -1213,15 +1623,34 @@ var MongoDBAdapter = class {
|
|
|
1213
1623
|
site_id: params.siteId,
|
|
1214
1624
|
timestamp: { $gte: new Date(dateRange.from), $lte: new Date(dateRange.to) }
|
|
1215
1625
|
};
|
|
1626
|
+
const filterMatch = buildFilterMatch(params.filters);
|
|
1216
1627
|
if (params.metric === "pageviews") {
|
|
1217
1628
|
baseMatch.type = "pageview";
|
|
1218
1629
|
}
|
|
1630
|
+
if (params.metric === "events") {
|
|
1631
|
+
baseMatch.type = "event";
|
|
1632
|
+
}
|
|
1633
|
+
if (params.metric === "conversions") {
|
|
1634
|
+
baseMatch.type = "event";
|
|
1635
|
+
const conversionEvents = params.conversionEvents ?? [];
|
|
1636
|
+
if (conversionEvents.length === 0) {
|
|
1637
|
+
const data2 = fillBuckets(
|
|
1638
|
+
new Date(dateRange.from),
|
|
1639
|
+
new Date(dateRange.to),
|
|
1640
|
+
granularity,
|
|
1641
|
+
granularityToDateFormat(granularity),
|
|
1642
|
+
[]
|
|
1643
|
+
);
|
|
1644
|
+
return { metric: params.metric, granularity, data: data2 };
|
|
1645
|
+
}
|
|
1646
|
+
baseMatch.event_name = { $in: conversionEvents };
|
|
1647
|
+
}
|
|
1219
1648
|
const dateFormat = granularityToDateFormat(granularity);
|
|
1220
1649
|
let pipeline;
|
|
1221
1650
|
if (params.metric === "visitors" || params.metric === "sessions") {
|
|
1222
1651
|
const groupField = params.metric === "visitors" ? "$visitor_id" : "$session_id";
|
|
1223
1652
|
pipeline = [
|
|
1224
|
-
{ $match: baseMatch },
|
|
1653
|
+
{ $match: { ...baseMatch, ...filterMatch } },
|
|
1225
1654
|
{
|
|
1226
1655
|
$group: {
|
|
1227
1656
|
_id: {
|
|
@@ -1240,7 +1669,7 @@ var MongoDBAdapter = class {
|
|
|
1240
1669
|
];
|
|
1241
1670
|
} else {
|
|
1242
1671
|
pipeline = [
|
|
1243
|
-
{ $match: baseMatch },
|
|
1672
|
+
{ $match: { ...baseMatch, ...filterMatch } },
|
|
1244
1673
|
{
|
|
1245
1674
|
$group: {
|
|
1246
1675
|
_id: { $dateToString: { format: dateFormat, date: "$timestamp" } },
|
|
@@ -1321,7 +1750,12 @@ var MongoDBAdapter = class {
|
|
|
1321
1750
|
const offset = params.offset ?? 0;
|
|
1322
1751
|
const match = { site_id: params.siteId };
|
|
1323
1752
|
if (params.type) match.type = params.type;
|
|
1324
|
-
if (params.eventName)
|
|
1753
|
+
if (params.eventName) {
|
|
1754
|
+
match.event_name = params.eventName;
|
|
1755
|
+
} else if (params.eventNames && params.eventNames.length > 0) {
|
|
1756
|
+
match.event_name = { $in: params.eventNames };
|
|
1757
|
+
}
|
|
1758
|
+
if (params.eventSource) match.event_source = params.eventSource;
|
|
1325
1759
|
if (params.visitorId) match.visitor_id = params.visitorId;
|
|
1326
1760
|
if (params.userId) match.user_id = params.userId;
|
|
1327
1761
|
if (params.period || params.dateFrom) {
|
|
@@ -1356,6 +1790,7 @@ var MongoDBAdapter = class {
|
|
|
1356
1790
|
}
|
|
1357
1791
|
const pipeline = [
|
|
1358
1792
|
{ $match: match },
|
|
1793
|
+
{ $sort: { timestamp: 1 } },
|
|
1359
1794
|
{
|
|
1360
1795
|
$group: {
|
|
1361
1796
|
_id: "$visitor_id",
|
|
@@ -1367,13 +1802,22 @@ var MongoDBAdapter = class {
|
|
|
1367
1802
|
totalPageviews: { $sum: { $cond: [{ $eq: ["$type", "pageview"] }, 1, 0] } },
|
|
1368
1803
|
sessions: { $addToSet: "$session_id" },
|
|
1369
1804
|
lastUrl: { $last: "$url" },
|
|
1805
|
+
referrer: { $last: "$referrer" },
|
|
1370
1806
|
device_type: { $last: "$device_type" },
|
|
1371
1807
|
browser: { $last: "$browser" },
|
|
1372
1808
|
os: { $last: "$os" },
|
|
1373
1809
|
country: { $last: "$country" },
|
|
1374
1810
|
city: { $last: "$city" },
|
|
1375
1811
|
region: { $last: "$region" },
|
|
1376
|
-
language: { $last: "$language" }
|
|
1812
|
+
language: { $last: "$language" },
|
|
1813
|
+
timezone: { $last: "$timezone" },
|
|
1814
|
+
screen_width: { $last: "$screen_width" },
|
|
1815
|
+
screen_height: { $last: "$screen_height" },
|
|
1816
|
+
utm_source: { $last: "$utm_source" },
|
|
1817
|
+
utm_medium: { $last: "$utm_medium" },
|
|
1818
|
+
utm_campaign: { $last: "$utm_campaign" },
|
|
1819
|
+
utm_term: { $last: "$utm_term" },
|
|
1820
|
+
utm_content: { $last: "$utm_content" }
|
|
1377
1821
|
}
|
|
1378
1822
|
},
|
|
1379
1823
|
{ $sort: { lastSeen: -1 } },
|
|
@@ -1395,9 +1839,19 @@ var MongoDBAdapter = class {
|
|
|
1395
1839
|
totalPageviews: u.totalPageviews,
|
|
1396
1840
|
totalSessions: u.sessions.length,
|
|
1397
1841
|
lastUrl: u.lastUrl ?? void 0,
|
|
1842
|
+
referrer: u.referrer ?? void 0,
|
|
1398
1843
|
device: u.device_type ? { type: u.device_type, browser: u.browser ?? "", os: u.os ?? "" } : void 0,
|
|
1399
1844
|
geo: u.country ? { country: u.country, city: u.city ?? void 0, region: u.region ?? void 0 } : void 0,
|
|
1400
|
-
language: u.language ?? void 0
|
|
1845
|
+
language: u.language ?? void 0,
|
|
1846
|
+
timezone: u.timezone ?? void 0,
|
|
1847
|
+
screen: u.screen_width || u.screen_height ? { width: u.screen_width ?? 0, height: u.screen_height ?? 0 } : void 0,
|
|
1848
|
+
utm: u.utm_source ? {
|
|
1849
|
+
source: u.utm_source ?? void 0,
|
|
1850
|
+
medium: u.utm_medium ?? void 0,
|
|
1851
|
+
campaign: u.utm_campaign ?? void 0,
|
|
1852
|
+
term: u.utm_term ?? void 0,
|
|
1853
|
+
content: u.utm_content ?? void 0
|
|
1854
|
+
} : void 0
|
|
1401
1855
|
}));
|
|
1402
1856
|
return {
|
|
1403
1857
|
users,
|
|
@@ -1426,6 +1880,13 @@ var MongoDBAdapter = class {
|
|
|
1426
1880
|
title: doc.title ?? void 0,
|
|
1427
1881
|
name: doc.event_name ?? void 0,
|
|
1428
1882
|
properties: doc.properties ?? void 0,
|
|
1883
|
+
eventSource: doc.event_source ? doc.event_source : void 0,
|
|
1884
|
+
eventSubtype: doc.event_subtype ? doc.event_subtype : void 0,
|
|
1885
|
+
pagePath: doc.page_path ?? void 0,
|
|
1886
|
+
targetUrlPath: doc.target_url_path ?? void 0,
|
|
1887
|
+
elementSelector: doc.element_selector ?? void 0,
|
|
1888
|
+
elementText: doc.element_text ?? void 0,
|
|
1889
|
+
scrollDepthPct: doc.scroll_depth_pct ?? void 0,
|
|
1429
1890
|
userId: doc.user_id ?? void 0,
|
|
1430
1891
|
traits: doc.traits ?? void 0,
|
|
1431
1892
|
geo: doc.country ? { country: doc.country, city: doc.city ?? void 0, region: doc.region ?? void 0 } : void 0,
|
|
@@ -1449,6 +1910,7 @@ var MongoDBAdapter = class {
|
|
|
1449
1910
|
name: data.name,
|
|
1450
1911
|
domain: data.domain ?? null,
|
|
1451
1912
|
allowed_origins: data.allowedOrigins ?? null,
|
|
1913
|
+
conversion_events: data.conversionEvents ?? null,
|
|
1452
1914
|
created_at: now,
|
|
1453
1915
|
updated_at: now
|
|
1454
1916
|
};
|
|
@@ -1472,6 +1934,7 @@ var MongoDBAdapter = class {
|
|
|
1472
1934
|
if (data.name !== void 0) updates.name = data.name;
|
|
1473
1935
|
if (data.domain !== void 0) updates.domain = data.domain || null;
|
|
1474
1936
|
if (data.allowedOrigins !== void 0) updates.allowed_origins = data.allowedOrigins.length > 0 ? data.allowedOrigins : null;
|
|
1937
|
+
if (data.conversionEvents !== void 0) updates.conversion_events = data.conversionEvents.length > 0 ? data.conversionEvents : null;
|
|
1475
1938
|
const result = await this.sites.findOneAndUpdate(
|
|
1476
1939
|
{ site_id: siteId },
|
|
1477
1940
|
{ $set: updates },
|
|
@@ -1502,6 +1965,7 @@ var MongoDBAdapter = class {
|
|
|
1502
1965
|
name: doc.name,
|
|
1503
1966
|
domain: doc.domain ?? void 0,
|
|
1504
1967
|
allowedOrigins: doc.allowed_origins ?? void 0,
|
|
1968
|
+
conversionEvents: doc.conversion_events ?? void 0,
|
|
1505
1969
|
createdAt: doc.created_at.toISOString(),
|
|
1506
1970
|
updatedAt: doc.updated_at.toISOString()
|
|
1507
1971
|
};
|
|
@@ -1755,6 +2219,7 @@ async function createCollector(config) {
|
|
|
1755
2219
|
if (allowed) {
|
|
1756
2220
|
res.setHeader?.("Access-Control-Allow-Origin", origin || "*");
|
|
1757
2221
|
res.setHeader?.("Access-Control-Allow-Methods", methods);
|
|
2222
|
+
res.setHeader?.("Access-Control-Allow-Credentials", "true");
|
|
1758
2223
|
const headers = ["Content-Type", extraHeaders].filter(Boolean).join(", ");
|
|
1759
2224
|
res.setHeader?.("Access-Control-Allow-Headers", headers);
|
|
1760
2225
|
}
|
|
@@ -1785,7 +2250,14 @@ async function createCollector(config) {
|
|
|
1785
2250
|
}
|
|
1786
2251
|
function handler() {
|
|
1787
2252
|
return async (req, res) => {
|
|
1788
|
-
|
|
2253
|
+
res.setHeader?.("Access-Control-Allow-Origin", "*");
|
|
2254
|
+
res.setHeader?.("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
2255
|
+
res.setHeader?.("Access-Control-Allow-Headers", "Content-Type");
|
|
2256
|
+
if (req.method === "OPTIONS") {
|
|
2257
|
+
res.writeHead?.(204);
|
|
2258
|
+
res.end?.();
|
|
2259
|
+
return;
|
|
2260
|
+
}
|
|
1789
2261
|
if (req.method !== "POST") {
|
|
1790
2262
|
sendJson(res, 405, { ok: false, error: "Method not allowed" });
|
|
1791
2263
|
return;
|
|
@@ -1864,8 +2336,13 @@ async function createCollector(config) {
|
|
|
1864
2336
|
period: params.period,
|
|
1865
2337
|
dateFrom: params.dateFrom,
|
|
1866
2338
|
dateTo: params.dateTo,
|
|
1867
|
-
granularity: q.granularity
|
|
2339
|
+
granularity: q.granularity,
|
|
2340
|
+
filters: q.filters ? JSON.parse(q.filters) : void 0
|
|
1868
2341
|
};
|
|
2342
|
+
if (tsParams.metric === "conversions") {
|
|
2343
|
+
const site = await db.getSite(params.siteId);
|
|
2344
|
+
tsParams.conversionEvents = site?.conversionEvents ?? [];
|
|
2345
|
+
}
|
|
1869
2346
|
const result2 = await db.queryTimeSeries(tsParams);
|
|
1870
2347
|
sendJson(res, 200, result2);
|
|
1871
2348
|
return;
|
|
@@ -1881,7 +2358,15 @@ async function createCollector(config) {
|
|
|
1881
2358
|
sendJson(res, 200, result2);
|
|
1882
2359
|
return;
|
|
1883
2360
|
}
|
|
1884
|
-
const
|
|
2361
|
+
const isConversionMetric = params.metric === "conversions" || params.metric === "top_conversions";
|
|
2362
|
+
let result;
|
|
2363
|
+
if (isConversionMetric) {
|
|
2364
|
+
const site = await db.getSite(params.siteId);
|
|
2365
|
+
const conversionEvents = site?.conversionEvents ?? [];
|
|
2366
|
+
result = await db.query({ ...params, conversionEvents });
|
|
2367
|
+
} else {
|
|
2368
|
+
result = await db.query(params);
|
|
2369
|
+
}
|
|
1885
2370
|
sendJson(res, 200, result);
|
|
1886
2371
|
} catch (err) {
|
|
1887
2372
|
sendJson(res, 500, { ok: false, error: err instanceof Error ? err.message : "Internal error" });
|
|
@@ -1978,10 +2463,13 @@ async function createCollector(config) {
|
|
|
1978
2463
|
sendJson(res, 401, { ok: false, error: "Invalid or missing secret key" });
|
|
1979
2464
|
return;
|
|
1980
2465
|
}
|
|
2466
|
+
const eventNames = typeof q.eventNames === "string" ? q.eventNames.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
1981
2467
|
const params = {
|
|
1982
2468
|
siteId: q.siteId,
|
|
1983
2469
|
type: q.type,
|
|
1984
2470
|
eventName: q.eventName,
|
|
2471
|
+
eventNames,
|
|
2472
|
+
eventSource: q.eventSource,
|
|
1985
2473
|
visitorId: q.visitorId,
|
|
1986
2474
|
userId: q.userId,
|
|
1987
2475
|
period: q.period,
|
|
@@ -2021,9 +2509,13 @@ async function createCollector(config) {
|
|
|
2021
2509
|
const visitorId = usersIdx >= 0 ? pathSegments[usersIdx + 1] : void 0;
|
|
2022
2510
|
const action = usersIdx >= 0 ? pathSegments[usersIdx + 2] : void 0;
|
|
2023
2511
|
if (visitorId && action === "events") {
|
|
2512
|
+
const eventNames = typeof q.eventNames === "string" ? q.eventNames.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
2024
2513
|
const params2 = {
|
|
2025
2514
|
siteId: q.siteId,
|
|
2026
2515
|
type: q.type,
|
|
2516
|
+
eventName: q.eventName,
|
|
2517
|
+
eventNames,
|
|
2518
|
+
eventSource: q.eventSource,
|
|
2027
2519
|
period: q.period,
|
|
2028
2520
|
dateFrom: q.dateFrom,
|
|
2029
2521
|
dateTo: q.dateTo,
|
|
@@ -2130,7 +2622,8 @@ function createAdapter(config) {
|
|
|
2130
2622
|
}
|
|
2131
2623
|
}
|
|
2132
2624
|
async function parseBody(req) {
|
|
2133
|
-
if (req.body) return req.body;
|
|
2625
|
+
if (req.body && typeof req.body === "object") return req.body;
|
|
2626
|
+
if (typeof req.body === "string") return JSON.parse(req.body);
|
|
2134
2627
|
return new Promise((resolve, reject) => {
|
|
2135
2628
|
let data = "";
|
|
2136
2629
|
req.on("data", (chunk) => {
|