Skip to content

Premium Analytics: point Jetpack's analytics links at the new dashboard - #50926

Merged
louwie17 merged 8 commits into
trunkfrom
wooa7s-1780-premium-analytics-update-jetpack-surfaces-that-still-link-to
Aug 5, 2026
Merged

Premium Analytics: point Jetpack's analytics links at the new dashboard#50926
louwie17 merged 8 commits into
trunkfrom
wooa7s-1780-premium-analytics-update-jetpack-surfaces-that-still-link-to

Conversation

@louwie17

@louwie17 louwie17 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Fixes WOOA7S-1780

Proposed changes

Several Jetpack surfaces still linked to admin.php?page=stats, which Premium Analytics replaces when jetpack_premium_analytics_enabled is on. Some of those URLs are confined to one spot, so the logic was updated inline; the rest needed the same non-trivial URL construction, so they go through one shared helper.

Every surface keeps its existing Stats link untouched and only routes to the dashboard where that has replaced the Stats page — the old links already worked, so nothing rebuilds them.

Updated inline

  • packages/my-jetpack — Stats card (src/products/class-stats.php). get_manage_url() returns the dashboard URL; get_purchase_url() returns null, the base-class default, which falls the action button back to the existing #/add-stats interstitial. The card's JS needs no URL logic at all — it already receives the destination as the product's manage_url. A premiumAnalyticsEnabled entry in get_my_jetpack_flags() covers the one thing the URL can't carry: whether to append force_refresh=1, which only the cached Stats page wants.

Updated via the shared helper

All in plugins/jetpack, each hasAnalyticsDashboard() ? getAnalyticsUrl( … ) : <existing Stats URL>:

Surface File Destination
At-a-Glance "View detailed stats" _inc/client/at-a-glance/stats/dash-stats-bottom.jsx dashboard root
At-a-Glance chart bars _inc/client/at-a-glance/stats/index.jsx Traffic section, with the bar's date range
Newsletter dashboard widget (×3 links) modules/subscriptions/newsletter-widget/src/helpers.ts Subscribers section
Block editor "delivery details" extensions/shared/memberships/subscribers-affirmation.jsx that post's Email opens tab

The helper

plugins/jetpack/_inc/shared/analytics-url.ts exports two functions:

  • hasAnalyticsDashboard() — whether the dashboard has replaced the Stats page. Premium Analytics publishes an analytics key on jetpack_admin_js_script_data (page slug, can_view, site timezone) only where it is the analytics UI, so its presence is the signal and no caller has to ask Jetpack about the feature flag.
  • getAnalyticsUrl( view ) — takes a discriminated union (dashboard or post, each with an optional section and date range) and resolves the rest: page slug, section vocabulary, date encoding. Returns null when the user can't open the dashboard, so call sites hide the control.

It lives in the plugin rather than a shared package because the plugin is its only consumer. Two details are worth a reviewer's eye:

  • Date ranges are offset-bearing instants, not bare dates. The dashboard writes from/to as yyyy-MM-dd'T'HH:mm:ss.SSSxxx in the site timezone and parses them back as instants, so a bare YYYY-MM-DD would be read as UTC midnight and land a day early west of UTC. date-fns + @date-fns/tz do the conversion, so a day that crosses a DST transition gets the correct — and different — offset at each boundary.
  • The inner search is encoded twice. @wordpress/boot keeps the router's whole path-and-search in a single p param and decodes twice on the way back. A single layer would decode the + of the UTC offset to a space and the date picker would reject the range. A test pins this.

js-packages/script-data is untouched.

Note on capabilities

This branch originally hid analytics links from Authors and Editors, because the dashboard required manage_options. WOOA7S-1779 has since landed on trunk, mapping jetpack_view_analytics to manage_options OR view_stats, and can_view now reports that meta capability — so a site that granted an editor view_stats keeps that access. The earlier caveat no longer applies.

Related product discussion/links

Does this pull request change what data or activity we track or use?

No. The new script-data payload is a bool, two strings and one current_user_can() check; no new tracking events, and the existing Tracks calls on these links are unchanged.

Testing instructions

The flag-off path is the main regression risk, so start there.

1. Flag off (default) — nothing should change

With jetpack_premium_analytics_enabled unset, confirm each link still points at admin.php?page=stats:

  • admin.php?page=jetpack#/dashboard → first card: "View detailed stats", and click a chart bar (try each of the Days / Weeks / Months tabs).
  • admin.php?page=my-jetpack → Stats card: primary button, and "View detailed stats" when the plan status offers an upgrade (the click should still carry &force_refresh=1).
  • wp-admin/index.php → "Jetpack Newsletter" widget: the two subscriber counts and the "View subscriber stats" quick link.
  • Post editor → pre-publish panel or the Newsletter toolbar menu, on a post with a subscription access level set → "delivery details".

2. Flag on, as an admin

wp option update jetpack_premium_analytics_enabled 1

Every link above should now land on admin.php?page=jetpack-premium-analytics-wp-admin with the right route:

  • The At-a-Glance chart bar should open the Traffic section with the date range matching the bar you clicked and the active tab — a "Weeks" bar should open that whole week, not just its first day. Worth checking on a site whose timezone is west of UTC, and on a bar spanning a DST change.
  • The newsletter widget links should open the Subscribers section.
  • "delivery details" should open that post's Email opens tab.

3. Flag on, as an Editor (the row most likely to be skipped)

Switch to a user with edit_posts but not manage_options:

  • The At-a-Glance stats card should still render, but both links should be absent.
  • The newsletter widget's subscriber counts should render as plain text, not links, and the "View subscriber stats" quick link should be gone.
  • In the editor, the "delivery details" sentence should read as plain text.
  • Confirm no 403 is reachable by clicking anything.

4. Flag on, tiered Stats plan

admin.php?page=jetpack-premium-analytics-wp-admin → Traffic, near the bottom: the Plan usage meter and its "Upgrade now" link should be unchanged from trunk. Needs a site with views_limit > 0, otherwise you get the empty state.

Automated: jetpack test php packages/premium-analytics, jetpack test php packages/my-jetpack, jetpack test js packages/my-jetpack, and for the plugin pnpm run test-gui / test-client / test-extensions.

Note that _inc/client/at-a-glance/**/test/component.jsx is dormant in CI — the gui testMatch only picks up component.js. I wired in the new range test explicitly but did not enable those two files: they fail on an unrelated @wordpress/theme ESM transform error in that config, which deserves its own issue.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DQezwoePExAo1sc1onk7fB

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.

  • To test on WoA, go to the Plugins menu on a WoA dev site. Click on the "Upload" button and follow the upgrade flow to be able to upload, install, and activate the Jetpack Beta plugin. Once the plugin is active, go to Jetpack > Jetpack Beta, select your plugin (Jetpack or WordPress.com Site Helper), and enable the wooa7s-1780-premium-analytics-update-jetpack-surfaces-that-still-link-to branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack wooa7s-1780-premium-analytics-update-jetpack-surfaces-that-still-link-to
bin/jetpack-downloader test jetpack-mu-wpcom-plugin wooa7s-1780-premium-analytics-update-jetpack-surfaces-that-still-link-to

Interested in more tips and information?

  • In your local development environment, use the jetpack rsync command to sync your changes to a WoA dev blog.
  • Read more about our development workflow here: PCYsg-eg0-p2
  • Figure out when your changes will be shipped to customers here: PCYsg-eg5-p2

@github-actions github-actions Bot added [Feature] Subscriptions All subscription-related things such as paid and unpaid, user management, and newsletter settings. [JS Package] Script Data [Package] My Jetpack [Package] Premium Analytics [Plugin] Jetpack Issues about the Jetpack plugin. https://wordpress.org/plugins/jetpack/ [Tests] Includes Tests Admin Page React-powered dashboard under the Jetpack menu RNA labels Jul 30, 2026
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add a "[Status]" label (In Progress, Needs Review, ...).
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

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:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

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.

@jp-launch-control

jp-launch-control Bot commented Jul 30, 2026

Copy link
Copy Markdown

Code Coverage Summary

Coverage changed in 10 files. Only the first 5 are listed here.

File Coverage Δ% Δ Uncovered
projects/plugins/jetpack/_inc/client/at-a-glance/stats/index.jsx 0/86 (0.00%) 0.00% 6 💔
projects/packages/my-jetpack/_inc/components/stats-section/index.jsx 3/51 (5.88%) -0.50% 4 💔
projects/plugins/jetpack/_inc/client/at-a-glance/stats/dash-stats-bottom.jsx 0/12 (0.00%) 0.00% 1 ❤️‍🩹
projects/plugins/jetpack/extensions/shared/memberships/subscribers-affirmation.jsx 73/129 (56.59%) 0.24% 1 ❤️‍🩹
projects/packages/my-jetpack/src/class-initializer.php 129/357 (36.13%) 0.18% 0 💚

2 files are newly checked for coverage.

File Coverage
projects/plugins/jetpack/_inc/client/at-a-glance/stats/chart-bar-range.js 16/16 (100.00%) 💚
projects/plugins/jetpack/_inc/shared/analytics-url.ts 30/30 (100.00%) 💚

Full summary · PHP report · JS report

If appropriate, add one of these labels to override the failing coverage check: Covered by non-unit tests Use to ignore the Code coverage requirement check when E2Es or other non-unit tests cover the code Coverage tests to be added later Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR I don't care about code coverage for this PR Use this label to ignore the check for insufficient code coveage.

@louwie17
louwie17 force-pushed the wooa7s-1780-premium-analytics-update-jetpack-surfaces-that-still-link-to branch from 18af56b to aa9d523 Compare August 4, 2026 13:21
@louwie17
louwie17 marked this pull request as ready for review August 4, 2026 13:22
@louwie17
louwie17 requested review from a team as code owners August 4, 2026 13:22
chihsuan
chihsuan previously approved these changes Aug 5, 2026

@chihsuan chihsuan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work @louwie17 tested well as described. 👍

One thing worth surfacing in case it hasn't come up: since #50915 the legacy Stats menu deliberately keeps rendering alongside Stats v2 while the flag is on, so the docblocks here that describe it in the present tense ("the Stats page is no longer registered") don't match what's on screen yet.

I also left a question to confirm the plan-usage widget behavior but non-blocking. Thanks!

function PlanUsageBar( { limit, usage, daysToReset, overLimitMonths }: PlanUsageBarProps ) {
const usageValue = usage ?? 0;
const isOverLimit = usageValue >= limit;
const upgradeHref = upgradeUrl();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once a site is over its limit, this leaves it with a warning but no action it can take, so the gap may become more visible as the dashboard rolls out.

Just wanted to confirm keep the CTA and point it there when that route is available or we want to ship without an upgrade entry point?

@louwie17
louwie17 force-pushed the wooa7s-1780-premium-analytics-update-jetpack-surfaces-that-still-link-to branch from d6f6960 to b775ef8 Compare August 5, 2026 09:12
louwie17 and others added 8 commits August 5, 2026 12:28
Seven links across Jetpack still pointed at the legacy Stats page. Rather
than spell a URL at each call site, add getAnalyticsUrl() to script-data:
callers describe where they want to go and the helper resolves which
analytics UI the site runs, the page slug, the site and blog identifiers,
the section vocabulary, and the date encoding.

Premium Analytics announces itself by publishing an `analytics` key on
jetpack_admin_js_script_data. Its presence is the branch signal, so no
caller has to ask Jetpack about the feature flag; when absent, the helper
builds the legacy Stats URLs byte-for-byte as before.

Notable details:

- The dashboard keeps its whole router path in a single `p` search param,
  and reads from/to as offset-bearing instants in the site timezone. A
  bare YYYY-MM-DD would be parsed as UTC midnight and land a day early
  west of UTC, so the helper takes calendar days and owns the encoding,
  reading the zone offset at each boundary so a DST-transition day gets
  the right offset on each end.
- At-a-Glance chart bars now open the period the bar stands for. The old
  link ignored the Days/Weeks/Months tab and always deep-linked one day.
- getAnalyticsUrl() returns null when the user lacks the capability to
  open the dashboard, and every call site hides its control rather than
  rendering a dead link.
- The plan-usage upgrade CTA is removed. It pointed at a Stats tier
  purchase screen that was a Calypso route inside the CDN-served Odyssey
  bundle, so it left with that dashboard and has no counterpart here.
- My Jetpack's Stats get_purchase_url() returns null, the base-class
  default, which falls back to the existing /add-stats interstitial.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DQezwoePExAo1sc1onk7fB
…nks to

Three reductions, all from review feedback:

- Drop the `report` and `video` views. Nothing constructed them; they were
  built from the design doc rather than from a caller, along with a
  13-member report id union and their tests.

- Stop hand-rolling timezone math. `toFixedOffset`, `offsetAt`,
  `offsetToMinutes` and the two-pass DST offset read are replaced by
  date-fns + @date-fns/tz, the same libraries the dashboard's own
  `dateToISOStringWithTZ` uses. The DST-transition test still passes, so
  the bespoke version was only reimplementing the library.

- Stop rebuilding legacy Stats URLs. Those links already worked, so each
  call site keeps its original expression untouched and only routes to
  the dashboard when it has replaced the Stats page. That also restores
  `force_refresh=1` on My Jetpack's card and the prop threading this had
  removed, so the non-dashboard path is now unchanged from trunk rather
  than merely equivalent to it.

`hasAnalyticsDashboard()` is the new gate. It answers a different
question from a null URL: false means "keep your existing link", whereas
null means the dashboard is the analytics UI but this user cannot open
it. Those must not collapse — `stats_admin_menu` is not registered once
the dashboard replaces Stats, so there is no page to fall back to.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DQezwoePExAo1sc1onk7fB
The helper was in js-packages/script-data, which meant adding date-fns to
a previously zero-dependency package that 23 projects depend on and that
is autopublished to npm. It only ever had two consumers, and one of them
did not need it at all.

My Jetpack drops it entirely. Its Stats card already receives the
destination as the product's `manage_url`, which `Stats::get_manage_url()`
resolves server-side, so the JS was rebuilding a URL PHP had already
sent. The one thing the URL cannot carry is whether to append
`force_refresh=1` — that is now a `premiumAnalyticsEnabled` entry in
`get_my_jetpack_flags()`, alongside the existing showFullJetpackStatsCard
and isStatsModuleActive flags.

That leaves the Jetpack plugin as the only consumer, so the helper moves
to `_inc/shared/analytics-url.ts`. Its three call sites live in different
trees built by two webpack configs, and the extensions build has no
bare-specifier root, so each imports it by relative path. `_inc/shared/`
is new: `_inc/client`, `modules/` and `extensions/` had no shared JS
between them before this.

script-data goes back to exactly trunk apart from the `analytics` key's
type — the shape of the global is its job, building URLs from it is not.
The jest bootstrap it needed goes away with the test, which now runs
under the plugin's existing client config.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DQezwoePExAo1sc1onk7fB
…fy the helper

script-data is now byte-identical to trunk. The `analytics` script-data key is
declared where it is read, via module augmentation, so the package that types
the global does not carry a key only the Jetpack plugin uses.

Test simplification (405 -> 227 lines), mostly by table-driving repetitive
cases. One test was worse than redundant: chart-bar-range's "reads the calendar
day in UTC" case monkey-patched Date.prototype.getTimezoneOffset, which the
implementation never calls, so it passed regardless of correctness. Removed,
and timezone independence is now verified by running both date suites under
UTC, Pacific/Kiritimati (+14), Pacific/Niue (-11), America/New_York and
Asia/Kathmandu (+05:45). Added the DST fall-back case, so both a 23-hour and a
25-hour day are covered rather than only spring-forward.

Cleanups from a review pass:
- Drop the `encoded.includes( 'Invalid' )` guard. date-fns `format()` throws a
  RangeError for an unusable zone; it never returns an "Invalid" string, so
  only the catch was ever reachable.
- DASHBOARD_SECTIONS was an identity map keyed by its own union. It is a list
  of accepted slugs, which is all it was doing.
- Inline analyticsPath, whose docblock restated its signature.
- Drop `export` from three types nothing imports, and the WHY repeated three
  times in the same file.
- barLink took siteAdminUrl and siteRawUrl as parameters while already reading
  this.props on its first line.
- format_gmt_offset used floor with a 1e-6 epsilon to guard a rounding case
  that cannot arise for quarter-hour offsets; round() says the same thing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DQezwoePExAo1sc1onk7fB
Roughly half of the new files were comment. Kept the reasoning a reader
cannot infer — why a bare YYYY-MM-DD will not do, why the router's `p`
param is encoded twice, why hasAnalyticsDashboard() differs from a null
URL — and dropped the restatements around it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DQezwoePExAo1sc1onk7fB
Adding date-fns and @date-fns/tz to plugins/jetpack changes peer resolution
for the @wordpress/* packages it shares them with, which pnpm records as new
snapshot keys. Earlier commits added only the importer entries, so the
resolution section was left stale and CI's lock check failed.

Regenerated with `pnpm install --resolution-only`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DQezwoePExAo1sc1onk7fB
Since #50915 the Stats menu deliberately keeps rendering alongside the
dashboard, so the docblocks describing it as deregistered were wrong.
Reworded to the present tense: the dashboard is the analytics UI, and
Stats remains available for now.

The behaviour is unchanged, but its reason was wrong too. Hiding a link
for a user who cannot open the dashboard is right not because there is no
Stats page to fall back to, but because the dashboard capability maps to
`manage_options` or `view_stats` — a user who fails it cannot open the
Stats page either.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DQezwoePExAo1sc1onk7fB
Reverts the plan-usage changes: render.tsx, its test, its story, and the
changelog entry that announced the removal.

The link was removed on the grounds that it pointed at a Stats screen
that no longer existed. It does still exist — since #50915 the Stats menu
keeps rendering alongside the dashboard, and following the link on a
flag-on site lands on a working Stats tier picker. Whether the CTA needs
a new home is a question for when the Stats page actually goes away, not
for this PR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DQezwoePExAo1sc1onk7fB
@louwie17
louwie17 force-pushed the wooa7s-1780-premium-analytics-update-jetpack-surfaces-that-still-link-to branch from b775ef8 to 3823ce2 Compare August 5, 2026 10:35
@louwie17
louwie17 merged commit ed4f74c into trunk Aug 5, 2026
112 of 113 checks passed
@louwie17
louwie17 deleted the wooa7s-1780-premium-analytics-update-jetpack-surfaces-that-still-link-to branch August 5, 2026 14:53
@github-actions github-actions Bot added [Status] UI Changes Add this to PRs that change the UI so documentation can be updated. and removed [Status] In Progress labels Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Admin Page React-powered dashboard under the Jetpack menu [Feature] Subscriptions All subscription-related things such as paid and unpaid, user management, and newsletter settings. [JS Package] Script Data [Package] My Jetpack [Package] Premium Analytics [Plugin] Jetpack Issues about the Jetpack plugin. https://wordpress.org/plugins/jetpack/ RNA [Status] UI Changes Add this to PRs that change the UI so documentation can be updated. [Tests] Includes Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants