Skip to content

Add an admin history view for resolved upgrade requests - #29628

Open
avervaet wants to merge 1 commit into
avervaet/overage-refresh-expiryfrom
avervaet/overage-request-history
Open

Add an admin history view for resolved upgrade requests#29628
avervaet wants to merge 1 commit into
avervaet/overage-refresh-expiryfrom
avervaet/overage-request-history

Conversation

@avervaet

Copy link
Copy Markdown
Contributor

Summary

  • Stacked on Let admins expire a spend-limit override at the next credit refresh #29623. Admins could only ever see pending upgrade requests — the moment one was resolved it vanished from the UI, with no way to see who requested what, who approved/denied it, or whether the resulting grant is still active.
  • Adds grantedAwuCredits / grantedExpiresAt / expiredAt to membership_upgrade_requests: a snapshot of what was actually granted, taken when a request is approved through the linked "Edit limit" flow (recordGrant).
  • Since the override is a single overwritable slot per membership (no stacking — see prior discussion), setUserSpendLimit now closes out any previously-tracked grant for that member (expireActiveGrantsForUser) before applying any new override, whether or not the new save is itself request-linked. The expiration sweep does the same when it reverts naturally. This means a request's history row accurately reflects "expired naturally" vs. "superseded early by a later change" — both surface as the grant no longer being active, with the actual end date.
  • Adds a "History" tab (admin requests table) showing requester, status (Approved/Denied), reason, resolver + resolution time, and the grant's status (active/expires on X/expired on X/superseded on X/no grant). Bounded to the 50 most recently resolved requests for now (no cursor pagination yet).

Test plan

  • npm run test -- lib/api/users/spend_limit.test.ts (front)
  • npm run test -- temporal/spend_limit_expiration/activities.test.ts (front) — sweep stamps expiredAt on the linked request.
  • npm run test -- "members/[uId]/spend_limit.test.ts" (front-api) — grant snapshot + supersede-on-new-save.
  • npm run test -- "credits/upgrade-requests.test.ts" (front-api) — ?status=resolved history listing.
  • Manually: approve an upgrade request via "Edit limit", switch to the "History" tab, verify the row shows the granted amount/expiry and resolver. Approve a second request for the same member with a smaller/shorter grant, verify the first row now shows "Superseded".

Deploy plan

🤖 Generated with Claude Code

Admins could only ever see pending upgrade requests — once resolved, a
request vanished from the UI with no way to see who requested what,
who approved/denied it, or whether the resulting grant is still active.

Adds grantedAwuCredits/grantedExpiresAt/expiredAt to
membership_upgrade_requests: a snapshot of what was actually granted
when a request is approved via the linked "Edit limit" flow.
setUserSpendLimit now closes out any previously-tracked grant for a
member before applying a new override — whether or not the new save is
itself request-linked, since the override is a single overwritable
slot and any change supersedes what came before. The expiration sweep
does the same when it reverts an override naturally.

Admins can now switch to a "History" tab on the requests table showing
requester, reason, resolver, resolution time, and the grant's status
(active/expired/superseded/none).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@avervaet
avervaet requested a review from a team as a code owner July 28, 2026 15:18
@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
playground Ignored Ignored Jul 28, 2026 3:18pm
storybook Ignored Ignored Preview Jul 28, 2026 3:18pm

Request Review

@dust-agent dust-agent Bot left a comment

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.

Reviewed against coding rules. Found a couple of issues below.

{status === "approved" ? (
<Chip size="xs" color="success" label="Approved" />
) : (
<Chip size="xs" color="warning" label="Denied" />

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.

[GEN6] Prefer exhaustive switch + assertNever over if/else on union types

status is typed as MembershipUpgradeRequestStatus (a string union that includes at least "pending" | "approved" | "denied"). Using a ternary here skips TypeScript's exhaustiveness check — if a new status value is added, this silently falls through to the Denied chip. Prefer an exhaustive switch with assertNeverAndIgnore (API data, client-side):

Suggested change
<Chip size="xs" color="warning" label="Denied" />
{(() => {
switch (status) {
case "approved":
return <Chip size="xs" color="success" label="Approved" />;
case "denied":
return <Chip size="xs" color="warning" label="Denied" />;
default:
assertNeverAndIgnore(status);
return null;
}
})()}

app.get(
"/",
ensureIsManager(),
validate("query", ListUpgradeRequestsQuerySchema),

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.

[BACK14] Keep Swagger documentation in sync with API schema changes

This endpoint's request schema now accepts a new ?status query parameter, and the response shape also gains new fields (resolvedBy, grantedAwuCredits, grantedExpiresAt, expiredAt) via toJSON(). Please verify the @swagger or @ignoreswagger annotation in this file (and swagger_private_schemas.ts if the response type is referenced there) is up to date.

// be snapshotted onto that request for the admin history view.
const UpdateUserSpendLimitBodySchema = z.discriminatedUnion("kind", [
z.object({ kind: z.literal("unlimited") }),
z.object({ kind: z.literal("unlimited"), requestId: z.string().optional() }),

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.

[BACK14] Keep Swagger documentation in sync with API schema changes

The request body schema now accepts an optional requestId field. Please verify the @swagger or @ignoreswagger annotation in this file (and any shared private schema) reflects this addition.

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