feat(web): add parent/child org scope to usage analytics#4254
Conversation
On the organization usage page, parent-org owners/billing managers can now switch the Scope between My Usage, an All Organizations aggregate (parent + children), the parent org, and each child org. Adds a multi-org aggregate mode to the usage filters/SQL, a getScopeOrganizations endpoint, and generalizes resolveOrgUsers to span multiple orgs.
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Files Reviewed (2 files)
Previous Review Summaries (6 snapshots, latest commit 72ea8e1)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 72ea8e1)Status: No Issues Found | Recommendation: Merge Files Reviewed (1 files)
Previous review (commit e63c07e)Status: 1 Issue Found | Recommendation: Address before merge Fix these issues in Kilo Cloud Overview
Issue Details (click to expand)WARNING
Files Reviewed (1 files)
Previous review (commit c294502)Status: 1 Issue Found | Recommendation: Address before merge Fix these issues in Kilo Cloud Overview
Issue Details (click to expand)WARNING
Files Reviewed (5 files)
Previous review (commit 3b2c8a0)Status: 2 Issues Found | Recommendation: Address before merge Fix these issues in Kilo Cloud Overview
Issue Details (click to expand)WARNING
Files Reviewed (4 files)
Previous review (commit ec36fcd)Status: 1 Issue Found | Recommendation: Address before merge Fix these issues in Kilo Cloud Overview
Issue Details (click to expand)WARNING
Files Reviewed (2 files)
Previous review (commit 64e92af)Status: 2 Issues Found | Recommendation: Address before merge Fix these issues in Kilo Cloud Overview
Issue Details (click to expand)WARNING
Files Reviewed (8 files)
Reviewed by gpt-5.4-20260305 · Input: 60.1K · Output: 4.1K · Cached: 237.1K Review guidance: REVIEW.md from base branch |
- Honor a deep-linked org scope while getScopeOrganizations is still loading so the cleanup effect no longer wipes grouping/user-filter state before validation runs. - Exclude soft-deleted child orgs from the scope list and the All Organizations aggregate.
The caller-controlled organizationIds filter drives per-org authorization queries and the Snowflake IN clause, so bound it at the API boundary (MAX_SCOPE_ORGANIZATION_IDS) to prevent resource exhaustion.
- Replace per-org authorization fan-out with batched helpers (getOrganizationsAccessRoles / ensureOrganizationsAccess) so the multi-org scope and resolveOrgUsers use a fixed number of queries. - Raise MAX_SCOPE_ORGANIZATION_IDS to 1000 so large parent hierarchies can use All Organizations without hitting the validation cap. - Migrate legacy `?viewAs=org-wide` deep links to the new `scope` model so they keep opening an org-wide view instead of collapsing to My Usage.
AIAdoptionScoreCard and ActiveKiloclawsTable now follow the selected single org instead of always the page org, and are hidden in the All Organizations aggregate (which they cannot represent), so the dashboard no longer mixes scopes when an admin switches to a child org.
Derive scopeListPending from the query's isLoading state instead of the absence of data, so a failed getScopeOrganizations request falls back to clamping the scope (to My Usage) rather than honoring a stale/unknown URL scope indefinitely.
Summary
On the organization usage page, parent-org owners/billing managers can now switch the Scope between richer options instead of just
My Usage/Entire Org:Non-parent orgs keep the original two-option behavior; plain members still only see their own usage.
Changes
Server (
usage-analytics-router.ts,usage-analytics-schemas.ts)organizationIdsto the usage filters (mutually exclusive withorganizationId, always org-wide).ensureScopeAccess: the multi-org path requires owner/billing_manager on every org in the list (parent owners inherit that on children).buildScopeConditions: emitsorganization_id IN (…)for the aggregate, honoring user include/exclude filters without pinning to self.getScopeOrganizationsendpoint returns the org + its children (owner/billing gated).resolveOrgUsersto resolve user labels across multiple orgs.Client (
UsageAnalyticsDashboard.tsx,UsageAnalyticsSidebar.tsx,FilterGeneratorPopover.tsx,hooks.ts,useUsageDashboardState.ts)viewAstoggle state with a singleorgScopevalue (self|all-orgs|<orgId>), persisted in the URL asscope.organizationId/organizationIds/viewAs, clamps unknown/unauthorized scopes toself, and clears stale per-user filters when the scope org changes.organizationIdsthrough the filter-suggestion popover and user-label resolution.Testing
pnpm typecheck— cleanoxlinton changed files — 0 warnings/errorsINclause, precedence over single-org, user-filter handling, and self/org-wide/personal scoping.Notes