Repository navigation
feat(reports): add feature adoption report - #4636
rezarahiminia wants to merge 2 commits into
Conversation
Add a website report that measures product feature adoption: how many
active users use a given event-based feature, how many of them are new
versus returning, and (optionally) how many complete an intended
success action.
Why
---
Umami already answers "how many visitors/events" and has Funnel,
Retention and Cohorts for adjacent questions, but it does not answer
"which features do people actually adopt, is that growing, and do
first-time users stick?". This report targets that gap with a single,
simple view instead of requiring a hand-built funnel plus manual math.
Metrics
-------
- adoptionRate: feature users / active users in the range (denominator
is all unique visitors, not only the ones who saw the feature).
- featureUsers, newUsers, returningUsers: a user is "new" when their
first-ever occurrence of the feature (across all time) falls inside
the selected range, otherwise "returning".
- completedUsers / completionRate (optional): users that fire the
success event after the feature event within a configurable window
(default 60 minutes).
- series: per-bucket feature users split into new vs returning.
Identity uses session_id, consistent with the existing Retention report
(Umami derives a stable session id per visitor, so first-use detection
works across visits).
API
---
GET /api/websites/:websiteId/feature-adoption
feature (required) event name that marks feature usage
successEvent (optional) event name that marks completion
window (optional) completion window in minutes, 1..1440
plus the standard analytics params (startAt/endAt/period, timezone,
unit, segment/cohort and dimension filters).
Curated contract.ts is included so the operation is picked up by
pnpm openapi:contracts.
Implementation
--------------
- src/queries/sql/features/getFeatureAdoption.ts: Postgres + ClickHouse
implementations behind runQuery, using the shared parseFilters helpers.
- src/app/api/websites/[websiteId]/feature-adoption/{route,contract}.ts
- src/components/hooks/queries/useFeatureAdoptionQuery.ts (+ index export)
- src/app/(main)/websites/[websiteId]/(reports)/feature-adoption/
{page,FeatureAdoptionPage,FeatureAdoption}.tsx
- src/lib/analytics-schema.ts: featureAdoptionQuerySchema
- src/permissions/share.ts: new 'feature-adoption' share section
- src/components/hooks/useWebsiteNavItems.tsx + messages.ts + en-US.json
i18n
----
New keys under label: feature-adoption ("Adoption" in the menu),
feature-adoption-title ("Feature adoption" report title),
feature-adoption-description, adoption-rate, new-users,
returning-users, success-event. Only en-US is added; eloqnt reports
missing translations for other locales as warnings (non-strict CI).
Tests
-----
src/queries/sql/features/getFeatureAdoption.test.ts covers both
backends: totals + series assembly, derived adoption/returning/
completion, success-event completion window, and parameter passing.
Notes for reviewers
-------------------
Generated artifacts (public/openapi.json, packages/api-client generated
client, and contract.generated.ts files) are intentionally not part of
this diff to keep it focused. Regenerate before merge with:
pnpm openapi:generate
pnpm openapi:contracts
pnpm generate:api:client
|
Someone is attempting to deploy a commit to the Umami Software Team on Vercel. A member of the Team first needs to authorize it. |
[Medium impact] The change adds a bounded website analytics report and a new share-link section. Fix completion counting before merging; filtered reports can show inflated rates and successful retries can be missed. Findings
SummaryAdds a feature-adoption report with event selectors, summary counts, a time series, and PostgreSQL and ClickHouse queries.
|
| select website_event.session_id as session_id, | ||
| min(website_event.created_at) as start_at | ||
| from website_event | ||
| where website_event.website_id = {{websiteId::uuid}} | ||
| and website_event.created_at between {{startDate}} and {{endDate}} | ||
| and website_event.event_name = {{feature}} | ||
| group by website_event.session_id |
There was a problem hiding this comment.
Completion counts ignore filters
In both database branches, featureUsers respects the selected filters and cohort, but completedUsers counts everyone on the website. A filtered group with one adopter can therefore show ten completions from other groups, producing a completion rate of 1000%. Build completion matches from the same filtered feature events.
| (select count(distinct fc.session_id) | ||
| from ( | ||
| select website_event.session_id as session_id, | ||
| min(website_event.created_at) as start_at |
There was a problem hiding this comment.
Completion only checks the first feature event in the range. With the default 60-minute window, a user who triggers the feature at 09:00, retries at 11:00, and succeeds at 11:05 is counted as incomplete. Their success follows a feature event within the promised window. Match success against any qualifying feature occurrence before counting distinct users. Both database branches need this change.
| count(distinct case | ||
| when ${getDateSQL('u.first_used_at', unit, timezone)} = ${getDateSQL( | ||
| 'website_event.created_at', | ||
| unit, | ||
| timezone, | ||
| )} | ||
| then website_event.session_id end) as "newUsers" |
There was a problem hiding this comment.
The series can count a returning user as new when the range starts partway through a bucket. For a range starting at 12:30 with hourly buckets, first use at 12:10 and another use at 12:40 produce newUsers: 1 in the series but newUsers: 0 in the totals. Bucket equality alone does not establish that first use falls inside the range. Add the range check to both series queries.
| <Text weight="bold">{t(labels.newUsers)}</Text> | ||
| <Text weight="bold">{t(labels.returningUsers)}</Text> | ||
| {data.series.flatMap(item => [ | ||
| <Text key={`${item.date}-d`}>{formatDate(item.date, 'PP', locale)}</Text>, |
There was a problem hiding this comment.
Every hourly or minute bucket on the same day gets the same label because the table uses formatDate(item.date, 'PP', locale). Short ranges use these smaller buckets, so readers cannot tell which time each row represents. Include the time for hourly and minute results.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| | 'revenue' | ||
| | 'attribution'; | ||
| | 'attribution' | ||
| | 'feature-adoption'; |
There was a problem hiding this comment.
Shared adoption pages cannot open
Adding feature-adoption to ShareSection does not make the report available on shared websites. SharePage uses a separate PAGE_COMPONENTS map that lacks this report. Even a share with feature-adoption: true redirects /share/{slug}/feature-adoption back to the overview. Register FeatureAdoptionPage in the shared page map and add the matching shared navigation entry.
Resolves the issues raised in review: - P1 completion counts ignored filters: completion is now derived from the same filtered/cohort-scoped feature events as featureUsers (a shared feature_events CTE), so completedUsers can no longer exceed featureUsers or pull in users from other segments. - P1 successful retries were missed: completion now matches a success event that follows *any* qualifying feature occurrence within the window, not only the first one. - P2 series/total newUsers disagreement: the series now requires the first use to actually fall inside the range (range check plus bucket match), so per-bucket new users add up to the totals when a range starts partway through a bucket. - P2 missing time labels: hourly/minute buckets now render with the time. - P2 shared pages: the report is registered as a shareable section (SharePage component map, share nav item, section list, and the website share form), so a share with feature-adoption enabled opens correctly. Tests updated for both Postgres and ClickHouse branches, adding coverage for filtered completion, retry handling, and the series range check.
|
Thanks for the review — all five points are addressed in 3260cc8. P1 Completion counts ignore filters — completion now derives from a shared P1 Successful retries are missed — completion matches a success event that follows any qualifying feature occurrence within the window ( P2 New-user counts disagree — the series now requires P2 Time labels are missing — the table formats hourly/minute buckets with the time ( P2 Shared adoption pages cannot open — the report is registered as a shareable section: added to the Tests were updated for both Postgres and ClickHouse branches, including new coverage for filtered completion, retries, and the series range check. |
Summary
Adds a Feature adoption report for websites. It answers a question Umami does not currently answer directly: which product features do people actually adopt, how many adopters are new versus returning, and how many complete the intended action?
The report lives next to the other Behavior reports (label Adoption in the sidebar, report title Feature adoption).
Motivation
Umami already covers "how much traffic / how many events", and has Funnel, Retention and Cohorts for adjacent questions. But there is no single view for feature-level adoption. Answering "is the new checkout being adopted, and do first-time users come back to it?" today requires building a funnel, plus manual math for the denominator and for new-vs-returning.
This report turns that into one screen:
Metrics
Given a required feature event and an optional success event + window (default 60 min):
activeUsersfeatureUsersnewUsersreturningUsersfeatureUsers - newUsers.adoptionRatefeatureUsers / activeUsers * 100.completedUserscompletionRatecompletedUsers / featureUsers * 100.series[]featureUsers/newUsers/returningUsers.Identity uses
session_id, consistent with the existing Retention report (Umami derives a stable session id per visitor, so "first ever use" works across visits).API
Query params:
feature(required) — event name that marks feature usagesuccessEvent(optional) — event name that marks completionwindow(optional) — completion window in minutes (1–1440, default 60)startAt/endAt(orperiod),timezone,unit,segment,cohort, and the usual dimension filtersA curated
contract.tsis included so the operation is picked up bypnpm openapi:contracts.Implementation
Supporting changes:
src/lib/analytics-schema.ts—featureAdoptionQuerySchemasrc/permissions/share.ts— newfeature-adoptionshare sectionsrc/components/hooks/useWebsiteNavItems.tsx,src/components/messages.ts,public/intl/messages/en-US.json— nav entry + i18n keysScreenshots
Add a screenshot here after running locally (Demo SaaS → Behavior → Adoption → event
signup_started, successsignup_completed). Happy to attach one if a maintainer wants it in the PR.How to test
Then open a website → Behavior → Adoption, pick a feature event (and optionally a success event).
Unit tests:
Covers both backends: totals + series assembly, derived adoption/returning/completion, the success-event window, and parameter passing.
Notes for reviewers
public/openapi.json, the@umami/api-clientgenerated client, and anycontract.generated.ts) are intentionally not in this diff to keep it focused. Regenerate before merge:en-USstrings are added;eloqntreports missing translations for other locales as warnings (non-strict CI).Checklist
canViewWebsiteSectionNeed help on this PR? Tag
@codesmith-botwith what you need.