Skip to content

Commit 31a7931

Browse files
committed
Forms: Replace 3 count queries with single optimized endpoint
Reduces from 3 separate REST requests to 1 optimized database query with caching. Related: #45339
1 parent 845fb3a commit 31a7931

File tree

3 files changed

+94
-41
lines changed

3 files changed

+94
-41
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: patch
2+
Type: changed
3+
4+
Forms: replace 3 separate count queries with single optimized counts endpoint.

projects/packages/forms/src/contact-form/class-contact-form-endpoint.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,16 @@ public function register_routes() {
254254
'callback' => array( $this, 'get_forms_config' ),
255255
)
256256
);
257+
258+
register_rest_route(
259+
$this->namespace,
260+
$this->rest_base . '/counts',
261+
array(
262+
'methods' => \WP_REST_Server::READABLE,
263+
'permission_callback' => array( $this, 'get_items_permissions_check' ),
264+
'callback' => array( $this, 'get_status_counts' ),
265+
)
266+
);
257267
}
258268

259269
/**
@@ -302,6 +312,49 @@ static function ( $post_id ) {
302312
);
303313
}
304314

315+
/**
316+
* Retrieves status counts for inbox, spam, and trash in a single optimized query.
317+
*
318+
* @param WP_REST_Request $request Full data about the request.
319+
* @return WP_REST_Response Response object on success.
320+
*/
321+
public function get_status_counts( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
322+
global $wpdb;
323+
324+
$cache_key = 'jetpack_forms_status_counts';
325+
$cached_result = get_transient( $cache_key );
326+
if ( false !== $cached_result ) {
327+
return rest_ensure_response( $cached_result );
328+
}
329+
330+
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
331+
$counts = $wpdb->get_row(
332+
$wpdb->prepare(
333+
"SELECT
334+
SUM(CASE WHEN post_status IN ('publish', 'draft') THEN 1 ELSE 0 END) as inbox,
335+
SUM(CASE WHEN post_status = %s THEN 1 ELSE 0 END) as spam,
336+
SUM(CASE WHEN post_status = %s THEN 1 ELSE 0 END) as trash
337+
FROM $wpdb->posts
338+
WHERE post_type = %s",
339+
'spam',
340+
'trash',
341+
'feedback'
342+
),
343+
ARRAY_A
344+
);
345+
// phpcs:enable
346+
347+
$result = array(
348+
'inbox' => (int) ( $counts['inbox'] ?? 0 ),
349+
'spam' => (int) ( $counts['spam'] ?? 0 ),
350+
'trash' => (int) ( $counts['trash'] ?? 0 ),
351+
);
352+
353+
set_transient( $cache_key, $result, 30 );
354+
355+
return rest_ensure_response( $result );
356+
}
357+
305358
/**
306359
* Adds the additional fields to the item's schema.
307360
*

projects/packages/forms/src/dashboard/hooks/use-inbox-data.ts

Lines changed: 37 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
/**
22
* External dependencies
33
*/
4+
import apiFetch from '@wordpress/api-fetch';
45
import { useEntityRecords } from '@wordpress/core-data';
56
import { useDispatch, useSelect } from '@wordpress/data';
7+
import { useEffect, useMemo, useState } from '@wordpress/element';
68
import { useSearchParams } from 'react-router';
79
/**
810
* Internal dependencies
@@ -36,6 +38,7 @@ interface UseInboxDataReturn {
3638
totalItemsTrash: number;
3739
records: FormResponse[];
3840
isLoadingData: boolean;
41+
isLoadingCounts: boolean;
3942
totalItems: number;
4043
totalPages: number;
4144
selectedResponsesCount: number;
@@ -77,52 +80,45 @@ export default function useInboxData(): UseInboxDataReturn {
7780

7881
const records = ( rawRecords || [] ) as FormResponse[];
7982

80-
const { isResolving: isLoadingInboxData, totalItems: totalItemsInbox = 0 } = useEntityRecords(
81-
'postType',
82-
'feedback',
83-
{
84-
page: 1,
85-
search: '',
86-
...currentQuery,
87-
status: 'publish,draft',
88-
per_page: 1,
89-
_fields: 'id',
90-
}
91-
);
83+
const countsQueryKey = useMemo( () => {
84+
return JSON.stringify( {
85+
status: statusFilter,
86+
parent: currentQuery?.parent,
87+
search: currentQuery?.search,
88+
after: currentQuery?.after,
89+
before: currentQuery?.before,
90+
} );
91+
}, [
92+
statusFilter,
93+
currentQuery?.parent,
94+
currentQuery?.search,
95+
currentQuery?.after,
96+
currentQuery?.before,
97+
] );
9298

93-
const { isResolving: isLoadingSpamData, totalItems: totalItemsSpam = 0 } = useEntityRecords(
94-
'postType',
95-
'feedback',
96-
{
97-
page: 1,
98-
search: '',
99-
...currentQuery,
100-
status: 'spam',
101-
per_page: 1,
102-
_fields: 'id',
103-
}
104-
);
99+
const [ counts, setCounts ] = useState( { inbox: 0, spam: 0, trash: 0 } );
100+
const [ isLoadingCounts, setIsLoadingCounts ] = useState( false );
105101

106-
const { isResolving: isLoadingTrashData, totalItems: totalItemsTrash = 0 } = useEntityRecords(
107-
'postType',
108-
'feedback',
109-
{
110-
page: 1,
111-
search: '',
112-
...currentQuery,
113-
status: 'trash',
114-
per_page: 1,
115-
_fields: 'id',
116-
}
117-
);
102+
useEffect( () => {
103+
const fetchCounts = async () => {
104+
setIsLoadingCounts( true );
105+
const response = await apiFetch< { inbox: number; spam: number; trash: number } >( {
106+
path: '/wp/v2/feedback/counts',
107+
} );
108+
setCounts( response );
109+
setIsLoadingCounts( false );
110+
};
111+
112+
fetchCounts();
113+
}, [ countsQueryKey, totalItems ] );
118114

119115
return {
120-
totalItemsInbox,
121-
totalItemsSpam,
122-
totalItemsTrash,
116+
totalItemsInbox: counts.inbox,
117+
totalItemsSpam: counts.spam,
118+
totalItemsTrash: counts.trash,
123119
records,
124-
isLoadingData:
125-
isLoadingRecordsData || isLoadingInboxData || isLoadingSpamData || isLoadingTrashData,
120+
isLoadingData: isLoadingRecordsData,
121+
isLoadingCounts,
126122
totalItems,
127123
totalPages,
128124
selectedResponsesCount,

0 commit comments

Comments
 (0)