Add the member adoption report endpoint - #8316
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests.
Additional details and impacted files@@ Coverage Diff @@
## dirt/prototype/adoption-report/data-access #8316 +/- ##
==============================================================================
- Coverage 69.64% 63.99% -5.65%
==============================================================================
Files 2476 2480 +4
Lines 106241 106350 +109
Branches 9636 9640 +4
==============================================================================
- Hits 73991 68061 -5930
- Misses 29783 35944 +6161
+ Partials 2467 2345 -122 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
eb9eba9 to
75087c9
Compare
14e0df2 to
b67ae5e
Compare
🤖 Bitwarden Claude Code ReviewOverall Assessment: APPROVE Reviewed the new One note outside the review: CI reports the Code Review DetailsNo findings met the bar for posting. Items considered and deliberately not raised:
|
b67ae5e to
6f572cc
Compare
6f572cc to
7d9623b
Compare
Exposes GET /reports/member-adoption/{organizationId}, behind the
MemberAdoptionReport feature flag and the same plan and reports authorization
the other organization reports use.
MemberAdoptionReportQuery turns the repository details into the organization
totals and the member rows. The response model projects the members straight
to its own model as it writes rather than buffering them into a list.
The SDK RequireFeature attribute is applied by its fully qualified name so the
controller keeps importing Bit.Core.Utilities; resolving it unqualified through
the SDK namespace would also rebind the pre-existing passkey-directory
endpoint and change its response when its flag is off.
All four counts now come from a single foreach over the repository's details, so the separate Count passes for active members and redeemed sponsorships are gone, and Members is a deferred projection rather than a second list. The response model already projects lazily as it writes, so no member list is retained for the life of the request. The activity cutoff and now are still captured once from TimeProvider, and the recency test is shared by the counting loop and the projection so the two cannot drift. The repository contract returns an IReadOnlyList, materialized with its connection or scope already disposed, which is what makes the deferred projection safe to enumerate after the query returns. At 17,001 members this holds about 4.5 MB less live heap per request (a 0.13 MB details copy plus roughly 4.4 MB of member objects). That is a small share of the measured per-request growth; the bulk is the detail objects themselves, the access graph rows the repositories read, and serialization buffers.
7d9623b to
2bfdf43
Compare
🎟️ Tracking
PM-35924
📔 Objective
Adds
GET /reports/member-adoption/{organizationId}, returning per-member adoption detail plus organization level counts.[Authorize<AccessReportsRequirement>]rather than the imperativeICurrentContext.AccessReportscheck the sibling actions use. Same principals (Owner, Admin, Custom withaccessReports, provider users), but it returns 403 where the neighbours return 404.organizationIdis bound[FromRoute]. Without it, MVC's Form-then-Route value provider order lets a form body shadow the route value, so the action could act on a different organization than the one authorized.UseRiskInsights, matching the other org scoped actions on this controller.HasRecentLoginuses an injectedTimeProviderand a 30 day window, bounded at both ends so a client with a skewed clock cannot sit permanently active.Total,ActiveandInactivecannot disagree with the member list.Top of a 3 PR stack, on
data-access.📸 Screenshots
No user-visible change.