Skip to content

Enhancement/#13006 - Implement RRM REST and datastore infrastructure concerning CTAs - #13208

Merged
nfmohit merged 40 commits into
developfrom
enhancement/#13006-rrm-rest-ctas
Aug 22, 2026
Merged

nfmohit merged 40 commits into
developfrom
enhancement/#13006-rrm-rest-ctas

Conversation

@hussain-t

@hussain-t hussain-t commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

Related issue(s):

Relevant technical choices

PR Author Checklist

  • My code is tested and passes existing unit tests.
  • My code has an appropriate set of unit tests which all pass.
  • My code is backward-compatible with WordPress 5.2 and PHP 7.4.
  • My code follows the WordPress coding standards.
  • My code has proper inline documentation.
  • I have added a QA Brief on the issue linked above.
  • I have signed the Contributor License Agreement (see https://cla.developers.google.com/).

Do not alter or remove anything below. The following sections will be managed by moderators only.

Code Reviewer Checklist

  • Run the code.
  • Ensure the acceptance criteria are satisfied.
  • Reassess the implementation with the IB.
  • Ensure no unrelated changes are included.
  • Ensure CI checks pass.
  • Check Storybook where applicable.
  • Ensure there is a QA Brief.
  • Ensure there are no unexpected significant changes to file sizes.

Merge Reviewer Checklist

  • Ensure the PR has the correct target branch.
  • Double-check that the PR is okay to be merged.
  • Ensure the corresponding issue has a ZenHub release assigned.
  • Add a changelog message to the issue.

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown

🤖 This comment is automatically updated by CI workflows. Each section is managed independently.

📚 Storybook for 7a15c7b:

  • Storybook has been deleted.

📦 Build files for 7a15c7b:

  • Build files have been deleted.

🎭 Playwright reports for 7a15c7b:

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown

Size Change: 0 B

Total Size: 3.36 MB

ℹ️ View Unchanged
Filename Size Change
dist/assets/blocks/reader-revenue-manager/block-editor-plugin/editor-styles.css 124 B 0 B
dist/assets/blocks/reader-revenue-manager/block-editor-plugin/editor-styles.js 0 B 0 B 🆕
dist/assets/blocks/reader-revenue-manager/block-editor-plugin/index.js 43.9 kB 0 B
dist/assets/blocks/reader-revenue-manager/common/editor-styles.css 307 B 0 B
dist/assets/blocks/reader-revenue-manager/common/editor-styles.js 0 B 0 B 🆕
dist/assets/blocks/reader-revenue-manager/contribute-with-google/index.js 7.03 kB 0 B
dist/assets/blocks/reader-revenue-manager/contribute-with-google/non-site-kit-user.js 6.21 kB 0 B
dist/assets/blocks/reader-revenue-manager/subscribe-with-google/index.js 7.03 kB 0 B
dist/assets/blocks/reader-revenue-manager/subscribe-with-google/non-site-kit-user.js 6.21 kB 0 B
dist/assets/blocks/sign-in-with-google/editor-styles.css 84 B 0 B
dist/assets/blocks/sign-in-with-google/editor-styles.js 0 B 0 B 🆕
dist/assets/blocks/sign-in-with-google/index.js 18.5 kB 0 B
dist/assets/css/googlesitekit-admin-css-********************.min.css 74.7 kB 0 B
dist/assets/css/googlesitekit-adminbar-css-********************.min.css 12.7 kB 0 B
dist/assets/css/googlesitekit-authorize-application-css-********************.min.css 851 B 0 B
dist/assets/css/googlesitekit-wp-dashboard-css-********************.min.css 9.09 kB 0 B
dist/assets/js/46-********************.js 3.84 kB 0 B
dist/assets/js/65-********************.js 1.03 kB 0 B
dist/assets/js/187-********************.js 101 kB 0 B
dist/assets/js/308-********************.js 3 kB 0 B
dist/assets/js/315-********************.js 3.08 kB 0 B
dist/assets/js/397-********************.js 477 kB 0 B
dist/assets/js/403-********************.js 2.26 kB 0 B
dist/assets/js/509-********************.js 970 B 0 B
dist/assets/js/658-********************.js 52.7 kB 0 B
dist/assets/js/917-********************.js 2.41 kB 0 B
dist/assets/js/analytics-advanced-tracking-********************.js 428 B 0 B
dist/assets/js/googlesitekit-activation-********************.js 29 kB 0 B
dist/assets/js/googlesitekit-ad-blocking-recovery-********************.js 66.7 kB 0 B
dist/assets/js/googlesitekit-admin-pointers-tracking-********************.js 5.36 kB 0 B
dist/assets/js/googlesitekit-adminbar-********************.js 41.4 kB 0 B
dist/assets/js/googlesitekit-api-********************.js 8.04 kB 0 B
dist/assets/js/googlesitekit-block-tracking-********************.js 5.57 kB 0 B
dist/assets/js/googlesitekit-components-********************.js 6.78 kB 0 B
dist/assets/js/googlesitekit-consent-mode-********************.js 26 kB 0 B
dist/assets/js/googlesitekit-data-********************.js 1.83 kB 0 B
dist/assets/js/googlesitekit-datastore-forms-********************.js 7.21 kB 0 B
dist/assets/js/googlesitekit-datastore-location-********************.js 1.6 kB 0 B
dist/assets/js/googlesitekit-datastore-pdf-********************.js 1.22 kB 0 B
dist/assets/js/googlesitekit-datastore-site-********************.js 19.5 kB 0 B
dist/assets/js/googlesitekit-datastore-ui-********************.js 7.37 kB 0 B
dist/assets/js/googlesitekit-datastore-user-********************.js 23.9 kB 0 B
dist/assets/js/googlesitekit-entity-dashboard-********************.js 89.4 kB 0 B
dist/assets/js/googlesitekit-events-provider-contact-form-7-********************.js 2.36 kB 0 B
dist/assets/js/googlesitekit-events-provider-content-events-********************.js 2.04 kB 0 B
dist/assets/js/googlesitekit-events-provider-easy-digital-downloads-********************.js 1.14 kB 0 B
dist/assets/js/googlesitekit-events-provider-mailchimp-********************.js 2.34 kB 0 B
dist/assets/js/googlesitekit-events-provider-ninja-forms-********************.js 2.3 kB 0 B
dist/assets/js/googlesitekit-events-provider-optin-monster-********************.js 2.23 kB 0 B
dist/assets/js/googlesitekit-events-provider-popup-maker-********************.js 2.47 kB 0 B
dist/assets/js/googlesitekit-events-provider-woocommerce-********************.js 1.14 kB 0 B
dist/assets/js/googlesitekit-events-provider-wpforms-********************.js 2.45 kB 0 B
dist/assets/js/googlesitekit-features-********************.js 23.3 kB 0 B
dist/assets/js/googlesitekit-i18n-********************.js 4.43 kB 0 B
dist/assets/js/googlesitekit-key-metrics-setup-********************.js 61.8 kB 0 B
dist/assets/js/googlesitekit-main-dashboard-********************.js 213 kB 0 B
dist/assets/js/googlesitekit-metric-selection-********************.js 65.3 kB 0 B
dist/assets/js/googlesitekit-modules-********************.js 28.3 kB 0 B
dist/assets/js/googlesitekit-modules-ads-********************.js 49.7 kB 0 B
dist/assets/js/googlesitekit-modules-adsense-********************.js 161 kB 0 B
dist/assets/js/googlesitekit-modules-analytics-4-********************.js 285 kB 0 B
dist/assets/js/googlesitekit-modules-pagespeed-insights-********************.js 25.4 kB 0 B
dist/assets/js/googlesitekit-modules-reader-revenue-manager-********************.js 114 kB +692 B (+0.61%)
dist/assets/js/googlesitekit-modules-search-console-********************.js 75.5 kB 0 B
dist/assets/js/googlesitekit-modules-sign-in-with-google-********************.js 35.3 kB 0 B
dist/assets/js/googlesitekit-modules-tagmanager-********************.js 31.9 kB 0 B
dist/assets/js/googlesitekit-notifications-********************.js 85.6 kB 0 B
dist/assets/js/googlesitekit-polyfills-********************.js 227 B 0 B
dist/assets/js/googlesitekit-settings-********************.js 170 kB 0 B
dist/assets/js/googlesitekit-splash-********************.js 92.9 kB 0 B
dist/assets/js/googlesitekit-user-input-********************.js 57.6 kB 0 B
dist/assets/js/googlesitekit-vendor-********************.js 314 kB 0 B
dist/assets/js/googlesitekit-vendor-lazy-pdf-********************.js 21.6 kB 0 B
dist/assets/js/googlesitekit-widgets-********************.js 179 kB 0 B
dist/assets/js/googlesitekit-wp-dashboard-********************.js 69 kB 0 B
dist/assets/js/runtime-********************.js 1.95 kB 0 B
dist/assets/js/sign-in-with-google-********************.js 1.14 kB 0 B

compressed-size-action

@nfmohit nfmohit left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thank you for your excellent work on this, @hussain-t!

I've been looking into this, and while the implementaton, I have a few concerns about the current shape that I think are worth discussing before we lock it in:

  1. The create API is keyed to an arbitrary newsletterConfig payload, rather than a generic config payload.
  2. On the PHP side, we're building the newsletterConfig without going through the model setters, so there's no allowlisted mapping of fields.
  3. The datapoint and datastore are hard-wired to newsletter signup, which means the next CTA type would require widening the public contract again.

What do you think about treating create as a generic type + config payload, and routing type-specific validation and mapping through a CTA type handler registry (both PHP and JS)? Shared fields like displayName would stay on the common create payload, and each type would own its own config allowlist/setters (and JS validation). Adding a new type would then just mean registering a handler, rather than touching the datapoint or datastore contract.

I've put together a quick PoC of this on #13332 to illustrate what I mean (not meant to merge as-is; the tests and call sites still need updating). Happy to walk through it together if that's easier.

@hussain-t

Copy link
Copy Markdown
Collaborator Author

Thanks @nfmohit, great suggestion. I've implemented it on this branch, following your PoC: create now takes a generic type + config, with type-specific validation and mapping in a CTA type handler registry on both sides. Tests are updated on both sides too.

A couple of small deviations from the PoC: the newsletter handler drives its allowlist from field/setter maps, so adding a field is a single entry, and it throws per-field errors (config.title) rather than a generic config, which should make the 400s easier to debug. Happy to change either if you'd prefer your version.

@nfmohit nfmohit left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thank you for addressing my feedback, @hussain-t! I've left a few additional feedback for you to consider, thank you!

Comment thread assets/js/modules/reader-revenue-manager/datastore/ctas.ts Outdated
Comment thread assets/js/modules/reader-revenue-manager/datastore/ctas.ts
Comment thread assets/js/modules/reader-revenue-manager/datastore/ctas.ts Outdated
Comment thread assets/js/modules/reader-revenue-manager/datastore/ctas.ts Outdated
@hussain-t

Copy link
Copy Markdown
Collaborator Author

Thanks for the review, @nfmohit; all addressed. Two notes:

Making the identifiers optional flips requiresParams to false in createFetchStore, and receiveGetCTAs then replaces the params with {} before the reducer runs, so the publication ID can't reach it. CTAs are now stored as a flat list for the configured publication, as fetchPublicationStoreReducerCallback does for publications.

On the JS registration points, it can be reduced to 2 with an assertion signature on validateConfig plus keying the registry off handler.type, but I'd leave it for now. The 4 places today are compile-enforced, so missing one gives you a tsc error at the exact spot, and I'd rather not trade that for type-level inference with no behavior change. Can revisit when a second CTA type lands.

@nfmohit nfmohit left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for addressing my feedback, @hussain-t!

I've left one comment which I'll address myself. Besides:

On the JS registration points, it can be reduced to 2 with an assertion signature on validateConfig plus keying the registry off handler.type, but I'd leave it for now. The 4 places today are compile-enforced, so missing one gives you a tsc error at the exact spot, and I'd rather not trade that for type-level inference with no behavior change. Can revisit when a second CTA type lands.

That sounds fair to me. I've opened #13428 so that we can address this post-launch when we get the chance.

Thanks!

* @param state Data store's state.
* @return {(Array.<Object>|undefined)} The CTAs; `undefined` if not loaded yet.
*/
getCTAs( state: CTAsState ): CTA[] | undefined {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Making the identifiers optional flips requiresParams to false in createFetchStore, and receiveGetCTAs then replaces the params with {} before the reducer runs, so the publication ID can't reach it. CTAs are now stored as a flat list for the configured publication, as fetchPublicationStoreReducerCallback does for publications.

The drawback I see with this approach is that the first successful fetch becomes the list for every publication. A later getCTAs({ publicationID }) for another publication will not refetch, and createCTA for publication A can append onto publication B’s list.

I realize that the express setup will usually use the saved publication, but that is still a broken cache key for a selector whose contract is per-publication.

We could still key the list by publication ID, falling back to the saved publicationID when none is passed. Stamping { ctas, params } from the get control callback is enough for the reducer to key the list after receiveGetCTAs wipes params.

This does complicate the implementation a bit, but it may be worth it as it would be more future-proof.

Given the time constraint, I'll go ahead and implement this. Let me know if you have any thoughts, thanks!

@nfmohit nfmohit left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM ✅

Note: I've verified that the failing E2E test in CI is unrelated to the changes in this PR.

Comment on lines +699 to +705
// Conditionally resolve settings so that the fetch reducer has
// the publication ID to key the list by.
const settingsResolution = maybeResolveSettings( registry, params );

if ( settingsResolution ) {
yield commonActions.await( settingsResolution );
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is a slightly out-of-scope change.

The selector falls back to state.settings.publicationID. If neither an ID nor settings were present, it always returned undefined. The old resolver treated that as “not loaded” and fetched again, so a no-arg getPublication() could loop even after the publication was in state.publications.

This change resolves the settings first to fix that issue.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

While working on this, I realized that we're necessarily duplicating the fallback mechanism on both the client and server sides.

I've opened #13429 to refactor this and relocate the mechanism to the client side, either as a nice-to-have or post-launch.

CC: @hussain-t and @JakePT for comments.

@nfmohit
nfmohit merged commit 68f36b1 into develop Aug 22, 2026
45 of 48 checks passed
@nfmohit
nfmohit deleted the enhancement/#13006-rrm-rest-ctas branch August 22, 2026 20:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add RRM REST and datastore infrastructure concerning CTAs

2 participants