Skip to content

fix(dashboard): surface quota pool delete failures instead of failing silently - #8829

Merged
diegosouzapw merged 2 commits into
release/v3.8.49from
fix/quota-pool-delete-silent-failure
Jul 28, 2026
Merged

diegosouzapw merged 2 commits into
release/v3.8.49from
fix/quota-pool-delete-silent-failure

Conversation

@diegosouzapw

Copy link
Copy Markdown
Owner

The bug

handleRemovePool awaited the DELETE and never looked at the response:

if (!confirm(t("removeConfirm"))) return;
await fetch(`/api/quota/pools/${id}`, { method: "DELETE" });
await mutate();

When the request failed — a 401 from an expired session, a 500, a dropped connection — the page revalidated, the card stayed exactly where it was and nothing was shown. From the operator's side that is indistinguishable from a misclick, so the natural reaction is to click again.

The page had no error surface at all: no pageError, no banner, no toast. The sibling flow in the API manager (handleRegenerateKey) already checks res.ok and renders the message — this page never got the same treatment.

The fix

A dismissible alert above the header, fed by both failure paths:

  • non-ok response — appends the API's own message when it sends one (error.message / error / message), falling back to the generic string;
  • thrown request — a network-level failure has no response to read, so it gets the generic string.

The alert clears at the start of the next attempt, so a transient failure does not leave a stale error on screen after a later delete succeeds.

Two new keys in the quotaShare namespace (removeFailed, dismiss), added across all 43 locale files. I deliberately did not run i18n:sync-ui — it wanted to add 938 unrelated __MISSING__ keys from other features, which does not belong in this diff. Non-Portuguese locales get the English source string.

Origin

Reported against two boxes on 2026-07-28. Worth being explicit: on investigation the delete itself was working — the audit log shows quota.pool.deleted for both pools, and a clean-browser reproduction on the homolog box completed the flow end to end. This change does not fix the delete; it makes the outcome visible either way, which is what was missing when the operator concluded it had not worked.

Tests

tests/unit/ui/quota-share-delete-error.test.tsx (vitest/jsdom, 5) — written first, confirmed red for the right reason (removeFailed absent from the DOM):

  1. non-ok response surfaces the error;
  2. a thrown request surfaces the error;
  3. the success path stays quiet and still calls mutate();
  4. a dismissed confirmation issues no DELETE at all — asserted by filtering the calls for method: "DELETE", since the component legitimately fetches side data on mount;
  5. an earlier error clears once a later delete succeeds.

The pre-existing quota-share-page.test.tsx still passes (8/8).

Gates

Gate Result
quota-share-delete-error.test.tsx 5/5
quota-share-page.test.tsx (regression) 8/8
typecheck:core clean
lint:json --max-warnings 0 1 error, pre-existing on the base in ProviderAccountRoutingCard.tsx:87 — untouched here. Not suppressed in this PR to avoid a duplicate diff: #8827 already adds that allowlist entry, so this goes green once that merges (or rebases onto it).

… silently

The handler awaited the DELETE and never looked at the response:

    if (!confirm(t("removeConfirm"))) return;
    await fetch(`/api/quota/pools/${id}`, { method: "DELETE" });
    await mutate();

When the request failed — a 401 from an expired session, a 500, a dropped
connection — the page revalidated, the card stayed exactly where it was, and
nothing was shown. From the operator's side the click was indistinguishable
from a misclick, so the natural reaction is to click again. The page had no
error surface at all, unlike the sibling flow in the API manager which already
checked res.ok and rendered the message.

Adds a dismissible alert above the header, fed by both failure paths: a
non-ok response (appending the API's message when it sends one) and a thrown
request, which has no response to read. The alert clears on the next attempt,
so a transient failure does not leave a stale error on screen.

Reported against two boxes on 2026-07-28. The delete itself was working there
— the audit log shows the pools were removed — which is exactly what this
change makes visible either way.

Tests (vitest/jsdom, 5): non-ok response, thrown request, success path stays
quiet and still revalidates, a dismissed confirmation issues no DELETE at all,
and an earlier error clears once a later delete succeeds.
@diegosouzapw
diegosouzapw merged commit c8f1d62 into release/v3.8.49 Jul 28, 2026
20 of 21 checks passed
@diegosouzapw
diegosouzapw deleted the fix/quota-pool-delete-silent-failure branch July 28, 2026 06:35
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
… silently (diegosouzapw#8829)

The handler awaited the DELETE and never looked at the response:

    if (!confirm(t("removeConfirm"))) return;
    await fetch(`/api/quota/pools/${id}`, { method: "DELETE" });
    await mutate();

When the request failed — a 401 from an expired session, a 500, a dropped
connection — the page revalidated, the card stayed exactly where it was, and
nothing was shown. From the operator's side the click was indistinguishable
from a misclick, so the natural reaction is to click again. The page had no
error surface at all, unlike the sibling flow in the API manager which already
checked res.ok and rendered the message.

Adds a dismissible alert above the header, fed by both failure paths: a
non-ok response (appending the API's message when it sends one) and a thrown
request, which has no response to read. The alert clears on the next attempt,
so a transient failure does not leave a stale error on screen.

Reported against two boxes on 2026-07-28. The delete itself was working there
— the audit log shows the pools were removed — which is exactly what this
change makes visible either way.

Tests (vitest/jsdom, 5): non-ok response, thrown request, success path stays
quiet and still revalidates, a dismissed confirmation issues no DELETE at all,
and an earlier error clears once a later delete succeeds.
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