-
-
Notifications
You must be signed in to change notification settings - Fork 11.3k
Added materialized view and duplicated v2 Tinybird endpoints #25719
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7549e5e
a2d818f
c3c70b9
a0b5716
2f11d9c
faa9745
1a5c2ef
3eb42a2
a2c09b0
87d6b67
6f3c56c
a2460c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| SCHEMA > | ||
| `site_uuid` LowCardinality(String), | ||
| `session_id` String, | ||
| `pageviews` AggregateFunction(count, UInt64), | ||
| `first_pageview` AggregateFunction(min, DateTime), | ||
| `last_pageview` AggregateFunction(max, DateTime), | ||
| `source` AggregateFunction(argMin, String, DateTime), | ||
| `device` AggregateFunction(argMin, String, DateTime), | ||
| `utm_source` AggregateFunction(argMin, String, DateTime), | ||
| `utm_medium` AggregateFunction(argMin, String, DateTime), | ||
| `utm_campaign` AggregateFunction(argMin, String, DateTime), | ||
| `utm_term` AggregateFunction(argMin, String, DateTime), | ||
| `utm_content` AggregateFunction(argMin, String, DateTime) | ||
|
|
||
| ENGINE "AggregatingMergeTree" | ||
| ENGINE_SORTING_KEY "site_uuid, session_id" | ||
|
Comment on lines
+1
to
+16
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file is new, so there is no diff to compare against the previous version of it. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| TOKEN "stats_page" READ | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No changes to this pipe compared to the original. Created duplicate |
||
| TOKEN "axis" READ | ||
|
|
||
| NODE _active_visitors_0 | ||
| SQL > | ||
| % | ||
| select | ||
| uniqExact(session_id) as active_visitors | ||
| from _mv_hits | ||
| where | ||
| site_uuid = {{String(site_uuid, 'mock_site_uuid', description="Site UUID", required=True)}} | ||
| and timestamp >= (now() - interval 5 minute) | ||
| {% if defined(post_uuid) %} and post_uuid = {{ String(post_uuid, description="Post UUID to filter on", required=False) }} {% end %} | ||
|
|
||
| TYPE ENDPOINT | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| TOKEN "stats_page" READ | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| TOKEN "axis" READ | ||
|
|
||
| NODE timeseries | ||
| SQL > | ||
|
|
||
| % | ||
| {% set _single_day = defined(date_from) and day_diff(date_from, date_to) == 0 %} | ||
| with | ||
| {% if defined(date_from) %} | ||
| toStartOfDay( | ||
| toDate( | ||
| {{ | ||
| Date( | ||
| date_from, | ||
| description="Starting day for filtering a date range", | ||
| required=False, | ||
| ) | ||
| }} | ||
| ) | ||
| ) as start, | ||
| {% else %} toStartOfDay(timestampAdd(today(), interval -7 day)) as start, | ||
| {% end %} | ||
| {% if defined(date_to) %} | ||
| toStartOfDay( | ||
| toDate( | ||
| {{ | ||
| Date( | ||
| date_to, | ||
| description="Finishing day for filtering a date range", | ||
| required=False, | ||
| ) | ||
| }} | ||
| ) | ||
| ) as end | ||
| {% else %} toStartOfDay(today()) as end | ||
| {% end %} | ||
| {% if _single_day %} | ||
| select | ||
| arrayJoin( | ||
| arrayMap( | ||
| x -> toDateTime(toString(toDateTime(x)), {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}}), | ||
| range( | ||
| toUInt32(toDateTime(start)), toUInt32(timestampAdd(end, interval 1 day)), 3600 | ||
| ) | ||
| ) | ||
| ) as date | ||
| {% else %} | ||
| select | ||
| arrayJoin( | ||
| arrayMap( | ||
| x -> toDate(x), | ||
| range(toUInt32(start), toUInt32(timestampAdd(end, interval 1 day)), 24 * 3600) | ||
| ) | ||
| ) as date | ||
| {% end %} | ||
|
|
||
|
|
||
| NODE session_data | ||
| DESCRIPTION > | ||
| Read session data from AggregatingMergeTree MV using -Merge combinators | ||
|
|
||
| SQL > | ||
| % | ||
| SELECT | ||
| site_uuid, | ||
| session_id, | ||
| countMerge(pageviews) as pageviews, | ||
| minMerge(first_pageview) as first_pageview, | ||
| maxMerge(last_pageview) as last_pageview | ||
| FROM _mv_session_data_v2 | ||
| WHERE site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }} | ||
| GROUP BY site_uuid, session_id | ||
|
|
||
| NODE session_metrics | ||
| DESCRIPTION > | ||
| Calculate session-level metrics (visits, pageviews, bounce rate, avg session duration) | ||
|
|
||
| SQL > | ||
|
|
||
| % | ||
| select | ||
| site_uuid, | ||
| {% if defined(date_from) and day_diff(date_from, date_to) == 0 %} | ||
| toStartOfHour(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) as date, | ||
| {% else %} | ||
| toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) as date, | ||
| {% end %} | ||
| sd.session_id, | ||
| pageviews, | ||
| pageviews = 1 as is_bounce, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this caught my eye at first and i was about to ask about it - but i see it comes from what was originally in
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah we could probably remove this because we don't show bounce rate anywhere in the UI anymore, but we can handle that separately 🙂
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe John is actually using bounce rate in axis. We need to revisit Tinybird use outside of Ghost at some point soon. |
||
| last_pageview - first_pageview as session_sec | ||
| from session_data sd | ||
| inner join filtered_sessions_v2 fs | ||
| on fs.session_id = sd.session_id | ||
|
|
||
|
|
||
| NODE data | ||
| DESCRIPTION > | ||
| Calculate KPIs per time period | ||
|
|
||
| SQL > | ||
|
|
||
| select | ||
| a.date, | ||
| uniq(distinct s.session_id) as visits, | ||
| sum(s.pageviews) as pageviews, | ||
| truncate(avg(s.is_bounce), 2) as bounce_rate, | ||
| truncate(avg(s.session_sec), 2) as avg_session_sec | ||
| from timeseries a | ||
| inner join session_metrics s on a.date = s.date | ||
| group by a.date | ||
| order by a.date | ||
|
|
||
|
|
||
| NODE pathname_pageviews | ||
| DESCRIPTION > | ||
| Calculate pageviews for specific pathname with time granularity handling | ||
|
|
||
| SQL > | ||
|
|
||
| % | ||
| select | ||
| {% if defined(date_from) and day_diff(date_from, date_to) == 0 %} | ||
| toStartOfHour(toTimezone(timestamp, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) as date, | ||
| {% else %} | ||
| toDate(toTimezone(timestamp, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) as date, | ||
| {% end %} | ||
| count() pageviews | ||
| from timeseries a | ||
| inner join _mv_hits h on | ||
| {% if defined(date_from) and day_diff(date_from, date_to) == 0 %} | ||
| a.date = toStartOfHour(toTimezone(timestamp, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) | ||
| {% else %} | ||
| a.date = toDate(toTimezone(timestamp, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) | ||
| {% end %} | ||
| inner join filtered_sessions_v2 fs | ||
| on fs.session_id = h.session_id | ||
| where | ||
| site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }} | ||
| {% if defined(member_status) %} and member_status IN {{ Array(member_status, "'undefined', 'free', 'paid'", description="Member status to filter on", required=False) }} {% end %} | ||
| {% if defined(location) %} and location = {{ String(location, description="Location to filter on", required=False) }} {% end %} | ||
| {% if defined(pathname) %} and pathname = {{ String(pathname, description="Pathname to filter on", required=False) }} {% end %} | ||
| {% if defined(post_uuid) %} and post_uuid = {{String(post_uuid, description="Post UUID to filter on", required=False) }} {% end %} | ||
| group by date | ||
| order by date | ||
|
|
||
|
|
||
| NODE finished_data | ||
| SQL > | ||
|
|
||
| % | ||
| select | ||
| a.date as date, | ||
| coalesce(b.visits, 0) as visits, | ||
| {% if defined(pathname) or defined(post_uuid) %}coalesce(c.pageviews, 0){% else %}coalesce(b.pageviews, 0){% end %} as pageviews, | ||
| coalesce(b.bounce_rate, 0) as bounce_rate, | ||
| coalesce(b.avg_session_sec, 0) as avg_session_sec | ||
| from timeseries a | ||
| left join data b on a.date = b.date | ||
| {% if defined(pathname) or defined(post_uuid) %}left join pathname_pageviews c on a.date = c.date{% end %} | ||
| TYPE ENDPOINT | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| TOKEN "stats_page" READ | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No changes in this pipe compared to the original, since this only uses _mv_hits. Created v2 for consistency so we can configure Ghost to use v2. |
||
| TOKEN "axis" READ | ||
|
|
||
| NODE _post_visitor_counts_0 | ||
| SQL > | ||
| % | ||
| select | ||
| post_uuid, | ||
| uniqExact(session_id) as visits | ||
| from _mv_hits | ||
| where | ||
| site_uuid = {{String(site_uuid, 'mock_site_uuid', description="Site UUID", required=True)}} | ||
| and post_uuid IN {{ Array(post_uuids, description="Array of post UUIDs to get visitor counts for", required=True) }} | ||
| group by post_uuid | ||
| order by visits desc | ||
|
|
||
| TYPE ENDPOINT | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| TOKEN "stats_page" READ | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| TOKEN "axis" READ | ||
|
|
||
| NODE session_data | ||
| SQL > | ||
| % | ||
| SELECT | ||
| site_uuid, | ||
| session_id, | ||
| argMinMerge(device) as device | ||
| FROM _mv_session_data_v2 | ||
| WHERE site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }} | ||
| GROUP BY site_uuid, session_id | ||
|
|
||
| NODE top_devices | ||
| SQL > | ||
| % | ||
| select | ||
| device, | ||
| count() as visits | ||
| from session_data sd | ||
| inner join filtered_sessions_v2 fs | ||
| on fs.session_id = sd.session_id | ||
| group by device | ||
| order by visits desc | ||
| limit {{ Int32(skip, 0) }},{{ Int32(limit, 50) }} | ||
|
|
||
| TYPE ENDPOINT | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| TOKEN "stats_page" READ | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| TOKEN "axis" READ | ||
|
|
||
| NODE _top_locations_0 | ||
| SQL > | ||
|
|
||
| % | ||
| select | ||
| location, | ||
| uniqExact(session_id) as visits | ||
| from _mv_hits h | ||
| inner join filtered_sessions_v2 fs | ||
| on fs.session_id = h.session_id | ||
| where | ||
| site_uuid = {{String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True)}} | ||
| {% if defined(member_status) %} | ||
| and member_status IN ( | ||
| select arrayJoin( | ||
| {{ Array(member_status, "'undefined', 'free', 'paid'", description="Member status to filter on", required=False) }} | ||
| || if('paid' IN {{ Array(member_status) }}, ['comped'], []) | ||
| ) | ||
| ) | ||
| {% end %} | ||
| {% if defined(location) %} and location = {{ String(location, description="Location to filter on", required=False) }} {% end %} | ||
| {% if defined(pathname) %} and pathname = {{ String(pathname, description="Pathname to filter on", required=False) }} {% end %} | ||
| {% if defined(post_uuid) %} and post_uuid = {{ String(post_uuid, description="Post UUID to filter on", required=False) }} {% end %} | ||
| group by location | ||
| order by visits desc | ||
| limit {{ Int32(skip, 0) }},{{ Int32(limit, 50) }} | ||
|
|
||
| TYPE ENDPOINT | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| TOKEN "stats_page" READ | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| TOKEN "axis" READ | ||
|
|
||
| NODE _top_pages_0 | ||
| SQL > | ||
|
|
||
| % | ||
| select | ||
| case when post_uuid = 'undefined' then '' else post_uuid end as post_uuid, | ||
| pathname, | ||
| uniqExact(session_id) as visits | ||
| from _mv_hits h | ||
| inner join filtered_sessions_v2 fs | ||
| on fs.session_id = h.session_id | ||
| where | ||
| site_uuid = {{String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True)}} | ||
| {% if defined(member_status) %} | ||
| and member_status IN ( | ||
| select arrayJoin( | ||
| {{ Array(member_status, "'undefined', 'free', 'paid'", description="Member status to filter on", required=False) }} | ||
| || if('paid' IN {{ Array(member_status) }}, ['comped'], []) | ||
| ) | ||
| ) | ||
| {% end %} | ||
| {% if defined(location) %} and location = {{ String(location, description="Location to filter on", required=False) }} {% end %} | ||
| {% if defined(pathname) %} and pathname = {{ String(pathname, description="Pathname to filter on", required=False) }} {% end %} | ||
| {% if defined(post_uuid) %} and post_uuid = {{ String(post_uuid, description="Post UUID to filter on", required=False) }} {% end %} | ||
| {% if defined(post_type) %} | ||
| {% if post_type == 'post' %} | ||
| and post_type = 'post' | ||
| {% else %} | ||
| and (post_type != 'post' or post_type is null) | ||
| {% end %} | ||
| {% end %} | ||
| group by post_uuid, pathname | ||
| order by visits desc | ||
| limit {{ Int32(skip, 0) }},{{ Int32(limit, 50) }} | ||
|
|
||
| TYPE ENDPOINT | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| TOKEN "stats_page" READ | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| TOKEN "axis" READ | ||
|
|
||
| NODE session_data | ||
| SQL > | ||
| % | ||
| SELECT | ||
| site_uuid, | ||
| session_id, | ||
| argMinMerge(source) as source | ||
| FROM _mv_session_data_v2 | ||
| WHERE site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }} | ||
| GROUP BY site_uuid, session_id | ||
|
|
||
| NODE top_sources | ||
| SQL > | ||
| % | ||
| select | ||
| source, | ||
| count() as visits | ||
| from session_data sd | ||
| inner join filtered_sessions_v2 fs | ||
| on fs.session_id = sd.session_id | ||
| group by source | ||
| order by visits desc | ||
| limit {{ Int32(skip, 0) }},{{ Int32(limit, 50) }} | ||
|
|
||
| TYPE ENDPOINT | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate version and endpointName inputs before string interpolation.
The code doesn't guard against edge cases where
config.versionis an empty string orendpointNameis undefined/null. An empty version would produce malformed URLs likeapi_kpis_.json.Consider adding validation:
📝 Committable suggestion
🤖 Prompt for AI Agents