From d91e9bddc2d8f51eb38b8fb556e42cf62f9e8684 Mon Sep 17 00:00:00 2001 From: brookewp Date: Mon, 24 Feb 2025 17:47:44 -0800 Subject: [PATCH 1/5] Update shopify to include pagination Signed-off-by: brookewp --- .../Shopify/Queries/SearchProducts.graphql | 15 +++++++++-- .../Shopify/ShopifyIntegration.php | 25 +++++++++++++++++++ inc/Validation/ConfigSchemas.php | 4 ++- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/inc/Integrations/Shopify/Queries/SearchProducts.graphql b/inc/Integrations/Shopify/Queries/SearchProducts.graphql index 7666fcd3..4711b3c6 100644 --- a/inc/Integrations/Shopify/Queries/SearchProducts.graphql +++ b/inc/Integrations/Shopify/Queries/SearchProducts.graphql @@ -1,5 +1,16 @@ -query SearchProducts($search: String) { - products(first: 10, query: $search, sortKey: BEST_SELLING) { +query SearchProducts($search: String!, $limit: Int!, $cursor_next: String) { + products( + first: $limit + after: $cursor_next + query: $search + sortKey: BEST_SELLING + ) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } edges { node { id diff --git a/inc/Integrations/Shopify/ShopifyIntegration.php b/inc/Integrations/Shopify/ShopifyIntegration.php index 52cd75af..fe2b2466 100644 --- a/inc/Integrations/Shopify/ShopifyIntegration.php +++ b/inc/Integrations/Shopify/ShopifyIntegration.php @@ -86,6 +86,19 @@ public static function get_queries( ShopifyDataSource $data_source ): array { 'search' => [ 'type' => 'ui:search_input', ], + 'limit' => [ + 'default_value' => 8, + 'name' => 'Items per page', + 'type' => 'ui:pagination_per_page', + ], + 'cursor_next' => [ + 'name' => 'Next page cursor', + 'type' => 'ui:pagination_cursor_next', + ], + 'cursor_previous' => [ + 'name' => 'Previous page cursor', + 'type' => 'ui:pagination_cursor_previous', + ], ], 'output_schema' => [ 'path' => '$.data.products.edges[*]', @@ -113,6 +126,18 @@ public static function get_queries( ShopifyDataSource $data_source ): array { ], ], ], + 'pagination_schema' => [ + 'cursor_next' => [ + 'name' => 'Next page cursor', + 'path' => '$.data.products.pageInfo.endCursor', + 'type' => 'string', + ], + 'cursor_previous' => [ + 'name' => 'Previous page cursor', + 'path' => '$.data.products.pageInfo.startCursor', + 'type' => 'string', + ], + ], 'graphql_query' => file_get_contents( __DIR__ . '/Queries/SearchProducts.graphql' ), ] ), ]; diff --git a/inc/Validation/ConfigSchemas.php b/inc/Validation/ConfigSchemas.php index 45c5b2a4..3351cb64 100644 --- a/inc/Validation/ConfigSchemas.php +++ b/inc/Validation/ConfigSchemas.php @@ -291,11 +291,13 @@ private static function generate_http_query_config_schema(): array { // items available in paginated results. This field must be defined // in order present to enable pagination support of any type, // including cursor-based pagination. - 'total_items' => Types::object( [ + 'total_items' => Types::nullable( + Types::object( [ 'name' => Types::nullable( Types::string() ), 'path' => Types::json_path(), 'type' => Types::enum( 'integer' ), ] ), + ), // This field provides a pagination cursor for the next page of // paginated results, or a null value if there is no next page. This // field must be defined in order to enable cursor-based pagination. From 1ec7391160a4c1b19e713d6d553910ce7b6ddc77 Mon Sep 17 00:00:00 2001 From: brookewp Date: Mon, 24 Feb 2025 17:58:36 -0800 Subject: [PATCH 2/5] Add optional next page boolean, and make total items optional Signed-off-by: brookewp --- .../Shopify/ShopifyIntegration.php | 5 +++++ inc/Validation/ConfigSchemas.php | 18 ++++++++++++++---- .../components/item-list/ItemList.tsx | 7 ++++++- .../components/modals/DataViewsModal.tsx | 4 ++++ .../hooks/usePaginationVariables.ts | 5 ++++- .../hooks/useRemoteData.ts | 7 ++++--- types/remote-data.d.ts | 10 ++++++---- 7 files changed, 43 insertions(+), 13 deletions(-) diff --git a/inc/Integrations/Shopify/ShopifyIntegration.php b/inc/Integrations/Shopify/ShopifyIntegration.php index fe2b2466..cb3612dd 100644 --- a/inc/Integrations/Shopify/ShopifyIntegration.php +++ b/inc/Integrations/Shopify/ShopifyIntegration.php @@ -137,6 +137,11 @@ public static function get_queries( ShopifyDataSource $data_source ): array { 'path' => '$.data.products.pageInfo.startCursor', 'type' => 'string', ], + 'has_next_page' => [ + 'name' => 'Has next page', + 'path' => '$.data.products.pageInfo.hasNextPage', + 'type' => 'boolean', + ], ], 'graphql_query' => file_get_contents( __DIR__ . '/Queries/SearchProducts.graphql' ), ] ), diff --git a/inc/Validation/ConfigSchemas.php b/inc/Validation/ConfigSchemas.php index 3351cb64..f75898ac 100644 --- a/inc/Validation/ConfigSchemas.php +++ b/inc/Validation/ConfigSchemas.php @@ -293,10 +293,10 @@ private static function generate_http_query_config_schema(): array { // including cursor-based pagination. 'total_items' => Types::nullable( Types::object( [ - 'name' => Types::nullable( Types::string() ), - 'path' => Types::json_path(), - 'type' => Types::enum( 'integer' ), - ] ), + 'name' => Types::nullable( Types::string() ), + 'path' => Types::json_path(), + 'type' => Types::enum( 'integer' ), + ] ), ), // This field provides a pagination cursor for the next page of // paginated results, or a null value if there is no next page. This @@ -318,6 +318,16 @@ private static function generate_http_query_config_schema(): array { 'type' => Types::enum( 'string' ), ] ), ), + // This field provides a boolean indicating if there is a next page of + // paginated results. This is helpful if the API does not provide a + // total number of items. + 'has_next_page' => Types::nullable( + Types::object( [ + 'name' => Types::nullable( Types::string() ), + 'path' => Types::json_path(), + 'type' => Types::enum( 'boolean' ), + ] ) + ), ] ) ), 'preprocess_response' => Types::nullable( Types::callable() ), diff --git a/src/blocks/remote-data-container/components/item-list/ItemList.tsx b/src/blocks/remote-data-container/components/item-list/ItemList.tsx index 6ba90c70..8c9e45ad 100644 --- a/src/blocks/remote-data-container/components/item-list/ItemList.tsx +++ b/src/blocks/remote-data-container/components/item-list/ItemList.tsx @@ -27,6 +27,7 @@ function getResultsWithId( results: RemoteDataResult[], instanceId: string ): Re interface ItemListProps { availableBindings: Record< string, RemoteDataBinding >; blockName: string; + hasNextPage: boolean; loading: boolean; onSelect: ( data: RemoteDataQueryInput ) => void; onSelectField?: ( data: FieldSelection, fieldValue: string ) => void; @@ -35,6 +36,7 @@ interface ItemListProps { remoteData?: RemoteData; searchInput: string; setPage: ( newPage: number ) => void; + setPerPage: ( newPerPage: number ) => void; setSearchInput: ( newValue: string ) => void; supportsSearch: boolean; totalItems?: number; @@ -45,6 +47,7 @@ export function ItemList( props: ItemListProps ) { const { availableBindings, blockName, + hasNextPage, loading, onSelect, onSelectField, @@ -53,6 +56,7 @@ export function ItemList( props: ItemListProps ) { remoteData, searchInput, setPage, + setPerPage, setSearchInput, supportsSearch, totalItems, @@ -121,6 +125,7 @@ export function ItemList( props: ItemListProps ) { function onChangeView( newView: View ) { setPage( newView.page ?? 1 ); + setPerPage( newView.perPage ?? perPage ?? data.length ); setSearchInput( newView.search ?? '' ); setView( newView ); } @@ -157,7 +162,7 @@ export function ItemList( props: ItemListProps ) { onChangeView={ onChangeView } paginationInfo={ { totalItems: totalItems ?? data.length, - totalPages: totalPages ?? 1, + totalPages: totalPages ?? ( hasNextPage ? page + 1 : page - 1 ), } } search={ supportsSearch } view={ view } diff --git a/src/blocks/remote-data-container/components/modals/DataViewsModal.tsx b/src/blocks/remote-data-container/components/modals/DataViewsModal.tsx index 1bd55b0a..0d6bfe23 100644 --- a/src/blocks/remote-data-container/components/modals/DataViewsModal.tsx +++ b/src/blocks/remote-data-container/components/modals/DataViewsModal.tsx @@ -31,10 +31,12 @@ export const DataViewsModal: React.FC< DataViewsModalProps > = props => { const { close, isOpen, open } = useModalState(); const { data, + hasNextPage, loading, page, searchInput, setPage, + setPerPage, setSearchInput, supportsSearch, totalItems, @@ -72,6 +74,7 @@ export const DataViewsModal: React.FC< DataViewsModalProps > = props => { = props => { remoteData={ data } searchInput={ searchInput } setPage={ setPage } + setPerPage={ setPerPage } setSearchInput={ setSearchInput } supportsSearch={ supportsSearch } totalItems={ totalItems } diff --git a/src/blocks/remote-data-container/hooks/usePaginationVariables.ts b/src/blocks/remote-data-container/hooks/usePaginationVariables.ts index d4cdf536..7f0a0df2 100644 --- a/src/blocks/remote-data-container/hooks/usePaginationVariables.ts +++ b/src/blocks/remote-data-container/hooks/usePaginationVariables.ts @@ -9,6 +9,7 @@ import { } from '@/blocks/remote-data-container/config/constants'; interface UsePaginationVariables { + hasNextPage?: boolean; onFetch: ( remoteData: RemoteData ) => void; page: number; paginationQueryInput: RemoteDataQueryInput; @@ -35,7 +36,7 @@ export function usePaginationVariables( { initialPerPage, inputVariables, }: UsePaginationVariablesInput ): UsePaginationVariables { - const [ paginationData, setPaginationData ] = useState< RemoteDataPagination >(); + const [ paginationData, setPaginationData ] = useState< RemoteDataPagination >( {} ); const [ page, setPage ] = useState< number >( initialPage ); const [ perPage, setPerPage ] = useState< number | null >( initialPerPage ?? null ); @@ -88,6 +89,7 @@ export function usePaginationVariables( { supportsCursorPagination || supportsPagePagination || supportsOffsetPagination; const totalItems = paginationData?.totalItems; const totalPages = totalItems && perPage ? Math.ceil( totalItems / perPage ) : undefined; + const hasNextPage = paginationData?.hasNextPage; function onFetch( remoteData: RemoteData ): void { if ( ! supportsPagination ) { @@ -120,6 +122,7 @@ export function usePaginationVariables( { } return { + hasNextPage, onFetch, page, paginationQueryInput, diff --git a/src/blocks/remote-data-container/hooks/useRemoteData.ts b/src/blocks/remote-data-container/hooks/useRemoteData.ts index 866e138a..d05ca54f 100644 --- a/src/blocks/remote-data-container/hooks/useRemoteData.ts +++ b/src/blocks/remote-data-container/hooks/useRemoteData.ts @@ -31,6 +31,7 @@ async function fetchRemoteData( requestData: RemoteDataApiRequest ): Promise< Re pagination: body.pagination && { cursorNext: body.pagination.cursor_next, cursorPrevious: body.pagination.cursor_previous, + hasNextPage: body.pagination.has_next_page, totalItems: body.pagination.total_items, }, queryInput: body.query_input, @@ -51,8 +52,7 @@ interface UseRemoteData { data?: RemoteData; error?: Error; fetch: ( queryInput: RemoteDataQueryInput ) => Promise< void >; - hasNextPage: boolean; - hasPreviousPage: boolean; + hasNextPage?: boolean; loading: boolean; page: number; perPage?: number; @@ -126,6 +126,7 @@ export function useRemoteData( { const inputVariables = query.inputs; const { + hasNextPage, onFetch: onFetchForPagination, page, perPage, @@ -237,7 +238,7 @@ export function useRemoteData( { data: resolvedData, error, fetch, - hasNextPage: totalPages ? page < totalPages : supportsPagination, + hasNextPage: hasNextPage ?? ( totalPages ? page < totalPages : supportsPagination ), hasPreviousPage: page > 1, loading, page, diff --git a/types/remote-data.d.ts b/types/remote-data.d.ts index 082e1f82..07b6ef5f 100644 --- a/types/remote-data.d.ts +++ b/types/remote-data.d.ts @@ -3,9 +3,10 @@ interface InnerBlockContext { } interface RemoteDataPagination { - cursorNext?: string; - cursorPrevious?: string; - totalItems: number; + hasNextPage?: boolean; + cursorNext?: string; + cursorPrevious?: string; + totalItems?: number; } interface RemoteDataResultFields { @@ -87,7 +88,8 @@ interface RemoteDataApiResponseBody { pagination?: { cursor_next?: string; cursor_previous?: string; - total_items: number; + has_next_page?: boolean; + total_items?: number; }; query_input: RemoteDataQueryInput; result_id: string; From 7a5ef2b86ef2ac2818647587877ea6bd04e81ec1 Mon Sep 17 00:00:00 2001 From: brookewp Date: Tue, 25 Feb 2025 11:14:00 -0800 Subject: [PATCH 3/5] Review suggestions Signed-off-by: brookewp --- inc/Validation/ConfigSchemas.php | 5 ++--- .../remote-data-container/components/item-list/ItemList.tsx | 2 +- .../remote-data-container/hooks/usePaginationVariables.ts | 2 +- types/remote-data.d.ts | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/inc/Validation/ConfigSchemas.php b/inc/Validation/ConfigSchemas.php index f75898ac..7bd7cf57 100644 --- a/inc/Validation/ConfigSchemas.php +++ b/inc/Validation/ConfigSchemas.php @@ -288,9 +288,8 @@ private static function generate_http_query_config_schema(): array { 'pagination_schema' => Types::nullable( Types::object( [ // This field provides an integer representing the total number of - // items available in paginated results. This field must be defined - // in order present to enable pagination support of any type, - // including cursor-based pagination. + // items available in paginated results. Either this field or + // `has_next_page` must be defined in order to enable 'total_items' => Types::nullable( Types::object( [ 'name' => Types::nullable( Types::string() ), diff --git a/src/blocks/remote-data-container/components/item-list/ItemList.tsx b/src/blocks/remote-data-container/components/item-list/ItemList.tsx index 8c9e45ad..0e6fe7e9 100644 --- a/src/blocks/remote-data-container/components/item-list/ItemList.tsx +++ b/src/blocks/remote-data-container/components/item-list/ItemList.tsx @@ -162,7 +162,7 @@ export function ItemList( props: ItemListProps ) { onChangeView={ onChangeView } paginationInfo={ { totalItems: totalItems ?? data.length, - totalPages: totalPages ?? ( hasNextPage ? page + 1 : page - 1 ), + totalPages: totalPages ?? ( hasNextPage ? page + 1 : page - 1 ) ?? 1, } } search={ supportsSearch } view={ view } diff --git a/src/blocks/remote-data-container/hooks/usePaginationVariables.ts b/src/blocks/remote-data-container/hooks/usePaginationVariables.ts index 7f0a0df2..51beb5bf 100644 --- a/src/blocks/remote-data-container/hooks/usePaginationVariables.ts +++ b/src/blocks/remote-data-container/hooks/usePaginationVariables.ts @@ -36,7 +36,7 @@ export function usePaginationVariables( { initialPerPage, inputVariables, }: UsePaginationVariablesInput ): UsePaginationVariables { - const [ paginationData, setPaginationData ] = useState< RemoteDataPagination >( {} ); + const [ paginationData, setPaginationData ] = useState< RemoteDataPagination >(); const [ page, setPage ] = useState< number >( initialPage ); const [ perPage, setPerPage ] = useState< number | null >( initialPerPage ?? null ); diff --git a/types/remote-data.d.ts b/types/remote-data.d.ts index 07b6ef5f..9aae9e24 100644 --- a/types/remote-data.d.ts +++ b/types/remote-data.d.ts @@ -3,9 +3,9 @@ interface InnerBlockContext { } interface RemoteDataPagination { - hasNextPage?: boolean; cursorNext?: string; cursorPrevious?: string; + hasNextPage?: boolean; totalItems?: number; } From b8b23b2c4d803ee0b947ebd01bb78fb1610ab57b Mon Sep 17 00:00:00 2001 From: brookewp Date: Tue, 25 Feb 2025 11:21:10 -0800 Subject: [PATCH 4/5] Fix types Signed-off-by: brookewp --- src/blocks/remote-data-container/hooks/useRemoteData.ts | 1 + types/remote-data.d.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/blocks/remote-data-container/hooks/useRemoteData.ts b/src/blocks/remote-data-container/hooks/useRemoteData.ts index d05ca54f..5a2b5da9 100644 --- a/src/blocks/remote-data-container/hooks/useRemoteData.ts +++ b/src/blocks/remote-data-container/hooks/useRemoteData.ts @@ -53,6 +53,7 @@ interface UseRemoteData { error?: Error; fetch: ( queryInput: RemoteDataQueryInput ) => Promise< void >; hasNextPage?: boolean; + hasPreviousPage: boolean; loading: boolean; page: number; perPage?: number; diff --git a/types/remote-data.d.ts b/types/remote-data.d.ts index 9aae9e24..1fdd288b 100644 --- a/types/remote-data.d.ts +++ b/types/remote-data.d.ts @@ -3,8 +3,8 @@ interface InnerBlockContext { } interface RemoteDataPagination { - cursorNext?: string; - cursorPrevious?: string; + cursorNext?: string; + cursorPrevious?: string; hasNextPage?: boolean; totalItems?: number; } From 52c0246573dded1e43a58021a96f09aeb6c16cbc Mon Sep 17 00:00:00 2001 From: brookewp Date: Tue, 25 Feb 2025 15:12:20 -0800 Subject: [PATCH 5/5] Add missing word Signed-off-by: brookewp --- inc/Validation/ConfigSchemas.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/Validation/ConfigSchemas.php b/inc/Validation/ConfigSchemas.php index 7bd7cf57..aa18737b 100644 --- a/inc/Validation/ConfigSchemas.php +++ b/inc/Validation/ConfigSchemas.php @@ -289,7 +289,7 @@ private static function generate_http_query_config_schema(): array { Types::object( [ // This field provides an integer representing the total number of // items available in paginated results. Either this field or - // `has_next_page` must be defined in order to enable + // `has_next_page` must be defined in order to enable pagination. 'total_items' => Types::nullable( Types::object( [ 'name' => Types::nullable( Types::string() ),