Skip to content

fix(admin): purge the open entry from the edit view, and let the purge button be used twice - #253

Merged
derrickmehaffy merged 4 commits into
mainfrom
fix/admin-purge-controls
Oct 13, 2026
Merged

derrickmehaffy merged 4 commits into
mainfrom
fix/admin-purge-controls

Conversation

@derrickmehaffy

@derrickmehaffy derrickmehaffy commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

What changed and why

Two small bugs in the admin panel's purge controls. Both are browser-only, so neither was visible to the jest suite.

1. The edit-view "Purge REST Cache" action did not purge the entry it was opened on

useCachedDocument built the purge params from the document's own scalar fields (toPurgeParams(document)). In Strapi 5 a document's id field is the numeric database row id, but a REST route's :id is the documentId - /api/articles/:id is requested and cached as /api/articles/<documentId>. So the action posted

{"contentType":"api::article.article","params":{"id":1,"documentId":"qwvh…","title":"…"},"wildcard":false}

and getRouteRegExp turned that into ^/api/articles/1\?, which matches no cache key.

Why nobody noticed: with clearRelatedCache: true (the default) a content type is a member of its own related set, so every purge also wildcards its own routes and the entry goes anyway. With clearRelatedCache: false the action showed "Cache purged successfully" and the open entry stayed cached - which contradicts docs/guide/invalidation/purging.md ("More actions → Purge REST Cache | The entry you have open").

Fix: keep the scalar fields (so a custom route like /api/articles/:slug is still narrowed to this entry) and set id to the documentId. That is the same scope the server's document service middleware already uses for its own purges ({ id: documentId } in registerDocumentServiceMiddleware.ts). Single types are unchanged: no params, wildcard.

2. The list-view purge button could only be used once per page load

PurgeCacheButton set isLoading when a purge started and never cleared it. The button stays mounted after the dialog closes, so the second time the dialog opened, its confirm button was a disabled spinner and the only way out was Cancel, until a reload remounted the component.

Fix: setIsLoading(false) in the finally that already closes the dialog, so success and failure both leave the control usable.

Why this is not breaking

  • No server change. No change to what is cached, cache keys, Cache-Control, or what is bypassed.
  • The purge endpoint's contract is unchanged; the admin now sends the params the endpoint always expected for an entry.
  • With clearRelatedCache: true the set of keys removed is a superset of before. The related-type wildcard still runs, and now the entry's own key is matched as well. With clearRelatedCache: false the purge now removes the keys for the open entry, which is what the action and the docs say it does. Nothing that used to be purged stops being purged.
  • No permission changes.

Tests

New e2e/tests/purge-controls.spec.ts:

  • the list-view purge can be used twice without a reload: opens the confirm dialog twice and requires the confirm button to be enabled both times.
  • the edit-view purge action purges the open entry: warms /api/articles/<documentId> from a cookie-less client, purges from the edit view, and requires a MISS afterwards. It also asserts the request body's params.id is the documentId. That second assertion matters: under the default clearRelatedCache: true the MISS alone would pass for the wrong reason, so the body check is what fails on the old code in the normal suite.

Both were run against unfixed main first and seen failing.

Before, with CLEAR_RELATED_CACHE=false:

  ✘  1 … › purge controls › the list-view purge can be used twice without a reload (9.0s)
  ✘  2 … › purge controls › the edit-view purge action purges the open entry (3.8s)

  1) … the list-view purge can be used twice without a reload
    Error: confirm button on attempt 2
    expect(locator).toBeEnabled() failed
    Locator:  getByRole('alertdialog').getByRole('button', { name: 'Purge REST Cache' })
    Expected: enabled
    Received: disabled
        14 × locator resolved to <button disabled type="button" aria-disabled="true" …>…</button>

  2) … the edit-view purge action purges the open entry
    Error: expect(received).toBe(expected) // Object.is equality
    Expected: "MISS"
    Received: "HIT"
    > 92 |     expect((await anonymous.get(entryUrl)).headers()['x-cache']).toBe('MISS');
  2 failed

Before, default config (clearRelatedCache: true): the MISS passes because the entry is masked by the related-type wildcard, and the body assertion catches it:

  ✘  1 … › purge controls › the edit-view purge action purges the open entry (4.2s)
    Error: expect(received).toBe(expected) // Object.is equality
    Expected: "qwvhq03ck4i8kbr4r5oa582z"
    Received: 1
    > 96 |     expect(sent.params?.id).toBe(documentId);

After, with CLEAR_RELATED_CACHE=false:

  ✓  1 … › purge controls › the list-view purge can be used twice without a reload (3.9s)
  ✓  2 … › purge controls › the edit-view purge action purges the open entry (3.7s)
  2 passed (44.3s)

Closes #

Checklist

  • Tests added or updated. For a bug fix: a spec in shared/tests/ that fails before the change. Browser-visible behaviour goes in e2e/tests/. (Both bugs are browser-only; specs are in e2e/tests/.)
  • pnpm run test:e2e:memory passes. (23 suites, 135 tests)
  • pnpm run test:lint passes (tsc --noEmit over server/ and admin/, plus eslint and prettier on the providers).
  • Touches admin/? pnpm run test:admin was run and passes. (15 passed, default config)
  • Touches a provider or the published package shape? Not applicable.
  • Any duration I touched is milliseconds. No duration touched.
  • User-visible change? docs/guide/ updated in this PR. docs/guide/invalidation/purging.md already said the edit-view action purges "the entry you have open", and this makes that true. A third commit corrects reference/routes.md, whose POST /rest-cache/purge example used "params": { "id": 42 }. That is the same numeric-id mistake. Both guides now say id is the documentId.
  • Conventional commit subjects, branched from main.

Notes for the reviewer

  • One commit per bug, so either can be reverted alone, plus a docs commit.

PurgeCacheButton set its loading state when a purge started and never
cleared it. The button stays mounted after the dialog closes, so the next
time the dialog opened its confirm button was a disabled spinner and the
only way out was Cancel - until a reload remounted the component.

Reset it in the same `finally` that closes the dialog, so success and
failure both leave the control usable.

Covered by e2e/tests/purge-controls.spec.ts, which lands with the
edit-view purge fix on this branch.
The edit-view "Purge REST Cache" action sent the document's scalar fields
as the route params. A Strapi 5 document's `id` field is the numeric row
id, but a REST route's `:id` is the documentId, so the purge built a
pattern like `^/api/articles/1\?` that no cache key can match.

With clearRelatedCache on - the default - this was invisible: a content
type is in its own related set, so every purge wildcards its own routes
and the entry went anyway. With clearRelatedCache off, the action reported
success and the open entry stayed cached.

Send `id: documentId`, the same scope the document service middleware
already uses for its own purges, and keep the other scalar fields so a
custom route such as `/:slug` is still narrowed to this entry. Single
types still send no params and wildcard.

The e2e spec asserts the request body as well as the resulting MISS, so
it fails on the old code under the default config too, not only with
CLEAR_RELATED_CACHE=false.
The routes reference showed `"params": { "id": 42 }`. A REST route's `:id` is the documentId, so a numeric id builds a pattern no cache key matches - the same mistake the edit-view action made. The purging guide already used a documentId in its example; both now say so in the field description.
@derrickmehaffy
derrickmehaffy merged commit 222a656 into main Oct 13, 2026
13 checks passed
@derrickmehaffy
derrickmehaffy deleted the fix/admin-purge-controls branch October 13, 2026 05:46
@github-actions github-actions Bot mentioned this pull request Oct 13, 2026
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