diff --git a/src/Models/class-inbound-smart-link.php b/src/Models/class-inbound-smart-link.php index 0c5e8efd1..5f2b82a94 100644 --- a/src/Models/class-inbound-smart-link.php +++ b/src/Models/class-inbound-smart-link.php @@ -230,7 +230,11 @@ private function get_post_data(): array { $this->post_data = array( 'id' => $post->ID, 'title' => $post->post_title, - 'type' => $post_type_label, + 'type' => array( + 'name' => $post->post_type, + 'label' => $post_type_label , + 'rest' => $post_type->rest_namespace . "/" . ( $post_type->rest_base ? $post_type->rest_base : $post->post_type ), + ), 'paragraph' => $paragraph['paragraph'], 'permalink' => get_permalink( $post ), 'edit_link' => get_edit_post_link( $post, 'html' ), @@ -444,7 +448,9 @@ public function set_source_from_url( string $url ): bool { // Since we couldn't find a post by URL, try to find a post with the same slug. $post_slug = basename( $url ); - $source_post = get_page_by_path( $post_slug, OBJECT, array( 'post', 'page' ) ); + + $public_post_types = get_post_types( array( 'public' => true, 'show_in_rest' => true ) ); + $source_post = get_page_by_path( $post_slug, OBJECT, array_keys( $public_post_types ) ); if ( null !== $source_post ) { $this->set_source_post( $source_post ); diff --git a/src/content-helper/common/base-wordpress-provider.tsx b/src/content-helper/common/base-wordpress-provider.tsx index 176c5e263..97b542aeb 100644 --- a/src/content-helper/common/base-wordpress-provider.tsx +++ b/src/content-helper/common/base-wordpress-provider.tsx @@ -255,8 +255,10 @@ export abstract class BaseWordPressProvider extends BaseProvider { queryParams: QueryParams = {}, id?: string, ): Promise> { + const restEndpoint = queryParams.rest_endpoint ?? '/wp/v2/posts'; + const posts = await this.apiFetch( { - path: addQueryArgs( '/wp/v2/posts', { ...queryParams, _embed: true, context: 'edit' } ), + path: addQueryArgs( restEndpoint, { ...queryParams, _embed: true, context: 'edit' } ), method: 'GET', }, id ); diff --git a/src/content-helper/dashboard-page/pages/traffic-boost/preview/preview.tsx b/src/content-helper/dashboard-page/pages/traffic-boost/preview/preview.tsx index 9a70b1c70..0a5100cd4 100644 --- a/src/content-helper/dashboard-page/pages/traffic-boost/preview/preview.tsx +++ b/src/content-helper/dashboard-page/pages/traffic-boost/preview/preview.tsx @@ -12,7 +12,6 @@ import { addQueryArgs } from '@wordpress/url'; * Internal dependencies */ import { HydratedPost } from '../../../../common/base-wordpress-provider'; -import { SnackbarNotices } from '../../../../common/components/snackbar-notices'; import { ContentHelperError, ContentHelperErrorCode } from '../../../../common/content-helper-error'; import { TrafficBoostLink, TrafficBoostProvider } from '../provider'; import { TrafficBoostSidebarTabs, TrafficBoostStore } from '../store'; @@ -547,7 +546,6 @@ export const TrafficBoostPreview = ( { isFrontendPreview={ isFrontendPreview } onLoadingChange={ setIsLoading } /> - { - // Find the target post for the inbound smart link. - const sourcePost = fetchedPosts.data.find( ( post ) => post.id === inboundSmartLink.source?.post_id ); - - if ( ! sourcePost ) { - return false; - } - - return this.createTrafficBoostLink( inboundSmartLink, sourcePost ); - } ).filter( ( link ) => link !== false ); + return this.createTrafficBoostLinks( response.data ); } /** @@ -216,45 +201,7 @@ export class TrafficBoostProvider extends BaseWordPressProvider { // Filter out any smart links that are not valid. const validSmartLinks = response.data.filter( ( inboundSmartLink ) => inboundSmartLink.validation?.valid ); - // Get the post IDs from the inbound smart links. - const postIds = validSmartLinks.map( ( inboundSmartLink ) => inboundSmartLink.source?.post_id ); - - // Fetch the posts for the inbound smart links. - const fetchedPosts = await this.getPosts( { - include: postIds, - posts_per_page: 100, - status: 'any', - } ); - - // Create the traffic boost links. - const trafficBoostLinks = validSmartLinks.map( ( inboundSmartLink ) => { - const sourcePost = fetchedPosts.data.find( ( p ) => p.id === inboundSmartLink.source?.post_id ); - - if ( ! sourcePost ) { - return false; - } - - return this.createTrafficBoostLink( inboundSmartLink, sourcePost ); - } ).filter( ( link ) => link !== false ); - - return trafficBoostLinks; - } - - /** - * Creates a suggestion for a given post, without generating the placement. - * - * @since 3.18.0 - * - * @param {HydratedPost} post The post to create a suggestion for. - * - * @return {Promise} The suggestion. - */ - public createSuggestion( post: HydratedPost ): TrafficBoostLink { - return { - uid: `suggestion-${ post.id }-${ Date.now() }`, - targetPost: post, - isSuggestion: true, - }; + return this.createTrafficBoostLinks( validSmartLinks ); } /** @@ -392,26 +339,7 @@ export class TrafficBoostProvider extends BaseWordPressProvider { return []; } - // Now we need to fetch the posts for the inbound smart links. - const fetchedPosts = await this.getPosts( { - include: inboundSmartLinks.map( ( link ) => link.source?.post_id ), - posts_per_page: 100, - status: 'any', - } ); - - if ( fetchedPosts.total_items > 100 ) { - // eslint-disable-next-line no-console - console.warn( 'Parse.ly: More than 100 inbound smart links. This is not supported yet.' ); - } - - return fetchedPosts.data - .map( ( post ) => ( { - uid: `inbound-${ post.id }-${ Date.now() }`, - targetPost: post, - smartLink: inboundSmartLinks.find( ( link ) => link.source?.post_id === post.id ), - isSuggestion: false, - } ) ) - .filter( ( link ) => link.smartLink !== undefined ); + return this.createTrafficBoostLinks( inboundSmartLinks ); } /** @@ -496,6 +424,82 @@ export class TrafficBoostProvider extends BaseWordPressProvider { return response.data; } + /** + * Creates a suggestion for a given post, without generating the placement. + * + * @since 3.18.0 + * + * @param {HydratedPost} post The post to create a suggestion for. + * + * @return {Promise} The suggestion. + */ + public createSuggestion( post: HydratedPost ): TrafficBoostLink { + return { + uid: `suggestion-${ post.id }-${ Date.now() }`, + targetPost: post, + isSuggestion: true, + }; + } + + /** + * Creates traffic boost links from inbound smart links. + * + * @since 3.18.0 + * + * @param {InboundSmartLink[]} inboundSmartLinks The inbound smart links to create traffic boost links from. + * + * @return {Promise} The traffic boost links. + */ + private async createTrafficBoostLinks( inboundSmartLinks: InboundSmartLink[] ): Promise { + // Split the inbound smart links into buckets of source post types. + const smartLinksByPostType = inboundSmartLinks.reduce( ( acc, inboundSmartLink ) => { + // Get the post REST endpoint for the inbound smart link source post type. + const postRestEndpoint = inboundSmartLink.post_data?.type.rest; + + if ( ! postRestEndpoint ) { + return acc; + } + + if ( ! acc[ postRestEndpoint ] ) { + acc[ postRestEndpoint ] = []; + } + + acc[ postRestEndpoint ].push( inboundSmartLink ); + return acc; + }, {} as Record ); + + // Get the posts for the inbound smart links in parallel. + const getPostsPromises = Object.entries( smartLinksByPostType ).map( async ( [ postRestEndpoint, smartLinks ] ) => { + // Get the post IDs from the inbound smart links. + const postIds = smartLinks.map( ( inboundSmartLink ) => inboundSmartLink.source?.post_id ); + + // Fetch the posts for the inbound smart links. + const fetchedPosts = await this.getPosts( { + include: postIds, + posts_per_page: 100, + status: 'any', + rest_endpoint: postRestEndpoint, + } ); + + return fetchedPosts.data; + } ); + + const fetchedPosts = await Promise.all( getPostsPromises ); + + // Create the traffic boost links. + const trafficBoostLinks = inboundSmartLinks.map( ( inboundSmartLink ) => { + const sourcePost = fetchedPosts.flat().find( ( p ) => p.id === inboundSmartLink.source?.post_id ); + + if ( ! sourcePost ) { + return false; + } + + return this.createTrafficBoostLink( inboundSmartLink, sourcePost ); + } ).filter( ( link ) => link !== false ); + + return trafficBoostLinks; + } + /** * Creates a traffic boost link from an inbound smart link. * @@ -506,7 +510,7 @@ export class TrafficBoostProvider extends BaseWordPressProvider { * * @return {TrafficBoostLink} The traffic boost link. */ - protected createTrafficBoostLink( inboundSmartLink: InboundSmartLink, targetPost: HydratedPost ): TrafficBoostLink { + private createTrafficBoostLink( inboundSmartLink: InboundSmartLink, targetPost: HydratedPost ): TrafficBoostLink { return { uid: inboundSmartLink.uid + '-' + Date.now(), targetPost, diff --git a/src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/links-list/links-list.scss b/src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/links-list/links-list.scss index 9357a90bf..c86eaab44 100644 --- a/src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/links-list/links-list.scss +++ b/src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/links-list/links-list.scss @@ -14,6 +14,7 @@ min-height: 0; .traffic-boost-single-link { + cursor: pointer; flex: 0 0 auto; display: flex; border-bottom: 1px solid var(--gray-350); diff --git a/src/content-helper/dashboard-page/pages/traffic-boost/single-post-component.tsx b/src/content-helper/dashboard-page/pages/traffic-boost/single-post-component.tsx index 3c1843841..d7ceb2253 100644 --- a/src/content-helper/dashboard-page/pages/traffic-boost/single-post-component.tsx +++ b/src/content-helper/dashboard-page/pages/traffic-boost/single-post-component.tsx @@ -19,6 +19,7 @@ import { TrafficBoostLink, TrafficBoostProvider } from './provider'; import { TrafficBoostSidebar } from './sidebar/sidebar'; import { TrafficBoostSidebarTabs, TrafficBoostStore } from './store'; import './traffic-boost.scss'; +import { SnackbarNotices } from '../../../common/components/snackbar-notices'; /** * Traffic Boost Post page component. @@ -436,6 +437,7 @@ export const TrafficBoostPostPage = (): React.JSX.Element => { onUpdateInboundLink={ handleUpdateInboundLink } /> ) } + ); }; diff --git a/src/content-helper/dashboard-page/pages/traffic-boost/traffic-boost.scss b/src/content-helper/dashboard-page/pages/traffic-boost/traffic-boost.scss index 323968e53..f04b0a0e1 100644 --- a/src/content-helper/dashboard-page/pages/traffic-boost/traffic-boost.scss +++ b/src/content-helper/dashboard-page/pages/traffic-boost/traffic-boost.scss @@ -27,3 +27,7 @@ align-self: stretch; } } + +.wp-parsely-snackbar-notices.traffic-boost-snackbar-notices { + padding-left: to_rem(480px); +} \ No newline at end of file diff --git a/src/content-helper/editor-sidebar/smart-linking/provider.ts b/src/content-helper/editor-sidebar/smart-linking/provider.ts index da5c50e9c..21031bf19 100644 --- a/src/content-helper/editor-sidebar/smart-linking/provider.ts +++ b/src/content-helper/editor-sidebar/smart-linking/provider.ts @@ -41,7 +41,11 @@ export type InboundSmartLink = SmartLink & { post_data?: { id: number; title: string; - type: string; + type: { + name: string; + label: string; + rest: string; + }; paragraph: string; is_first_paragraph: boolean; is_last_paragraph: boolean; diff --git a/src/content-helper/editor-sidebar/smart-linking/review-modal/component-inbound-link.tsx b/src/content-helper/editor-sidebar/smart-linking/review-modal/component-inbound-link.tsx index c97d6b7d2..eb612616a 100644 --- a/src/content-helper/editor-sidebar/smart-linking/review-modal/component-inbound-link.tsx +++ b/src/content-helper/editor-sidebar/smart-linking/review-modal/component-inbound-link.tsx @@ -85,7 +85,7 @@ const LinkingPostDetails = ( { link }: LinkingPostDetailsProps ): React.JSX.Elem { link.post_data?.date } by { link.post_data?.author } -
{ link.post_data?.type }
+
{ link.post_data?.type.label }
); }; diff --git a/src/rest-api/content-helper/class-endpoint-traffic-boost.php b/src/rest-api/content-helper/class-endpoint-traffic-boost.php index 9cb3b291d..d566597c2 100644 --- a/src/rest-api/content-helper/class-endpoint-traffic-boost.php +++ b/src/rest-api/content-helper/class-endpoint-traffic-boost.php @@ -302,15 +302,21 @@ public function generate_link_suggestions( WP_REST_Request $request ) { */ $discard_previous = $request->get_param( 'discard_previous' ); - $inbound_suggestions = $this->suggestions_api->get_inbound_links( $post, array( 'max_items' => $max_items, - 'max_link_words' => 4, ) ); + $time = $inbound_suggestions['request_duration']; + $raw_response = $inbound_suggestions['raw_response']; + $skipped = $inbound_suggestions['skipped']; + + unset( $inbound_suggestions['request_duration'] ); + unset( $inbound_suggestions['raw_response'] ); + unset( $inbound_suggestions['skipped'] ); + if ( is_wp_error( $inbound_suggestions ) ) { return $inbound_suggestions; } @@ -338,6 +344,9 @@ function ( Inbound_Smart_Link $link ) use ( $save ) { $response = array( 'data' => $suggestions, + 'request_duration' => $time, + 'raw_response' => $raw_response, + 'skipped' => $skipped, ); if ( null !== $discard_result ) { diff --git a/src/services/suggestions-api/endpoints/class-endpoint-suggest-inbound-link-positions.php b/src/services/suggestions-api/endpoints/class-endpoint-suggest-inbound-link-positions.php index 98bb39790..6c89c145f 100644 --- a/src/services/suggestions-api/endpoints/class-endpoint-suggest-inbound-link-positions.php +++ b/src/services/suggestions-api/endpoints/class-endpoint-suggest-inbound-link-positions.php @@ -68,7 +68,7 @@ public function get_inbound_link_positions( 'text' => wp_strip_all_tags( $destination_post->post_content ), ); - $request_body = apply_filters( 'parsely_suggest_inbound_link_positions_request_body', $request_body, $source_post, $destination_post ); + $request_body = apply_filters( 'wp_parsely_suggest_inbound_link_positions_request_body', $request_body, $source_post, $destination_post ); $response = $this->request( 'POST', array(), $request_body ); diff --git a/src/services/suggestions-api/endpoints/class-endpoint-suggest-inbound-links.php b/src/services/suggestions-api/endpoints/class-endpoint-suggest-inbound-links.php index 0f8766b99..2d2d3a770 100644 --- a/src/services/suggestions-api/endpoints/class-endpoint-suggest-inbound-links.php +++ b/src/services/suggestions-api/endpoints/class-endpoint-suggest-inbound-links.php @@ -22,7 +22,6 @@ * * @phpstan-type Endpoint_Suggest_Inbound_Links_Options = array{ * max_items?: int, - * max_link_words?: int, * title?: string, * text?: string * } @@ -67,16 +66,18 @@ public function get_inbound_links( $request_body = array( 'canonical_url' => $post_url, 'output_config' => array( + 'blending_weight' => 0.9, 'max_items' => $options['max_items'] ?? 10, - 'max_link_words' => $options['max_link_words'] ?? 4, ), 'title' => $post->post_title, 'text' => wp_strip_all_tags( $post->post_content ), ); - $request_body = apply_filters( 'parsely_suggest_inbound_links_request_body', $request_body, $post, $options ); + $request_body = apply_filters( 'wp_parsely_suggest_inbound_links_request_body', $request_body, $post, $options ); + $time = microtime( true ); $response = $this->request( 'POST', array(), $request_body ); + $time = microtime( true ) - $time; if ( is_wp_error( $response ) ) { return $response; @@ -84,6 +85,7 @@ public function get_inbound_links( // Convert the links to Inbound_Smart_Link objects. $links = array(); + $skipped = array(); foreach ( $response as $link ) { $link = apply_filters( 'wp_parsely_suggest_inbound_links_link', $link ); @@ -103,13 +105,16 @@ public function get_inbound_links( // Set the source post from the URL. $did_set_source = $link_obj->set_source_from_url( $link['source_url'] ); - // If no source post was found or the source post is the same as the destination post, skip it. + // If no source post was found or the source post is the same as the destination post, skip to + // the next link suggestion. if ( ! $did_set_source || $link_obj->source_post_id === $post->ID ) { - continue; + $skipped[] = $link_obj->to_array(); + break; } - // If the link doesn't not have a valid placement, skip it. + // If the link doesn't not have a valid placement, skip to the next anchor text suggestion. if ( ! $link_obj->has_valid_placement() ) { + $skipped[] = $link_obj->to_array(); continue; } @@ -123,6 +128,10 @@ public function get_inbound_links( } } + $links['request_duration'] = $time; + $links['raw_response'] = $response; + $links['skipped'] = $skipped; + return $links; } diff --git a/src/services/suggestions-api/endpoints/class-suggestions-api-base-endpoint.php b/src/services/suggestions-api/endpoints/class-suggestions-api-base-endpoint.php index 79c7e2cb3..c352d13e0 100644 --- a/src/services/suggestions-api/endpoints/class-suggestions-api-base-endpoint.php +++ b/src/services/suggestions-api/endpoints/class-suggestions-api-base-endpoint.php @@ -48,7 +48,7 @@ protected function get_request_options( string $method ): array { 'method' => $method, 'headers' => array( 'Content-Type' => 'application/json; charset=utf-8' ), 'data_format' => 'body', - 'timeout' => 60, //phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout + 'timeout' => 90, //phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout 'body' => '{}', );