Skip to content

feat(admin): deployment-admin billing page with per-org task quota overrides - #5863

Open
viktormarinho wants to merge 1 commit into
mainfrom
viktormarinho/admin-org-billing-quota
Open

feat(admin): deployment-admin billing page with per-org task quota overrides#5863
viktormarinho wants to merge 1 commit into
mainfrom
viktormarinho/admin-org-billing-quota

Conversation

@viktormarinho

@viktormarinho viktormarinho commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a Billing tab to the deployment-admin dashboard (/_admin/billing), visible only when the deployment enforces the task quota (STUDIO_TASK_QUOTA_ENFORCED). Operators can see how much quota each org has consumed/claimed and set per-org free/monthly allowances — the operator action migration 164's override columns were designed for (they had no writer until now).

API (apps/api/src/api/routes/admin.ts)

  • GET /api/_admin/me now returns taskQuotaEnforced — the flag the UI tab gates on.
  • GET /api/_admin/billing/orgs?search&limit — org listing ordered by all-time live claims (top consumers first), with subscription status, current bucket (trial / sub:<periodEnd> / sub:pending), used/limit for that bucket, and the per-org overrides. The bucket and effective limit are computed with the same taskQuotaState the enforcement path uses, and every count shares the new LIVE_CLAIM_FILTER from OrganizationBillingStorage, so this view cannot disagree with what a dispatch would allow.
  • PATCH /api/_admin/billing/orgs/:orgId — sets/clears overrides via a new OrganizationBillingStorage.setQuotaOverrides (keeps all writers of organization_billing in the storage class). null resets to the deployment default; omitted fields are untouched. Validates positive int4 (0/negatives/overflow → 400, never a constraint 500). Audited like /impersonate/member-add: durable stdout line + PostHog, real-actor attribution under impersonation, and previous values included so a lowered quota is reconstructable.

Both endpoints sit behind the existing requireDeploymentAdmin + untrusted-Origin middlewares. No Stripe ids are exposed in the listing. The PATCH deliberately works while the gate is off (columns are dormant; overrides set ahead of enabling stick).

Web (apps/web)

  • New routes/admin/billing.tsx mirroring the orgs tab: search, usage per current period (with renewal date), all-time claims, quota columns showing default (N) when unset, and an edit dialog where blank = deployment default. Tab hidden unless /me reports taskQuotaEnforced.
  • i18n complete (en + pt-BR).

Tests

New e2e coverage in packages/e2e/tests/deployment-admin.spec.ts:

  • Override round-trip (set → effective limit → reset to default), including the upsert's insert branch (org with no billing row).
  • Omitted-field-vs-null PATCH semantics (a non-null override survives an omission).
  • Trial and subscription buckets: the sub:<ISO> key round-trip a timestamptz serialization drift would break, plus sub:pending.
  • Released (refunded) claims excluded from used/totalClaims.
  • Validation 400s (zero, negative, non-integer, string, int4 overflow, empty patch, JSON null body) and unknown-org 404.
  • Browser: Billing tab absent when the quota gate is off.

Testing notes

  • bun run fmt, bun run check (all workspaces), bun run lint, and bun run knip pass.
  • bun test apps/api/src/billing apps/api/src/settings: pure unit tests pass (the 2 pre-existing integration tests in that dir require a local Postgres and are unrelated).
  • The new e2e test needs the Playwright server (packages/e2e); it follows the spec's serial-mode + fresh-org-per-test doctrine.

Scale note: the listing's ordering subquery is an index probe per org pre-LIMIT (same ~10^5-org ceiling as /orgs); the doc comment records why the /orgs LATERAL upgrade path does not apply here and what does.


Summary by cubic

Adds a Billing tab to the deployment-admin dashboard to view per‑org task‑quota usage and set free/monthly quota overrides. The tab appears only when task quota is enforced, and usage/limits match the enforcement logic.

  • New Features
    • Web: New /_admin/billing with search, current-period usage (with renew date), all‑time claims, and an edit dialog where blank = deployment default. Tab is gated by taskQuotaEnforced from /api/_admin/me. i18n added (en, pt‑BR).
    • API: GET /api/_admin/me now returns taskQuotaEnforced. GET /api/_admin/billing/orgs?search&limit lists orgs ordered by all‑time live claims, with status, bucket key (trial/sub:<periodEnd>/sub:pending), used/limit, and overrides; counts use the same taskQuotaState and LIVE_CLAIM_FILTER as enforcement.
    • API: PATCH /api/_admin/billing/orgs/:orgId sets per‑org overrides; null resets to defaults, omitted fields are untouched. Validates positive int4, audits with previous values, and works even if the gate is off.
    • Storage: Adds OrganizationBillingStorage.setQuotaOverrides, liveClaimCountsByPeriod, and shared LIVE_CLAIM_FILTER for consistent counting.
    • Tests: E2E covers override round‑trip, insert‑branch upsert, bucket keys, refund exclusion, validation errors, and Billing‑tab gating.

Written for commit 164c4b7. Summary will update on new commits.

Review in cubic

…errides

Adds a Billing tab to the deployment-admin dashboard, shown only when the
deployment enforces the task quota (GET /api/_admin/me now exposes
taskQuotaEnforced for the gate).

- GET /api/_admin/billing/orgs: per-org quota consumption, ordered by
  all-time live claims; the current bucket and effective limit are computed
  with the same taskQuotaState + LIVE_CLAIM_FILTER the enforcement path
  uses, so the view cannot disagree with what a dispatch would allow.
- PATCH /api/_admin/billing/orgs/:orgId: sets/clears the per-org override
  columns from migration 164 (previously writer-less), via a new
  OrganizationBillingStorage.setQuotaOverrides. Validates positive int4,
  audits with previous values like the other privileged admin actions.
- Web: admin billing page mirroring the orgs tab (search, usage, edit-quota
  dialog with blank-means-default), i18n en + pt-BR.
- E2E: override round-trip, insert-branch self-heal, omitted-field
  semantics, trial and sub:<periodEnd>/sub:pending buckets, released-claim
  refunds, validation 400s (incl. int4 overflow and JSON null body), and
  Billing-tab absence when the quota gate is off.
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