Marketplace: Cache Discover page content

We were hitting the WCCOM API directly from the front end. However,
that limits of in terms of caching and reducing the load on WCCOM.

To prevent that, we added a REST API endpoint. This endpoint
fetches discover page content from WCCOM and puts in a transient.
This is actually how the page works in the previous version. So
we were able to reuse a lot of the code.
This commit is contained in:
raicem 2023-08-17 11:54:51 +03:00
parent c42e6bcc5b
commit 60c07013d4
6 changed files with 108 additions and 44 deletions

View File

@ -8,21 +8,29 @@ import { useEffect, useState } from '@wordpress/element';
*/
import ProductList from '../product-list/product-list';
import { fetchDiscoverPageData, ProductGroup } from '../../utils/functions';
import ProductLoader from '../product-loader/product-loader';
import './discover.scss';
export default function Discover(): JSX.Element | null {
const [ productGroups, setProductGroups ] = useState<
Array< ProductGroup >
>( [] );
const [ isLoading, setIsLoading ] = useState( false );
useEffect( () => {
fetchDiscoverPageData().then( ( products: Array< ProductGroup > ) => {
setProductGroups( products );
} );
setIsLoading( true );
fetchDiscoverPageData()
.then( ( products: Array< ProductGroup > ) => {
setProductGroups( products );
} )
.finally( () => {
setIsLoading( false );
} );
}, [] );
if ( ! productGroups.length ) {
return null;
if ( isLoading ) {
return <ProductLoader />;
}
const groupsList = productGroups.flatMap( ( group ) => group );

View File

@ -25,6 +25,8 @@ export default function NoResults(): JSX.Element {
useEffect( () => {
if ( query.term ) {
setNoResultsTerm( query.term );
return;
}
if ( query.category ) {
@ -42,7 +44,7 @@ export default function NoResults(): JSX.Element {
setisLoadingProductGroup( true );
fetchDiscoverPageData()
.then( ( products: Array< ProductGroup > ) => {
.then( ( products: ProductGroup[] ) => {
const mostPopularGroup = products.find(
( group ) => group.id === 'most-popular'
);

View File

@ -23,15 +23,9 @@ export const ProductListContext = createContext< ProductListContextType >( {
isLoading: false,
} );
type ProductListContextProviderProps = {
export function ProductListContextProvider( props: {
children: JSX.Element;
country?: string;
locale?: string;
};
export function ProductListContextProvider(
props: ProductListContextProviderProps
): JSX.Element {
} ): JSX.Element {
const [ isLoading, setIsLoading ] = useState( false );
const [ productList, setProductList ] = useState< Product[] >( [] );
@ -47,9 +41,9 @@ export function ProductListContextProvider(
const params = new URLSearchParams();
params.append( 'term', query.term ?? '' );
params.append( 'country', props.country ?? '' );
params.append( 'locale', props.locale ?? '' );
if ( query.term ) {
params.append( 'term', query.term );
}
if ( query.category ) {
params.append( 'category', query.category );
@ -94,7 +88,7 @@ export function ProductListContextProvider(
.finally( () => {
setIsLoading( false );
} );
}, [ query, props.country, props.locale ] );
}, [ query ] );
return (
<ProductListContext.Provider value={ contextValue }>

View File

@ -1,9 +1,15 @@
/**
* External dependencies
*/
import apiFetch from '@wordpress/api-fetch';
/**
* Internal dependencies
*/
import { Product } from '../components/product-list/types';
import { MARKETPLACE_URL } from '../components/constants';
import { CategoryAPIItem } from '../components/category-selector/types';
import { LOCALE } from '../../utils/admin-settings';
interface ProductGroup {
id: string;
@ -13,26 +19,28 @@ interface ProductGroup {
}
// Fetch data for the discover page from the WooCommerce.com API
const fetchDiscoverPageData = async (): Promise< Array< ProductGroup > > => {
const fetchUrl = MARKETPLACE_URL + '/wp-json/wccom-extensions/2.0/featured';
async function fetchDiscoverPageData(): Promise< ProductGroup[] > {
let url = '/wc/v3/marketplace/featured';
return fetch( fetchUrl )
.then( ( response ) => {
if ( ! response.ok ) {
throw new Error( response.statusText );
}
return response.json();
} )
.then( ( json ) => {
return json;
} )
.catch( () => {
return [];
} );
};
if ( LOCALE.userLocale ) {
url = `${ url }?locale=${ LOCALE.userLocale }`;
}
try {
return await apiFetch( { path: url.toString() } );
} catch ( error ) {
return [];
}
}
function fetchCategories(): Promise< CategoryAPIItem[] > {
return fetch( MARKETPLACE_URL + '/wp-json/wccom-extensions/1.0/categories' )
let url = MARKETPLACE_URL + '/wp-json/wccom-extensions/1.0/categories';
if ( LOCALE.userLocale ) {
url = `${ url }?locale=${ LOCALE.userLocale }`;
}
return fetch( url.toString() )
.then( ( response ) => {
if ( ! response.ok ) {
throw new Error( response.statusText );

View File

@ -64,8 +64,24 @@ class WC_Admin_Addons {
* @return void
*/
public static function render_featured() {
$featured = self::fetch_featured();
if ( is_wp_error( $featured ) ) {
self::output_empty( $featured->get_error_message() );
}
self::output_featured( $featured );
}
/**
* Fetch featured products from WCCOM's the Featured 2.0 Endpoint and cache the data for a day.
*
* @return array|WP_Error
*/
public static function fetch_featured() {
$locale = get_user_locale();
$featured = self::get_locale_data_from_transient( 'wc_addons_featured', $locale );
if ( false === $featured ) {
$headers = array();
$auth = WC_Helper_Options::get( 'auth' );
@ -96,9 +112,7 @@ class WC_Admin_Addons {
? __( 'We encountered an SSL error. Please ensure your site supports TLS version 1.2 or above.', 'woocommerce' )
: $raw_featured->get_error_message();
self::output_empty( $message );
return;
return new WP_Error( 'wc-addons-connection-error', $message );
}
$response_code = (int) wp_remote_retrieve_response_code( $raw_featured );
@ -117,18 +131,15 @@ class WC_Admin_Addons {
$response_code
);
self::output_empty( $message );
return;
return new WP_Error( 'wc-addons-connection-error', $message );
}
$featured = json_decode( wp_remote_retrieve_body( $raw_featured ) );
if ( empty( $featured ) || ! is_array( $featured ) ) {
do_action( 'woocommerce_page_wc-addons_connection_error', 'Empty or malformed response' );
$message = __( 'Our request to the featured API got a malformed response.', 'woocommerce' );
self::output_empty( $message );
return;
return new WP_Error( 'wc-addons-connection-error', $message );
}
if ( $featured ) {
@ -136,7 +147,7 @@ class WC_Admin_Addons {
}
}
self::output_featured( $featured );
return $featured;
}
/**

View File

@ -25,6 +25,7 @@ class WC_Helper_Admin {
*/
public static function load() {
add_filter( 'woocommerce_admin_shared_settings', array( __CLASS__, 'add_marketplace_settings' ) );
add_filter( 'rest_api_init', array( __CLASS__, 'register_rest_routes' ) );
}
/**
@ -82,6 +83,46 @@ class WC_Helper_Admin {
return $connect_url;
}
/**
* Registers the REST routes for the featured products endpoint.
* This endpoint is used by the WooCommerce > Extensions > Discover
* page.
*/
public static function register_rest_routes() {
register_rest_route(
'wc/v3',
'/marketplace/featured',
array(
'methods' => 'GET',
'callback' => array( __CLASS__, 'get_featured' ),
'permission_callback' => array( __CLASS__, 'get_permission' ),
)
);
}
/**
* The Extensions page can only be accessed by users with the manage_woocommerce
* capability. So the API mimics that behavior.
*/
public static function get_permission() {
return current_user_can( 'manage_woocommerce' );
}
/**
* Fetch featured procucts from WooCommerce.com and serve them
* as JSON.
*/
public static function get_featured() {
$featured = WC_Admin_Addons::fetch_featured();
if ( is_wp_error( $featured ) ) {
wp_send_json_error( array( 'message' => $featured->get_error_message() ) );
}
wp_send_json( $featured );
}
}
WC_Helper_Admin::load();