Dashboard shows possible incorrect values
Describe the Bug
I'm a bit confused about the values/names the dashboard displays. For example, from the screenshot, the UI shows 748 "visitors" but the api return a key called "uniques".
The chart displays the Unique Visitors and Page Views. If you count the values for each time on the chart for the unique values, you ended up with 920.
It does not make sense to have more unique visitors than "total" visitors. I'm not sure if the "Visitors" should be relabeled to "Unique Visitors" in the summary and in the chart it should be called "Total Visitors"(or just "Visitors") or if its something else in the code.
Thank you
Database
PostgreSQL
Relevant log output
No response
Which browser are you using? (if relevant)
No response
How are you deploying your application? (if relevant)
No response
Both the summary and table are unique visitors, but the table is grouped into a time series, so you can have the same unique visitor across multiple hours causing the increased count. However I think your right, having one label say Unique visitors and the other Visitors can cause confusion.
Thanks @franciscao633 , Is there any way to get the total and table values for all visitors, not just unique?
My 2 cents:
Umami does not track Users per se, only Sessions grouped by IP/UserAgent and Page Views grouped by Sessions, so there's not really a distinction between Unique Visitors and Total Visits. Because people may be confused about what these somewhat technical terms mean, I think they should always be called "Visits" (unique Sessions) and "Views" (total Page Views) in the frontend.
I do think we can provide something like "Total Visits" vs "Unique Visits" though:
Google Analytics tracks individual users by fingerprinting them in many ways, most importantly by their logged in Google account. So if a Google user is logged in on multiple devices, they will have multiple sessions, but all associated to one user. So Google can count unique visitors (each user is counted once) and total visits (each session is counted once).
However, Umami doesn't track users, only sessions and pageViews. A session is identified by IP+UserAgent+WebsiteId+Hostname (sessionId). So if a user visits the website on multiple devices or browsers, they will have multiple sessions, but we don't know if they are the same user or not, so we can't differentiate between unique visitors and total visits like Google does.
We can however count sessions split by subunit periods of time, like days into hours. So we can define "unique visits" as the number of unique sessionIds in a unit and "total visits" as the sum of all unique sessionIds in subunits of a unit.
For example, if we're counting visits by day (unit) and a "user" (sessionId) visits the website in 3 different hours (subunits) of that day, then we count 1 unique visit and 3 total visits for that day. Then if another "user" visits the website in 2 different hours of that day, we count 2 unique visits and 5 total visits for that day and so on.
I implemented this "total visits" stat in my fork.
This is how it looks like in the src/queries/analytics/sessions/getSessionStats.ts query (y is unique, z is total):
let subqueryUnit = unit;
switch (unit) {
case 'hour':
// This might be pushing it. We're counting unique visits by hour
// and total visits by minute. If a user visits the website
// in 2 different minutes of the same hour, then we count
// 1 unique visit and 2 total visits for that hour...
subqueryUnit = 'minute';
break;
case 'day':
subqueryUnit = 'hour';
break;
case 'month':
subqueryUnit = 'day';
break;
case 'year':
subqueryUnit = 'month';
break;
}
const subquery = `
select g.u as unit, sum(g.c)::bigint as total from (
select
${getDateQuery('website_event.created_at', subqueryUnit, timezone)} as su,
${getDateQuery('website_event.created_at', unit, timezone)} as u,
count(distinct website_event.session_id) as c
from website_event
${joinSession}
where website_event.website_id = {{websiteId::uuid}}
and website_event.created_at between {{startDate}} and {{endDate}}
and event_type = {{eventType}}
${filterQuery}
group by su, u
) as g group by u
`;
const query = `
with subquery_cte as (${subquery})
select
m.x,
m.y,
(SELECT total FROM subquery_cte WHERE unit = m.x) as z
from (
select
${getDateQuery('website_event.created_at', unit, timezone)} x,
count(distinct website_event.session_id) y
from website_event
${joinSession}
where website_event.website_id = {{websiteId::uuid}}
and website_event.created_at between {{startDate}} and {{endDate}}
and event_type = {{eventType}}
${filterQuery}
group by x
) as m
`;
const result = await rawQuery(query, params);
return result;
And in the src/queries/analytics/getWebsiteStats.ts query:
return rawQuery(
`
select
sum(t.c) as "pageviews",
count(distinct t.session_id) as "uniques",
count(t.session_id) as "totalvisits",
sum(case when t.c = 1 then 1 else 0 end) as "bounces",
sum(t.time) as "totaltime"
from (
select
website_event.session_id,
${getDateQuery('website_event.created_at', 'hour')},
count(*) as c,
${getTimestampIntervalQuery('website_event.created_at')} as "time"
from website_event
join website
on website_event.website_id = website.website_id
${joinSession}
where website.website_id = {{websiteId::uuid}}
and website_event.created_at between {{startDate}} and {{endDate}}
and event_type = {{eventType}}
${filterQuery}
group by 1, 2
) as t
`,
params,
);
This issue is stale because it has been open for 60 days with no activity.
This is fixed in v2.10.0.