Skip to content

Centralize endpoint infrastructure and remove hard-coded frontend endpoint URLs#23041

Closed
pls78 wants to merge 22 commits intotrunkfrom
refactor-endpoint-logic
Closed

Centralize endpoint infrastructure and remove hard-coded frontend endpoint URLs#23041
pls78 wants to merge 22 commits intotrunkfrom
refactor-endpoint-logic

Conversation

@pls78
Copy link
Copy Markdown
Member

@pls78 pls78 commented Mar 3, 2026

Context

Two related refactoring efforts on top of the AI reorganization branch:

  1. Centralize endpoint infrastructure (cherry-picked from PR Feature/re organize ai code #22615): consolidates duplicated Endpoint_Interface and Endpoint_List classes into a shared src/routes/endpoint/ location, introduces specialized per-domain endpoint interfaces for DI auto-wiring, and creates an Endpoints_Repository base class.
  2. Remove hard-coded frontend endpoint URLs: replaces all hard-coded REST API paths in 6 frontend files with values read from backend-provided window.wpseoAiGenerator.endpoints and window.wpseoAiConsent.endpoints.

Summary

This PR can be summarized in the following changelog entry:

  • Centralizes endpoint infrastructure (Endpoint_Interface, Endpoint_List, Endpoints_Repository) into src/routes/endpoint/.
  • Removes hard-coded REST API paths from AI frontend code, replacing them with backend-provided endpoint paths.

Relevant technical choices:

  • A new to_paths_array() method was added to Endpoint_List instead of modifying to_array().
    • The frontend's apiFetch uses the path: parameter which expects relative paths (e.g., yoast/v1/ai_generator/get_suggestions), while to_array() returns full URLs via rest_url() (e.g., https://site.com/wp-json/yoast/v1/...).
    • Dashboard and Task List consumers depend on to_array() returning full URLs, so a new method avoids breaking those.
  • getSuggestions and bustSubscriptionCache were used directly in hooks/components, not via Redux.
    • A new endpoints store slice was created.
    • This follows the same pattern as packages/js/src/general/initialize.js which reads endpoints from window.wpseoScriptData.dashboard.endpoints.*.
  • Store slices default endpoints to "" instead of hard-coded paths.
    • Actual values come from the backend via initialState merge at store registration time.
    • This is safe because AI features are gated behind conditionals that ensure script data is present.
  • Consent_Endpoint_Interface, Free_Sparks_Endpoint_Interface, and Generator_Endpoint_Interface enable the DI container to automatically wire endpoints into the correct Endpoints_Repository via variadic constructor injection, without manual service registration.
  • The Bust_Subscription_Cache_Endpoint has been added.
    • The route existed but had no corresponding Endpoint class. Now all of 5 the endpoints: consent, getSuggestions, getUsage, freeSparks, bustSubscriptionCache follow the same pattern.
  • The endpoint repository injection and to_paths_array() calls were mirrored into the legacy integration classes (src/ai-consent/ and src/ai-generator/) to preserve backwards compatibility with older versions of Yoast SEO Premium that still reference the old namespace classes.

Test instructions

Test instructions for the acceptance test before the PR gets merged

This PR can be acceptance tested by following these steps:

  • Open a post in the block editor (ensure Yoast SEO Premium is active with a valid subscription).
  • Open the AI generator modal (e.g., via the SEO title or meta description field).
  • Verify the AI generator loads and functions correctly (generates suggestions, shows usage count).
  • Open the browser console and verify no errors related to missing endpoints or failed API calls.
  • If the user has not given AI consent yet, verify the consent modal appears and works correctly.
  • Verify no hard-coded endpoint paths remain: grep -r "yoast/v1/ai" packages/js/src/ should return no results.

Premium compatibility

  • Please also test Premium AI features under the following circumstances:
    1. A version of Premium that supports the AI code restructuring (new Yoast\WP\SEO\AI\ namespaces).
    2. An older version of Premium that still relies on the legacy Yoast\WP\SEO\AI_Consent\ / Yoast\WP\SEO\AI_Generator\ namespaces

Dashboard and Task List tests

  • Please smoke-test:
    • Dashboard
      • Smoke test the Google Site Kit widgets
    • Task List
      • Visit the Task List in Yoast SEO -> General -> Task List
      • Verify it correctly visualizes some tasks
      • Complete a task
        • Verify it correctly shows as completed

Relevant test scenarios

  • Changes should be tested with the browser console open
  • Changes should be tested on different posts/pages/taxonomies/custom post types/custom taxonomies
  • Changes should be tested on different editors (Default Block/Gutenberg/Classic/Elementor/other)
  • Changes should be tested on different browsers
  • Changes should be tested on multisite

Check the browser console for any 404 errors on AI-related REST API calls, which would indicate endpoints are not being passed correctly from the backend.

Test instructions for QA when the code is in the RC

  • QA should use the same steps as above.

QA can test this PR by following these steps:

Impact check

This PR affects the following parts of the plugin, which may require extra testing:

  • AI Generator modal (suggestions, subscription cache busting)
  • AI Consent modal
  • Free Sparks functionality
  • The shared endpoint infrastructure in src/routes/endpoint/ (used by Dashboard and Task List features — verify those still work)

Other environments

  • This PR also affects Shopify. I have added a changelog entry starting with [shopify-seo], added test instructions for Shopify and attached the Shopify label to this PR.
  • This PR also affects Yoast SEO for Google Docs. I have added a changelog entry starting with [yoast-doc-extension], added test instructions for Yoast SEO for Google Docs and attached the Google Docs Add-on label to this PR.

Documentation

  • I have written documentation for this change. For example, comments in the Relevant technical choices, comments in the code, documentation on Confluence / shared Google Drive / Yoast developer portal, or other.

Quality assurance

  • I have tested this code to the best of my abilities.
  • During testing, I had activated all plugins that Yoast SEO provides integrations for.
  • I have added unit tests to verify the code works as intended.
  • If any part of the code is behind a feature flag, my test instructions also cover cases where the feature flag is switched off.
  • I have written this PR in accordance with my team's definition of done.
  • I have checked that the base branch is correctly set.
  • I have run grunt build:images and commited the results, if my PR introduces new images or SVGs.

Innovation

  • No innovation project is applicable for this PR.
  • This PR falls under an innovation project. I have attached the innovation label.
  • I have added my hours to the WBSO document.

Fixes #

pls78 and others added 15 commits March 2, 2026 16:30
Consolidates duplicated Endpoint_Interface and Endpoint_List classes
into a shared routes/endpoint location, removing domain-specific copies.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This is done so that our DI container knows which endpoints to gather in each integration through the endpoints repo
The legacy ai-free-sparks directory still referenced the old
Endpoint_Interface location before it was moved to routes/endpoint/.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update test abstracts and expectations to match the new endpoint
repository dependencies added to integration classes. Also fix
Endpoint_List import namespace and trailing comma CS issue.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add to_paths_array() to Endpoint_List which returns namespace+route
paths instead of full URLs, suitable for frontend apiFetch path usage.

Create Bust_Subscription_Cache_Endpoint implementing
Generator_Endpoint_Interface so it is auto-wired into the DI container
and included in the localized endpoint data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use to_paths_array() instead of to_array() in AI consent and generator
integrations so the frontend receives relative paths compatible with
apiFetch's path parameter rather than full URLs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace hard-coded endpoint strings with values read from the
backend-provided window globals (wpseoAiGenerator.endpoints and
wpseoAiConsent.endpoints).

Add an endpoints store slice for getSuggestions and
bustSubscriptionCache, wire it into the AI generator store, and
remove hard-coded defaults from consent, usage-count, and free-sparks
store slices.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the last two hard-coded endpoint paths: use-suggestions.js now
reads getSuggestions from the Redux store via selectGetSuggestionsEndpoint,
and subscription-error.js reads bustSubscriptionCache via
selectBustSubscriptionCacheEndpoint.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@pls78 pls78 added the changelog: non-user-facing Needs to be included in the 'Non-userfacing' category in the changelog label Mar 3, 2026
@pls78 pls78 linked an issue Mar 3, 2026 that may be closed by this pull request
The old src/ai-consent/ and src/ai-generator/ integration classes are
still loaded when an older version of Yoast SEO Premium is active.
Mirror the endpoint repository injection and to_paths_array() calls
so that wpseoAiConsent.endpoints and wpseoAiGenerator.endpoints are
present regardless of which integration runs last.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 5, 2026

A merge conflict has been detected for the proposed code changes in this PR. Please resolve the conflict by either rebasing the PR or merging in changes from the base branch.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors how REST endpoints are represented and exposed across the AI features, and updates the AI frontend to consume backend-provided endpoint paths instead of hard-coded strings.

Changes:

  • Introduces shared endpoint infrastructure under src/routes/endpoint/ (interface, list, repository) and domain-specific endpoint marker interfaces + repositories for DI wiring.
  • Exposes endpoint paths to the frontend via to_paths_array() and updates AI script data to include an endpoints map.
  • Updates AI frontend store initialization and hooks/components to read endpoint paths from window.wpseoAiGenerator.endpoints / window.wpseoAiConsent.endpoints, plus adds Jest coverage for the new endpoints store slice.

Reviewed changes

Copilot reviewed 42 out of 42 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/Unit/AI/Generator/User_Interface/AI_Generator_Integration/Get_Script_Data_Test.php Updates generator script-data unit test to expect endpoints and mock endpoint list merging.
tests/Unit/AI/Generator/User_Interface/AI_Generator_Integration/Enqueue_Assets_Test.php Updates asset enqueue unit test to expect endpoints and mock endpoint list merging.
tests/Unit/AI/Generator/User_Interface/AI_Generator_Integration/Abstract_AI_Generator_Integration_Test.php Adds endpoint repository mocks and injects them into the integration constructor.
tests/Unit/AI/Generator/Infrastructure/Endpoints/Bust_Subscription_Cache_Endpoint/Get_Name_Test.php Adds unit test for new bust-subscription-cache endpoint name.
tests/Unit/AI/Generator/Infrastructure/Endpoints/Bust_Subscription_Cache_Endpoint/Abstract_Bust_Subscription_Cache_Endpoint_Test.php Adds shared test setup for bust-subscription-cache endpoint tests.
tests/Unit/AI/Generator/Domain/Endpoint/Endpoint_List/To_Paths_Array_Test.php Adds unit test for Endpoint_List::to_paths_array() path behavior.
tests/Unit/AI/Generator/Domain/Endpoint/Endpoint_List/Abstract_Endpoint_List_Test.php Updates test base to use shared Yoast\WP\SEO\Routes\Endpoint\Endpoint_List.
tests/Unit/AI/Free_Sparks/Infrastructure/Endpoints/Free_Sparks_Endpoint/Get_Name_Test.php Updates expected endpoint key to freeSparks.
tests/Unit/AI/Consent/User_Interface/AI_Consent_Integration/Get_Script_Data_Test.php Mocks endpoints repository/list for consent script data generation.
tests/Unit/AI/Consent/User_Interface/AI_Consent_Integration/Enqueue_Assets_Test.php Expects endpoints in localized consent script data.
tests/Unit/AI/Consent/User_Interface/AI_Consent_Integration/Abstract_AI_Consent_Integration_Test.php Injects consent endpoints repository into consent integration tests.
src/routes/endpoint/endpoints-repository.php Adds shared Endpoints_Repository that builds an Endpoint_List from variadic endpoints.
src/routes/endpoint/endpoint-list.php Moves/extends shared Endpoint_List and adds to_paths_array() + merge_with().
src/routes/endpoint/endpoint-interface.php Moves shared Endpoint_Interface into Yoast\WP\SEO\Routes\Endpoint.
src/ai/generator/user-interface/ai-generator-integration.php Injects endpoint repositories and exposes an endpoints map in script data (modern namespace).
src/ai/generator/infrastructure/endpoints/get-usage-endpoint.php Switches to generator marker interface for DI grouping.
src/ai/generator/infrastructure/endpoints/get-suggestions-endpoint.php Switches to generator marker interface for DI grouping.
src/ai/generator/infrastructure/endpoints/generator-endpoint-interface.php Adds generator marker interface extending shared Endpoint_Interface.
src/ai/generator/infrastructure/endpoints/bust-subscription-cache-endpoint.php Adds missing endpoint class for busting subscription cache.
src/ai/generator/domain/endpoint/endpoint-interface.php Removes old generator-domain endpoint interface (replaced by shared one).
src/ai/generator/application/generator-endpoints-repository.php Adds generator endpoints repository extending shared repository for DI wiring.
src/ai/free-sparks/infrastructure/endpoints/free-sparks-endpoint.php Updates Free Sparks endpoint key to freeSparks and uses marker interface.
src/ai/free-sparks/infrastructure/endpoints/free-sparks-endpoint-interface.php Adds Free Sparks marker interface extending shared Endpoint_Interface.
src/ai/free-sparks/application/free-sparks-endpoints-repository.php Adds Free Sparks endpoints repository extending shared repository.
src/ai/consent/user-interface/ai-consent-integration.php Injects consent endpoints repository and exposes endpoints in consent script data.
src/ai/consent/infrastructure/endpoints/consent-endpoint.php Switches consent endpoint to consent marker interface.
src/ai/consent/infrastructure/endpoints/consent-endpoint-interface.php Adds consent marker interface extending shared Endpoint_Interface.
src/ai/consent/domain/endpoint/endpoint-interface.php Removes old consent-domain endpoint interface (replaced by shared one).
src/ai/consent/application/consent-endpoints-repository.php Adds consent endpoints repository extending shared repository.
src/ai-generator/user-interface/ai-generator-integration.php Mirrors endpoint repo injection + endpoints exposure into legacy integration.
src/ai-free-sparks/infrastructure/endpoints/free-sparks-endpoint.php Updates legacy free-sparks endpoint to import new shared Endpoint_Interface location.
src/ai-consent/user-interface/ai-consent-integration.php Mirrors consent endpoints repository injection into legacy consent integration.
packages/js/tests/ai-generator/store/endpoints.test.js Adds Jest tests for new endpoints store slice (defaults + selectors).
packages/js/src/shared-admin/store/ai-generator-has-consent.js Removes hard-coded consent endpoint default (now populated via script data).
packages/js/src/ai-generator/store/usage-count.js Removes hard-coded usage endpoint default (now populated via script data).
packages/js/src/ai-generator/store/index.js Registers new endpoints slice and merges it into store actions/selectors/state.
packages/js/src/ai-generator/store/free-sparks.js Removes hard-coded free-sparks endpoint default (now populated via script data).
packages/js/src/ai-generator/store/endpoints.js Adds endpoints slice + selectors for getSuggestions / bustSubscriptionCache.
packages/js/src/ai-generator/initialize.js Reads endpoints from window.wpseoAiGenerator.endpoints.* into store initial state.
packages/js/src/ai-generator/hooks/use-suggestions.js Uses store-provided getSuggestions endpoint instead of hard-coded path.
packages/js/src/ai-generator/components/errors/subscription-error.js Uses store-provided bustSubscriptionCache endpoint instead of hard-coded path.
packages/js/src/ai-consent/initialize.js Reads consent endpoint from window.wpseoAiConsent.endpoints.consent.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +166 to +176
$endpoints = $this->generator_endpoints_repository->get_all_endpoints()
->merge_with(
$this->free_sparks_endpoints_repository->get_all_endpoints(),
)->to_paths_array();
return [
'hasConsent' => $this->user_helper->get_meta( $user_id, '_yoast_wpseo_ai_consent', true ),
'productSubscriptions' => $this->get_product_subscriptions(),
'hasSeenIntroduction' => $this->introductions_seen_repository->is_introduction_seen( $user_id, AI_Fix_Assessments_Upsell::ID ),
'requestTimeout' => $this->api_client->get_request_timeout(),
'isFreeSparks' => $this->options_helper->get( 'ai_free_sparks_started_on', null ) !== null,
'endpoints' => $endpoints,
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

wpseoAiGenerator.endpoints.consent is expected by the AI generator frontend (see packages/js/src/ai-generator/initialize.js and Introduction component), but this integration only merges Generator + Free Sparks endpoints. As a result the consent endpoint will be missing and consent POSTs will use an empty path. Consider also including the consent endpoint here (e.g., inject Consent_Endpoints_Repository and merge it, or otherwise ensure consent is part of the returned endpoints map).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed.

Comment on lines +164 to 176
$endpoints = $this->generator_endpoints_repository->get_all_endpoints()
->merge_with(
$this->free_sparks_endpoints_repository->get_all_endpoints(),
)->to_paths_array();

return [
'hasConsent' => $this->user_helper->get_meta( $user_id, '_yoast_wpseo_ai_consent', true ),
'productSubscriptions' => $this->get_product_subscriptions(),
'hasSeenIntroduction' => $this->introductions_seen_repository->is_introduction_seen( $user_id, AI_Fix_Assessments_Upsell::ID ),
'requestTimeout' => $this->api_client->get_request_timeout(),
'isFreeSparks' => $this->options_helper->get( 'ai_free_sparks_started_on', null ) !== null,
'endpoints' => $endpoints,
];
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

Same as the namespaced AI generator integration: the frontend expects a consent entry in wpseoAiGenerator.endpoints, but this code only merges Generator + Free Sparks endpoints. This will leave the consent endpoint empty and break consent requests from the generator UI. Consider merging consent endpoints here as well (e.g., inject Consent_Endpoints_Repository and include its list in the merge).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed.

Comment on lines +6 to +10
* Repository for endpoints.
*/
class Endpoints_Repository {

/**
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

PR description mentions introducing an abstract Endpoints_Repository, but this implementation is currently a concrete class. Either mark this class as abstract (if direct instantiation should be prevented) or update the PR description/design to reflect that it is intentionally instantiable.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Updated PR description.

Comment thread src/ai/generator/application/generator-endpoints-repository.php Outdated
Comment thread src/ai/free-sparks/application/free-sparks-endpoints-repository.php Outdated
Comment thread src/ai/consent/application/consent-endpoints-repository.php Outdated
pls78 and others added 3 commits March 9, 2026 15:55
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…y.php

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Base automatically changed from refactor-ai-organization to trunk March 12, 2026 11:18
The frontend AI generator expects `wpseoAiGenerator.endpoints.consent`
but the generator integration only merged Generator and Free Sparks
endpoints, omitting the Consent endpoint. This caused the Introduction
component's consent POST to use an empty path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coveralls
Copy link
Copy Markdown

coveralls commented Mar 12, 2026

Pull Request Test Coverage Report for Build 3c15ea526b3339d41eabd2ae6dea91cfbdc286dc

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 45 of 89 (50.56%) changed or added relevant lines in 16 files are covered.
  • 3 unchanged lines in 3 files lost coverage.
  • Overall coverage increased (+0.6%) to 53.413%

Changes Missing Coverage Covered Lines Changed/Added Lines %
packages/js/src/ai-generator/components/errors/subscription-error.js 0 1 0.0%
packages/js/src/ai-generator/hooks/use-suggestions.js 0 1 0.0%
src/ai/consent/application/consent-endpoints-repository.php 0 2 0.0%
src/ai/free-sparks/application/free-sparks-endpoints-repository.php 0 2 0.0%
src/ai/generator/application/generator-endpoints-repository.php 0 2 0.0%
src/dashboard/application/endpoints/endpoints-repository.php 0 2 0.0%
src/routes/endpoint/endpoints-repository.php 5 7 71.43%
src/routes/endpoint/endpoint-list.php 5 9 55.56%
src/ai-consent/user-interface/ai-consent-integration.php 0 5 0.0%
src/ai/generator/infrastructure/endpoints/bust-subscription-cache-endpoint.php 2 8 25.0%
Files with Coverage Reduction New Missed Lines %
packages/js/src/ai-generator/hooks/use-suggestions.js 1 5.75%
src/ai-generator/user-interface/ai-generator-integration.php 1 0.0%
src/ai-consent/user-interface/ai-consent-integration.php 1 0.0%
Totals Coverage Status
Change from base Build 3b129c2485fe269efc371d6a4c9efe287e675c71: 0.6%
Covered Lines: 34271
Relevant Lines: 64456

💛 - Coveralls

…Endpoints_Repository

Dashboard and Task List had their own identical copies of
Endpoint_Interface, Endpoint_List, and Endpoints_Repository.
Now both extend the shared base in src/routes/endpoint/, following
the same pattern the AI feature already uses. Domain-specific marker
interfaces (Dashboard_Endpoint_Interface, Task_List_Endpoint_Interface)
preserve type safety for DI constructor injection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@thijsoo thijsoo left a comment

Choose a reason for hiding this comment

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

CR + ACC 👍

@thijsoo thijsoo closed this Mar 16, 2026
@thijsoo thijsoo deleted the refactor-endpoint-logic branch March 16, 2026 15:24
@thijsoo thijsoo mentioned this pull request Mar 18, 2026
19 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog: non-user-facing Needs to be included in the 'Non-userfacing' category in the changelog

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor the Endpoint logic

4 participants