Modules: rewrite legacy Backbone list-table as React + <AdminPage>#48314
Modules: rewrite legacy Backbone list-table as React + <AdminPage>#48314CGastrell wants to merge 8 commits into
Conversation
The Modules admin page (`admin.php?page=jetpack_modules`) was the last classic Jetpack admin screen still rendered through the Backbone list-table view. This commit replaces that view with a React tree that mounts under a single `<div id="jp-modules-admin-root">`, dressed in the shared `jetpack-admin-page-layout` SCSS mixin and `<AdminPage>` chrome — matching the rest of the admin-page-layout-unification series. Visual goal was parity with trunk: two-column layout (list left, filter sidebar right), preserved active-row highlight, preserved "Configure" link before each action, preserved offline-mode handling and unavailable_reason text. Modernization comes from primitive swaps: Activate / Deactivate buttons become a <ToggleControl>, segmented filters become a <ToggleGroupControl>, and the top-right Dashboard / Settings links are passed through the AdminPage actions slot, replacing the legacy masthead buttons emitted by wrap_ui(). Plumbing -------- * class.jetpack-admin-page.php: render() now bypasses wrap_ui() for both page=jetpack and page=jetpack_modules, so the React tree is the only chrome on the page (no double masthead, no double footer). * class.jetpack-settings-page.php: page_render() collapses to a single root div plus the noscript / REST-disabled notices. * class.jetpack-modules-list-table.php: drops the three Backbone wp_register_script calls and enqueues the new modules-admin bundle plus the existing jetpackModulesData localized blob. * tools/webpack.config.js: adds modules-admin as a sibling entry to network-admin. * _inc/client/modules-admin/: new entry — index.tsx (React app) and style.scss (mixin scope + grid layout). Backbone sources (_inc/jetpack-modules.js, .models.js, .views.js) are left in place but no longer enqueued, so a single-commit revert restores the old experience. Deferred to follow-ups ---------------------- * Bulk activate / deactivate (dropdown + Apply, with row checkboxes). * Thickbox "More info" per-module modal (long_description / modalinfo). * URL state sync for search / filter / sort / tag via replaceState. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
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: The Jetpack plugin has different release cadences depending on the platform:
If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack. |
Code Coverage SummaryCoverage changed in 3 files.
1 file is newly checked for coverage.
Full summary · PHP report · JS report Coverage check overridden by
Coverage tests to be added later
|
* Changelog `Type: changed` is invalid for `plugins/jetpack` (the plugin's changelogger config only accepts major / enhancement / compat / bugfix / other). Switch to `enhancement`. * `<ToggleControl>` requires a `label` prop in its TS signature even though we render it visually unlabeled (the row name beside it acts as the label, and the per-row `aria-label` carries the accessibility name). Pass an empty string to satisfy the type. * `new Jetpack_Modules_List_Table()` is intentional fire-and-forget — the constructor enqueues the React bundle. Suppress Phan's PhanNoopNew with an inline annotation explaining the side effect. No behavior change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per project policy "use @wordpress/ui first, fall back to @wordpress/components
only when ui doesn't ship the primitive": swap the two header-action primitives
that have ui equivalents.
* `Button` from @wordpress/components → `Button` from @wordpress/ui, with
`nativeButton={ false }` + `render={ <a href="..." /> }` for navigational
rendering (the same pattern Jetpack already uses elsewhere, e.g. AI MCP
setup).
* `__experimentalHStack` from @wordpress/components → `Stack` from @wordpress/ui
with `direction="row" gap="sm"`. Loses one experimental import in the
process.
ui doesn't ship `ToggleControl`, `ToggleGroupControl`, or `SearchControl` yet,
so those stay imported from @wordpress/components. `Page` (the AdminPage
chrome) is already pulled from @wordpress/admin-ui transitively via
@automattic/jetpack-components' AdminPage component.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The original commit's comments referenced PR-history phrasing like "Option C draft v1", "v1 scope", and "deferred follow-ups" — context that belongs in the PR description, not in code that future readers will see in isolation. Rewrite the file/method header comments and inline annotations to describe what the code does and why it exists, without referring to the rewrite itself or the migration milestone. No behavior change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Search, view, sort, and tag filters now seed from `URLSearchParams` on mount and reflect into the URL on every change. Defaults are dropped so a clean URL stays clean. Refreshing or sharing the URL restores all four filters; visual is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps each module name in an anchor pointing at item.learn_more_button (the module's docs / product / wp-admin URL), opening in a new tab. This matches trunk's Backbone js_template behavior. Modules without a learn_more_button keep the static name label. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a per-row checkbox column, a master "select all" checkbox scoped to the currently filtered + available rows (with indeterminate when partial), and a Bulk actions select + Apply button above the list. Submitting routes to the existing admin.php?page=jetpack&action=bulk-(activate|deactivate) handler with nonces.bulk; the server validates the nonce, runs activate/deactivate per slug, and redirects back so module state refreshes naturally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sets the @wordpress/ui Button's `loading` prop while the bulk navigation is in flight so the button shows a spinner and is aria-disabled, instead of looking idle while the page reloads. Yields one frame via setTimeout before assigning window.location.href so React paints the loading state before the navigation starts. Also drops the invalid `variant="secondary"` (not a valid @wordpress/ui Button variant; was silently falling through to the default solid look). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Honestly, to me, it would make sense to figure out which of these features we want on the Products page, and finally ditch having two versions of the same thing. ;-) |
Point taken but, does the page work as expected? |
Proposed changes
Modules admin page (
admin.php?page=jetpack_modules) — the last classic Jetpack admin screen still rendered through the Backbone list-table view — is rewritten as a React tree wrapped in<AdminPage>, dressed in the sharedjetpack-admin-page-layoutSCSS mixin (matching the rest of the admin-page-layout-unification series).unavailable_reasontext.<ToggleControl>.<ToggleGroupControl>.<AdminPage>'sactionsslot.class.jetpack-admin-page.php::render()now bypasseswrap_ui()for bothpage=jetpackandpage=jetpack_modulesso the React tree is the only chrome on the page (no double masthead, no double footer).class.jetpack-settings-page.php::page_render()collapses to a single root div + the noscript / REST-disabled notices.class.jetpack-modules-list-table.phpdrops the three Backbonewp_register_scriptcalls and enqueues the newmodules-adminbundle plus the existingjetpackModulesDatalocalized blob._inc/jetpack-modules.js,.models.js,.views.js) are left in place but no longer enqueued, so a single-commit revert restores the old experience.Subsequent commits — feature parity restored
The three items originally cut from v1 have now landed in this PR (separate commits, easy to read in isolation):
c11e6eafd8— URL state sync.search/filter/sort/tagseed fromURLSearchParamson mount and reflect into the URL viahistory.replaceStateon every change. Defaults are dropped so a clean URL stays clean.7fcd75fc12— Module name link. Each module name is rendered as<a href={item.learn_more_button} target="_blank" rel="noreferrer noopener">— matching trunk's Backbonejs_templatebehavior. Modules without alearn_more_buttonkeep the static name label.087bfcbf62— Bulk activate / deactivate. Adds a per-row checkbox column, a master "select all" checkbox scoped to the current view (with indeterminate state when partial), and a Bulk actions select + Apply button above the list. Submits to the existingadmin.php?page=jetpack&action=bulk-(activate|deactivate)handler withnonces.bulk— the server validates the nonce, runs activate/deactivate per slug, and redirects back so module state refreshes naturally.Before / After
Trunk (before): legacy Backbone list-table.
This PR (after): same layout, modernized primitives, bulk-actions toolbar above the list.
Updated screenshots showing the bulk-actions toolbar and the URL-sync state are available in
.playwright-mcp/modules-smoke/—iter4-urlsync-desktop-restored.png(filters round-tripped via URL),iter6-bulk-desktop.png(toolbar at rest),iter6-bulk-selected.png(2 rows ticked, "Activate" chosen, Apply enabled), anditer6-bulk-mobile.png.Related product discussion/links
Does this pull request change what data or activity we track or use?
No. The existing
wpa_page_viewtracks event withpath: 'old_settings'is preserved verbatim. No new events, no new permissions, no new endpoints. The existing nonce-protected URLs (admin.php?page=jetpack&action=activate|deactivate&module=...for single-row toggle,admin.php?page=jetpack&action=bulk-(activate|deactivate)&modules[]=...for bulk) are reused as-is.Testing instructions
Core layout
admin.php?page=jetpack_modules).<ToggleControl>— the page should reload (server redirect) and reflect the new state.admin.php?page=jetpack#/dashboardand#/settingsrespectively.admin.php?page=jetpack(the main dashboard) and confirm it still renders correctly (the wrap_ui bypass change touched both pages).URL state sync
share, View=Active, Sort by=Newest, Show=Social. Confirm the URL becomes?page=jetpack_modules&search=share&filter=true&sort=introduced&tag=Social.?page=jetpack_modules.Module name link
learn_more_buttonURL (a jetpack.com docs / product / wp-admin link) in a new tab, matching trunk.learn_more_buttonshould keep the static name label (no anchor).Bulk activate / deactivate