Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 38 additions & 18 deletions src/design-library/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,46 @@ const LATEST_API_VERSION = 'v4'

let designLibrary = {}
let designs = {}
let pages = {}

export const getBlockName = block => block.replace( /^[\w-]+\//, '' )

const hasLibraryError = library =>
library && ( library.wp_remote_get_error || library.content_error )

export const fetchDesignLibrary = async ( forceReset = false, version = '', type = 'patterns' ) => {
if ( forceReset ) {
doAction( 'stackable.design-library.reset-cache' )
designLibrary = {}
designs = {}
pages = {}
}

if ( ( type === 'patterns' && ! Object.keys( designs ).length ) ||
( type === 'pages' && ! Object.keys( pages ).length )
) {
const needsFetch = ( type === 'patterns' || type === 'pages' ) && ! designLibrary[ type ]

if ( needsFetch ) {
const results = await apiFetch( {
path: `/stackable/v2/design_library/${ type }${ forceReset ? '/reset' : '' }`,
method: 'GET',
} )
const designsPerType = results
} ) || {}

designLibrary[ type ] = designsPerType
designLibrary[ type ] = results

if ( type === 'patterns' ) {
designs = designsPerType[ LATEST_API_VERSION ] ?? {}
} else {
pages = designsPerType[ LATEST_API_VERSION ] ?? {}
if ( hasLibraryError( results ) ) {
if ( type === 'patterns' ) {
designs = {}
}
} else if ( type === 'patterns' ) {
designs = results[ LATEST_API_VERSION ] ?? {}
Comment on lines +21 to +36

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not cache or normalize failed client responses as an empty library.

Line 27 turns an undefined API result into {}, which passes getDesigns validation and renders an empty library. Also, after an error response is stored, Line 21 prevents later calls from retrying because the error object is truthy. Preserve invalid responses as an error sentinel and include hasLibraryError( designLibrary[ type ] ) in needsFetch.

Proposed fix
-	const needsFetch = ( type === 'patterns' || type === 'pages' ) && ! designLibrary[ type ]
+	const needsFetch = ( type === 'patterns' || type === 'pages' ) &&
+		( ! designLibrary[ type ] || hasLibraryError( designLibrary[ type ] ) )
 
 	if ( needsFetch ) {
-		const results = await apiFetch( {
+		const response = await apiFetch( {
 			path: `/stackable/v2/design_library/${ type }${ forceReset ? '/reset' : '' }`,
 			method: 'GET',
-		} ) || {}
+		} )
+		const results = response && typeof response === 'object'
+			? response
+			: { content_error: { message: 'Failed to load design library.' } }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const needsFetch = ( type === 'patterns' || type === 'pages' ) && ! designLibrary[ type ]
if ( needsFetch ) {
const results = await apiFetch( {
path: `/stackable/v2/design_library/${ type }${ forceReset ? '/reset' : '' }`,
method: 'GET',
} )
const designsPerType = results
} ) || {}
designLibrary[ type ] = designsPerType
designLibrary[ type ] = results
if ( type === 'patterns' ) {
designs = designsPerType[ LATEST_API_VERSION ] ?? {}
} else {
pages = designsPerType[ LATEST_API_VERSION ] ?? {}
if ( hasLibraryError( results ) ) {
if ( type === 'patterns' ) {
designs = {}
}
} else if ( type === 'patterns' ) {
designs = results[ LATEST_API_VERSION ] ?? {}
const needsFetch = ( type === 'patterns' || type === 'pages' ) &&
( ! designLibrary[ type ] || hasLibraryError( designLibrary[ type ] ) )
if ( needsFetch ) {
const response = await apiFetch( {
path: `/stackable/v2/design_library/${ type }${ forceReset ? '/reset' : '' }`,
method: 'GET',
} )
const results = response && typeof response === 'object'
? response
: { content_error: { message: 'Failed to load design library.' } }
designLibrary[ type ] = results
if ( hasLibraryError( results ) ) {
if ( type === 'patterns' ) {
designs = {}
}
} else if ( type === 'patterns' ) {
designs = results[ LATEST_API_VERSION ] ?? {}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/design-library/index.js` around lines 21 - 36, Update the fetch logic
around needsFetch and apiFetch so failed or undefined responses remain
identifiable as errors rather than being normalized to an empty object. Include
hasLibraryError( designLibrary[ type ] ) in the needsFetch condition so cached
error responses can be retried, and preserve the raw invalid response as the
stored error sentinel while retaining normal successful-response handling.

}
}

return designLibrary[ type ]?.[ version || LATEST_API_VERSION ] ?? designLibrary[ type ]
const library = designLibrary[ type ] || {}

// Return the raw response when it contains fetch/parse errors so callers can handle them.
if ( hasLibraryError( library ) ) {
return library
}

return library[ version || LATEST_API_VERSION ] ?? library
}

export const fetchDesign = async designId => {
Expand All @@ -61,10 +70,17 @@ export const getDesigns = async ( {
reset = false,
type = 'patterns',
} ) => {
const designLibrary = await fetchDesignLibrary( reset, LATEST_API_VERSION, type )
const library = await fetchDesignLibrary( reset, LATEST_API_VERSION, type )

if ( ! library || typeof library !== 'object' ) {
const error = { message: 'Failed to load design library.' }
// eslint-disable-next-line no-console
console.error( 'Stackable: ', error )
return { error }
}

if ( designLibrary.wp_remote_get_error || designLibrary.content_error ) {
const error = designLibrary.wp_remote_get_error ?? designLibrary.content_error
if ( hasLibraryError( library ) ) {
const error = library.wp_remote_get_error ?? library.content_error
// eslint-disable-next-line no-console
console.error( 'Stackable: ', error )
return { error }
Expand All @@ -75,7 +91,7 @@ export const getDesigns = async ( {
await fetchDesignLibrary()
}

return Object.values( designLibrary )
return Object.values( library )
}

export const filterDesigns = async ( {
Expand Down Expand Up @@ -104,12 +120,16 @@ export const filterDesigns = async ( {
export const getDesign = async ( designId, version = '' ) => {
const library = await fetchDesignLibrary( false, version )

if ( ! library || hasLibraryError( library ) ) {
return null
}

const meta = library[ designId ]

let design = await applyFilters( 'stackable.design-library.get-design', null, designId, meta, version )

// Every design has their own template file which contains the entire design, get that.
if ( ! design && meta.template ) {
if ( ! design && meta?.template ) {
design = await fetchDesign( designId, version )
}

Expand Down
29 changes: 23 additions & 6 deletions src/design-library/init.php
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,19 @@ public function get_design_library_from_cloud( $type = 'patterns' ) {

$designs = get_transient( $transient_name );

// Retry when a previous failed fetch was cached without usable library data.
if (
! empty( $designs ) &&
(
isset( $designs['wp_remote_get_error'] ) ||
isset( $designs['content_error'] )
) &&
empty( $designs[ self::API_VERSION ] )
) {
delete_transient( $transient_name );
$designs = false;
}

// Fetch designs.
if ( empty( $designs ) ) {
$designs = array();
Expand All @@ -253,6 +266,8 @@ public function get_design_library_from_cloud( $type = 'patterns' ) {
'code' => $response->get_error_code(),
'message' => $response->get_error_message(),
);
$designs[ self::API_VERSION ] = null;
// Do not cache failed fetches so the next request can retry.
} else {
$content_body = wp_remote_retrieve_body( $response );
$content = apply_filters( 'stackable_design_library_retreive_body', $content_body );
Expand All @@ -263,14 +278,16 @@ public function get_design_library_from_cloud( $type = 'patterns' ) {
$designs['content_error'] = array(
'message' => $content_body,
);
$designs[ self::API_VERSION ] = null;
// Do not cache failed fetches so the next request can retry.
} else {
// We add the latest designs in the `v4` area.
$designs[ self::API_VERSION ] = $content;

// Cache successful results.
set_transient( $transient_name, $designs, 7 * DAY_IN_SECONDS );
}
}

// We add the latest designs in the `v4` area.
$designs[ self::API_VERSION ] = $content;

// Cache results.
set_transient( $transient_name, $designs, 7 * DAY_IN_SECONDS );
}

if ( $type === 'pages' ) {
Expand Down
5 changes: 5 additions & 0 deletions src/lazy-components/design-library/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ const ModalDesignLibrary = props => {

setSidebarDesigns( _designs )
setSelectedCategory( '' )
} ).catch( error => {
setSidebarDesigns( [] )
setErrors( {
message: error?.message || String( error ),
} )
} ).finally( () => {
setDoReset( false )
setIsBusy( false )
Expand Down
Loading