ghost 6.9.0 → 6.9.3

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.
Files changed (72) hide show
  1. package/components/tryghost-i18n-6.9.3.tgz +0 -0
  2. package/core/built/admin/assets/activitypub/activitypub.js +2 -2
  3. package/core/built/admin/assets/activitypub/{index-C19nEXqT.mjs → index-D-0TWnTq.mjs} +2447 -2443
  4. package/core/built/admin/assets/activitypub/{index-B29oZuTp.mjs → index-Dz4ykJpN.mjs} +2 -2
  5. package/core/built/admin/assets/admin-x-settings/{CodeEditorView-BNKxdfRt.mjs → CodeEditorView-C4K2SFCa.mjs} +2 -2
  6. package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +2 -2
  7. package/core/built/admin/assets/admin-x-settings/{index-B-_a183c.mjs → index-D1JNSNMf.mjs} +2 -2
  8. package/core/built/admin/assets/admin-x-settings/{index-CjRGpMVv.mjs → index-DLS4G2Af.mjs} +2 -2
  9. package/core/built/admin/assets/admin-x-settings/{index-Q0XmL0KU.mjs → index-jt7mifza.mjs} +23310 -22917
  10. package/core/built/admin/assets/admin-x-settings/{modals-omgXN6i-.mjs → modals-Bkrvssih.mjs} +3336 -3339
  11. package/core/built/admin/assets/{chunk.524.774a2df444e2ffde4942.js → chunk.524.6d040cb21c767f0236ae.js} +7 -7
  12. package/core/built/admin/assets/{chunk.582.ca4f05f3c39fda05b54c.js → chunk.582.e35aed09ef750c552601.js} +9 -9
  13. package/core/built/admin/assets/ghost-192beb3c8f2f6e58b70d3aade481ae15.css +1 -0
  14. package/core/built/admin/assets/{ghost-94d0fbb20e8e880fa9ba144cf26ab050.js → ghost-5fd8d4d5e1ffe4d8405d30f3efba12b7.js} +105 -89
  15. package/core/built/admin/assets/ghost-dark-e50f4c063ecc977f023e78da2dd67b42.css +1 -0
  16. package/core/built/admin/assets/posts/posts.js +6745 -6741
  17. package/core/built/admin/assets/stats/stats.js +16015 -16011
  18. package/core/built/admin/index.html +4 -4
  19. package/core/server/data/tinybird/datasources/_mv_hits.datasource +4 -1
  20. package/core/server/data/tinybird/endpoints/README.md +109 -0
  21. package/core/server/data/tinybird/endpoints/api_kpis.pipe +2 -23
  22. package/core/server/data/tinybird/endpoints/api_monitoring_ingestion.pipe +14 -13
  23. package/core/server/data/tinybird/endpoints/api_monitoring_ingestion_aggregated.pipe +13 -12
  24. package/core/server/data/tinybird/endpoints/api_top_locations.pipe +0 -19
  25. package/core/server/data/tinybird/endpoints/api_top_pages.pipe +0 -23
  26. package/core/server/data/tinybird/endpoints/api_top_sources.pipe +0 -11
  27. package/core/server/data/tinybird/endpoints/api_top_utm_campaigns.pipe +0 -6
  28. package/core/server/data/tinybird/endpoints/api_top_utm_contents.pipe +0 -6
  29. package/core/server/data/tinybird/endpoints/api_top_utm_mediums.pipe +0 -6
  30. package/core/server/data/tinybird/endpoints/api_top_utm_sources.pipe +0 -6
  31. package/core/server/data/tinybird/endpoints/api_top_utm_terms.pipe +0 -6
  32. package/core/server/data/tinybird/pipes/filtered_sessions.pipe +28 -8
  33. package/core/server/data/tinybird/pipes/mv_hits.pipe +23 -11
  34. package/core/server/data/tinybird/tests/api_top_pages.yaml +5 -2
  35. package/core/server/services/email-analytics/EmailAnalyticsService.js +53 -6
  36. package/core/server/services/email-service/DomainWarmingService.js +47 -9
  37. package/core/server/services/email-service/DomainWarmingService.ts +62 -11
  38. package/core/server/services/email-service/EmailServiceWrapper.js +2 -1
  39. package/core/server/services/member-welcome-emails/jobs/index.js +13 -9
  40. package/package.json +3 -3
  41. package/tsconfig.tsbuildinfo +1 -1
  42. package/yarn.lock +5 -50
  43. package/components/tryghost-i18n-6.9.0.tgz +0 -0
  44. package/core/built/admin/assets/ghost-dark-6c9cfa9c364e28c57e5983f68ec6f2fc.css +0 -1
  45. package/core/built/admin/assets/ghost-f724c1d53f5402f78a2d8cf8beb7c716.css +0 -1
  46. package/core/built/admin/assets/img/google-docs-1e42cc272fc088da49e4b0ddfb01b006.svg +0 -6
  47. package/core/built/admin/assets/img/mailchimp-f22b1e130aac764965b9306d7265a6b2.svg +0 -8
  48. package/core/built/admin/assets/img/patreon-b19a5e6418a72977a16b30039d374d04.svg +0 -7
  49. package/core/built/admin/assets/img/paypal-38e9448ce7549ea4caf8e7753ae661d6.svg +0 -8
  50. package/core/built/admin/assets/img/slackicon-406aadea8994ca2ddee9c1d7157208db.png +0 -0
  51. package/core/built/admin/assets/img/themes/Alto-0dfe76694ed222d6d96fc9c8db979a38.png +0 -0
  52. package/core/built/admin/assets/img/themes/Bulletin-d66dec818ad0ba2965dd7eb3130c621c.png +0 -0
  53. package/core/built/admin/assets/img/themes/Casper-9a0ce71df3a1c589c1414ad2aa5b8aeb.png +0 -0
  54. package/core/built/admin/assets/img/themes/Dawn-302fbfdbc352098a256137159bd83dd8.png +0 -0
  55. package/core/built/admin/assets/img/themes/Digest-698e78d8e8481daff8ae4a8647528dc9.png +0 -0
  56. package/core/built/admin/assets/img/themes/Dope-d099dfca697adae16baa76f89520c5b3.png +0 -0
  57. package/core/built/admin/assets/img/themes/Ease-7075f809892f10c58892e15a57a4aae4.png +0 -0
  58. package/core/built/admin/assets/img/themes/Edge-1e5e0eec6941d7bdca02cebb66187357.png +0 -0
  59. package/core/built/admin/assets/img/themes/Edition-10111a2b8458168dcff81b7fb151be70.png +0 -0
  60. package/core/built/admin/assets/img/themes/Episode-e4c86d1f75ef1d8a77791d7fd519fdd4.png +0 -0
  61. package/core/built/admin/assets/img/themes/Headline-f70eaf49b9fcae1ddfe3d4496d8be54d.png +0 -0
  62. package/core/built/admin/assets/img/themes/Journal-07d35b2311501d2738bad1907ba2f7e1.png +0 -0
  63. package/core/built/admin/assets/img/themes/London-4e042390da16fecef947f3a7001d03db.png +0 -0
  64. package/core/built/admin/assets/img/themes/Ruby-b896885448e0f28ca62c6dcd56c732aa.png +0 -0
  65. package/core/built/admin/assets/img/themes/Solo-0292eb9ae0ca7b578cff50824d40cc86.png +0 -0
  66. package/core/built/admin/assets/img/themes/Source-Magazine-176385eb99561485ce6385598f7599b0.png +0 -0
  67. package/core/built/admin/assets/img/themes/Source-Newsletter-bb1a8611a326edb78f9ecb8bc1838c81.png +0 -0
  68. package/core/built/admin/assets/img/themes/Source-f29c6d6abe774ea7d5940a7431069fce.png +0 -0
  69. package/core/built/admin/assets/img/themes/Taste-a24d2a786900d9caff7e773ca0040991.png +0 -0
  70. package/core/built/admin/assets/img/themes/Wave-b98fadfd3ed16e8b2e383dd8e8e5dca2.png +0 -0
  71. package/core/built/admin/assets/img/typeform-9f23f8712d776a7515594676285266f5.svg +0 -7
  72. package/core/built/admin/assets/img/zero-bounce-ee799eddb1f88e33ab8c462858cbfed9.png +0 -0
@@ -6,7 +6,7 @@
6
6
  <title>Ghost</title>
7
7
 
8
8
 
9
- <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%22%2C%22editorUrl%22%3A%22%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%226.9%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%2C%22editorFilename%22%3A%22koenig-lexical.umd.js%22%2C%22editorHash%22%3A%2237bd1e3e4d%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%22a4b3b38319%22%2C%22activitypubFilename%22%3A%22activitypub.js%22%2C%22activitypubHash%22%3A%2238e5073e2a%22%2C%22postsFilename%22%3A%22posts.js%22%2C%22postsHash%22%3A%22460f25b871%22%2C%22statsFilename%22%3A%22stats.js%22%2C%22statsHash%22%3A%22c820ebd57d%22%2C%22activitypubRemoteConfigUrl%22%3A%22%2F.ghost%2Factivitypub%2Fstable%2Fclient-config%22%7D" />
9
+ <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%22%2C%22editorUrl%22%3A%22%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%226.9%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%2C%22editorFilename%22%3A%22koenig-lexical.umd.js%22%2C%22editorHash%22%3A%2237bd1e3e4d%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%223d200b447b%22%2C%22activitypubFilename%22%3A%22activitypub.js%22%2C%22activitypubHash%22%3A%2241a8ee70ff%22%2C%22postsFilename%22%3A%22posts.js%22%2C%22postsHash%22%3A%2273ad0a757c%22%2C%22statsFilename%22%3A%22stats.js%22%2C%22statsHash%22%3A%229c456bb0f1%22%2C%22activitypubRemoteConfigUrl%22%3A%22%2F.ghost%2Factivitypub%2Fstable%2Fclient-config%22%7D" />
10
10
 
11
11
  <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1, minimal-ui, viewport-fit=cover" />
12
12
  <meta name="pinterest" content="nopin" />
@@ -28,7 +28,7 @@
28
28
  </style>
29
29
 
30
30
  <link integrity="" rel="stylesheet" href="assets/vendor-0ede59da8efb5e28fa929557f7ff7154.css">
31
- <link integrity="" rel="stylesheet" href="assets/ghost-f724c1d53f5402f78a2d8cf8beb7c716.css" title="light">
31
+ <link integrity="" rel="stylesheet" href="assets/ghost-192beb3c8f2f6e58b70d3aade481ae15.css" title="light">
32
32
 
33
33
 
34
34
  </head>
@@ -49,7 +49,7 @@
49
49
 
50
50
  <script src="assets/vendor-aed0068cf9b67d042dd23a6343545b7b.js"></script>
51
51
  <script src="assets/chunk.397.a720333cfffc99c47e71.js"></script>
52
- <script src="assets/chunk.524.774a2df444e2ffde4942.js"></script>
53
- <script src="assets/ghost-94d0fbb20e8e880fa9ba144cf26ab050.js"></script>
52
+ <script src="assets/chunk.524.6d040cb21c767f0236ae.js"></script>
53
+ <script src="assets/ghost-5fd8d4d5e1ffe4d8405d30f3efba12b7.js"></script>
54
54
  </body>
55
55
  </html>
@@ -1,6 +1,9 @@
1
1
  SCHEMA >
2
2
  `site_uuid` LowCardinality(String),
3
3
  `timestamp` DateTime,
4
+ `received_at` DateTime64(3),
5
+ `inserted_at` DateTime64(3),
6
+ `ingestion_latency_ms` Int64,
4
7
  `action` LowCardinality(String),
5
8
  `version` LowCardinality(String),
6
9
  `session_id` String,
@@ -23,4 +26,4 @@ SCHEMA >
23
26
 
24
27
  ENGINE "MergeTree"
25
28
  ENGINE_PARTITION_KEY "toYYYYMM(timestamp)"
26
- ENGINE_SORTING_KEY "site_uuid, timestamp, session_id"
29
+ ENGINE_SORTING_KEY "site_uuid, timestamp, session_id"
@@ -0,0 +1,109 @@
1
+ # Tinybird Analytics Endpoints
2
+
3
+ ## Filtering Architecture
4
+
5
+ ### Session-Level vs Hit-Level Attributes
6
+
7
+ Ghost analytics distinguishes between two types of attributes:
8
+
9
+ #### Session-Level Attributes
10
+ These are captured from the **first hit** (earliest timestamp) in a session using `argMin(field, timestamp)` in the `mv_session_data` materialized view:
11
+
12
+ - `source` - Referring domain
13
+ - `utm_source` - UTM source parameter
14
+ - `utm_medium` - UTM medium parameter
15
+ - `utm_campaign` - UTM campaign parameter
16
+ - `utm_term` - UTM term parameter
17
+ - `utm_content` - UTM content parameter
18
+
19
+ Session-level attributes represent the **origin** of the session and remain constant throughout the entire session, even if subsequent pageviews have different UTM parameters or come from different sources.
20
+
21
+ #### Hit-Level Attributes
22
+ These can vary across pageviews within a session:
23
+
24
+ - `pathname` - URL path of the page
25
+ - `post_uuid` - UUID of the post/page
26
+ - `member_status` - Member status at time of hit (can change during a session)
27
+
28
+ ### How Filtering Works
29
+
30
+ All endpoint filtering is handled through the `filtered_sessions.pipe`, which uses a two-stage approach:
31
+
32
+ **Stage 1: Query Filters (Hit-Level)**
33
+ ```sql
34
+ NODE query_filters
35
+ ```
36
+ Finds sessions where **at least one hit** matches the hit-level filter criteria (pathname, post_uuid, member_status). A session qualifies if ANY of its pageviews match the specified criteria.
37
+
38
+ **Stage 2: Session Attributes (Session-Level)**
39
+ ```sql
40
+ NODE sessions_filtered_by_session_attributes
41
+ ```
42
+ Further filters by session-level attributes (source, utm_*) by joining with `mv_session_data`. These filters check attributes from the **first hit only**.
43
+
44
+ **Stage 3: Final Output**
45
+ ```sql
46
+ NODE filtered_sessions
47
+ ```
48
+ Returns session IDs that match **all** filter criteria (both hit-level and session-level).
49
+
50
+ ### Important Behavior: All Hits from Matching Sessions
51
+
52
+ When endpoints join `_mv_hits` with `filtered_sessions`, they return **ALL hits from sessions that match the filter criteria**, not just the specific hits that matched.
53
+
54
+ #### Example: api_top_pages
55
+
56
+ ```sql
57
+ select
58
+ post_uuid,
59
+ pathname,
60
+ uniqExact(session_id) as visits
61
+ from _mv_hits h
62
+ inner join filtered_sessions fs
63
+ on fs.session_id = h.session_id
64
+ ```
65
+
66
+ **Scenario:** Filter by `utm_medium=social`
67
+
68
+ If there are 5 sessions where the first hit had `utm_medium=social`:
69
+ - The query returns ALL pageviews from those 5 sessions
70
+ - A single session might visit multiple pages (e.g., /, /about/, /blog/post/)
71
+ - Each page shows how many of the 5 sessions visited it
72
+ - The sum of visits across all pages can exceed 5 because sessions are counted once per unique page they visited
73
+
74
+ **Result:**
75
+ ```json
76
+ {"pathname":"/about/","visits":3} // 3 of the 5 sessions visited /about/
77
+ {"pathname":"/","visits":3} // 3 of the 5 sessions visited /
78
+ {"pathname":"/blog/hello/","visits":2} // 2 of the 5 sessions visited /blog/hello/
79
+ ```
80
+
81
+ Total: 8 page-session combinations from 5 unique sessions.
82
+
83
+ This behavior answers the question: **"What pages did users visit when they came from [source/utm]?"** rather than **"Which specific pageviews had [source/utm] in the URL?"**
84
+
85
+ ### Filter Placement Rules
86
+
87
+ When creating or modifying endpoints:
88
+
89
+ 1. **Session-level filters** (source, utm_*) → Only in `filtered_sessions.pipe`
90
+ 2. **Hit-level filters** (pathname, post_uuid, member_status) → Can be in both:
91
+ - `filtered_sessions.pipe` (for session qualification)
92
+ - Endpoint queries (for additional filtering of results)
93
+ 3. **Never duplicate** session-level filters in endpoint queries - always rely on `filtered_sessions`
94
+
95
+ ### API Endpoint Patterns
96
+
97
+ All analytics endpoints follow this pattern:
98
+
99
+ ```sql
100
+ from _mv_hits h
101
+ inner join filtered_sessions fs
102
+ on fs.session_id = h.session_id
103
+ where
104
+ site_uuid = {{ site_uuid }}
105
+ -- Date range filters
106
+ -- Hit-level filters only (if needed for this specific endpoint)
107
+ ```
108
+
109
+ The join with `filtered_sessions` ensures only hits from sessions matching the filter criteria are included, while the `where` clause can apply additional hit-level filtering specific to the endpoint's purpose.
@@ -79,17 +79,6 @@ SQL >
79
79
  on fs.session_id = sd.session_id
80
80
  where
81
81
  site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }}
82
- {% if defined(date_from) and day_diff(date_from, date_to) == 0 %}
83
- and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) = {{ Date(date_from) }}
84
- {% else %}
85
- {% if defined(date_from) %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) >= {{ Date(date_from) }} {% else %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) >= timestampAdd(today(), interval -7 day) {% end %}
86
- {% if defined(date_to) %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) <= {{ Date(date_to) }} {% else %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) <= today() {% end %}
87
- {% end %}
88
- {% if defined(utm_source) %} and utm_source = {{ String(utm_source, description="UTM source to filter on", required=False) }} {% end %}
89
- {% if defined(utm_medium) %} and utm_medium = {{ String(utm_medium, description="UTM medium to filter on", required=False) }} {% end %}
90
- {% if defined(utm_campaign) %} and utm_campaign = {{ String(utm_campaign, description="UTM campaign to filter on", required=False) }} {% end %}
91
- {% if defined(utm_term) %} and utm_term = {{ String(utm_term, description="UTM term to filter on", required=False) }} {% end %}
92
- {% if defined(utm_content) %} and utm_content = {{ String(utm_content, description="UTM content to filter on", required=False) }} {% end %}
93
82
 
94
83
 
95
84
  NODE data
@@ -131,24 +120,14 @@ SQL >
131
120
  {% else %}
132
121
  a.date = toDate(toTimezone(timestamp, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}}))
133
122
  {% end %}
123
+ inner join filtered_sessions fs
124
+ on fs.session_id = h.session_id
134
125
  where
135
126
  site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }}
136
- {% if defined(date_from) and day_diff(date_from, date_to) == 0 %}
137
- and toDate(toTimezone(timestamp, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) = {{ Date(date_from) }}
138
- {% else %}
139
- {% if defined(date_from) %} and toDate(toTimezone(timestamp, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) >= {{ Date(date_from) }} {% else %} and toDate(toTimezone(timestamp, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) >= timestampAdd(today(), interval -7 day) {% end %}
140
- {% if defined(date_to) %} and toDate(toTimezone(timestamp, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) <= {{ Date(date_to) }} {% else %} and toDate(toTimezone(timestamp, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) <= today() {% end %}
141
- {% end %}
142
127
  {% if defined(member_status) %} and member_status IN {{ Array(member_status, "'undefined', 'free', 'paid'", description="Member status to filter on", required=False) }} {% end %}
143
- {% if defined(source) %} and source = {{ String(source, description="Source to filter on", required=False) }} {% end %}
144
128
  {% if defined(location) %} and location = {{ String(location, description="Location to filter on", required=False) }} {% end %}
145
129
  {% if defined(pathname) %} and pathname = {{ String(pathname, description="Pathname to filter on", required=False) }} {% end %}
146
130
  {% if defined(post_uuid) %} and post_uuid = {{String(post_uuid, description="Post UUID to filter on", required=False) }} {% end %}
147
- {% if defined(utm_source) %} and utm_source = {{ String(utm_source, description="UTM source to filter on", required=False) }} {% end %}
148
- {% if defined(utm_medium) %} and utm_medium = {{ String(utm_medium, description="UTM medium to filter on", required=False) }} {% end %}
149
- {% if defined(utm_campaign) %} and utm_campaign = {{ String(utm_campaign, description="UTM campaign to filter on", required=False) }} {% end %}
150
- {% if defined(utm_term) %} and utm_term = {{ String(utm_term, description="UTM term to filter on", required=False) }} {% end %}
151
- {% if defined(utm_content) %} and utm_content = {{ String(utm_content, description="UTM content to filter on", required=False) }} {% end %}
152
131
  group by date
153
132
  order by date
154
133
 
@@ -4,15 +4,16 @@ TOKEN "axis" READ
4
4
  NODE ingestion_latency
5
5
  SQL >
6
6
  %
7
- SELECT
7
+ SELECT
8
8
  toDate(inserted_at) as date,
9
9
  site_uuid,
10
10
  inserted_at,
11
- parseDateTimeBestEffort(JSONExtractString(payload, 'meta', 'received_timestamp'), 3) as received_timestamp,
12
- dateDiff('millisecond', received_timestamp, inserted_at) as latency_ms
13
- FROM analytics_events
14
- WHERE
15
- JSONExtractString(payload, 'meta', 'received_timestamp') != ''
11
+ received_at,
12
+ ingestion_latency_ms
13
+ FROM _mv_hits
14
+ WHERE
15
+ -- ingestion_latency_ms is set to -1 if received_at is invalid
16
+ ingestion_latency_ms >= 0
16
17
  {% if defined(start_date) %}
17
18
  AND toDate(inserted_at) >= {{ Date(start_date, description="Start date for filtering", required=False) }}
18
19
  {% else %}
@@ -30,19 +31,19 @@ SQL >
30
31
  NODE ingestion_metrics
31
32
  SQL >
32
33
  %
33
- SELECT
34
+ SELECT
34
35
  date,
35
36
  {% if defined(site_uuid) %}
36
37
  site_uuid,
37
38
  {% end %}
38
39
  count() as total_events,
39
- round(avg(latency_ms)) as avg_latency_ms,
40
- round(quantile(0.5)(latency_ms)) as p50_latency_ms,
41
- round(quantile(0.95)(latency_ms)) as p95_latency_ms,
42
- round(min(latency_ms)) as min_latency_ms,
43
- round(max(latency_ms)) as max_latency_ms
40
+ round(avg(ingestion_latency_ms)) as avg_latency_ms,
41
+ round(quantile(0.5)(ingestion_latency_ms)) as p50_latency_ms,
42
+ round(quantile(0.95)(ingestion_latency_ms)) as p95_latency_ms,
43
+ round(min(ingestion_latency_ms)) as min_latency_ms,
44
+ round(max(ingestion_latency_ms)) as max_latency_ms
44
45
  FROM ingestion_latency
45
46
  GROUP BY date{% if defined(site_uuid) %}, site_uuid{% end %}
46
47
  ORDER BY date DESC{% if defined(site_uuid) %}, site_uuid{% end %}
47
48
 
48
- TYPE ENDPOINT
49
+ TYPE ENDPOINT
@@ -4,14 +4,15 @@ TOKEN "axis" READ
4
4
  NODE ingestion_latency
5
5
  SQL >
6
6
  %
7
- SELECT
7
+ SELECT
8
8
  site_uuid,
9
9
  inserted_at,
10
- parseDateTimeBestEffort(JSONExtractString(payload, 'meta', 'received_timestamp'), 3) as received_timestamp,
11
- dateDiff('millisecond', received_timestamp, inserted_at) as latency_ms
12
- FROM analytics_events
13
- WHERE
14
- JSONExtractString(payload, 'meta', 'received_timestamp') != ''
10
+ received_at,
11
+ ingestion_latency_ms
12
+ FROM _mv_hits
13
+ WHERE
14
+ -- ingestion_latency_ms is set to -1 if received_at is invalid
15
+ ingestion_latency_ms >= 0
15
16
  {% if defined(start_date) %}
16
17
  AND toDate(inserted_at) >= {{ Date(start_date, description="Start date for filtering", required=False) }}
17
18
  {% else %}
@@ -29,16 +30,16 @@ SQL >
29
30
  NODE ingestion_metrics
30
31
  SQL >
31
32
  %
32
- SELECT
33
+ SELECT
33
34
  {% if defined(site_uuid) %}
34
35
  site_uuid,
35
36
  {% end %}
36
37
  count() as total_events,
37
- round(avg(latency_ms)) as avg_latency_ms,
38
- round(quantile(0.5)(latency_ms)) as p50_latency_ms,
39
- round(quantile(0.95)(latency_ms)) as p95_latency_ms,
40
- round(min(latency_ms)) as min_latency_ms,
41
- round(max(latency_ms)) as max_latency_ms
38
+ round(avg(ingestion_latency_ms)) as avg_latency_ms,
39
+ round(quantile(0.5)(ingestion_latency_ms)) as p50_latency_ms,
40
+ round(quantile(0.95)(ingestion_latency_ms)) as p95_latency_ms,
41
+ round(min(ingestion_latency_ms)) as min_latency_ms,
42
+ round(max(ingestion_latency_ms)) as max_latency_ms
42
43
  FROM ingestion_latency
43
44
  {% if defined(site_uuid) %}
44
45
  GROUP BY site_uuid
@@ -13,24 +13,6 @@ SQL >
13
13
  on fs.session_id = h.session_id
14
14
  where
15
15
  site_uuid = {{String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True)}}
16
- {% if defined(date_from) %}
17
- and toDate(toTimezone(timestamp, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}}))
18
- >=
19
- {{ Date(date_from, description="Starting day for filtering a date range", required=False) }}
20
- {% else %}
21
- and toDate(toTimezone(timestamp, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}}))
22
- >=
23
- timestampAdd(today(), interval -7 day)
24
- {% end %}
25
- {% if defined(date_to) %}
26
- and toDate(toTimezone(timestamp, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}}))
27
- <=
28
- {{ Date(date_to, description="Finishing day for filtering a date range", required=False) }}
29
- {% else %}
30
- and toDate(toTimezone(timestamp, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}}))
31
- <=
32
- today()
33
- {% end %}
34
16
  {% if defined(member_status) %}
35
17
  and member_status IN (
36
18
  select arrayJoin(
@@ -39,7 +21,6 @@ SQL >
39
21
  )
40
22
  )
41
23
  {% end %}
42
- {% if defined(source) %} and source = {{ String(source, description="Source to filter on", required=False) }} {% end %}
43
24
  {% if defined(location) %} and location = {{ String(location, description="Location to filter on", required=False) }} {% end %}
44
25
  {% if defined(pathname) %} and pathname = {{ String(pathname, description="Pathname to filter on", required=False) }} {% end %}
45
26
  {% if defined(post_uuid) %} and post_uuid = {{ String(post_uuid, description="Post UUID to filter on", required=False) }} {% end %}
@@ -14,24 +14,6 @@ SQL >
14
14
  on fs.session_id = h.session_id
15
15
  where
16
16
  site_uuid = {{String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True)}}
17
- {% if defined(date_from) %}
18
- and toDate(toTimezone(timestamp, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}}))
19
- >=
20
- {{ Date(date_from, description="Starting day for filtering a date range", required=False) }}
21
- {% else %}
22
- and toDate(toTimezone(timestamp, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}}))
23
- >=
24
- timestampAdd(today(), interval -7 day)
25
- {% end %}
26
- {% if defined(date_to) %}
27
- and toDate(toTimezone(timestamp, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}}))
28
- <=
29
- {{ Date(date_to, description="Finishing day for filtering a date range", required=False) }}
30
- {% else %}
31
- and toDate(toTimezone(timestamp, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}}))
32
- <=
33
- today()
34
- {% end %}
35
17
  {% if defined(member_status) %}
36
18
  and member_status IN (
37
19
  select arrayJoin(
@@ -50,11 +32,6 @@ SQL >
50
32
  and (post_type != 'post' or post_type is null)
51
33
  {% end %}
52
34
  {% end %}
53
- {% if defined(utm_source) %} and utm_source = {{ String(utm_source, description="UTM source to filter on", required=False) }} {% end %}
54
- {% if defined(utm_medium) %} and utm_medium = {{ String(utm_medium, description="UTM medium to filter on", required=False) }} {% end %}
55
- {% if defined(utm_campaign) %} and utm_campaign = {{ String(utm_campaign, description="UTM campaign to filter on", required=False) }} {% end %}
56
- {% if defined(utm_term) %} and utm_term = {{ String(utm_term, description="UTM term to filter on", required=False) }} {% end %}
57
- {% if defined(utm_content) %} and utm_content = {{ String(utm_content, description="UTM content to filter on", required=False) }} {% end %}
58
35
  group by post_uuid, pathname
59
36
  order by visits desc
60
37
  limit {{ Int32(skip, 0) }},{{ Int32(limit, 50) }}
@@ -12,17 +12,6 @@ SQL >
12
12
  on fs.session_id = sd.session_id
13
13
  where
14
14
  site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }}
15
- {% if defined(date_from) and day_diff(date_from, date_to) == 0 %}
16
- and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) = {{ Date(date_from) }}
17
- {% else %}
18
- {% if defined(date_from) %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) >= {{ Date(date_from) }} {% else %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) >= timestampAdd(today(), interval -7 day) {% end %}
19
- {% if defined(date_to) %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) <= {{ Date(date_to) }} {% else %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) <= today() {% end %}
20
- {% end %}
21
- {% if defined(utm_source) %} and utm_source = {{ String(utm_source, description="UTM source to filter on", required=False) }} {% end %}
22
- {% if defined(utm_medium) %} and utm_medium = {{ String(utm_medium, description="UTM medium to filter on", required=False) }} {% end %}
23
- {% if defined(utm_campaign) %} and utm_campaign = {{ String(utm_campaign, description="UTM campaign to filter on", required=False) }} {% end %}
24
- {% if defined(utm_term) %} and utm_term = {{ String(utm_term, description="UTM term to filter on", required=False) }} {% end %}
25
- {% if defined(utm_content) %} and utm_content = {{ String(utm_content, description="UTM content to filter on", required=False) }} {% end %}
26
15
  group by source
27
16
  order by visits desc
28
17
  limit {{ Int32(skip, 0) }},{{ Int32(limit, 50) }}
@@ -13,12 +13,6 @@ SQL >
13
13
  where
14
14
  site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }}
15
15
  and utm_campaign != ''
16
- {% if defined(date_from) and day_diff(date_from, date_to) == 0 %}
17
- and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) = {{ Date(date_from) }}
18
- {% else %}
19
- {% if defined(date_from) %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) >= {{ Date(date_from) }} {% else %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) >= timestampAdd(today(), interval -7 day) {% end %}
20
- {% if defined(date_to) %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) <= {{ Date(date_to) }} {% else %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) <= today() {% end %}
21
- {% end %}
22
16
  group by utm_campaign
23
17
  order by visits desc
24
18
  limit {{ Int32(skip, 0) }},{{ Int32(limit, 50) }}
@@ -13,12 +13,6 @@ SQL >
13
13
  where
14
14
  site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }}
15
15
  and utm_content != ''
16
- {% if defined(date_from) and day_diff(date_from, date_to) == 0 %}
17
- and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) = {{ Date(date_from) }}
18
- {% else %}
19
- {% if defined(date_from) %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) >= {{ Date(date_from) }} {% else %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) >= timestampAdd(today(), interval -7 day) {% end %}
20
- {% if defined(date_to) %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) <= {{ Date(date_to) }} {% else %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) <= today() {% end %}
21
- {% end %}
22
16
  group by utm_content
23
17
  order by visits desc
24
18
  limit {{ Int32(skip, 0) }},{{ Int32(limit, 50) }}
@@ -13,12 +13,6 @@ SQL >
13
13
  where
14
14
  site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }}
15
15
  and utm_medium != ''
16
- {% if defined(date_from) and day_diff(date_from, date_to) == 0 %}
17
- and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) = {{ Date(date_from) }}
18
- {% else %}
19
- {% if defined(date_from) %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) >= {{ Date(date_from) }} {% else %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) >= timestampAdd(today(), interval -7 day) {% end %}
20
- {% if defined(date_to) %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) <= {{ Date(date_to) }} {% else %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) <= today() {% end %}
21
- {% end %}
22
16
  group by utm_medium
23
17
  order by visits desc
24
18
  limit {{ Int32(skip, 0) }},{{ Int32(limit, 50) }}
@@ -13,12 +13,6 @@ SQL >
13
13
  where
14
14
  site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }}
15
15
  and utm_source != ''
16
- {% if defined(date_from) and day_diff(date_from, date_to) == 0 %}
17
- and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) = {{ Date(date_from) }}
18
- {% else %}
19
- {% if defined(date_from) %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) >= {{ Date(date_from) }} {% else %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) >= timestampAdd(today(), interval -7 day) {% end %}
20
- {% if defined(date_to) %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) <= {{ Date(date_to) }} {% else %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) <= today() {% end %}
21
- {% end %}
22
16
  group by utm_source
23
17
  order by visits desc
24
18
  limit {{ Int32(skip, 0) }},{{ Int32(limit, 50) }}
@@ -13,12 +13,6 @@ SQL >
13
13
  where
14
14
  site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }}
15
15
  and utm_term != ''
16
- {% if defined(date_from) and day_diff(date_from, date_to) == 0 %}
17
- and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) = {{ Date(date_from) }}
18
- {% else %}
19
- {% if defined(date_from) %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) >= {{ Date(date_from) }} {% else %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) >= timestampAdd(today(), interval -7 day) {% end %}
20
- {% if defined(date_to) %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) <= {{ Date(date_to) }} {% else %} and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) <= today() {% end %}
21
- {% end %}
22
16
  group by utm_term
23
17
  order by visits desc
24
18
  limit {{ Int32(skip, 0) }},{{ Int32(limit, 50) }}
@@ -1,8 +1,10 @@
1
1
  TOKEN "axis" READ
2
2
 
3
- NODE query_filters
3
+ NODE sessions_filtered_by_hit_attributes
4
4
  DESCRIPTION >
5
- Get sessions that match the filter criteria
5
+ Get sessions where at least one hit matches the hit-level filter criteria.
6
+ Hit-level filters (pathname, post_uuid, member_status, location) can vary across pageviews within a session.
7
+ A session qualifies if ANY of its hits match the specified criteria.
6
8
 
7
9
  SQL >
8
10
  %
@@ -20,22 +22,40 @@ SQL >
20
22
  )
21
23
  )
22
24
  {% end %}
23
- {% if defined(source) %} and source = {{ String(source, description="Source to filter on", required=False) }} {% end %}
24
25
  {% if defined(location) %} and location = {{ String(location, description="Location to filter on", required=False) }} {% end %}
25
26
  {% if defined(pathname) %} and pathname = {{ String(pathname, description="Pathname to filter on", required=False) }} {% end %}
26
- {% if defined (post_uuid) %} and post_uuid = {{ String(post_uuid, description="Post UUID to filter on", required=False) }} {% end %}
27
+ {% if defined(post_uuid) %} and post_uuid = {{ String(post_uuid, description="Post UUID to filter on", required=False) }} {% end %}
27
28
 
28
- NODE sessions_filtered_by_source
29
+ NODE sessions_filtered_by_session_attributes
29
30
  DESCRIPTION >
30
- Further filter by source when using the source filter. This is necessary because the source is specific to the first hit in a session,
31
+ Further filter by session-level attributes (source, utm_*). This is necessary because these attributes are specific to the first hit in a session,
31
32
  whereas other filters are on hits.
32
33
 
33
34
  SQL >
34
35
  %
35
36
  select session_id
36
37
  from mv_session_data sd
37
- inner join query_filters qf
38
- on qf.session_id = sd.session_id
38
+ inner join sessions_filtered_by_hit_attributes sfha
39
+ on sfha.session_id = sd.session_id
39
40
  where
40
41
  site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }}
42
+ {% if defined(date_from) %}
43
+ {# Filter from specified start date #}
44
+ and toDate(toTimezone(first_pageview,{{ String(timezone, 'Etc/UTC', description="Site timezone", required=True) }})) >= {{ Date(date_from) }}
45
+ {% else %}
46
+ {# Default to last 7 days if no start date provided #}
47
+ and toDate(toTimezone(first_pageview,{{ String(timezone, 'Etc/UTC', description="Site timezone", required=True) }})) >= timestampAdd(today(), interval -7 day)
48
+ {% end %}
49
+ {% if defined(date_to) %}
50
+ {# Filter to specified end date #}
51
+ and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) <= {{ Date(date_to) }}
52
+ {% else %}
53
+ {# Default to today if no end date provided #}
54
+ and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) <= today()
55
+ {% end %}
41
56
  {% if defined(source) %} and source = {{ String(source, description="Source to filter on", required=False) }} {% end %}
57
+ {% if defined(utm_source) %} and utm_source = {{ String(utm_source, description="UTM source to filter on", required=False) }} {% end %}
58
+ {% if defined(utm_medium) %} and utm_medium = {{ String(utm_medium, description="UTM medium to filter on", required=False) }} {% end %}
59
+ {% if defined(utm_campaign) %} and utm_campaign = {{ String(utm_campaign, description="UTM campaign to filter on", required=False) }} {% end %}
60
+ {% if defined(utm_term) %} and utm_term = {{ String(utm_term, description="UTM term to filter on", required=False) }} {% end %}
61
+ {% if defined(utm_content) %} and utm_content = {{ String(utm_content, description="UTM content to filter on", required=False) }} {% end %}
@@ -4,6 +4,10 @@ NODE mv_hits_0
4
4
  SQL >
5
5
 
6
6
  SELECT timestamp,
7
+ inserted_at,
8
+ -- payload.meta.received_timestamp may be missing or null
9
+ -- Nullable fields incur performance penalty in clickhouse, so we default to zero (unix epoch) instead of null.
10
+ parseDateTime64BestEffortOrZero(JSONExtractString(payload, 'meta', 'received_timestamp'), 3) as received_at,
7
11
  action,
8
12
  version,
9
13
  coalesce(session_id, '0') as session_id,
@@ -37,6 +41,14 @@ SQL >
37
41
  SELECT
38
42
  site_uuid,
39
43
  timestamp,
44
+ received_at,
45
+ inserted_at,
46
+ case
47
+ -- set to -1 if received_at is the unix epoch, or if received_at is after inserted_at
48
+ when toUnixTimestamp(received_at) = 0 then -1
49
+ when received_at > inserted_at then -1
50
+ else date_diff('millisecond', received_at, inserted_at)
51
+ end as ingestion_latency_ms,
40
52
  action,
41
53
  version,
42
54
  session_id,
@@ -54,20 +66,20 @@ SQL >
54
66
  when referrer IN ('Instagram', 'www.instagram.com') then 'Instagram'
55
67
  when referrer IN ('LinkedIn', 'LINKEDIN_COMPANY') then 'LinkedIn'
56
68
  when referrer IN ('l.threads.com') then 'Threads'
57
-
69
+
58
70
  -- Reddit Ecosystem
59
71
  when referrer IN ('www.reddit.com', 'out.reddit.com', 'old.reddit.com', 'com.reddit.frontpage') then 'Reddit'
60
-
72
+
61
73
  -- Search Engines (keep distinctions)
62
74
  when referrer IN ('search.brave.com') then 'Brave Search'
63
75
  when referrer IN ('www.ecosia.org') then 'Ecosia'
64
-
76
+
65
77
  -- Email Services
66
78
  when referrer IN ('Gmail', 'com.google.android.gm', 'mail.google.com') then 'Gmail'
67
79
  when referrer IN ('Outlook.com') then 'Outlook'
68
80
  when referrer IN ('Yahoo!', 'www.yahoo.com', 'Yahoo! Mail', 'r.search.yahoo.com') then 'Yahoo!'
69
81
  when referrer IN ('AOL Mail') then 'AOL Mail'
70
-
82
+
71
83
  -- Content Platforms
72
84
  when referrer IN ('flipboard', 'flipboard.com', 'flipboard.app') then 'Flipboard'
73
85
  when referrer IN ('substack', 'substack.com') then 'Substack'
@@ -75,22 +87,22 @@ SQL >
75
87
  when referrer IN ('buffer') then 'Buffer'
76
88
  when referrer IN ('Taboola') then 'Taboola'
77
89
  when referrer IN ('AppNexus') then 'AppNexus'
78
-
90
+
79
91
  -- Wikipedia
80
92
  when referrer IN ('en.wikipedia.org', 'en.m.wikipedia.org') then 'Wikipedia'
81
-
93
+
82
94
  -- Mastodon Network
83
95
  when referrer IN ('mastodon.social', 'mastodon.online', 'org.joinmastodon.android', 'phanpy.social', 'dev.phanpy.social') then 'Mastodon'
84
-
96
+
85
97
  -- News Aggregators
86
98
  when referrer IN ('www.memeorandum.com', 'memeorandum.com') then 'Memeorandum'
87
99
  when referrer IN ('ground.news') then 'Ground News'
88
100
  when referrer IN ('apple.news') then 'Apple News'
89
101
  when referrer IN ('www.smartnews.com') then 'SmartNews'
90
-
102
+
91
103
  -- Keep other sources as-is
92
- when domainWithoutWWW(referrer) != '' then domainWithoutWWW(referrer)
93
- else referrer
104
+ when domainWithoutWWW(referrer) != '' then domainWithoutWWW(referrer)
105
+ else referrer
94
106
  end as source,
95
107
  pathname,
96
108
  href,
@@ -137,4 +149,4 @@ SQL >
137
149
  FROM mv_hits_0
138
150
 
139
151
  TYPE materialized
140
- DATASOURCE _mv_hits
152
+ DATASOURCE _mv_hits
@@ -94,19 +94,22 @@
94
94
  parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC&utm_medium=social
95
95
  expected_result: |
96
96
  {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1fc","pathname":"\/about\/","visits":3}
97
- {"post_uuid":"","pathname":"\/","visits":2}
97
+ {"post_uuid":"","pathname":"\/","visits":3}
98
+ {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":2}
98
99
 
99
100
  - name: Filtered by utm_campaign - brand_awareness
100
101
  description: Filtered by utm_campaign - brand_awareness
101
102
  parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC&utm_campaign=brand_awareness
102
103
  expected_result: |
103
- {"post_uuid":"","pathname":"\/","visits":1}
104
+ {"post_uuid":"","pathname":"\/","visits":2}
105
+ {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1fc","pathname":"\/about\/","visits":1}
104
106
  {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":1}
105
107
 
106
108
  - name: Filtered by utm_term - discount
107
109
  description: Filtered by utm_term - discount
108
110
  parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC&utm_term=discount
109
111
  expected_result: |
112
+ {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1fc","pathname":"\/about\/","visits":1}
110
113
  {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":1}
111
114
 
112
115
  - name: Filtered by utm_content - post_123