From 53eba6e595fe57419ea3d82ac95c10e26b35827a Mon Sep 17 00:00:00 2001 From: brookewp Date: Wed, 12 Mar 2025 11:34:29 -0700 Subject: [PATCH 01/16] Add loop with input example with new collection type Signed-off-by: brookewp --- .../rest-api/art-institute/art-institute.php | 53 +++++++++++++++++++ inc/Editor/BlockManagement/ConfigRegistry.php | 8 +-- inc/Validation/ConfigSchemas.php | 2 + .../placeholders/ItemSelectQueryType.tsx | 24 ++++++++- .../components/popovers/InputPopover.tsx | 4 +- 5 files changed, 85 insertions(+), 6 deletions(-) diff --git a/example/rest-api/art-institute/art-institute.php b/example/rest-api/art-institute/art-institute.php index d854c407d..34b457159 100644 --- a/example/rest-api/art-institute/art-institute.php +++ b/example/rest-api/art-institute/art-institute.php @@ -89,6 +89,51 @@ function register_aic_block(): void { ], ]); + $collection_query = HttpQuery::from_array([ + 'data_source' => $aic_data_source, + 'endpoint' => function ( array $input_variables ) use ( $aic_data_source ): string { + $endpoint = $aic_data_source->get_endpoint(); + return add_query_arg( [ + 'limit' => $input_variables['limit'], + 'fields' => 'id,title,image_id,artist_title', + ], $endpoint ); + }, + 'input_schema' => [ + 'limit' => [ + 'name' => 'Limit', + 'type' => 'ui:input', + 'default_value' => 10, + ], + ], + 'output_schema' => [ + 'is_collection' => true, + 'path' => '$.data[*]', + 'type' => [ + 'id' => [ + 'name' => 'Art ID', + 'type' => 'id', + ], + 'artist_title' => [ + 'name' => 'Artist Title', + 'type' => 'string', + 'path' => '$.artist_title', + ], + 'title' => [ + 'name' => 'Title', + 'type' => 'string', + 'path' => '$.title', + ], + 'image_url' => [ + 'name' => 'Image URL', + 'generate' => function ( $data ): string { + return 'https://www.artic.edu/iiif/2/' . $data['image_id'] . '/full/843,/0/default.jpg'; + }, + 'type' => 'image_url', + ], + ], + ], + ]); + $search_art_query = HttpQuery::from_array([ 'data_source' => $aic_data_source, 'endpoint' => function ( array $input_variables ) use ( $aic_data_source ): string { @@ -156,6 +201,14 @@ function register_aic_block(): void { ], ]); + register_remote_data_block( [ + 'title' => 'Art Institute of Chicago Loop', + 'render_query' => [ + 'query' => $collection_query, + 'loop' => true, + ], + ] ); + register_remote_data_block([ 'title' => 'Art Institute of Chicago', 'icon' => 'art', diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index c10f2ffea..76ad4386a 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -51,6 +51,8 @@ public static function register_block( array $user_config = [] ): bool|WP_Error $display_query = self::inflate_query( $user_config[ self::RENDER_QUERY_KEY ]['query'] ); $input_schema = $display_query->get_input_schema(); + $is_loop = $user_config[ self::RENDER_QUERY_KEY ]['loop'] ?? false; + // Build the base configuration for the block. This is our own internal // configuration, not what will be passed to WordPress's register_block_type. // @see BlockRegistration::register_block_type::register_blocks. @@ -58,7 +60,7 @@ public static function register_block( array $user_config = [] ): bool|WP_Error 'description' => '', 'icon' => $user_config['icon'] ?? 'cloud', 'name' => $block_name, - 'loop' => $user_config[ self::RENDER_QUERY_KEY ]['loop'] ?? false, + 'loop' => $is_loop, 'overrides' => $user_config['overrides'] ?? [], 'patterns' => [], 'queries' => [ @@ -75,9 +77,9 @@ public static function register_block( array $user_config = [] ): bool|WP_Error 'type' => $input_var['type'] ?? 'string', ]; }, array_keys( $input_schema ), array_values( $input_schema ) ), - 'name' => 'Manual input', + 'name' => $is_loop ? 'Collection' : 'Manual input', 'query_key' => self::DISPLAY_QUERY_KEY, - 'type' => 'input', + 'type' => $is_loop ? 'collection' : 'input', ], ], 'title' => $block_title, diff --git a/inc/Validation/ConfigSchemas.php b/inc/Validation/ConfigSchemas.php index 4b309bf7a..47fd60dce 100644 --- a/inc/Validation/ConfigSchemas.php +++ b/inc/Validation/ConfigSchemas.php @@ -206,6 +206,8 @@ private static function generate_http_query_config_schema(): array { // implode an array of IDs into a comma-separated list and map it // to a query parameter). 'id:list', + // A string that represents an input field to refine the query results. + 'ui:input', // A string that represents search query input. An input variable // with this type must be present for the query to be considered a // search query. diff --git a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx index b5a7c33dc..601ddda8b 100644 --- a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx +++ b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx @@ -1,4 +1,4 @@ -import { ButtonGroup } from '@wordpress/components'; +import { ButtonGroup, Button } from '@wordpress/components'; import { InputModal } from '../modals/InputModal'; import { InputPopover } from '../popovers/InputPopover'; @@ -44,6 +44,28 @@ export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) { ) : ( ); + case 'collection': + if ( selector.inputs.length === 1 && selector.inputs[ 0 ] ) { + return ( + + ); + } + return ( + + ); } return null; diff --git a/src/blocks/remote-data-container/components/popovers/InputPopover.tsx b/src/blocks/remote-data-container/components/popovers/InputPopover.tsx index 815889ff0..2ae411c31 100644 --- a/src/blocks/remote-data-container/components/popovers/InputPopover.tsx +++ b/src/blocks/remote-data-container/components/popovers/InputPopover.tsx @@ -23,7 +23,7 @@ interface InputPopoverProps { } export function InputPopover( props: InputPopoverProps ) { - const { input, onSelect } = props; + const { input, onSelect, title } = props; const dataSourceType = getBlockDataSourceType( props.blockName ); @@ -62,7 +62,7 @@ export function InputPopover( props: InputPopoverProps ) { }; default: return { - buttonText: __( 'Provide manual input' ), + buttonText: __( 'Provide' ) + ' ' + title, }; } } From e682ebe231ffebd690b32c56e06e5b81cacd4329 Mon Sep 17 00:00:00 2001 From: brookewp Date: Wed, 12 Mar 2025 11:35:07 -0700 Subject: [PATCH 02/16] Add instructions to placeholder and reduce to one placeholder Signed-off-by: brookewp --- .../rest-api/art-institute/art-institute.php | 1 + .../BlockManagement/BlockRegistration.php | 1 + inc/Editor/BlockManagement/ConfigRegistry.php | 1 + inc/Validation/ConfigSchemas.php | 1 + .../components/placeholders/Placeholder.tsx | 32 +++++++++++++------ .../placeholders/PlaceholderLoop.tsx | 29 ----------------- .../placeholders/PlaceholderSingle.tsx | 26 --------------- types/localized-block-data.d.ts | 1 + 8 files changed, 28 insertions(+), 64 deletions(-) delete mode 100644 src/blocks/remote-data-container/components/placeholders/PlaceholderLoop.tsx delete mode 100644 src/blocks/remote-data-container/components/placeholders/PlaceholderSingle.tsx diff --git a/example/rest-api/art-institute/art-institute.php b/example/rest-api/art-institute/art-institute.php index 34b457159..2428f2ce5 100644 --- a/example/rest-api/art-institute/art-institute.php +++ b/example/rest-api/art-institute/art-institute.php @@ -203,6 +203,7 @@ function register_aic_block(): void { register_remote_data_block( [ 'title' => 'Art Institute of Chicago Loop', + 'instructions' => 'This block displays a set amount of artworks based on the provided limit.', 'render_query' => [ 'query' => $collection_query, 'loop' => true, diff --git a/inc/Editor/BlockManagement/BlockRegistration.php b/inc/Editor/BlockManagement/BlockRegistration.php index b59f61f21..d40f38c49 100644 --- a/inc/Editor/BlockManagement/BlockRegistration.php +++ b/inc/Editor/BlockManagement/BlockRegistration.php @@ -77,6 +77,7 @@ public static function register_block_configuration( array $config ): array { $block_config = [ 'availableBindings' => $available_bindings, 'availableOverrides' => $config['overrides'] ?? [], + 'instructions' => $config['instructions'], 'loop' => $config['loop'], 'name' => $block_name, 'dataSourceType' => ConfigStore::get_data_source_type( $block_name ), diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index 76ad4386a..3cb55dc41 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -59,6 +59,7 @@ public static function register_block( array $user_config = [] ): bool|WP_Error $config = [ 'description' => '', 'icon' => $user_config['icon'] ?? 'cloud', + 'instructions' => $user_config['instructions'] ?? null, 'name' => $block_name, 'loop' => $is_loop, 'overrides' => $user_config['overrides'] ?? [], diff --git a/inc/Validation/ConfigSchemas.php b/inc/Validation/ConfigSchemas.php index 47fd60dce..210f32942 100644 --- a/inc/Validation/ConfigSchemas.php +++ b/inc/Validation/ConfigSchemas.php @@ -76,6 +76,7 @@ public static function get_remote_data_block_attribute_config_schema(): array { private static function generate_remote_data_block_config_schema(): array { return Types::object( [ 'icon' => Types::nullable( Types::string() ), + 'instructions' => Types::nullable( Types::string() ), 'patterns' => Types::nullable( Types::list_of( Types::object( [ diff --git a/src/blocks/remote-data-container/components/placeholders/Placeholder.tsx b/src/blocks/remote-data-container/components/placeholders/Placeholder.tsx index 5b43a5f3b..886738dff 100644 --- a/src/blocks/remote-data-container/components/placeholders/Placeholder.tsx +++ b/src/blocks/remote-data-container/components/placeholders/Placeholder.tsx @@ -1,17 +1,31 @@ -import { PlaceholderLoop } from '@/blocks/remote-data-container/components/placeholders/PlaceholderLoop'; -import { PlaceholderSingle } from '@/blocks/remote-data-container/components/placeholders/PlaceholderSingle'; +import { IconType, Placeholder as PlaceholderComponent } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { cloud } from '@wordpress/icons'; -export interface PlaceholderProps { +import { ItemSelectQueryType } from '@/blocks/remote-data-container/components/placeholders/ItemSelectQueryType'; + +interface PlaceholderProps { blockConfig: BlockConfig; - onSelect: ( input: RemoteDataQueryInput[] ) => void; + onSelect: ( data: RemoteDataQueryInput[] ) => void; } export function Placeholder( props: PlaceholderProps ) { - const { loop } = props.blockConfig; + const { blockConfig, onSelect } = props; + const { instructions, loop, settings } = blockConfig; + + const iconElement: IconType = ( settings.icon as IconType ) ?? cloud; - if ( loop ) { - return ; - } + const defaultInstructions = loop + ? __( 'This block displays a list of items.' ) + : __( 'This block requires selection of one or more items for display.' ); - return ; + return ( + + + + ); } diff --git a/src/blocks/remote-data-container/components/placeholders/PlaceholderLoop.tsx b/src/blocks/remote-data-container/components/placeholders/PlaceholderLoop.tsx deleted file mode 100644 index eec13fa52..000000000 --- a/src/blocks/remote-data-container/components/placeholders/PlaceholderLoop.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Button, Placeholder } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { cloud } from '@wordpress/icons'; - -interface PlaceholderLoopProps { - blockConfig: BlockConfig; - onSelect: ( data: RemoteDataQueryInput[] ) => void; -} - -export function PlaceholderLoop( props: PlaceholderLoopProps ) { - const { - blockConfig: { - settings: { title }, - }, - onSelect, - } = props; - - return ( - - - - ); -} diff --git a/src/blocks/remote-data-container/components/placeholders/PlaceholderSingle.tsx b/src/blocks/remote-data-container/components/placeholders/PlaceholderSingle.tsx deleted file mode 100644 index ff8a59507..000000000 --- a/src/blocks/remote-data-container/components/placeholders/PlaceholderSingle.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { IconType, Placeholder } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { cloud } from '@wordpress/icons'; - -import { ItemSelectQueryType } from '@/blocks/remote-data-container/components/placeholders/ItemSelectQueryType'; - -interface PlaceholderSingleProps { - blockConfig: BlockConfig; - onSelect: ( data: RemoteDataQueryInput[] ) => void; -} - -export function PlaceholderSingle( props: PlaceholderSingleProps ) { - const { blockConfig, onSelect } = props; - - const iconElement: IconType = ( blockConfig.settings.icon as IconType ) ?? cloud; - - return ( - - - - ); -} diff --git a/types/localized-block-data.d.ts b/types/localized-block-data.d.ts index 2856c562f..08ad38cb1 100644 --- a/types/localized-block-data.d.ts +++ b/types/localized-block-data.d.ts @@ -25,6 +25,7 @@ interface BlockConfig { availableBindings: AvailableBindings; availableOverrides: InputVariableOverride[]; dataSourceType: string; + instructions?: string; loop: boolean; name: string; patterns: { From 9d79a1ad1a66bd3f217b7c858e2a64d2b0027f30 Mon Sep 17 00:00:00 2001 From: brookewp Date: Wed, 12 Mar 2025 18:07:02 -0700 Subject: [PATCH 03/16] Rename to loop for clarity Signed-off-by: brookewp --- inc/Editor/BlockManagement/ConfigRegistry.php | 2 +- .../placeholders/ItemSelectQueryType.tsx | 31 +++++++++---------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index 3cb55dc41..c8141f737 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -80,7 +80,7 @@ public static function register_block( array $user_config = [] ): bool|WP_Error }, array_keys( $input_schema ), array_values( $input_schema ) ), 'name' => $is_loop ? 'Collection' : 'Manual input', 'query_key' => self::DISPLAY_QUERY_KEY, - 'type' => $is_loop ? 'collection' : 'input', + 'type' => $is_loop ? 'loop' : 'input', ], ], 'title' => $block_title, diff --git a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx index 601ddda8b..a57fdb0c5 100644 --- a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx +++ b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx @@ -39,12 +39,7 @@ export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) { /> ); case 'input': - return selector.inputs.length === 1 && selector.inputs[ 0 ] ? ( - - ) : ( - - ); - case 'collection': + case 'loop': if ( selector.inputs.length === 1 && selector.inputs[ 0 ] ) { return ( ); } - return ( - - ); + return ; } + return ( + + ); + return null; } ) } From 915471929802353eff95e4fba7d9d69bd8caeaae Mon Sep 17 00:00:00 2001 From: brookewp Date: Wed, 12 Mar 2025 15:34:55 -0700 Subject: [PATCH 04/16] Add shopify and salesforce examples Signed-off-by: brookewp --- .../SalesforceD2CIntegration.php | 55 ++++++++++++++ .../Queries/GetProductsByCollectionId.graphql | 27 +++++++ .../Shopify/ShopifyIntegration.php | 73 +++++++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 inc/Integrations/Shopify/Queries/GetProductsByCollectionId.graphql diff --git a/inc/Integrations/SalesforceD2C/SalesforceD2CIntegration.php b/inc/Integrations/SalesforceD2C/SalesforceD2CIntegration.php index bdb5e24bf..68b133641 100644 --- a/inc/Integrations/SalesforceD2C/SalesforceD2CIntegration.php +++ b/inc/Integrations/SalesforceD2C/SalesforceD2CIntegration.php @@ -145,12 +145,67 @@ private static function get_queries( SalesforceD2CDataSource $data_source ): arr ], 'request_headers' => $get_request_headers, ] ), + 'by_category' => HttpQuery::from_array( [ + 'data_source' => $data_source, + 'endpoint' => function ( array $input_variables ) use ( $base_endpoint, $service_config ): string { + return sprintf( + '%s/services/data/v63.0/commerce/webstores/%s/search/products?categoryId=%s', + $base_endpoint, + $service_config['store_id'], + urlencode( $input_variables['category_id'] ) + ); + + }, + 'input_schema' => [ + 'category_id' => [ + 'required' => true, + 'type' => 'ui:input', + ], + ], + 'output_schema' => [ + 'path' => '$.productsPage.products[*]', + 'is_collection' => true, + 'type' => [ + 'product_id' => [ + 'name' => 'Product ID', + 'path' => '$.id', + 'type' => 'id', + ], + 'product_sku' => [ + 'name' => 'Product SKU', + 'path' => '$.fields.StockKeepingUnit.value', + 'type' => 'string', + ], + 'name' => [ + 'name' => 'Name', + 'path' => '$.name', + 'type' => 'title', + ], + 'image_url' => [ + 'name' => 'Image URL', + 'path' => '$.defaultImage.url', + 'type' => 'image_url', + ], + ], + ], + 'request_headers' => $get_request_headers, + ] ), ]; } public static function register_blocks_for_salesforce_data_source( SalesforceD2CDataSource $data_source ): void { $queries = self::get_queries( $data_source ); + register_remote_data_block( [ + 'title' => $data_source->get_display_name() . ': Products by Category', + 'icon' => 'money-alt', + 'instructions' => 'Enter a category ID to display products from that category.', + 'render_query' => [ + 'query' => $queries['by_category'], + 'loop' => true, + ], + ] ); + register_remote_data_block( [ 'title' => $data_source->get_display_name(), diff --git a/inc/Integrations/Shopify/Queries/GetProductsByCollectionId.graphql b/inc/Integrations/Shopify/Queries/GetProductsByCollectionId.graphql new file mode 100644 index 000000000..3d6fb4133 --- /dev/null +++ b/inc/Integrations/Shopify/Queries/GetProductsByCollectionId.graphql @@ -0,0 +1,27 @@ +query GetProductsByCollectionId($id: ID!) { + collection(id: $id) { + id + title + products(first: 10) { + nodes { + id + collections(first: 10) { + nodes { + title + } + } + description + featuredImage { + url + altText + } + priceRange { + maxVariantPrice { + amount + } + } + title + } + } + } +} diff --git a/inc/Integrations/Shopify/ShopifyIntegration.php b/inc/Integrations/Shopify/ShopifyIntegration.php index 619524b15..26ce53630 100644 --- a/inc/Integrations/Shopify/ShopifyIntegration.php +++ b/inc/Integrations/Shopify/ShopifyIntegration.php @@ -80,6 +80,67 @@ public static function get_queries( ShopifyDataSource $data_source ): array { ], 'graphql_query' => file_get_contents( __DIR__ . '/Queries/GetProductById.graphql' ), ] ), + 'shopify_get_products_by_collection_id' => GraphqlQuery::from_array( [ + 'data_source' => $data_source, + 'input_schema' => [ + 'id' => [ + 'name' => 'Collection ID', + 'type' => 'ui:input', + ], + ], + 'output_schema' => [ + 'is_collection' => true, + 'path' => '$.data.collection.products.nodes[*]', + 'type' => [ + 'id' => [ + 'name' => 'Product ID', + 'path' => '$.id', + 'type' => 'id', + ], + 'collections' => [ + 'name' => 'Collections', + 'path' => '$.collections.nodes[*]', + 'generate' => function ( array $data ): string { + if (is_array($data) && isset($data['collections']['nodes'])) { + $titles = array_map(function($node) { + return $node['title'] ?? ''; + }, $data['collections']['nodes']); + + return implode(', ', array_filter($titles)); + } + return ''; + }, + 'type' => 'string', + ], + 'description' => [ + 'name' => 'Product description', + 'path' => '$.descriptionHtml', + 'type' => 'string', + ], + 'image_alt_text' => [ + 'name' => 'Image Alt Text', + 'path' => '$.featuredImage.altText', + 'type' => 'image_alt', + ], + 'image_url' => [ + 'name' => 'Item image URL', + 'path' => '$.featuredImage.url', + 'type' => 'image_url', + ], + 'price' => [ + 'name' => 'Item price', + 'path' => '$.priceRange.maxVariantPrice.amount', + 'type' => 'currency_in_current_locale', + ], + 'title' => [ + 'name' => 'Product title', + 'path' => '$.title', + 'type' => 'title', + ], + ], + ], + 'graphql_query' => file_get_contents( __DIR__ . '/Queries/GetProductsByCollectionId.graphql' ), + ] ), 'shopify_search_products' => GraphqlQuery::from_array( [ 'data_source' => $data_source, 'input_schema' => [ @@ -152,6 +213,17 @@ public static function register_blocks_for_shopify_data_source( ShopifyDataSourc $block_title = $data_source->get_display_name(); $queries = self::get_queries( $data_source ); + register_remote_data_block( [ + 'title' => $block_title . ': Products by Collection', + 'icon' => 'cart', + 'instructions' => 'Enter a collection ID to display products from that collection.', + 'render_query' => [ + 'query' => $queries['shopify_get_products_by_collection_id'], + 'loop' => true, + ], + ] ); + + register_remote_data_block( [ 'title' => $block_title, 'icon' => 'cart', @@ -172,6 +244,7 @@ public static function register_blocks_for_shopify_data_source( ShopifyDataSourc ], ], ] ); + } /** From c0708ad2be93babb10bba7a6e61a3153ef44da4b Mon Sep 17 00:00:00 2001 From: brookewp Date: Wed, 12 Mar 2025 15:35:02 -0700 Subject: [PATCH 05/16] Add icon to art loop Signed-off-by: brookewp --- example/rest-api/art-institute/art-institute.php | 1 + 1 file changed, 1 insertion(+) diff --git a/example/rest-api/art-institute/art-institute.php b/example/rest-api/art-institute/art-institute.php index 2428f2ce5..af42d8195 100644 --- a/example/rest-api/art-institute/art-institute.php +++ b/example/rest-api/art-institute/art-institute.php @@ -203,6 +203,7 @@ function register_aic_block(): void { register_remote_data_block( [ 'title' => 'Art Institute of Chicago Loop', + 'icon' => 'art', 'instructions' => 'This block displays a set amount of artworks based on the provided limit.', 'render_query' => [ 'query' => $collection_query, From 4b46429358bc75a3c6b426ad55fc887d40af7ca0 Mon Sep 17 00:00:00 2001 From: brookewp Date: Wed, 12 Mar 2025 18:18:42 -0700 Subject: [PATCH 06/16] Update names Signed-off-by: brookewp --- inc/Integrations/SalesforceD2C/SalesforceD2CIntegration.php | 1 + inc/Integrations/Shopify/ShopifyIntegration.php | 1 + 2 files changed, 2 insertions(+) diff --git a/inc/Integrations/SalesforceD2C/SalesforceD2CIntegration.php b/inc/Integrations/SalesforceD2C/SalesforceD2CIntegration.php index 68b133641..c6b39264b 100644 --- a/inc/Integrations/SalesforceD2C/SalesforceD2CIntegration.php +++ b/inc/Integrations/SalesforceD2C/SalesforceD2CIntegration.php @@ -160,6 +160,7 @@ private static function get_queries( SalesforceD2CDataSource $data_source ): arr 'category_id' => [ 'required' => true, 'type' => 'ui:input', + 'name' => 'Category ID', ], ], 'output_schema' => [ diff --git a/inc/Integrations/Shopify/ShopifyIntegration.php b/inc/Integrations/Shopify/ShopifyIntegration.php index 26ce53630..9f3397adf 100644 --- a/inc/Integrations/Shopify/ShopifyIntegration.php +++ b/inc/Integrations/Shopify/ShopifyIntegration.php @@ -34,6 +34,7 @@ public static function get_queries( ShopifyDataSource $data_source ): array { 'input_schema' => [ 'id' => [ 'type' => 'id', + 'name' => 'Product ID', ], ], 'output_schema' => [ From 7afa52888156779f23d532775f4b9f46b3060bf1 Mon Sep 17 00:00:00 2001 From: brookewp Date: Wed, 12 Mar 2025 18:44:58 -0700 Subject: [PATCH 07/16] Linting Signed-off-by: brookewp --- .../SalesforceD2C/SalesforceD2CIntegration.php | 1 - inc/Integrations/Shopify/ShopifyIntegration.php | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/inc/Integrations/SalesforceD2C/SalesforceD2CIntegration.php b/inc/Integrations/SalesforceD2C/SalesforceD2CIntegration.php index c6b39264b..141d3a919 100644 --- a/inc/Integrations/SalesforceD2C/SalesforceD2CIntegration.php +++ b/inc/Integrations/SalesforceD2C/SalesforceD2CIntegration.php @@ -154,7 +154,6 @@ private static function get_queries( SalesforceD2CDataSource $data_source ): arr $service_config['store_id'], urlencode( $input_variables['category_id'] ) ); - }, 'input_schema' => [ 'category_id' => [ diff --git a/inc/Integrations/Shopify/ShopifyIntegration.php b/inc/Integrations/Shopify/ShopifyIntegration.php index 9f3397adf..6440f34f6 100644 --- a/inc/Integrations/Shopify/ShopifyIntegration.php +++ b/inc/Integrations/Shopify/ShopifyIntegration.php @@ -102,12 +102,12 @@ public static function get_queries( ShopifyDataSource $data_source ): array { 'name' => 'Collections', 'path' => '$.collections.nodes[*]', 'generate' => function ( array $data ): string { - if (is_array($data) && isset($data['collections']['nodes'])) { - $titles = array_map(function($node) { + if ( is_array( $data ) && isset( $data['collections']['nodes'] ) ) { + $titles = array_map(function ( $node ) { return $node['title'] ?? ''; }, $data['collections']['nodes']); - return implode(', ', array_filter($titles)); + return implode( ', ', array_filter( $titles ) ); } return ''; }, @@ -245,7 +245,6 @@ public static function register_blocks_for_shopify_data_source( ShopifyDataSourc ], ], ] ); - } /** From 479a3efef1d1918bed770994cd1def4d47b6e4f5 Mon Sep 17 00:00:00 2001 From: brookewp Date: Wed, 19 Mar 2025 11:20:27 -0700 Subject: [PATCH 08/16] Add query input to panel for updating Signed-off-by: brookewp --- .../components/panels/QueryInputsPanel.tsx | 39 +++++++++++++++++++ src/blocks/remote-data-container/edit.tsx | 15 +++++++ 2 files changed, 54 insertions(+) create mode 100644 src/blocks/remote-data-container/components/panels/QueryInputsPanel.tsx diff --git a/src/blocks/remote-data-container/components/panels/QueryInputsPanel.tsx b/src/blocks/remote-data-container/components/panels/QueryInputsPanel.tsx new file mode 100644 index 000000000..427a2db56 --- /dev/null +++ b/src/blocks/remote-data-container/components/panels/QueryInputsPanel.tsx @@ -0,0 +1,39 @@ +import { Button, PanelBody, TextControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +interface QueryInputsPanelProps { + queryInputs: RemoteDataQueryInput[]; + onUpdateQueryInputs: ( inputs: RemoteDataQueryInput[] ) => void; +} + +export function QueryInputsPanel( { queryInputs, onUpdateQueryInputs }: QueryInputsPanelProps ) { + return ( + +
{ + event.preventDefault(); + onUpdateQueryInputs( queryInputs ); + } } + > + { queryInputs.map( ( input, index ) => + Object.entries( input ).map( ( [ key, value ] ) => ( + { + const newInputs = queryInputs.map( ( item, i ) => + i === index ? { ...item, [ key ]: newValue } : item + ); + onUpdateQueryInputs( newInputs ); + } } + /> + ) ) + ) } + + +
+ ); +} diff --git a/src/blocks/remote-data-container/edit.tsx b/src/blocks/remote-data-container/edit.tsx index 2eb305d14..ca0b566da 100644 --- a/src/blocks/remote-data-container/edit.tsx +++ b/src/blocks/remote-data-container/edit.tsx @@ -3,6 +3,7 @@ import { BlockEditProps } from '@wordpress/blocks'; import { Spinner } from '@wordpress/components'; import { useState } from '@wordpress/element'; +import { QueryInputsPanel } from './components/panels/QueryInputsPanel'; import { InnerBlocks } from '@/blocks/remote-data-container/components/InnerBlocks'; import { DataPanel } from '@/blocks/remote-data-container/components/panels/DataPanel'; import { OverridesPanel } from '@/blocks/remote-data-container/components/panels/OverridesPanel'; @@ -83,6 +84,16 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ) { } } + function onUpdateQueryInputs( inputs: RemoteDataQueryInput[] ): void { + if ( ! remoteDataAttribute ) return; + + updateRemoteData( { + ...remoteDataAttribute, + queryInputs: inputs, + } ); + refreshRemoteData(); + } + // No remote data has been selected yet, show a placeholder. if ( ! data ) { return ( @@ -110,6 +121,10 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ) { return ( <> + Date: Wed, 19 Mar 2025 11:35:39 -0700 Subject: [PATCH 09/16] Add handling for comma separated inputs Signed-off-by: brookewp --- .../components/panels/QueryInputsPanel.tsx | 54 +++++++++++++------ 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/src/blocks/remote-data-container/components/panels/QueryInputsPanel.tsx b/src/blocks/remote-data-container/components/panels/QueryInputsPanel.tsx index 427a2db56..479817b58 100644 --- a/src/blocks/remote-data-container/components/panels/QueryInputsPanel.tsx +++ b/src/blocks/remote-data-container/components/panels/QueryInputsPanel.tsx @@ -10,25 +10,49 @@ export function QueryInputsPanel( { queryInputs, onUpdateQueryInputs }: QueryInp return (
{ + onSubmit={ event => { event.preventDefault(); - onUpdateQueryInputs( queryInputs ); + + const cleanedInputs = queryInputs.map( input => { + const entries = Object.entries( input ).map( ( [ key, value ] ) => [ + key, + typeof value === 'string' && value.includes( ',' ) + ? value + .split( ',' ) + .map( item => item.trim() ) + .filter( Boolean ) + : value, + ] ); + + return Object.fromEntries( entries ) as RemoteDataQueryInput; + } ); + + onUpdateQueryInputs( cleanedInputs ); } } > { queryInputs.map( ( input, index ) => - Object.entries( input ).map( ( [ key, value ] ) => ( - { - const newInputs = queryInputs.map( ( item, i ) => - i === index ? { ...item, [ key ]: newValue } : item - ); - onUpdateQueryInputs( newInputs ); - } } - /> - ) ) + Object.entries( input ).map( ( [ key, value ] ) => { + const displayValue = Array.isArray( value ) ? value.join( ',' ) : ( value as string ); + + return ( + { + const processedValue = newValue.includes( ',' ) + ? newValue.split( ',' ).map( item => item.trim() ) + : newValue; + + onUpdateQueryInputs( + queryInputs.map( ( item, itemIndex ) => + itemIndex === index ? { ...item, [ key ]: processedValue } : item + ) + ); + } } + /> + ); + } ) ) }