Marketplace: cache Discover page content in a transient (#39782)

This commit is contained in:
Cem Ünalan 2023-08-18 21:48:19 +03:00 committed by GitHub
commit 3c92e9078c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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 ProductList from '../product-list/product-list';
import { fetchDiscoverPageData, ProductGroup } from '../../utils/functions'; import { fetchDiscoverPageData, ProductGroup } from '../../utils/functions';
import ProductLoader from '../product-loader/product-loader';
import './discover.scss'; import './discover.scss';
export default function Discover(): JSX.Element | null { export default function Discover(): JSX.Element | null {
const [ productGroups, setProductGroups ] = useState< const [ productGroups, setProductGroups ] = useState<
Array< ProductGroup > Array< ProductGroup >
>( [] ); >( [] );
const [ isLoading, setIsLoading ] = useState( false );
useEffect( () => { useEffect( () => {
fetchDiscoverPageData().then( ( products: Array< ProductGroup > ) => { setIsLoading( true );
fetchDiscoverPageData()
.then( ( products: Array< ProductGroup > ) => {
setProductGroups( products ); setProductGroups( products );
} )
.finally( () => {
setIsLoading( false );
} ); } );
}, [] ); }, [] );
if ( ! productGroups.length ) { if ( isLoading ) {
return null; return <ProductLoader />;
} }
const groupsList = productGroups.flatMap( ( group ) => group ); const groupsList = productGroups.flatMap( ( group ) => group );

View File

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

View File

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

View File

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

View File

@ -64,8 +64,24 @@ class WC_Admin_Addons {
* @return void * @return void
*/ */
public static function render_featured() { 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(); $locale = get_user_locale();
$featured = self::get_locale_data_from_transient( 'wc_addons_featured', $locale ); $featured = self::get_locale_data_from_transient( 'wc_addons_featured', $locale );
if ( false === $featured ) { if ( false === $featured ) {
$headers = array(); $headers = array();
$auth = WC_Helper_Options::get( 'auth' ); $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' ) ? __( 'We encountered an SSL error. Please ensure your site supports TLS version 1.2 or above.', 'woocommerce' )
: $raw_featured->get_error_message(); : $raw_featured->get_error_message();
self::output_empty( $message ); return new WP_Error( 'wc-addons-connection-error', $message );
return;
} }
$response_code = (int) wp_remote_retrieve_response_code( $raw_featured ); $response_code = (int) wp_remote_retrieve_response_code( $raw_featured );
@ -117,18 +131,15 @@ class WC_Admin_Addons {
$response_code $response_code
); );
self::output_empty( $message ); return new WP_Error( 'wc-addons-connection-error', $message );
return;
} }
$featured = json_decode( wp_remote_retrieve_body( $raw_featured ) ); $featured = json_decode( wp_remote_retrieve_body( $raw_featured ) );
if ( empty( $featured ) || ! is_array( $featured ) ) { if ( empty( $featured ) || ! is_array( $featured ) ) {
do_action( 'woocommerce_page_wc-addons_connection_error', 'Empty or malformed response' ); do_action( 'woocommerce_page_wc-addons_connection_error', 'Empty or malformed response' );
$message = __( 'Our request to the featured API got a malformed response.', 'woocommerce' ); $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 ) { 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() { public static function load() {
add_filter( 'woocommerce_admin_shared_settings', array( __CLASS__, 'add_marketplace_settings' ) ); 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; 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(); WC_Helper_Admin::load();