Skip to content

Newsletter: port Subscribers DataViews onto the modernization chassis#48581

Merged
dhasilva merged 11 commits intotrunkfrom
try/newsletter-modernization-subscribers
May 7, 2026
Merged

Newsletter: port Subscribers DataViews onto the modernization chassis#48581
dhasilva merged 11 commits intotrunkfrom
try/newsletter-modernization-subscribers

Conversation

@keoshi
Copy link
Copy Markdown
Contributor

@keoshi keoshi commented May 6, 2026

Part of #48530 — third of six PRs to modernize Jetpack's Newsletter into a unified wp-admin product. Builds on the page-shell chassis from #48574 (now on trunk) by filling the empty <Page> body with the Subscribers DataViews list, the row + bulk action modals, and the subscriber-detail inspector.

Proposed changes

  • Replace the chassis's empty <Page title="Newsletter" /> with <SubscribersBody> — selection, modal orchestration, and the <SubscribersDataViews> mount — wrapped in the same QueryClientProvider and page chrome. Adds the page subtitle ("Manage everyone subscribed to your site.") and the Add Subscribers / More options header actions.
  • Render <SubscriberDetailContent> in the Inspector slot when ?subscriber=…&u=… is present. The chassis already wired route.inspector to that boolean; this PR just fills the slot.
  • Port the Subscribers data layer (_inc/subscribers/data/* REST client + React Query hooks) and the lib helpers (CSV parse, DataViews i18n, site context, plans, status, Tracks, view-state) directly from Newsletter: unify Subscribers + Settings into a single wp-admin product #48420. The existing query-client.ts from the chassis is the same one referenced everywhere.
  • Port the rendering layer — subscribers-data-views.tsx, subscribers-body.tsx, cells/, detail/, modals/, header-actions.tsx, empty-state.tsx, jetpack-logo.tsx — from Newsletter: unify Subscribers + Settings into a single wp-admin product #48420.
  • Lift Gravatar (with hovercard support, SHA-256 hashing, configurable defaultImage) from Forms into @automattic/jetpack-components/gravatar. Forms switches to it; the per-package @gravatar-com/hovercards and js-sha256 deps move with it. Newsletter consumes the lifted version directly.
  • Add WPCOM_REST_API_V2_Endpoint_Subscribers_List registering /wpcom/v2/subscribers/{list,individual,totals,stats,add,remove,products,comp,remove-comp}. Each route proxies to the corresponding WP.com endpoint via Automattic\Jetpack\Connection\Client::wpcom_json_api_request_as_user. Glob-loaded — no loader edits. Route registration is gated behind the same rsm_jetpack_ui_modernization_newsletter filter the UI uses, so on flag-off sites the proxy is a no-op (rest_no_route).
  • Tests: PHPUnit coverage for the new endpoint (filter gate, cap check, input validation across add / remove / individual / stats) and Jest coverage for useSubscriberRemoveMutation (singular vs bulk snackbar copy, the partial-failure aggregator, the MAX_BULK_REMOVE cap).

All gated behind rsm_jetpack_ui_modernization_newsletter. With the flag off, the legacy Newsletter Settings page renders unchanged.

Related product discussion/links

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

  • REST surface, new on Jetpack-connected self-hosted sites. Adds proxies for wpcom/v2/subscribers/{list,individual,totals,stats,add,remove,products,comp,remove-comp}. Each one calls the corresponding WP.com endpoint via the existing site connection — no new identifiers or payloads beyond what WP.com already returns. Mirrors the surface that Newsletter: unify Subscribers + Settings into a single wp-admin product #48420 was about to ship. Routes only register when the modernization filter is on, so flag-off sites expose no new surface.
  • No new Tracks events in this PR. Analytics init still happens once at the page Stage (lifted by the chassis). The jetpack_newsletter_tab_view event from the original umbrella lands in PR 6 along with the rest of the telemetry pass.
  • No changes to privacy notices.
  • add_script_data payload. No new keys.

Testing instructions

Pull this branch, run pnpm install and pnpm jetpack build plugins/jetpack --deps to refresh both Newsletter and the Jetpack plugin's REST surface. Then verify both flag states.

Flag OFF (regression check)

  • Make sure no add_filter( 'rsm_jetpack_ui_modernization_newsletter', ... ) exists in any mu-plugin / wp-config.php / theme.
  • Visit wp-admin/admin.php?page=jetpack-newsletter.
  • Confirm today's Newsletter Settings page renders exactly as before. analytics.initialize still fires from NewsletterSettingsApp (untouched by this PR).
  • curl -s /wp-json/wpcom/v2/subscribers/list -H 'X-WP-Nonce: …' should return rest_no_route — the proxy is gated off too.
  • Visit Forms inbox + Forms responses — subscriber Gravatars still render (regression check on the Gravatar lift; the visual class changes from .jp-forms__gravatar to .jetpack-components-gravatar, but the same border-radius: 50% + flex-shrink rules apply).

Flag ON (Subscribers list)

  • Add add_filter( 'rsm_jetpack_ui_modernization_newsletter', '__return_true' ); to a mu-plugin or Code Snippet.
  • Hard-refresh wp-admin/admin.php?page=jetpack-newsletter.
  • Page header shows the Newsletter title, the "Manage everyone subscribed to your site." subtitle, an Add subscribers primary button, and a More options menu (⋮).
  • DataViews table loads with Name / Subscription type / Email subscription / Date subscribed / Actions columns and the site's real subscribers. Pagination shows the correct total + page count.
  • Sort by Date subscribed. URL gains ?sort=date_subscribed. Reload — sort persists.
  • Click a subscriber row → the inspector slides in with joined date, email status, Emails sent / Open rate / Click rate stats. URL gains ?subscriber=…&u=…. Click the close button — params strip; inspector disappears.
  • Click Add subscribers → modal opens with Manually / Upload CSV / From Substack tabs. Add a couple of test emails via Manually and confirm the success snackbar; the new rows appear in the table.
  • Open a row's menu → Remove subscriber prompts a confirmation; confirming removes the row + fires a success snackbar. On a row with a user_id, Comp a subscription offers a plan picker; Remove comp undoes it.
  • Use the page-header More options ⋮Download as CSV → a CSV download starts.
  • DevTools → Network: wp-build's bundle loads under build/pages/jetpack-newsletter-dashboard/, not the legacy newsletter.js. No console errors.

Toggle round-trip

  • Comment out the add_filter line and refresh — legacy Newsletter Settings page returns immediately.
  • Re-enable the filter and refresh — Subscribers list returns. Both directions reversible without cache clears.

Screenshots

Subscribers list (flag on)

Subscribers tab

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 6, 2026

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 try/newsletter-modernization-subscribers branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack try/newsletter-modernization-subscribers
bin/jetpack-downloader test jetpack-mu-wpcom-plugin try/newsletter-modernization-subscribers

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
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 6, 2026

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.

@github-actions github-actions Bot added the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label May 6, 2026
@keoshi keoshi added [Status] Needs Review This PR is ready for review. and removed [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. labels May 6, 2026
@jp-launch-control
Copy link
Copy Markdown

jp-launch-control Bot commented May 6, 2026

Code Coverage Summary

2 files are newly checked for coverage.

File Coverage
projects/js-packages/components/components/gravatar/index.tsx 0/12 (0.00%) 💔
projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-subscribers-list.php 270/515 (52.43%) 💚

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.

@keoshi keoshi self-assigned this May 6, 2026
@dhasilva dhasilva force-pushed the try/newsletter-modernization-page-shell branch from 126ad0b to 1eb4e7c Compare May 6, 2026 23:14
Base automatically changed from try/newsletter-modernization-page-shell to trunk May 6, 2026 23:39
keoshi and others added 7 commits May 6, 2026 21:00
The Gravatar component (with hovercard support, SHA-256 hashing, and a
configurable defaultImage) moves into @automattic/jetpack-components
exposed at the `./gravatar` subpath. Forms switches to it; the local
copy in `projects/packages/forms/src/dashboard/components/gravatar/`
is removed and the `@gravatar-com/hovercards` and `js-sha256` deps move
with it.

Sets up Newsletter's Subscribers port to consume the same shared
component.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds `WPCOM_REST_API_V2_Endpoint_Subscribers_List` registering
`/wpcom/v2/subscribers/{list,individual,totals,stats,add,remove,products,comp,remove-comp}`.
Each route proxies to the corresponding WP.com endpoint via
`Automattic\Jetpack\Connection\Client::wpcom_json_api_request_as_user`.

The wp-admin Newsletter page (next commit) consumes these endpoints to
render the Subscribers DataViews table and per-subscriber actions on
Jetpack-connected self-hosted sites. Glob-loaded by
`load-wpcom-endpoints.php`, so no loader changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ports the non-rendering plumbing from #48420's Phase 2 into
`_inc/subscribers/`:

- `data/api.ts` + `data/types.ts` — REST client + DataViews/API types.
- React Query hooks for list, individual, totals, stats, add, remove,
  comp / remove-comp, and memberships products.
- `lib/` helpers: CSV parsing, DataViews i18n, site context, subscriber
  helpers, subscription plans + status, Tracks events, and the
  DataViews `View` <-> URL state hook.

No UI yet. The list, cells, modals, and detail pane (next commit)
consume these hooks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops in the rendering layer ported from #48420:

- `subscribers-data-views.tsx` — the table/list with fields, filters,
  pagination, row actions, and bulk actions.
- `subscribers-body.tsx` — selection, modal orchestration, and the
  `<SubscribersDataViews>` mount.
- `cells/{subscriber-identity,subscription-status-cell,subscription-type-cell}`
  — column renderers (identity uses the shared `Gravatar`).
- `detail/subscriber-detail-content.tsx` — sliding inspector content
  with joined date, status, and email engagement stats.
- `modals/{add-subscribers,comp,remove-comp,unsubscribe}-modal` — row
  + bulk action dialogs.
- `header-actions.tsx`, `empty-state.tsx`, `jetpack-logo.tsx` — page
  chrome accents.

Not yet mounted; the next commit wires `<SubscribersBody />` into the
route Stage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The page-shell scaffolded in #48574 now renders the Subscribers list:
the Stage wraps `<SubscribersBody>` (selection, modals, header
actions) inside the existing `<Page>` chrome and `QueryClientProvider`,
adds the page subtitle, and pulls in `route.scss` for the identity
cell, inspector chrome, and detail layout styles.

The route still has no tabs — Settings stays on its legacy surface
behind the modernization filter until a later PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the empty inspector with `<SubscriberDetailContent>` reading
`?subscriber=…&u=…` from the URL — boot's router already gates the
mount on those params via `route.inspector` (added in #48574).

Wraps the slot in its own `QueryClientProvider` so subscriber-detail
queries share the cache the stage has already warmed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dhasilva dhasilva force-pushed the try/newsletter-modernization-subscribers branch from 397ab60 to a43e4c7 Compare May 7, 2026 00:22
dhasilva and others added 2 commits May 6, 2026 22:02
…ter + add tests

Until the dashboard UI ships, register no /wpcom/v2/subscribers/* routes unless
`rsm_jetpack_ui_modernization_newsletter` is on — same gate the wp-build page
already uses. The check lives inside `register_routes()` (which fires on
`rest_api_init`) so theme- or plugin-added filters are applied before it
evaluates, instead of at file-load time when only mu-plugins have run.

Adds a focused test class covering the new gate, the cap check (anon and
under-privileged users), and the handler-level input validation paths in
`add` / `remove` / `individual` / `stats` — the layers most likely to silently
regress without coverage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pass `noopener,noreferrer` to `window.open` for the page-header CSV download —
matches the pattern used everywhere else we open a new tab.

Adds Jest coverage for `useSubscriberRemoveMutation`: singular vs bulk
snackbar copy, the partial-failure path that emits both success and error
notices, and the MAX_BULK_REMOVE cap that protects WP.com from a runaway
selection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dhasilva dhasilva requested a review from a team as a code owner May 7, 2026 01:22
Lifting `Gravatar` into `@automattic/jetpack-components` made every package
whose tests touch that components index transitively evaluate
`gravatar/index.tsx`, which side-effect imports
`@gravatar-com/hovercards/dist/style.css`. Jest's default behavior is to skip
`node_modules` for transformation, so the CSS reached the JS parser and blew
up with `SyntaxError: Unexpected token '.'` across publicize, social, and
the plugins/jetpack admin-page tests.

Two configs maintain their own allowlists: the shared base config in
`tools/js-tools/jest/config.base.js`, and the plugins/jetpack gui runner at
`projects/plugins/jetpack/tests/jest.config.gui.js`. The gui runner's local
pattern overrides the base via `transformIgnorePatterns` set semantics
(union — any matching pattern wins, so a local "ignore" beats a base
"unignore"), so both need the same allowlist entry.

Adds `@gravatar-com` to both alongside `uplot` and `@wordpress/admin-ui`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dhasilva dhasilva force-pushed the try/newsletter-modernization-subscribers branch from 871e74f to 92fca54 Compare May 7, 2026 01:44
@dhasilva dhasilva merged commit 441d4da into trunk May 7, 2026
91 of 92 checks passed
@dhasilva dhasilva deleted the try/newsletter-modernization-subscribers branch May 7, 2026 02:10
@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] Needs Review This PR is ready for review. labels May 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Feature] Contact Form [JS Package] Components [Package] Forms [Package] Newsletter [Package] Wp Build Polyfills [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