@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.cjs
CHANGED
|
@@ -192,6 +192,13 @@ CREATE TABLE IF NOT EXISTS ${EVENTS_TABLE} (
|
|
|
192
192
|
title Nullable(String),
|
|
193
193
|
event_name Nullable(String),
|
|
194
194
|
properties Nullable(String),
|
|
195
|
+
event_source LowCardinality(Nullable(String)),
|
|
196
|
+
event_subtype LowCardinality(Nullable(String)),
|
|
197
|
+
page_path Nullable(String),
|
|
198
|
+
target_url_path Nullable(String),
|
|
199
|
+
element_selector Nullable(String),
|
|
200
|
+
element_text Nullable(String),
|
|
201
|
+
scroll_depth_pct Nullable(UInt8),
|
|
195
202
|
user_id Nullable(String),
|
|
196
203
|
traits Nullable(String),
|
|
197
204
|
country LowCardinality(Nullable(String)),
|
|
@@ -223,6 +230,7 @@ CREATE TABLE IF NOT EXISTS ${SITES_TABLE} (
|
|
|
223
230
|
name String,
|
|
224
231
|
domain Nullable(String),
|
|
225
232
|
allowed_origins Nullable(String),
|
|
233
|
+
conversion_events Nullable(String),
|
|
226
234
|
created_at DateTime64(3),
|
|
227
235
|
updated_at DateTime64(3),
|
|
228
236
|
version UInt64,
|
|
@@ -235,6 +243,39 @@ function toCHDateTime(d) {
|
|
|
235
243
|
const iso = typeof d === "string" ? d : d.toISOString();
|
|
236
244
|
return iso.replace("T", " ").replace("Z", "");
|
|
237
245
|
}
|
|
246
|
+
function buildFilterConditions(filters) {
|
|
247
|
+
if (!filters) return { conditions: [], params: {} };
|
|
248
|
+
const map = {
|
|
249
|
+
"geo.country": "country",
|
|
250
|
+
"geo.city": "city",
|
|
251
|
+
"geo.region": "region",
|
|
252
|
+
"language": "language",
|
|
253
|
+
"device.type": "device_type",
|
|
254
|
+
"device.browser": "browser",
|
|
255
|
+
"device.os": "os",
|
|
256
|
+
"utm.source": "utm_source",
|
|
257
|
+
"utm.medium": "utm_medium",
|
|
258
|
+
"utm.campaign": "utm_campaign",
|
|
259
|
+
"utm.term": "utm_term",
|
|
260
|
+
"utm.content": "utm_content",
|
|
261
|
+
"referrer": "referrer",
|
|
262
|
+
"event_source": "event_source",
|
|
263
|
+
"event_subtype": "event_subtype",
|
|
264
|
+
"page_path": "page_path",
|
|
265
|
+
"target_url_path": "target_url_path",
|
|
266
|
+
"event_name": "event_name",
|
|
267
|
+
"type": "type"
|
|
268
|
+
};
|
|
269
|
+
const conditions = [];
|
|
270
|
+
const params = {};
|
|
271
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
272
|
+
if (!value || !map[key]) continue;
|
|
273
|
+
const paramKey = `f_${key.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
274
|
+
conditions.push(`${map[key]} = {${paramKey}:String}`);
|
|
275
|
+
params[paramKey] = value;
|
|
276
|
+
}
|
|
277
|
+
return { conditions, params };
|
|
278
|
+
}
|
|
238
279
|
var ClickHouseAdapter = class {
|
|
239
280
|
client;
|
|
240
281
|
constructor(url) {
|
|
@@ -248,6 +289,16 @@ var ClickHouseAdapter = class {
|
|
|
248
289
|
async init() {
|
|
249
290
|
await this.client.command({ query: CREATE_EVENTS_TABLE });
|
|
250
291
|
await this.client.command({ query: CREATE_SITES_TABLE });
|
|
292
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS event_source LowCardinality(Nullable(String))` });
|
|
293
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS event_subtype LowCardinality(Nullable(String))` });
|
|
294
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS page_path Nullable(String)` });
|
|
295
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS target_url_path Nullable(String)` });
|
|
296
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS element_selector Nullable(String)` });
|
|
297
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS element_text Nullable(String)` });
|
|
298
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS scroll_depth_pct Nullable(UInt8)` });
|
|
299
|
+
await this.client.command({
|
|
300
|
+
query: `ALTER TABLE ${SITES_TABLE} ADD COLUMN IF NOT EXISTS conversion_events Nullable(String)`
|
|
301
|
+
});
|
|
251
302
|
}
|
|
252
303
|
async close() {
|
|
253
304
|
await this.client.close();
|
|
@@ -266,6 +317,13 @@ var ClickHouseAdapter = class {
|
|
|
266
317
|
title: e.title ?? null,
|
|
267
318
|
event_name: e.name ?? null,
|
|
268
319
|
properties: e.properties ? JSON.stringify(e.properties) : null,
|
|
320
|
+
event_source: e.eventSource ?? null,
|
|
321
|
+
event_subtype: e.eventSubtype ?? null,
|
|
322
|
+
page_path: e.pagePath ?? null,
|
|
323
|
+
target_url_path: e.targetUrlPath ?? null,
|
|
324
|
+
element_selector: e.elementSelector ?? null,
|
|
325
|
+
element_text: e.elementText ?? null,
|
|
326
|
+
scroll_depth_pct: e.scrollDepthPct ?? null,
|
|
269
327
|
user_id: e.userId ?? null,
|
|
270
328
|
traits: e.traits ? JSON.stringify(e.traits) : null,
|
|
271
329
|
country: e.geo?.country ?? null,
|
|
@@ -302,6 +360,8 @@ var ClickHouseAdapter = class {
|
|
|
302
360
|
to: toCHDateTime(dateRange.to),
|
|
303
361
|
limit
|
|
304
362
|
};
|
|
363
|
+
const filter = buildFilterConditions(q.filters);
|
|
364
|
+
const filterSql = filter.conditions.length > 0 ? ` AND ${filter.conditions.join(" AND ")}` : "";
|
|
305
365
|
let data = [];
|
|
306
366
|
let total = 0;
|
|
307
367
|
switch (q.metric) {
|
|
@@ -311,8 +371,8 @@ var ClickHouseAdapter = class {
|
|
|
311
371
|
WHERE site_id = {siteId:String}
|
|
312
372
|
AND timestamp >= {from:String}
|
|
313
373
|
AND timestamp <= {to:String}
|
|
314
|
-
AND type = 'pageview'`,
|
|
315
|
-
params
|
|
374
|
+
AND type = 'pageview'${filterSql}`,
|
|
375
|
+
{ ...params, ...filter.params }
|
|
316
376
|
);
|
|
317
377
|
total = Number(rows[0]?.value ?? 0);
|
|
318
378
|
data = [{ key: "pageviews", value: total }];
|
|
@@ -323,8 +383,8 @@ var ClickHouseAdapter = class {
|
|
|
323
383
|
`SELECT uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
324
384
|
WHERE site_id = {siteId:String}
|
|
325
385
|
AND timestamp >= {from:String}
|
|
326
|
-
AND timestamp <= {to:String}`,
|
|
327
|
-
params
|
|
386
|
+
AND timestamp <= {to:String}${filterSql}`,
|
|
387
|
+
{ ...params, ...filter.params }
|
|
328
388
|
);
|
|
329
389
|
total = Number(rows[0]?.value ?? 0);
|
|
330
390
|
data = [{ key: "visitors", value: total }];
|
|
@@ -335,8 +395,8 @@ var ClickHouseAdapter = class {
|
|
|
335
395
|
`SELECT uniq(session_id) AS value FROM ${EVENTS_TABLE}
|
|
336
396
|
WHERE site_id = {siteId:String}
|
|
337
397
|
AND timestamp >= {from:String}
|
|
338
|
-
AND timestamp <= {to:String}`,
|
|
339
|
-
params
|
|
398
|
+
AND timestamp <= {to:String}${filterSql}`,
|
|
399
|
+
{ ...params, ...filter.params }
|
|
340
400
|
);
|
|
341
401
|
total = Number(rows[0]?.value ?? 0);
|
|
342
402
|
data = [{ key: "sessions", value: total }];
|
|
@@ -348,13 +408,33 @@ var ClickHouseAdapter = class {
|
|
|
348
408
|
WHERE site_id = {siteId:String}
|
|
349
409
|
AND timestamp >= {from:String}
|
|
350
410
|
AND timestamp <= {to:String}
|
|
351
|
-
AND type = 'event'`,
|
|
352
|
-
params
|
|
411
|
+
AND type = 'event'${filterSql}`,
|
|
412
|
+
{ ...params, ...filter.params }
|
|
353
413
|
);
|
|
354
414
|
total = Number(rows[0]?.value ?? 0);
|
|
355
415
|
data = [{ key: "events", value: total }];
|
|
356
416
|
break;
|
|
357
417
|
}
|
|
418
|
+
case "conversions": {
|
|
419
|
+
const conversionEvents = q.conversionEvents ?? [];
|
|
420
|
+
if (conversionEvents.length === 0) {
|
|
421
|
+
total = 0;
|
|
422
|
+
data = [{ key: "conversions", value: 0 }];
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
const rows = await this.queryRows(
|
|
426
|
+
`SELECT count() AS value FROM ${EVENTS_TABLE}
|
|
427
|
+
WHERE site_id = {siteId:String}
|
|
428
|
+
AND timestamp >= {from:String}
|
|
429
|
+
AND timestamp <= {to:String}
|
|
430
|
+
AND type = 'event'
|
|
431
|
+
AND event_name IN {eventNames:Array(String)}${filterSql}`,
|
|
432
|
+
{ ...params, eventNames: conversionEvents, ...filter.params }
|
|
433
|
+
);
|
|
434
|
+
total = Number(rows[0]?.value ?? 0);
|
|
435
|
+
data = [{ key: "conversions", value: total }];
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
358
438
|
case "top_pages": {
|
|
359
439
|
const rows = await this.queryRows(
|
|
360
440
|
`SELECT url AS key, count() AS value FROM ${EVENTS_TABLE}
|
|
@@ -362,11 +442,11 @@ var ClickHouseAdapter = class {
|
|
|
362
442
|
AND timestamp >= {from:String}
|
|
363
443
|
AND timestamp <= {to:String}
|
|
364
444
|
AND type = 'pageview'
|
|
365
|
-
AND url IS NOT NULL
|
|
445
|
+
AND url IS NOT NULL${filterSql}
|
|
366
446
|
GROUP BY url
|
|
367
447
|
ORDER BY value DESC
|
|
368
448
|
LIMIT {limit:UInt32}`,
|
|
369
|
-
params
|
|
449
|
+
{ ...params, ...filter.params }
|
|
370
450
|
);
|
|
371
451
|
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
372
452
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
@@ -380,11 +460,11 @@ var ClickHouseAdapter = class {
|
|
|
380
460
|
AND timestamp <= {to:String}
|
|
381
461
|
AND type = 'pageview'
|
|
382
462
|
AND referrer IS NOT NULL
|
|
383
|
-
AND referrer != ''
|
|
463
|
+
AND referrer != ''${filterSql}
|
|
384
464
|
GROUP BY referrer
|
|
385
465
|
ORDER BY value DESC
|
|
386
466
|
LIMIT {limit:UInt32}`,
|
|
387
|
-
params
|
|
467
|
+
{ ...params, ...filter.params }
|
|
388
468
|
);
|
|
389
469
|
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
390
470
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
@@ -396,11 +476,11 @@ var ClickHouseAdapter = class {
|
|
|
396
476
|
WHERE site_id = {siteId:String}
|
|
397
477
|
AND timestamp >= {from:String}
|
|
398
478
|
AND timestamp <= {to:String}
|
|
399
|
-
AND country IS NOT NULL
|
|
479
|
+
AND country IS NOT NULL${filterSql}
|
|
400
480
|
GROUP BY country
|
|
401
481
|
ORDER BY value DESC
|
|
402
482
|
LIMIT {limit:UInt32}`,
|
|
403
|
-
params
|
|
483
|
+
{ ...params, ...filter.params }
|
|
404
484
|
);
|
|
405
485
|
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
406
486
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
@@ -412,11 +492,11 @@ var ClickHouseAdapter = class {
|
|
|
412
492
|
WHERE site_id = {siteId:String}
|
|
413
493
|
AND timestamp >= {from:String}
|
|
414
494
|
AND timestamp <= {to:String}
|
|
415
|
-
AND city IS NOT NULL
|
|
495
|
+
AND city IS NOT NULL${filterSql}
|
|
416
496
|
GROUP BY city
|
|
417
497
|
ORDER BY value DESC
|
|
418
498
|
LIMIT {limit:UInt32}`,
|
|
419
|
-
params
|
|
499
|
+
{ ...params, ...filter.params }
|
|
420
500
|
);
|
|
421
501
|
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
422
502
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
@@ -430,10 +510,132 @@ var ClickHouseAdapter = class {
|
|
|
430
510
|
AND timestamp <= {to:String}
|
|
431
511
|
AND type = 'event'
|
|
432
512
|
AND event_name IS NOT NULL
|
|
513
|
+
${filterSql}
|
|
433
514
|
GROUP BY event_name
|
|
434
515
|
ORDER BY value DESC
|
|
435
516
|
LIMIT {limit:UInt32}`,
|
|
436
|
-
params
|
|
517
|
+
{ ...params, ...filter.params }
|
|
518
|
+
);
|
|
519
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
520
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
521
|
+
break;
|
|
522
|
+
}
|
|
523
|
+
case "top_conversions": {
|
|
524
|
+
const conversionEvents = q.conversionEvents ?? [];
|
|
525
|
+
if (conversionEvents.length === 0) {
|
|
526
|
+
total = 0;
|
|
527
|
+
data = [];
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
const rows = await this.queryRows(
|
|
531
|
+
`SELECT event_name AS key, count() AS value FROM ${EVENTS_TABLE}
|
|
532
|
+
WHERE site_id = {siteId:String}
|
|
533
|
+
AND timestamp >= {from:String}
|
|
534
|
+
AND timestamp <= {to:String}
|
|
535
|
+
AND type = 'event'
|
|
536
|
+
AND event_name IN {eventNames:Array(String)}
|
|
537
|
+
${filterSql}
|
|
538
|
+
GROUP BY event_name
|
|
539
|
+
ORDER BY value DESC
|
|
540
|
+
LIMIT {limit:UInt32}`,
|
|
541
|
+
{ ...params, eventNames: conversionEvents, ...filter.params }
|
|
542
|
+
);
|
|
543
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
544
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
case "top_exit_pages": {
|
|
548
|
+
const rows = await this.queryRows(
|
|
549
|
+
`SELECT url AS key, count() AS value FROM (
|
|
550
|
+
SELECT session_id, argMax(url, timestamp) AS url
|
|
551
|
+
FROM ${EVENTS_TABLE}
|
|
552
|
+
WHERE site_id = {siteId:String}
|
|
553
|
+
AND timestamp >= {from:String}
|
|
554
|
+
AND timestamp <= {to:String}
|
|
555
|
+
AND type = 'pageview'
|
|
556
|
+
AND url IS NOT NULL${filterSql}
|
|
557
|
+
GROUP BY session_id
|
|
558
|
+
)
|
|
559
|
+
GROUP BY url
|
|
560
|
+
ORDER BY value DESC
|
|
561
|
+
LIMIT {limit:UInt32}`,
|
|
562
|
+
{ ...params, ...filter.params }
|
|
563
|
+
);
|
|
564
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
565
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
case "top_transitions": {
|
|
569
|
+
const rows = await this.queryRows(
|
|
570
|
+
`SELECT concat(prev_url, ' \u2192 ', url) AS key, count() AS value FROM (
|
|
571
|
+
SELECT session_id, url,
|
|
572
|
+
lag(url) OVER (PARTITION BY session_id ORDER BY timestamp) AS prev_url
|
|
573
|
+
FROM ${EVENTS_TABLE}
|
|
574
|
+
WHERE site_id = {siteId:String}
|
|
575
|
+
AND timestamp >= {from:String}
|
|
576
|
+
AND timestamp <= {to:String}
|
|
577
|
+
AND type = 'pageview'
|
|
578
|
+
AND url IS NOT NULL${filterSql}
|
|
579
|
+
)
|
|
580
|
+
WHERE prev_url IS NOT NULL
|
|
581
|
+
GROUP BY key
|
|
582
|
+
ORDER BY value DESC
|
|
583
|
+
LIMIT {limit:UInt32}`,
|
|
584
|
+
{ ...params, ...filter.params }
|
|
585
|
+
);
|
|
586
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
587
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
case "top_scroll_pages": {
|
|
591
|
+
const rows = await this.queryRows(
|
|
592
|
+
`SELECT page_path AS key, count() AS value FROM ${EVENTS_TABLE}
|
|
593
|
+
WHERE site_id = {siteId:String}
|
|
594
|
+
AND timestamp >= {from:String}
|
|
595
|
+
AND timestamp <= {to:String}
|
|
596
|
+
AND type = 'event'
|
|
597
|
+
AND event_subtype = 'scroll_depth'
|
|
598
|
+
AND page_path IS NOT NULL${filterSql}
|
|
599
|
+
GROUP BY page_path
|
|
600
|
+
ORDER BY value DESC
|
|
601
|
+
LIMIT {limit:UInt32}`,
|
|
602
|
+
{ ...params, ...filter.params }
|
|
603
|
+
);
|
|
604
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
605
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
606
|
+
break;
|
|
607
|
+
}
|
|
608
|
+
case "top_button_clicks": {
|
|
609
|
+
const rows = await this.queryRows(
|
|
610
|
+
`SELECT ifNull(element_text, element_selector) AS key, count() AS value FROM ${EVENTS_TABLE}
|
|
611
|
+
WHERE site_id = {siteId:String}
|
|
612
|
+
AND timestamp >= {from:String}
|
|
613
|
+
AND timestamp <= {to:String}
|
|
614
|
+
AND type = 'event'
|
|
615
|
+
AND event_subtype = 'button_click'
|
|
616
|
+
AND (element_text IS NOT NULL OR element_selector IS NOT NULL)${filterSql}
|
|
617
|
+
GROUP BY key
|
|
618
|
+
ORDER BY value DESC
|
|
619
|
+
LIMIT {limit:UInt32}`,
|
|
620
|
+
{ ...params, ...filter.params }
|
|
621
|
+
);
|
|
622
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
623
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
624
|
+
break;
|
|
625
|
+
}
|
|
626
|
+
case "top_link_targets": {
|
|
627
|
+
const rows = await this.queryRows(
|
|
628
|
+
`SELECT target_url_path AS key, count() AS value FROM ${EVENTS_TABLE}
|
|
629
|
+
WHERE site_id = {siteId:String}
|
|
630
|
+
AND timestamp >= {from:String}
|
|
631
|
+
AND timestamp <= {to:String}
|
|
632
|
+
AND type = 'event'
|
|
633
|
+
AND event_subtype IN ('link_click','outbound_click')
|
|
634
|
+
AND target_url_path IS NOT NULL${filterSql}
|
|
635
|
+
GROUP BY target_url_path
|
|
636
|
+
ORDER BY value DESC
|
|
637
|
+
LIMIT {limit:UInt32}`,
|
|
638
|
+
{ ...params, ...filter.params }
|
|
437
639
|
);
|
|
438
640
|
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
439
641
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
@@ -446,10 +648,11 @@ var ClickHouseAdapter = class {
|
|
|
446
648
|
AND timestamp >= {from:String}
|
|
447
649
|
AND timestamp <= {to:String}
|
|
448
650
|
AND device_type IS NOT NULL
|
|
651
|
+
${filterSql}
|
|
449
652
|
GROUP BY device_type
|
|
450
653
|
ORDER BY value DESC
|
|
451
654
|
LIMIT {limit:UInt32}`,
|
|
452
|
-
params
|
|
655
|
+
{ ...params, ...filter.params }
|
|
453
656
|
);
|
|
454
657
|
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
455
658
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
@@ -462,10 +665,11 @@ var ClickHouseAdapter = class {
|
|
|
462
665
|
AND timestamp >= {from:String}
|
|
463
666
|
AND timestamp <= {to:String}
|
|
464
667
|
AND browser IS NOT NULL
|
|
668
|
+
${filterSql}
|
|
465
669
|
GROUP BY browser
|
|
466
670
|
ORDER BY value DESC
|
|
467
671
|
LIMIT {limit:UInt32}`,
|
|
468
|
-
params
|
|
672
|
+
{ ...params, ...filter.params }
|
|
469
673
|
);
|
|
470
674
|
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
471
675
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
@@ -478,10 +682,11 @@ var ClickHouseAdapter = class {
|
|
|
478
682
|
AND timestamp >= {from:String}
|
|
479
683
|
AND timestamp <= {to:String}
|
|
480
684
|
AND os IS NOT NULL
|
|
685
|
+
${filterSql}
|
|
481
686
|
GROUP BY os
|
|
482
687
|
ORDER BY value DESC
|
|
483
688
|
LIMIT {limit:UInt32}`,
|
|
484
|
-
params
|
|
689
|
+
{ ...params, ...filter.params }
|
|
485
690
|
);
|
|
486
691
|
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
487
692
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
@@ -489,7 +694,7 @@ var ClickHouseAdapter = class {
|
|
|
489
694
|
}
|
|
490
695
|
}
|
|
491
696
|
const result = { metric: q.metric, period, data, total };
|
|
492
|
-
if (q.compare && ["pageviews", "visitors", "sessions", "events"].includes(q.metric)) {
|
|
697
|
+
if (q.compare && ["pageviews", "visitors", "sessions", "events", "conversions"].includes(q.metric)) {
|
|
493
698
|
const prevRange = previousPeriodRange(dateRange);
|
|
494
699
|
const prevResult = await this.query({
|
|
495
700
|
...q,
|
|
@@ -519,7 +724,12 @@ var ClickHouseAdapter = class {
|
|
|
519
724
|
const granularity = params.granularity ?? autoGranularity(period);
|
|
520
725
|
const bucketFn = this.granularityToClickHouseFunc(granularity);
|
|
521
726
|
const dateFormat = granularityToDateFormat(granularity);
|
|
727
|
+
const filter = buildFilterConditions(params.filters);
|
|
728
|
+
const filterSql = filter.conditions.length > 0 ? ` AND ${filter.conditions.join(" AND ")}` : "";
|
|
522
729
|
const typeFilter = params.metric === "pageviews" ? `AND type = 'pageview'` : "";
|
|
730
|
+
const eventsFilter = params.metric === "events" ? `AND type = 'event'` : "";
|
|
731
|
+
const conversionsFilter = params.metric === "conversions" ? `AND type = 'event' AND event_name IN {eventNames:Array(String)}` : "";
|
|
732
|
+
const extraFilters = [typeFilter, eventsFilter, conversionsFilter, filterSql].filter(Boolean).join(" ");
|
|
523
733
|
let sql;
|
|
524
734
|
if (params.metric === "visitors" || params.metric === "sessions") {
|
|
525
735
|
const field = params.metric === "visitors" ? "visitor_id" : "session_id";
|
|
@@ -529,7 +739,7 @@ var ClickHouseAdapter = class {
|
|
|
529
739
|
WHERE site_id = {siteId:String}
|
|
530
740
|
AND timestamp >= {from:String}
|
|
531
741
|
AND timestamp <= {to:String}
|
|
532
|
-
${
|
|
742
|
+
${extraFilters}
|
|
533
743
|
GROUP BY bucket
|
|
534
744
|
ORDER BY bucket ASC
|
|
535
745
|
`;
|
|
@@ -540,7 +750,7 @@ var ClickHouseAdapter = class {
|
|
|
540
750
|
WHERE site_id = {siteId:String}
|
|
541
751
|
AND timestamp >= {from:String}
|
|
542
752
|
AND timestamp <= {to:String}
|
|
543
|
-
${
|
|
753
|
+
${extraFilters}
|
|
544
754
|
GROUP BY bucket
|
|
545
755
|
ORDER BY bucket ASC
|
|
546
756
|
`;
|
|
@@ -548,7 +758,9 @@ var ClickHouseAdapter = class {
|
|
|
548
758
|
const rows = await this.queryRows(sql, {
|
|
549
759
|
siteId: params.siteId,
|
|
550
760
|
from: toCHDateTime(dateRange.from),
|
|
551
|
-
to: toCHDateTime(dateRange.to)
|
|
761
|
+
to: toCHDateTime(dateRange.to),
|
|
762
|
+
eventNames: params.conversionEvents ?? [],
|
|
763
|
+
...filter.params
|
|
552
764
|
});
|
|
553
765
|
const mappedRows = rows.map((r) => ({
|
|
554
766
|
_id: this.convertClickHouseBucket(r.bucket, granularity),
|
|
@@ -666,6 +878,14 @@ var ClickHouseAdapter = class {
|
|
|
666
878
|
conditions.push(`event_name = {eventName:String}`);
|
|
667
879
|
queryParams.eventName = params.eventName;
|
|
668
880
|
}
|
|
881
|
+
if (params.eventSource) {
|
|
882
|
+
conditions.push(`event_source = {eventSource:String}`);
|
|
883
|
+
queryParams.eventSource = params.eventSource;
|
|
884
|
+
}
|
|
885
|
+
if (params.eventNames && params.eventNames.length > 0) {
|
|
886
|
+
conditions.push(`event_name IN {eventNames:Array(String)}`);
|
|
887
|
+
queryParams.eventNames = params.eventNames;
|
|
888
|
+
}
|
|
669
889
|
if (params.visitorId) {
|
|
670
890
|
conditions.push(`visitor_id = {visitorId:String}`);
|
|
671
891
|
queryParams.visitorId = params.visitorId;
|
|
@@ -688,7 +908,9 @@ var ClickHouseAdapter = class {
|
|
|
688
908
|
const [events, countRows] = await Promise.all([
|
|
689
909
|
this.queryRows(
|
|
690
910
|
`SELECT event_id, type, timestamp, session_id, visitor_id, url, referrer, title,
|
|
691
|
-
event_name, properties,
|
|
911
|
+
event_name, properties, event_source, event_subtype, page_path, target_url_path,
|
|
912
|
+
element_selector, element_text, scroll_depth_pct,
|
|
913
|
+
user_id, traits, country, city, region,
|
|
692
914
|
device_type, browser, os, language,
|
|
693
915
|
utm_source, utm_medium, utm_campaign, utm_term, utm_content
|
|
694
916
|
FROM ${EVENTS_TABLE}
|
|
@@ -733,13 +955,22 @@ var ClickHouseAdapter = class {
|
|
|
733
955
|
countIf(type = 'pageview') AS totalPageviews,
|
|
734
956
|
uniq(session_id) AS totalSessions,
|
|
735
957
|
anyLast(url) AS lastUrl,
|
|
958
|
+
anyLast(referrer) AS referrer,
|
|
736
959
|
anyLast(device_type) AS device_type,
|
|
737
960
|
anyLast(browser) AS browser,
|
|
738
961
|
anyLast(os) AS os,
|
|
739
962
|
anyLast(country) AS country,
|
|
740
963
|
anyLast(city) AS city,
|
|
741
964
|
anyLast(region) AS region,
|
|
742
|
-
anyLast(language) AS language
|
|
965
|
+
anyLast(language) AS language,
|
|
966
|
+
anyLast(timezone) AS timezone,
|
|
967
|
+
anyLast(screen_width) AS screen_width,
|
|
968
|
+
anyLast(screen_height) AS screen_height,
|
|
969
|
+
anyLast(utm_source) AS utm_source,
|
|
970
|
+
anyLast(utm_medium) AS utm_medium,
|
|
971
|
+
anyLast(utm_campaign) AS utm_campaign,
|
|
972
|
+
anyLast(utm_term) AS utm_term,
|
|
973
|
+
anyLast(utm_content) AS utm_content
|
|
743
974
|
FROM ${EVENTS_TABLE}
|
|
744
975
|
WHERE ${where}
|
|
745
976
|
GROUP BY visitor_id
|
|
@@ -767,9 +998,19 @@ var ClickHouseAdapter = class {
|
|
|
767
998
|
totalPageviews: Number(u.totalPageviews),
|
|
768
999
|
totalSessions: Number(u.totalSessions),
|
|
769
1000
|
lastUrl: u.lastUrl ? String(u.lastUrl) : void 0,
|
|
1001
|
+
referrer: u.referrer ? String(u.referrer) : void 0,
|
|
770
1002
|
device: u.device_type ? { type: String(u.device_type), browser: String(u.browser ?? ""), os: String(u.os ?? "") } : void 0,
|
|
771
1003
|
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,
|
|
772
|
-
language: u.language ? String(u.language) : void 0
|
|
1004
|
+
language: u.language ? String(u.language) : void 0,
|
|
1005
|
+
timezone: u.timezone ? String(u.timezone) : void 0,
|
|
1006
|
+
screen: u.screen_width || u.screen_height ? { width: Number(u.screen_width ?? 0), height: Number(u.screen_height ?? 0) } : void 0,
|
|
1007
|
+
utm: u.utm_source ? {
|
|
1008
|
+
source: String(u.utm_source),
|
|
1009
|
+
medium: u.utm_medium ? String(u.utm_medium) : void 0,
|
|
1010
|
+
campaign: u.utm_campaign ? String(u.utm_campaign) : void 0,
|
|
1011
|
+
term: u.utm_term ? String(u.utm_term) : void 0,
|
|
1012
|
+
content: u.utm_content ? String(u.utm_content) : void 0
|
|
1013
|
+
} : void 0
|
|
773
1014
|
}));
|
|
774
1015
|
return {
|
|
775
1016
|
users,
|
|
@@ -797,6 +1038,7 @@ var ClickHouseAdapter = class {
|
|
|
797
1038
|
name: data.name,
|
|
798
1039
|
domain: data.domain,
|
|
799
1040
|
allowedOrigins: data.allowedOrigins,
|
|
1041
|
+
conversionEvents: data.conversionEvents,
|
|
800
1042
|
createdAt: nowISO,
|
|
801
1043
|
updatedAt: nowISO
|
|
802
1044
|
};
|
|
@@ -808,6 +1050,7 @@ var ClickHouseAdapter = class {
|
|
|
808
1050
|
name: site.name,
|
|
809
1051
|
domain: site.domain ?? null,
|
|
810
1052
|
allowed_origins: site.allowedOrigins ? JSON.stringify(site.allowedOrigins) : null,
|
|
1053
|
+
conversion_events: site.conversionEvents ? JSON.stringify(site.conversionEvents) : null,
|
|
811
1054
|
created_at: nowCH,
|
|
812
1055
|
updated_at: nowCH,
|
|
813
1056
|
version: 1,
|
|
@@ -819,7 +1062,7 @@ var ClickHouseAdapter = class {
|
|
|
819
1062
|
}
|
|
820
1063
|
async getSite(siteId) {
|
|
821
1064
|
const rows = await this.queryRows(
|
|
822
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, created_at, updated_at
|
|
1065
|
+
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, updated_at
|
|
823
1066
|
FROM ${SITES_TABLE} FINAL
|
|
824
1067
|
WHERE site_id = {siteId:String} AND is_deleted = 0`,
|
|
825
1068
|
{ siteId }
|
|
@@ -828,7 +1071,7 @@ var ClickHouseAdapter = class {
|
|
|
828
1071
|
}
|
|
829
1072
|
async getSiteBySecret(secretKey) {
|
|
830
1073
|
const rows = await this.queryRows(
|
|
831
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, created_at, updated_at
|
|
1074
|
+
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, updated_at
|
|
832
1075
|
FROM ${SITES_TABLE} FINAL
|
|
833
1076
|
WHERE secret_key = {secretKey:String} AND is_deleted = 0`,
|
|
834
1077
|
{ secretKey }
|
|
@@ -837,7 +1080,7 @@ var ClickHouseAdapter = class {
|
|
|
837
1080
|
}
|
|
838
1081
|
async listSites() {
|
|
839
1082
|
const rows = await this.queryRows(
|
|
840
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, created_at, updated_at
|
|
1083
|
+
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, updated_at
|
|
841
1084
|
FROM ${SITES_TABLE} FINAL
|
|
842
1085
|
WHERE is_deleted = 0
|
|
843
1086
|
ORDER BY created_at DESC`,
|
|
@@ -847,7 +1090,7 @@ var ClickHouseAdapter = class {
|
|
|
847
1090
|
}
|
|
848
1091
|
async updateSite(siteId, data) {
|
|
849
1092
|
const currentRows = await this.queryRows(
|
|
850
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, created_at, updated_at, version
|
|
1093
|
+
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, updated_at, version
|
|
851
1094
|
FROM ${SITES_TABLE} FINAL
|
|
852
1095
|
WHERE site_id = {siteId:String} AND is_deleted = 0`,
|
|
853
1096
|
{ siteId }
|
|
@@ -861,6 +1104,7 @@ var ClickHouseAdapter = class {
|
|
|
861
1104
|
const newName = data.name !== void 0 ? data.name : String(current.name);
|
|
862
1105
|
const newDomain = data.domain !== void 0 ? data.domain || null : current.domain ? String(current.domain) : null;
|
|
863
1106
|
const newOrigins = data.allowedOrigins !== void 0 ? data.allowedOrigins.length > 0 ? JSON.stringify(data.allowedOrigins) : null : current.allowed_origins ? String(current.allowed_origins) : null;
|
|
1107
|
+
const newConversions = data.conversionEvents !== void 0 ? data.conversionEvents.length > 0 ? JSON.stringify(data.conversionEvents) : null : current.conversion_events ? String(current.conversion_events) : null;
|
|
864
1108
|
await this.client.insert({
|
|
865
1109
|
table: SITES_TABLE,
|
|
866
1110
|
values: [{
|
|
@@ -869,6 +1113,7 @@ var ClickHouseAdapter = class {
|
|
|
869
1113
|
name: newName,
|
|
870
1114
|
domain: newDomain,
|
|
871
1115
|
allowed_origins: newOrigins,
|
|
1116
|
+
conversion_events: newConversions,
|
|
872
1117
|
created_at: toCHDateTime(String(current.created_at)),
|
|
873
1118
|
updated_at: nowCH,
|
|
874
1119
|
version: newVersion,
|
|
@@ -882,13 +1127,14 @@ var ClickHouseAdapter = class {
|
|
|
882
1127
|
name: newName,
|
|
883
1128
|
domain: newDomain ?? void 0,
|
|
884
1129
|
allowedOrigins: newOrigins ? JSON.parse(newOrigins) : void 0,
|
|
1130
|
+
conversionEvents: newConversions ? JSON.parse(newConversions) : void 0,
|
|
885
1131
|
createdAt: String(current.created_at),
|
|
886
1132
|
updatedAt: nowISO
|
|
887
1133
|
};
|
|
888
1134
|
}
|
|
889
1135
|
async deleteSite(siteId) {
|
|
890
1136
|
const currentRows = await this.queryRows(
|
|
891
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, created_at, version
|
|
1137
|
+
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, version
|
|
892
1138
|
FROM ${SITES_TABLE} FINAL
|
|
893
1139
|
WHERE site_id = {siteId:String} AND is_deleted = 0`,
|
|
894
1140
|
{ siteId }
|
|
@@ -904,6 +1150,7 @@ var ClickHouseAdapter = class {
|
|
|
904
1150
|
name: String(current.name),
|
|
905
1151
|
domain: current.domain ? String(current.domain) : null,
|
|
906
1152
|
allowed_origins: current.allowed_origins ? String(current.allowed_origins) : null,
|
|
1153
|
+
conversion_events: current.conversion_events ? String(current.conversion_events) : null,
|
|
907
1154
|
created_at: toCHDateTime(String(current.created_at)),
|
|
908
1155
|
updated_at: nowCH,
|
|
909
1156
|
version: Number(current.version) + 1,
|
|
@@ -915,7 +1162,7 @@ var ClickHouseAdapter = class {
|
|
|
915
1162
|
}
|
|
916
1163
|
async regenerateSecret(siteId) {
|
|
917
1164
|
const currentRows = await this.queryRows(
|
|
918
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, created_at, version
|
|
1165
|
+
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, version
|
|
919
1166
|
FROM ${SITES_TABLE} FINAL
|
|
920
1167
|
WHERE site_id = {siteId:String} AND is_deleted = 0`,
|
|
921
1168
|
{ siteId }
|
|
@@ -934,6 +1181,7 @@ var ClickHouseAdapter = class {
|
|
|
934
1181
|
name: String(current.name),
|
|
935
1182
|
domain: current.domain ? String(current.domain) : null,
|
|
936
1183
|
allowed_origins: current.allowed_origins ? String(current.allowed_origins) : null,
|
|
1184
|
+
conversion_events: current.conversion_events ? String(current.conversion_events) : null,
|
|
937
1185
|
created_at: toCHDateTime(String(current.created_at)),
|
|
938
1186
|
updated_at: nowCH,
|
|
939
1187
|
version: Number(current.version) + 1,
|
|
@@ -947,6 +1195,7 @@ var ClickHouseAdapter = class {
|
|
|
947
1195
|
name: String(current.name),
|
|
948
1196
|
domain: current.domain ? String(current.domain) : void 0,
|
|
949
1197
|
allowedOrigins: current.allowed_origins ? JSON.parse(String(current.allowed_origins)) : void 0,
|
|
1198
|
+
conversionEvents: current.conversion_events ? JSON.parse(String(current.conversion_events)) : void 0,
|
|
950
1199
|
createdAt: String(current.created_at),
|
|
951
1200
|
updatedAt: nowISO
|
|
952
1201
|
};
|
|
@@ -967,6 +1216,7 @@ var ClickHouseAdapter = class {
|
|
|
967
1216
|
name: String(row.name),
|
|
968
1217
|
domain: row.domain ? String(row.domain) : void 0,
|
|
969
1218
|
allowedOrigins: row.allowed_origins ? JSON.parse(String(row.allowed_origins)) : void 0,
|
|
1219
|
+
conversionEvents: row.conversion_events ? JSON.parse(String(row.conversion_events)) : void 0,
|
|
970
1220
|
createdAt: new Date(String(row.created_at)).toISOString(),
|
|
971
1221
|
updatedAt: new Date(String(row.updated_at)).toISOString()
|
|
972
1222
|
};
|
|
@@ -983,6 +1233,13 @@ var ClickHouseAdapter = class {
|
|
|
983
1233
|
title: row.title ? String(row.title) : void 0,
|
|
984
1234
|
name: row.event_name ? String(row.event_name) : void 0,
|
|
985
1235
|
properties: this.parseJSON(row.properties),
|
|
1236
|
+
eventSource: row.event_source ? String(row.event_source) : void 0,
|
|
1237
|
+
eventSubtype: row.event_subtype ? String(row.event_subtype) : void 0,
|
|
1238
|
+
pagePath: row.page_path ? String(row.page_path) : void 0,
|
|
1239
|
+
targetUrlPath: row.target_url_path ? String(row.target_url_path) : void 0,
|
|
1240
|
+
elementSelector: row.element_selector ? String(row.element_selector) : void 0,
|
|
1241
|
+
elementText: row.element_text ? String(row.element_text) : void 0,
|
|
1242
|
+
scrollDepthPct: row.scroll_depth_pct !== null && row.scroll_depth_pct !== void 0 ? Number(row.scroll_depth_pct) : void 0,
|
|
986
1243
|
userId: row.user_id ? String(row.user_id) : void 0,
|
|
987
1244
|
traits: this.parseJSON(row.traits),
|
|
988
1245
|
geo: row.country ? {
|
|
@@ -1019,6 +1276,36 @@ var ClickHouseAdapter = class {
|
|
|
1019
1276
|
var import_mongodb = require("mongodb");
|
|
1020
1277
|
var EVENTS_COLLECTION = "litemetrics_events";
|
|
1021
1278
|
var SITES_COLLECTION = "litemetrics_sites";
|
|
1279
|
+
function buildFilterMatch(filters) {
|
|
1280
|
+
if (!filters) return {};
|
|
1281
|
+
const map = {
|
|
1282
|
+
"geo.country": "country",
|
|
1283
|
+
"geo.city": "city",
|
|
1284
|
+
"geo.region": "region",
|
|
1285
|
+
"language": "language",
|
|
1286
|
+
"device.type": "device_type",
|
|
1287
|
+
"device.browser": "browser",
|
|
1288
|
+
"device.os": "os",
|
|
1289
|
+
"utm.source": "utm_source",
|
|
1290
|
+
"utm.medium": "utm_medium",
|
|
1291
|
+
"utm.campaign": "utm_campaign",
|
|
1292
|
+
"utm.term": "utm_term",
|
|
1293
|
+
"utm.content": "utm_content",
|
|
1294
|
+
"referrer": "referrer",
|
|
1295
|
+
"event_source": "event_source",
|
|
1296
|
+
"event_subtype": "event_subtype",
|
|
1297
|
+
"page_path": "page_path",
|
|
1298
|
+
"target_url_path": "target_url_path",
|
|
1299
|
+
"event_name": "event_name",
|
|
1300
|
+
"type": "type"
|
|
1301
|
+
};
|
|
1302
|
+
const match = {};
|
|
1303
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
1304
|
+
if (!value || !map[key]) continue;
|
|
1305
|
+
match[map[key]] = value;
|
|
1306
|
+
}
|
|
1307
|
+
return match;
|
|
1308
|
+
}
|
|
1022
1309
|
var MongoDBAdapter = class {
|
|
1023
1310
|
client;
|
|
1024
1311
|
db;
|
|
@@ -1054,6 +1341,13 @@ var MongoDBAdapter = class {
|
|
|
1054
1341
|
title: e.title ?? null,
|
|
1055
1342
|
event_name: e.name ?? null,
|
|
1056
1343
|
properties: e.properties ?? null,
|
|
1344
|
+
event_source: e.eventSource ?? null,
|
|
1345
|
+
event_subtype: e.eventSubtype ?? null,
|
|
1346
|
+
page_path: e.pagePath ?? null,
|
|
1347
|
+
target_url_path: e.targetUrlPath ?? null,
|
|
1348
|
+
element_selector: e.elementSelector ?? null,
|
|
1349
|
+
element_text: e.elementText ?? null,
|
|
1350
|
+
scroll_depth_pct: e.scrollDepthPct ?? null,
|
|
1057
1351
|
user_id: e.userId ?? null,
|
|
1058
1352
|
traits: e.traits ?? null,
|
|
1059
1353
|
country: e.geo?.country ?? null,
|
|
@@ -1084,12 +1378,13 @@ var MongoDBAdapter = class {
|
|
|
1084
1378
|
site_id: siteId,
|
|
1085
1379
|
timestamp: { $gte: new Date(dateRange.from), $lte: new Date(dateRange.to) }
|
|
1086
1380
|
};
|
|
1381
|
+
const filterMatch = buildFilterMatch(q.filters);
|
|
1087
1382
|
let data = [];
|
|
1088
1383
|
let total = 0;
|
|
1089
1384
|
switch (q.metric) {
|
|
1090
1385
|
case "pageviews": {
|
|
1091
1386
|
const [result2] = await this.collection.aggregate([
|
|
1092
|
-
{ $match: { ...baseMatch, type: "pageview" } },
|
|
1387
|
+
{ $match: { ...baseMatch, ...filterMatch, type: "pageview" } },
|
|
1093
1388
|
{ $count: "count" }
|
|
1094
1389
|
]).toArray();
|
|
1095
1390
|
total = result2?.count ?? 0;
|
|
@@ -1098,7 +1393,7 @@ var MongoDBAdapter = class {
|
|
|
1098
1393
|
}
|
|
1099
1394
|
case "visitors": {
|
|
1100
1395
|
const [result2] = await this.collection.aggregate([
|
|
1101
|
-
{ $match: baseMatch },
|
|
1396
|
+
{ $match: { ...baseMatch, ...filterMatch } },
|
|
1102
1397
|
{ $group: { _id: "$visitor_id" } },
|
|
1103
1398
|
{ $count: "count" }
|
|
1104
1399
|
]).toArray();
|
|
@@ -1108,7 +1403,7 @@ var MongoDBAdapter = class {
|
|
|
1108
1403
|
}
|
|
1109
1404
|
case "sessions": {
|
|
1110
1405
|
const [result2] = await this.collection.aggregate([
|
|
1111
|
-
{ $match: baseMatch },
|
|
1406
|
+
{ $match: { ...baseMatch, ...filterMatch } },
|
|
1112
1407
|
{ $group: { _id: "$session_id" } },
|
|
1113
1408
|
{ $count: "count" }
|
|
1114
1409
|
]).toArray();
|
|
@@ -1118,16 +1413,31 @@ var MongoDBAdapter = class {
|
|
|
1118
1413
|
}
|
|
1119
1414
|
case "events": {
|
|
1120
1415
|
const [result2] = await this.collection.aggregate([
|
|
1121
|
-
{ $match: { ...baseMatch, type: "event" } },
|
|
1416
|
+
{ $match: { ...baseMatch, ...filterMatch, type: "event" } },
|
|
1122
1417
|
{ $count: "count" }
|
|
1123
1418
|
]).toArray();
|
|
1124
1419
|
total = result2?.count ?? 0;
|
|
1125
1420
|
data = [{ key: "events", value: total }];
|
|
1126
1421
|
break;
|
|
1127
1422
|
}
|
|
1423
|
+
case "conversions": {
|
|
1424
|
+
const conversionEvents = q.conversionEvents ?? [];
|
|
1425
|
+
if (conversionEvents.length === 0) {
|
|
1426
|
+
total = 0;
|
|
1427
|
+
data = [{ key: "conversions", value: 0 }];
|
|
1428
|
+
break;
|
|
1429
|
+
}
|
|
1430
|
+
const [result2] = await this.collection.aggregate([
|
|
1431
|
+
{ $match: { ...baseMatch, ...filterMatch, type: "event", event_name: { $in: conversionEvents } } },
|
|
1432
|
+
{ $count: "count" }
|
|
1433
|
+
]).toArray();
|
|
1434
|
+
total = result2?.count ?? 0;
|
|
1435
|
+
data = [{ key: "conversions", value: total }];
|
|
1436
|
+
break;
|
|
1437
|
+
}
|
|
1128
1438
|
case "top_pages": {
|
|
1129
1439
|
const rows = await this.collection.aggregate([
|
|
1130
|
-
{ $match: { ...baseMatch, type: "pageview", url: { $ne: null } } },
|
|
1440
|
+
{ $match: { ...baseMatch, ...filterMatch, type: "pageview", url: { $ne: null } } },
|
|
1131
1441
|
{ $group: { _id: "$url", value: { $sum: 1 } } },
|
|
1132
1442
|
{ $sort: { value: -1 } },
|
|
1133
1443
|
{ $limit: limit }
|
|
@@ -1138,7 +1448,7 @@ var MongoDBAdapter = class {
|
|
|
1138
1448
|
}
|
|
1139
1449
|
case "top_referrers": {
|
|
1140
1450
|
const rows = await this.collection.aggregate([
|
|
1141
|
-
{ $match: { ...baseMatch, type: "pageview", referrer: { $nin: [null, ""] } } },
|
|
1451
|
+
{ $match: { ...baseMatch, ...filterMatch, type: "pageview", referrer: { $nin: [null, ""] } } },
|
|
1142
1452
|
{ $group: { _id: "$referrer", value: { $sum: 1 } } },
|
|
1143
1453
|
{ $sort: { value: -1 } },
|
|
1144
1454
|
{ $limit: limit }
|
|
@@ -1149,7 +1459,7 @@ var MongoDBAdapter = class {
|
|
|
1149
1459
|
}
|
|
1150
1460
|
case "top_countries": {
|
|
1151
1461
|
const rows = await this.collection.aggregate([
|
|
1152
|
-
{ $match: { ...baseMatch, country: { $ne: null } } },
|
|
1462
|
+
{ $match: { ...baseMatch, ...filterMatch, country: { $ne: null } } },
|
|
1153
1463
|
{ $group: { _id: "$country", value: { $addToSet: "$visitor_id" } } },
|
|
1154
1464
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1155
1465
|
{ $sort: { value: -1 } },
|
|
@@ -1161,7 +1471,7 @@ var MongoDBAdapter = class {
|
|
|
1161
1471
|
}
|
|
1162
1472
|
case "top_cities": {
|
|
1163
1473
|
const rows = await this.collection.aggregate([
|
|
1164
|
-
{ $match: { ...baseMatch, city: { $ne: null } } },
|
|
1474
|
+
{ $match: { ...baseMatch, ...filterMatch, city: { $ne: null } } },
|
|
1165
1475
|
{ $group: { _id: "$city", value: { $addToSet: "$visitor_id" } } },
|
|
1166
1476
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1167
1477
|
{ $sort: { value: -1 } },
|
|
@@ -1173,7 +1483,7 @@ var MongoDBAdapter = class {
|
|
|
1173
1483
|
}
|
|
1174
1484
|
case "top_events": {
|
|
1175
1485
|
const rows = await this.collection.aggregate([
|
|
1176
|
-
{ $match: { ...baseMatch, type: "event", event_name: { $ne: null } } },
|
|
1486
|
+
{ $match: { ...baseMatch, ...filterMatch, type: "event", event_name: { $ne: null } } },
|
|
1177
1487
|
{ $group: { _id: "$event_name", value: { $sum: 1 } } },
|
|
1178
1488
|
{ $sort: { value: -1 } },
|
|
1179
1489
|
{ $limit: limit }
|
|
@@ -1182,9 +1492,109 @@ var MongoDBAdapter = class {
|
|
|
1182
1492
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1183
1493
|
break;
|
|
1184
1494
|
}
|
|
1495
|
+
case "top_conversions": {
|
|
1496
|
+
const conversionEvents = q.conversionEvents ?? [];
|
|
1497
|
+
if (conversionEvents.length === 0) {
|
|
1498
|
+
total = 0;
|
|
1499
|
+
data = [];
|
|
1500
|
+
break;
|
|
1501
|
+
}
|
|
1502
|
+
const rows = await this.collection.aggregate([
|
|
1503
|
+
{ $match: { ...baseMatch, ...filterMatch, type: "event", event_name: { $in: conversionEvents } } },
|
|
1504
|
+
{ $group: { _id: "$event_name", value: { $sum: 1 } } },
|
|
1505
|
+
{ $sort: { value: -1 } },
|
|
1506
|
+
{ $limit: limit }
|
|
1507
|
+
]).toArray();
|
|
1508
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
1509
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1510
|
+
break;
|
|
1511
|
+
}
|
|
1512
|
+
case "top_exit_pages": {
|
|
1513
|
+
const rows = await this.collection.aggregate([
|
|
1514
|
+
{ $match: { ...baseMatch, ...filterMatch, type: "pageview", url: { $ne: null } } },
|
|
1515
|
+
{ $sort: { timestamp: 1 } },
|
|
1516
|
+
{ $group: { _id: "$session_id", url: { $last: "$url" } } },
|
|
1517
|
+
{ $group: { _id: "$url", value: { $sum: 1 } } },
|
|
1518
|
+
{ $sort: { value: -1 } },
|
|
1519
|
+
{ $limit: limit }
|
|
1520
|
+
]).toArray();
|
|
1521
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
1522
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1523
|
+
break;
|
|
1524
|
+
}
|
|
1525
|
+
case "top_transitions": {
|
|
1526
|
+
const rows = await this.collection.aggregate([
|
|
1527
|
+
{ $match: { ...baseMatch, ...filterMatch, type: "pageview", url: { $ne: null } } },
|
|
1528
|
+
{
|
|
1529
|
+
$setWindowFields: {
|
|
1530
|
+
partitionBy: "$session_id",
|
|
1531
|
+
sortBy: { timestamp: 1 },
|
|
1532
|
+
output: {
|
|
1533
|
+
prev_url: { $shift: { output: "$url", by: -1 } }
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
},
|
|
1537
|
+
{ $match: { prev_url: { $ne: null } } },
|
|
1538
|
+
{ $group: { _id: { $concat: ["$prev_url", " \u2192 ", "$url"] }, value: { $sum: 1 } } },
|
|
1539
|
+
{ $sort: { value: -1 } },
|
|
1540
|
+
{ $limit: limit }
|
|
1541
|
+
]).toArray();
|
|
1542
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
1543
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1544
|
+
break;
|
|
1545
|
+
}
|
|
1546
|
+
case "top_scroll_pages": {
|
|
1547
|
+
const rows = await this.collection.aggregate([
|
|
1548
|
+
{ $match: { ...baseMatch, ...filterMatch, type: "event", event_subtype: "scroll_depth", page_path: { $ne: null } } },
|
|
1549
|
+
{ $group: { _id: "$page_path", value: { $sum: 1 } } },
|
|
1550
|
+
{ $sort: { value: -1 } },
|
|
1551
|
+
{ $limit: limit }
|
|
1552
|
+
]).toArray();
|
|
1553
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
1554
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1555
|
+
break;
|
|
1556
|
+
}
|
|
1557
|
+
case "top_button_clicks": {
|
|
1558
|
+
const rows = await this.collection.aggregate([
|
|
1559
|
+
{
|
|
1560
|
+
$match: {
|
|
1561
|
+
...baseMatch,
|
|
1562
|
+
...filterMatch,
|
|
1563
|
+
type: "event",
|
|
1564
|
+
event_subtype: "button_click",
|
|
1565
|
+
$or: [{ element_text: { $ne: null } }, { element_selector: { $ne: null } }]
|
|
1566
|
+
}
|
|
1567
|
+
},
|
|
1568
|
+
{ $group: { _id: { $ifNull: ["$element_text", "$element_selector"] }, value: { $sum: 1 } } },
|
|
1569
|
+
{ $sort: { value: -1 } },
|
|
1570
|
+
{ $limit: limit }
|
|
1571
|
+
]).toArray();
|
|
1572
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
1573
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1574
|
+
break;
|
|
1575
|
+
}
|
|
1576
|
+
case "top_link_targets": {
|
|
1577
|
+
const rows = await this.collection.aggregate([
|
|
1578
|
+
{
|
|
1579
|
+
$match: {
|
|
1580
|
+
...baseMatch,
|
|
1581
|
+
...filterMatch,
|
|
1582
|
+
type: "event",
|
|
1583
|
+
event_subtype: { $in: ["link_click", "outbound_click"] },
|
|
1584
|
+
target_url_path: { $ne: null }
|
|
1585
|
+
}
|
|
1586
|
+
},
|
|
1587
|
+
{ $group: { _id: "$target_url_path", value: { $sum: 1 } } },
|
|
1588
|
+
{ $sort: { value: -1 } },
|
|
1589
|
+
{ $limit: limit }
|
|
1590
|
+
]).toArray();
|
|
1591
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
1592
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1593
|
+
break;
|
|
1594
|
+
}
|
|
1185
1595
|
case "top_devices": {
|
|
1186
1596
|
const rows = await this.collection.aggregate([
|
|
1187
|
-
{ $match: { ...baseMatch, device_type: { $ne: null } } },
|
|
1597
|
+
{ $match: { ...baseMatch, ...filterMatch, device_type: { $ne: null } } },
|
|
1188
1598
|
{ $group: { _id: "$device_type", value: { $addToSet: "$visitor_id" } } },
|
|
1189
1599
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1190
1600
|
{ $sort: { value: -1 } },
|
|
@@ -1196,7 +1606,7 @@ var MongoDBAdapter = class {
|
|
|
1196
1606
|
}
|
|
1197
1607
|
case "top_browsers": {
|
|
1198
1608
|
const rows = await this.collection.aggregate([
|
|
1199
|
-
{ $match: { ...baseMatch, browser: { $ne: null } } },
|
|
1609
|
+
{ $match: { ...baseMatch, ...filterMatch, browser: { $ne: null } } },
|
|
1200
1610
|
{ $group: { _id: "$browser", value: { $addToSet: "$visitor_id" } } },
|
|
1201
1611
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1202
1612
|
{ $sort: { value: -1 } },
|
|
@@ -1208,7 +1618,7 @@ var MongoDBAdapter = class {
|
|
|
1208
1618
|
}
|
|
1209
1619
|
case "top_os": {
|
|
1210
1620
|
const rows = await this.collection.aggregate([
|
|
1211
|
-
{ $match: { ...baseMatch, os: { $ne: null } } },
|
|
1621
|
+
{ $match: { ...baseMatch, ...filterMatch, os: { $ne: null } } },
|
|
1212
1622
|
{ $group: { _id: "$os", value: { $addToSet: "$visitor_id" } } },
|
|
1213
1623
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1214
1624
|
{ $sort: { value: -1 } },
|
|
@@ -1220,7 +1630,7 @@ var MongoDBAdapter = class {
|
|
|
1220
1630
|
}
|
|
1221
1631
|
}
|
|
1222
1632
|
const result = { metric: q.metric, period, data, total };
|
|
1223
|
-
if (q.compare && ["pageviews", "visitors", "sessions", "events"].includes(q.metric)) {
|
|
1633
|
+
if (q.compare && ["pageviews", "visitors", "sessions", "events", "conversions"].includes(q.metric)) {
|
|
1224
1634
|
const prevRange = previousPeriodRange(dateRange);
|
|
1225
1635
|
const prevResult = await this.query({
|
|
1226
1636
|
...q,
|
|
@@ -1252,15 +1662,34 @@ var MongoDBAdapter = class {
|
|
|
1252
1662
|
site_id: params.siteId,
|
|
1253
1663
|
timestamp: { $gte: new Date(dateRange.from), $lte: new Date(dateRange.to) }
|
|
1254
1664
|
};
|
|
1665
|
+
const filterMatch = buildFilterMatch(params.filters);
|
|
1255
1666
|
if (params.metric === "pageviews") {
|
|
1256
1667
|
baseMatch.type = "pageview";
|
|
1257
1668
|
}
|
|
1669
|
+
if (params.metric === "events") {
|
|
1670
|
+
baseMatch.type = "event";
|
|
1671
|
+
}
|
|
1672
|
+
if (params.metric === "conversions") {
|
|
1673
|
+
baseMatch.type = "event";
|
|
1674
|
+
const conversionEvents = params.conversionEvents ?? [];
|
|
1675
|
+
if (conversionEvents.length === 0) {
|
|
1676
|
+
const data2 = fillBuckets(
|
|
1677
|
+
new Date(dateRange.from),
|
|
1678
|
+
new Date(dateRange.to),
|
|
1679
|
+
granularity,
|
|
1680
|
+
granularityToDateFormat(granularity),
|
|
1681
|
+
[]
|
|
1682
|
+
);
|
|
1683
|
+
return { metric: params.metric, granularity, data: data2 };
|
|
1684
|
+
}
|
|
1685
|
+
baseMatch.event_name = { $in: conversionEvents };
|
|
1686
|
+
}
|
|
1258
1687
|
const dateFormat = granularityToDateFormat(granularity);
|
|
1259
1688
|
let pipeline;
|
|
1260
1689
|
if (params.metric === "visitors" || params.metric === "sessions") {
|
|
1261
1690
|
const groupField = params.metric === "visitors" ? "$visitor_id" : "$session_id";
|
|
1262
1691
|
pipeline = [
|
|
1263
|
-
{ $match: baseMatch },
|
|
1692
|
+
{ $match: { ...baseMatch, ...filterMatch } },
|
|
1264
1693
|
{
|
|
1265
1694
|
$group: {
|
|
1266
1695
|
_id: {
|
|
@@ -1279,7 +1708,7 @@ var MongoDBAdapter = class {
|
|
|
1279
1708
|
];
|
|
1280
1709
|
} else {
|
|
1281
1710
|
pipeline = [
|
|
1282
|
-
{ $match: baseMatch },
|
|
1711
|
+
{ $match: { ...baseMatch, ...filterMatch } },
|
|
1283
1712
|
{
|
|
1284
1713
|
$group: {
|
|
1285
1714
|
_id: { $dateToString: { format: dateFormat, date: "$timestamp" } },
|
|
@@ -1360,7 +1789,12 @@ var MongoDBAdapter = class {
|
|
|
1360
1789
|
const offset = params.offset ?? 0;
|
|
1361
1790
|
const match = { site_id: params.siteId };
|
|
1362
1791
|
if (params.type) match.type = params.type;
|
|
1363
|
-
if (params.eventName)
|
|
1792
|
+
if (params.eventName) {
|
|
1793
|
+
match.event_name = params.eventName;
|
|
1794
|
+
} else if (params.eventNames && params.eventNames.length > 0) {
|
|
1795
|
+
match.event_name = { $in: params.eventNames };
|
|
1796
|
+
}
|
|
1797
|
+
if (params.eventSource) match.event_source = params.eventSource;
|
|
1364
1798
|
if (params.visitorId) match.visitor_id = params.visitorId;
|
|
1365
1799
|
if (params.userId) match.user_id = params.userId;
|
|
1366
1800
|
if (params.period || params.dateFrom) {
|
|
@@ -1395,6 +1829,7 @@ var MongoDBAdapter = class {
|
|
|
1395
1829
|
}
|
|
1396
1830
|
const pipeline = [
|
|
1397
1831
|
{ $match: match },
|
|
1832
|
+
{ $sort: { timestamp: 1 } },
|
|
1398
1833
|
{
|
|
1399
1834
|
$group: {
|
|
1400
1835
|
_id: "$visitor_id",
|
|
@@ -1406,13 +1841,22 @@ var MongoDBAdapter = class {
|
|
|
1406
1841
|
totalPageviews: { $sum: { $cond: [{ $eq: ["$type", "pageview"] }, 1, 0] } },
|
|
1407
1842
|
sessions: { $addToSet: "$session_id" },
|
|
1408
1843
|
lastUrl: { $last: "$url" },
|
|
1844
|
+
referrer: { $last: "$referrer" },
|
|
1409
1845
|
device_type: { $last: "$device_type" },
|
|
1410
1846
|
browser: { $last: "$browser" },
|
|
1411
1847
|
os: { $last: "$os" },
|
|
1412
1848
|
country: { $last: "$country" },
|
|
1413
1849
|
city: { $last: "$city" },
|
|
1414
1850
|
region: { $last: "$region" },
|
|
1415
|
-
language: { $last: "$language" }
|
|
1851
|
+
language: { $last: "$language" },
|
|
1852
|
+
timezone: { $last: "$timezone" },
|
|
1853
|
+
screen_width: { $last: "$screen_width" },
|
|
1854
|
+
screen_height: { $last: "$screen_height" },
|
|
1855
|
+
utm_source: { $last: "$utm_source" },
|
|
1856
|
+
utm_medium: { $last: "$utm_medium" },
|
|
1857
|
+
utm_campaign: { $last: "$utm_campaign" },
|
|
1858
|
+
utm_term: { $last: "$utm_term" },
|
|
1859
|
+
utm_content: { $last: "$utm_content" }
|
|
1416
1860
|
}
|
|
1417
1861
|
},
|
|
1418
1862
|
{ $sort: { lastSeen: -1 } },
|
|
@@ -1434,9 +1878,19 @@ var MongoDBAdapter = class {
|
|
|
1434
1878
|
totalPageviews: u.totalPageviews,
|
|
1435
1879
|
totalSessions: u.sessions.length,
|
|
1436
1880
|
lastUrl: u.lastUrl ?? void 0,
|
|
1881
|
+
referrer: u.referrer ?? void 0,
|
|
1437
1882
|
device: u.device_type ? { type: u.device_type, browser: u.browser ?? "", os: u.os ?? "" } : void 0,
|
|
1438
1883
|
geo: u.country ? { country: u.country, city: u.city ?? void 0, region: u.region ?? void 0 } : void 0,
|
|
1439
|
-
language: u.language ?? void 0
|
|
1884
|
+
language: u.language ?? void 0,
|
|
1885
|
+
timezone: u.timezone ?? void 0,
|
|
1886
|
+
screen: u.screen_width || u.screen_height ? { width: u.screen_width ?? 0, height: u.screen_height ?? 0 } : void 0,
|
|
1887
|
+
utm: u.utm_source ? {
|
|
1888
|
+
source: u.utm_source ?? void 0,
|
|
1889
|
+
medium: u.utm_medium ?? void 0,
|
|
1890
|
+
campaign: u.utm_campaign ?? void 0,
|
|
1891
|
+
term: u.utm_term ?? void 0,
|
|
1892
|
+
content: u.utm_content ?? void 0
|
|
1893
|
+
} : void 0
|
|
1440
1894
|
}));
|
|
1441
1895
|
return {
|
|
1442
1896
|
users,
|
|
@@ -1465,6 +1919,13 @@ var MongoDBAdapter = class {
|
|
|
1465
1919
|
title: doc.title ?? void 0,
|
|
1466
1920
|
name: doc.event_name ?? void 0,
|
|
1467
1921
|
properties: doc.properties ?? void 0,
|
|
1922
|
+
eventSource: doc.event_source ? doc.event_source : void 0,
|
|
1923
|
+
eventSubtype: doc.event_subtype ? doc.event_subtype : void 0,
|
|
1924
|
+
pagePath: doc.page_path ?? void 0,
|
|
1925
|
+
targetUrlPath: doc.target_url_path ?? void 0,
|
|
1926
|
+
elementSelector: doc.element_selector ?? void 0,
|
|
1927
|
+
elementText: doc.element_text ?? void 0,
|
|
1928
|
+
scrollDepthPct: doc.scroll_depth_pct ?? void 0,
|
|
1468
1929
|
userId: doc.user_id ?? void 0,
|
|
1469
1930
|
traits: doc.traits ?? void 0,
|
|
1470
1931
|
geo: doc.country ? { country: doc.country, city: doc.city ?? void 0, region: doc.region ?? void 0 } : void 0,
|
|
@@ -1488,6 +1949,7 @@ var MongoDBAdapter = class {
|
|
|
1488
1949
|
name: data.name,
|
|
1489
1950
|
domain: data.domain ?? null,
|
|
1490
1951
|
allowed_origins: data.allowedOrigins ?? null,
|
|
1952
|
+
conversion_events: data.conversionEvents ?? null,
|
|
1491
1953
|
created_at: now,
|
|
1492
1954
|
updated_at: now
|
|
1493
1955
|
};
|
|
@@ -1511,6 +1973,7 @@ var MongoDBAdapter = class {
|
|
|
1511
1973
|
if (data.name !== void 0) updates.name = data.name;
|
|
1512
1974
|
if (data.domain !== void 0) updates.domain = data.domain || null;
|
|
1513
1975
|
if (data.allowedOrigins !== void 0) updates.allowed_origins = data.allowedOrigins.length > 0 ? data.allowedOrigins : null;
|
|
1976
|
+
if (data.conversionEvents !== void 0) updates.conversion_events = data.conversionEvents.length > 0 ? data.conversionEvents : null;
|
|
1514
1977
|
const result = await this.sites.findOneAndUpdate(
|
|
1515
1978
|
{ site_id: siteId },
|
|
1516
1979
|
{ $set: updates },
|
|
@@ -1541,6 +2004,7 @@ var MongoDBAdapter = class {
|
|
|
1541
2004
|
name: doc.name,
|
|
1542
2005
|
domain: doc.domain ?? void 0,
|
|
1543
2006
|
allowedOrigins: doc.allowed_origins ?? void 0,
|
|
2007
|
+
conversionEvents: doc.conversion_events ?? void 0,
|
|
1544
2008
|
createdAt: doc.created_at.toISOString(),
|
|
1545
2009
|
updatedAt: doc.updated_at.toISOString()
|
|
1546
2010
|
};
|
|
@@ -1794,6 +2258,7 @@ async function createCollector(config) {
|
|
|
1794
2258
|
if (allowed) {
|
|
1795
2259
|
res.setHeader?.("Access-Control-Allow-Origin", origin || "*");
|
|
1796
2260
|
res.setHeader?.("Access-Control-Allow-Methods", methods);
|
|
2261
|
+
res.setHeader?.("Access-Control-Allow-Credentials", "true");
|
|
1797
2262
|
const headers = ["Content-Type", extraHeaders].filter(Boolean).join(", ");
|
|
1798
2263
|
res.setHeader?.("Access-Control-Allow-Headers", headers);
|
|
1799
2264
|
}
|
|
@@ -1824,7 +2289,14 @@ async function createCollector(config) {
|
|
|
1824
2289
|
}
|
|
1825
2290
|
function handler() {
|
|
1826
2291
|
return async (req, res) => {
|
|
1827
|
-
|
|
2292
|
+
res.setHeader?.("Access-Control-Allow-Origin", "*");
|
|
2293
|
+
res.setHeader?.("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
2294
|
+
res.setHeader?.("Access-Control-Allow-Headers", "Content-Type");
|
|
2295
|
+
if (req.method === "OPTIONS") {
|
|
2296
|
+
res.writeHead?.(204);
|
|
2297
|
+
res.end?.();
|
|
2298
|
+
return;
|
|
2299
|
+
}
|
|
1828
2300
|
if (req.method !== "POST") {
|
|
1829
2301
|
sendJson(res, 405, { ok: false, error: "Method not allowed" });
|
|
1830
2302
|
return;
|
|
@@ -1903,8 +2375,13 @@ async function createCollector(config) {
|
|
|
1903
2375
|
period: params.period,
|
|
1904
2376
|
dateFrom: params.dateFrom,
|
|
1905
2377
|
dateTo: params.dateTo,
|
|
1906
|
-
granularity: q.granularity
|
|
2378
|
+
granularity: q.granularity,
|
|
2379
|
+
filters: q.filters ? JSON.parse(q.filters) : void 0
|
|
1907
2380
|
};
|
|
2381
|
+
if (tsParams.metric === "conversions") {
|
|
2382
|
+
const site = await db.getSite(params.siteId);
|
|
2383
|
+
tsParams.conversionEvents = site?.conversionEvents ?? [];
|
|
2384
|
+
}
|
|
1908
2385
|
const result2 = await db.queryTimeSeries(tsParams);
|
|
1909
2386
|
sendJson(res, 200, result2);
|
|
1910
2387
|
return;
|
|
@@ -1920,7 +2397,15 @@ async function createCollector(config) {
|
|
|
1920
2397
|
sendJson(res, 200, result2);
|
|
1921
2398
|
return;
|
|
1922
2399
|
}
|
|
1923
|
-
const
|
|
2400
|
+
const isConversionMetric = params.metric === "conversions" || params.metric === "top_conversions";
|
|
2401
|
+
let result;
|
|
2402
|
+
if (isConversionMetric) {
|
|
2403
|
+
const site = await db.getSite(params.siteId);
|
|
2404
|
+
const conversionEvents = site?.conversionEvents ?? [];
|
|
2405
|
+
result = await db.query({ ...params, conversionEvents });
|
|
2406
|
+
} else {
|
|
2407
|
+
result = await db.query(params);
|
|
2408
|
+
}
|
|
1924
2409
|
sendJson(res, 200, result);
|
|
1925
2410
|
} catch (err) {
|
|
1926
2411
|
sendJson(res, 500, { ok: false, error: err instanceof Error ? err.message : "Internal error" });
|
|
@@ -2017,10 +2502,13 @@ async function createCollector(config) {
|
|
|
2017
2502
|
sendJson(res, 401, { ok: false, error: "Invalid or missing secret key" });
|
|
2018
2503
|
return;
|
|
2019
2504
|
}
|
|
2505
|
+
const eventNames = typeof q.eventNames === "string" ? q.eventNames.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
2020
2506
|
const params = {
|
|
2021
2507
|
siteId: q.siteId,
|
|
2022
2508
|
type: q.type,
|
|
2023
2509
|
eventName: q.eventName,
|
|
2510
|
+
eventNames,
|
|
2511
|
+
eventSource: q.eventSource,
|
|
2024
2512
|
visitorId: q.visitorId,
|
|
2025
2513
|
userId: q.userId,
|
|
2026
2514
|
period: q.period,
|
|
@@ -2060,9 +2548,13 @@ async function createCollector(config) {
|
|
|
2060
2548
|
const visitorId = usersIdx >= 0 ? pathSegments[usersIdx + 1] : void 0;
|
|
2061
2549
|
const action = usersIdx >= 0 ? pathSegments[usersIdx + 2] : void 0;
|
|
2062
2550
|
if (visitorId && action === "events") {
|
|
2551
|
+
const eventNames = typeof q.eventNames === "string" ? q.eventNames.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
2063
2552
|
const params2 = {
|
|
2064
2553
|
siteId: q.siteId,
|
|
2065
2554
|
type: q.type,
|
|
2555
|
+
eventName: q.eventName,
|
|
2556
|
+
eventNames,
|
|
2557
|
+
eventSource: q.eventSource,
|
|
2066
2558
|
period: q.period,
|
|
2067
2559
|
dateFrom: q.dateFrom,
|
|
2068
2560
|
dateTo: q.dateTo,
|
|
@@ -2169,7 +2661,8 @@ function createAdapter(config) {
|
|
|
2169
2661
|
}
|
|
2170
2662
|
}
|
|
2171
2663
|
async function parseBody(req) {
|
|
2172
|
-
if (req.body) return req.body;
|
|
2664
|
+
if (req.body && typeof req.body === "object") return req.body;
|
|
2665
|
+
if (typeof req.body === "string") return JSON.parse(req.body);
|
|
2173
2666
|
return new Promise((resolve, reject) => {
|
|
2174
2667
|
let data = "";
|
|
2175
2668
|
req.on("data", (chunk) => {
|