Forms: refresh cached plan on editor return after plan purchase - #50324
Conversation
Paid blocks (e.g. the Forms file-upload field) kept showing their upgrade nudge after a plan purchase because block availability is baked from the cached jetpack_active_plan option, which only refreshes on the daily heartbeat. Refresh the plan from WordPress.com when the editor loads with ?plan_upgraded (connected non-WPCOM sites, throttled and time-boxed), and add a client-side one-shot reload fallback for the provisioning race. See FORMS-712.
|
Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.
Interested in more tips and information?
|
|
Thank you for your PR! When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:
This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖 Follow this PR Review Process:
If you have questions about anything, reach out in #jetpack-developers for guidance! Jetpack plugin: No scheduled milestone found for this plugin. If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack. |
Code Coverage SummaryCoverage changed in 2 files.
Full summary · PHP report · JS report Coverage check overridden by
Coverage tests to be added later
|
jest.spyOn on window.location.reload throws 'Cannot assign to read only property' in this jsdom config, so the IIFE test suite failed to run on CI. Replace window.location and window.sessionStorage via Object.defineProperty using a Map-backed sessionStorage double whose methods can be overridden to simulate storage that throws.
window.location is non-configurable and reload is read-only in this jsdom, and a real reload() logs to console (which jest-console fails on). Drive the query string via history.pushState and install the reload mock on whichever of the location instance or its prototype accepts a redefine.
jsdom's Location is unforgeable so reload() cannot be mocked; a real reload() logs a 'Not implemented: navigation' console error that jest-console fails on. Assert the reload path via the guard write + consumed console error, and the no-reload paths via expect(console).not.toHaveErrored().
…ll-doesnt-go-away-after-plan-purchase
|
@enejb — I dug into FORMS-712 and have a fix up here (draft for now, so we can test it on JN and via the beta plugin). Wanted to sanity-check the approach with you before taking it out of draft. Root cause: on a self-hosted Jetpack (or Atomic) site, the editor decides whether a paid block like the Forms file-upload field shows its upsell from The fix is two parts. Server: when the editor loads with The honest caveat: if WP.com hasn't actually recorded the purchase yet when you land back in the editor (the "delay in sync" theory in the ticket), even a forced refresh gets the old plan and you'd still see the nudge until the next load. The reload catches a slightly-late provision, but a truly lagging WP.com is beyond what the site can fix locally. So this closes the structural gap and the common case; it's strictly better than today, but not a 100% guarantee in that pathological race. One thing I didn't touch: the activity log dashboard you mentioned. That's a separate surface with its own caching, so it needs its own look — happy to investigate that separately if it's still happening. Does this direction seem reasonable? If so, I'll double-check my work and take it out of draft for a real review. |
…ll-doesnt-go-away-after-plan-purchase
…ll-doesnt-go-away-after-plan-purchase
| alreadyReloaded, | ||
| } ) { | ||
| return ( | ||
| ! isSimple && |
|
The code makes sense to me. I think we can ship this since it does improve things. |
|
Thanks for testing this, @enejb! On the two things you flagged:
Neither blocks this one — appreciate the review! |



Fixes FORMS-712 (Linear).
Proposed changes
The Forms file-upload field (and any other
plan_check-gated editor block) kept showing its upgrade nudge after a plan purchase. Block availability (Jetpack_Editor_Initial_State.available_blocks) is computed server-side at editor render fromCurrent_Plan::supports(), which reads the cachedjetpack_active_planoption. That option is only refreshed by the dailyjetpack_v2_heartbeatcron, and nothing refreshed it when the user returned from checkout — so the editor rendered the upsell from a pre-purchase plan while the "Congratulations" banner (driven by the?plan_upgradedURL param) showed success. Simple/WordPress.com sites gate features live viawpcom_site_has_feature(), so they don't hit this; it's a self-hosted Jetpack / Atomic issue.class.jetpack-gutenberg.php): inenqueue_block_editor_assets(), when the editor loads with?plan_upgraded, refresh the plan from WordPress.com before block availability is computed, so the first post-checkout render is correct. Guarded to connected non-WPCOM sites, throttled to once per minute via a transient (a repeated/bookmarked?plan_upgradedURL can't trigger a blocking request every load), and time-boxed to 5s so a slow origin can't hang the editor.extensions/shared/plan-upgrade-notification.js): a one-shot reload fallback for the narrow race where WordPress.com finishes provisioning between the server refresh and the banner's own site fetch. It reloads once when the freshly fetched plan slug differs from the slug the page rendered with. The guard is keyed on the rendered plan slug and stored insessionStoragethrough throw-safe helpers, and the reload only fires if the guard can be persisted first — so browsers with storage blocked/full (Safari private mode, blocked site data) can never enter a reload loop.shouldReloadAfterPlanUpgrade()as a pure function and added unit tests for it plus the IIFE orchestration (reload guard, error path, storage-throw, normal navigation).Reviewed with a multi-persona code review + an independent adversarial pass; the two P2s they agreed on (unthrottled blocking refresh;
sessionStoragethrowing past optional chaining) are addressed above.Related product discussion/links
Does this pull request change what data or activity we track or use?
No. The server refresh only re-fetches the site's own plan from WordPress.com (authenticated as the blog) into the existing
jetpack_active_plancache; no new data is collected or sent.Testing instructions
Reproduces on a self-hosted Jetpack site connected to a WordPress.com account (e.g. a Jurassic Ninja site); it does not reproduce on Simple sites.
Baseline (before this change / on trunk):
With this change:
Quick mechanism check without a real purchase (WP-CLI on the connected site):
wp option get jetpack_active_planon a free site → thefield-fileblock reportsavailable:false, missing_plan.jetpack_completeand reload the editor →field-filereportsavailable:trueand the nudge is gone. (This is the exact state the server refresh produces on the?plan_upgradedreturn.)