Skip to content

Fix incomplete retention report#4126

Open
Maxime-J wants to merge 1 commit intoumami-software:devfrom
Maxime-J:fix-retention
Open

Fix incomplete retention report#4126
Maxime-J wants to merge 1 commit intoumami-software:devfrom
Maxime-J:fix-retention

Conversation

@Maxime-J
Copy link
Copy Markdown
Contributor

@Maxime-J Maxime-J commented Apr 1, 2026

Closes #3450.

As stated in issue, days shown per cohort are currently limited to the number of cohorts, which can skip values for websites without new visitors every day.

Moreover, grey squares are possibly shown in impossible positions, like in Day 1 of a cohort that started on the last day of the month.
Current docs example has an impossible square on Day 28 of Aug 4 cohort.

This PR fixes the incomplete issue and ensures Cell on every possible position
+ a slight code change: find() instead of filter()[0]

Note that a cohort starting the last day of the month will then appear thinner (you might want to update styling):
retention

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 1, 2026

@Maxime-J 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
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 1, 2026

Greptile Summary

This PR fixes a long-standing bug in the retention cohort report where visible day-columns were incorrectly capped by the number of cohort rows rather than the actual elapsed days from each cohort's start date to the report's end date. A cohort that started on the last day of a month could show impossible grey cells (e.g. Day 28 for an Aug 31 cohort), and cohort rows for websites with infrequent new visitors would be truncated early.

Key changes

  • Removes the totalDays = rows.length proxy and replaces it with differenceInCalendarDays(endDate, new Date(date)) per row — the semantically correct bound for each cohort.
  • Returns null for any day > maxDay, preventing cells that cannot logically have data.
  • Replaces records.filter(a => a.day === day)[0] with the equivalent (and cleaner) records.find(a => a.day === day).
  • Adds a single import from date-fns, which is already a listed project dependency (^2.23.0).

Confidence Score: 5/5

  • Safe to merge — the change is a targeted, well-scoped bug fix with no new dependencies or side-effects.
  • No P0 or P1 issues found. The new visibility guard is semantically correct, date-fns is already in package.json, and endDate is always endOfMonth() (a proper Date object), so differenceInCalendarDays will behave as expected.
  • No files require special attention.

Important Files Changed

Filename Overview
src/app/(main)/websites/[websiteId]/(reports)/retention/Retention.tsx Replaces the incorrect row-count-based cell-visibility guard with a precise differenceInCalendarDays(endDate, cohortDate) check, and swaps filter()[0] for find(). Dependency (date-fns) is already in package.json.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[rows.map — iterate cohorts] --> B["maxDay = differenceInCalendarDays(endDate, cohortDate)"]
    B --> C[days.map — iterate day columns]
    C --> D{day > maxDay?}
    D -- Yes --> E[return null — cell not rendered]
    D -- No --> F["records.find(a => a.day === day)"]
    F --> G{percentage found?}
    G -- Yes --> H["Render Cell with 'X.XX%'"]
    G -- No --> I[Render empty Cell]
Loading

Reviews (1): Last reviewed commit: "Fix incomplete retention report" | Re-trigger Greptile

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