Skip to content

feat(reports): add feature adoption report - #4636

Open
rezarahiminia wants to merge 2 commits into
umami-software:devfrom
rezarahiminia:feat/feature-adoption-dashboard
Open

rezarahiminia wants to merge 2 commits into
umami-software:devfrom
rezarahiminia:feat/feature-adoption-dashboard

Conversation

@rezarahiminia

@rezarahiminia rezarahiminia commented Oct 17, 2026 •

Copy link
Copy Markdown

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:

  • Adoption rate uses all active users as the denominator (not just users who saw the feature), so it reflects real product reach.
  • New vs returning distinguishes first-time adopters from people who were already using the feature before the selected range.
  • Completion (optional) measures whether the feature usage leads to a success event, which is the thing you usually actually care about.

Metrics

Given a required feature event and an optional success event + window (default 60 min):

Metric Definition
activeUsers Unique visitors (sessions) active anywhere in the range — the denominator.
featureUsers Unique visitors that fired the feature event in the range.
newUsers Feature users whose first-ever occurrence of the feature (across all time) falls inside the range.
returningUsers featureUsers - newUsers.
adoptionRate featureUsers / activeUsers * 100.
completedUsers Feature users that fired the success event after the feature event, within the window.
completionRate completedUsers / featureUsers * 100.
series[] Per-bucket 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

GET /api/websites/{websiteId}/feature-adoption

Query params:

  • feature (required) — event name that marks feature usage
  • successEvent (optional) — event name that marks completion
  • window (optional) — completion window in minutes (1–1440, default 60)
  • plus the standard analytics params: startAt/endAt (or period), timezone, unit, segment, cohort, and the usual dimension filters

A curated contract.ts is included so the operation is picked up by pnpm openapi:contracts.

Implementation

src/queries/sql/features/getFeatureAdoption.ts                              # Postgres + ClickHouse
src/app/api/websites/[websiteId]/feature-adoption/route.ts                 # route handler
src/app/api/websites/[websiteId]/feature-adoption/contract.ts              # OpenAPI contract
src/components/hooks/queries/useFeatureAdoptionQuery.ts                    # react-query hook
src/app/(main)/websites/[websiteId]/(reports)/feature-adoption/
  ├─ page.tsx
  ├─ FeatureAdoptionPage.tsx
  └─ FeatureAdoption.tsx
src/queries/sql/features/getFeatureAdoption.test.ts                        # tests

Supporting changes:

  • src/lib/analytics-schema.ts — featureAdoptionQuerySchema
  • src/permissions/share.ts — new feature-adoption share section
  • src/components/hooks/useWebsiteNavItems.tsx, src/components/messages.ts, public/intl/messages/en-US.json — nav entry + i18n keys

Screenshots

Add a screenshot here after running locally (Demo SaaS → Behavior → Adoption → event signup_started, success signup_completed). Happy to attach one if a maintainer wants it in the PR.

How to test

pnpm db:migrate
pnpm db:seed
pnpm dev

Then open a website → Behavior → Adoption, pick a feature event (and optionally a success event).

Unit tests:

pnpm vitest run src/queries/sql/features/getFeatureAdoption.test.ts

Covers both backends: totals + series assembly, derived adoption/returning/completion, the success-event window, and parameter passing.

Notes for reviewers

  • Generated artifacts (public/openapi.json, the @umami/api-client generated client, and any contract.generated.ts) are intentionally not in this diff to keep it focused. Regenerate before merge:
    pnpm openapi:generate
    pnpm openapi:contracts
    pnpm generate:api:client
  • Only en-US strings are added; eloqnt reports missing translations for other locales as warnings (non-strict CI).
  • Naming/placement are easy to change — happy to rename the menu item or make this a saved report / board widget if that fits Umami better.

Checklist

  • Both Postgres and ClickHouse implementations
  • Permission check via canViewWebsiteSection
  • i18n keys added
  • Unit tests added
  • Generated OpenAPI / api-client (regenerate on merge)
  • Screenshot

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

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
@vercel

vercel Bot commented Oct 17, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the Umami Software Team on Vercel.

A member of the Team first needs to authorize it.

@greptile-apps

greptile-apps Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 3/5

[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

  1. P1 Completion counts ignore filters ▶
  2. P1 Successful retries are missed ▶
  3. P2 New-user counts disagree ▶
  4. P2 Time labels are missing ▶
  5. P2 Shared adoption pages cannot open ▶

Summary

Adds a feature-adoption report with event selectors, summary counts, a time series, and PostgreSQL and ClickHouse queries.

  • The feature adoption API returns reach, first-time use, and optional completion counts.
  • The report shows feature adoption and optional success by date.
  • Adoption appears in the Behavior menu and can be enabled for share links.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A["Feature and success event selectors"] --> B["useFeatureAdoptionQuery"]
  B --> C["GET feature-adoption"]
  C --> D["Check website and section access"]
  D --> E["Resolve dates and filters"]
  E --> F["PostgreSQL or ClickHouse"]
  F --> G["Totals and time series"]
  G --> H["Adoption report"]
Loading

Reviews (1) · Last reviewed commit: "feat(reports): add feature adoption repo..." · Reviewed by Greptile

Comment on lines +103 to +109
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Successful retries are missed

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.

Comment on lines +176 to +182
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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 New-user counts disagree

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>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Time labels are missing

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!

Comment thread src/permissions/share.ts
| 'revenue'
| 'attribution';
| 'attribution'
| 'feature-adoption';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.
@rezarahiminia

Copy link
Copy Markdown
Author

Thanks for the review — all five points are addressed in 3260cc8.

P1 Completion counts ignore filters — completion now derives from a shared feature_events CTE that carries the same filterQuery/cohort as featureUsers, so completedUsers can no longer exceed featureUsers or include users from other segments.

P1 Successful retries are missed — completion matches a success event that follows any qualifying feature occurrence within the window (se.created_at >= fe.created_at joined over every feature_events row), not just the first one.

P2 New-user counts disagree — the series now requires u.first_used_at between startDate and endDate in addition to the bucket match, so per-bucket newUsers sums to the totals when a range starts partway through a bucket.

P2 Time labels are missing — the table formats hourly/minute buckets with the time (PP p); day-and-larger buckets stay date-only.

P2 Shared adoption pages cannot open — the report is registered as a shareable section: added to the SharePage component map, the share nav (Behavior), ALL_SECTION_IDS in ShareProvider, and SHARE_NAV_ITEMS in the website share form, so a share with feature-adoption: true opens.

Tests were updated for both Postgres and ClickHouse branches, including new coverage for filtered completion, retries, and the series range check.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant