Skip to content

feat(admin): redesign organizations table with entitlements/usage tabs#3602

Open
alexkgold wants to merge 6 commits into
mainfrom
feat/org-table-updates
Open

feat(admin): redesign organizations table with entitlements/usage tabs#3602
alexkgold wants to merge 6 commits into
mainfrom
feat/org-table-updates

Conversation

@alexkgold
Copy link
Copy Markdown
Contributor

@alexkgold alexkgold commented May 29, 2026

Brings a lot more data into admin organizations table and splits out trial organizations from others.

image image image

Summary

  • Splits /admin/organizations into Entitlements and Usage tabs and reuses the same component for /admin/organizations/trials, with a renamed header, a Trial-specific create button label, and Usage as the default tab. The Organizations page no longer shows a Create button at all.
  • Surfaces new operational signals (SSO, Provider/Model controls, Data Privacy, GitHub/GitLab/Slack integrations, Auto Top-Up, Stripe link in Links) by extending the admin list query with derived booleans and JSONB checks; the row-level Stripe link, Pylon link, and View Org link now live together in a Links cell.
  • Centralizes the Stripe subscription status registry under lib/admin/stripe-subscription-statuses so the filter dropdown, badge, and TRPC input enum share one source of truth, extracts a hasPlatformIntegrationSql helper for the github/gitlab/slack EXISTS subqueries, fixes a count-query bug that broke the new Stripe Status filter, and nulls subscription_amount_usd for non-billable subscription statuses so the Subscription column doesn't display the price of a churned plan as if it were current MRR.
  • Broadens the org-dashboard seed so every new column has variety (Stripe customer IDs, auto top-up, SSO domains, settings overrides, github/gitlab/slack integrations across active/pending/suspended).

Verification

  • Re-seeded with pnpm dev:seed app:org-dashboard and walked /admin/organizations (Entitlements + Usage tabs) and /admin/organizations/trials (Trial Organizations header, Usage tab default, Create Org Trial button).
  • Confirmed the Stripe Status filter applies on /admin/organizations without crashing the count query (regression test added: list — stripe_status filter in organization-admin-router.test.ts).
  • Confirmed Tier Features pills, Integrations pills, Auto Top-Up On/Off, KiloClaw counts, and Stripe/Pylon/View Org links render with seed variety.
  • Verified git check-ignore still ignores arbitrary .env.foo while preserving committed .env.local.example, .envrc, and apps/web/.env.test.

Visual Changes

Before After

Reviewer Notes

  • The latestSubscriptions derived table no longer filters on subscription_status = 'active' so the new `latest_stripe_status` can populate; `subscription_amount_usd` is masked to `NULL` when the latest status is not in `('active','trialing','past_due')` to preserve the previous "current paying value" semantics.
  • `OrganizationsTable` callsite ergonomics changed: instead of `showCreateButton` + `createButtonLabel`, callers pass an optional `create={{ label }}` config so a button label can never be rendered without a wired click target.
  • `countQuery` now joins `latestSubscriptions` because `finalWhereCondition` may reference `latest_subscriptions.subscription_status`. A regression test exercises this path.
  • The `.gitignore` change replaces the broad `.env*` rule with `.env` + `.env.` plus `!` overrides for `.env.example`, `.env..example`, `.env.test`, and `.envrc` so committed env example/test files keep being trackable.

alexkgold added 4 commits May 28, 2026 16:13
Split admin organizations table into Entitlements and Usage tabs surfacing
SSO, provider/model controls, data privacy, GitHub/GitLab/Slack integrations,
and auto top-up status alongside existing usage and billing fields. Adds
backing fields to the admin list query, dedicated trials page header and
create button copy, and seed coverage for every new column.
- Critical: countQuery now joins latestSubscriptions, so the new Stripe Status
  filter no longer throws (regression test added).
- Null subscription_amount_usd for non-billable statuses so the Subscription
  column doesn't display the dollar amount of churned plans as if it were MRR.
- Tighten .gitignore .env* rule to preserve .env.example/.env.test/.envrc.
- Propagate showStripeStatus through the Entitlements column so the trials
  page hides Stripe Status whether the user is on the Usage or Entitlements
  tab.
- Centralize the Stripe subscription status registry under
  lib/admin/stripe-subscription-statuses so the filter dropdown, badge, and
  router input share one source of truth, and tighten the router input from
  free-form string to the canonical enum.
- Extract hasPlatformIntegrationSql helper so the active/pending status set
  for github/gitlab/slack EXISTS subqueries lives in one place.
- Replace showCreateButton + createButtonLabel with a single create config
  object so the create-button label can never be rendered without a wired
  click target.
Comment thread kilo-app/.expo/cache/eslint/.cache_15fcvgc Outdated
Comment thread apps/web/src/routers/organizations/organization-admin-router.ts Outdated
@kilo-code-bot
Copy link
Copy Markdown
Contributor

kilo-code-bot Bot commented May 29, 2026

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

All previously flagged issues are resolved: the accidentally committed local ESLint cache file (kilo-app/.expo/cache/eslint/.cache_15fcvgc) has been deleted and **/.expo/ has been added to .gitignore to prevent recurrence.

Changes Since Last Review

Resolved (1):

  • kilo-app/.expo/cache/eslint/.cache_15fcvgc — local ESLint cache with developer machine paths accidentally committed ✅ File deleted and **/.expo/ added to .gitignore
Files Reviewed (16 files)
  • .gitignore
  • apps/web/src/app/admin/api/organizations/hooks.ts
  • apps/web/src/app/admin/components/AppSidebar.tsx
  • apps/web/src/app/admin/components/OrganizationFilters.tsx
  • apps/web/src/app/admin/components/OrganizationMetricCards.tsx
  • apps/web/src/app/admin/components/OrganizationTableBody.tsx
  • apps/web/src/app/admin/components/OrganizationTableHeader.tsx
  • apps/web/src/app/admin/components/OrganizationsTable.tsx
  • apps/web/src/app/admin/organizations/trials/page.tsx
  • apps/web/src/lib/admin/stripe-subscription-statuses.ts
  • apps/web/src/routers/organizations/organization-admin-router.ts
  • apps/web/src/routers/organizations/organization-admin-router.test.ts
  • apps/web/src/types/admin.ts
  • dev/seed/app/org-dashboard.ts
  • kilo-app/.expo/cache/eslint/.cache_15fcvgc ✅ Deleted

Reviewed by claude-sonnet-4.6 · 167,181 tokens

Review guidance: REVIEW.md from base branch main

alexkgold and others added 2 commits May 29, 2026 15:36
Co-authored-by: kilo-code-bot[bot] <240665456+kilo-code-bot[bot]@users.noreply.github.com>
The kilo-app/.expo/cache/eslint/.cache_15fcvgc file contained absolute
developer paths and was committed by accident. Add a repo-wide
`**/.expo/` ignore rule (matching the apps/mobile precedent) so any
Expo app's cache anywhere in the tree is excluded going forward.
[sharedParams, updateUrl]
);

const buttons = create ? (
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.

Problem: OrganizationsTable now only renders the create button when a create prop is passed, but /admin/organizations/page.tsx still calls without that prop, so the main page loses the “Create Organization” action.

Solution: preserve the old default by either:

  • passing create={{ label: 'Create Organization' }} from /admin/organizations/page.tsx, or
  • defaulting create inside OrganizationsTable to { label: 'Create Organization' }.

// Get total count using the same filtering logic. Must mirror baseQuery's
// joins so finalWhereCondition references (e.g. latestSubscriptions for the
// stripe_status filter) resolve.
const countQuery = db
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.

Problem: the pagination count query always joins the latestSubscriptions windowed subquery, even though it only needs that join when stripe_status is filtering by latest subscription status. This adds avoidable historical subscription-table work to normal list requests.

Solution: build the count query without latestSubscriptions by default, and add that join only when stripe_status is present.

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.

3 participants