Stats admin: Add pricing grid for new installations without a plan - #51103
Stats admin: Add pricing grid for new installations without a plan#51103kangzj wants to merge 14 commits into
Conversation
- Add PHP eligibility check for new sites (not connected OR created after launch date) - Build pricing grid React component with free/paid CTAs - Free CTA: Checkout for free product, returns to dashboard - Paid CTA: Links to Calypso purchase page for tier selection - Support unlinked site flows (auto-connect-after-checkout) - Add webpack build scaffolding for stats-admin pricing grid - Add changelog entry for feature Fixes: STATS-366
… control - Allows pricing grid to be accessible to more admin users - Properly gates the Odyssey dashboard behind connection checks - Supports showing pricing grid on new installations This ensures the pricing grid is accessible and can be shown based on the eligibility check (new site + no plan), while the full dashboard remains gated behind connection status.
|
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. Mu Wpcom plugin:
If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack. Wpcomsh plugin:
If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack. |
|
Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.
Interested in more tips and information?
|
Code Coverage SummaryCannot generate coverage summary while tests are failing. 🤐 Please fix the tests, or re-run the Code coverage job if it was something being flaky. |
The stats module only loads once the site is connected, so new installations never got the Stats menu and the pricing grid was unreachable. Register the dashboard from the stats-admin package's Main (which runs on every admin request) when the pricing grid is eligible, and keep the module path for connected sites. Also: - Revert the menu capability to view_stats: the stats package maps it for administrators on every admin request, so manage_options was both unnecessary pre-connection and a regression for non-admin stats users. - Fix the eligibility check referencing a nonexistent Automattic\Jetpack\Plans\Current_Plan class (the real class is Automattic\Jetpack\Current_Plan), which fataled every admin request. - Skip the old stats interface when the pricing grid should show for connected sites that have Odyssey disabled.
Reuse the same PricingTable layout as the Jetpack Search upsell page instead of a bespoke card grid, with copy following the STATS-366 grid spec: the paid column leads with its real differentiators (UTM tracking, device stats, region and city locations, priority support), the view tier sits next to the price, and shared features are compared per row. Also: - Fetch localized pricing for the paid product from the public WordPress.com products endpoint server-side (transient-cached) so the grid shows the viewer's real currency instead of a hardcoded USD price. - Fix the script registration path (it resolved a directory too high, 404ing the bundle and skipping its asset dependencies) and inject the connection initial state via the connection package instead of a nonexistent plugin file.
Cover the four eligibility scenarios: unconnected site, connected site created before/after the launch date, and sites holding a Stats product or a plan with the stats-paid feature. Seed the assumed-site-creation-date transient in the base test case so the connection manager doesn't run a user query WorDBless can't satisfy, and ignore webpack build output.
Point Get Paid Stats at admin.php?page=stats#!/stats/purchase/{blogId}
whenever the site has a blog ID, instead of leaving wp-admin for
Calypso. The URL carries a view=purchase query flag so the dashboard
renders Odyssey there rather than the pricing grid (PHP can't see the
hash fragment). Sites that never connected fall back to Calypso.
Build cache accidentally committed with the initial draft; .cache/ is now gitignored.
Both CTAs now land on Odyssey views flagged with a view query param (traffic for free, purchase for paid); loading either view marks the grid dismissed so it doesn't reappear — including after Odyssey's 'I will do it later'. Dismissal is recorded in the wpcom stats notices (pricing_grid_dismissed) for connected sites, with a local option covering unconnected sites where that endpoint is unreachable. 'Start for free' no longer runs a checkout for the free product — it goes straight to the Stats dashboard, so the checkout workflow hook, the connection initial state, and the unused initial-state fields are removed. Also reword the paid price legend to 'from 10k monthly views'.
Code reviewFound 1 issue:
jetpack/projects/plugins/jetpack/modules/stats.php Lines 247 to 261 in ff18e32 jetpack/projects/packages/stats-admin/src/class-main.php Lines 55 to 62 in ff18e32 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
Address code-review feedback: - noheader requests (the adminbar sparkline and dashboard-widget chart images) always take the legacy raw-image path again; the pricing grid only overrides the Odyssey-disabled case for normal page views. - Only register the dashboard from the package for unconnected sites, restoring mutual exclusivity with the stats module's own menu decision on connected sites. - Never treat WordPress.com Simple/WoA sites as eligible: Stats ships with their plans, and connection-based eligibility doesn't apply. - Fix the get_paid_pricing() return docblock.
|
Addressed in 84a089a:
|
The assumed site creation date was the wrong signal: an old WordPress install that connects Jetpack after launch is a new Stats installation, while its earliest admin/post predates the launch. Record the first successful registration time (jetpack_site_registered) in an option and treat connected sites as new only when that recorded time is on/after the launch date. Reconnects keep the original value, and sites already connected before the feature shipped have no recorded time, so neither qualifies.
The draft's pnpm-lock.yaml was regenerated through a proxying registry, which rewrote every resolution entry and stripped dependency snapshots (postcss lost picocolors, crashing ESLint, Stylelint, and Storybook in CI). Regenerate it from trunk against the default registry — the diff is now just the stats-admin importer. Also drop dependencies the reworked pricing grid no longer uses (connection/checkout, wp-components, testing-library, TypeScript preset) and align sass-loader with the version used monorepo-wide.
Without the production-include attribute the gitignored build/ directory would be missing from the mirrored release package.
The stats-admin composer.json gained real build scripts; the jetpack, mu-wpcom-plugin, and wpcomsh lock files embed that package metadata and were flagged stale by CI.
|
The wp-admin pricing grid implementation has been removed in e1d2be3 — the grid UI now lives in Odyssey (Automattic/wp-calypso#113366, route This PR is now PHP-only and keeps the part Odyssey depends on: the Stats dashboard registers as the Stats menu for eligible new sites before the site is connected (first-connection-date eligibility recorded on |
|
Per discussion, pre-connection support has been dropped in a7161d8 — the Stats menu registers only for connected sites, exactly as on trunk. The pricing grid ships entirely via Automattic/wp-calypso#113366, reachable through the existing Stats menu on connected sites. This PR is now a small janitorial change: the Odyssey-vs-legacy registration decision is consolidated in |
a7161d8 to
4c47c40
Compare
|
Reverted to 4c47c40 per discussion: pre-connection support is back — |
4c47c40 to
66a3451
Compare
add_filter( 'jetpack_stats_pricing_grid_show', '__return_true' ) shows the grid regardless of eligibility or dismissal — useful for testing and demos.
Fixes https://linear.app/a8c/issue/STATS-366/rework-the-stats-pricing-grid-jetpackcomstats-and-purchase-flow
Why this matters
New Jetpack installations without a Stats plan had no upgrade path in wp-admin — and before this PR, an unconnected site had no Stats menu at all (the stats module only loads once the site is connected). New users now land on a clear Free vs Paid choice the moment they install, before connecting.
Proposed changes
Menu registration before connection
Main(initialized on every admin request viaConfig) for unconnected eligible sites, so the Stats menu exists before connecting. Connected sites go exclusively through the stats module's own menu decision, andnoheaderchart-image requests (adminbar sparkline, dashboard widget) always keep the legacy raw-image path.view_stats— the stats package maps it for administrators on every admin request, so it works pre-connection and non-admin stats users keep their access.Eligibility (
class-eligibility.php)jetpack_site_registeredaction (jetpack_stats_first_connected_atoption); reconnects keep the original value, and sites already connected before the feature shipped have no recorded time, so neither qualifies. (Connection date, not the assumed site creation date — an old WordPress install connecting Jetpack today is a new Stats installation.)stats-paidfeature and no Stats product.jetpack_stats_pricing_grid_showfilter can force the grid on for testing/demos.Eligibility_Test).Pricing grid (Search-style)
PricingTablelayout as the Jetpack Search upsell page: paid column leads with the real differentiators (UTM tracking, device stats, region & city locations, priority support), the view tier sits next to the price ("per month, from 10k monthly views, billed yearly"), shared features are compared per row, and there is no classifier language.admin.php?page=stats&view=traffic) — no checkout for the free product.admin.php?page=stats&view=purchase#!/stats/purchase/{blogId}(withfrom=jetpack-stats-pricing-gridand aredirect_uriback to the Stats page). Sites that never connected (no blog ID) fall back to the Calypso purchase page.viewquery flag exists because PHP can't see the hash fragment: it makes the dashboard render Odyssey instead of the pricing grid at those URLs.Dismissal
pricing_grid_dismissed— needs the matching wpcom-side change) for connected sites, with a local option (jetpack_stats_pricing_grid_dismissed) covering unconnected sites, where that endpoint is unreachable.Screenshots
Before — unconnected site,
admin.php?page=stats:After — same unconnected site:
"Start for free" — straight to the Stats dashboard (
view=traffic), grid dismissed:"Get Paid Stats" — the in-admin Odyssey purchase page at
admin.php?page=stats&view=purchase#!/stats/purchase/{blogId}:After dismissal — plain
admin.php?page=statsrenders the dashboard, not the grid:Related product discussion/links
Does this pull request change what data or activity we track or use?
No tracking changes. The paid CTA carries the
from=jetpack-stats-pricing-gridreferrer Calypso already records; the free CTA performs no checkout at all.Testing instructions
Setup
jp build packages/stats-admin plugins/jetpackadd_filter( 'jetpack_stats_pricing_grid_show', '__return_true' );Test 1: Unconnected site → pricing grid
admin.php?page=stats— the Search-style pricing table renders: paid column highlighted with localized monthly price, free column at 0.view=traffic); returning toadmin.php?page=statskeeps showing the dashboard — the grid is dismissed.wp option delete jetpack_stats_pricing_grid_dismissed, then click Get Paid Stats → the Odyssey purchase page loads in wp-admin with the tier slider; after "I will do it later" the grid likewise stays dismissed.Test 2: Site connected before launch → Odyssey dashboard
jetpack_stats_first_connected_atoption).Test 3: Site with a Stats plan → Odyssey dashboard
Regression checks
noheaderrequests keep the legacy raw-image path).view_statscapability).jp test php packages/stats-admin— 49 tests pass (Eligibility_Test,Dashboard_Test).