Odyssey Stats: Hide Jetpack upsells for products the site already owns - #112461
Conversation
The Odyssey Stats upsell section showed a product whenever the site was missing any one of the product's feature slugs, so a site on Jetpack Security (which bundles VaultPress Backup) was still prompted to buy Backup, and only checkout revealed the product was already included. Invert the check: each upsell now lists the entitlement features that mean the product is owned, and the card is hidden when the site has all of them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Jetpack Cloud Live (direct link)
Automattic for Agencies Live (direct link)
Dashboard Live (dotcom) (direct link)
|
|
Here is how your PR affects size of JS and CSS bundles shipped to the user's browser: Async-loaded Components (~179 bytes removed 📉 [gzipped]) DetailsReact components that are loaded lazily, when a certain part of UI is displayed for the first time. Legend What is parsed and gzip size?Parsed Size: Uncompressed size of the JS and CSS files. This much code needs to be parsed and stored in memory. Generated by performance advisor bot at iscalypsofastyet.com. |
There was a problem hiding this comment.
Pull request overview
Updates the Odyssey Stats Jetpack upsell section to hide upsell cards when the site already owns the relevant product entitlements (including via bundled plans), addressing redundant upsells like prompting Backup on Jetpack Security sites.
Changes:
- Replaces the old “missing any feature → show upsell” logic with
ownedFeaturesand a “has all owned features → hide upsell” check. - Extracts the visibility logic into a pure
filterUpsellsBySiteFeatures()helper and wires it into the section. - Adds unit tests covering the Jetpack Security (Daily) scenario and other key ownership combinations.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| client/my-sites/stats/jetpack-upsell-section/upsell-card/available-upsells.tsx | Switches upsell ownership modeling to ownedFeatures and introduces the reusable filtering helper. |
| client/my-sites/stats/jetpack-upsell-section/index.tsx | Uses the new helper to compute visible upsells based on active site entitlements. |
| client/my-sites/stats/jetpack-upsell-section/test/available-upsells.tsx | Adds unit tests to ensure bundled-plan ownership hides the correct upsell cards. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // owned. The upsell is hidden when the site has all of them. | ||
| ownedFeatures: string[]; | ||
| checkoutSlug: string; | ||
| checkoutUrl: string | null; |
| export function getUpsellFeatureSlugs(): string[] { | ||
| const upsells = getAvailableUpsells(); | ||
| return upsells.flatMap( ( upsell ) => upsell.features ); | ||
| return upsells.flatMap( ( upsell ) => upsell.ownedFeatures ); | ||
| } |
kangzj
left a comment
There was a problem hiding this comment.
Tests well and looks good.
I think we should probably come back later and see if there are existing feature sets to be used rather than creating our own, which would be hard to keep in sync.
Thanks for the fast fix @dognose24. Appreciate it!
|
@kangzj Thanks for the review! I dug into your "existing feature sets" question — findings: Where the old lists came from. The feature lists were introduced with the section in #99048 (Feb 2025) and the Security list was expanded to 18 slugs three days later in #99471. The shape of that list (including unrelated slugs like Existing feature sets. Every entitlement slug this PR uses already has a constant in Follow-up. This PR is already in the merge queue (its branch is locked), so I'll land the constant swap — together with Copilot's two nits (optional |
|
Follow-up with the constant swap + Copilot nits is up: #112497 |
Fixes STATS-286
Proposed Changes
ownedFeaturesentitlement slugs that mean the product is already owned (directly or via a bundling plan), and the card is hidden when the site has all of them.filterUpsellsBySiteFeatures()helper and add unit tests covering the Security-plan scenario from the issue.Before / after, per card
The old
featuresarrays were product selling-point dumps used as ownership checks — "show the card if the site lacks any slug in the list". The newownedFeaturesare the minimal entitlement set meaning "the product's substance is already owned" — "hide the card if the site has all of them".backups,full-activity-log,real-time-backups,priority_supportbackupsreal-time-backups(Daily = daily backups) → wrongly shown. New: hasbackups→ hidden.wordads,google-analytics,core/audio,simple-payments, …) plus bothbackups-dailyandreal-time-backupsbackups,scan,antispamreal-time-backups,google-my-business→ Security owners were upsold Security. New: has all three → hidden.search,instant-searchsearchvideopress,videopress-1tb-storagevideopresscloud-critical-csssocial-enhanced-publishingBecause the old Security list contained both mutually-exclusive backup granularities (
backups-dailyandreal-time-backups), it was unsatisfiable for any plan except Complete — matching the report that the card showed "for any site that isn't on the Jetpack Complete plan".Why are these changes being made?
Testing Instructions
yarn test-client client/my-sites/stats/jetpack-upsell-section— 4/4 tests pass.add_filter( 'option_jetpack_active_plan', fn( $plan ) => array_merge_recursive( $plan, [ 'features' => [ 'active' => [ 'backups', 'scan', 'antispam' ] ] ] ) );in an mu-plugin. The section reads entitlements from these slugs, so the filter exercises the same code path as a real purchase.Test results (verified on a self-hosted Jetpack site, local Docker)
Verified against real plan data (the section reads entitlements from the
jetpack_active_planfeature slugs thatstats-admininlines into the page):backups/searchare in the site's active features); Security, VideoPress, Boost and Social still show.backups,backups-daily,scan,antispambut notreal-time-backups): only VideoPress, Boost and Social show. Under the previous logic this site was shown both the Backup and Security cards, because the missingreal-time-backupsslug satisfied the "any feature missing" check.ownedFeatures(backups,scan,antispam,search) match what the plan actually reports.searchandinstant-search, so the Search card is also hidden for free-tier Search sites — there is no feature slug distinguishing free from paid Search; keying off entitlement is the intended behavior here.My purchases
Upsell cards
Pre-merge Checklist
🤖 Generated with Claude Code