-
Notifications
You must be signed in to change notification settings - Fork 336
Description
Feature Description
The Invite component (implemented in #11857) currently fetches all eligible subscribers in a single request and filters them client-side. This works for small sites but does not scale - a site with hundreds of administrators and shared-role users would transfer a large payload on every panel open and search keystroke would filter in-memory.
This issue moves search and pagination to the server. The existing GET core/site/data/email-reporting-eligible-subscribers endpoint gains optional page and search query parameters and returns a paginated response with metadata (users, total, totalPages). The frontend datastore is updated to pass these parameters through, and the Invite component is refactored to delegate filtering to the server instead of performing it locally.
Do not alter or remove anything below. The following sections will be managed by moderators only.
Acceptance criteria
GET /core/site/data/email-reporting-eligible-subscribersaccepts optional query params:page(int, default 1) – current page number.per_page(int, default 20, max 100) – results per page.search(string) – filters users by display name or email (case-insensitive partial match).
- Response includes pagination metadata alongside the user list:
users[]– array of eligible user objects.total– total matching users count.totalPages– calculated total pages.
- Empty search returns all eligible users paginated.
- Non-matching search returns empty
users[]withtotal: 0. - Frontend datastore action/resolver updated to support pagination and search params.
- The existing Invite component implemented in Create the “Invite others to subscribe” component #11857 should be updated to use this new server-side search/filtering.
Implementation Brief
Backend (PHP)
-
Update file
includes/Core/Email_Reporting/Eligible_Subscribers_Query.php:- Define a class constant
PER_PAGE = 20. - Extend
get_eligible_users( int $exclude_user_id, array $args = array() ): arrayto accept an optional$argsarray with keyspage(default 1) andsearch(default''). UsePER_PAGEfor the fixed page size. - Refactor
query_admins()andquery_shared_roles()to accept and forward search args. Add'search' => '*' . $search . '*'with'search_columns' => array( 'display_name', 'user_email' )toWP_User_Queryargs when a search term is provided. - Continue querying all matching IDs from both sources (admins + shared roles), deduplicating via array keys, then apply pagination (
offsetcalculated frompageandPER_PAGE/number=PER_PAGE) to the merged set. - Set
count_totaltotruein queries to enable total count retrieval. - Add new method
get_eligible_users_count( int $exclude_user_id, string $search = '' ): intthat returns the total matching user count without pagination (needed for thetotalandtotalPagesresponse fields).
- Define a class constant
-
Update file
includes/Core/Email_Reporting/REST_Email_Reporting_Controller.php:- Add query parameter schema to the
email-reporting-eligible-subscribersroute registration:page: integer, default 1, minimum 1.search: string, default''.
- Update the route callback to extract
pageandsearchfromWP_REST_Requestand pass them toget_eligible_users(). - Change the response from a flat array of user objects to an object with three keys:
users: array of eligible user objects (same fields as today).total: integer fromget_eligible_users_count().totalPages: calculated asceil( total / PER_PAGE )(using the constant fromEligible_Subscribers_Query).
- Add query parameter schema to the
Frontend (JavaScript)
- Update file
assets/js/googlesitekit/datastore/site/email-reporting.js:- Update the existing
fetchGetEligibleSubscribersStore(or create a replacement) to accept{ page, search }parameters in the control callback, passing them as{ page, search }to the API request. - Change initial state for
eligibleSubscribersfrom a flat value to an object keyed by stringified params (e.g.{}) so each parameter combination is cached independently. - Update the
getEligibleSubscribers( state, { page, search } )selector to return{ users, total, totalPages }from the cached entry, orundefinedon cache miss. - Update the resolver for
getEligibleSubscribersto trigger a fetch when the requested param combination is not yet cached. - Ensure the existing
getEligibleSubscribersselector continues to filter out the current user from theusersarray.
- Update the existing
Frontend — Component Integration (JavaScript)
-
Update file
assets/js/components/email-reporting/InviteOthersToSubscribe/index.js:- Move debounce logic from
InviteSearchInputup to this component: derivedebouncedSearchTermfromsearchTermusing the existinguseDebouncehook (300 ms) so the debounced value drives the API call. - Replace the parameter-less
getEligibleSubscribers()call withgetEligibleSubscribers( { search: debouncedSearchTerm } ). - Update
isLoadingto checkhasFinishedResolution( 'getEligibleSubscribers', [ { search: debouncedSearchTerm } ] ). - Use
totalfrom the selector response (with empty search) to decide whether to show the search input (threshold remains > 6). - Remove the client-side
invitableUsersfilter (user.subscribed). The server already excludes subscribed users. - Pass
usersdirectly from the selector response toInviteUserList. - Remove page-related state — pagination is handled transparently by the resolver.
- Keep existing reset behaviour: clear
searchTermandinviteResultswhenisOpenbecomes false.
- Move debounce logic from
-
Update file
assets/js/components/email-reporting/InviteOthersToSubscribe/InviteSearchInput.js:- Remove internal debounce logic (
useDebounce,debouncedOnChange). The parent now handles debouncing. - Simplify to a controlled input: call
onChangedirectly on every keystroke. - Keep the clear button — call
onChange('')on clear.
- Remove internal debounce logic (
-
Update file
assets/js/components/email-reporting/InviteOthersToSubscribe/InviteUserList.js:- Remove the
useMemo-based client-side filtering. Theusersprop is now pre-filtered by the server. - Remove the
searchTermprop entirely — no longer needed. - Keep existing loading skeleton, empty state, and user row rendering.
- Remove the
Tests
-
PHPUnit tests in
tests/phpunit/integration/Core/Email_Reporting/Eligible_Subscribers_QueryTest.php:- Test pagination returns the correct subset of users.
- Test search filters by display name and email.
- Test
get_eligible_users_count()returns the correct total. - Test combined search + pagination.
- Test deduplication of admin + shared-role users before pagination.
-
PHPUnit tests in
tests/phpunit/integration/Core/Email_Reporting/REST_Email_Reporting_ControllerTest.php:- Test default request (no params) returns first 20 users with
users,total,totalPages. - Test
pageparam returns the correct page. - Test
searchparam filters results. - Test response shape includes
users,total,totalPages. - Test non-matching search returns
{ users: [], total: 0, totalPages: 0 }.
- Test default request (no params) returns first 20 users with
-
Jest tests in
assets/js/googlesitekit/datastore/site/email-reporting.test.js:- Test
getEligibleSubscribersselector returns cached data for matching params. - Test resolver fetches with correct params on cache miss.
- Test different param combinations are cached separately.
- Test
-
Jest tests in
assets/js/components/email-reporting/InviteOthersToSubscribe/index.test.js:- Test initial render fetches with empty search term.
- Test typing in search triggers server fetch after 300 ms debounce.
- Test clearing search resets to unfiltered results.
- Test panel close resets all state.
-
Jest tests in
assets/js/components/email-reporting/InviteOthersToSubscribe/InviteUserList.test.js:- Remove or update tests that relied on client-side
searchTermfiltering.
- Remove or update tests that relied on client-side
-
Jest tests in
assets/js/components/email-reporting/InviteOthersToSubscribe/InviteSearchInput.test.js:- Update tests to reflect removed internal debounce (direct
onChangecalls).
- Update tests to reflect removed internal debounce (direct
QA Brief
- Setup Site Kit with
proactiveUserEngagementfeature flag - Open dev tools, networking tab, and filter requests for
email-reporting-eligible-subscriber - Open a user menu > manage email reports
- Verify that requests are made, and params for
page=Xandsearchare included and response is formatted in:{"users":[],"total":X"totalPages":X}- If neither SC or GA4 modules are shared, there should be no eligible users returned
- When one or both modules are shared, all users with access should be showing
- Close the panel and reopen, if nothing changed requests should be cached and no new request should be made
- When using a search box, requests should be made with
searchparam including the search teerm and proper pagination (page=X)
Note: Ping @zutigrm for a small user generating plugin, so you can quickly create X number of users with set roles on the website for testing
Changelog entry
- Improve user filtering and requests for subscriptions.