Centralize endpoint infrastructure and remove hard-coded frontend endpoint URLs#23041
Centralize endpoint infrastructure and remove hard-coded frontend endpoint URLs#23041
Conversation
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>
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>
|
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. |
There was a problem hiding this comment.
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 anendpointsmap. - 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.
| $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, |
There was a problem hiding this comment.
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).
| $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, | ||
| ]; |
There was a problem hiding this comment.
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).
| * Repository for endpoints. | ||
| */ | ||
| class Endpoints_Repository { | ||
|
|
||
| /** |
There was a problem hiding this comment.
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.
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>
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>
…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>
Context
Two related refactoring efforts on top of the AI reorganization branch:
Endpoint_InterfaceandEndpoint_Listclasses into a sharedsrc/routes/endpoint/location, introduces specialized per-domain endpoint interfaces for DI auto-wiring, and creates anEndpoints_Repositorybase class.window.wpseoAiGenerator.endpointsandwindow.wpseoAiConsent.endpoints.Summary
This PR can be summarized in the following changelog entry:
Endpoint_Interface,Endpoint_List,Endpoints_Repository) intosrc/routes/endpoint/.Relevant technical choices:
to_paths_array()method was added toEndpoint_Listinstead of modifyingto_array().apiFetchuses thepath:parameter which expects relative paths (e.g.,yoast/v1/ai_generator/get_suggestions), whileto_array()returns full URLs viarest_url()(e.g.,https://site.com/wp-json/yoast/v1/...).to_array()returning full URLs, so a new method avoids breaking those.getSuggestionsandbustSubscriptionCachewere used directly in hooks/components, not via Redux.endpointsstore slice was created.packages/js/src/general/initialize.jswhich reads endpoints fromwindow.wpseoScriptData.dashboard.endpoints.*.""instead of hard-coded paths.initialStatemerge at store registration time.Consent_Endpoint_Interface,Free_Sparks_Endpoint_Interface, andGenerator_Endpoint_Interfaceenable the DI container to automatically wire endpoints into the correctEndpoints_Repositoryvia variadic constructor injection, without manual service registration.Bust_Subscription_Cache_Endpointhas been added.consent,getSuggestions,getUsage,freeSparks,bustSubscriptionCachefollow the same pattern.to_paths_array()calls were mirrored into the legacy integration classes (src/ai-consent/andsrc/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:
grep -r "yoast/v1/ai" packages/js/src/should return no results.Premium compatibility
Yoast\WP\SEO\AI\namespaces).Yoast\WP\SEO\AI_Consent\/Yoast\WP\SEO\AI_Generator\namespacesDashboard and Task List tests
Yoast SEO->General->Task ListRelevant test scenarios
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 can test this PR by following these steps:
Impact check
This PR affects the following parts of the plugin, which may require extra testing:
src/routes/endpoint/(used by Dashboard and Task List features — verify those still work)Other environments
[shopify-seo], added test instructions for Shopify and attached theShopifylabel to this PR.[yoast-doc-extension], added test instructions for Yoast SEO for Google Docs and attached theGoogle Docs Add-onlabel to this PR.Documentation
Quality assurance
grunt build:imagesand commited the results, if my PR introduces new images or SVGs.Innovation
innovationlabel.Fixes #