From a8e54d1e9ffede59f7a00d6cdaf5ca0571a086f1 Mon Sep 17 00:00:00 2001 From: Sauer Date: Thu, 22 Oct 2026 08:01:29 +0000 Subject: [PATCH 1/2] feat(web): name rejected webhook counts and endpoints in quota alerts (web 0.40.4) Adds get_quota_rejections() (migration 00060), which reads per-endpoint quota_rejected counts from endpoint_daily_stats for the current period, leaving out team-billed endpoints. The quota-exhausted email, /api/usage and the dashboard QuotaBanner now report the total and the most rejected endpoint slugs. A failed lookup falls back to the generic email. --- .../dashboard/quota-banner.test.tsx | 63 +++++ .../web/components/dashboard/quota-banner.tsx | 45 ++- apps/web/lib/changelog.ts | 13 +- .../lib/email/quota-exhausted-email.test.ts | 88 ++++++ apps/web/lib/email/quota-exhausted-email.ts | 65 ++++- apps/web/lib/quota-emails.test.ts | 45 +++ apps/web/lib/quota-emails.ts | 16 +- apps/web/lib/supabase/database.ts | 9 + .../web/lib/supabase/quota-rejections.test.ts | 56 ++++ apps/web/lib/supabase/quota-rejections.ts | 48 ++++ apps/web/lib/supabase/usage.ts | 10 + apps/web/package.json | 2 +- .../supabase-control-plane.test.ts | 1 + .../supabase-quota-rejections.test.ts | 258 ++++++++++++++++++ .../migrations/00060_quota_rejections.sql | 71 +++++ 15 files changed, 780 insertions(+), 10 deletions(-) create mode 100644 apps/web/components/dashboard/quota-banner.test.tsx create mode 100644 apps/web/lib/supabase/quota-rejections.test.ts create mode 100644 apps/web/lib/supabase/quota-rejections.ts create mode 100644 apps/web/tests/integration/supabase-quota-rejections.test.ts create mode 100644 supabase/migrations/00060_quota_rejections.sql diff --git a/apps/web/components/dashboard/quota-banner.test.tsx b/apps/web/components/dashboard/quota-banner.test.tsx new file mode 100644 index 000000000..f9439a03a --- /dev/null +++ b/apps/web/components/dashboard/quota-banner.test.tsx @@ -0,0 +1,63 @@ +import { describe, expect, it } from "vitest"; +import { renderToStaticMarkup } from "react-dom/server"; +import { QuotaRejectionDetail } from "./quota-banner"; + +describe("QuotaRejectionDetail", () => { + it("renders nothing before any webhook was rejected", () => { + expect(renderToStaticMarkup()).toBe(""); + expect( + renderToStaticMarkup( + + ) + ).toBe(""); + }); + + it("names the total and each affected endpoint", () => { + const html = renderToStaticMarkup( + + ); + const text = html.replace(/<[^>]+>/g, ""); + expect(text).toBe( + "1,420 webhooks rejected this period on stripe-prod (1,000), gh-staging (420)." + ); + expect(html).toContain('stripe-prod'); + }); + + it("uses singular wording and summarizes unlisted endpoints", () => { + const one = renderToStaticMarkup( + + ).replace(/<[^>]+>/g, ""); + expect(one).toBe("1 webhook rejected this period on solo (1)."); + + const many = renderToStaticMarkup( + + ).replace(/<[^>]+>/g, ""); + expect(many).toBe("9 webhooks rejected this period on a (9) and 3 more endpoints."); + }); + + it("escapes slugs", () => { + const html = renderToStaticMarkup( + x", rejected: 1 }], + }} + /> + ); + expect(html).not.toContain("