From e93636e1d8df84620fafc76247a74611648c3d49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Federico=20D=27Eredit=C3=A0?= Date: Tue, 9 Sep 2025 09:13:57 +0200 Subject: [PATCH] feat(queries): add `BOUNCE_THRESHOLD` and enhance bounce calculation logic Introduced `BOUNCE_THRESHOLD` constant for configurable bounce definitions. Updated bounce calculation logic in `getWebsiteStats` and `getInsights` to use `events_count`. --- docker-compose.yml | 1 + src/lib/constants.ts | 4 ++++ src/queries/sql/getWebsiteStats.ts | 20 ++++++++++---------- src/queries/sql/reports/getInsights.ts | 14 +++++++------- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 7b51db66..0a57142d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,7 @@ services: DATABASE_URL: postgresql://umami:umami@db:5432/umami DATABASE_TYPE: postgresql APP_SECRET: replace-me-with-a-random-string + # BOUNCE_THRESHOLD: 2 # OPTIONAL: set the minimum amount custom events to trigger a bounce depends_on: db: condition: service_healthy diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 2718c135..f3e73233 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,4 +1,8 @@ export const CURRENT_VERSION = process.env.currentVersion; +export const BOUNCE_THRESHOLD = Math.max( + 1, + Number.parseInt(process.env.BOUNCE_THRESHOLD || '1', 10) || 1, +); export const AUTH_TOKEN = 'umami.auth'; export const LOCALE_CONFIG = 'umami.locale'; export const TIMEZONE_CONFIG = 'umami.timezone'; diff --git a/src/queries/sql/getWebsiteStats.ts b/src/queries/sql/getWebsiteStats.ts index e13ff8f1..48e0612b 100644 --- a/src/queries/sql/getWebsiteStats.ts +++ b/src/queries/sql/getWebsiteStats.ts @@ -2,7 +2,7 @@ import clickhouse from '@/lib/clickhouse'; import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db'; import prisma from '@/lib/prisma'; import { QueryFilters } from '@/lib/types'; -import { EVENT_COLUMNS, EVENT_TYPE } from '@/lib/constants'; +import { BOUNCE_THRESHOLD, EVENT_COLUMNS, EVENT_TYPE } from '@/lib/constants'; export async function getWebsiteStats( ...args: [websiteId: string, filters: QueryFilters] @@ -33,7 +33,7 @@ async function relationalQuery( sum(t.c) as "pageviews", count(distinct t.session_id) as "visitors", count(distinct t.visit_id) as "visits", - sum(case when t.c = 1 and t.has_event = 0 then 1 else 0 end) as "bounces", + sum(case when t.c = 1 and t.events_count < ${BOUNCE_THRESHOLD} then 1 else 0 end) as "bounces", sum(${getTimestampDiffSQL('t.min_time', 't.max_time')}) as "totaltime" from ( select @@ -42,14 +42,14 @@ async function relationalQuery( sum(case when website_event.event_type = ${EVENT_TYPE.pageView} then 1 else 0 end) as "c", min(website_event.created_at) as "min_time", max(website_event.created_at) as "max_time", - max(case when exists ( - select 1 + max(( + select count(*) from website_event we2 where we2.website_id = website_event.website_id and we2.session_id = website_event.session_id and we2.created_at between {{startDate}} and {{endDate}} and we2.event_type = ${EVENT_TYPE.customEvent} - ) then 1 else 0 end) as "has_event" + )) as "events_count" from website_event ${cohortQuery} ${joinSession} @@ -83,7 +83,7 @@ async function clickhouseQuery( sum(t.c) as "pageviews", uniq(t.session_id) as "visitors", uniq(t.visit_id) as "visits", - sumIf(1, t.c = 1 and ifNull(e.has_event, 0) = 0) as "bounces", + sumIf(1, t.c = 1 and ifNull(e.events_count, 0) < ${BOUNCE_THRESHOLD}) as "bounces", sum(max_time-min_time) as "totaltime" from ( select @@ -99,8 +99,8 @@ async function clickhouseQuery( ${filterQuery} group by session_id, visit_id ) as t - left join ( - select session_id, toUInt8(count() > 0) as has_event + left join ( + select session_id, toUInt32(count()) as events_count from website_event where website_id = {websiteId:UUID} and created_at between {startDate:DateTime64} and {endDate:DateTime64} @@ -114,7 +114,7 @@ async function clickhouseQuery( sum(t.c) as "pageviews", uniq(session_id) as "visitors", uniq(visit_id) as "visits", - sumIf(1, t.c = 1 and ifNull(e.has_event, 0) = 0) as "bounces", + sumIf(1, t.c = 1 and ifNull(e.events_count, 0) < ${BOUNCE_THRESHOLD}) as "bounces", sum(max_time-min_time) as "totaltime" from (select session_id, @@ -130,7 +130,7 @@ async function clickhouseQuery( group by session_id, visit_id ) as t left join ( - select session_id, toUInt8(sumIf(views, event_type = ${EVENT_TYPE.customEvent}) > 0) as has_event + select session_id, toUInt32(sumIf(views, event_type = ${EVENT_TYPE.customEvent})) as events_count from website_event_stats_hourly where website_id = {websiteId:UUID} and created_at between {startDate:DateTime64} and {endDate:DateTime64} diff --git a/src/queries/sql/reports/getInsights.ts b/src/queries/sql/reports/getInsights.ts index 2eed9649..4b3d78a2 100644 --- a/src/queries/sql/reports/getInsights.ts +++ b/src/queries/sql/reports/getInsights.ts @@ -1,7 +1,7 @@ import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db'; import prisma from '@/lib/prisma'; import clickhouse from '@/lib/clickhouse'; -import { EVENT_TYPE, FILTER_COLUMNS, SESSION_COLUMNS } from '@/lib/constants'; +import { BOUNCE_THRESHOLD, EVENT_TYPE, FILTER_COLUMNS, SESSION_COLUMNS } from '@/lib/constants'; import { QueryFilters } from '@/lib/types'; export async function getInsights( @@ -41,7 +41,7 @@ async function relationalQuery( sum(t.c) as "views", count(distinct t.session_id) as "visitors", count(distinct t.visit_id) as "visits", - sum(case when t.c = 1 and t.has_events = 0 then 1 else 0 end) as "bounces", + sum(case when t.c = 1 and t.events_count < ${BOUNCE_THRESHOLD} then 1 else 0 end) as "bounces", sum(${getTimestampDiffSQL('t.min_time', 't.max_time')}) as "totaltime", ${parseFieldsByName(fields)} from ( @@ -52,14 +52,14 @@ async function relationalQuery( sum(case when website_event.event_type = ${EVENT_TYPE.pageView} then 1 else 0 end) as "c", min(website_event.created_at) as "min_time", max(website_event.created_at) as "max_time", - max(case when exists ( - select 1 + max(( + select count(*) from website_event we2 where we2.website_id = website_event.website_id and we2.session_id = website_event.session_id and we2.created_at between {{startDate}} and {{endDate}} and we2.event_type = ${EVENT_TYPE.customEvent} - ) then 1 else 0 end) as "has_events" + )) as "events_count" from website_event ${cohortQuery} ${joinSession} @@ -98,7 +98,7 @@ async function clickhouseQuery( sum(t.c) as "views", count(distinct t.session_id) as "visitors", count(distinct t.visit_id) as "visits", - sumIf(1, t.c = 1 and ifNull(e.has_event, 0) = 0) as "bounces", + sumIf(1, t.c = 1 and ifNull(e.events_count, 0) < ${BOUNCE_THRESHOLD}) as "bounces", sum(max_time-min_time) as "totaltime", ${parseFieldsByName(fields)} from ( @@ -118,7 +118,7 @@ async function clickhouseQuery( session_id, visit_id ) as t left join ( - select session_id, toUInt8(count() > 0) as has_event + select session_id, toUInt32(count()) as events_count from website_event where website_id = {websiteId:UUID} and created_at between {startDate:DateTime64} and {endDate:DateTime64}