From 48e710d71c13589441925ddaa35814bc50a79a9f Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Wed, 7 May 2025 14:17:47 +1000 Subject: [PATCH 01/74] Attempting to port code from PR405 into here --- .../rest-api/art-institute/art-institute.php | 26 +++------ inc/Editor/BlockManagement/ConfigRegistry.php | 56 +++++++++++++++++-- inc/ExampleApi/ExampleApi.php | 12 ++-- inc/Validation/ConfigSchemas.php | 19 ++++++- 4 files changed, 82 insertions(+), 31 deletions(-) diff --git a/example/rest-api/art-institute/art-institute.php b/example/rest-api/art-institute/art-institute.php index 920c15556..7f6b54d6d 100644 --- a/example/rest-api/art-institute/art-institute.php +++ b/example/rest-api/art-institute/art-institute.php @@ -204,28 +204,20 @@ 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, - 'loop' => true, - ], - ] ); - register_remote_data_block([ 'title' => 'Art Institute of Chicago', 'icon' => 'art', - 'render_query' => [ - 'query' => $get_art_query, + 'instructions' => 'This block displays a set amount of artworks based on the provided limit. This could be 1, or many.', + 'queries' => [ + 'display' => $get_art_query, + 'collection' => $collection_query, + 'search' => $search_art_query, ], - 'selection_queries' => [ - [ - 'query' => $search_art_query, - 'type' => 'search', + 'query_configurations' => [ + 'display' => [ + 'source_query' => 'search', ], ], - ]); + ] ); } add_action( 'init', __NAMESPACE__ . '\\register_aic_block' ); diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index e984fddba..6902e96ea 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -48,7 +48,20 @@ public static function register_block( array $user_config = [] ): bool|WP_Error return self::create_error( $block_title, sprintf( 'Block %s has already been registered', $block_name ) ); } - $display_query = self::inflate_query( $user_config[ self::RENDER_QUERY_KEY ]['query'] ); + $display_query = null; + + // Throw an error if the display query isn't set. + if ( ! isset( $user_config['queries']['display'] ) ) { + return self::create_error( $block_title, 'The display query is required' ); + } + + $display_query = self::inflate_query( $user_config['queries']['display'] ); + + // Initialize the queries array with the display query. + $queries = [ + self::DISPLAY_QUERY_KEY => $display_query, + ]; + $input_schema = $display_query->get_input_schema(); $output_schema = $display_query->get_output_schema(); $is_collection = true === ( $output_schema['is_collection'] ?? false ); @@ -70,9 +83,7 @@ public static function register_block( array $user_config = [] ): bool|WP_Error 'name' => $block_name, 'overrides' => $user_config['overrides'] ?? [], 'patterns' => [], - 'queries' => [ - self::DISPLAY_QUERY_KEY => $display_query, - ], + 'queries' => $queries, 'selectors' => [ [ 'image_url' => $display_query->get_image_url(), @@ -85,9 +96,42 @@ public static function register_block( array $user_config = [] ): bool|WP_Error 'title' => $block_title, ]; + // Add any additional queries to the queries array. + foreach ( $user_config['queries'] as $query_key => $query ) { + // The display query is already added to the queries array. + if ( self::DISPLAY_QUERY_KEY === $query_key ) { + continue; + } + + $query = self::inflate_query( $query ); + $queries[ $query_key ] = $query; + + // Check if this query is configured as a source for another query. + foreach ( $user_config['query_configurations'] ?? [] as $target_key => $target_config ) { + if ( $target_config['source_query'] === $query_key ) { + + // ToDo: Add in the input validation check. + array_unshift( + $config['selectors'], + [ + 'image_url' => $query->get_image_url(), + 'inputs' => self::map_input_variables( $query->get_input_schema() ), + 'name' => ucfirst( $query_key ), + 'query_key' => $query_key, + 'type' => 'search', + ] + ); + break; + } + } + } + + // set the queries on the config. + $config['queries'] = $queries; + // Register "selectors" which allow the user to use a query to assist in // selecting data for display by the block. - foreach ( $user_config[ self::SELECTION_QUERIES_KEY ] ?? [] as $selection_query ) { +/* foreach ( $user_config[ self::SELECTION_QUERIES_KEY ] ?? [] as $selection_query ) { $from_query = self::inflate_query( $selection_query['query'] ); $from_query_type = $selection_query['type']; $to_query = $display_query; @@ -124,7 +168,7 @@ public static function register_block( array $user_config = [] ): bool|WP_Error 'type' => $from_query_type, ] ); - } + } */ // Register patterns which can be used with the block. foreach ( $user_config['patterns'] ?? [] as $pattern ) { diff --git a/inc/ExampleApi/ExampleApi.php b/inc/ExampleApi/ExampleApi.php index d885b30ec..8a90f4b54 100644 --- a/inc/ExampleApi/ExampleApi.php +++ b/inc/ExampleApi/ExampleApi.php @@ -114,13 +114,13 @@ public static function register_remote_data_block(): void { register_remote_data_block( [ 'title' => self::$block_title, - 'render_query' => [ - 'query' => $get_record_query, + 'queries' => [ + 'display' => $get_record_query, + 'list' => $get_table_query, ], - 'selection_queries' => [ - [ - 'query' => $get_table_query, - 'type' => 'list', + 'query_configurations' => [ + 'display' => [ + 'source_query' => 'list', ], ], ] ); diff --git a/inc/Validation/ConfigSchemas.php b/inc/Validation/ConfigSchemas.php index 65803c80d..50b0de704 100644 --- a/inc/Validation/ConfigSchemas.php +++ b/inc/Validation/ConfigSchemas.php @@ -86,12 +86,12 @@ private static function generate_remote_data_block_config_schema(): array { ] ) ) ), - 'render_query' => Types::object( [ + 'render_query' => Types::nullable(Types::object( [ 'query' => Types::one_of( Types::instance_of( QueryInterface::class ), Types::serialized_config_for( HttpQueryInterface::class ), ), - ] ), + ] ) ), 'selection_queries' => Types::nullable( Types::list_of( Types::object( [ @@ -107,6 +107,21 @@ private static function generate_remote_data_block_config_schema(): array { ] ) ) ), + 'queries' => Types::nullable( Types::record( + Types::string(), + Types::one_of( + Types::instance_of( QueryInterface::class ), + Types::serialized_config_for( HttpQueryInterface::class ), + ) + ) ), + 'query_configurations' => Types::nullable( + Types::record( + Types::string(), + Types::object( [ + 'source_query' => Types::string(), + ] ) + ) + ), 'overrides' => Types::nullable( Types::list_of( Types::object( [ From 7b90e48d8b37e82ce35e1a72828cdbd242446285 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Thu, 8 May 2025 14:58:54 +1000 Subject: [PATCH 02/74] Add support for multiple display queries --- inc/Editor/BlockManagement/ConfigRegistry.php | 129 +++++++++--------- 1 file changed, 62 insertions(+), 67 deletions(-) diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index 6902e96ea..9d45b09fa 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -48,34 +48,70 @@ public static function register_block( array $user_config = [] ): bool|WP_Error return self::create_error( $block_title, sprintf( 'Block %s has already been registered', $block_name ) ); } - $display_query = null; + $queries = []; + $selectors = []; - // Throw an error if the display query isn't set. - if ( ! isset( $user_config['queries']['display'] ) ) { - return self::create_error( $block_title, 'The display query is required' ); - } - - $display_query = self::inflate_query( $user_config['queries']['display'] ); - - // Initialize the queries array with the display query. - $queries = [ - self::DISPLAY_QUERY_KEY => $display_query, - ]; - - $input_schema = $display_query->get_input_schema(); - $output_schema = $display_query->get_output_schema(); - $is_collection = true === ( $output_schema['is_collection'] ?? false ); + foreach ( $user_config['queries'] as $query_key => $query ) { + $query = self::inflate_query( $query ); + $queries[ $query_key ] = $query; + if ( self::DISPLAY_QUERY_KEY === $query_key ) { + $input_schema = $query->get_input_schema(); + $output_schema = $query->get_output_schema(); + $is_collection = true === ( $output_schema['is_collection'] ?? false ); + $has_required_variables = array_reduce( + array_column( $input_schema, 'required' ), + fn( $carry, $required ) => $carry || ( $required ?? true ), + false + ); + $selectors[] = [ + 'image_url' => $query->get_image_url(), + 'inputs' => self::map_input_variables( $input_schema ), + 'name' => $has_required_variables ? 'Manual input' : ( $is_collection ? 'Load collection' : 'Load item' ), + 'query_key' => self::DISPLAY_QUERY_KEY, + 'type' => $has_required_variables ? 'manual-input' : 'load-without-input', + ]; + } else { + $source_query_found = false; + // Check if this query is configured as a source for another query. + foreach ( $user_config['query_configurations'] ?? [] as $target_key => $target_config ) { + if ( $target_config['source_query'] === $query_key ) { + $source_query_found = true; + // ToDo: Add in the input validation check. + array_unshift( + $selectors, + [ + 'image_url' => $query->get_image_url(), + 'inputs' => self::map_input_variables( $query->get_input_schema() ), + 'name' => ucfirst( $query_key ), + 'query_key' => $query_key, + 'type' => 'search', + ] + ); + break; + } + } - // Check if any variables are required - $has_required_variables = array_reduce( - array_column( $input_schema, 'required' ), - fn( $carry, $required ) => $carry || ( $required ?? true ), - false - ); + // if source_query_found is false, it means we have another display query without a source query. We need to generate a selector for it. + if ( ! $source_query_found ) { + $input_schema = $query->get_input_schema(); + $output_schema = $query->get_output_schema(); + $is_collection = true === ( $output_schema['is_collection'] ?? false ); + $has_required_variables = array_reduce( + array_column( $input_schema, 'required' ), + fn( $carry, $required ) => $carry || ( $required ?? true ), + false + ); + $selectors[] = [ + 'image_url' => $query->get_image_url(), + 'inputs' => self::map_input_variables( $query->get_input_schema() ), + 'name' => ucfirst( $query_key ), + 'query_key' => $query_key, + 'type' => $has_required_variables ? 'manual-input' : 'load-without-input', + ]; + } + } + } - // 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. $config = [ 'description' => '', 'icon' => $user_config['icon'] ?? 'cloud', @@ -84,51 +120,10 @@ public static function register_block( array $user_config = [] ): bool|WP_Error 'overrides' => $user_config['overrides'] ?? [], 'patterns' => [], 'queries' => $queries, - 'selectors' => [ - [ - 'image_url' => $display_query->get_image_url(), - 'inputs' => self::map_input_variables( $input_schema ), - 'name' => $has_required_variables ? 'Manual input' : ( $is_collection ? 'Load collection' : 'Load item' ), - 'query_key' => self::DISPLAY_QUERY_KEY, - 'type' => $has_required_variables ? 'manual-input' : 'load-without-input', - ], - ], + 'selectors' => $selectors, 'title' => $block_title, ]; - // Add any additional queries to the queries array. - foreach ( $user_config['queries'] as $query_key => $query ) { - // The display query is already added to the queries array. - if ( self::DISPLAY_QUERY_KEY === $query_key ) { - continue; - } - - $query = self::inflate_query( $query ); - $queries[ $query_key ] = $query; - - // Check if this query is configured as a source for another query. - foreach ( $user_config['query_configurations'] ?? [] as $target_key => $target_config ) { - if ( $target_config['source_query'] === $query_key ) { - - // ToDo: Add in the input validation check. - array_unshift( - $config['selectors'], - [ - 'image_url' => $query->get_image_url(), - 'inputs' => self::map_input_variables( $query->get_input_schema() ), - 'name' => ucfirst( $query_key ), - 'query_key' => $query_key, - 'type' => 'search', - ] - ); - break; - } - } - } - - // set the queries on the config. - $config['queries'] = $queries; - // Register "selectors" which allow the user to use a query to assist in // selecting data for display by the block. /* foreach ( $user_config[ self::SELECTION_QUERIES_KEY ] ?? [] as $selection_query ) { From 5854bb42d995083bb8b43984aad7fe5e322fe485 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Thu, 8 May 2025 15:38:19 +1000 Subject: [PATCH 03/74] Clean up the code and add in validation framework --- inc/Editor/BlockManagement/ConfigRegistry.php | 77 +++++++------------ 1 file changed, 29 insertions(+), 48 deletions(-) diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index 9d45b09fa..72844075a 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -54,15 +54,17 @@ public static function register_block( array $user_config = [] ): bool|WP_Error foreach ( $user_config['queries'] as $query_key => $query ) { $query = self::inflate_query( $query ); $queries[ $query_key ] = $query; + $input_schema = $query->get_input_schema(); + $output_schema = $query->get_output_schema(); + if ( self::DISPLAY_QUERY_KEY === $query_key ) { - $input_schema = $query->get_input_schema(); - $output_schema = $query->get_output_schema(); $is_collection = true === ( $output_schema['is_collection'] ?? false ); $has_required_variables = array_reduce( array_column( $input_schema, 'required' ), fn( $carry, $required ) => $carry || ( $required ?? true ), false ); + $selectors[] = [ 'image_url' => $query->get_image_url(), 'inputs' => self::map_input_variables( $input_schema ), @@ -76,12 +78,12 @@ public static function register_block( array $user_config = [] ): bool|WP_Error foreach ( $user_config['query_configurations'] ?? [] as $target_key => $target_config ) { if ( $target_config['source_query'] === $query_key ) { $source_query_found = true; - // ToDo: Add in the input validation check. + array_unshift( $selectors, [ 'image_url' => $query->get_image_url(), - 'inputs' => self::map_input_variables( $query->get_input_schema() ), + 'inputs' => self::map_input_variables( $input_schema ), 'name' => ucfirst( $query_key ), 'query_key' => $query_key, 'type' => 'search', @@ -93,17 +95,16 @@ public static function register_block( array $user_config = [] ): bool|WP_Error // if source_query_found is false, it means we have another display query without a source query. We need to generate a selector for it. if ( ! $source_query_found ) { - $input_schema = $query->get_input_schema(); - $output_schema = $query->get_output_schema(); $is_collection = true === ( $output_schema['is_collection'] ?? false ); $has_required_variables = array_reduce( array_column( $input_schema, 'required' ), fn( $carry, $required ) => $carry || ( $required ?? true ), false ); + $selectors[] = [ 'image_url' => $query->get_image_url(), - 'inputs' => self::map_input_variables( $query->get_input_schema() ), + 'inputs' => self::map_input_variables( $input_schema ), 'name' => ucfirst( $query_key ), 'query_key' => $query_key, 'type' => $has_required_variables ? 'manual-input' : 'load-without-input', @@ -124,47 +125,6 @@ public static function register_block( array $user_config = [] ): bool|WP_Error 'title' => $block_title, ]; - // Register "selectors" which allow the user to use a query to assist in - // selecting data for display by the block. -/* foreach ( $user_config[ self::SELECTION_QUERIES_KEY ] ?? [] as $selection_query ) { - $from_query = self::inflate_query( $selection_query['query'] ); - $from_query_type = $selection_query['type']; - $to_query = $display_query; - - $config['queries'][ $from_query::class ] = $from_query; - - $from_input_schema = $from_query->get_input_schema(); - $from_output_schema = $from_query->get_output_schema(); - - foreach ( array_keys( $to_query->get_input_schema() ) as $to ) { - if ( ! isset( $from_output_schema['type'][ $to ] ) ) { - return self::create_error( $block_title, sprintf( 'Cannot map key "%1$s" from %2$s query. The display query for this block requires a "%1$s" key as an input, but it is not present in the output schema for the %2$s query. Try adding a "%1$s" mapping to the output schema for the %2$s query.', esc_html( $to ), $from_query_type ) ); - } - } - - if ( self::SEARCH_QUERY_KEY === $from_query_type ) { - $search_input_count = count( array_filter( $from_input_schema, function ( array $input_var ): bool { - return 'ui:search_input' === $input_var['type']; - } ) ); - - if ( 1 !== $search_input_count ) { - return self::create_error( $block_title, 'A search query must have one input variable with type "ui:search_input"' ); - } - } - - // Add the selector to the configuration. - array_unshift( - $config['selectors'], - [ - 'image_url' => $from_query->get_image_url(), - 'inputs' => self::map_input_variables( $input_schema ), - 'name' => $selection_query['display_name'] ?? ucfirst( $from_query_type ), - 'query_key' => $from_query::class, - 'type' => $from_query_type, - ] - ); - } */ - // Register patterns which can be used with the block. foreach ( $user_config['patterns'] ?? [] as $pattern ) { $parsed_blocks = parse_blocks( $pattern['html'] ); @@ -185,6 +145,27 @@ public static function register_block( array $user_config = [] ): bool|WP_Error return true; } + // ToDo: The source query is the from query, and the target query is the to query when calling this. So, the display query is the to query and the source query for the data is the from query. The from query's key is the from_query_key. + private static function validate_query_mapping( array $to_query_input_schema, array $from_query_output_schema, string $block_title, string $from_query_key ): WP_Error|bool { + foreach ( array_keys( $to_query_input_schema ) as $to ) { + if ( ! isset( $from_query_output_schema['type'][ $to ] ) ) { + return self::create_error( $block_title, sprintf( 'Cannot map key "%1$s" from %2$s query. The display query for this block requires a "%1$s" key as an input, but it is not present in the output schema for the %2$s query. Try adding a "%1$s" mapping to the output schema for the %2$s query.', esc_html( $to ), $from_query_key ) ); + } + } + + if ( self::SEARCH_QUERY_KEY === $from_query_key ) { + $search_input_count = count( array_filter( $to_query_input_schema, function ( array $input_var ): bool { + return 'ui:search_input' === $input_var['type']; + } ) ); + + if ( 1 !== $search_input_count ) { + return self::create_error( $block_title, 'A search query must have one input variable with type "ui:search_input"' ); + } + } + + return true; + } + private static function register_block_pattern( string $block_name, string $pattern_title, string $pattern_content ): string { // Add the block arg to any bindings present in the pattern. $pattern_name = 'remote-data-blocks/' . sanitize_title_with_dashes( $pattern_title, '', 'save' ); From 716415bc21a750f0117b27bf71c9481e01d1c283 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Fri, 9 May 2025 14:25:30 +1000 Subject: [PATCH 04/74] Stop crashes for the old query way, and remove the deprecated buttongroup flow --- .../BlockManagement/BlockRegistration.php | 15 +++++++++++++ .../placeholders/ItemSelectQueryType.tsx | 22 +++++++++++++++---- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/inc/Editor/BlockManagement/BlockRegistration.php b/inc/Editor/BlockManagement/BlockRegistration.php index 7519b4ca9..63a795bbf 100644 --- a/inc/Editor/BlockManagement/BlockRegistration.php +++ b/inc/Editor/BlockManagement/BlockRegistration.php @@ -8,9 +8,14 @@ use RemoteDataBlocks\Telemetry\Telemetry; use RemoteDataBlocks\Editor\BlockPatterns\BlockPatterns; use RemoteDataBlocks\REST\RemoteDataController; +use RemoteDataBlocks\Logging\Logger; +use RemoteDataBlocks\Logging\LoggerInterface; + use function register_block_type; class BlockRegistration { + private static LoggerInterface $logger; + /** * @var array */ @@ -21,6 +26,8 @@ class BlockRegistration { ]; public static function init(): void { + self::$logger = new Logger(); + add_action( 'init', [ __CLASS__, 'register_helper_blocks' ], 10, 0 ); add_action( 'init', [ __CLASS__, 'register_container_blocks' ], 50, 0 ); add_action( 'enqueue_block_editor_assets', [ __CLASS__, 'enqueue_block_assets' ], 10, 0 ); @@ -64,6 +71,14 @@ public static function register_container_blocks(): void { foreach ( ConfigStore::get_block_configurations() as $block_configuration ) { $block_name = $block_configuration['name']; + // This is to prevent the legacy query configuration from causing errors. + // This does make it a breaking change for users who have not updated to the new query configuration. + // ToDo: Add a migration path for users who have not updated to the new query configuration. + if ( count( $block_configuration['queries'] ) === 0 ) { + self::$logger->warning( sprintf( 'Block %s has no queries and will not be registered', $block_name ) ); + continue; + } + [ $config, $script_handle ] = self::register_block_configuration( $block_configuration ); $all_remote_block_configs[ $block_name ] = $config; $scripts_to_localize[] = $script_handle; diff --git a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx index e2d4f158b..3d227f9d3 100644 --- a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx +++ b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx @@ -1,4 +1,8 @@ -import { Button, ButtonGroup } from '@wordpress/components'; +import { + Button, + __experimentalToggleGroupControl as ToggleGroupControl, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; import { DataViewsModal } from '@/blocks/remote-data-container/components/modals/DataViewsModal'; import { InputModal } from '@/blocks/remote-data-container/components/modals/InputModal'; @@ -16,7 +20,12 @@ export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) { } = props; return ( - + { selectors.map( selector => { const title = selector.name; const selectorProps = { @@ -40,7 +49,12 @@ export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) { ); case 'load-without-input': return ( - ); @@ -60,6 +74,6 @@ export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) { return null; } ) } - + ); } From 790215f0579e824843741ed1040bd89a02770825 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Fri, 9 May 2025 15:54:45 +1000 Subject: [PATCH 05/74] Abstract away the query selection to another level and show query names instead --- .../rest-api/art-institute/art-institute.php | 3 + inc/Config/Query/HttpQuery.php | 7 + inc/Config/Query/QueryInterface.php | 1 + inc/Editor/BlockManagement/ConfigRegistry.php | 3 + inc/ExampleApi/ExampleApi.php | 2 + inc/Validation/ConfigSchemas.php | 1 + .../placeholders/ItemSelectQueryType.tsx | 121 +++++++++++------- types/localized-block-data.d.ts | 1 + 8 files changed, 94 insertions(+), 45 deletions(-) diff --git a/example/rest-api/art-institute/art-institute.php b/example/rest-api/art-institute/art-institute.php index 7f6b54d6d..42be25adc 100644 --- a/example/rest-api/art-institute/art-institute.php +++ b/example/rest-api/art-institute/art-institute.php @@ -26,6 +26,7 @@ function register_aic_block(): void { ] ); $get_art_query = HttpQuery::from_array([ + 'query_name' => 'Get Art', 'data_source' => $aic_data_source, 'endpoint' => function ( array $input_variables ) use ( $aic_data_source ): string { $endpoint = $aic_data_source->get_endpoint(); @@ -87,6 +88,7 @@ function register_aic_block(): void { ]); $collection_query = HttpQuery::from_array([ + 'query_name' => 'Get Art Collection', 'data_source' => $aic_data_source, 'endpoint' => function ( array $input_variables ) use ( $aic_data_source ): string { $endpoint = $aic_data_source->get_endpoint(); @@ -138,6 +140,7 @@ function register_aic_block(): void { ]); $search_art_query = HttpQuery::from_array([ + 'query_name' => 'Search Art Collection', 'data_source' => $aic_data_source, 'endpoint' => function ( array $input_variables ) use ( $aic_data_source ): string { $endpoint = $aic_data_source->get_endpoint(); diff --git a/inc/Config/Query/HttpQuery.php b/inc/Config/Query/HttpQuery.php index c9c13870f..7c8bb2ae1 100644 --- a/inc/Config/Query/HttpQuery.php +++ b/inc/Config/Query/HttpQuery.php @@ -95,6 +95,13 @@ public function get_input_schema(): array { return $this->config['input_schema'] ?? []; } + /** + * Get the query name for this query. + */ + public function get_query_name(): ?string { + return $this->config['query_name'] ?? null; + } + /** * Get the output schema for this query. */ diff --git a/inc/Config/Query/QueryInterface.php b/inc/Config/Query/QueryInterface.php index b75c82e51..08bd5265f 100644 --- a/inc/Config/Query/QueryInterface.php +++ b/inc/Config/Query/QueryInterface.php @@ -14,6 +14,7 @@ interface QueryInterface extends ArraySerializableInterface { public function execute( array $input_variables ): array|WP_Error; public function execute_batch( array $array_of_input_variables ): array|WP_Error; public function get_data_source(): DataSourceInterface; + public function get_query_name(): ?string; public function get_image_url(): ?string; public function get_input_schema(): array; public function get_output_schema(): array; diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index 72844075a..f1838f36a 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -66,6 +66,7 @@ public static function register_block( array $user_config = [] ): bool|WP_Error ); $selectors[] = [ + 'display_name' => $query->get_query_name(), 'image_url' => $query->get_image_url(), 'inputs' => self::map_input_variables( $input_schema ), 'name' => $has_required_variables ? 'Manual input' : ( $is_collection ? 'Load collection' : 'Load item' ), @@ -82,6 +83,7 @@ public static function register_block( array $user_config = [] ): bool|WP_Error array_unshift( $selectors, [ + 'display_name' => $query->get_query_name(), 'image_url' => $query->get_image_url(), 'inputs' => self::map_input_variables( $input_schema ), 'name' => ucfirst( $query_key ), @@ -103,6 +105,7 @@ public static function register_block( array $user_config = [] ): bool|WP_Error ); $selectors[] = [ + 'display_name' => $query->get_query_name(), 'image_url' => $query->get_image_url(), 'inputs' => self::map_input_variables( $input_schema ), 'name' => ucfirst( $query_key ), diff --git a/inc/ExampleApi/ExampleApi.php b/inc/ExampleApi/ExampleApi.php index 8a90f4b54..3766d3f28 100644 --- a/inc/ExampleApi/ExampleApi.php +++ b/inc/ExampleApi/ExampleApi.php @@ -46,6 +46,7 @@ public static function register_remote_data_block(): void { ] ); $get_record_query = HttpQuery::from_array( [ + 'query_name' => 'Get Record', 'data_source' => $data_source, 'input_schema' => [ 'record_id' => [ @@ -81,6 +82,7 @@ public static function register_remote_data_block(): void { ] ); $get_table_query = HttpQuery::from_array( [ + 'query_name' => 'Get Table', 'data_source' => $data_source, 'input_schema' => [], 'output_schema' => [ diff --git a/inc/Validation/ConfigSchemas.php b/inc/Validation/ConfigSchemas.php index 50b0de704..74cb66740 100644 --- a/inc/Validation/ConfigSchemas.php +++ b/inc/Validation/ConfigSchemas.php @@ -171,6 +171,7 @@ private static function generate_generic_http_data_source_config_schema(): array private static function generate_http_query_config_schema(): array { return Types::object( [ + 'query_name' => Types::nullable( Types::string() ), 'cache_ttl' => Types::nullable( Types::one_of( Types::callable(), Types::integer(), Types::null() ) ), 'data_source' => Types::one_of( Types::instance_of( HttpDataSourceInterface::class ), diff --git a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx index 3d227f9d3..7df0d385f 100644 --- a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx +++ b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx @@ -3,6 +3,7 @@ import { __experimentalToggleGroupControl as ToggleGroupControl, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { useState } from 'react'; import { DataViewsModal } from '@/blocks/remote-data-container/components/modals/DataViewsModal'; import { InputModal } from '@/blocks/remote-data-container/components/modals/InputModal'; @@ -19,60 +20,90 @@ export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) { onSelect, } = props; + const [ activeModal, setActiveModal ] = useState< { + type: 'search' | 'list' | 'manual-input'; + selector: ( typeof selectors )[ 0 ]; + } | null >( null ); + + const [ activePopover, setActivePopover ] = useState< { + selector: ( typeof selectors )[ 0 ]; + } | null >( null ); + + const handleSelectorClick = ( selector: ( typeof selectors )[ 0 ] ) => { + switch ( selector.type ) { + case 'search': + case 'list': + setActiveModal( { type: selector.type, selector } ); + break; + case 'load-without-input': + onSelect( [ {} ] ); + break; + case 'manual-input': + if ( selector.inputs.length === 1 && selector.inputs[ 0 ] ) { + setActivePopover( { selector } ); + } else { + setActiveModal( { type: 'manual-input', selector } ); + } + break; + } + }; + + if ( activeModal ) { + return activeModal.type === 'manual-input' ? ( + + ) : ( + + ); + } + + if ( activePopover && activePopover.selector.inputs[ 0 ] ) { + return ( + + ); + } + return ( { selectors.map( selector => { const title = selector.name; - const selectorProps = { - blockName, - headerImage: selector.image_url, - inputVariables: selector.inputs, - onSelect, - queryKey: selector.query_key, - title, - }; - - switch ( selector.type ) { - case 'search': - case 'list': - return ( - - ); - case 'load-without-input': - return ( - - ); - case 'manual-input': - if ( selector.inputs.length === 1 && selector.inputs[ 0 ] ) { - return ( - - ); - } - return ; - } - return null; + return ( + + ); } ) } ); diff --git a/types/localized-block-data.d.ts b/types/localized-block-data.d.ts index 2f27209c3..5bcf984c2 100644 --- a/types/localized-block-data.d.ts +++ b/types/localized-block-data.d.ts @@ -38,6 +38,7 @@ interface BlockConfig { inputs: InputVariable[]; name: string; query_key: string; + display_name?: string; type: string; }[]; settings: { From 9385d73db83a52b980176445f076af4f1c897284 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Mon, 12 May 2025 14:45:59 +1000 Subject: [PATCH 06/74] Simplify the query configuration --- example/rest-api/art-institute/art-institute.php | 4 +--- inc/Editor/BlockManagement/ConfigRegistry.php | 2 +- inc/ExampleApi/ExampleApi.php | 4 +--- inc/Validation/ConfigSchemas.php | 4 +--- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/example/rest-api/art-institute/art-institute.php b/example/rest-api/art-institute/art-institute.php index 42be25adc..363920a30 100644 --- a/example/rest-api/art-institute/art-institute.php +++ b/example/rest-api/art-institute/art-institute.php @@ -217,9 +217,7 @@ function register_aic_block(): void { 'search' => $search_art_query, ], 'query_configurations' => [ - 'display' => [ - 'source_query' => 'search', - ], + 'display' => 'search', ], ] ); } diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index f1838f36a..f87106a4e 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -77,7 +77,7 @@ public static function register_block( array $user_config = [] ): bool|WP_Error $source_query_found = false; // Check if this query is configured as a source for another query. foreach ( $user_config['query_configurations'] ?? [] as $target_key => $target_config ) { - if ( $target_config['source_query'] === $query_key ) { + if ( $target_config === $query_key ) { $source_query_found = true; array_unshift( diff --git a/inc/ExampleApi/ExampleApi.php b/inc/ExampleApi/ExampleApi.php index 3766d3f28..4e7a86c2b 100644 --- a/inc/ExampleApi/ExampleApi.php +++ b/inc/ExampleApi/ExampleApi.php @@ -121,9 +121,7 @@ public static function register_remote_data_block(): void { 'list' => $get_table_query, ], 'query_configurations' => [ - 'display' => [ - 'source_query' => 'list', - ], + 'display' => 'list', ], ] ); } diff --git a/inc/Validation/ConfigSchemas.php b/inc/Validation/ConfigSchemas.php index 74cb66740..d486be893 100644 --- a/inc/Validation/ConfigSchemas.php +++ b/inc/Validation/ConfigSchemas.php @@ -117,9 +117,7 @@ private static function generate_remote_data_block_config_schema(): array { 'query_configurations' => Types::nullable( Types::record( Types::string(), - Types::object( [ - 'source_query' => Types::string(), - ] ) + Types::string(), ) ), 'overrides' => Types::nullable( From 22349feb481434fd28d8ebfb9e76e4ae62539b6d Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Mon, 12 May 2025 15:06:02 +1000 Subject: [PATCH 07/74] Simplify the query_configuration schema --- .../rest-api/art-institute/art-institute.php | 3 - inc/Config/Query/HttpQuery.php | 7 -- inc/Config/Query/QueryInterface.php | 1 - inc/Editor/BlockManagement/ConfigRegistry.php | 68 +++++++------------ inc/ExampleApi/ExampleApi.php | 2 - inc/Validation/ConfigSchemas.php | 1 - 6 files changed, 24 insertions(+), 58 deletions(-) diff --git a/example/rest-api/art-institute/art-institute.php b/example/rest-api/art-institute/art-institute.php index 363920a30..fbfd24478 100644 --- a/example/rest-api/art-institute/art-institute.php +++ b/example/rest-api/art-institute/art-institute.php @@ -26,7 +26,6 @@ function register_aic_block(): void { ] ); $get_art_query = HttpQuery::from_array([ - 'query_name' => 'Get Art', 'data_source' => $aic_data_source, 'endpoint' => function ( array $input_variables ) use ( $aic_data_source ): string { $endpoint = $aic_data_source->get_endpoint(); @@ -88,7 +87,6 @@ function register_aic_block(): void { ]); $collection_query = HttpQuery::from_array([ - 'query_name' => 'Get Art Collection', 'data_source' => $aic_data_source, 'endpoint' => function ( array $input_variables ) use ( $aic_data_source ): string { $endpoint = $aic_data_source->get_endpoint(); @@ -140,7 +138,6 @@ function register_aic_block(): void { ]); $search_art_query = HttpQuery::from_array([ - 'query_name' => 'Search Art Collection', 'data_source' => $aic_data_source, 'endpoint' => function ( array $input_variables ) use ( $aic_data_source ): string { $endpoint = $aic_data_source->get_endpoint(); diff --git a/inc/Config/Query/HttpQuery.php b/inc/Config/Query/HttpQuery.php index 7c8bb2ae1..c9c13870f 100644 --- a/inc/Config/Query/HttpQuery.php +++ b/inc/Config/Query/HttpQuery.php @@ -95,13 +95,6 @@ public function get_input_schema(): array { return $this->config['input_schema'] ?? []; } - /** - * Get the query name for this query. - */ - public function get_query_name(): ?string { - return $this->config['query_name'] ?? null; - } - /** * Get the output schema for this query. */ diff --git a/inc/Config/Query/QueryInterface.php b/inc/Config/Query/QueryInterface.php index 08bd5265f..b75c82e51 100644 --- a/inc/Config/Query/QueryInterface.php +++ b/inc/Config/Query/QueryInterface.php @@ -14,7 +14,6 @@ interface QueryInterface extends ArraySerializableInterface { public function execute( array $input_variables ): array|WP_Error; public function execute_batch( array $array_of_input_variables ): array|WP_Error; public function get_data_source(): DataSourceInterface; - public function get_query_name(): ?string; public function get_image_url(): ?string; public function get_input_schema(): array; public function get_output_schema(): array; diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index f87106a4e..788c861c4 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -51,13 +51,28 @@ public static function register_block( array $user_config = [] ): bool|WP_Error $queries = []; $selectors = []; + $query_configurations = array_values( $user_config['query_configurations'] ?? [] ); + foreach ( $user_config['queries'] as $query_key => $query ) { $query = self::inflate_query( $query ); $queries[ $query_key ] = $query; $input_schema = $query->get_input_schema(); $output_schema = $query->get_output_schema(); - if ( self::DISPLAY_QUERY_KEY === $query_key ) { + // check if the query_key is present in the user_config['query_configurations'] array + if ( in_array( $query_key, $query_configurations, true ) ) { + array_unshift( + $selectors, + [ + 'display_name' => self::get_query_name_from_key( $query_key ), + 'image_url' => $query->get_image_url(), + 'inputs' => self::map_input_variables( $input_schema ), + 'name' => ucfirst( $query_key ), + 'query_key' => $query_key, + 'type' => 'search', + ] + ); + } else { $is_collection = true === ( $output_schema['is_collection'] ?? false ); $has_required_variables = array_reduce( array_column( $input_schema, 'required' ), @@ -66,53 +81,13 @@ public static function register_block( array $user_config = [] ): bool|WP_Error ); $selectors[] = [ - 'display_name' => $query->get_query_name(), + 'display_name' => self::get_query_name_from_key( $query_key ), 'image_url' => $query->get_image_url(), 'inputs' => self::map_input_variables( $input_schema ), - 'name' => $has_required_variables ? 'Manual input' : ( $is_collection ? 'Load collection' : 'Load item' ), - 'query_key' => self::DISPLAY_QUERY_KEY, + 'name' => self::DISPLAY_QUERY_KEY === $query_key ? ( $has_required_variables ? 'Manual input' : ( $is_collection ? 'Load collection' : 'Load item' ) ) : ucfirst( $query_key ), + 'query_key' => $query_key, 'type' => $has_required_variables ? 'manual-input' : 'load-without-input', ]; - } else { - $source_query_found = false; - // Check if this query is configured as a source for another query. - foreach ( $user_config['query_configurations'] ?? [] as $target_key => $target_config ) { - if ( $target_config === $query_key ) { - $source_query_found = true; - - array_unshift( - $selectors, - [ - 'display_name' => $query->get_query_name(), - 'image_url' => $query->get_image_url(), - 'inputs' => self::map_input_variables( $input_schema ), - 'name' => ucfirst( $query_key ), - 'query_key' => $query_key, - 'type' => 'search', - ] - ); - break; - } - } - - // if source_query_found is false, it means we have another display query without a source query. We need to generate a selector for it. - if ( ! $source_query_found ) { - $is_collection = true === ( $output_schema['is_collection'] ?? false ); - $has_required_variables = array_reduce( - array_column( $input_schema, 'required' ), - fn( $carry, $required ) => $carry || ( $required ?? true ), - false - ); - - $selectors[] = [ - 'display_name' => $query->get_query_name(), - 'image_url' => $query->get_image_url(), - 'inputs' => self::map_input_variables( $input_schema ), - 'name' => ucfirst( $query_key ), - 'query_key' => $query_key, - 'type' => $has_required_variables ? 'manual-input' : 'load-without-input', - ]; - } } } @@ -218,4 +193,9 @@ function ( string $slug, array $input_var ): array { array_values( $input_schema ) ); } + + private static function get_query_name_from_key( string $key ): string { + // Replace any non-alphanumeric characters with spaces and convert to title case + return ucwords( preg_replace( '/[^a-zA-Z0-9]/', ' ', $key ) ); + } } diff --git a/inc/ExampleApi/ExampleApi.php b/inc/ExampleApi/ExampleApi.php index 4e7a86c2b..6a10f757c 100644 --- a/inc/ExampleApi/ExampleApi.php +++ b/inc/ExampleApi/ExampleApi.php @@ -46,7 +46,6 @@ public static function register_remote_data_block(): void { ] ); $get_record_query = HttpQuery::from_array( [ - 'query_name' => 'Get Record', 'data_source' => $data_source, 'input_schema' => [ 'record_id' => [ @@ -82,7 +81,6 @@ public static function register_remote_data_block(): void { ] ); $get_table_query = HttpQuery::from_array( [ - 'query_name' => 'Get Table', 'data_source' => $data_source, 'input_schema' => [], 'output_schema' => [ diff --git a/inc/Validation/ConfigSchemas.php b/inc/Validation/ConfigSchemas.php index d486be893..2ce440a53 100644 --- a/inc/Validation/ConfigSchemas.php +++ b/inc/Validation/ConfigSchemas.php @@ -169,7 +169,6 @@ private static function generate_generic_http_data_source_config_schema(): array private static function generate_http_query_config_schema(): array { return Types::object( [ - 'query_name' => Types::nullable( Types::string() ), 'cache_ttl' => Types::nullable( Types::one_of( Types::callable(), Types::integer(), Types::null() ) ), 'data_source' => Types::one_of( Types::instance_of( HttpDataSourceInterface::class ), From f8363d79d71349556402cd38a4c59649ae67e5fc Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Mon, 12 May 2025 15:40:38 +1000 Subject: [PATCH 08/74] Pop in more configuration at the query level --- .../rest-api/art-institute/art-institute.php | 13 ++++++----- inc/Config/Query/HttpQuery.php | 14 ++++++++++++ inc/Config/Query/QueryInterface.php | 2 ++ inc/Editor/BlockManagement/ConfigRegistry.php | 22 +++++++++++++------ inc/ExampleApi/ExampleApi.php | 10 ++++----- inc/Validation/ConfigSchemas.php | 6 +++++ 6 files changed, 49 insertions(+), 18 deletions(-) diff --git a/example/rest-api/art-institute/art-institute.php b/example/rest-api/art-institute/art-institute.php index fbfd24478..969191b29 100644 --- a/example/rest-api/art-institute/art-institute.php +++ b/example/rest-api/art-institute/art-institute.php @@ -84,6 +84,8 @@ function register_aic_block(): void { ], ], ], + 'required_query' => 'search_art', + 'type' => 'display', ]); $collection_query = HttpQuery::from_array([ @@ -135,6 +137,7 @@ function register_aic_block(): void { ], ], ], + 'type' => 'list', ]); $search_art_query = HttpQuery::from_array([ @@ -202,6 +205,7 @@ function register_aic_block(): void { 'type' => 'integer', ], ], + 'type' => 'search', ]); register_remote_data_block([ @@ -209,12 +213,9 @@ function register_aic_block(): void { 'icon' => 'art', 'instructions' => 'This block displays a set amount of artworks based on the provided limit. This could be 1, or many.', 'queries' => [ - 'display' => $get_art_query, - 'collection' => $collection_query, - 'search' => $search_art_query, - ], - 'query_configurations' => [ - 'display' => 'search', + 'get_art' => $get_art_query, + 'get_art_collection' => $collection_query, + 'search_art' => $search_art_query, ], ] ); } diff --git a/inc/Config/Query/HttpQuery.php b/inc/Config/Query/HttpQuery.php index c9c13870f..f999d35a1 100644 --- a/inc/Config/Query/HttpQuery.php +++ b/inc/Config/Query/HttpQuery.php @@ -62,6 +62,20 @@ public function get_cache_ttl( array $input_variables ): null|int { return null; } + /** + * Get the required query for this query. + */ + public function get_required_query(): ?string { + return $this->config['required_query'] ?? null; + } + + /** + * Get the type of this query. + */ + public function get_type(): string { + return $this->config['type']; + } + /** * Get the data source associated with this query. */ diff --git a/inc/Config/Query/QueryInterface.php b/inc/Config/Query/QueryInterface.php index b75c82e51..77df3ffc4 100644 --- a/inc/Config/Query/QueryInterface.php +++ b/inc/Config/Query/QueryInterface.php @@ -15,6 +15,8 @@ public function execute( array $input_variables ): array|WP_Error; public function execute_batch( array $array_of_input_variables ): array|WP_Error; public function get_data_source(): DataSourceInterface; public function get_image_url(): ?string; + public function get_required_query(): ?string; + public function get_type(): string; public function get_input_schema(): array; public function get_output_schema(): array; public function get_pagination_schema(): ?array; diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index 788c861c4..655632776 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -50,17 +50,25 @@ public static function register_block( array $user_config = [] ): bool|WP_Error $queries = []; $selectors = []; + $required_queries = []; - $query_configurations = array_values( $user_config['query_configurations'] ?? [] ); + // go over the queries, inflate each one, get the required query, skip if it's not present and then make a list out of it. + foreach ( $user_config['queries'] as $query_key => $query ) { + $query = self::inflate_query( $query ); + + // ToDo: Add a validation step to check if the required query is present in the user_config['queries'] array. + if ( $query->get_required_query() && ! empty( $query->get_required_query() ) ) { + $required_queries[] = $query->get_required_query(); + } + } foreach ( $user_config['queries'] as $query_key => $query ) { $query = self::inflate_query( $query ); - $queries[ $query_key ] = $query; + $queries[ self::DISPLAY_QUERY_KEY === $query->get_type() ? self::DISPLAY_QUERY_KEY : $query_key ] = $query; $input_schema = $query->get_input_schema(); $output_schema = $query->get_output_schema(); - // check if the query_key is present in the user_config['query_configurations'] array - if ( in_array( $query_key, $query_configurations, true ) ) { + if ( in_array( $query_key, $required_queries, true ) ) { array_unshift( $selectors, [ @@ -69,7 +77,7 @@ public static function register_block( array $user_config = [] ): bool|WP_Error 'inputs' => self::map_input_variables( $input_schema ), 'name' => ucfirst( $query_key ), 'query_key' => $query_key, - 'type' => 'search', + 'type' => $query->get_type(), ] ); } else { @@ -84,8 +92,8 @@ public static function register_block( array $user_config = [] ): bool|WP_Error 'display_name' => self::get_query_name_from_key( $query_key ), 'image_url' => $query->get_image_url(), 'inputs' => self::map_input_variables( $input_schema ), - 'name' => self::DISPLAY_QUERY_KEY === $query_key ? ( $has_required_variables ? 'Manual input' : ( $is_collection ? 'Load collection' : 'Load item' ) ) : ucfirst( $query_key ), - 'query_key' => $query_key, + 'name' => self::DISPLAY_QUERY_KEY === $query->get_type() ? ( $has_required_variables ? 'Manual input' : ( $is_collection ? 'Load collection' : 'Load item' ) ) : ucfirst( $query_key ), + 'query_key' => self::DISPLAY_QUERY_KEY === $query->get_type() ? self::DISPLAY_QUERY_KEY : $query_key, 'type' => $has_required_variables ? 'manual-input' : 'load-without-input', ]; } diff --git a/inc/ExampleApi/ExampleApi.php b/inc/ExampleApi/ExampleApi.php index 6a10f757c..42be87ae3 100644 --- a/inc/ExampleApi/ExampleApi.php +++ b/inc/ExampleApi/ExampleApi.php @@ -77,6 +77,8 @@ public static function register_remote_data_block(): void { ], ], ], + 'required_query' => 'get_table', + 'type' => 'display', 'query_runner' => new ExampleApiQueryRunner(), ] ); @@ -109,17 +111,15 @@ public static function register_remote_data_block(): void { ], ], ], + 'type' => 'list', 'query_runner' => new ExampleApiQueryRunner(), ] ); register_remote_data_block( [ 'title' => self::$block_title, 'queries' => [ - 'display' => $get_record_query, - 'list' => $get_table_query, - ], - 'query_configurations' => [ - 'display' => 'list', + 'get_record' => $get_record_query, + 'get_table' => $get_table_query, ], ] ); } diff --git a/inc/Validation/ConfigSchemas.php b/inc/Validation/ConfigSchemas.php index 2ce440a53..f0bb4275b 100644 --- a/inc/Validation/ConfigSchemas.php +++ b/inc/Validation/ConfigSchemas.php @@ -169,6 +169,12 @@ private static function generate_generic_http_data_source_config_schema(): array private static function generate_http_query_config_schema(): array { return Types::object( [ + 'required_query' => Types::nullable( Types::string() ), + 'type' => Types::enum( + ConfigRegistry::LIST_QUERY_KEY, + ConfigRegistry::SEARCH_QUERY_KEY, + ConfigRegistry::DISPLAY_QUERY_KEY, + ), 'cache_ttl' => Types::nullable( Types::one_of( Types::callable(), Types::integer(), Types::null() ) ), 'data_source' => Types::one_of( Types::instance_of( HttpDataSourceInterface::class ), From bc7208b01c796637c826025c39493250c242e93e Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Tue, 13 May 2025 12:20:40 +1000 Subject: [PATCH 09/74] Attempting to use the type instead of the query key for display queries --- inc/Editor/BlockManagement/BlockRegistration.php | 6 ++++-- inc/Editor/BlockManagement/ConfigRegistry.php | 14 ++++++++++++-- inc/Editor/BlockManagement/ConfigStore.php | 5 ++--- inc/Editor/DataBinding/BlockBindings.php | 4 ++-- src/blocks/remote-data-container/edit.tsx | 2 +- 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/inc/Editor/BlockManagement/BlockRegistration.php b/inc/Editor/BlockManagement/BlockRegistration.php index 63a795bbf..4699fa2e8 100644 --- a/inc/Editor/BlockManagement/BlockRegistration.php +++ b/inc/Editor/BlockManagement/BlockRegistration.php @@ -99,7 +99,9 @@ public static function register_block_configuration( array $config ): array { // Set available bindings from the display query output mappings. $available_bindings = []; - $output_schema = $config['queries'][ ConfigRegistry::DISPLAY_QUERY_KEY ]->get_output_schema(); + // This shouldn't be null, as we'd have already validated by this point. + $display_query = ConfigRegistry::get_display_query( $config['queries'] ); + $output_schema = $display_query->get_output_schema(); foreach ( $output_schema['type'] ?? [] as $key => $mapping ) { $available_bindings[ $key ] = [ 'name' => $mapping['name'], @@ -133,7 +135,7 @@ public static function register_block_configuration( array $config ): array { $script_handle = $block_type->editor_script_handles[0] ?? ''; // Register a default pattern that simply displays the available data. - $default_pattern_name = BlockPatterns::register_default_block_pattern( $block_name, $config['title'], $config['queries'][ ConfigRegistry::DISPLAY_QUERY_KEY ] ); + $default_pattern_name = BlockPatterns::register_default_block_pattern( $block_name, $config['title'], $display_query ); $block_config['patterns']['default'] = $default_pattern_name; return [ $block_config, $script_handle ]; diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index 655632776..d63744a4d 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -64,7 +64,7 @@ public static function register_block( array $user_config = [] ): bool|WP_Error foreach ( $user_config['queries'] as $query_key => $query ) { $query = self::inflate_query( $query ); - $queries[ self::DISPLAY_QUERY_KEY === $query->get_type() ? self::DISPLAY_QUERY_KEY : $query_key ] = $query; + $queries[ $query_key ] = $query; $input_schema = $query->get_input_schema(); $output_schema = $query->get_output_schema(); @@ -93,7 +93,7 @@ public static function register_block( array $user_config = [] ): bool|WP_Error 'image_url' => $query->get_image_url(), 'inputs' => self::map_input_variables( $input_schema ), 'name' => self::DISPLAY_QUERY_KEY === $query->get_type() ? ( $has_required_variables ? 'Manual input' : ( $is_collection ? 'Load collection' : 'Load item' ) ) : ucfirst( $query_key ), - 'query_key' => self::DISPLAY_QUERY_KEY === $query->get_type() ? self::DISPLAY_QUERY_KEY : $query_key, + 'query_key' => $query_key, 'type' => $has_required_variables ? 'manual-input' : 'load-without-input', ]; } @@ -206,4 +206,14 @@ private static function get_query_name_from_key( string $key ): string { // Replace any non-alphanumeric characters with spaces and convert to title case return ucwords( preg_replace( '/[^a-zA-Z0-9]/', ' ', $key ) ); } + + public static function get_display_query( array $queries ): ?QueryInterface { + foreach ( $queries as $query ) { + if ( $query instanceof QueryInterface && $query->get_type() === self::DISPLAY_QUERY_KEY ) { + return $query; + } + } + + return null; + } } diff --git a/inc/Editor/BlockManagement/ConfigStore.php b/inc/Editor/BlockManagement/ConfigStore.php index 6ed252ce0..e30a1f762 100644 --- a/inc/Editor/BlockManagement/ConfigStore.php +++ b/inc/Editor/BlockManagement/ConfigStore.php @@ -4,7 +4,6 @@ defined( 'ABSPATH' ) || exit(); -use RemoteDataBlocks\Config\Query\QueryInterface; use RemoteDataBlocks\Integrations\GenericHttp\GenericHttpDataSource; use RemoteDataBlocks\Logging\Logger; use RemoteDataBlocks\Logging\LoggerInterface; @@ -78,8 +77,8 @@ public static function get_data_source_type( string $block_name ): ?string { return null; } - $query = $config['queries'][ ConfigRegistry::DISPLAY_QUERY_KEY ] ?? null; - if ( ! ( $query instanceof QueryInterface ) ) { + $query = ConfigRegistry::get_display_query( $config['queries'] ); + if ( ! $query ) { return null; } diff --git a/inc/Editor/DataBinding/BlockBindings.php b/inc/Editor/DataBinding/BlockBindings.php index 65986531f..9075f951f 100644 --- a/inc/Editor/DataBinding/BlockBindings.php +++ b/inc/Editor/DataBinding/BlockBindings.php @@ -5,7 +5,6 @@ defined( 'ABSPATH' ) || exit(); use RemoteDataBlocks\Config\BlockAttribute\RemoteDataBlockAttribute; -use RemoteDataBlocks\Editor\BlockManagement\ConfigRegistry; use RemoteDataBlocks\Editor\BlockManagement\ConfigStore; use RemoteDataBlocks\Logging\Logger; use RemoteDataBlocks\Logging\LoggerInterface; @@ -140,7 +139,8 @@ private static function execute_queries( array $block_context, array $source_arg $remote_data = $remote_data->to_array(); $block_name = $source_args['block'] ?? $remote_data['blockName']; $enabled_overrides = $source_args['enabledOverrides'] ?? $remote_data['enabledOverrides']; - $query_key = $source_args['queryKey'] ?? $remote_data['queryKey'] ?? ConfigRegistry::DISPLAY_QUERY_KEY; + // ToDo: Re-think the default of the display key here as that's not going to be the case. + $query_key = $source_args['queryKey'] ?? $remote_data['queryKey']; // Extract the input variables. Allow the binding source args to override. $array_of_input_variables = $source_args['queryInputs'] ?? $remote_data['queryInputs']; diff --git a/src/blocks/remote-data-container/edit.tsx b/src/blocks/remote-data-container/edit.tsx index 3383f8460..f50dcd8ab 100644 --- a/src/blocks/remote-data-container/edit.tsx +++ b/src/blocks/remote-data-container/edit.tsx @@ -3,7 +3,6 @@ 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'; @@ -18,6 +17,7 @@ import { useRemoteData } from '@/blocks/remote-data-container/hooks/useRemoteDat import { hasRemoteDataChanged } from '@/utils/block-binding'; import { getBlockConfig } from '@/utils/localized-block-data'; import { migrateRemoteData } from '@/utils/remote-data'; +import { QueryInputsPanel } from './components/panels/QueryInputsPanel'; import './editor.scss'; export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ) { From 14ce52a162734ba96cc13f29ef3a57cfe04c4098 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Tue, 13 May 2025 15:59:29 +1000 Subject: [PATCH 10/74] Bug: look at the console log and you'll see the wrong query is always executed --- inc/Editor/BlockManagement/ConfigRegistry.php | 1 + .../components/modals/InputModal.tsx | 3 +- .../placeholders/ItemSelectQueryType.tsx | 102 ++++++++---------- .../components/popovers/InputPopover.tsx | 5 +- src/blocks/remote-data-container/edit.tsx | 13 +-- .../hooks/useRemoteData.ts | 3 + 6 files changed, 60 insertions(+), 67 deletions(-) diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index d63744a4d..49b0c4824 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -92,6 +92,7 @@ public static function register_block( array $user_config = [] ): bool|WP_Error 'display_name' => self::get_query_name_from_key( $query_key ), 'image_url' => $query->get_image_url(), 'inputs' => self::map_input_variables( $input_schema ), + // ToDo: Could this be removed so we don't need to assume special logic for the display query? 'name' => self::DISPLAY_QUERY_KEY === $query->get_type() ? ( $has_required_variables ? 'Manual input' : ( $is_collection ? 'Load collection' : 'Load item' ) ) : ucfirst( $query_key ), 'query_key' => $query_key, 'type' => $has_required_variables ? 'manual-input' : 'load-without-input', diff --git a/src/blocks/remote-data-container/components/modals/InputModal.tsx b/src/blocks/remote-data-container/components/modals/InputModal.tsx index 8f3744a17..87a0d911d 100644 --- a/src/blocks/remote-data-container/components/modals/InputModal.tsx +++ b/src/blocks/remote-data-container/components/modals/InputModal.tsx @@ -1,8 +1,8 @@ import { Button, - TextControl, __experimentalHStack as HStack, __experimentalSpacer as Spacer, + TextControl, } from '@wordpress/components'; import { useState } from '@wordpress/element'; @@ -17,6 +17,7 @@ interface InputModalProps { headerImage?: string; inputs: InputVariable[]; onSelect: ( data: RemoteDataQueryInput[] ) => void; + queryKey?: string; title: string; } diff --git a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx index 7df0d385f..0d10d286c 100644 --- a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx +++ b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx @@ -20,68 +20,56 @@ export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) { onSelect, } = props; - const [ activeModal, setActiveModal ] = useState< { - type: 'search' | 'list' | 'manual-input'; - selector: ( typeof selectors )[ 0 ]; - } | null >( null ); - - const [ activePopover, setActivePopover ] = useState< { - selector: ( typeof selectors )[ 0 ]; - } | null >( null ); + const [ activeSelector, setActiveSelector ] = useState< ( typeof selectors )[ 0 ] | null >( null ); const handleSelectorClick = ( selector: ( typeof selectors )[ 0 ] ) => { - switch ( selector.type ) { + setActiveSelector( selector ); + }; + + if ( activeSelector ) { + const selectorProps = { + blockName, + headerImage: activeSelector.image_url, + inputVariables: activeSelector.inputs, + onSelect, + queryKey: activeSelector.query_key, + title: activeSelector.name, + }; + + switch ( activeSelector.type ) { case 'search': case 'list': - setActiveModal( { type: selector.type, selector } ); - break; + return ( + + ); case 'load-without-input': onSelect( [ {} ] ); - break; + return null; case 'manual-input': - if ( selector.inputs.length === 1 && selector.inputs[ 0 ] ) { - setActivePopover( { selector } ); - } else { - setActiveModal( { type: 'manual-input', selector } ); + if ( activeSelector.inputs.length === 1 && activeSelector.inputs[ 0 ] ) { + return ( + + ); } - break; + return ( + + ); + default: + return null; } - }; - - if ( activeModal ) { - return activeModal.type === 'manual-input' ? ( - - ) : ( - - ); - } - - if ( activePopover && activePopover.selector.inputs[ 0 ] ) { - return ( - - ); } return ( @@ -92,16 +80,14 @@ export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) { __next40pxDefaultSize > { selectors.map( selector => { - const title = selector.name; - return ( ); } ) } diff --git a/src/blocks/remote-data-container/components/popovers/InputPopover.tsx b/src/blocks/remote-data-container/components/popovers/InputPopover.tsx index 2ae411c31..cd8eadc72 100644 --- a/src/blocks/remote-data-container/components/popovers/InputPopover.tsx +++ b/src/blocks/remote-data-container/components/popovers/InputPopover.tsx @@ -1,8 +1,8 @@ import { Button, - Popover, - __experimentalInputControl as InputControl, ExternalLink, + __experimentalInputControl as InputControl, + Popover, } from '@wordpress/components'; import { useState } from '@wordpress/element'; import { keyboardReturn } from '@wordpress/icons'; @@ -20,6 +20,7 @@ interface InputPopoverProps { input: InputVariable; onSelect: ( data: RemoteDataQueryInput[] ) => void; title: string; + queryKey: string; } export function InputPopover( props: InputPopoverProps ) { diff --git a/src/blocks/remote-data-container/edit.tsx b/src/blocks/remote-data-container/edit.tsx index f50dcd8ab..ccb6fa4d4 100644 --- a/src/blocks/remote-data-container/edit.tsx +++ b/src/blocks/remote-data-container/edit.tsx @@ -3,21 +3,19 @@ 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'; import { PatternSelection } from '@/blocks/remote-data-container/components/pattern-selection/PatternSelection'; import { Placeholder } from '@/blocks/remote-data-container/components/placeholders/Placeholder'; -import { - CONTAINER_CLASS_NAME, - DISPLAY_QUERY_KEY, -} from '@/blocks/remote-data-container/config/constants'; +import { CONTAINER_CLASS_NAME } from '@/blocks/remote-data-container/config/constants'; import { usePatterns } from '@/blocks/remote-data-container/hooks/usePatterns'; import { useRemoteData } from '@/blocks/remote-data-container/hooks/useRemoteData'; import { hasRemoteDataChanged } from '@/utils/block-binding'; import { getBlockConfig } from '@/utils/localized-block-data'; import { migrateRemoteData } from '@/utils/remote-data'; -import { QueryInputsPanel } from './components/panels/QueryInputsPanel'; + import './editor.scss'; export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ) { @@ -39,7 +37,10 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ) { blockName, externallyManagedRemoteData: remoteDataAttribute, externallyManagedUpdateRemoteData: updateRemoteData, - queryKey: DISPLAY_QUERY_KEY, + queryKey: + blockConfig.selectors.find( + selector => selector.type === 'manual-input' || selector.type === 'load-without-input' + )?.query_key ?? '', } ); const [ showPatternSelection, setShowPatternSelection ] = useState< boolean >( false ); diff --git a/src/blocks/remote-data-container/hooks/useRemoteData.ts b/src/blocks/remote-data-container/hooks/useRemoteData.ts index 2c51d9abf..fac7ff654 100644 --- a/src/blocks/remote-data-container/hooks/useRemoteData.ts +++ b/src/blocks/remote-data-container/hooks/useRemoteData.ts @@ -101,6 +101,7 @@ export function useRemoteData( { const hasResolvedData = Boolean( resolvedData ); const blockConfig = getBlockConfig( blockName ); + const query = blockConfig?.selectors?.find( selector => selector.query_key === queryKey ); if ( ! query ) { @@ -192,6 +193,8 @@ export function useRemoteData( { inputs[ 0 ] = { ...inputs[ 0 ], ...managedQueryInput }; } + console.log( 'queryKey', queryKey ); + const requestData: RemoteDataApiRequest = { block_name: blockName, query_key: queryKey, From b130ff26aea29ca1fb708daf4ce8bb2d47bdf9ae Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Wed, 14 May 2025 12:30:51 +1000 Subject: [PATCH 11/74] Comment away everything that initializes remote data with teh wrong query key --- .../placeholders/ItemSelectQueryType.tsx | 13 +- .../components/placeholders/Placeholder.tsx | 9 +- src/blocks/remote-data-container/edit.tsx | 169 +++++++++--------- .../hooks/useRemoteData.ts | 2 +- types/remote-data.d.ts | 1 + 5 files changed, 96 insertions(+), 98 deletions(-) diff --git a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx index 0d10d286c..6c5f6cd96 100644 --- a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx +++ b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx @@ -11,19 +11,22 @@ import { InputPopover } from '@/blocks/remote-data-container/components/popovers interface ItemSelectQueryTypeProps { blockConfig: BlockConfig; - onSelect: ( data: RemoteDataQueryInput[] ) => void; + initializeRemoteData: ( queryKey: string ) => void; } export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) { const { blockConfig: { name: blockName, selectors }, - onSelect, + initializeRemoteData, } = props; - const [ activeSelector, setActiveSelector ] = useState< ( typeof selectors )[ 0 ] | null >( null ); + const [ activeSelector, setActiveSelector ] = useState< ( typeof selectors )[ 0 ] | null >( + null + ); const handleSelectorClick = ( selector: ( typeof selectors )[ 0 ] ) => { setActiveSelector( selector ); + initializeRemoteData( selector.query_key ); }; if ( activeSelector ) { @@ -31,7 +34,7 @@ export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) { blockName, headerImage: activeSelector.image_url, inputVariables: activeSelector.inputs, - onSelect, + onSelect: () => {}, queryKey: activeSelector.query_key, title: activeSelector.name, }; @@ -47,7 +50,7 @@ export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) { /> ); case 'load-without-input': - onSelect( [ {} ] ); + // onSelect( [ {} ] ); return null; case 'manual-input': if ( activeSelector.inputs.length === 1 && activeSelector.inputs[ 0 ] ) { diff --git a/src/blocks/remote-data-container/components/placeholders/Placeholder.tsx b/src/blocks/remote-data-container/components/placeholders/Placeholder.tsx index f4eaa0a18..78bbd0c96 100644 --- a/src/blocks/remote-data-container/components/placeholders/Placeholder.tsx +++ b/src/blocks/remote-data-container/components/placeholders/Placeholder.tsx @@ -6,11 +6,11 @@ import { ItemSelectQueryType } from '@/blocks/remote-data-container/components/p export interface PlaceholderProps { blockConfig: BlockConfig; - onSelect: ( input: RemoteDataQueryInput[] ) => void; + initializeRemoteData: ( queryKey: string ) => void; } export function Placeholder( props: PlaceholderProps ) { - const { blockConfig, onSelect } = props; + const { blockConfig, initializeRemoteData } = props; const { instructions, settings } = blockConfig; const iconElement: IconType = ( settings.icon as IconType ) ?? cloud; @@ -23,7 +23,10 @@ export function Placeholder( props: PlaceholderProps ) { instructions ?? __( 'This block requires selection of one or more items for display.' ) } > - + ); } diff --git a/src/blocks/remote-data-container/edit.tsx b/src/blocks/remote-data-container/edit.tsx index ccb6fa4d4..9d3b759ab 100644 --- a/src/blocks/remote-data-container/edit.tsx +++ b/src/blocks/remote-data-container/edit.tsx @@ -1,18 +1,9 @@ -import { BlockPattern, InspectorControls, useBlockProps } from '@wordpress/block-editor'; +import { useBlockProps } from '@wordpress/block-editor'; 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'; -import { PatternSelection } from '@/blocks/remote-data-container/components/pattern-selection/PatternSelection'; import { Placeholder } from '@/blocks/remote-data-container/components/placeholders/Placeholder'; import { CONTAINER_CLASS_NAME } from '@/blocks/remote-data-container/config/constants'; -import { usePatterns } from '@/blocks/remote-data-container/hooks/usePatterns'; -import { useRemoteData } from '@/blocks/remote-data-container/hooks/useRemoteData'; -import { hasRemoteDataChanged } from '@/utils/block-binding'; import { getBlockConfig } from '@/utils/localized-block-data'; import { migrateRemoteData } from '@/utils/remote-data'; @@ -26,101 +17,101 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ) { throw new Error( `Block configuration not found for block: ${ blockName }` ); } - const rootClientId = props.clientId; const blockProps = useBlockProps( { className: CONTAINER_CLASS_NAME } ); const remoteDataAttribute = migrateRemoteData( props.attributes.remoteData ); - const { getSupportedPatterns, innerBlocksPattern, insertPatternBlocks, resetInnerBlocks } = - usePatterns( blockName, rootClientId ); + // const { getSupportedPatterns, innerBlocksPattern, insertPatternBlocks, resetInnerBlocks } = + // usePatterns( blockName, rootClientId ); - const { data, fetch, loading, reset, supportsPagination } = useRemoteData( { - blockName, - externallyManagedRemoteData: remoteDataAttribute, - externallyManagedUpdateRemoteData: updateRemoteData, - queryKey: - blockConfig.selectors.find( - selector => selector.type === 'manual-input' || selector.type === 'load-without-input' - )?.query_key ?? '', - } ); + // const { data, fetch, loading, reset, supportsPagination } = useRemoteData( { + // blockName, + // externallyManagedRemoteData: remoteDataAttribute, + // externallyManagedUpdateRemoteData: updateRemoteData, + // queryKey: '', + // } ); - const [ showPatternSelection, setShowPatternSelection ] = useState< boolean >( false ); + // const [ showPatternSelection, setShowPatternSelection ] = useState< boolean >( false ); - function refreshRemoteData(): void { - void fetch( remoteDataAttribute?.queryInputs ?? [ {} ] ); + function initializeRemoteData( queryKey: string ): void { + console.log( 'Initializing remote data for query key', queryKey ); } - function resetPatternSelection(): void { - resetInnerBlocks(); - setShowPatternSelection( false ); - } - - function resetRemoteData(): void { - reset(); - resetPatternSelection(); - } - - function onSelectPattern( pattern: BlockPattern ): void { - insertPatternBlocks( pattern, supportsPagination ); - setShowPatternSelection( false ); - } - - function onSelectRemoteData( inputs: RemoteDataQueryInput[] ): void { - void fetch( inputs ).then( () => { - if ( innerBlocksPattern ) { - insertPatternBlocks( innerBlocksPattern, supportsPagination ); - return; - } - - setShowPatternSelection( true ); - } ); - } - - function updateRemoteData( remoteData?: RemoteData ): void { - if ( hasRemoteDataChanged( remoteDataAttribute, remoteData ) ) { - props.setAttributes( { remoteData } ); - } - } - - function onUpdateQueryInputs( queryKey: string, inputs: RemoteDataQueryInput[] ): void { - if ( ! remoteDataAttribute ) { - return; - } - - updateRemoteData( { - ...remoteDataAttribute, - queryInputs: inputs, - queryKey, - } ); - refreshRemoteData(); - } + // function refreshRemoteData(): void { + // void fetch( remoteDataAttribute?.queryInputs ?? [ {} ] ); + // } + + // function resetPatternSelection(): void { + // resetInnerBlocks(); + // setShowPatternSelection( false ); + // } + + // function resetRemoteData(): void { + // reset(); + // resetPatternSelection(); + // } + + // function onSelectPattern( pattern: BlockPattern ): void { + // insertPatternBlocks( pattern, true ); + // setShowPatternSelection( false ); + // } + + // function onSelectRemoteData( inputs: RemoteDataQueryInput[] ): void { + // void fetch( inputs ).then( () => { + // if ( innerBlocksPattern ) { + // insertPatternBlocks( innerBlocksPattern, supportsPagination ); + // return; + // } + + // setShowPatternSelection( true ); + // } ); + // } + + // function updateRemoteData( remoteData?: RemoteData ): void { + // if ( hasRemoteDataChanged( remoteDataAttribute, remoteData ) ) { + // props.setAttributes( { remoteData } ); + // } + // } + + // function onUpdateQueryInputs( queryKey: string, inputs: RemoteDataQueryInput[] ): void { + // if ( ! remoteDataAttribute ) { + // return; + // } + + // updateRemoteData( { + // ...remoteDataAttribute, + // queryInputs: inputs, + // queryKey, + // } ); + // refreshRemoteData(); + // } // No remote data has been selected yet, show a placeholder. - if ( ! data ) { + if ( ! remoteDataAttribute?.queryKey ) { return (
- +
); } - if ( showPatternSelection ) { - const supportedPatterns = getSupportedPatterns( data.results[ 0 ] ); - - return ( -
- -
- ); - } + // if ( showPatternSelection ) { + // const supportedPatterns = getSupportedPatterns( data.results[ 0 ] ); + + // return ( + //
+ // + //
+ // ); + // } return ( <> - + { /* ) { remoteData={ data } selectors={ blockConfig.selectors } /> - + */ }
- { loading && ( + { /* { loading && (
) { } } />
- ) } + ) } */ }
diff --git a/src/blocks/remote-data-container/hooks/useRemoteData.ts b/src/blocks/remote-data-container/hooks/useRemoteData.ts index fac7ff654..c6311a6a3 100644 --- a/src/blocks/remote-data-container/hooks/useRemoteData.ts +++ b/src/blocks/remote-data-container/hooks/useRemoteData.ts @@ -193,7 +193,7 @@ export function useRemoteData( { inputs[ 0 ] = { ...inputs[ 0 ], ...managedQueryInput }; } - console.log( 'queryKey', queryKey ); + console.log( 'Query key being used for fetch', queryKey ); const requestData: RemoteDataApiRequest = { block_name: blockName, diff --git a/types/remote-data.d.ts b/types/remote-data.d.ts index 9f07233d8..99645f5ed 100644 --- a/types/remote-data.d.ts +++ b/types/remote-data.d.ts @@ -42,6 +42,7 @@ interface RemoteData { interface RemoteDataBlockAttributes { remoteData?: RemoteData; + queryKey?: string; } interface RemoteDataNoResultsBlockAttributes { From 74699d7dc5702b54ad58a1166bc9d8e299ca04e7 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Wed, 14 May 2025 15:33:43 +1000 Subject: [PATCH 12/74] Attempting to move just the remote data lgic to another component, leaving the query selection as it's own component. Query entry logic is broken --- .../components/QueryComponent.tsx | 143 ++++++++++++++++++ .../placeholders/ItemSelectQueryType.tsx | 8 +- .../components/placeholders/Placeholder.tsx | 4 +- src/blocks/remote-data-container/edit.tsx | 137 +++-------------- 4 files changed, 171 insertions(+), 121 deletions(-) create mode 100644 src/blocks/remote-data-container/components/QueryComponent.tsx diff --git a/src/blocks/remote-data-container/components/QueryComponent.tsx b/src/blocks/remote-data-container/components/QueryComponent.tsx new file mode 100644 index 000000000..e9b84a034 --- /dev/null +++ b/src/blocks/remote-data-container/components/QueryComponent.tsx @@ -0,0 +1,143 @@ +import { + BlockPattern, + InnerBlocks, + InspectorControls, + useBlockProps, +} from '@wordpress/block-editor'; +import { useEffect, useState } from '@wordpress/element'; + +import { DataPanel } from './panels/DataPanel'; +import { OverridesPanel } from './panels/OverridesPanel'; +import { QueryInputsPanel } from './panels/QueryInputsPanel'; +import { PatternSelection } from '@/blocks/remote-data-container/components/pattern-selection/PatternSelection'; +import { CONTAINER_CLASS_NAME } from '@/blocks/remote-data-container/config/constants'; +import { usePatterns } from '@/blocks/remote-data-container/hooks/usePatterns'; +import { useRemoteData } from '@/blocks/remote-data-container/hooks/useRemoteData'; +import { hasRemoteDataChanged } from '@/utils/block-binding'; + +export interface QueryComponentProps { + queryKeySelected: string; + blockConfig: BlockConfig; + blockName: string; + rootClientId: string; + remoteDataAttribute: RemoteData | undefined; + setAttributes: ( attributes: RemoteDataBlockAttributes ) => void; + queryInputs: RemoteDataQueryInput[]; + onQueryInputsChange?: ( inputs: RemoteDataQueryInput[] ) => void; +} + +export function QueryComponent( props: QueryComponentProps ) { + const { + queryKeySelected, + blockConfig, + blockName, + rootClientId, + remoteDataAttribute, + setAttributes, + queryInputs, + onQueryInputsChange, + } = props; + + const blockProps = useBlockProps( { className: CONTAINER_CLASS_NAME } ); + const { getSupportedPatterns, innerBlocksPattern, insertPatternBlocks, resetInnerBlocks } = + usePatterns( blockName, rootClientId ); + const { data, fetch, reset, supportsPagination } = useRemoteData( { + blockName, + externallyManagedRemoteData: remoteDataAttribute, + externallyManagedUpdateRemoteData: updateRemoteData, + queryKey: queryKeySelected, + } ); + const [ showPatternSelection, setShowPatternSelection ] = useState< boolean >( false ); + + // Monitor queryInputs changes from parent + useEffect( () => { + void fetch( queryInputs ).then( () => { + if ( innerBlocksPattern ) { + insertPatternBlocks( innerBlocksPattern, supportsPagination ); + return; + } + + setShowPatternSelection( true ); + } ); + }, [ queryInputs ] ); + + function refreshRemoteData(): void { + void fetch( remoteDataAttribute?.queryInputs ?? [ {} ] ); + } + + function resetPatternSelection(): void { + resetInnerBlocks(); + setShowPatternSelection( false ); + } + + function resetRemoteData(): void { + reset(); + resetPatternSelection(); + } + + function onSelectPattern( pattern: BlockPattern ): void { + insertPatternBlocks( pattern, true ); + setShowPatternSelection( false ); + } + + function updateRemoteData( remoteData?: RemoteData ): void { + if ( hasRemoteDataChanged( remoteDataAttribute, remoteData ) ) { + setAttributes( { remoteData } ); + } + } + + function onUpdateQueryInputs( queryKey: string, inputs: RemoteDataQueryInput[] ): void { + if ( ! remoteDataAttribute ) { + return; + } + + updateRemoteData( { + ...remoteDataAttribute, + queryInputs: inputs, + queryKey, + } ); + onQueryInputsChange?.( inputs ); + } + + if ( showPatternSelection ) { + const supportedPatterns = getSupportedPatterns( data?.results[ 0 ] ); + + return ( +
+ +
+ ); + } + + return ( + <> + { data && ( + + + + + + ) } +
+ +
+ + ); +} diff --git a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx index 6c5f6cd96..ffb223068 100644 --- a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx +++ b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx @@ -2,8 +2,8 @@ import { Button, __experimentalToggleGroupControl as ToggleGroupControl, } from '@wordpress/components'; +import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { useState } from 'react'; import { DataViewsModal } from '@/blocks/remote-data-container/components/modals/DataViewsModal'; import { InputModal } from '@/blocks/remote-data-container/components/modals/InputModal'; @@ -12,12 +12,14 @@ import { InputPopover } from '@/blocks/remote-data-container/components/popovers interface ItemSelectQueryTypeProps { blockConfig: BlockConfig; initializeRemoteData: ( queryKey: string ) => void; + onSelect: ( data: RemoteDataQueryInput[] ) => void; } export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) { const { blockConfig: { name: blockName, selectors }, initializeRemoteData, + onSelect, } = props; const [ activeSelector, setActiveSelector ] = useState< ( typeof selectors )[ 0 ] | null >( @@ -34,7 +36,7 @@ export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) { blockName, headerImage: activeSelector.image_url, inputVariables: activeSelector.inputs, - onSelect: () => {}, + onSelect, queryKey: activeSelector.query_key, title: activeSelector.name, }; @@ -50,7 +52,7 @@ export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) { /> ); case 'load-without-input': - // onSelect( [ {} ] ); + onSelect( [ {} ] ); return null; case 'manual-input': if ( activeSelector.inputs.length === 1 && activeSelector.inputs[ 0 ] ) { diff --git a/src/blocks/remote-data-container/components/placeholders/Placeholder.tsx b/src/blocks/remote-data-container/components/placeholders/Placeholder.tsx index 78bbd0c96..2f4d16ee5 100644 --- a/src/blocks/remote-data-container/components/placeholders/Placeholder.tsx +++ b/src/blocks/remote-data-container/components/placeholders/Placeholder.tsx @@ -7,10 +7,11 @@ import { ItemSelectQueryType } from '@/blocks/remote-data-container/components/p export interface PlaceholderProps { blockConfig: BlockConfig; initializeRemoteData: ( queryKey: string ) => void; + onSelect: ( input: RemoteDataQueryInput[] ) => void; } export function Placeholder( props: PlaceholderProps ) { - const { blockConfig, initializeRemoteData } = props; + const { blockConfig, initializeRemoteData, onSelect } = props; const { instructions, settings } = blockConfig; const iconElement: IconType = ( settings.icon as IconType ) ?? cloud; @@ -26,6 +27,7 @@ export function Placeholder( props: PlaceholderProps ) { ); diff --git a/src/blocks/remote-data-container/edit.tsx b/src/blocks/remote-data-container/edit.tsx index 9d3b759ab..4e6eaab22 100644 --- a/src/blocks/remote-data-container/edit.tsx +++ b/src/blocks/remote-data-container/edit.tsx @@ -1,15 +1,16 @@ -import { useBlockProps } from '@wordpress/block-editor'; import { BlockEditProps } from '@wordpress/blocks'; +import { useState } from '@wordpress/element'; -import { InnerBlocks } from '@/blocks/remote-data-container/components/InnerBlocks'; +import { QueryComponent } from './components/QueryComponent'; import { Placeholder } from '@/blocks/remote-data-container/components/placeholders/Placeholder'; -import { CONTAINER_CLASS_NAME } from '@/blocks/remote-data-container/config/constants'; import { getBlockConfig } from '@/utils/localized-block-data'; import { migrateRemoteData } from '@/utils/remote-data'; import './editor.scss'; export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ) { + const [ queryKeySelected, setQueryKeySelected ] = useState< string >( '' ); + const [ queryInputs, setQueryInputs ] = useState< RemoteDataQueryInput[] >( [] ); const blockName = props.name; const blockConfig = getBlockConfig( blockName ); @@ -17,131 +18,33 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ) { throw new Error( `Block configuration not found for block: ${ blockName }` ); } - const blockProps = useBlockProps( { className: CONTAINER_CLASS_NAME } ); const remoteDataAttribute = migrateRemoteData( props.attributes.remoteData ); - // const { getSupportedPatterns, innerBlocksPattern, insertPatternBlocks, resetInnerBlocks } = - // usePatterns( blockName, rootClientId ); - - // const { data, fetch, loading, reset, supportsPagination } = useRemoteData( { - // blockName, - // externallyManagedRemoteData: remoteDataAttribute, - // externallyManagedUpdateRemoteData: updateRemoteData, - // queryKey: '', - // } ); - - // const [ showPatternSelection, setShowPatternSelection ] = useState< boolean >( false ); - function initializeRemoteData( queryKey: string ): void { + setQueryKeySelected( queryKey ); console.log( 'Initializing remote data for query key', queryKey ); } - // function refreshRemoteData(): void { - // void fetch( remoteDataAttribute?.queryInputs ?? [ {} ] ); - // } - - // function resetPatternSelection(): void { - // resetInnerBlocks(); - // setShowPatternSelection( false ); - // } - - // function resetRemoteData(): void { - // reset(); - // resetPatternSelection(); - // } - - // function onSelectPattern( pattern: BlockPattern ): void { - // insertPatternBlocks( pattern, true ); - // setShowPatternSelection( false ); - // } - - // function onSelectRemoteData( inputs: RemoteDataQueryInput[] ): void { - // void fetch( inputs ).then( () => { - // if ( innerBlocksPattern ) { - // insertPatternBlocks( innerBlocksPattern, supportsPagination ); - // return; - // } - - // setShowPatternSelection( true ); - // } ); - // } - - // function updateRemoteData( remoteData?: RemoteData ): void { - // if ( hasRemoteDataChanged( remoteDataAttribute, remoteData ) ) { - // props.setAttributes( { remoteData } ); - // } - // } - - // function onUpdateQueryInputs( queryKey: string, inputs: RemoteDataQueryInput[] ): void { - // if ( ! remoteDataAttribute ) { - // return; - // } - - // updateRemoteData( { - // ...remoteDataAttribute, - // queryInputs: inputs, - // queryKey, - // } ); - // refreshRemoteData(); - // } - - // No remote data has been selected yet, show a placeholder. - if ( ! remoteDataAttribute?.queryKey ) { - return ( -
- -
- ); - } - - // if ( showPatternSelection ) { - // const supportedPatterns = getSupportedPatterns( data.results[ 0 ] ); - - // return ( - //
- // - //
- // ); - // } - return ( <> - { /* - - - - */ } - -
- { /* { loading && ( -
- -
- ) } */ } - -
+ ) } ); } From d67ccb26313531b59ad68122e7c496deb139bcdb Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Thu, 15 May 2025 09:36:45 +1000 Subject: [PATCH 13/74] Load the queryKey and queryInputs from state if they are available --- src/blocks/remote-data-container/edit.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/blocks/remote-data-container/edit.tsx b/src/blocks/remote-data-container/edit.tsx index 4e6eaab22..7019d8f25 100644 --- a/src/blocks/remote-data-container/edit.tsx +++ b/src/blocks/remote-data-container/edit.tsx @@ -9,8 +9,6 @@ import { migrateRemoteData } from '@/utils/remote-data'; import './editor.scss'; export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ) { - const [ queryKeySelected, setQueryKeySelected ] = useState< string >( '' ); - const [ queryInputs, setQueryInputs ] = useState< RemoteDataQueryInput[] >( [] ); const blockName = props.name; const blockConfig = getBlockConfig( blockName ); @@ -20,6 +18,15 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ) { const remoteDataAttribute = migrateRemoteData( props.attributes.remoteData ); + const [ queryKeySelected, setQueryKeySelected ] = useState< string >( + remoteDataAttribute?.queryKey ?? '' + ); + const [ queryInputs, setQueryInputs ] = useState< RemoteDataQueryInput[] >( + remoteDataAttribute?.queryInputs ?? [] + ); + + console.log( 'remoteDataAttribute', remoteDataAttribute ); + function initializeRemoteData( queryKey: string ): void { setQueryKeySelected( queryKey ); console.log( 'Initializing remote data for query key', queryKey ); From de3a74220e9746b74b652d5c7992a6c14c72bd3e Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Thu, 15 May 2025 15:08:33 +1000 Subject: [PATCH 14/74] Attempting to redo the whole queries system --- .../components/QueryComponent.tsx | 22 ++-- .../placeholders/ItemSelectQueryType.tsx | 120 ++++++------------ .../components/placeholders/Placeholder.tsx | 34 ----- .../QuerySelectionPlaceholder.tsx | 49 +++++++ src/blocks/remote-data-container/edit.tsx | 50 +++----- types/remote-data.d.ts | 1 - 6 files changed, 125 insertions(+), 151 deletions(-) delete mode 100644 src/blocks/remote-data-container/components/placeholders/Placeholder.tsx create mode 100644 src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx diff --git a/src/blocks/remote-data-container/components/QueryComponent.tsx b/src/blocks/remote-data-container/components/QueryComponent.tsx index e9b84a034..9e57a0a55 100644 --- a/src/blocks/remote-data-container/components/QueryComponent.tsx +++ b/src/blocks/remote-data-container/components/QueryComponent.tsx @@ -16,7 +16,7 @@ import { useRemoteData } from '@/blocks/remote-data-container/hooks/useRemoteDat import { hasRemoteDataChanged } from '@/utils/block-binding'; export interface QueryComponentProps { - queryKeySelected: string; + queryKey: string; blockConfig: BlockConfig; blockName: string; rootClientId: string; @@ -28,7 +28,7 @@ export interface QueryComponentProps { export function QueryComponent( props: QueryComponentProps ) { const { - queryKeySelected, + queryKey, blockConfig, blockName, rootClientId, @@ -45,21 +45,25 @@ export function QueryComponent( props: QueryComponentProps ) { blockName, externallyManagedRemoteData: remoteDataAttribute, externallyManagedUpdateRemoteData: updateRemoteData, - queryKey: queryKeySelected, + queryKey, } ); const [ showPatternSelection, setShowPatternSelection ] = useState< boolean >( false ); - // Monitor queryInputs changes from parent useEffect( () => { - void fetch( queryInputs ).then( () => { + onSelectRemoteData( queryInputs ); + }, [ queryInputs ] ); + + function onSelectRemoteData( inputs: RemoteDataQueryInput[] ): void { + void fetch( inputs ).then( () => { if ( innerBlocksPattern ) { insertPatternBlocks( innerBlocksPattern, supportsPagination ); return; } + console.log( 'change pattern selection to true' ); setShowPatternSelection( true ); } ); - }, [ queryInputs ] ); + } function refreshRemoteData(): void { void fetch( remoteDataAttribute?.queryInputs ?? [ {} ] ); @@ -86,7 +90,7 @@ export function QueryComponent( props: QueryComponentProps ) { } } - function onUpdateQueryInputs( queryKey: string, inputs: RemoteDataQueryInput[] ): void { + function onUpdateQueryInputs( newQueryKey: string, inputs: RemoteDataQueryInput[] ): void { if ( ! remoteDataAttribute ) { return; } @@ -94,11 +98,13 @@ export function QueryComponent( props: QueryComponentProps ) { updateRemoteData( { ...remoteDataAttribute, queryInputs: inputs, - queryKey, + queryKey: newQueryKey, } ); onQueryInputsChange?.( inputs ); } + console.log( showPatternSelection ); + if ( showPatternSelection ) { const supportedPatterns = getSupportedPatterns( data?.results[ 0 ] ); diff --git a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx index ffb223068..92298fd3c 100644 --- a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx +++ b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx @@ -1,9 +1,4 @@ -import { - Button, - __experimentalToggleGroupControl as ToggleGroupControl, -} from '@wordpress/components'; -import { useState } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; +import { Button, __experimentalToggleGroupControl as ToggleGroupControl, } from '@wordpress/components'; import { DataViewsModal } from '@/blocks/remote-data-container/components/modals/DataViewsModal'; import { InputModal } from '@/blocks/remote-data-container/components/modals/InputModal'; @@ -11,90 +6,59 @@ import { InputPopover } from '@/blocks/remote-data-container/components/popovers interface ItemSelectQueryTypeProps { blockConfig: BlockConfig; - initializeRemoteData: ( queryKey: string ) => void; onSelect: ( data: RemoteDataQueryInput[] ) => void; } export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) { const { blockConfig: { name: blockName, selectors }, - initializeRemoteData, onSelect, } = props; - const [ activeSelector, setActiveSelector ] = useState< ( typeof selectors )[ 0 ] | null >( - null - ); - - const handleSelectorClick = ( selector: ( typeof selectors )[ 0 ] ) => { - setActiveSelector( selector ); - initializeRemoteData( selector.query_key ); - }; - - if ( activeSelector ) { - const selectorProps = { - blockName, - headerImage: activeSelector.image_url, - inputVariables: activeSelector.inputs, - onSelect, - queryKey: activeSelector.query_key, - title: activeSelector.name, - }; + return ( + + { selectors.map( selector => { + const title = selector.name; + const selectorProps = { + blockName, + headerImage: selector.image_url, + inputVariables: selector.inputs, + onSelect, + queryKey: selector.query_key, + title, + }; - switch ( activeSelector.type ) { - case 'search': - case 'list': - return ( - - ); - case 'load-without-input': - onSelect( [ {} ] ); - return null; - case 'manual-input': - if ( activeSelector.inputs.length === 1 && activeSelector.inputs[ 0 ] ) { - return ( - - ); + switch ( selector.type ) { + case 'search': + case 'list': + return ( + + ); + case 'load-without-input': + return ( + + ); + case 'manual-input': + if ( selector.inputs.length === 1 && selector.inputs[ 0 ] ) { + return ( + + ); + } + return ; } - return ( - - ); - default: - return null; - } - } - return ( - - { selectors.map( selector => { - return ( - - ); + return null; } ) } ); diff --git a/src/blocks/remote-data-container/components/placeholders/Placeholder.tsx b/src/blocks/remote-data-container/components/placeholders/Placeholder.tsx deleted file mode 100644 index 2f4d16ee5..000000000 --- a/src/blocks/remote-data-container/components/placeholders/Placeholder.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { IconType, Placeholder as PlaceholderComponent } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { cloud } from '@wordpress/icons'; - -import { ItemSelectQueryType } from '@/blocks/remote-data-container/components/placeholders/ItemSelectQueryType'; - -export interface PlaceholderProps { - blockConfig: BlockConfig; - initializeRemoteData: ( queryKey: string ) => void; - onSelect: ( input: RemoteDataQueryInput[] ) => void; -} - -export function Placeholder( props: PlaceholderProps ) { - const { blockConfig, initializeRemoteData, onSelect } = props; - const { instructions, settings } = blockConfig; - - const iconElement: IconType = ( settings.icon as IconType ) ?? cloud; - - return ( - - - - ); -} diff --git a/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx b/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx new file mode 100644 index 000000000..45e5117c3 --- /dev/null +++ b/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx @@ -0,0 +1,49 @@ +import { + Button, + __experimentalToggleGroupControl as ToggleGroupControl, + IconType, + Placeholder as PlaceholderComponent, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { cloud } from '@wordpress/icons'; + +export interface QuerySelectionPlaceholderProps { + blockConfig: BlockConfig; + onSelect: ( queryKey: string ) => void; +} + +export function QuerySelectionPlaceholder( props: QuerySelectionPlaceholderProps ) { + const { blockConfig, onSelect } = props; + const { instructions, settings, selectors } = blockConfig; + + const iconElement: IconType = ( settings.icon as IconType ) ?? cloud; + + return ( + + + { selectors.map( selector => { + return ( + + ); + } ) } + + + ); +} diff --git a/src/blocks/remote-data-container/edit.tsx b/src/blocks/remote-data-container/edit.tsx index 7019d8f25..d08e87eba 100644 --- a/src/blocks/remote-data-container/edit.tsx +++ b/src/blocks/remote-data-container/edit.tsx @@ -1,8 +1,9 @@ +import { useBlockProps, InnerBlocks } from '@wordpress/block-editor'; import { BlockEditProps } from '@wordpress/blocks'; import { useState } from '@wordpress/element'; -import { QueryComponent } from './components/QueryComponent'; -import { Placeholder } from '@/blocks/remote-data-container/components/placeholders/Placeholder'; +import { QuerySelectionPlaceholder } from '@/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder'; +import { CONTAINER_CLASS_NAME } from '@/blocks/remote-data-container/config/constants'; import { getBlockConfig } from '@/utils/localized-block-data'; import { migrateRemoteData } from '@/utils/remote-data'; @@ -16,42 +17,31 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ) { throw new Error( `Block configuration not found for block: ${ blockName }` ); } + const blockProps = useBlockProps( { className: CONTAINER_CLASS_NAME } ); const remoteDataAttribute = migrateRemoteData( props.attributes.remoteData ); + const [ queryKey, setQueryKey ] = useState< string >( remoteDataAttribute?.queryKey ?? '' ); - const [ queryKeySelected, setQueryKeySelected ] = useState< string >( - remoteDataAttribute?.queryKey ?? '' - ); - const [ queryInputs, setQueryInputs ] = useState< RemoteDataQueryInput[] >( - remoteDataAttribute?.queryInputs ?? [] - ); + // const [ queryInputs, setQueryInputs ] = useState< RemoteDataQueryInput[] >( + // remoteDataAttribute?.queryInputs ?? [ {} ] + // ); + + // console.log( 'remoteDataAttribute', remoteDataAttribute ); + + // function setAttributes( attributes: RemoteDataBlockAttributes ): void { + // props.setAttributes( attributes ); + // } - console.log( 'remoteDataAttribute', remoteDataAttribute ); + console.log( 'queryKey', queryKey ); - function initializeRemoteData( queryKey: string ): void { - setQueryKeySelected( queryKey ); - console.log( 'Initializing remote data for query key', queryKey ); + if ( ! queryKey ) { + return ; } return ( <> - { queryKeySelected && ( - - ) } - { ! queryKeySelected && ( - - ) } +
+ +
); } diff --git a/types/remote-data.d.ts b/types/remote-data.d.ts index 99645f5ed..9f07233d8 100644 --- a/types/remote-data.d.ts +++ b/types/remote-data.d.ts @@ -42,7 +42,6 @@ interface RemoteData { interface RemoteDataBlockAttributes { remoteData?: RemoteData; - queryKey?: string; } interface RemoteDataNoResultsBlockAttributes { From dda2bdd34f59fcff90b15bd7fcc3e0be53d2dff3 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Thu, 15 May 2025 16:49:10 +1000 Subject: [PATCH 15/74] Group selectors by query --- inc/Editor/BlockManagement/ConfigRegistry.php | 7 +- .../placeholders/ItemSelectQueryType.tsx | 99 +++++++++---------- .../QuerySelectionPlaceholder.tsx | 49 ++++++--- 3 files changed, 91 insertions(+), 64 deletions(-) diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index 49b0c4824..3d9c8c7ee 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -58,7 +58,8 @@ public static function register_block( array $user_config = [] ): bool|WP_Error // ToDo: Add a validation step to check if the required query is present in the user_config['queries'] array. if ( $query->get_required_query() && ! empty( $query->get_required_query() ) ) { - $required_queries[] = $query->get_required_query(); + // Add the mapping of the required query to the required queries array. + $required_queries[ $query->get_required_query() ] = $query_key; } } @@ -68,7 +69,7 @@ public static function register_block( array $user_config = [] ): bool|WP_Error $input_schema = $query->get_input_schema(); $output_schema = $query->get_output_schema(); - if ( in_array( $query_key, $required_queries, true ) ) { + if ( isset( $required_queries[ $query_key ] ) ) { array_unshift( $selectors, [ @@ -78,6 +79,7 @@ public static function register_block( array $user_config = [] ): bool|WP_Error 'name' => ucfirst( $query_key ), 'query_key' => $query_key, 'type' => $query->get_type(), + 'group' => $required_queries[ $query_key ], ] ); } else { @@ -96,6 +98,7 @@ public static function register_block( array $user_config = [] ): bool|WP_Error 'name' => self::DISPLAY_QUERY_KEY === $query->get_type() ? ( $has_required_variables ? 'Manual input' : ( $is_collection ? 'Load collection' : 'Load item' ) ) : ucfirst( $query_key ), 'query_key' => $query_key, 'type' => $has_required_variables ? 'manual-input' : 'load-without-input', + 'group' => $query_key, ]; } } diff --git a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx index 92298fd3c..50b480a42 100644 --- a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx +++ b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx @@ -1,65 +1,62 @@ -import { Button, __experimentalToggleGroupControl as ToggleGroupControl, } from '@wordpress/components'; +import { Button } from '@wordpress/components'; import { DataViewsModal } from '@/blocks/remote-data-container/components/modals/DataViewsModal'; import { InputModal } from '@/blocks/remote-data-container/components/modals/InputModal'; import { InputPopover } from '@/blocks/remote-data-container/components/popovers/InputPopover'; +// Inline type for selector from BlockConfig +// (could also import BlockConfig and use BlockConfig['selectors'][0] if preferred) +type Selector = { + image_url?: string; + inputs: InputVariable[]; + name: string; + query_key: string; + display_name?: string; + type: string; +}; + interface ItemSelectQueryTypeProps { - blockConfig: BlockConfig; + blockName: string; + selector: Selector; onSelect: ( data: RemoteDataQueryInput[] ) => void; } export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) { - const { - blockConfig: { name: blockName, selectors }, + const { blockName, selector, onSelect } = props; + const title = selector.name; + const selectorProps = { + blockName, + headerImage: selector.image_url, + inputVariables: selector.inputs, onSelect, - } = props; - - return ( - - { selectors.map( selector => { - const title = selector.name; - const selectorProps = { - blockName, - headerImage: selector.image_url, - inputVariables: selector.inputs, - onSelect, - queryKey: selector.query_key, - title, - }; + queryKey: selector.query_key, + title, + }; - switch ( selector.type ) { - case 'search': - case 'list': - return ( - - ); - case 'load-without-input': - return ( - - ); - case 'manual-input': - if ( selector.inputs.length === 1 && selector.inputs[ 0 ] ) { - return ( - - ); - } - return ; - } + switch ( selector.type ) { + case 'search': + case 'list': + return ( + + ); + case 'load-without-input': + return ( + + ); + case 'manual-input': + if ( selector.inputs.length === 1 && selector.inputs[ 0 ] ) { + return ( + + ); + } + return ; + } - return null; - } ) } - - ); + return null; } diff --git a/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx b/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx index 45e5117c3..db1cb4fc3 100644 --- a/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx +++ b/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx @@ -4,8 +4,21 @@ import { IconType, Placeholder as PlaceholderComponent, } from '@wordpress/components'; +import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { cloud } from '@wordpress/icons'; +import { ItemSelectQueryType } from './ItemSelectQueryType'; + +// Inline type for selector from BlockConfig +// (could also import BlockConfig and use BlockConfig['selectors'][0] if preferred) +type Selector = { + image_url?: string; + inputs: InputVariable[]; + name: string; + query_key: string; + display_name?: string; + type: string; +}; export interface QuerySelectionPlaceholderProps { blockConfig: BlockConfig; @@ -17,6 +30,22 @@ export function QuerySelectionPlaceholder( props: QuerySelectionPlaceholderProps const { instructions, settings, selectors } = blockConfig; const iconElement: IconType = ( settings.icon as IconType ) ?? cloud; + const [ selectedSelector, setSelectedSelector ] = useState< Selector | null >( null ); + + if ( selectedSelector ) { + return ( + { + setSelectedSelector( null ); + if ( selectedSelector && typeof selectedSelector.query_key === 'string' ) { + onSelect( selectedSelector.query_key ); + } + } } + /> + ); + } return ( - { selectors.map( selector => { - return ( - - ); - } ) } + { selectors.map( selector => ( + + ) ) }
); From d5e40e4ee66804c204e77402222e552bf0befa2e Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Fri, 16 May 2025 09:54:30 +1000 Subject: [PATCH 16/74] Got the UI working finally, with the exception of pattern selection being reset on a reload --- inc/Editor/BlockManagement/ConfigRegistry.php | 4 +- .../placeholders/ItemSelectQueryType.tsx | 112 ++++++++++-------- .../QuerySelectionPlaceholder.tsx | 86 ++++++++------ src/blocks/remote-data-container/edit.tsx | 45 ++++--- .../panels/QueryInputsPanel.test.tsx | 1 + types/localized-block-data.d.ts | 1 + 6 files changed, 146 insertions(+), 103 deletions(-) diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index 3d9c8c7ee..0ee446280 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -79,7 +79,7 @@ public static function register_block( array $user_config = [] ): bool|WP_Error 'name' => ucfirst( $query_key ), 'query_key' => $query_key, 'type' => $query->get_type(), - 'group' => $required_queries[ $query_key ], + 'query_group' => $required_queries[ $query_key ], ] ); } else { @@ -98,7 +98,7 @@ public static function register_block( array $user_config = [] ): bool|WP_Error 'name' => self::DISPLAY_QUERY_KEY === $query->get_type() ? ( $has_required_variables ? 'Manual input' : ( $is_collection ? 'Load collection' : 'Load item' ) ) : ucfirst( $query_key ), 'query_key' => $query_key, 'type' => $has_required_variables ? 'manual-input' : 'load-without-input', - 'group' => $query_key, + 'query_group' => $query_key, ]; } } diff --git a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx index 50b480a42..cea7378c7 100644 --- a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx +++ b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx @@ -1,62 +1,78 @@ -import { Button } from '@wordpress/components'; +import { + Button, + __experimentalToggleGroupControl as ToggleGroupControl, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; import { DataViewsModal } from '@/blocks/remote-data-container/components/modals/DataViewsModal'; import { InputModal } from '@/blocks/remote-data-container/components/modals/InputModal'; import { InputPopover } from '@/blocks/remote-data-container/components/popovers/InputPopover'; -// Inline type for selector from BlockConfig -// (could also import BlockConfig and use BlockConfig['selectors'][0] if preferred) -type Selector = { - image_url?: string; - inputs: InputVariable[]; - name: string; - query_key: string; - display_name?: string; - type: string; -}; - interface ItemSelectQueryTypeProps { - blockName: string; - selector: Selector; + blockConfig: BlockConfig; + queryGroup: string; onSelect: ( data: RemoteDataQueryInput[] ) => void; } export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) { - const { blockName, selector, onSelect } = props; - const title = selector.name; - const selectorProps = { - blockName, - headerImage: selector.image_url, - inputVariables: selector.inputs, + const { + blockConfig: { name: blockName, selectors }, + queryGroup, onSelect, - queryKey: selector.query_key, - title, - }; + } = props; + + return ( + + { selectors + .filter( selector => selector.query_group === queryGroup ) + .map( selector => { + const title = selector.name; + const selectorProps = { + blockName, + headerImage: selector.image_url, + inputVariables: selector.inputs, + onSelect, + queryKey: selector.query_key, + title, + }; - switch ( selector.type ) { - case 'search': - case 'list': - return ( - - ); - case 'load-without-input': - return ( - - ); - case 'manual-input': - if ( selector.inputs.length === 1 && selector.inputs[ 0 ] ) { - return ( - - ); - } - return ; - } + switch ( selector.type ) { + case 'search': + case 'list': + return ( + + ); + case 'load-without-input': + return ( + + ); + case 'manual-input': + if ( selector.inputs.length === 1 && selector.inputs[ 0 ] ) { + return ( + + ); + } + return ; + } - return null; + return null; + } ) } + + ); } diff --git a/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx b/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx index db1cb4fc3..622a328aa 100644 --- a/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx +++ b/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx @@ -1,16 +1,16 @@ import { Button, - __experimentalToggleGroupControl as ToggleGroupControl, IconType, Placeholder as PlaceholderComponent, + __experimentalToggleGroupControl as ToggleGroupControl, } from '@wordpress/components'; import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { cloud } from '@wordpress/icons'; + import { ItemSelectQueryType } from './ItemSelectQueryType'; // Inline type for selector from BlockConfig -// (could also import BlockConfig and use BlockConfig['selectors'][0] if preferred) type Selector = { image_url?: string; inputs: InputVariable[]; @@ -18,33 +18,38 @@ type Selector = { query_key: string; display_name?: string; type: string; + query_group: string; }; export interface QuerySelectionPlaceholderProps { blockConfig: BlockConfig; - onSelect: ( queryKey: string ) => void; + onQueryGroupSelect: ( group: string ) => void; + onQueryInputsSelect: ( inputs: RemoteDataQueryInput[] ) => void; } export function QuerySelectionPlaceholder( props: QuerySelectionPlaceholderProps ) { - const { blockConfig, onSelect } = props; + const { blockConfig, onQueryGroupSelect, onQueryInputsSelect } = props; const { instructions, settings, selectors } = blockConfig; const iconElement: IconType = ( settings.icon as IconType ) ?? cloud; - const [ selectedSelector, setSelectedSelector ] = useState< Selector | null >( null ); - if ( selectedSelector ) { - return ( - { - setSelectedSelector( null ); - if ( selectedSelector && typeof selectedSelector.query_key === 'string' ) { - onSelect( selectedSelector.query_key ); - } - } } - /> - ); + // Create a unique list of query groups + const queryGroups: string[] = [ + ...new Set( selectors.map( ( selector: Selector ) => selector.query_group ) ), + ]; + + const [ selectedGroup, setSelectedGroup ] = useState< string >( '' ); + const [ showSelectors, setShowSelectors ] = useState< boolean >( false ); + + function handleSelectorOnSelect( inputs: RemoteDataQueryInput[] ) { + setShowSelectors( false ); + onQueryGroupSelect( selectedGroup ); + onQueryInputsSelect( inputs ); + } + + function handleGroupOnSelect( group: string ) { + setSelectedGroup( group ); + setShowSelectors( true ); } return ( @@ -55,22 +60,35 @@ export function QuerySelectionPlaceholder( props: QuerySelectionPlaceholderProps instructions ?? __( 'This block requires selection of one or more items for display.' ) } > - - { selectors.map( selector => ( - - ) ) } - + { ! showSelectors && ( + + { queryGroups.map( ( group: string ) => ( + + ) ) } + + ) } + { showSelectors && ( + + ) } ); } diff --git a/src/blocks/remote-data-container/edit.tsx b/src/blocks/remote-data-container/edit.tsx index d08e87eba..6d31df552 100644 --- a/src/blocks/remote-data-container/edit.tsx +++ b/src/blocks/remote-data-container/edit.tsx @@ -1,9 +1,8 @@ -import { useBlockProps, InnerBlocks } from '@wordpress/block-editor'; import { BlockEditProps } from '@wordpress/blocks'; import { useState } from '@wordpress/element'; +import { QueryComponent } from './components/QueryComponent'; import { QuerySelectionPlaceholder } from '@/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder'; -import { CONTAINER_CLASS_NAME } from '@/blocks/remote-data-container/config/constants'; import { getBlockConfig } from '@/utils/localized-block-data'; import { migrateRemoteData } from '@/utils/remote-data'; @@ -17,31 +16,39 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ) { throw new Error( `Block configuration not found for block: ${ blockName }` ); } - const blockProps = useBlockProps( { className: CONTAINER_CLASS_NAME } ); + const rootClientId = props.clientId; const remoteDataAttribute = migrateRemoteData( props.attributes.remoteData ); - const [ queryKey, setQueryKey ] = useState< string >( remoteDataAttribute?.queryKey ?? '' ); + const [ queryGroup, setQueryGroup ] = useState< string >( remoteDataAttribute?.queryKey ?? '' ); - // const [ queryInputs, setQueryInputs ] = useState< RemoteDataQueryInput[] >( - // remoteDataAttribute?.queryInputs ?? [ {} ] - // ); - - // console.log( 'remoteDataAttribute', remoteDataAttribute ); - - // function setAttributes( attributes: RemoteDataBlockAttributes ): void { - // props.setAttributes( attributes ); - // } + const [ queryInputs, setQueryInputs ] = useState< RemoteDataQueryInput[] >( + remoteDataAttribute?.queryInputs ?? [ {} ] + ); - console.log( 'queryKey', queryKey ); + function setAttributes( attributes: RemoteDataBlockAttributes ): void { + props.setAttributes( attributes ); + } - if ( ! queryKey ) { - return ; + if ( ! queryGroup ) { + return ( + + ); } return ( <> -
- -
+ ); } diff --git a/tests/src/blocks/remote-data-container/components/panels/QueryInputsPanel.test.tsx b/tests/src/blocks/remote-data-container/components/panels/QueryInputsPanel.test.tsx index 1cb86c29e..b3e082508 100644 --- a/tests/src/blocks/remote-data-container/components/panels/QueryInputsPanel.test.tsx +++ b/tests/src/blocks/remote-data-container/components/panels/QueryInputsPanel.test.tsx @@ -26,6 +26,7 @@ describe( 'QueryInputsPanel', () => { name: 'test_selector', query_key: 'test_query_key', type: 'manual', + query_group: 'test_query_group', }, ]; diff --git a/types/localized-block-data.d.ts b/types/localized-block-data.d.ts index 5bcf984c2..6d1e31060 100644 --- a/types/localized-block-data.d.ts +++ b/types/localized-block-data.d.ts @@ -40,6 +40,7 @@ interface BlockConfig { query_key: string; display_name?: string; type: string; + query_group: string; }[]; settings: { category: string; From 330c959101fcbb34275f82e1f3cfc8c46337f209 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Fri, 16 May 2025 09:59:35 +1000 Subject: [PATCH 17/74] Fix the pattern selection bug --- .../remote-data-container/components/QueryComponent.tsx | 8 +++++--- src/blocks/remote-data-container/edit.tsx | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/blocks/remote-data-container/components/QueryComponent.tsx b/src/blocks/remote-data-container/components/QueryComponent.tsx index 9e57a0a55..6547a2780 100644 --- a/src/blocks/remote-data-container/components/QueryComponent.tsx +++ b/src/blocks/remote-data-container/components/QueryComponent.tsx @@ -54,13 +54,17 @@ export function QueryComponent( props: QueryComponentProps ) { }, [ queryInputs ] ); function onSelectRemoteData( inputs: RemoteDataQueryInput[] ): void { + // if the old queryInputs and new ones are the same, skip this call. + if ( JSON.stringify( remoteDataAttribute?.queryInputs ) === JSON.stringify( inputs ) ) { + return; + } + void fetch( inputs ).then( () => { if ( innerBlocksPattern ) { insertPatternBlocks( innerBlocksPattern, supportsPagination ); return; } - console.log( 'change pattern selection to true' ); setShowPatternSelection( true ); } ); } @@ -103,8 +107,6 @@ export function QueryComponent( props: QueryComponentProps ) { onQueryInputsChange?.( inputs ); } - console.log( showPatternSelection ); - if ( showPatternSelection ) { const supportedPatterns = getSupportedPatterns( data?.results[ 0 ] ); diff --git a/src/blocks/remote-data-container/edit.tsx b/src/blocks/remote-data-container/edit.tsx index 6d31df552..3950eadf4 100644 --- a/src/blocks/remote-data-container/edit.tsx +++ b/src/blocks/remote-data-container/edit.tsx @@ -21,7 +21,7 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ) { const [ queryGroup, setQueryGroup ] = useState< string >( remoteDataAttribute?.queryKey ?? '' ); const [ queryInputs, setQueryInputs ] = useState< RemoteDataQueryInput[] >( - remoteDataAttribute?.queryInputs ?? [ {} ] + remoteDataAttribute?.queryInputs ?? [] ); function setAttributes( attributes: RemoteDataBlockAttributes ): void { From 49ece0519d616d3be471ef5df2d6bdbfd1c1f16c Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Fri, 16 May 2025 13:49:35 +1000 Subject: [PATCH 18/74] Fix the bug related to the query group and query key not being picked up correctly. --- inc/REST/RemoteDataController.php | 8 ++ .../components/FieldShortcodeSelectNew.tsx | 2 + .../components/FieldShortcodeSelection.tsx | 1 + .../components/QueryComponent.tsx | 3 + .../components/modals/DataViewsModal.tsx | 19 +++- .../components/modals/InputModal.tsx | 6 +- .../placeholders/ItemSelectQueryType.tsx | 105 ++++++++++-------- .../QuerySelectionPlaceholder.tsx | 10 +- .../components/popovers/InputPopover.tsx | 6 +- src/blocks/remote-data-container/edit.tsx | 12 +- .../hooks/useRemoteData.ts | 8 +- .../components/modals/InputModal.test.tsx | 2 + types/remote-data.d.ts | 3 + 13 files changed, 121 insertions(+), 64 deletions(-) diff --git a/inc/REST/RemoteDataController.php b/inc/REST/RemoteDataController.php index d093cf1f8..edca48025 100644 --- a/inc/REST/RemoteDataController.php +++ b/inc/REST/RemoteDataController.php @@ -48,6 +48,12 @@ public static function register_rest_routes(): void { return is_array( $value ); }, ], + 'query_group' => [ + 'required' => true, + 'sanitize_callback' => function ( $value ) { + return strval( $value ); + }, + ], ], ] ); } @@ -55,6 +61,7 @@ public static function register_rest_routes(): void { public static function execute_queries( WP_REST_Request $request ): array|WP_Error { $block_name = $request->get_param( 'block_name' ); $query_key = $request->get_param( 'query_key' ); + $query_group = $request->get_param( 'query_group' ); $query_inputs = $request->get_param( 'query_inputs' ); $block_config = ConfigStore::get_block_configuration( $block_name ); @@ -70,6 +77,7 @@ public static function execute_queries( WP_REST_Request $request ): array|WP_Err 'block_name' => $block_name, 'result_id' => wp_generate_uuid4(), 'query_key' => $query_key, + 'query_group' => $query_group, ], $query_response ); diff --git a/src/block-editor/format-types/field-shortcode/components/FieldShortcodeSelectNew.tsx b/src/block-editor/format-types/field-shortcode/components/FieldShortcodeSelectNew.tsx index 194dc3bee..25f2e7c6d 100644 --- a/src/block-editor/format-types/field-shortcode/components/FieldShortcodeSelectNew.tsx +++ b/src/block-editor/format-types/field-shortcode/components/FieldShortcodeSelectNew.tsx @@ -57,6 +57,8 @@ export function FieldShortcodeSelectNew( props: FieldShortcodeSelectNewProps ) { blockName={ blockConfig.name } headerImage={ compatibleSelector.image_url } onSelectField={ onSelectField } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + queryGroup={ compatibleSelector.query_group } queryKey={ compatibleSelector.query_key } renderTrigger={ ( { onClick } ) => ( diff --git a/src/block-editor/format-types/field-shortcode/components/FieldShortcodeSelection.tsx b/src/block-editor/format-types/field-shortcode/components/FieldShortcodeSelection.tsx index b4de5abb2..c4af659d6 100644 --- a/src/block-editor/format-types/field-shortcode/components/FieldShortcodeSelection.tsx +++ b/src/block-editor/format-types/field-shortcode/components/FieldShortcodeSelection.tsx @@ -125,6 +125,7 @@ export function FieldShortcodeSelectField( props: FieldShortcodeSelectFieldProps const { data, fetch, loading } = useRemoteData( { blockName: props.blockName, queryKey: DISPLAY_QUERY_KEY, + queryGroup: DISPLAY_QUERY_KEY, } ); useEffect( () => { diff --git a/src/blocks/remote-data-container/components/QueryComponent.tsx b/src/blocks/remote-data-container/components/QueryComponent.tsx index 6547a2780..acf28a26c 100644 --- a/src/blocks/remote-data-container/components/QueryComponent.tsx +++ b/src/blocks/remote-data-container/components/QueryComponent.tsx @@ -16,6 +16,7 @@ import { useRemoteData } from '@/blocks/remote-data-container/hooks/useRemoteDat import { hasRemoteDataChanged } from '@/utils/block-binding'; export interface QueryComponentProps { + queryGroup: string; queryKey: string; blockConfig: BlockConfig; blockName: string; @@ -28,6 +29,7 @@ export interface QueryComponentProps { export function QueryComponent( props: QueryComponentProps ) { const { + queryGroup, queryKey, blockConfig, blockName, @@ -45,6 +47,7 @@ export function QueryComponent( props: QueryComponentProps ) { blockName, externallyManagedRemoteData: remoteDataAttribute, externallyManagedUpdateRemoteData: updateRemoteData, + queryGroup, queryKey, } ); const [ showPatternSelection, setShowPatternSelection ] = useState< boolean >( false ); diff --git a/src/blocks/remote-data-container/components/modals/DataViewsModal.tsx b/src/blocks/remote-data-container/components/modals/DataViewsModal.tsx index 0afa55411..3e2b6bd57 100644 --- a/src/blocks/remote-data-container/components/modals/DataViewsModal.tsx +++ b/src/blocks/remote-data-container/components/modals/DataViewsModal.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { BaseControl, Button, Modal, __experimentalHStack as HStack } from '@wordpress/components'; import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; @@ -13,15 +14,25 @@ interface DataViewsModalProps { className?: string; blockName: string; headerImage?: string; - onSelect?: ( data: RemoteDataQueryInput[] ) => void; + onSelect?: ( key: string, data: RemoteDataQueryInput[] ) => void; onSelectField?: ( data: FieldSelection, fieldValue: string ) => void; queryKey: string; + queryGroup: string; renderTrigger?: ( props: { onClick: () => void } ) => React.ReactNode; title?: string; } export const DataViewsModal: React.FC< DataViewsModalProps > = props => { - const { className, blockName, onSelect, onSelectField, queryKey, renderTrigger, title } = props; + const { + className, + blockName, + onSelect, + onSelectField, + queryKey, + queryGroup, + renderTrigger, + title, + } = props; const blockConfig = getBlockConfig( blockName ); @@ -45,7 +56,7 @@ export const DataViewsModal: React.FC< DataViewsModalProps > = props => { supportsSearch, totalItems, totalPages, - } = useRemoteData( { blockName, fetchOnMount: true, queryKey } ); + } = useRemoteData( { blockName, fetchOnMount: true, queryGroup, queryKey } ); // For selection, DataViews transacts only in IDs, so we provide the UUID from // the API response as a synthetic ID and map them to the full result. @@ -77,7 +88,7 @@ export const DataViewsModal: React.FC< DataViewsModalProps > = props => { return; } - onSelect?.( createQueryInputsFromRemoteDataResults( results ) ); + onSelect?.( queryKey, createQueryInputsFromRemoteDataResults( results ) ); sendTracksEvent( 'add_block', { action: 'select_item', selected_option: 'search_from_list', diff --git a/src/blocks/remote-data-container/components/modals/InputModal.tsx b/src/blocks/remote-data-container/components/modals/InputModal.tsx index 87a0d911d..0ac91f7d7 100644 --- a/src/blocks/remote-data-container/components/modals/InputModal.tsx +++ b/src/blocks/remote-data-container/components/modals/InputModal.tsx @@ -16,8 +16,8 @@ interface InputModalProps { blockName: string; headerImage?: string; inputs: InputVariable[]; - onSelect: ( data: RemoteDataQueryInput[] ) => void; - queryKey?: string; + onSelect: ( key: string, data: RemoteDataQueryInput[] ) => void; + queryKey: string; title: string; } @@ -35,7 +35,7 @@ export function InputModal( props: InputModalProps ) { } function onSelectItem(): void { - props.onSelect( [ inputState ] ); + props.onSelect( props.queryKey, [ inputState ] ); close(); sendTracksEvent( 'add_block', { action: 'select_item', diff --git a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx index cea7378c7..def858385 100644 --- a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx +++ b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx @@ -8,18 +8,25 @@ import { DataViewsModal } from '@/blocks/remote-data-container/components/modals import { InputModal } from '@/blocks/remote-data-container/components/modals/InputModal'; import { InputPopover } from '@/blocks/remote-data-container/components/popovers/InputPopover'; +// Inline type for selector from BlockConfig +type Selector = { + image_url?: string; + inputs: InputVariable[]; + name: string; + query_key: string; + display_name?: string; + type: string; + query_group: string; +}; + interface ItemSelectQueryTypeProps { - blockConfig: BlockConfig; - queryGroup: string; - onSelect: ( data: RemoteDataQueryInput[] ) => void; + blockName: string; + selectors: Selector[]; + onSelect: ( key: string, data: RemoteDataQueryInput[] ) => void; } export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) { - const { - blockConfig: { name: blockName, selectors }, - queryGroup, - onSelect, - } = props; + const { blockName, selectors, onSelect } = props; return ( - { selectors - .filter( selector => selector.query_group === queryGroup ) - .map( selector => { - const title = selector.name; - const selectorProps = { - blockName, - headerImage: selector.image_url, - inputVariables: selector.inputs, - onSelect, - queryKey: selector.query_key, - title, - }; + { selectors.map( selector => { + const title = selector.name; + const selectorProps = { + blockName, + headerImage: selector.image_url, + inputVariables: selector.inputs, + onSelect, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + queryGroup: selector.query_group, + queryKey: selector.query_key, + title, + }; - switch ( selector.type ) { - case 'search': - case 'list': + switch ( selector.type ) { + case 'search': + case 'list': + return ( + + ); + case 'load-without-input': + return ( + + ); + case 'manual-input': + if ( selector.inputs.length === 1 && selector.inputs[ 0 ] ) { return ( - ); - case 'load-without-input': - return ( - - ); - case 'manual-input': - if ( selector.inputs.length === 1 && selector.inputs[ 0 ] ) { - return ( - - ); - } - return ; - } + } + return ; + } - return null; - } ) } + return null; + } ) } ); } diff --git a/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx b/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx index 622a328aa..64ff9fb0e 100644 --- a/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx +++ b/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx @@ -24,11 +24,12 @@ type Selector = { export interface QuerySelectionPlaceholderProps { blockConfig: BlockConfig; onQueryGroupSelect: ( group: string ) => void; + onQueryKeySelect: ( key: string ) => void; onQueryInputsSelect: ( inputs: RemoteDataQueryInput[] ) => void; } export function QuerySelectionPlaceholder( props: QuerySelectionPlaceholderProps ) { - const { blockConfig, onQueryGroupSelect, onQueryInputsSelect } = props; + const { blockConfig, onQueryGroupSelect, onQueryKeySelect, onQueryInputsSelect } = props; const { instructions, settings, selectors } = blockConfig; const iconElement: IconType = ( settings.icon as IconType ) ?? cloud; @@ -41,9 +42,10 @@ export function QuerySelectionPlaceholder( props: QuerySelectionPlaceholderProps const [ selectedGroup, setSelectedGroup ] = useState< string >( '' ); const [ showSelectors, setShowSelectors ] = useState< boolean >( false ); - function handleSelectorOnSelect( inputs: RemoteDataQueryInput[] ) { + function handleSelectorOnSelect( queryKey: string, inputs: RemoteDataQueryInput[] ) { setShowSelectors( false ); onQueryGroupSelect( selectedGroup ); + onQueryKeySelect( queryKey ); onQueryInputsSelect( inputs ); } @@ -84,8 +86,8 @@ export function QuerySelectionPlaceholder( props: QuerySelectionPlaceholderProps ) } { showSelectors && ( selector.query_group === selectedGroup ) } onSelect={ handleSelectorOnSelect } /> ) } diff --git a/src/blocks/remote-data-container/components/popovers/InputPopover.tsx b/src/blocks/remote-data-container/components/popovers/InputPopover.tsx index cd8eadc72..807438660 100644 --- a/src/blocks/remote-data-container/components/popovers/InputPopover.tsx +++ b/src/blocks/remote-data-container/components/popovers/InputPopover.tsx @@ -18,13 +18,13 @@ interface InputPopoverProps { blockName: string; headerImage?: string; input: InputVariable; - onSelect: ( data: RemoteDataQueryInput[] ) => void; + onSelect: ( key: string, data: RemoteDataQueryInput[] ) => void; title: string; queryKey: string; } export function InputPopover( props: InputPopoverProps ) { - const { input, onSelect, title } = props; + const { input, onSelect, title, queryKey } = props; const dataSourceType = getBlockDataSourceType( props.blockName ); @@ -38,7 +38,7 @@ export function InputPopover( props: InputPopoverProps ) { } function onSelectItem(): void { - onSelect( [ inputState ] ); + onSelect( queryKey, [ inputState ] ); close(); sendTracksEvent( 'add_block', { action: 'select_item', diff --git a/src/blocks/remote-data-container/edit.tsx b/src/blocks/remote-data-container/edit.tsx index 3950eadf4..7c9af6eba 100644 --- a/src/blocks/remote-data-container/edit.tsx +++ b/src/blocks/remote-data-container/edit.tsx @@ -18,10 +18,14 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ) { const rootClientId = props.clientId; const remoteDataAttribute = migrateRemoteData( props.attributes.remoteData ); - const [ queryGroup, setQueryGroup ] = useState< string >( remoteDataAttribute?.queryKey ?? '' ); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const [ queryGroup, setQueryGroup ] = useState< string >( remoteDataAttribute?.queryGroup || '' ); + + const [ queryKey, setQueryKey ] = useState< string >( remoteDataAttribute?.queryKey || '' ); const [ queryInputs, setQueryInputs ] = useState< RemoteDataQueryInput[] >( - remoteDataAttribute?.queryInputs ?? [] + remoteDataAttribute?.queryInputs || [] ); function setAttributes( attributes: RemoteDataBlockAttributes ): void { @@ -32,6 +36,7 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ) { return ( @@ -43,7 +48,8 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ) { void; + queryGroup: string; queryKey: string; } @@ -91,6 +93,7 @@ export function useRemoteData( { initialSearchInput, onSuccess, queryKey, + queryGroup, }: UseRemoteDataInput ): UseRemoteData { const [ data, setData ] = useState< RemoteData >(); const [ error, setError ] = useState< Error >(); @@ -102,7 +105,9 @@ export function useRemoteData( { const blockConfig = getBlockConfig( blockName ); - const query = blockConfig?.selectors?.find( selector => selector.query_key === queryKey ); + const query = blockConfig?.selectors?.find( + selector => selector.query_key === queryKey && selector.query_group === queryGroup + ); if ( ! query ) { // Here we intentionally throw an error instead of calling setError, because @@ -197,6 +202,7 @@ export function useRemoteData( { const requestData: RemoteDataApiRequest = { block_name: blockName, + query_group: queryGroup, query_key: queryKey, query_inputs: inputs, }; diff --git a/tests/src/blocks/remote-data-container/components/modals/InputModal.test.tsx b/tests/src/blocks/remote-data-container/components/modals/InputModal.test.tsx index 28678d277..acb1d585c 100644 --- a/tests/src/blocks/remote-data-container/components/modals/InputModal.test.tsx +++ b/tests/src/blocks/remote-data-container/components/modals/InputModal.test.tsx @@ -17,7 +17,9 @@ describe( 'InputModal', () => { { slug: 'input2', name: 'Input 2', required: false, type: 'text' }, ] as InputVariable[], onSelect: mockOnSelect, + onKeySelect: vi.fn(), title: 'Test Modal', + queryKey: 'test-key', }; afterEach( cleanup ); diff --git a/types/remote-data.d.ts b/types/remote-data.d.ts index 9f07233d8..6ed8c07d7 100644 --- a/types/remote-data.d.ts +++ b/types/remote-data.d.ts @@ -36,6 +36,7 @@ interface RemoteData { queryInput?: RemoteDataQueryInput; queryInputs: RemoteDataQueryInput[]; queryKey?: string; + queryGroup?: string; resultId: string; results: RemoteDataApiResult[]; } @@ -92,6 +93,7 @@ interface RemoteDataInnerBlockAttributes { interface RemoteDataApiRequest { block_name: string; + query_group: string; query_inputs: RemoteDataQueryInput[]; query_key: string; } @@ -105,6 +107,7 @@ interface RemoteDataApiResponseBody { block_name: string; metadata: Record< string, RemoteDataResultFields >; pagination?: RemoteDataPagination; + query_group: string; query_inputs: RemoteDataQueryInput[]; query_key: string; result_id: string; From 5cbf9d98b1b1264efb1d3a698479f68087b34c43 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Fri, 16 May 2025 14:24:09 +1000 Subject: [PATCH 19/74] Fix the query input schema for a non-display query being wrong --- inc/Editor/BlockManagement/ConfigRegistry.php | 9 +++++++-- src/blocks/remote-data-container/hooks/useRemoteData.ts | 2 -- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index 0ee446280..592127fb1 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -59,7 +59,10 @@ public static function register_block( array $user_config = [] ): bool|WP_Error // ToDo: Add a validation step to check if the required query is present in the user_config['queries'] array. if ( $query->get_required_query() && ! empty( $query->get_required_query() ) ) { // Add the mapping of the required query to the required queries array. - $required_queries[ $query->get_required_query() ] = $query_key; + $required_queries[ $query->get_required_query() ] = [ + 'query_key' => $query_key, + 'query' => $query, + ]; } } @@ -70,6 +73,8 @@ public static function register_block( array $user_config = [] ): bool|WP_Error $output_schema = $query->get_output_schema(); if ( isset( $required_queries[ $query_key ] ) ) { + $input_schema = $required_queries[ $query_key ]['query']->get_input_schema(); + array_unshift( $selectors, [ @@ -79,7 +84,7 @@ public static function register_block( array $user_config = [] ): bool|WP_Error 'name' => ucfirst( $query_key ), 'query_key' => $query_key, 'type' => $query->get_type(), - 'query_group' => $required_queries[ $query_key ], + 'query_group' => $required_queries[ $query_key ]['query_key'], ] ); } else { diff --git a/src/blocks/remote-data-container/hooks/useRemoteData.ts b/src/blocks/remote-data-container/hooks/useRemoteData.ts index 29f647faa..1421983e7 100644 --- a/src/blocks/remote-data-container/hooks/useRemoteData.ts +++ b/src/blocks/remote-data-container/hooks/useRemoteData.ts @@ -198,8 +198,6 @@ export function useRemoteData( { inputs[ 0 ] = { ...inputs[ 0 ], ...managedQueryInput }; } - console.log( 'Query key being used for fetch', queryKey ); - const requestData: RemoteDataApiRequest = { block_name: blockName, query_group: queryGroup, From 4228e4364a4771c8127900085a69d44365a2b459 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Fri, 16 May 2025 14:49:35 +1000 Subject: [PATCH 20/74] Fix more bugs --- .../components/QueryComponent.tsx | 22 ++++++++++++++----- .../hooks/useRemoteDataContext.ts | 1 + 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/blocks/remote-data-container/components/QueryComponent.tsx b/src/blocks/remote-data-container/components/QueryComponent.tsx index acf28a26c..6a36f380e 100644 --- a/src/blocks/remote-data-container/components/QueryComponent.tsx +++ b/src/blocks/remote-data-container/components/QueryComponent.tsx @@ -4,6 +4,7 @@ import { InspectorControls, useBlockProps, } from '@wordpress/block-editor'; +import { Spinner } from '@wordpress/components'; import { useEffect, useState } from '@wordpress/element'; import { DataPanel } from './panels/DataPanel'; @@ -37,13 +38,12 @@ export function QueryComponent( props: QueryComponentProps ) { remoteDataAttribute, setAttributes, queryInputs, - onQueryInputsChange, } = props; const blockProps = useBlockProps( { className: CONTAINER_CLASS_NAME } ); const { getSupportedPatterns, innerBlocksPattern, insertPatternBlocks, resetInnerBlocks } = usePatterns( blockName, rootClientId ); - const { data, fetch, reset, supportsPagination } = useRemoteData( { + const { data, fetch, reset, supportsPagination, loading } = useRemoteData( { blockName, externallyManagedRemoteData: remoteDataAttribute, externallyManagedUpdateRemoteData: updateRemoteData, @@ -87,7 +87,7 @@ export function QueryComponent( props: QueryComponentProps ) { } function onSelectPattern( pattern: BlockPattern ): void { - insertPatternBlocks( pattern, true ); + insertPatternBlocks( pattern, supportsPagination ); setShowPatternSelection( false ); } @@ -107,7 +107,7 @@ export function QueryComponent( props: QueryComponentProps ) { queryInputs: inputs, queryKey: newQueryKey, } ); - onQueryInputsChange?.( inputs ); + refreshRemoteData(); } if ( showPatternSelection ) { @@ -142,11 +142,23 @@ export function QueryComponent( props: QueryComponentProps ) { selectors.query_group === queryGroup + ) } /> ) }
+ { loading && ( +
+ +
+ ) }
diff --git a/src/blocks/remote-data-container/hooks/useRemoteDataContext.ts b/src/blocks/remote-data-container/hooks/useRemoteDataContext.ts index 21be1821e..6f46b49dc 100644 --- a/src/blocks/remote-data-container/hooks/useRemoteDataContext.ts +++ b/src/blocks/remote-data-container/hooks/useRemoteDataContext.ts @@ -26,6 +26,7 @@ export function useRemoteDataContext( context: Record< string, unknown > ): Remo blockName: remoteDataBlockName, metadata: {}, queryInputs: [], + queryGroup: 'Example Query Group', queryKey: 'Example Query Key', resultId: '', results: [ From df8e1b661c7b9dee9d41c0826cd312b3424b44ad Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Fri, 16 May 2025 15:40:51 +1000 Subject: [PATCH 21/74] Another bug fix and some debuggers --- src/blocks/remote-data-container/components/QueryComponent.tsx | 3 ++- src/blocks/remote-data-container/hooks/useRemoteData.ts | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/blocks/remote-data-container/components/QueryComponent.tsx b/src/blocks/remote-data-container/components/QueryComponent.tsx index 6a36f380e..455d33241 100644 --- a/src/blocks/remote-data-container/components/QueryComponent.tsx +++ b/src/blocks/remote-data-container/components/QueryComponent.tsx @@ -47,8 +47,9 @@ export function QueryComponent( props: QueryComponentProps ) { blockName, externallyManagedRemoteData: remoteDataAttribute, externallyManagedUpdateRemoteData: updateRemoteData, + // This is done on purpose as we want to execute the query with the same group as the query key, aka the display query. queryGroup, - queryKey, + queryKey: queryGroup, } ); const [ showPatternSelection, setShowPatternSelection ] = useState< boolean >( false ); diff --git a/src/blocks/remote-data-container/hooks/useRemoteData.ts b/src/blocks/remote-data-container/hooks/useRemoteData.ts index 1421983e7..20ff7208c 100644 --- a/src/blocks/remote-data-container/hooks/useRemoteData.ts +++ b/src/blocks/remote-data-container/hooks/useRemoteData.ts @@ -23,6 +23,8 @@ async function unmemoizedfetchRemoteData( data: requestData, } ); + debugger; + if ( ! body ) { return null; } @@ -31,6 +33,7 @@ async function unmemoizedfetchRemoteData( blockName: body.block_name, metadata: body.metadata, pagination: body.pagination, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment queryGroup: body.query_group, queryKey: body.query_key, queryInputs: body.query_inputs, From d13b99171dd233e463d7e6e13e6e361e3c77a9db Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Fri, 16 May 2025 15:59:22 +1000 Subject: [PATCH 22/74] Port the missing caching fix --- inc/ExampleApi/ExampleApi.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/inc/ExampleApi/ExampleApi.php b/inc/ExampleApi/ExampleApi.php index 42be87ae3..150a0b47a 100644 --- a/inc/ExampleApi/ExampleApi.php +++ b/inc/ExampleApi/ExampleApi.php @@ -47,6 +47,11 @@ public static function register_remote_data_block(): void { $get_record_query = HttpQuery::from_array( [ 'data_source' => $data_source, + 'endpoint' => function ( array $input_variables ) use ( $data_source ): string { + // This is not a real API, but we want to make sure to generate + // valid cache keys that do not collide with other queries. + return sprintf( '%s/%s', $data_source->get_endpoint(), $input_variables['record_id'] ?? '' ); + }, 'input_schema' => [ 'record_id' => [ 'name' => 'Record ID', From c1b79c86bc5c72e90a06ea082ffc530baddc213e Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Mon, 19 May 2025 09:11:50 +1000 Subject: [PATCH 23/74] Adding ToDos for pending work for this PR --- .../remote-data-container/components/QueryComponent.tsx | 8 ++++++++ src/blocks/remote-data-container/edit.tsx | 5 +++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/blocks/remote-data-container/components/QueryComponent.tsx b/src/blocks/remote-data-container/components/QueryComponent.tsx index 455d33241..59d11cf66 100644 --- a/src/blocks/remote-data-container/components/QueryComponent.tsx +++ b/src/blocks/remote-data-container/components/QueryComponent.tsx @@ -1,10 +1,12 @@ import { + BlockEditorStoreSelectors, BlockPattern, InnerBlocks, InspectorControls, useBlockProps, } from '@wordpress/block-editor'; import { Spinner } from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; import { useEffect, useState } from '@wordpress/element'; import { DataPanel } from './panels/DataPanel'; @@ -31,6 +33,8 @@ export interface QueryComponentProps { export function QueryComponent( props: QueryComponentProps ) { const { queryGroup, + // ToDo: See if this is actually needed. + // eslint-disable-next-line @typescript-eslint/no-unused-vars queryKey, blockConfig, blockName, @@ -51,6 +55,9 @@ export function QueryComponent( props: QueryComponentProps ) { queryGroup, queryKey: queryGroup, } ); + + // ToDo: Fix this. + // const { hasMultiSelection } = useSelect< BlockEditorStoreSelectors >( blockEditorStore ); const [ showPatternSelection, setShowPatternSelection ] = useState< boolean >( false ); useEffect( () => { @@ -82,6 +89,7 @@ export function QueryComponent( props: QueryComponentProps ) { setShowPatternSelection( false ); } + // ToDo: This doesn't work. function resetRemoteData(): void { reset(); resetPatternSelection(); diff --git a/src/blocks/remote-data-container/edit.tsx b/src/blocks/remote-data-container/edit.tsx index 725cc2a09..9077b341f 100644 --- a/src/blocks/remote-data-container/edit.tsx +++ b/src/blocks/remote-data-container/edit.tsx @@ -19,10 +19,11 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ): JSX. const rootClientId = props.clientId; const remoteDataAttribute = migrateRemoteData( props.attributes.remoteData ); + // ToDo: Fix this. // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const [ queryGroup, setQueryGroup ] = useState< string >( remoteDataAttribute?.queryGroup || '' ); + const [ queryGroup, setQueryGroup ] = useState< string >( remoteDataAttribute?.queryGroup ?? '' ); - const [ queryKey, setQueryKey ] = useState< string >( remoteDataAttribute?.queryKey || '' ); + const [ queryKey, setQueryKey ] = useState< string >( remoteDataAttribute?.queryKey ?? '' ); const [ queryInputs, setQueryInputs ] = useState< RemoteDataQueryInput[] >( remoteDataAttribute?.queryInputs || [] From 0d16b944f69bb29cfe89a4bd1babbaca0d6fdffb Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Mon, 19 May 2025 11:25:23 +1000 Subject: [PATCH 24/74] Fix the reset block function and remove the unnecessary code --- .../components/QueryComponent.tsx | 8 +++----- .../components/placeholders/ItemSelectQueryType.tsx | 1 + .../placeholders/QuerySelectionPlaceholder.tsx | 4 +--- src/blocks/remote-data-container/edit.tsx | 10 ++++++---- .../remote-data-container/hooks/useRemoteData.ts | 1 + 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/blocks/remote-data-container/components/QueryComponent.tsx b/src/blocks/remote-data-container/components/QueryComponent.tsx index 59d11cf66..52a01b734 100644 --- a/src/blocks/remote-data-container/components/QueryComponent.tsx +++ b/src/blocks/remote-data-container/components/QueryComponent.tsx @@ -20,7 +20,6 @@ import { hasRemoteDataChanged } from '@/utils/block-binding'; export interface QueryComponentProps { queryGroup: string; - queryKey: string; blockConfig: BlockConfig; blockName: string; rootClientId: string; @@ -28,20 +27,19 @@ export interface QueryComponentProps { setAttributes: ( attributes: RemoteDataBlockAttributes ) => void; queryInputs: RemoteDataQueryInput[]; onQueryInputsChange?: ( inputs: RemoteDataQueryInput[] ) => void; + resetQuery: () => void; } export function QueryComponent( props: QueryComponentProps ) { const { queryGroup, - // ToDo: See if this is actually needed. - // eslint-disable-next-line @typescript-eslint/no-unused-vars - queryKey, blockConfig, blockName, rootClientId, remoteDataAttribute, setAttributes, queryInputs, + resetQuery, } = props; const blockProps = useBlockProps( { className: CONTAINER_CLASS_NAME } ); @@ -89,10 +87,10 @@ export function QueryComponent( props: QueryComponentProps ) { setShowPatternSelection( false ); } - // ToDo: This doesn't work. function resetRemoteData(): void { reset(); resetPatternSelection(); + resetQuery(); } function onSelectPattern( pattern: BlockPattern ): void { diff --git a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx index def858385..95cc8baa2 100644 --- a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx +++ b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx @@ -42,6 +42,7 @@ export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) { headerImage: selector.image_url, inputVariables: selector.inputs, onSelect, + // ToDo: Fix this. // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment queryGroup: selector.query_group, queryKey: selector.query_key, diff --git a/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx b/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx index 64ff9fb0e..1b79af444 100644 --- a/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx +++ b/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx @@ -24,12 +24,11 @@ type Selector = { export interface QuerySelectionPlaceholderProps { blockConfig: BlockConfig; onQueryGroupSelect: ( group: string ) => void; - onQueryKeySelect: ( key: string ) => void; onQueryInputsSelect: ( inputs: RemoteDataQueryInput[] ) => void; } export function QuerySelectionPlaceholder( props: QuerySelectionPlaceholderProps ) { - const { blockConfig, onQueryGroupSelect, onQueryKeySelect, onQueryInputsSelect } = props; + const { blockConfig, onQueryGroupSelect, onQueryInputsSelect } = props; const { instructions, settings, selectors } = blockConfig; const iconElement: IconType = ( settings.icon as IconType ) ?? cloud; @@ -45,7 +44,6 @@ export function QuerySelectionPlaceholder( props: QuerySelectionPlaceholderProps function handleSelectorOnSelect( queryKey: string, inputs: RemoteDataQueryInput[] ) { setShowSelectors( false ); onQueryGroupSelect( selectedGroup ); - onQueryKeySelect( queryKey ); onQueryInputsSelect( inputs ); } diff --git a/src/blocks/remote-data-container/edit.tsx b/src/blocks/remote-data-container/edit.tsx index 9077b341f..e741daeb4 100644 --- a/src/blocks/remote-data-container/edit.tsx +++ b/src/blocks/remote-data-container/edit.tsx @@ -23,8 +23,6 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ): JSX. // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const [ queryGroup, setQueryGroup ] = useState< string >( remoteDataAttribute?.queryGroup ?? '' ); - const [ queryKey, setQueryKey ] = useState< string >( remoteDataAttribute?.queryKey ?? '' ); - const [ queryInputs, setQueryInputs ] = useState< RemoteDataQueryInput[] >( remoteDataAttribute?.queryInputs || [] ); @@ -33,11 +31,15 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ): JSX. props.setAttributes( attributes ); } + function resetQuery(): void { + setQueryGroup( '' ); + setQueryInputs( [] ); + } + if ( ! queryGroup ) { return ( @@ -50,11 +52,11 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ): JSX. blockConfig={ blockConfig } blockName={ blockName } queryGroup={ queryGroup } - queryKey={ queryKey } queryInputs={ queryInputs } setAttributes={ setAttributes } rootClientId={ rootClientId } remoteDataAttribute={ remoteDataAttribute } + resetQuery={ resetQuery } /> ); diff --git a/src/blocks/remote-data-container/hooks/useRemoteData.ts b/src/blocks/remote-data-container/hooks/useRemoteData.ts index 89a6457a9..2ebbaf53f 100644 --- a/src/blocks/remote-data-container/hooks/useRemoteData.ts +++ b/src/blocks/remote-data-container/hooks/useRemoteData.ts @@ -25,6 +25,7 @@ async function unmemoizedfetchRemoteData( blockName: body.block_name, metadata: body.metadata, pagination: body.pagination, + // ToDo: Fix this. // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment queryGroup: body.query_group, queryKey: body.query_key, From eac246fd40c9ca2463777c4e82b3c9abedf7a466 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Mon, 19 May 2025 15:11:33 +1000 Subject: [PATCH 25/74] Fix shortcode selection --- .../components/InlineBindingSelection.tsx | 14 +++++++------- .../components/QueryComponent.tsx | 2 -- .../components/modals/DataViewsModal.tsx | 4 ++-- .../components/modals/InputModal.tsx | 5 ++--- .../components/panels/DataPanel.tsx | 14 ++++++++------ .../components/panels/QueryInputsPanel.tsx | 10 +++++++--- .../placeholders/ItemSelectQueryType.tsx | 4 ++-- .../placeholders/QuerySelectionPlaceholder.tsx | 2 +- .../components/popovers/InputPopover.tsx | 7 +++---- .../remote-data-container/config/constants.ts | 1 - src/blocks/remote-data-container/edit.tsx | 1 + src/utils/localized-block-data.ts | 14 ++++++++++++++ 12 files changed, 47 insertions(+), 31 deletions(-) diff --git a/src/block-editor/format-types/inline-binding/components/InlineBindingSelection.tsx b/src/block-editor/format-types/inline-binding/components/InlineBindingSelection.tsx index b35abc19a..7c3504726 100644 --- a/src/block-editor/format-types/inline-binding/components/InlineBindingSelection.tsx +++ b/src/block-editor/format-types/inline-binding/components/InlineBindingSelection.tsx @@ -2,12 +2,9 @@ import { BaseControl, Icon, MenuItem, Spinner } from '@wordpress/components'; import { useEffect } from '@wordpress/element'; import { check } from '@wordpress/icons'; -import { - DISPLAY_QUERY_KEY, - TEXT_FIELD_TYPES, -} from '@/blocks/remote-data-container/config/constants'; +import { TEXT_FIELD_TYPES } from '@/blocks/remote-data-container/config/constants'; import { useRemoteData } from '@/blocks/remote-data-container/hooks/useRemoteData'; -import { getBlockAvailableBindings } from '@/utils/localized-block-data'; +import { getBlockAvailableBindings, getDisplayQueryGroup } from '@/utils/localized-block-data'; import { getRemoteDataResultValue } from '@/utils/remote-data'; interface FieldSelectionProps { @@ -122,10 +119,13 @@ interface InlineBindingSelectFieldProps { } export function InlineBindingSelectField( props: InlineBindingSelectFieldProps ) { + // ToDo: This is getting the first compatible selector under a block, just like the field selection. + const queryGroup = getDisplayQueryGroup( props.blockName ); + const { data, fetch, loading } = useRemoteData( { blockName: props.blockName, - queryKey: DISPLAY_QUERY_KEY, - queryGroup: DISPLAY_QUERY_KEY, + queryKey: queryGroup, + queryGroup, } ); useEffect( () => { diff --git a/src/blocks/remote-data-container/components/QueryComponent.tsx b/src/blocks/remote-data-container/components/QueryComponent.tsx index 52a01b734..bf1f52068 100644 --- a/src/blocks/remote-data-container/components/QueryComponent.tsx +++ b/src/blocks/remote-data-container/components/QueryComponent.tsx @@ -1,12 +1,10 @@ import { - BlockEditorStoreSelectors, BlockPattern, InnerBlocks, InspectorControls, useBlockProps, } from '@wordpress/block-editor'; import { Spinner } from '@wordpress/components'; -import { useSelect } from '@wordpress/data'; import { useEffect, useState } from '@wordpress/element'; import { DataPanel } from './panels/DataPanel'; diff --git a/src/blocks/remote-data-container/components/modals/DataViewsModal.tsx b/src/blocks/remote-data-container/components/modals/DataViewsModal.tsx index 3152d0202..f541c0335 100644 --- a/src/blocks/remote-data-container/components/modals/DataViewsModal.tsx +++ b/src/blocks/remote-data-container/components/modals/DataViewsModal.tsx @@ -14,7 +14,7 @@ interface DataViewsModalProps { className?: string; blockName: string; headerImage?: string; - onSelect?: ( key: string, data: RemoteDataQueryInput[] ) => void; + onSelect?: ( data: RemoteDataQueryInput[] ) => void; onSelectField?: ( data: FieldSelection, fieldValue: string ) => void; queryKey: string; queryGroup: string; @@ -92,7 +92,7 @@ export const DataViewsModal: React.FC< DataViewsModalProps > = props => { return; } - onSelect?.( queryKey, createQueryInputsFromRemoteDataResults( results ) ); + onSelect?.( createQueryInputsFromRemoteDataResults( results ) ); sendTracksEvent( 'add_block', { action: 'select_item', selected_option: 'search_from_list', diff --git a/src/blocks/remote-data-container/components/modals/InputModal.tsx b/src/blocks/remote-data-container/components/modals/InputModal.tsx index 0ac91f7d7..804c08cc2 100644 --- a/src/blocks/remote-data-container/components/modals/InputModal.tsx +++ b/src/blocks/remote-data-container/components/modals/InputModal.tsx @@ -16,8 +16,7 @@ interface InputModalProps { blockName: string; headerImage?: string; inputs: InputVariable[]; - onSelect: ( key: string, data: RemoteDataQueryInput[] ) => void; - queryKey: string; + onSelect: ( data: RemoteDataQueryInput[] ) => void; title: string; } @@ -35,7 +34,7 @@ export function InputModal( props: InputModalProps ) { } function onSelectItem(): void { - props.onSelect( props.queryKey, [ inputState ] ); + props.onSelect( [ inputState ] ); close(); sendTracksEvent( 'add_block', { action: 'select_item', diff --git a/src/blocks/remote-data-container/components/panels/DataPanel.tsx b/src/blocks/remote-data-container/components/panels/DataPanel.tsx index f812b1fe0..657c0eaeb 100644 --- a/src/blocks/remote-data-container/components/panels/DataPanel.tsx +++ b/src/blocks/remote-data-container/components/panels/DataPanel.tsx @@ -1,7 +1,7 @@ import { __experimentalConfirmDialog as ConfirmDialog, Button, - ButtonGroup, + __experimentalToggleGroupControl as ToggleGroupControl, PanelBody, } from '@wordpress/components'; import { useState } from '@wordpress/element'; @@ -44,7 +44,12 @@ export function DataPanel( props: DataPanelProps ) { return ( - + @@ -77,7 +77,7 @@ export function QuerySelectionPlaceholder( props: QuerySelectionPlaceholderProps { showSelectors && ( ) } diff --git a/src/blocks/remote-data-container/edit.tsx b/src/blocks/remote-data-container/edit.tsx index aaa5c6480..64fb73b27 100644 --- a/src/blocks/remote-data-container/edit.tsx +++ b/src/blocks/remote-data-container/edit.tsx @@ -4,7 +4,7 @@ import { useState } from '@wordpress/element'; import { EditErrorBoundary } from './components/EditErrorBoundary'; import { QueryComponent } from './components/QueryComponent'; import { QuerySelectionPlaceholder } from '@/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder'; -import { getBlockConfig, getDisplayKeyFromQueryKey } from '@/utils/localized-block-data'; +import { getBlockConfig, getDisplayQueryKeyFromQueryKey } from '@/utils/localized-block-data'; import { migrateRemoteData } from '@/utils/remote-data'; import './editor.scss'; @@ -20,14 +20,10 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ): JSX. const rootClientId = props.clientId; const remoteDataAttribute = migrateRemoteData( props.attributes.remoteData ); - const displayQueryKey = getDisplayKeyFromQueryKey( - blockName, - remoteDataAttribute?.queryKey ?? '' + const [ displayQueryKey, setDisplayQueryKey ] = useState< string >( + getDisplayQueryKeyFromQueryKey( blockName, remoteDataAttribute?.queryKey ?? '' ) ); - // ToDo: Rename this, it's been left as is to avoid a massive refactor for prototyping a new idea. - const [ queryGroup, setQueryGroup ] = useState< string >( displayQueryKey ); - const [ queryInputs, setQueryInputs ] = useState< RemoteDataQueryInput[] >( remoteDataAttribute?.queryInputs ?? [] ); @@ -37,15 +33,15 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ): JSX. } function resetQuery(): void { - setQueryGroup( '' ); + setDisplayQueryKey( '' ); setQueryInputs( [] ); } - if ( ! queryGroup ) { + if ( ! displayQueryKey ) { return ( ); @@ -57,7 +53,7 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ): JSX. ( blockEditorStore ); @@ -93,7 +93,7 @@ export function usePatterns( pattern => ( pattern?.blockTypes?.includes( remoteDataBlockName ) || pattern.blocks.some( block => hasBlockBinding( block, remoteDataBlockName ) ) ) && - ( ! pattern.keywords || pattern.keywords.includes( queryGroup ) ) + ( ! pattern.keywords || pattern.keywords.includes( displayQueryKey ) ) ); // If no result is provided, return the supported patterns as is. diff --git a/src/utils/localized-block-data.ts b/src/utils/localized-block-data.ts index 9c2b4492a..d4ddbea81 100644 --- a/src/utils/localized-block-data.ts +++ b/src/utils/localized-block-data.ts @@ -9,7 +9,7 @@ export function getBlockConfig( blockName: string ): BlockConfig | undefined { return window.REMOTE_DATA_BLOCKS?.config?.[ blockName ]; } -export function getDisplayKeyFromQueryKey( blockName: string, queryKey: string ): string { +export function getDisplayQueryKeyFromQueryKey( blockName: string, queryKey: string ): string { const displayQueriesToSelectors = getBlockConfig( blockName )?.displayQueriesToSelectors ?? {}; // iterate over the keys of the display_queries_to_selectors object. diff --git a/tests/src/block-editor/filters/withBlockBinding.test.tsx b/tests/src/block-editor/filters/withBlockBinding.test.tsx index c9f70490c..392fed403 100644 --- a/tests/src/block-editor/filters/withBlockBinding.test.tsx +++ b/tests/src/block-editor/filters/withBlockBinding.test.tsx @@ -103,7 +103,6 @@ describe( 'withBlockBinding', () => { blockName: 'test/block', results: createResults( [ { field1: 'value1' } ] ), queryKey: 'key', - queryGroup: 'key', }; render( @@ -128,7 +127,6 @@ describe( 'withBlockBinding', () => { blockName: 'test/block', results: createResults( [ { field1: 'value1' } ] ), queryKey: 'key', - queryGroup: 'key', }; render( { blockName: 'test/block', results: createResults( [ { field1: 'value1' } ] ), queryKey: 'key', - queryGroup: 'key', }; render( { blockName: 'test/block', results: createResults( [ { title: 'New Title' } ] ), queryKey: 'key', - queryGroup: 'key', }, }, name: 'test/block', @@ -239,7 +235,6 @@ describe( 'withBlockBinding', () => { blockName: 'test/block', results: createResults( [ { title: 'Matching Title' } ] ), queryKey: 'key', - queryGroup: 'key', }, }, name: 'test/block', @@ -276,7 +271,6 @@ describe( 'withBlockBinding', () => { blockName: 'test/block', results: createResults( [ { title: 'New Title' } ] ), queryKey: 'key', - queryGroup: 'key', }, }, name: 'test/block', diff --git a/tests/src/utils/remote-data.test.ts b/tests/src/utils/remote-data.test.ts index 52bd3db94..d3a22b8b7 100644 --- a/tests/src/utils/remote-data.test.ts +++ b/tests/src/utils/remote-data.test.ts @@ -130,7 +130,6 @@ describe( 'remote-data utils', () => { expect( migrated ).toEqual( { queryKey: 'display', - queryGroup: 'display', queryInputs: [ { title: 'Title 1' } ], results: [ { diff --git a/types/localized-block-data.d.ts b/types/localized-block-data.d.ts index 750feca9f..dfb2989bf 100644 --- a/types/localized-block-data.d.ts +++ b/types/localized-block-data.d.ts @@ -23,7 +23,6 @@ interface Selector { inputs: InputVariable[]; name: string; query_key: string; - display_name?: string; type: string; } @@ -39,9 +38,9 @@ interface BlockConfig { dataSourceType: string; instructions?: string; name: string; - patterns: Record; + patterns: Record< string, string >; selectors: Selector[]; - displayQueriesToSelectors: Record; + displayQueriesToSelectors: Record< string, string[] >; settings: { category: string; description?: string; diff --git a/types/remote-data.d.ts b/types/remote-data.d.ts index 4cd37f1be..8364b6ca1 100644 --- a/types/remote-data.d.ts +++ b/types/remote-data.d.ts @@ -54,10 +54,7 @@ interface RemoteDataTemplateBlockAttributes {} interface FieldSelection { action: 'add_field_shortcode' | 'update_field_shortcode' | 'reset_field_shortcode'; - remoteData?: Pick< - RemoteData, - 'blockName' | 'metadata' | 'queryInputs' | 'queryKey' - >; + remoteData?: Pick< RemoteData, 'blockName' | 'metadata' | 'queryInputs' | 'queryKey' >; selectedField: string; selectionPath: 'select_new_tab' | 'select_existing_tab' | 'select_meta_tab' | 'popover'; type: 'field' | 'meta'; From bd65475c31f3430fad4b1b5b194cc71d3e2b881c Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Thu, 29 May 2025 16:08:37 +1000 Subject: [PATCH 48/74] Add guards from the tests --- inc/Editor/BlockManagement/ConfigRegistry.php | 5 +++++ inc/Editor/BlockManagement/ConfigStore.php | 2 +- .../components/InlineBindingSelectFieldPopover.tsx | 9 ++++++--- .../inline-binding/components/InlineBindingSelection.tsx | 3 +-- tests/inc/Editor/ConfigStoreTest.php | 3 +++ 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index 29b8f9a15..7620fffb9 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -137,6 +137,11 @@ public static function register_block( array $block_config = [] ): bool|WP_Error $display_query = self::inflate_query( $block_config[ self::QUERIES_KEY ][ $display_query_key ] ); $display_query_input_schema = $display_query->get_input_schema(); + // If the type is not set, or is not an array, skip. + if ( ! is_array( $output_schema['type'] ) || empty( $output_schema['type'] ) ) { + continue; + } + // Check if the query's output schema intersects with the display query's input schema. $intersecting_keys = array_intersect_key( $output_schema['type'], $display_query_input_schema ); diff --git a/inc/Editor/BlockManagement/ConfigStore.php b/inc/Editor/BlockManagement/ConfigStore.php index 922139368..34bf9dd7d 100644 --- a/inc/Editor/BlockManagement/ConfigStore.php +++ b/inc/Editor/BlockManagement/ConfigStore.php @@ -77,7 +77,7 @@ public static function get_data_source_type( string $block_name ): ?string { return null; } - $display_queries_to_selectors = $config['display_queries_to_selectors']; + $display_queries_to_selectors = $config['display_queries_to_selectors'] ?? []; if ( empty( $display_queries_to_selectors ) ) { return null; } diff --git a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectFieldPopover.tsx b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectFieldPopover.tsx index 705b2567d..04d476621 100644 --- a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectFieldPopover.tsx +++ b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectFieldPopover.tsx @@ -35,8 +35,12 @@ export function InlineBindingSelectFieldPopover( props: InlineBindingSelectField selector => [ 'list', 'search' ].includes( selector.type ) ); - const queryKey = remoteData?.queryKey ?? compatibleSelector?.query_key ?? ''; - const displayQueryKey = getDisplayQueryKeyFromQueryKey( remoteData?.blockName ?? '', queryKey ); + const queryKey: string = + remoteData?.queryKey ?? + getDisplayQueryKeyFromQueryKey( + remoteData?.blockName ?? '', + compatibleSelector?.query_key ?? '' + ); return ( props.onSelectField( { ...data, action: 'update_field_shortcode' }, fieldValue ) diff --git a/src/block-editor/format-types/inline-binding/components/InlineBindingSelection.tsx b/src/block-editor/format-types/inline-binding/components/InlineBindingSelection.tsx index 2cdc9731d..567dff3b6 100644 --- a/src/block-editor/format-types/inline-binding/components/InlineBindingSelection.tsx +++ b/src/block-editor/format-types/inline-binding/components/InlineBindingSelection.tsx @@ -120,13 +120,12 @@ interface InlineBindingSelectFieldProps { queryInputs: RemoteDataQueryInput[]; selectedField?: string; queryKey: string; - displayQueryKey: string; } export function InlineBindingSelectField( props: InlineBindingSelectFieldProps ) { const { data, fetch, loading } = useRemoteData( { blockName: props.blockName, - queryKey: props.displayQueryKey, + queryKey: props.queryKey, } ); useEffect( () => { diff --git a/tests/inc/Editor/ConfigStoreTest.php b/tests/inc/Editor/ConfigStoreTest.php index ffd99caea..69800c225 100644 --- a/tests/inc/Editor/ConfigStoreTest.php +++ b/tests/inc/Editor/ConfigStoreTest.php @@ -40,6 +40,9 @@ public function testGetDataSourceReturnsDataSource(): void { 'output_schema' => [ 'type' => 'string' ], ] ), ], + 'display_queries_to_selectors' => [ + 'display' => [ 'display' ], + ], ] ); $this->assertEquals( 'airtable', ConfigStore::get_data_source_type( 'airtable_remote_blocks' ) ); From 7e8bcf1c69a822489b12dd5163c6ab3abc3c434d Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Fri, 30 May 2025 11:40:54 +1000 Subject: [PATCH 49/74] Fix all errors --- .../BlockManagement/BlockRegistration.php | 5 +- inc/Editor/BlockManagement/ConfigRegistry.php | 56 ++++++++++--------- tests/inc/Functions/FunctionsTest.php | 27 ++++++++- tests/inc/Mocks/MockQuery.php | 4 ++ tests/integration/RDBTestCase.php | 1 - tests/integration/telemetry/TelemetryTest.php | 5 ++ 6 files changed, 68 insertions(+), 30 deletions(-) diff --git a/inc/Editor/BlockManagement/BlockRegistration.php b/inc/Editor/BlockManagement/BlockRegistration.php index 015076184..085a3ffef 100644 --- a/inc/Editor/BlockManagement/BlockRegistration.php +++ b/inc/Editor/BlockManagement/BlockRegistration.php @@ -89,7 +89,10 @@ public static function register_block_configuration( array $config ): array { $patterns = $config['patterns'] ?? []; - foreach ( array_keys( $display_queries_to_selectors ) as $display_query_key ) { + // Using array_keys here triggers a psalm error, so it's set to $_ instead. + // Supressing the psalm error is a not a good idea, so instead this is the better solution. + // ToDo: Fix the psalm error, and see if array_keys could be used here again. + foreach ( $display_queries_to_selectors as $display_query_key => $_ ) { $display_query = $config['queries'][ $display_query_key ]; $available_bindings_for_query = []; $output_schema = $display_query->get_output_schema(); diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index 7620fffb9..0a6aa91e1 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -103,14 +103,13 @@ public static function register_block( array $block_config = [] ): bool|WP_Error // Generate the selectors for the selector queries. foreach ( $block_config[ self::QUERIES_KEY ] as $query_key => $query ) { - // Inflate the query. + // Inflate the query, and add it to the queries array. $query = self::inflate_query( $query ); + $queries[ $query_key ] = $query; + $input_schema = $query->get_input_schema(); $output_schema = $query->get_output_schema(); - // This is only done because of ConfigStore::get_data_sources_as_array() needing it. - $queries[ $query_key ] = $query; - // Generate the selector for the display query, and then continue on to the next query. if ( in_array( $query_key, $block_config['display_queries'], true ) ) { $is_collection = true === ( $output_schema['is_collection'] ?? false ); @@ -133,15 +132,22 @@ public static function register_block( array $block_config = [] ): bool|WP_Error continue; } + // Infer the type of the query, for selector generation and to validate the non-display queries. + $inferred_type = self::infer_query_type( $input_schema, $output_schema ); + if ( is_wp_error( $inferred_type ) ) { + return $inferred_type; + } + + // If the output schema's type is not an array, then skip selector generation as that'll not work. + // This has been done because some output schemas have the type set to string. + if ( ! is_array( $output_schema['type'] ) ) { + continue; + } + foreach ( $block_config['display_queries'] as $display_query_key ) { $display_query = self::inflate_query( $block_config[ self::QUERIES_KEY ][ $display_query_key ] ); $display_query_input_schema = $display_query->get_input_schema(); - // If the type is not set, or is not an array, skip. - if ( ! is_array( $output_schema['type'] ) || empty( $output_schema['type'] ) ) { - continue; - } - // Check if the query's output schema intersects with the display query's input schema. $intersecting_keys = array_intersect_key( $output_schema['type'], $display_query_input_schema ); @@ -157,25 +163,22 @@ public static function register_block( array $block_config = [] ): bool|WP_Error } // Validate the query mapping. - $validation_result = self::validate_query_mapping( $display_query_input_schema, $input_schema, $output_schema, $block_title, $query_key ); + $validation_result = self::validate_query_mapping( $display_query_input_schema, $output_schema, $block_title, $query_key ); if ( is_wp_error( $validation_result ) ) { return $validation_result; } - // Infer the type of the query. - $inferred_type = self::infer_query_type( $input_schema, $output_schema ); - if ( is_wp_error( $inferred_type ) ) { - return $inferred_type; - } - - // Add the selector for the query. - $selectors[] = [ - 'image_url' => $query->get_image_url(), - 'inputs' => self::map_input_variables( $input_schema ), - 'name' => self::get_query_name_from_key( $query_key ), - 'query_key' => $query_key, - 'type' => $inferred_type, - ]; + // Add the selector for the query to the beginning of the selectors array. + array_unshift( + $selectors, + [ + 'image_url' => $query->get_image_url(), + 'inputs' => self::map_input_variables( $input_schema ), + 'name' => self::get_query_name_from_key( $query_key ), + 'query_key' => $query_key, + 'type' => $inferred_type, + ] + ); $display_queries_to_selectors_map[ $display_query_key ][] = $query_key; @@ -241,7 +244,7 @@ private static function validate_selector_query_mapping( array $intersecting_key }, ARRAY_FILTER_USE_KEY ); } - private static function validate_query_mapping( array $to_query_input_schema, array $from_query_input_schema, array $from_query_output_schema, string $block_title, string $from_query_key ): WP_Error|bool { + private static function validate_query_mapping( array $to_query_input_schema, array $from_query_output_schema, string $block_title, string $from_query_key ): WP_Error|bool { foreach ( array_keys( $to_query_input_schema ) as $to ) { if ( ! isset( $from_query_output_schema['type'][ $to ] ) ) { return self::create_error( $block_title, sprintf( 'Cannot map key "%1$s" from %2$s query. The display query for this block requires a "%1$s" key as an input, but it is not present in the output schema for the %2$s query. Try adding a "%1$s" mapping to the output schema for the %2$s query.', esc_html( $to ), $from_query_key ) ); @@ -319,7 +322,8 @@ private static function infer_query_type( array $input_schema, array $output_sch return self::LIST_QUERY_KEY; } - // This should never happen, but if it does, we need to error out. + // This will happen if a query has not been configured correctly as a search or list query. + // So we error out, to replace the previous way of validating when the type was set. return self::create_error( 'Unknown query type', 'Could not infer the type of the query' ); } } diff --git a/tests/inc/Functions/FunctionsTest.php b/tests/inc/Functions/FunctionsTest.php index d4b674027..c8461d381 100644 --- a/tests/inc/Functions/FunctionsTest.php +++ b/tests/inc/Functions/FunctionsTest.php @@ -20,16 +20,39 @@ class FunctionsTest extends TestCase { protected function setUp(): void { parent::setUp(); $this->mock_logger = new MockLogger(); - $this->mock_query = MockQuery::create(); + $this->mock_query = MockQuery::create( [ + 'input_schema' => [ + 'id' => [ + 'name' => 'ID', + 'type' => 'id', + ], + ], + ] ); $this->mock_list_query = MockQuery::create( [ 'output_schema' => [ 'is_collection' => true, + 'type' => [ + 'id' => [ + 'name' => 'ID', + 'path' => '$.id', + 'type' => 'id', + ], + ], ], ] ); $this->mock_search_query = MockQuery::create( [ 'input_schema' => [ 'search' => [ 'type' => 'ui:search_input' ], ], + 'output_schema' => [ + 'type' => [ + 'id' => [ + 'name' => 'ID', + 'path' => '$.id', + 'type' => 'id', + ], + ], + ], ] ); ConfigRegistry::init( $this->mock_logger ); @@ -174,6 +197,6 @@ public function testRegisterSearchQueryWithoutSearchTerms(): void { $this->assertTrue( $this->mock_logger->hasLoggedLevel( LogLevel::ERROR ) ); $error_logs = $this->mock_logger->getLogsByLevel( LogLevel::ERROR ); - $this->assertStringContainsString( 'ui:search_input', $error_logs[0]['message'] ); + $this->assertStringContainsString( 'Error registering block Unknown query type: Could not infer the type of the query', $error_logs[0]['message'] ); } } diff --git a/tests/inc/Mocks/MockQuery.php b/tests/inc/Mocks/MockQuery.php index 907cc8c8e..0682d46d3 100644 --- a/tests/inc/Mocks/MockQuery.php +++ b/tests/inc/Mocks/MockQuery.php @@ -30,6 +30,10 @@ public function preprocess_response( mixed $response_data, array $input_variable return $response_data; } + public function set_input_schema( array $input_schema ): void { + $this->config['input_schema'] = $input_schema; + } + public function set_output_schema( array $output_schema ): void { $this->config['output_schema'] = $output_schema; } diff --git a/tests/integration/RDBTestCase.php b/tests/integration/RDBTestCase.php index 8c3ee3ba0..340761823 100644 --- a/tests/integration/RDBTestCase.php +++ b/tests/integration/RDBTestCase.php @@ -1,6 +1,5 @@ [ 'display' => [ 'display' ] ], 'queries' => [ 'display' => MockQuery::create(), ], @@ -109,6 +110,7 @@ public function test_track_remote_data_blocks_usage_tracks_nested_blocks(): void ] ); ConfigStore::set_block_configuration( 'remote-data-blocks/example', [ + 'display_queries_to_selectors' => [ 'display' => [ 'display' ] ], 'queries' => [ 'display' => MockQuery::create(), ], @@ -156,6 +158,7 @@ public function test_track_remote_data_blocks_usage_tracks_multiple_nested_block ] ); ConfigStore::set_block_configuration( 'remote-data-blocks/example', [ + 'display_queries_to_selectors' => [ 'display' => [ 'display' ] ], 'queries' => [ 'display' => MockQuery::create(), ], @@ -203,6 +206,7 @@ public function test_track_remote_data_blocks_usage_tracks_fallback_blocks_in_ne ] ); ConfigStore::set_block_configuration( 'remote-data-blocks/example', [ + 'display_queries_to_selectors' => [ 'display' => [ 'display' ] ], 'queries' => [ 'display' => MockQuery::create(), ], @@ -273,6 +277,7 @@ public function test_track_remote_data_blocks_usage_tracks_nested_remote_data_bl ] ); ConfigStore::set_block_configuration( 'remote-data-blocks/example', [ + 'display_queries_to_selectors' => [ 'display' => [ 'display' ] ], 'queries' => [ 'display' => MockQuery::create(), ], From 364e638e2634e1e373ff2f9d061bb2e597d33c0a Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Fri, 30 May 2025 11:47:57 +1000 Subject: [PATCH 50/74] Tweak the function name --- src/block-editor/filters/withBlockBinding.tsx | 4 ++-- .../inline-binding/components/InlineBindingSelection.tsx | 4 ++-- src/utils/localized-block-data.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/block-editor/filters/withBlockBinding.tsx b/src/block-editor/filters/withBlockBinding.tsx index ed70f0052..a65d53050 100644 --- a/src/block-editor/filters/withBlockBinding.tsx +++ b/src/block-editor/filters/withBlockBinding.tsx @@ -17,7 +17,7 @@ import { PATTERN_OVERRIDES_CONTEXT_KEY, } from '@/config/constants'; import { getBoundBlockClassName, getMismatchedAttributes } from '@/utils/block-binding'; -import { getBlockAvailableBindingsForQuery, getBlockTitle } from '@/utils/localized-block-data'; +import { getAvailableBindingsForQuery, getBlockTitle } from '@/utils/localized-block-data'; interface BoundBlockEditProps { attributes: RemoteDataInnerBlockAttributes; @@ -114,7 +114,7 @@ export const withBlockBinding = createHigherOrderComponent( BlockEdit => { const queryKey = remoteData.queryKey; - const availableBindings = getBlockAvailableBindingsForQuery( + const availableBindings = getAvailableBindingsForQuery( remoteData?.blockName ?? '', queryKey ?? '' ); diff --git a/src/block-editor/format-types/inline-binding/components/InlineBindingSelection.tsx b/src/block-editor/format-types/inline-binding/components/InlineBindingSelection.tsx index 567dff3b6..4b414dd42 100644 --- a/src/block-editor/format-types/inline-binding/components/InlineBindingSelection.tsx +++ b/src/block-editor/format-types/inline-binding/components/InlineBindingSelection.tsx @@ -4,7 +4,7 @@ import { check } from '@wordpress/icons'; import { TEXT_FIELD_TYPES } from '@/blocks/remote-data-container/config/constants'; import { useRemoteData } from '@/blocks/remote-data-container/hooks/useRemoteData'; -import { getBlockAvailableBindingsForQuery } from '@/utils/localized-block-data'; +import { getAvailableBindingsForQuery } from '@/utils/localized-block-data'; import { getRemoteDataResultValue } from '@/utils/remote-data'; interface FieldSelectionProps { @@ -73,7 +73,7 @@ export function FieldSelection( props: FieldSelectionProps ) { type FieldSelectionWithFieldsProps = Omit< FieldSelectionProps, 'fields' | 'fieldType' >; export function FieldSelectionFromAvailableBindings( props: FieldSelectionWithFieldsProps ) { - const availableBindings = getBlockAvailableBindingsForQuery( + const availableBindings = getAvailableBindingsForQuery( props.remoteData.blockName, props.remoteData.queryKey ?? '' ); diff --git a/src/utils/localized-block-data.ts b/src/utils/localized-block-data.ts index d4ddbea81..c5031fa91 100644 --- a/src/utils/localized-block-data.ts +++ b/src/utils/localized-block-data.ts @@ -1,4 +1,4 @@ -export function getBlockAvailableBindingsForQuery( +export function getAvailableBindingsForQuery( blockName: string, queryKey: string ): AvailableBindingsForQuery { From cf8a99d6547c4aacd2a5912eca6d315eebd4d74c Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Fri, 30 May 2025 13:06:54 +1000 Subject: [PATCH 51/74] Minor code review points --- inc/Editor/BlockManagement/ConfigRegistry.php | 2 +- inc/Editor/BlockPatterns/BlockPatterns.php | 1 - src/blocks/remote-data-container/hooks/useRemoteData.ts | 1 - tests/inc/Functions/FunctionsTest.php | 2 +- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index 0a6aa91e1..4bc8ccccb 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -324,6 +324,6 @@ private static function infer_query_type( array $input_schema, array $output_sch // This will happen if a query has not been configured correctly as a search or list query. // So we error out, to replace the previous way of validating when the type was set. - return self::create_error( 'Unknown query type', 'Could not infer the type of the query' ); + return self::create_error( 'Unknown query type', 'Could not infer the type of the query. Valid query types are "search" and "list".' ); } } diff --git a/inc/Editor/BlockPatterns/BlockPatterns.php b/inc/Editor/BlockPatterns/BlockPatterns.php index 66dd9d129..bb5a12921 100644 --- a/inc/Editor/BlockPatterns/BlockPatterns.php +++ b/inc/Editor/BlockPatterns/BlockPatterns.php @@ -177,7 +177,6 @@ public static function register_default_block_pattern( string $block_name, strin $content = self::populate_template( 'empty', [] ); } - // ToDo: Ensure this name is compliant with what's expected by the block editor. $pattern_name = sprintf( '%s/%s-pattern', $block_name, $display_query_key ); register_block_pattern( diff --git a/src/blocks/remote-data-container/hooks/useRemoteData.ts b/src/blocks/remote-data-container/hooks/useRemoteData.ts index 9a2d73672..3f6cbcf74 100644 --- a/src/blocks/remote-data-container/hooks/useRemoteData.ts +++ b/src/blocks/remote-data-container/hooks/useRemoteData.ts @@ -95,7 +95,6 @@ export function useRemoteData( { const hasResolvedData = Boolean( resolvedData ); const blockConfig = getBlockConfig( blockName ); - const query = blockConfig?.selectors?.find( selector => selector.query_key === queryKey ); if ( ! query ) { diff --git a/tests/inc/Functions/FunctionsTest.php b/tests/inc/Functions/FunctionsTest.php index c8461d381..59b8e51e2 100644 --- a/tests/inc/Functions/FunctionsTest.php +++ b/tests/inc/Functions/FunctionsTest.php @@ -197,6 +197,6 @@ public function testRegisterSearchQueryWithoutSearchTerms(): void { $this->assertTrue( $this->mock_logger->hasLoggedLevel( LogLevel::ERROR ) ); $error_logs = $this->mock_logger->getLogsByLevel( LogLevel::ERROR ); - $this->assertStringContainsString( 'Error registering block Unknown query type: Could not infer the type of the query', $error_logs[0]['message'] ); + $this->assertStringContainsString( 'Error registering block Unknown query type: Could not infer the type of the query. Valid query types are "search" and "list".', $error_logs[0]['message'] ); } } From ab4cd35800f6fb443b302e456659fee7819ea59c Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Fri, 30 May 2025 13:27:06 +1000 Subject: [PATCH 52/74] Remove more unecessary lines --- inc/Editor/DataBinding/BlockBindings.php | 2 -- .../components/panels/QueryInputsPanel.tsx | 8 +------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/inc/Editor/DataBinding/BlockBindings.php b/inc/Editor/DataBinding/BlockBindings.php index c855df4c4..a1b536cdf 100644 --- a/inc/Editor/DataBinding/BlockBindings.php +++ b/inc/Editor/DataBinding/BlockBindings.php @@ -133,8 +133,6 @@ private static function execute_queries( array $block_context ): array|WP_Error $remote_data = $remote_data->to_array(); $block_name = $remote_data['blockName']; $enabled_overrides = $remote_data['enabledOverrides'] ?? []; - // The fallback of the display query key has been kept for backwards compatibility. - // It's fine to do so, given the error check present on the query below. $query_key = $remote_data['queryKey'] ?? ConfigRegistry::DISPLAY_QUERY_KEY; $array_of_input_variables = $remote_data['queryInputs']; diff --git a/src/blocks/remote-data-container/components/panels/QueryInputsPanel.tsx b/src/blocks/remote-data-container/components/panels/QueryInputsPanel.tsx index 666127a99..aa753aff5 100644 --- a/src/blocks/remote-data-container/components/panels/QueryInputsPanel.tsx +++ b/src/blocks/remote-data-container/components/panels/QueryInputsPanel.tsx @@ -13,13 +13,7 @@ export function QueryInputsPanel( { remoteData, selectors, }: QueryInputsPanelProps ) { - const { queryInputs = [], queryKey } = remoteData; - - // throw an error if the queryKey is empty. - if ( ! queryKey ) { - throw new Error( 'Query key should not be empty, when using the QueryInputsPanel' ); - } - + const { queryInputs = [], queryKey = '' } = remoteData; const [ localInputs, setLocalInputs ] = useState( queryInputs ); const inputDefinitions = selectors?.find( selector => selector.query_key === queryKey )?.inputs ?? []; From 4f01da21ce049f71ec676281dea2dce6386de83a Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Fri, 30 May 2025 13:47:37 +1000 Subject: [PATCH 53/74] Add Extra tests --- tests/inc/Functions/FunctionsTest.php | 45 +++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/inc/Functions/FunctionsTest.php b/tests/inc/Functions/FunctionsTest.php index 59b8e51e2..9a0b4dd1b 100644 --- a/tests/inc/Functions/FunctionsTest.php +++ b/tests/inc/Functions/FunctionsTest.php @@ -199,4 +199,49 @@ public function testRegisterSearchQueryWithoutSearchTerms(): void { $error_logs = $this->mock_logger->getLogsByLevel( LogLevel::ERROR ); $this->assertStringContainsString( 'Error registering block Unknown query type: Could not infer the type of the query. Valid query types are "search" and "list".', $error_logs[0]['message'] ); } + + public function testRegisterBlockWithOldSchemaFormatNoRenderQuery(): void { + register_remote_data_block( [ + 'title' => 'Test Block with Old Schema Format No Render Query', + ] ); + + $this->assertTrue( $this->mock_logger->hasLoggedLevel( LogLevel::ERROR ) ); + $error_logs = $this->mock_logger->getLogsByLevel( LogLevel::ERROR ); + $this->assertStringContainsString( 'Error registering block Test Block with Old Schema Format No Render Query: Render query is required', $error_logs[0]['message'] ); + } + + public function testRegisterBlockWithNewConfigSchema(): void { + register_remote_data_block( [ + 'title' => 'Test Block with New Config Schema', + 'queries' => [ + 'display' => $this->mock_query, + 'search' => $this->mock_search_query, + 'list' => $this->mock_list_query, + ], + 'display_queries' => [ 'display', 'list' ], + ] ); + + $block_name = 'remote-data-blocks/test-block-with-new-config-schema'; + $config = ConfigStore::get_block_configuration( $block_name ); + + // Ensure that display is in the display_queries_to_selectors for display along with search. + $this->assertSame( [ 'display', 'search' ], $config['display_queries_to_selectors']['display'] ); + + // Ensure that list is in the display_queries_to_selectors for list along with display and search. + $this->assertSame( [ 'list' ], $config['display_queries_to_selectors']['list'] ); + } + + public function testRegisterBlockWithBadDisplayQueries(): void { + register_remote_data_block( [ + 'title' => 'Test Block with Bad Display Queries', + 'queries' => [ + 'display' => $this->mock_query, + ], + 'display_queries' => [ 'display', 'list', 'test' ], + ] ); + + $this->assertTrue( $this->mock_logger->hasLoggedLevel( LogLevel::ERROR ) ); + $error_logs = $this->mock_logger->getLogsByLevel( LogLevel::ERROR ); + $this->assertStringContainsString( 'Error registering block Test Block with Bad Display Queries: Display query "list" not found', $error_logs[0]['message'] ); + } } From 82b30540459074e87831eb932682095ece4f8b23 Mon Sep 17 00:00:00 2001 From: chriszarate Date: Wed, 4 Jun 2025 13:57:06 -0600 Subject: [PATCH 54/74] Add RemoteDataBlock serializable config --- inc/Config/Block/RemoteDataBlock.php | 59 +++++++++++++++++ inc/Editor/BlockManagement/ConfigRegistry.php | 63 ++++--------------- inc/Editor/DataBinding/BlockBindings.php | 2 +- .../Editor/DataBinding/BlockBindingsTest.php | 2 +- tests/inc/Functions/FunctionsTest.php | 2 +- 5 files changed, 74 insertions(+), 54 deletions(-) create mode 100644 inc/Config/Block/RemoteDataBlock.php diff --git a/inc/Config/Block/RemoteDataBlock.php b/inc/Config/Block/RemoteDataBlock.php new file mode 100644 index 000000000..88fd7a49d --- /dev/null +++ b/inc/Config/Block/RemoteDataBlock.php @@ -0,0 +1,59 @@ +validate( $block_config ); + // Validate the provided block configuration. + $block_config = RemoteDataBlock::from_array( $block_config ); - if ( is_wp_error( $validated ) ) { - return $validated; + if ( is_wp_error( $block_config ) ) { + self::$logger->error( $block_config->get_error_message() ); + return $block_config; } // Check if the block has already been registered. + $block_config = $block_config->to_array(); $block_title = $block_config['title']; $block_name = ConfigStore::get_block_name( $block_title ); if ( ConfigStore::is_registered_block( $block_name ) ) { return self::create_error( $block_title, sprintf( 'Block %s has already been registered', $block_name ) ); } + if ( empty( $block_config['display_queries'] ) ) { + return self::create_error( $block_title, 'Block configuration must have a non-empty "display_queries" array' ); + } + // Pre-validate the display queries, to ensure they exist. foreach ( $block_config['display_queries'] as $display_query_key ) { if ( ! isset( $block_config[ self::QUERIES_KEY ][ $display_query_key ] ) ) { diff --git a/inc/Editor/DataBinding/BlockBindings.php b/inc/Editor/DataBinding/BlockBindings.php index a1b536cdf..fe23f53b3 100644 --- a/inc/Editor/DataBinding/BlockBindings.php +++ b/inc/Editor/DataBinding/BlockBindings.php @@ -133,7 +133,7 @@ private static function execute_queries( array $block_context ): array|WP_Error $remote_data = $remote_data->to_array(); $block_name = $remote_data['blockName']; $enabled_overrides = $remote_data['enabledOverrides'] ?? []; - $query_key = $remote_data['queryKey'] ?? ConfigRegistry::DISPLAY_QUERY_KEY; + $query_key = $remote_data['queryKey'] ?? ConfigRegistry::DEPRECATED_DISPLAY_QUERY_KEY; $array_of_input_variables = $remote_data['queryInputs']; $block_config = ConfigStore::get_block_configuration( $block_name ); diff --git a/tests/inc/Editor/DataBinding/BlockBindingsTest.php b/tests/inc/Editor/DataBinding/BlockBindingsTest.php index 6726a3da7..879fbb6e0 100644 --- a/tests/inc/Editor/DataBinding/BlockBindingsTest.php +++ b/tests/inc/Editor/DataBinding/BlockBindingsTest.php @@ -543,7 +543,7 @@ private function create_mock_query_runner_with_error( WP_Error $error ): MockQue private function create_mock_block_config( MockQueryRunner $query_runner ): array { return [ 'queries' => [ - ConfigRegistry::DISPLAY_QUERY_KEY => MockQuery::create( [ + ConfigRegistry::DEPRECATED_DISPLAY_QUERY_KEY => MockQuery::create( [ 'input_schema' => self::MOCK_INPUT_SCHEMA, 'output_schema' => self::MOCK_OUTPUT_SCHEMA, 'query_runner' => $query_runner, diff --git a/tests/inc/Functions/FunctionsTest.php b/tests/inc/Functions/FunctionsTest.php index 9a0b4dd1b..e38af21f8 100644 --- a/tests/inc/Functions/FunctionsTest.php +++ b/tests/inc/Functions/FunctionsTest.php @@ -207,7 +207,7 @@ public function testRegisterBlockWithOldSchemaFormatNoRenderQuery(): void { $this->assertTrue( $this->mock_logger->hasLoggedLevel( LogLevel::ERROR ) ); $error_logs = $this->mock_logger->getLogsByLevel( LogLevel::ERROR ); - $this->assertStringContainsString( 'Error registering block Test Block with Old Schema Format No Render Query: Render query is required', $error_logs[0]['message'] ); + $this->assertStringContainsString( 'Error registering block Test Block with Old Schema Format No Render Query: Block configuration must have a non-empty "display_queries" array', $error_logs[0]['message'] ); } public function testRegisterBlockWithNewConfigSchema(): void { From ff81040c4851f70ef2ae41b127e11d80db2110b6 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Thu, 5 Jun 2025 08:01:44 +1000 Subject: [PATCH 55/74] Remove unused code --- tests/inc/Mocks/MockQuery.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/inc/Mocks/MockQuery.php b/tests/inc/Mocks/MockQuery.php index 0682d46d3..907cc8c8e 100644 --- a/tests/inc/Mocks/MockQuery.php +++ b/tests/inc/Mocks/MockQuery.php @@ -30,10 +30,6 @@ public function preprocess_response( mixed $response_data, array $input_variable return $response_data; } - public function set_input_schema( array $input_schema ): void { - $this->config['input_schema'] = $input_schema; - } - public function set_output_schema( array $output_schema ): void { $this->config['output_schema'] = $output_schema; } From 84196d811dda59418d31305a44ed24febf3be201 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Fri, 6 Jun 2025 11:35:18 +1000 Subject: [PATCH 56/74] Switch from using display queries to placeholder --- example/blocks/art-block/art-block.php | 8 +++++- inc/Config/Block/RemoteDataBlock.php | 11 +++++--- inc/Editor/BlockManagement/ConfigRegistry.php | 28 +++++++++++-------- inc/ExampleApi/ExampleApi.php | 7 ++++- inc/Validation/ConfigSchemas.php | 10 ++++++- remote-data-blocks.php | 8 +++--- tests/inc/Functions/FunctionsTest.php | 26 +++++++++++++++-- 7 files changed, 74 insertions(+), 24 deletions(-) diff --git a/example/blocks/art-block/art-block.php b/example/blocks/art-block/art-block.php index 2ded70cc0..69771d258 100644 --- a/example/blocks/art-block/art-block.php +++ b/example/blocks/art-block/art-block.php @@ -132,7 +132,13 @@ function register_art_remote_data_block(): void { 'display' => $get_art_query, 'search_art' => $search_art_query, ], - 'display_queries' => [ 'display' ], + 'placeholders' => [ + [ + 'name' => 'Get Art', + 'query_key' => 'display', + 'icon' => 'art', + ], + ], ] ); } add_action( 'init', __NAMESPACE__ . '\\register_art_remote_data_block' ); diff --git a/inc/Config/Block/RemoteDataBlock.php b/inc/Config/Block/RemoteDataBlock.php index 88fd7a49d..a7068b21a 100644 --- a/inc/Config/Block/RemoteDataBlock.php +++ b/inc/Config/Block/RemoteDataBlock.php @@ -28,16 +28,19 @@ public static function get_config_schema(): array { */ public static function migrate_config( array $config = [] ): array|WP_Error { // Nothing to migrate, return the block config as is. - if ( isset( $config[ ConfigRegistry::QUERIES_KEY ] ) ) { + if ( isset( $config[ ConfigRegistry::PLACEHOLDERS_KEY ] ) ) { return $config; } $queries = []; - $display_queries = []; + $placeholders = []; if ( isset( $config[ self::DEPRECATED_RENDER_QUERY_KEY ]['query'] ) ) { $queries[ ConfigRegistry::DEPRECATED_DISPLAY_QUERY_KEY ] = $config[ self::DEPRECATED_RENDER_QUERY_KEY ]['query']; - $display_queries = [ ConfigRegistry::DEPRECATED_DISPLAY_QUERY_KEY ]; + $placeholders[] = [ + 'name' => 'Display', + 'query_key' => ConfigRegistry::DEPRECATED_DISPLAY_QUERY_KEY, + ]; unset( $config[ self::DEPRECATED_RENDER_QUERY_KEY ] ); } @@ -52,7 +55,7 @@ public static function migrate_config( array $config = [] ): array|WP_Error { // Set queries. $config[ ConfigRegistry::QUERIES_KEY ] = $queries; - $config[ ConfigRegistry::DISPLAY_QUERIES_KEY ] = $display_queries; + $config[ ConfigRegistry::PLACEHOLDERS_KEY ] = $placeholders; return $config; } diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index 3fcd0e421..c3c22739b 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -20,7 +20,7 @@ class ConfigRegistry { private static LoggerInterface $logger; public const DEPRECATED_DISPLAY_QUERY_KEY = 'display'; - public const DISPLAY_QUERIES_KEY = 'display_queries'; + public const PLACEHOLDERS_KEY = 'placeholders'; public const LIST_QUERY_KEY = 'list'; public const SEARCH_QUERY_KEY = 'search'; public const QUERIES_KEY = 'queries'; @@ -47,14 +47,15 @@ public static function register_block( array $block_config = [] ): bool|WP_Error return self::create_error( $block_title, sprintf( 'Block %s has already been registered', $block_name ) ); } - if ( empty( $block_config['display_queries'] ) ) { - return self::create_error( $block_title, 'Block configuration must have a non-empty "display_queries" array' ); + // ToDo: This is optional, so we should generate it in the event that it's not present. + if ( empty( $block_config[ self::PLACEHOLDERS_KEY ] ) ) { + return self::create_error( $block_title, 'Block configuration must have a non-empty "placeholders" array' ); } - // Pre-validate the display queries, to ensure they exist. - foreach ( $block_config['display_queries'] as $display_query_key ) { - if ( ! isset( $block_config[ self::QUERIES_KEY ][ $display_query_key ] ) ) { - return self::create_error( $block_title, sprintf( 'Display query "%s" not found', $display_query_key ) ); + // Pre-validate the placeholders, to ensure they exist. + foreach ( $block_config[ self::PLACEHOLDERS_KEY ] as $placeholder ) { + if ( ! isset( $block_config[ self::QUERIES_KEY ][ $placeholder['query_key'] ] ) ) { + return self::create_error( $block_title, sprintf( 'Query "%s" not found for placeholder "%s"', $placeholder['query_key'], $placeholder['name'] ) ); } } @@ -71,8 +72,11 @@ public static function register_block( array $block_config = [] ): bool|WP_Error $input_schema = $query->get_input_schema(); $output_schema = $query->get_output_schema(); + // match the query_key against the query_key property in a placeholder entry. + $filtered_placeholders = array_filter( $block_config[ self::PLACEHOLDERS_KEY ], fn( $placeholder ) => $placeholder['query_key'] === $query_key ); + // Generate the selector for the display query, and then continue on to the next query. - if ( in_array( $query_key, $block_config['display_queries'], true ) ) { + if ( ! empty( $filtered_placeholders ) ) { $is_collection = true === ( $output_schema['is_collection'] ?? false ); $has_required_variables = array_reduce( array_column( $input_schema, 'required' ), @@ -88,11 +92,13 @@ public static function register_block( array $block_config = [] ): bool|WP_Error 'type' => $has_required_variables ? 'manual-input' : 'load-without-input', ]; + // ToDo: Should consider inserting the icon here along with the name. $display_queries_to_selectors_map[ $query_key ][] = $query_key; continue; } + // ToDo: Should switch this to be an array of types instead. // Infer the type of the query, for selector generation and to validate the non-display queries. $inferred_type = self::infer_query_type( $input_schema, $output_schema ); if ( is_wp_error( $inferred_type ) ) { @@ -105,8 +111,8 @@ public static function register_block( array $block_config = [] ): bool|WP_Error continue; } - foreach ( $block_config['display_queries'] as $display_query_key ) { - $display_query = self::inflate_query( $block_config[ self::QUERIES_KEY ][ $display_query_key ] ); + foreach ( $block_config[ self::PLACEHOLDERS_KEY ] as $placeholder ) { + $display_query = self::inflate_query( $block_config[ self::QUERIES_KEY ][ $placeholder['query_key'] ] ); $display_query_input_schema = $display_query->get_input_schema(); // Check if the query's output schema intersects with the display query's input schema. @@ -141,7 +147,7 @@ public static function register_block( array $block_config = [] ): bool|WP_Error ] ); - $display_queries_to_selectors_map[ $display_query_key ][] = $query_key; + $display_queries_to_selectors_map[ $placeholder['query_key'] ][] = $query_key; // We have found the relevant display query, so we can break out of the loop. break; diff --git a/inc/ExampleApi/ExampleApi.php b/inc/ExampleApi/ExampleApi.php index a9db0e5c4..b942dac77 100644 --- a/inc/ExampleApi/ExampleApi.php +++ b/inc/ExampleApi/ExampleApi.php @@ -124,7 +124,12 @@ public static function register_remote_data_block(): void { 'display' => $get_record_query, 'get_table' => $get_table_query, ], - 'display_queries' => [ 'display' ], + 'placeholders' => [ + [ + 'name' => 'Get Record', + 'query_key' => 'display', + ], + ], ] ); } } diff --git a/inc/Validation/ConfigSchemas.php b/inc/Validation/ConfigSchemas.php index 2e6fe6733..2d797d091 100644 --- a/inc/Validation/ConfigSchemas.php +++ b/inc/Validation/ConfigSchemas.php @@ -85,7 +85,15 @@ private static function generate_remote_data_block_config_schema(): array { ] ) ) ), - 'display_queries' => Types::list_of( Types::string() ), + 'placeholders' => Types::nullable( + Types::list_of( + Types::object( [ + 'name' => Types::string(), + 'query_key' => Types::string(), + 'icon' => Types::nullable( Types::string() ), + ] ), + ) + ), 'queries' => Types::record( Types::string(), Types::one_of( diff --git a/remote-data-blocks.php b/remote-data-blocks.php index 1afe01e8c..8aa2f1f9b 100644 --- a/remote-data-blocks.php +++ b/remote-data-blocks.php @@ -66,7 +66,7 @@ // Plugin developers: If you need to register additional code for testing, you // can do so here, e.g.: -// require_once __DIR__ . '/example/blocks/art-block/art-block.php'; -// require_once __DIR__ . '/example/blocks/github-markdown-block/github-markdown-block.php'; -// require_once __DIR__ . '/example/blocks/shopify-mock-store-block/shopify-mock-store-block.php'; -// require_once __DIR__ . '/example/blocks/zip-code-block/zip-code-block.php'; +require_once __DIR__ . '/example/blocks/art-block/art-block.php'; +require_once __DIR__ . '/example/blocks/github-markdown-block/github-markdown-block.php'; +require_once __DIR__ . '/example/blocks/shopify-mock-store-block/shopify-mock-store-block.php'; +require_once __DIR__ . '/example/blocks/zip-code-block/zip-code-block.php'; diff --git a/tests/inc/Functions/FunctionsTest.php b/tests/inc/Functions/FunctionsTest.php index e38af21f8..e2c20513c 100644 --- a/tests/inc/Functions/FunctionsTest.php +++ b/tests/inc/Functions/FunctionsTest.php @@ -218,7 +218,16 @@ public function testRegisterBlockWithNewConfigSchema(): void { 'search' => $this->mock_search_query, 'list' => $this->mock_list_query, ], - 'display_queries' => [ 'display', 'list' ], + 'placeholders' => [ + [ + 'name' => 'Get', + 'query_key' => 'display', + ], + [ + 'name' => 'List', + 'query_key' => 'list', + ], + ], ] ); $block_name = 'remote-data-blocks/test-block-with-new-config-schema'; @@ -237,7 +246,20 @@ public function testRegisterBlockWithBadDisplayQueries(): void { 'queries' => [ 'display' => $this->mock_query, ], - 'display_queries' => [ 'display', 'list', 'test' ], + 'placeholders' => [ + [ + 'name' => 'Get', + 'query_key' => 'display', + ], + [ + 'name' => 'List', + 'query_key' => 'list', + ], + [ + 'name' => 'Test', + 'query_key' => 'test', + ], + ], ] ); $this->assertTrue( $this->mock_logger->hasLoggedLevel( LogLevel::ERROR ) ); From 19ff3b49c334bbdf80456f0814db6a9b84ba902f Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Fri, 6 Jun 2025 15:13:18 +1000 Subject: [PATCH 57/74] Get the case working when no placeholders are provided --- inc/Config/Block/RemoteDataBlock.php | 2 +- inc/Editor/BlockManagement/ConfigRegistry.php | 228 ++++++++++++------ inc/ExampleApi/ExampleApi.php | 6 - remote-data-blocks.php | 8 +- 4 files changed, 154 insertions(+), 90 deletions(-) diff --git a/inc/Config/Block/RemoteDataBlock.php b/inc/Config/Block/RemoteDataBlock.php index a7068b21a..314ad5b2d 100644 --- a/inc/Config/Block/RemoteDataBlock.php +++ b/inc/Config/Block/RemoteDataBlock.php @@ -28,7 +28,7 @@ public static function get_config_schema(): array { */ public static function migrate_config( array $config = [] ): array|WP_Error { // Nothing to migrate, return the block config as is. - if ( isset( $config[ ConfigRegistry::PLACEHOLDERS_KEY ] ) ) { + if ( isset( $config[ ConfigRegistry::QUERIES_KEY ] ) ) { return $config; } diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index c3c22739b..02dfc56d3 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -47,110 +47,180 @@ public static function register_block( array $block_config = [] ): bool|WP_Error return self::create_error( $block_title, sprintf( 'Block %s has already been registered', $block_name ) ); } - // ToDo: This is optional, so we should generate it in the event that it's not present. - if ( empty( $block_config[ self::PLACEHOLDERS_KEY ] ) ) { - return self::create_error( $block_title, 'Block configuration must have a non-empty "placeholders" array' ); - } - - // Pre-validate the placeholders, to ensure they exist. - foreach ( $block_config[ self::PLACEHOLDERS_KEY ] as $placeholder ) { - if ( ! isset( $block_config[ self::QUERIES_KEY ][ $placeholder['query_key'] ] ) ) { - return self::create_error( $block_title, sprintf( 'Query "%s" not found for placeholder "%s"', $placeholder['query_key'], $placeholder['name'] ) ); - } - } - $queries = []; $selectors = []; $display_queries_to_selectors_map = []; - // Generate the selectors for the selector queries. - foreach ( $block_config[ self::QUERIES_KEY ] as $query_key => $query ) { - // Inflate the query, and add it to the queries array. - $query = self::inflate_query( $query ); - $queries[ $query_key ] = $query; + if ( ! empty( $block_config[ self::PLACEHOLDERS_KEY ] ) ) { + // Pre-validate the placeholders, to ensure they exist. + foreach ( $block_config[ self::PLACEHOLDERS_KEY ] as $placeholder ) { + if ( ! isset( $block_config[ self::QUERIES_KEY ][ $placeholder['query_key'] ] ) ) { + return self::create_error( $block_title, sprintf( 'Query "%s" not found for placeholder "%s"', $placeholder['query_key'], $placeholder['name'] ) ); + } + } + + // Generate the selectors for the selector queries. + foreach ( $block_config[ self::QUERIES_KEY ] as $query_key => $query ) { + // Inflate the query, and add it to the queries array. + $query = self::inflate_query( $query ); + $queries[ $query_key ] = $query; - $input_schema = $query->get_input_schema(); - $output_schema = $query->get_output_schema(); + $input_schema = $query->get_input_schema(); + $output_schema = $query->get_output_schema(); - // match the query_key against the query_key property in a placeholder entry. - $filtered_placeholders = array_filter( $block_config[ self::PLACEHOLDERS_KEY ], fn( $placeholder ) => $placeholder['query_key'] === $query_key ); + // match the query_key against the query_key property in a placeholder entry. + $filtered_placeholders = array_filter( $block_config[ self::PLACEHOLDERS_KEY ], fn( $placeholder ) => $placeholder['query_key'] === $query_key ); + + // Generate the selector for the display query, and then continue on to the next query. + if ( ! empty( $filtered_placeholders ) ) { + $is_collection = true === ( $output_schema['is_collection'] ?? false ); + $has_required_variables = array_reduce( + array_column( $input_schema, 'required' ), + fn( $carry, $required ) => $carry || ( $required ?? true ), + false + ); + + $selectors[] = [ + 'image_url' => $query->get_image_url(), + 'inputs' => self::map_input_variables( $input_schema ), + 'name' => $has_required_variables ? 'Manual input' : ( $is_collection ? 'Load collection' : 'Load item' ), + 'query_key' => $query_key, + 'type' => $has_required_variables ? 'manual-input' : 'load-without-input', + ]; + + $display_queries_to_selectors_map[ $query_key ][] = $query_key; + + continue; + } + + // ToDo: Should switch this to be an array of types instead. + // Infer the type of the query, for selector generation and to validate the non-display queries. + $inferred_type = self::infer_query_type( $input_schema, $output_schema ); + if ( 'unknown' === $inferred_type ) { + return self::create_error( 'Unknown query type', 'Could not infer the type of the query. Valid query types are "search" and "list".' ); + } + + // If the output schema's type is not an array, then skip selector generation as that'll not work. + // This has been done because some output schemas have the type set to string. + if ( ! is_array( $output_schema['type'] ) ) { + continue; + } - // Generate the selector for the display query, and then continue on to the next query. - if ( ! empty( $filtered_placeholders ) ) { - $is_collection = true === ( $output_schema['is_collection'] ?? false ); + foreach ( $block_config[ self::PLACEHOLDERS_KEY ] as $placeholder ) { + $display_query = self::inflate_query( $block_config[ self::QUERIES_KEY ][ $placeholder['query_key'] ] ); + $display_query_input_schema = $display_query->get_input_schema(); + + // Check if the query's output schema intersects with the display query's input schema. + $intersecting_keys = array_intersect_key( $output_schema['type'], $display_query_input_schema ); + + // Skip this, if they don't intersect. + if ( empty( $intersecting_keys ) ) { + continue; + } + + // Ensure the name and type of the schemas are truly valid. + $valid_intersecting_keys = self::validate_selector_query_mapping( $intersecting_keys, $display_query_input_schema, $output_schema ); + if ( empty( $valid_intersecting_keys ) ) { + continue; + } + + // Validate the query mapping. + $validation_result = self::validate_query_mapping( $display_query_input_schema, $output_schema, $block_title, $query_key ); + if ( is_wp_error( $validation_result ) ) { + return $validation_result; + } + + // Add the selector for the query to the beginning of the selectors array. + array_unshift( + $selectors, + [ + 'image_url' => $query->get_image_url(), + 'inputs' => self::map_input_variables( $input_schema ), + 'name' => self::get_query_name_from_key( $query_key ), + 'query_key' => $query_key, + 'type' => $inferred_type, + ] + ); + + $display_queries_to_selectors_map[ $placeholder['query_key'] ][] = $query_key; + + // We have found the relevant display query, so we can break out of the loop. + break; + } + } + } else { + foreach ( $block_config[ self::QUERIES_KEY ] as $placeholder_query_key => $placeholder_query ) { + $placeholder_query = self::inflate_query( $placeholder_query ); + $queries[ $placeholder_query_key ] = $placeholder_query; + + // Skip if this query key is already mapped as a selector for any display query. + if ( in_array( $placeholder_query_key, array_merge( ...array_values( $display_queries_to_selectors_map ) ), true ) ) { + continue; + } + + $placeholder_query_input_schema = $placeholder_query->get_input_schema(); + $placeholder_query_output_schema = $placeholder_query->get_output_schema(); + + $is_collection = true === ( $placeholder_query_output_schema['is_collection'] ?? false ); $has_required_variables = array_reduce( - array_column( $input_schema, 'required' ), + array_column( $placeholder_query_input_schema, 'required' ), fn( $carry, $required ) => $carry || ( $required ?? true ), false ); $selectors[] = [ - 'image_url' => $query->get_image_url(), - 'inputs' => self::map_input_variables( $input_schema ), + 'image_url' => $placeholder_query->get_image_url(), + 'inputs' => self::map_input_variables( $placeholder_query_input_schema ), 'name' => $has_required_variables ? 'Manual input' : ( $is_collection ? 'Load collection' : 'Load item' ), - 'query_key' => $query_key, + 'query_key' => $placeholder_query_key, 'type' => $has_required_variables ? 'manual-input' : 'load-without-input', ]; - // ToDo: Should consider inserting the icon here along with the name. - $display_queries_to_selectors_map[ $query_key ][] = $query_key; + $display_queries_to_selectors_map[ $placeholder_query_key ][] = $placeholder_query_key; - continue; - } + foreach ( $block_config[ self::QUERIES_KEY ] as $selector_query_key => $selector_query ) { + $selector_query = self::inflate_query( $selector_query ); + $queries[ $selector_query_key ] = $selector_query; - // ToDo: Should switch this to be an array of types instead. - // Infer the type of the query, for selector generation and to validate the non-display queries. - $inferred_type = self::infer_query_type( $input_schema, $output_schema ); - if ( is_wp_error( $inferred_type ) ) { - return $inferred_type; - } - - // If the output schema's type is not an array, then skip selector generation as that'll not work. - // This has been done because some output schemas have the type set to string. - if ( ! is_array( $output_schema['type'] ) ) { - continue; - } + $selector_query_input_schema = $selector_query->get_input_schema(); + $selector_query_output_schema = $selector_query->get_output_schema(); - foreach ( $block_config[ self::PLACEHOLDERS_KEY ] as $placeholder ) { - $display_query = self::inflate_query( $block_config[ self::QUERIES_KEY ][ $placeholder['query_key'] ] ); - $display_query_input_schema = $display_query->get_input_schema(); + $inferred_selector_query_type = self::infer_query_type( $selector_query_input_schema, $selector_query_output_schema ); + if ( 'unknown' === $inferred_selector_query_type ) { + continue; + } - // Check if the query's output schema intersects with the display query's input schema. - $intersecting_keys = array_intersect_key( $output_schema['type'], $display_query_input_schema ); + if ( ! is_array( $selector_query_output_schema['type'] ) ) { + continue; + } - // Skip this, if they don't intersect. - if ( empty( $intersecting_keys ) ) { - continue; - } + $intersecting_keys = array_intersect_key( $selector_query_output_schema['type'], $placeholder_query_input_schema ); - // Ensure the name and type of the schemas are truly valid. - $valid_intersecting_keys = self::validate_selector_query_mapping( $intersecting_keys, $display_query_input_schema, $output_schema ); - if ( empty( $valid_intersecting_keys ) ) { - continue; - } + // Skip this, if they don't intersect. + if ( empty( $intersecting_keys ) ) { + continue; + } - // Validate the query mapping. - $validation_result = self::validate_query_mapping( $display_query_input_schema, $output_schema, $block_title, $query_key ); - if ( is_wp_error( $validation_result ) ) { - return $validation_result; - } + $validation_result = self::validate_query_mapping( $placeholder_query_input_schema, $selector_query_output_schema, $block_title, $placeholder_query_key ); + if ( is_wp_error( $validation_result ) ) { + return $validation_result; + } - // Add the selector for the query to the beginning of the selectors array. - array_unshift( - $selectors, - [ - 'image_url' => $query->get_image_url(), - 'inputs' => self::map_input_variables( $input_schema ), - 'name' => self::get_query_name_from_key( $query_key ), - 'query_key' => $query_key, - 'type' => $inferred_type, - ] - ); - $display_queries_to_selectors_map[ $placeholder['query_key'] ][] = $query_key; + // Add the selector for the query to the beginning of the selectors array. + array_unshift( + $selectors, + [ + 'image_url' => $selector_query->get_image_url(), + 'inputs' => self::map_input_variables( $selector_query_input_schema ), + 'name' => self::get_query_name_from_key( $selector_query_key ), + 'query_key' => $selector_query_key, + 'type' => $inferred_selector_query_type, + ] + ); - // We have found the relevant display query, so we can break out of the loop. - break; + $display_queries_to_selectors_map[ $placeholder_query_key ][] = $selector_query_key; + } } } @@ -276,7 +346,7 @@ private static function get_query_name_from_key( string $key ): string { return ucwords( preg_replace( '/[^a-zA-Z0-9]/', ' ', $key ) ); } - private static function infer_query_type( array $input_schema, array $output_schema ): string|WP_Error { + private static function infer_query_type( array $input_schema, array $output_schema ): string { // If any input variable has type 'ui:search_input', it's a search query. foreach ( $input_schema as $input_var ) { if ( isset( $input_var['type'] ) && 'ui:search_input' === $input_var['type'] ) { @@ -291,6 +361,6 @@ private static function infer_query_type( array $input_schema, array $output_sch // This will happen if a query has not been configured correctly as a search or list query. // So we error out, to replace the previous way of validating when the type was set. - return self::create_error( 'Unknown query type', 'Could not infer the type of the query. Valid query types are "search" and "list".' ); + return 'unknown'; } } diff --git a/inc/ExampleApi/ExampleApi.php b/inc/ExampleApi/ExampleApi.php index b942dac77..eb2d459d4 100644 --- a/inc/ExampleApi/ExampleApi.php +++ b/inc/ExampleApi/ExampleApi.php @@ -124,12 +124,6 @@ public static function register_remote_data_block(): void { 'display' => $get_record_query, 'get_table' => $get_table_query, ], - 'placeholders' => [ - [ - 'name' => 'Get Record', - 'query_key' => 'display', - ], - ], ] ); } } diff --git a/remote-data-blocks.php b/remote-data-blocks.php index 8aa2f1f9b..1afe01e8c 100644 --- a/remote-data-blocks.php +++ b/remote-data-blocks.php @@ -66,7 +66,7 @@ // Plugin developers: If you need to register additional code for testing, you // can do so here, e.g.: -require_once __DIR__ . '/example/blocks/art-block/art-block.php'; -require_once __DIR__ . '/example/blocks/github-markdown-block/github-markdown-block.php'; -require_once __DIR__ . '/example/blocks/shopify-mock-store-block/shopify-mock-store-block.php'; -require_once __DIR__ . '/example/blocks/zip-code-block/zip-code-block.php'; +// require_once __DIR__ . '/example/blocks/art-block/art-block.php'; +// require_once __DIR__ . '/example/blocks/github-markdown-block/github-markdown-block.php'; +// require_once __DIR__ . '/example/blocks/shopify-mock-store-block/shopify-mock-store-block.php'; +// require_once __DIR__ . '/example/blocks/zip-code-block/zip-code-block.php'; From f85229775f8f97b4869821825f2b18d93f927951 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Tue, 10 Jun 2025 15:05:46 +1000 Subject: [PATCH 58/74] Add support for a query being a display, and selector query within the standard happy path. All the inline-block binding code has been commented out as that's not supported at the moment. --- .../BlockManagement/BlockRegistration.php | 1 - inc/Editor/BlockManagement/ConfigRegistry.php | 57 ++-- inc/Editor/BlockManagement/ConfigStore.php | 1 + inc/REST/RemoteDataController.php | 16 +- remote-data-blocks.php | 2 +- src/block-editor/filters/withBlockBinding.tsx | 4 +- .../components/InlineBindingButton.tsx | 262 +++++++-------- .../InlineBindingSelectExisting.tsx | 82 ++--- .../InlineBindingSelectFieldPopover.tsx | 148 ++++----- .../components/InlineBindingSelectMeta.tsx | 82 ++--- .../components/InlineBindingSelectNew.tsx | 136 ++++---- .../components/InlineBindingSelection.tsx | 308 +++++++++--------- .../format-types/inline-binding/index.ts | 16 +- .../components/QueryComponent.tsx | 18 +- .../components/modals/DataViewsModal.tsx | 20 +- .../components/modals/InputModal.tsx | 5 +- .../components/panels/QueryInputsPanel.tsx | 12 +- .../placeholders/ItemSelectQueryType.tsx | 10 +- .../QuerySelectionPlaceholder.tsx | 18 +- .../components/popovers/InputPopover.tsx | 7 +- src/blocks/remote-data-container/edit.tsx | 11 +- .../hooks/useRemoteData.ts | 19 +- .../hooks/useRemoteDataContext.ts | 3 +- src/utils/localized-block-data.ts | 13 - src/utils/remote-data.ts | 6 +- .../filters/withBlockBinding.test.tsx | 37 ++- .../components/modals/InputModal.test.tsx | 2 +- .../panels/QueryInputsPanel.test.tsx | 5 +- .../loop-template/LoopTemplate.test.tsx | 3 +- types/localized-block-data.d.ts | 3 +- types/remote-data.d.ts | 11 +- 31 files changed, 671 insertions(+), 647 deletions(-) diff --git a/inc/Editor/BlockManagement/BlockRegistration.php b/inc/Editor/BlockManagement/BlockRegistration.php index 085a3ffef..25ce6d8ec 100644 --- a/inc/Editor/BlockManagement/BlockRegistration.php +++ b/inc/Editor/BlockManagement/BlockRegistration.php @@ -117,7 +117,6 @@ public static function register_block_configuration( array $config ): array { 'name' => $block_name, 'dataSourceType' => ConfigStore::get_data_source_type( $block_name ), 'patterns' => $patterns, - 'selectors' => $config['selectors'], 'displayQueriesToSelectors' => $display_queries_to_selectors, 'settings' => [ 'category' => self::$block_category['slug'], diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index 02dfc56d3..efa76af27 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -48,9 +48,10 @@ public static function register_block( array $block_config = [] ): bool|WP_Error } $queries = []; - $selectors = []; + // ToDo: Add support for name, and icon. $display_queries_to_selectors_map = []; + // ToDo: Refactor to not be this crazy. if ( ! empty( $block_config[ self::PLACEHOLDERS_KEY ] ) ) { // Pre-validate the placeholders, to ensure they exist. foreach ( $block_config[ self::PLACEHOLDERS_KEY ] as $placeholder ) { @@ -80,7 +81,7 @@ public static function register_block( array $block_config = [] ): bool|WP_Error false ); - $selectors[] = [ + $selector_config = [ 'image_url' => $query->get_image_url(), 'inputs' => self::map_input_variables( $input_schema ), 'name' => $has_required_variables ? 'Manual input' : ( $is_collection ? 'Load collection' : 'Load item' ), @@ -88,7 +89,7 @@ public static function register_block( array $block_config = [] ): bool|WP_Error 'type' => $has_required_variables ? 'manual-input' : 'load-without-input', ]; - $display_queries_to_selectors_map[ $query_key ][] = $query_key; + $display_queries_to_selectors_map[ $query_key ][] = $selector_config; continue; } @@ -131,19 +132,19 @@ public static function register_block( array $block_config = [] ): bool|WP_Error } // Add the selector for the query to the beginning of the selectors array. + $selector_config = [ + 'image_url' => $query->get_image_url(), + 'inputs' => self::map_input_variables( $input_schema ), + 'name' => self::get_query_name_from_key( $query_key ), + 'query_key' => $query_key, + 'type' => $inferred_type, + ]; + array_unshift( - $selectors, - [ - 'image_url' => $query->get_image_url(), - 'inputs' => self::map_input_variables( $input_schema ), - 'name' => self::get_query_name_from_key( $query_key ), - 'query_key' => $query_key, - 'type' => $inferred_type, - ] + $display_queries_to_selectors_map[ $placeholder['query_key'] ], + $selector_config ); - $display_queries_to_selectors_map[ $placeholder['query_key'] ][] = $query_key; - // We have found the relevant display query, so we can break out of the loop. break; } @@ -153,11 +154,6 @@ public static function register_block( array $block_config = [] ): bool|WP_Error $placeholder_query = self::inflate_query( $placeholder_query ); $queries[ $placeholder_query_key ] = $placeholder_query; - // Skip if this query key is already mapped as a selector for any display query. - if ( in_array( $placeholder_query_key, array_merge( ...array_values( $display_queries_to_selectors_map ) ), true ) ) { - continue; - } - $placeholder_query_input_schema = $placeholder_query->get_input_schema(); $placeholder_query_output_schema = $placeholder_query->get_output_schema(); @@ -168,7 +164,7 @@ public static function register_block( array $block_config = [] ): bool|WP_Error false ); - $selectors[] = [ + $selector_config = [ 'image_url' => $placeholder_query->get_image_url(), 'inputs' => self::map_input_variables( $placeholder_query_input_schema ), 'name' => $has_required_variables ? 'Manual input' : ( $is_collection ? 'Load collection' : 'Load item' ), @@ -176,7 +172,7 @@ public static function register_block( array $block_config = [] ): bool|WP_Error 'type' => $has_required_variables ? 'manual-input' : 'load-without-input', ]; - $display_queries_to_selectors_map[ $placeholder_query_key ][] = $placeholder_query_key; + $display_queries_to_selectors_map[ $placeholder_query_key ][] = $selector_config; foreach ( $block_config[ self::QUERIES_KEY ] as $selector_query_key => $selector_query ) { $selector_query = self::inflate_query( $selector_query ); @@ -206,20 +202,18 @@ public static function register_block( array $block_config = [] ): bool|WP_Error return $validation_result; } + $selector_config = [ + 'image_url' => $selector_query->get_image_url(), + 'inputs' => self::map_input_variables( $selector_query_input_schema ), + 'name' => self::get_query_name_from_key( $selector_query_key ), + 'query_key' => $selector_query_key, + 'type' => $inferred_selector_query_type, + ]; - // Add the selector for the query to the beginning of the selectors array. array_unshift( - $selectors, - [ - 'image_url' => $selector_query->get_image_url(), - 'inputs' => self::map_input_variables( $selector_query_input_schema ), - 'name' => self::get_query_name_from_key( $selector_query_key ), - 'query_key' => $selector_query_key, - 'type' => $inferred_selector_query_type, - ] + $display_queries_to_selectors_map[ $placeholder_query_key ], + $selector_config ); - - $display_queries_to_selectors_map[ $placeholder_query_key ][] = $selector_query_key; } } } @@ -232,7 +226,6 @@ public static function register_block( array $block_config = [] ): bool|WP_Error 'overrides' => $block_config['overrides'] ?? [], 'patterns' => [], 'queries' => $queries, - 'selectors' => $selectors, 'display_queries_to_selectors' => $display_queries_to_selectors_map, 'title' => $block_title, ]; diff --git a/inc/Editor/BlockManagement/ConfigStore.php b/inc/Editor/BlockManagement/ConfigStore.php index 34bf9dd7d..d2d4ee35d 100644 --- a/inc/Editor/BlockManagement/ConfigStore.php +++ b/inc/Editor/BlockManagement/ConfigStore.php @@ -82,6 +82,7 @@ public static function get_data_source_type( string $block_name ): ?string { return null; } + // ToDo: Should there be any verification that the display queries are all from the same data source? // We are getting just the first display query in this instance, as they'd belong to the same data source. foreach ( array_keys( $display_queries_to_selectors ) as $display_query_key ) { $display_query = $config['queries'][ $display_query_key ]; diff --git a/inc/REST/RemoteDataController.php b/inc/REST/RemoteDataController.php index 21667bd71..ab78763b7 100644 --- a/inc/REST/RemoteDataController.php +++ b/inc/REST/RemoteDataController.php @@ -36,7 +36,13 @@ public static function register_rest_routes(): void { return null !== ConfigStore::get_block_configuration( $value ); }, ], - 'query_key' => [ + 'display_query_key' => [ + 'required' => true, + 'sanitize_callback' => function ( $value ) { + return strval( $value ); + }, + ], + 'selector_query_key' => [ 'required' => true, 'sanitize_callback' => function ( $value ) { return strval( $value ); @@ -54,11 +60,12 @@ public static function register_rest_routes(): void { public static function execute_queries( WP_REST_Request $request ): array|WP_Error { $block_name = $request->get_param( 'block_name' ); - $query_key = $request->get_param( 'query_key' ); + $display_query_key = $request->get_param( 'display_query_key' ); + $selector_query_key = $request->get_param( 'selector_query_key' ); $query_inputs = $request->get_param( 'query_inputs' ); $block_config = ConfigStore::get_block_configuration( $block_name ); - $query = $block_config['queries'][ $query_key ]; + $query = $block_config['queries'][ $selector_query_key ]; $query_response = $query->execute_batch( $query_inputs ); if ( is_wp_error( $query_response ) ) { @@ -72,7 +79,8 @@ public static function execute_queries( WP_REST_Request $request ): array|WP_Err [ 'block_name' => $block_name, 'result_id' => wp_generate_uuid4(), - 'query_key' => $query_key, + 'display_query_key' => $display_query_key, + 'selector_query_key' => $selector_query_key, ], $query_response ); diff --git a/remote-data-blocks.php b/remote-data-blocks.php index 1afe01e8c..f35640b02 100644 --- a/remote-data-blocks.php +++ b/remote-data-blocks.php @@ -66,7 +66,7 @@ // Plugin developers: If you need to register additional code for testing, you // can do so here, e.g.: -// require_once __DIR__ . '/example/blocks/art-block/art-block.php'; +require_once __DIR__ . '/example/blocks/art-block/art-block.php'; // require_once __DIR__ . '/example/blocks/github-markdown-block/github-markdown-block.php'; // require_once __DIR__ . '/example/blocks/shopify-mock-store-block/shopify-mock-store-block.php'; // require_once __DIR__ . '/example/blocks/zip-code-block/zip-code-block.php'; diff --git a/src/block-editor/filters/withBlockBinding.tsx b/src/block-editor/filters/withBlockBinding.tsx index a65d53050..115a94c58 100644 --- a/src/block-editor/filters/withBlockBinding.tsx +++ b/src/block-editor/filters/withBlockBinding.tsx @@ -112,11 +112,11 @@ export const withBlockBinding = createHigherOrderComponent( BlockEdit => { return ; } - const queryKey = remoteData.queryKey; + const displayQueryKey = remoteData.displayQueryKey; const availableBindings = getAvailableBindingsForQuery( remoteData?.blockName ?? '', - queryKey ?? '' + displayQueryKey ?? '' ); const hasAvailableBindings = Boolean( Object.keys( availableBindings ).length ); diff --git a/src/block-editor/format-types/inline-binding/components/InlineBindingButton.tsx b/src/block-editor/format-types/inline-binding/components/InlineBindingButton.tsx index 96a79b3bc..0c1a26437 100644 --- a/src/block-editor/format-types/inline-binding/components/InlineBindingButton.tsx +++ b/src/block-editor/format-types/inline-binding/components/InlineBindingButton.tsx @@ -1,144 +1,144 @@ -import { BlockControls } from '@wordpress/block-editor'; -import { ToolbarDropdownMenu, ToolbarGroup } from '@wordpress/components'; -import { useEffect, useState } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; -import { RichTextFormat, insertObject, WPFormatEditProps } from '@wordpress/rich-text'; +// import { BlockControls } from '@wordpress/block-editor'; +// import { ToolbarDropdownMenu, ToolbarGroup } from '@wordpress/components'; +// import { useEffect, useState } from '@wordpress/element'; +// import { __ } from '@wordpress/i18n'; +// import { RichTextFormat, insertObject, WPFormatEditProps } from '@wordpress/rich-text'; -import { InlineBindingSelectExisting } from '@/block-editor/format-types/inline-binding/components/InlineBindingSelectExisting'; -import { InlineBindingSelectFieldPopover } from '@/block-editor/format-types/inline-binding/components/InlineBindingSelectFieldPopover'; -import { InlineBindingSelectMeta } from '@/block-editor/format-types/inline-binding/components/InlineBindingSelectMeta'; -import { InlineBindingSelectNew } from '@/block-editor/format-types/inline-binding/components/InlineBindingSelectNew'; -import { useExistingRemoteData } from '@/block-editor/format-types/inline-binding/hooks/useExistingRemoteData'; -import { - formatName, - formatTypeSettings, -} from '@/block-editor/format-types/inline-binding/settings'; -import { sendTracksEvent } from '@/blocks/remote-data-container/utils/tracks'; -import { getBlockDataSourceType } from '@/utils/localized-block-data'; -import '@/block-editor/format-types/inline-binding/components/InlineBinding.scss'; +// import { InlineBindingSelectExisting } from '@/block-editor/format-types/inline-binding/components/InlineBindingSelectExisting'; +// import { InlineBindingSelectFieldPopover } from '@/block-editor/format-types/inline-binding/components/InlineBindingSelectFieldPopover'; +// import { InlineBindingSelectMeta } from '@/block-editor/format-types/inline-binding/components/InlineBindingSelectMeta'; +// import { InlineBindingSelectNew } from '@/block-editor/format-types/inline-binding/components/InlineBindingSelectNew'; +// import { useExistingRemoteData } from '@/block-editor/format-types/inline-binding/hooks/useExistingRemoteData'; +// import { +// formatName, +// formatTypeSettings, +// } from '@/block-editor/format-types/inline-binding/settings'; +// import { sendTracksEvent } from '@/blocks/remote-data-container/utils/tracks'; +// import { getBlockDataSourceType } from '@/utils/localized-block-data'; +// import '@/block-editor/format-types/inline-binding/components/InlineBinding.scss'; -function parseDataQuery( dataQuery?: string ): FieldSelection | null { - if ( ! dataQuery ) { - return null; - } +// function parseDataQuery( dataQuery?: string ): FieldSelection | null { +// if ( ! dataQuery ) { +// return null; +// } - try { - return JSON.parse( dataQuery ) as FieldSelection; - } catch ( _err ) { - return null; - } -} +// try { +// return JSON.parse( dataQuery ) as FieldSelection; +// } catch ( _err ) { +// return null; +// } +// } -export function InlineBindingButton( props: WPFormatEditProps ) { - const { onChange, onFocus, value, isObjectActive, activeObjectAttributes, contentRef } = props; - const fieldSelection = parseDataQuery( activeObjectAttributes?.[ 'data-query' ] ); - const [ showUI, setShowUI ] = useState< boolean >( false ); +// export function InlineBindingButton( props: WPFormatEditProps ) { +// const { onChange, onFocus, value, isObjectActive, activeObjectAttributes, contentRef } = props; +// const fieldSelection = parseDataQuery( activeObjectAttributes?.[ 'data-query' ] ); +// const [ showUI, setShowUI ] = useState< boolean >( false ); - useEffect( () => { - if ( isObjectActive ) { - setShowUI( true ); - } - }, [ isObjectActive ] ); +// useEffect( () => { +// if ( isObjectActive ) { +// setShowUI( true ); +// } +// }, [ isObjectActive ] ); - const updateOrInsertField = ( data: FieldSelection | null, fieldValue: string ) => { - // Only serialize a subset of necessary data. - const serializedData: Partial< FieldSelection > = { - remoteData: data?.remoteData, - selectedField: data?.selectedField, - type: data?.type, - }; +// const updateOrInsertField = ( data: FieldSelection | null, fieldValue: string ) => { +// // Only serialize a subset of necessary data. +// const serializedData: Partial< FieldSelection > = { +// remoteData: data?.remoteData, +// selectedField: data?.selectedField, +// type: data?.type, +// }; - const format: RichTextFormat = { - attributes: { - ...activeObjectAttributes, - 'data-query': data ? JSON.stringify( serializedData ) : '', - }, - innerHTML: fieldValue, - type: formatName, - }; +// const format: RichTextFormat = { +// attributes: { +// ...activeObjectAttributes, +// 'data-query': data ? JSON.stringify( serializedData ) : '', +// }, +// innerHTML: fieldValue, +// type: formatName, +// }; - onChange( - Object.keys( activeObjectAttributes ).length - ? { - ...value, - replacements: value.replacements.map( ( replacement, index ) => - index === value.start ? format : replacement - ), - } - : insertObject( value, format ) - ); - }; +// onChange( +// Object.keys( activeObjectAttributes ).length +// ? { +// ...value, +// replacements: value.replacements.map( ( replacement, index ) => +// index === value.start ? format : replacement +// ), +// } +// : insertObject( value, format ) +// ); +// }; - const onSelectField = ( data: FieldSelection, fieldValue: string ) => { - updateOrInsertField( data, fieldValue ); - setShowUI( false ); - onFocus(); - sendTracksEvent( 'field_shortcode', { - action: data.action, - data_source_type: getBlockDataSourceType( data.remoteData?.blockName ), - selection_path: data.selectionPath, - } ); - }; +// const onSelectField = ( data: FieldSelection, fieldValue: string ) => { +// updateOrInsertField( data, fieldValue ); +// setShowUI( false ); +// onFocus(); +// sendTracksEvent( 'field_shortcode', { +// action: data.action, +// data_source_type: getBlockDataSourceType( data.remoteData?.blockName ), +// selection_path: data.selectionPath, +// } ); +// }; - const resetField = ( blockName?: string ): void => { - updateOrInsertField( null, 'Unbound field' ); - sendTracksEvent( 'field_shortcode', { - action: 'reset_field_shortcode', - data_source_type: getBlockDataSourceType( blockName ), - } ); - }; +// const resetField = ( blockName?: string ): void => { +// updateOrInsertField( null, 'Unbound field' ); +// sendTracksEvent( 'field_shortcode', { +// action: 'reset_field_shortcode', +// data_source_type: getBlockDataSourceType( blockName ), +// } ); +// }; - const remoteData = useExistingRemoteData(); +// const remoteData = useExistingRemoteData(); - return ( - <> - - - { remoteData.length > 0 ? ( - - { () => ( - - - - - - ) } - - ) : ( - - ) } - - +// return ( +// <> +// +// +// { remoteData.length > 0 ? ( +// +// { () => ( +// +// +// +// +// +// ) } +// +// ) : ( +// +// ) } +// +// - { showUI && fieldSelection && ( - { - setShowUI( false ); - onFocus(); - } } - onSelectField={ ( data, fieldValue ) => - onSelectField( { ...data, selectionPath: 'popover' }, fieldValue ) - } - resetField={ resetField } - /> - ) } - - ); -} +// { showUI && fieldSelection && ( +// { +// setShowUI( false ); +// onFocus(); +// } } +// onSelectField={ ( data, fieldValue ) => +// onSelectField( { ...data, selectionPath: 'popover' }, fieldValue ) +// } +// resetField={ resetField } +// /> +// ) } +// +// ); +// } diff --git a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectExisting.tsx b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectExisting.tsx index 37dec4d81..de13da97b 100644 --- a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectExisting.tsx +++ b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectExisting.tsx @@ -1,45 +1,45 @@ -import { DropdownMenu, MenuGroup } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { chevronRightSmall } from '@wordpress/icons'; +// import { DropdownMenu, MenuGroup } from '@wordpress/components'; +// import { __ } from '@wordpress/i18n'; +// import { chevronRightSmall } from '@wordpress/icons'; -import { FieldSelectionFromAvailableBindings } from '@/block-editor/format-types/inline-binding/components/InlineBindingSelection'; -import { getBlocksConfig } from '@/utils/localized-block-data'; +// import { FieldSelectionFromAvailableBindings } from '@/block-editor/format-types/inline-binding/components/InlineBindingSelection'; +// import { getBlocksConfig } from '@/utils/localized-block-data'; -interface InlineBindingSelectExistingProps { - onSelectField: ( data: FieldSelection, fieldValue: string ) => void; - remoteData: RemoteData[]; -} +// interface InlineBindingSelectExistingProps { +// onSelectField: ( data: FieldSelection, fieldValue: string ) => void; +// remoteData: RemoteData[]; +// } -export function InlineBindingSelectExisting( props: InlineBindingSelectExistingProps ) { - const blockConfigs = getBlocksConfig(); - const { remoteData: remoteDatas } = props; +// export function InlineBindingSelectExisting( props: InlineBindingSelectExistingProps ) { +// const blockConfigs = getBlocksConfig(); +// const { remoteData: remoteDatas } = props; - return remoteDatas.length > 0 ? ( - - { () => - remoteDatas.map( remoteData => ( - - - props.onSelectField( { ...data, selectionPath: 'select_existing_tab' }, fieldValue ) - } - remoteData={ remoteData } - /> - - ) ) - } - - ) : undefined; -} +// return remoteDatas.length > 0 ? ( +// +// { () => +// remoteDatas.map( remoteData => ( +// +// +// props.onSelectField( { ...data, selectionPath: 'select_existing_tab' }, fieldValue ) +// } +// remoteData={ remoteData } +// /> +// +// ) ) +// } +// +// ) : undefined; +// } diff --git a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectFieldPopover.tsx b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectFieldPopover.tsx index 04d476621..9da774e66 100644 --- a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectFieldPopover.tsx +++ b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectFieldPopover.tsx @@ -1,80 +1,80 @@ -import { - Button, - Card, - CardBody, - CardFooter, - CardHeader, - __experimentalHeading as Heading, - Popover, -} from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { WPFormat, useAnchor } from '@wordpress/rich-text'; +// import { +// Button, +// Card, +// CardBody, +// CardFooter, +// CardHeader, +// __experimentalHeading as Heading, +// Popover, +// } from '@wordpress/components'; +// import { __ } from '@wordpress/i18n'; +// import { WPFormat, useAnchor } from '@wordpress/rich-text'; -import { InlineBindingSelectField } from '@/block-editor/format-types/inline-binding/components/InlineBindingSelection'; -import { getBlockConfig, getDisplayQueryKeyFromQueryKey } from '@/utils/localized-block-data'; +// import { InlineBindingSelectField } from '@/block-editor/format-types/inline-binding/components/InlineBindingSelection'; +// import { getBlockConfig, getDisplayQueryKeyFromQueryKey } from '@/utils/localized-block-data'; -interface InlineBindingSelectFieldPopoverProps { - contentRef: React.RefObject< HTMLElement >; - fieldSelection: FieldSelection; - formatTypeSettings: WPFormat; - onSelectField: ( data: FieldSelection, fieldValue: string ) => void; - onClose: () => void; - resetField: ( blockName?: string ) => void; -} +// interface InlineBindingSelectFieldPopoverProps { +// contentRef: React.RefObject< HTMLElement >; +// fieldSelection: FieldSelection; +// formatTypeSettings: WPFormat; +// onSelectField: ( data: FieldSelection, fieldValue: string ) => void; +// onClose: () => void; +// resetField: ( blockName?: string ) => void; +// } -export function InlineBindingSelectFieldPopover( props: InlineBindingSelectFieldPopoverProps ) { - const popoverAnchor = useAnchor( { - editableContentElement: props.contentRef.current, - settings: props.formatTypeSettings, - } ); - const { remoteData, selectedField, type } = props.fieldSelection; +// export function InlineBindingSelectFieldPopover( props: InlineBindingSelectFieldPopoverProps ) { +// const popoverAnchor = useAnchor( { +// editableContentElement: props.contentRef.current, +// settings: props.formatTypeSettings, +// } ); +// const { remoteData, selectedField, type } = props.fieldSelection; - // For now, we will use the first compatible selector, but this should be improved. - // Same as InlineBindingSelection.tsx - const compatibleSelector = getBlockConfig( remoteData?.blockName ?? '' )?.selectors.find( - selector => [ 'list', 'search' ].includes( selector.type ) - ); +// // For now, we will use the first compatible selector, but this should be improved. +// // Same as InlineBindingSelection.tsx +// const compatibleSelector = getBlockConfig( remoteData?.blockName ?? '' )?.selectors.find( +// selector => [ 'list', 'search' ].includes( selector.type ) +// ); - const queryKey: string = - remoteData?.queryKey ?? - getDisplayQueryKeyFromQueryKey( - remoteData?.blockName ?? '', - compatibleSelector?.query_key ?? '' - ); +// const queryKey: string = +// remoteData?.queryKey ?? +// getDisplayQueryKeyFromQueryKey( +// remoteData?.blockName ?? '', +// compatibleSelector?.query_key ?? '' +// ); - return ( - - - - { __( 'Select a field to bind', 'remote-data-blocks' ) } - - - - props.onSelectField( { ...data, action: 'update_field_shortcode' }, fieldValue ) - } - queryInputs={ remoteData?.queryInputs ?? [ {} ] } - selectedField={ selectedField } - /> - - - - - - - ); -} +// return ( +// +// +// +// { __( 'Select a field to bind', 'remote-data-blocks' ) } +// +// +// +// props.onSelectField( { ...data, action: 'update_field_shortcode' }, fieldValue ) +// } +// queryInputs={ remoteData?.queryInputs ?? [ {} ] } +// selectedField={ selectedField } +// /> +// +// +// +// +// +// +// ); +// } diff --git a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectMeta.tsx b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectMeta.tsx index bba41f2e0..eae132187 100644 --- a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectMeta.tsx +++ b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectMeta.tsx @@ -1,45 +1,45 @@ -import { DropdownMenu, MenuGroup } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { chevronRightSmall } from '@wordpress/icons'; +// import { DropdownMenu, MenuGroup } from '@wordpress/components'; +// import { __ } from '@wordpress/i18n'; +// import { chevronRightSmall } from '@wordpress/icons'; -import { FieldSelectionFromMetaFields } from '@/block-editor/format-types/inline-binding/components/InlineBindingSelection'; -import { useExistingRemoteData } from '@/block-editor/format-types/inline-binding/hooks/useExistingRemoteData'; -import { getBlocksConfig } from '@/utils/localized-block-data'; +// import { FieldSelectionFromMetaFields } from '@/block-editor/format-types/inline-binding/components/InlineBindingSelection'; +// import { useExistingRemoteData } from '@/block-editor/format-types/inline-binding/hooks/useExistingRemoteData'; +// import { getBlocksConfig } from '@/utils/localized-block-data'; -interface InlineBindingSelectMetaProps { - onSelectField: ( data: FieldSelection, fieldValue: string ) => void; -} +// interface InlineBindingSelectMetaProps { +// onSelectField: ( data: FieldSelection, fieldValue: string ) => void; +// } -export function InlineBindingSelectMeta( props: InlineBindingSelectMetaProps ) { - const blockConfigs = getBlocksConfig(); - const remoteDatas: RemoteData[] = useExistingRemoteData(); +// export function InlineBindingSelectMeta( props: InlineBindingSelectMetaProps ) { +// const blockConfigs = getBlocksConfig(); +// const remoteDatas: RemoteData[] = useExistingRemoteData(); - return remoteDatas.length > 0 ? ( - - { () => - remoteDatas.map( remoteData => ( - - - props.onSelectField( { ...data, selectionPath: 'select_meta_tab' }, fieldValue ) - } - remoteData={ remoteData } - /> - - ) ) - } - - ) : undefined; -} +// return remoteDatas.length > 0 ? ( +// +// { () => +// remoteDatas.map( remoteData => ( +// +// +// props.onSelectField( { ...data, selectionPath: 'select_meta_tab' }, fieldValue ) +// } +// remoteData={ remoteData } +// /> +// +// ) ) +// } +// +// ) : undefined; +// } diff --git a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectNew.tsx b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectNew.tsx index 93646a47e..8b241b245 100644 --- a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectNew.tsx +++ b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectNew.tsx @@ -1,74 +1,74 @@ -import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components'; -import { DropdownMenuProps } from '@wordpress/components/build-types/dropdown-menu/types'; -import { __ } from '@wordpress/i18n'; -import { chevronRightSmall } from '@wordpress/icons'; +// import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components'; +// import { DropdownMenuProps } from '@wordpress/components/build-types/dropdown-menu/types'; +// import { __ } from '@wordpress/i18n'; +// import { chevronRightSmall } from '@wordpress/icons'; -import { DataViewsModal } from '@/blocks/remote-data-container/components/modals/DataViewsModal'; -import { getBlocksConfig } from '@/utils/localized-block-data'; +// import { DataViewsModal } from '@/blocks/remote-data-container/components/modals/DataViewsModal'; +// import { getBlocksConfig } from '@/utils/localized-block-data'; -type InlineBindingSelectNewProps = Omit< DropdownMenuProps, 'label' > & { - onSelectField: ( data: FieldSelection, fieldValue: string ) => void; - label?: string; -}; +// type InlineBindingSelectNewProps = Omit< DropdownMenuProps, 'label' > & { +// onSelectField: ( data: FieldSelection, fieldValue: string ) => void; +// label?: string; +// }; -export function InlineBindingSelectNew( props: InlineBindingSelectNewProps ) { - const { onSelectField, ...restProps } = props; - const blockConfigs = getBlocksConfig(); - const blocksByType = Object.values( blockConfigs ).reduce< - Record< string, Array< BlocksConfig[ keyof BlocksConfig ] > > - >( ( source, blockConfig ) => { - const type = blockConfig.dataSourceType; - if ( ! source[ type ] ) { - source[ type ] = []; - } - source[ type ].push( blockConfig ); - return source; - }, {} ); +// export function InlineBindingSelectNew( props: InlineBindingSelectNewProps ) { +// const { onSelectField, ...restProps } = props; +// const blockConfigs = getBlocksConfig(); +// const blocksByType = Object.values( blockConfigs ).reduce< +// Record< string, Array< BlocksConfig[ keyof BlocksConfig ] > > +// >( ( source, blockConfig ) => { +// const type = blockConfig.dataSourceType; +// if ( ! source[ type ] ) { +// source[ type ] = []; +// } +// source[ type ].push( blockConfig ); +// return source; +// }, {} ); - return ( - - { () => - Object.entries( blocksByType ).map( ( [ dataSourceType, configs ] ) => ( - - { configs.map( blockConfig => { - // For now, we will use the first compatible selector, but this - // should be improved. - const compatibleSelector = blockConfig.selectors.find( selector => - [ 'list', 'search' ].includes( selector.type ) - ); +// return ( +// +// { () => +// Object.entries( blocksByType ).map( ( [ dataSourceType, configs ] ) => ( +// +// { configs.map( blockConfig => { +// // For now, we will use the first compatible selector, but this +// // should be improved. +// const compatibleSelector = blockConfig.selectors.find( selector => +// [ 'list', 'search' ].includes( selector.type ) +// ); - if ( ! compatibleSelector ) { - return null; - } +// if ( ! compatibleSelector ) { +// return null; +// } - return ( - ( - - { blockConfig.settings?.title ?? blockConfig.name } - - ) } - /> - ); - } ) } - - ) ) - } - - ); -} +// return ( +// ( +// +// { blockConfig.settings?.title ?? blockConfig.name } +// +// ) } +// /> +// ); +// } ) } +// +// ) ) +// } +// +// ); +// } diff --git a/src/block-editor/format-types/inline-binding/components/InlineBindingSelection.tsx b/src/block-editor/format-types/inline-binding/components/InlineBindingSelection.tsx index 4b414dd42..80bc06efd 100644 --- a/src/block-editor/format-types/inline-binding/components/InlineBindingSelection.tsx +++ b/src/block-editor/format-types/inline-binding/components/InlineBindingSelection.tsx @@ -1,154 +1,154 @@ -import { BaseControl, Icon, MenuItem, Spinner } from '@wordpress/components'; -import { useEffect } from '@wordpress/element'; -import { check } from '@wordpress/icons'; - -import { TEXT_FIELD_TYPES } from '@/blocks/remote-data-container/config/constants'; -import { useRemoteData } from '@/blocks/remote-data-container/hooks/useRemoteData'; -import { getAvailableBindingsForQuery } from '@/utils/localized-block-data'; -import { getRemoteDataResultValue } from '@/utils/remote-data'; - -interface FieldSelectionProps { - fields: Record< string, { name: string; value: string } >; - onSelectField: ( data: FieldSelection, fieldValue: string ) => void; - selectedField?: string; - remoteData: RemoteData; - fieldType: 'field' | 'meta'; -} - -export function FieldSelection( props: FieldSelectionProps ) { - return ( - <> - { Object.entries( props.fields ).map( ( [ fieldName, fieldDetails ], index ) => { - const fieldSelection: FieldSelection = { - action: 'add_field_shortcode', - selectedField: fieldName, - remoteData: props.remoteData, - type: props.fieldType, - selectionPath: 'select_new_tab', - }; - - return ( - { - evt.preventDefault(); - props.onSelectField( fieldSelection, fieldDetails.value ); - } } - onKeyDown={ evt => { - if ( evt.key.toLowerCase() === 'enter' ) { - props.onSelectField( fieldSelection, fieldDetails.value ); - } - } } - suffix={ - props.selectedField === fieldName ? ( - - ) : undefined - } - > - - - { fieldDetails.name }: - - { fieldDetails.value?.toString() } - - - ); - } ) } - - ); -} - -type FieldSelectionWithFieldsProps = Omit< FieldSelectionProps, 'fields' | 'fieldType' >; - -export function FieldSelectionFromAvailableBindings( props: FieldSelectionWithFieldsProps ) { - const availableBindings = getAvailableBindingsForQuery( - props.remoteData.blockName, - props.remoteData.queryKey ?? '' - ); - - const fields = Object.entries( availableBindings ).reduce< FieldSelectionProps[ 'fields' ] >( - ( acc, [ fieldName, binding ] ) => { - const fieldValue = getRemoteDataResultValue( props.remoteData.results[ 0 ], fieldName ); - if ( ! fieldValue || ! TEXT_FIELD_TYPES.includes( binding.type ) ) { - return acc; - } - - return { - ...acc, - [ fieldName ]: { - name: binding.name, - value: fieldValue, - }, - }; - }, - {} - ); - - return ; -} - -export function FieldSelectionFromMetaFields( props: FieldSelectionWithFieldsProps ) { - const fields: FieldSelectionProps[ 'fields' ] = Object.fromEntries( - Object.entries( props.remoteData.metadata ?? {} ).map( ( [ fieldName, metadatum ] ) => [ - fieldName, - { - name: metadatum.name, - value: metadatum.value?.toString() ?? '', - }, - ] ) - ); - - return ; -} - -interface InlineBindingSelectFieldProps { - blockName: string; - fieldType: 'field' | 'meta'; - onSelectField: ( data: FieldSelection, fieldValue: string ) => void; - queryInputs: RemoteDataQueryInput[]; - selectedField?: string; - queryKey: string; -} - -export function InlineBindingSelectField( props: InlineBindingSelectFieldProps ) { - const { data, fetch, loading } = useRemoteData( { - blockName: props.blockName, - queryKey: props.queryKey, - } ); - - useEffect( () => { - if ( loading || data ) { - return; - } - - void fetch( props.queryInputs ); - }, [ loading, data ] ); - - if ( ! data || loading ) { - return ; - } - - const selectionProps: FieldSelectionWithFieldsProps = { - onSelectField: props.onSelectField, - remoteData: data, - selectedField: props.selectedField, - }; - - if ( 'meta' === props.fieldType ) { - return ; - } - - return ; -} +// import { BaseControl, Icon, MenuItem, Spinner } from '@wordpress/components'; +// import { useEffect } from '@wordpress/element'; +// import { check } from '@wordpress/icons'; + +// import { TEXT_FIELD_TYPES } from '@/blocks/remote-data-container/config/constants'; +// import { useRemoteData } from '@/blocks/remote-data-container/hooks/useRemoteData'; +// import { getAvailableBindingsForQuery } from '@/utils/localized-block-data'; +// import { getRemoteDataResultValue } from '@/utils/remote-data'; + +// interface FieldSelectionProps { +// fields: Record< string, { name: string; value: string } >; +// onSelectField: ( data: FieldSelection, fieldValue: string ) => void; +// selectedField?: string; +// remoteData: RemoteData; +// fieldType: 'field' | 'meta'; +// } + +// export function FieldSelection( props: FieldSelectionProps ) { +// return ( +// <> +// { Object.entries( props.fields ).map( ( [ fieldName, fieldDetails ], index ) => { +// const fieldSelection: FieldSelection = { +// action: 'add_field_shortcode', +// selectedField: fieldName, +// remoteData: props.remoteData, +// type: props.fieldType, +// selectionPath: 'select_new_tab', +// }; + +// return ( +// { +// evt.preventDefault(); +// props.onSelectField( fieldSelection, fieldDetails.value ); +// } } +// onKeyDown={ evt => { +// if ( evt.key.toLowerCase() === 'enter' ) { +// props.onSelectField( fieldSelection, fieldDetails.value ); +// } +// } } +// suffix={ +// props.selectedField === fieldName ? ( +// +// ) : undefined +// } +// > +// +// +// { fieldDetails.name }: +// +// { fieldDetails.value?.toString() } +// +// +// ); +// } ) } +// +// ); +// } + +// type FieldSelectionWithFieldsProps = Omit< FieldSelectionProps, 'fields' | 'fieldType' >; + +// export function FieldSelectionFromAvailableBindings( props: FieldSelectionWithFieldsProps ) { +// const availableBindings = getAvailableBindingsForQuery( +// props.remoteData.blockName, +// props.remoteData.queryKey ?? '' +// ); + +// const fields = Object.entries( availableBindings ).reduce< FieldSelectionProps[ 'fields' ] >( +// ( acc, [ fieldName, binding ] ) => { +// const fieldValue = getRemoteDataResultValue( props.remoteData.results[ 0 ], fieldName ); +// if ( ! fieldValue || ! TEXT_FIELD_TYPES.includes( binding.type ) ) { +// return acc; +// } + +// return { +// ...acc, +// [ fieldName ]: { +// name: binding.name, +// value: fieldValue, +// }, +// }; +// }, +// {} +// ); + +// return ; +// } + +// export function FieldSelectionFromMetaFields( props: FieldSelectionWithFieldsProps ) { +// const fields: FieldSelectionProps[ 'fields' ] = Object.fromEntries( +// Object.entries( props.remoteData.metadata ?? {} ).map( ( [ fieldName, metadatum ] ) => [ +// fieldName, +// { +// name: metadatum.name, +// value: metadatum.value?.toString() ?? '', +// }, +// ] ) +// ); + +// return ; +// } + +// interface InlineBindingSelectFieldProps { +// blockName: string; +// fieldType: 'field' | 'meta'; +// onSelectField: ( data: FieldSelection, fieldValue: string ) => void; +// queryInputs: RemoteDataQueryInput[]; +// selectedField?: string; +// queryKey: string; +// } + +// export function InlineBindingSelectField( props: InlineBindingSelectFieldProps ) { +// const { data, fetch, loading } = useRemoteData( { +// blockName: props.blockName, +// queryKey: props.queryKey, +// } ); + +// useEffect( () => { +// if ( loading || data ) { +// return; +// } + +// void fetch( props.queryInputs ); +// }, [ loading, data ] ); + +// if ( ! data || loading ) { +// return ; +// } + +// const selectionProps: FieldSelectionWithFieldsProps = { +// onSelectField: props.onSelectField, +// remoteData: data, +// selectedField: props.selectedField, +// }; + +// if ( 'meta' === props.fieldType ) { +// return ; +// } + +// return ; +// } diff --git a/src/block-editor/format-types/inline-binding/index.ts b/src/block-editor/format-types/inline-binding/index.ts index 9ba452003..8b04b7e3c 100644 --- a/src/block-editor/format-types/inline-binding/index.ts +++ b/src/block-editor/format-types/inline-binding/index.ts @@ -1,10 +1,10 @@ -import { registerFormatType } from '@wordpress/rich-text'; +// import { registerFormatType } from '@wordpress/rich-text'; -import { InlineBindingButton } from '@/block-editor/format-types/inline-binding/components/InlineBindingButton'; -import { formatTypeSettings } from '@/block-editor/format-types/inline-binding/settings'; +// import { InlineBindingButton } from '@/block-editor/format-types/inline-binding/components/InlineBindingButton'; +// import { formatTypeSettings } from '@/block-editor/format-types/inline-binding/settings'; -// Register the inline binding format type. -registerFormatType( 'remote-data-blocks/inline-binding', { - ...formatTypeSettings, - edit: InlineBindingButton, -} ); +// // Register the inline binding format type. +// registerFormatType( 'remote-data-blocks/inline-binding', { +// ...formatTypeSettings, +// edit: InlineBindingButton, +// } ); diff --git a/src/blocks/remote-data-container/components/QueryComponent.tsx b/src/blocks/remote-data-container/components/QueryComponent.tsx index 88588db18..5cfa0e621 100644 --- a/src/blocks/remote-data-container/components/QueryComponent.tsx +++ b/src/blocks/remote-data-container/components/QueryComponent.tsx @@ -50,7 +50,8 @@ export function QueryComponent( props: QueryComponentProps ) { blockName, externallyManagedRemoteData: remoteDataAttribute, externallyManagedUpdateRemoteData: updateRemoteData, - queryKey: displayQueryKey, + displayQueryKey, + selectorQueryKey: displayQueryKey, } ); const { hasMultiSelection } = useSelect< BlockEditorStoreSelectors >( blockEditorStore ); @@ -102,7 +103,10 @@ export function QueryComponent( props: QueryComponentProps ) { } } - function onUpdateQueryInputs( newQueryKey: string, inputs: RemoteDataQueryInput[] ): void { + function onUpdateQueryInputs( + newSelectorQueryKey: string, + inputs: RemoteDataQueryInput[] + ): void { if ( ! remoteDataAttribute ) { return; } @@ -110,16 +114,12 @@ export function QueryComponent( props: QueryComponentProps ) { updateRemoteData( { ...remoteDataAttribute, queryInputs: inputs, - queryKey: newQueryKey, + selectorQueryKey: newSelectorQueryKey, + displayQueryKey, } ); refreshRemoteData(); } - function getSelectorsForDisplayQueryKey(): Selector[] { - const selectorKeys = blockConfig.displayQueriesToSelectors[ displayQueryKey ] ?? []; - return blockConfig.selectors.filter( selector => selectorKeys?.includes( selector.query_key ) ); - } - if ( showPatternSelection ) { const supportedPatterns = getSupportedPatterns( data?.results[ 0 ] ); @@ -152,7 +152,7 @@ export function QueryComponent( props: QueryComponentProps ) { ) } diff --git a/src/blocks/remote-data-container/components/modals/DataViewsModal.tsx b/src/blocks/remote-data-container/components/modals/DataViewsModal.tsx index 3918573c7..aee2122ca 100644 --- a/src/blocks/remote-data-container/components/modals/DataViewsModal.tsx +++ b/src/blocks/remote-data-container/components/modals/DataViewsModal.tsx @@ -13,15 +13,25 @@ interface DataViewsModalProps { className?: string; blockName: string; headerImage?: string; - onSelect?: ( data: RemoteDataQueryInput[] ) => void; + onSelect?: ( data: RemoteDataQueryInput[], selectorQueryKey?: string ) => void; onSelectField?: ( data: FieldSelection, fieldValue: string ) => void; - queryKey: string; + selectorQueryKey: string; + displayQueryKey: string; renderTrigger?: ( props: { onClick: () => void } ) => React.ReactNode; title?: string; } export const DataViewsModal: React.FC< DataViewsModalProps > = props => { - const { className, blockName, onSelect, onSelectField, queryKey, renderTrigger, title } = props; + const { + className, + blockName, + onSelect, + onSelectField, + selectorQueryKey, + displayQueryKey, + renderTrigger, + title, + } = props; const blockConfig = getBlockConfig( blockName ); @@ -45,7 +55,7 @@ export const DataViewsModal: React.FC< DataViewsModalProps > = props => { supportsSearch, totalItems, totalPages, - } = useRemoteData( { blockName, fetchOnMount: true, queryKey } ); + } = useRemoteData( { blockName, fetchOnMount: true, displayQueryKey, selectorQueryKey } ); // For selection, DataViews transacts only in IDs, so we provide the UUID from // the API response as a synthetic ID and map them to the full result. @@ -81,7 +91,7 @@ export const DataViewsModal: React.FC< DataViewsModalProps > = props => { return; } - onSelect?.( createQueryInputsFromRemoteDataResults( results ) ); + onSelect?.( createQueryInputsFromRemoteDataResults( results ), selectorQueryKey ); sendTracksEvent( 'add_block', { action: 'select_item', selected_option: 'search_from_list', diff --git a/src/blocks/remote-data-container/components/modals/InputModal.tsx b/src/blocks/remote-data-container/components/modals/InputModal.tsx index 8f3744a17..b3c35a6c3 100644 --- a/src/blocks/remote-data-container/components/modals/InputModal.tsx +++ b/src/blocks/remote-data-container/components/modals/InputModal.tsx @@ -16,7 +16,8 @@ interface InputModalProps { blockName: string; headerImage?: string; inputs: InputVariable[]; - onSelect: ( data: RemoteDataQueryInput[] ) => void; + onSelect: ( data: RemoteDataQueryInput[], selectorQueryKey?: string ) => void; + selectorQueryKey: string; title: string; } @@ -34,7 +35,7 @@ export function InputModal( props: InputModalProps ) { } function onSelectItem(): void { - props.onSelect( [ inputState ] ); + props.onSelect( [ inputState ], props.selectorQueryKey ); close(); sendTracksEvent( 'add_block', { action: 'select_item', diff --git a/src/blocks/remote-data-container/components/panels/QueryInputsPanel.tsx b/src/blocks/remote-data-container/components/panels/QueryInputsPanel.tsx index aa753aff5..25520bb4f 100644 --- a/src/blocks/remote-data-container/components/panels/QueryInputsPanel.tsx +++ b/src/blocks/remote-data-container/components/panels/QueryInputsPanel.tsx @@ -3,9 +3,9 @@ import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; interface QueryInputsPanelProps { - onUpdateQueryInputs: ( queryKey: string, inputs: RemoteDataQueryInput[] ) => void; + onUpdateQueryInputs: ( selectorQueryKey: string, inputs: RemoteDataQueryInput[] ) => void; remoteData: RemoteData; - selectors: BlockConfig[ 'selectors' ]; + selectors: Selector[]; } export function QueryInputsPanel( { @@ -13,10 +13,10 @@ export function QueryInputsPanel( { remoteData, selectors, }: QueryInputsPanelProps ) { - const { queryInputs = [], queryKey = '' } = remoteData; + const { queryInputs = [], selectorQueryKey = '' } = remoteData; const [ localInputs, setLocalInputs ] = useState( queryInputs ); const inputDefinitions = - selectors?.find( selector => selector.query_key === queryKey )?.inputs ?? []; + selectors?.find( selector => selector.query_key === selectorQueryKey )?.inputs ?? []; return ( @@ -37,7 +37,7 @@ export function QueryInputsPanel( { return Object.fromEntries( entries ) as RemoteDataQueryInput; } ); - onUpdateQueryInputs( queryKey, cleanedInputs ); + onUpdateQueryInputs( selectorQueryKey, cleanedInputs ); } } > { localInputs.map( ( input, index ) => @@ -58,7 +58,7 @@ export function QueryInputsPanel( { ); } } onBlur={ () => { - onUpdateQueryInputs( queryKey, localInputs ); + onUpdateQueryInputs( selectorQueryKey, localInputs ); } } __next40pxDefaultSize __nextHasNoMarginBottom diff --git a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx index abcaef71e..c0b2fbf17 100644 --- a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx +++ b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx @@ -11,11 +11,12 @@ import { InputPopover } from '@/blocks/remote-data-container/components/popovers interface ItemSelectQueryTypeProps { blockName: string; selectors: Selector[]; - onSelect: ( data: RemoteDataQueryInput[] ) => void; + displayQueryKey: string; + onSelect: ( data: RemoteDataQueryInput[], selectorQueryKey?: string ) => void; } export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) { - const { blockName, selectors, onSelect } = props; + const { blockName, selectors, displayQueryKey, onSelect } = props; return ( { - onSelect( [ {} ] ); + onSelect( [ {} ], selector.query_key ); } } variant="primary" > diff --git a/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx b/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx index 00ba21524..2b5c1ceb5 100644 --- a/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx +++ b/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx @@ -13,12 +13,14 @@ import { ItemSelectQueryType } from './ItemSelectQueryType'; export interface QuerySelectionPlaceholderProps { blockConfig: BlockConfig; onDisplayQueryKeySelect: ( displayQueryKey: string ) => void; + onSelectorQueryKeySelect: ( selectorQueryKey: string ) => void; onQueryInputsSelect: ( inputs: RemoteDataQueryInput[] ) => void; } export function QuerySelectionPlaceholder( props: QuerySelectionPlaceholderProps ) { - const { blockConfig, onDisplayQueryKeySelect, onQueryInputsSelect } = props; - const { instructions, settings, selectors, displayQueriesToSelectors } = blockConfig; + const { blockConfig, onDisplayQueryKeySelect, onSelectorQueryKeySelect, onQueryInputsSelect } = + props; + const { instructions, settings, displayQueriesToSelectors } = blockConfig; const iconElement: IconType = ( settings.icon as IconType ) ?? cloud; @@ -28,9 +30,11 @@ export function QuerySelectionPlaceholder( props: QuerySelectionPlaceholderProps const [ selectedDisplayQueryKey, setSelectedDisplayQueryKey ] = useState< string >( '' ); const [ showSelectors, setShowSelectors ] = useState< boolean >( false ); - function handleSelectorOnSelect( inputs: RemoteDataQueryInput[] ) { + function handleSelectorOnSelect( inputs: RemoteDataQueryInput[], selectorQueryKey?: string ) { setShowSelectors( false ); onDisplayQueryKeySelect( selectedDisplayQueryKey ); + // ToDo: Should the selector key be set to the display query key if no selector query key is provided? + onSelectorQueryKeySelect( selectorQueryKey ?? selectedDisplayQueryKey ); onQueryInputsSelect( inputs ); } @@ -39,11 +43,6 @@ export function QuerySelectionPlaceholder( props: QuerySelectionPlaceholderProps setShowSelectors( true ); } - function getSelectorsForDisplayQueryKey(): Selector[] { - const selectorKeys = displayQueriesToSelectors[ selectedDisplayQueryKey ] ?? []; - return selectors.filter( selector => selectorKeys?.includes( selector.query_key ) ); - } - return ( ) } diff --git a/src/blocks/remote-data-container/components/popovers/InputPopover.tsx b/src/blocks/remote-data-container/components/popovers/InputPopover.tsx index cb34f3657..c911d1a7e 100644 --- a/src/blocks/remote-data-container/components/popovers/InputPopover.tsx +++ b/src/blocks/remote-data-container/components/popovers/InputPopover.tsx @@ -18,12 +18,13 @@ interface InputPopoverProps { blockName: string; headerImage?: string; input: InputVariable; - onSelect: ( data: RemoteDataQueryInput[] ) => void; + onSelect: ( data: RemoteDataQueryInput[], selectorQueryKey?: string ) => void; + selectorQueryKey: string; title: string; } export function InputPopover( props: InputPopoverProps ) { - const { input, onSelect, title } = props; + const { input, onSelect, selectorQueryKey, title } = props; const dataSourceType = getBlockDataSourceType( props.blockName ); @@ -37,7 +38,7 @@ export function InputPopover( props: InputPopoverProps ) { } function onSelectItem(): void { - onSelect( [ inputState ] ); + onSelect( [ inputState ], selectorQueryKey ); close(); sendTracksEvent( 'add_block', { action: 'select_item', diff --git a/src/blocks/remote-data-container/edit.tsx b/src/blocks/remote-data-container/edit.tsx index 64fb73b27..a7723e1a5 100644 --- a/src/blocks/remote-data-container/edit.tsx +++ b/src/blocks/remote-data-container/edit.tsx @@ -4,7 +4,7 @@ import { useState } from '@wordpress/element'; import { EditErrorBoundary } from './components/EditErrorBoundary'; import { QueryComponent } from './components/QueryComponent'; import { QuerySelectionPlaceholder } from '@/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder'; -import { getBlockConfig, getDisplayQueryKeyFromQueryKey } from '@/utils/localized-block-data'; +import { getBlockConfig } from '@/utils/localized-block-data'; import { migrateRemoteData } from '@/utils/remote-data'; import './editor.scss'; @@ -21,7 +21,10 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ): JSX. const remoteDataAttribute = migrateRemoteData( props.attributes.remoteData ); const [ displayQueryKey, setDisplayQueryKey ] = useState< string >( - getDisplayQueryKeyFromQueryKey( blockName, remoteDataAttribute?.queryKey ?? '' ) + remoteDataAttribute?.displayQueryKey ?? '' + ); + const [ selectorQueryKey, setSelectorQueryKey ] = useState< string >( + remoteDataAttribute?.selectorQueryKey ?? '' ); const [ queryInputs, setQueryInputs ] = useState< RemoteDataQueryInput[] >( @@ -34,14 +37,16 @@ export function Edit( props: BlockEditProps< RemoteDataBlockAttributes > ): JSX. function resetQuery(): void { setDisplayQueryKey( '' ); + setSelectorQueryKey( '' ); setQueryInputs( [] ); } - if ( ! displayQueryKey ) { + if ( ! displayQueryKey && ! selectorQueryKey ) { return ( ); diff --git a/src/blocks/remote-data-container/hooks/useRemoteData.ts b/src/blocks/remote-data-container/hooks/useRemoteData.ts index 1b5d30fa9..54d3dc925 100644 --- a/src/blocks/remote-data-container/hooks/useRemoteData.ts +++ b/src/blocks/remote-data-container/hooks/useRemoteData.ts @@ -26,7 +26,8 @@ async function unmemoizedfetchRemoteData( blockName: body.block_name, metadata: body.metadata, pagination: body.pagination, - queryKey: body.query_key, + displayQueryKey: body.display_query_key, + selectorQueryKey: body.selector_query_key, queryInputs: body.query_inputs, resultId: body.result_id, results: body.results, @@ -65,7 +66,8 @@ interface UseRemoteDataInput { initialPerPage?: number; initialSearchInput?: string; onSuccess?: () => void; - queryKey: string; + displayQueryKey: string; + selectorQueryKey: string; } // This hook fetches remote data and manages state for the requests. @@ -85,7 +87,8 @@ export function useRemoteData( { initialPerPage, initialSearchInput, onSuccess, - queryKey, + displayQueryKey, + selectorQueryKey, }: UseRemoteDataInput ): UseRemoteData { const [ data, setData ] = useState< RemoteData >(); const [ error, setError ] = useState< Error >(); @@ -96,13 +99,16 @@ export function useRemoteData( { const hasResolvedData = Boolean( resolvedData ); const blockConfig = getBlockConfig( blockName ); - const query = blockConfig?.selectors?.find( selector => selector.query_key === queryKey ); + const selectors = blockConfig?.displayQueriesToSelectors[ displayQueryKey ] ?? []; + const query = selectors.find( selector => selector.query_key === selectorQueryKey ); if ( ! query ) { // Here we intentionally throw an error instead of calling setError, because // this indicates a misconfiguration somewhere in our code, not a runtime / // query error. - throw new Error( `Query not found for block "${ blockName }" and key "${ queryKey }".` ); + throw new Error( + `Query not found for block "${ blockName }" and key "${ selectorQueryKey }".` + ); } // Overrides must be provided via externallyManagedRemoteData @@ -192,7 +198,8 @@ export function useRemoteData( { const requestData: RemoteDataApiRequest = { block_name: blockName, - query_key: queryKey, + display_query_key: displayQueryKey, + selector_query_key: selectorQueryKey, query_inputs: inputs, }; diff --git a/src/blocks/remote-data-container/hooks/useRemoteDataContext.ts b/src/blocks/remote-data-container/hooks/useRemoteDataContext.ts index 536178279..30eec1ea4 100644 --- a/src/blocks/remote-data-container/hooks/useRemoteDataContext.ts +++ b/src/blocks/remote-data-container/hooks/useRemoteDataContext.ts @@ -28,7 +28,8 @@ export function useRemoteDataContext( context: Record< string, unknown > ): Remo blockName: remoteDataBlockName, metadata: {}, queryInputs: [], - queryKey: 'Example Query Key', + displayQueryKey: 'Example Display Query Key', + selectorQueryKey: 'Example Selector Query Key', resultId: '', results: [ { diff --git a/src/utils/localized-block-data.ts b/src/utils/localized-block-data.ts index c5031fa91..9cb52d62f 100644 --- a/src/utils/localized-block-data.ts +++ b/src/utils/localized-block-data.ts @@ -9,19 +9,6 @@ export function getBlockConfig( blockName: string ): BlockConfig | undefined { return window.REMOTE_DATA_BLOCKS?.config?.[ blockName ]; } -export function getDisplayQueryKeyFromQueryKey( blockName: string, queryKey: string ): string { - const displayQueriesToSelectors = getBlockConfig( blockName )?.displayQueriesToSelectors ?? {}; - - // iterate over the keys of the display_queries_to_selectors object. - for ( const [ displayQueryKey, selectors ] of Object.entries( displayQueriesToSelectors ) ) { - if ( selectors.includes( queryKey ) ) { - return displayQueryKey; - } - } - - return ''; -} - export function getBlockDataSourceType( blockName?: string ): string { if ( ! blockName ) { return ''; diff --git a/src/utils/remote-data.ts b/src/utils/remote-data.ts index b4960cd7f..6b31d9068 100644 --- a/src/utils/remote-data.ts +++ b/src/utils/remote-data.ts @@ -96,12 +96,10 @@ export function migrateRemoteData( remoteData?: RemoteData ): RemoteData | undef return createRemoteDataResult( resultValue, `${ num }` ); } ); - // By default, the query key used to be display. So if it's not set, we can assume it's display. - const queryKey = remoteData.queryKey ?? 'display'; - return { ...rest, - queryKey, + displayQueryKey: remoteData.displayQueryKey ?? 'display', + selectorQueryKey: remoteData.selectorQueryKey ?? 'display', queryInputs: remoteData.queryInputs ?? ( queryInput ? [ queryInput ] : [ {} ] ), results: migratedResults, }; diff --git a/tests/src/block-editor/filters/withBlockBinding.test.tsx b/tests/src/block-editor/filters/withBlockBinding.test.tsx index 392fed403..891c9abd1 100644 --- a/tests/src/block-editor/filters/withBlockBinding.test.tsx +++ b/tests/src/block-editor/filters/withBlockBinding.test.tsx @@ -47,15 +47,16 @@ describe( 'withBlockBinding', () => { dataSourceType: 'test-source', name: 'test/block', patterns: { key: 'test/block/pattern' }, - displayQueriesToSelectors: { key: [ 'key' ] }, - selectors: [ - { - query_key: 'key', - type: 'manual-input', - inputs: [], - name: 'test-name', - }, - ], + displayQueriesToSelectors: { + key: [ + { + query_key: 'key', + type: 'manual-input', + inputs: [], + name: 'test-name', + }, + ], + }, settings: { category: 'widget', title: 'Test block', @@ -102,7 +103,8 @@ describe( 'withBlockBinding', () => { const remoteData = { blockName: 'test/block', results: createResults( [ { field1: 'value1' } ] ), - queryKey: 'key', + displayQueryKey: 'key', + selectorQueryKey: 'key', }; render( @@ -126,7 +128,8 @@ describe( 'withBlockBinding', () => { const remoteData = { blockName: 'test/block', results: createResults( [ { field1: 'value1' } ] ), - queryKey: 'key', + displayQueryKey: 'key', + selectorQueryKey: 'key', }; render( { const remoteData = { blockName: 'test/block', results: createResults( [ { field1: 'value1' } ] ), - queryKey: 'key', + displayQueryKey: 'key', + selectorQueryKey: 'key', }; render( { [ REMOTE_DATA_CONTEXT_KEY ]: { blockName: 'test/block', results: createResults( [ { title: 'New Title' } ] ), - queryKey: 'key', + displayQueryKey: 'key', + selectorQueryKey: 'key', }, }, name: 'test/block', @@ -234,7 +239,8 @@ describe( 'withBlockBinding', () => { [ REMOTE_DATA_CONTEXT_KEY ]: { blockName: 'test/block', results: createResults( [ { title: 'Matching Title' } ] ), - queryKey: 'key', + displayQueryKey: 'key', + selectorQueryKey: 'key', }, }, name: 'test/block', @@ -270,7 +276,8 @@ describe( 'withBlockBinding', () => { [ REMOTE_DATA_CONTEXT_KEY ]: { blockName: 'test/block', results: createResults( [ { title: 'New Title' } ] ), - queryKey: 'key', + displayQueryKey: 'key', + selectorQueryKey: 'key', }, }, name: 'test/block', diff --git a/tests/src/blocks/remote-data-container/components/modals/InputModal.test.tsx b/tests/src/blocks/remote-data-container/components/modals/InputModal.test.tsx index acb1d585c..254c82541 100644 --- a/tests/src/blocks/remote-data-container/components/modals/InputModal.test.tsx +++ b/tests/src/blocks/remote-data-container/components/modals/InputModal.test.tsx @@ -19,7 +19,7 @@ describe( 'InputModal', () => { onSelect: mockOnSelect, onKeySelect: vi.fn(), title: 'Test Modal', - queryKey: 'test-key', + selectorQueryKey: 'test-key', }; afterEach( cleanup ); diff --git a/tests/src/blocks/remote-data-container/components/panels/QueryInputsPanel.test.tsx b/tests/src/blocks/remote-data-container/components/panels/QueryInputsPanel.test.tsx index 1cb86c29e..bc4b9bcf5 100644 --- a/tests/src/blocks/remote-data-container/components/panels/QueryInputsPanel.test.tsx +++ b/tests/src/blocks/remote-data-container/components/panels/QueryInputsPanel.test.tsx @@ -5,7 +5,7 @@ import { describe, expect, it, vi } from 'vitest'; import { QueryInputsPanel } from '@/blocks/remote-data-container/components/panels/QueryInputsPanel'; describe( 'QueryInputsPanel', () => { - const selectors: BlockConfig[ 'selectors' ] = [ + const selectors: Selector[] = [ { inputs: [ { @@ -32,7 +32,8 @@ describe( 'QueryInputsPanel', () => { const remoteData: RemoteData = { blockName: 'test/block', metadata: {}, - queryKey: 'test_query_key', + selectorQueryKey: 'test_query_key', + displayQueryKey: 'test_display_key', queryInputs: [], resultId: 'test', results: [], diff --git a/tests/src/blocks/remote-data-template/components/loop-template/LoopTemplate.test.tsx b/tests/src/blocks/remote-data-template/components/loop-template/LoopTemplate.test.tsx index 765392761..cd5ff324d 100644 --- a/tests/src/blocks/remote-data-template/components/loop-template/LoopTemplate.test.tsx +++ b/tests/src/blocks/remote-data-template/components/loop-template/LoopTemplate.test.tsx @@ -9,7 +9,8 @@ describe( 'LoopTemplate', () => { blockName: 'test/block', metadata: {}, queryInputs: [ {} ], - queryKey: 'test-query', + selectorQueryKey: 'test-query', + displayQueryKey: 'test-display', resultId: 'test-result', results: [ { diff --git a/types/localized-block-data.d.ts b/types/localized-block-data.d.ts index dfb2989bf..8b96a0942 100644 --- a/types/localized-block-data.d.ts +++ b/types/localized-block-data.d.ts @@ -39,8 +39,7 @@ interface BlockConfig { instructions?: string; name: string; patterns: Record< string, string >; - selectors: Selector[]; - displayQueriesToSelectors: Record< string, string[] >; + displayQueriesToSelectors: Record< string, Selector[] >; settings: { category: string; description?: string; diff --git a/types/remote-data.d.ts b/types/remote-data.d.ts index 8364b6ca1..698a6b8c6 100644 --- a/types/remote-data.d.ts +++ b/types/remote-data.d.ts @@ -35,7 +35,8 @@ interface RemoteData { /** @deprecated */ queryInput?: RemoteDataQueryInput; queryInputs: RemoteDataQueryInput[]; - queryKey?: string; + displayQueryKey?: string; + selectorQueryKey?: string; resultId: string; results: RemoteDataApiResult[]; } @@ -54,7 +55,7 @@ interface RemoteDataTemplateBlockAttributes {} interface FieldSelection { action: 'add_field_shortcode' | 'update_field_shortcode' | 'reset_field_shortcode'; - remoteData?: Pick< RemoteData, 'blockName' | 'metadata' | 'queryInputs' | 'queryKey' >; + remoteData?: Pick< RemoteData, 'blockName' | 'metadata' | 'queryInputs' | 'displayQueryKey' | 'selectorQueryKey' >; selectedField: string; selectionPath: 'select_new_tab' | 'select_existing_tab' | 'select_meta_tab' | 'popover'; type: 'field' | 'meta'; @@ -93,7 +94,8 @@ interface RemoteDataInnerBlockAttributes { interface RemoteDataApiRequest { block_name: string; query_inputs: RemoteDataQueryInput[]; - query_key: string; + display_query_key: string; + selector_query_key: string; } interface RemoteDataApiResult { @@ -108,7 +110,8 @@ interface RemoteDataApiResponseBody { metadata: Record< string, RemoteDataResultFields >; pagination?: RemoteDataPagination; query_inputs: RemoteDataQueryInput[]; - query_key: string; + display_query_key: string; + selector_query_key: string; result_id: string; results: RemoteDataApiResult[]; } From 2eb260b7fde75feb43112b2f1caf94d246ac4529 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Tue, 10 Jun 2025 15:23:05 +1000 Subject: [PATCH 59/74] Drop selectorQueryKey and attempt to fix errors in happy path --- .../remote-data-container/components/QueryComponent.tsx | 2 ++ .../components/modals/DataViewsModal.tsx | 5 +++-- .../components/modals/InputModal.tsx | 5 ++--- .../components/placeholders/ItemSelectQueryType.tsx | 4 ++-- .../components/placeholders/QuerySelectionPlaceholder.tsx | 8 ++------ .../components/popovers/InputPopover.tsx | 7 +++---- src/blocks/remote-data-container/edit.tsx | 7 +------ 7 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/blocks/remote-data-container/components/QueryComponent.tsx b/src/blocks/remote-data-container/components/QueryComponent.tsx index 5cfa0e621..9da7ed528 100644 --- a/src/blocks/remote-data-container/components/QueryComponent.tsx +++ b/src/blocks/remote-data-container/components/QueryComponent.tsx @@ -50,6 +50,8 @@ export function QueryComponent( props: QueryComponentProps ) { blockName, externallyManagedRemoteData: remoteDataAttribute, externallyManagedUpdateRemoteData: updateRemoteData, + // This is done on purpose, as the selector query is the input to the display query. + // So the real query being executed is the display query. displayQueryKey, selectorQueryKey: displayQueryKey, } ); diff --git a/src/blocks/remote-data-container/components/modals/DataViewsModal.tsx b/src/blocks/remote-data-container/components/modals/DataViewsModal.tsx index aee2122ca..422eb27af 100644 --- a/src/blocks/remote-data-container/components/modals/DataViewsModal.tsx +++ b/src/blocks/remote-data-container/components/modals/DataViewsModal.tsx @@ -13,7 +13,7 @@ interface DataViewsModalProps { className?: string; blockName: string; headerImage?: string; - onSelect?: ( data: RemoteDataQueryInput[], selectorQueryKey?: string ) => void; + onSelect?: ( data: RemoteDataQueryInput[] ) => void; onSelectField?: ( data: FieldSelection, fieldValue: string ) => void; selectorQueryKey: string; displayQueryKey: string; @@ -43,6 +43,7 @@ export const DataViewsModal: React.FC< DataViewsModalProps > = props => { selection.length > 1 ? __( 'items selected in total' ) : __( 'item selected in total' ); const { close, isOpen, open } = useModalState(); + const { data, hasNextPage, @@ -91,7 +92,7 @@ export const DataViewsModal: React.FC< DataViewsModalProps > = props => { return; } - onSelect?.( createQueryInputsFromRemoteDataResults( results ), selectorQueryKey ); + onSelect?.( createQueryInputsFromRemoteDataResults( results ) ); sendTracksEvent( 'add_block', { action: 'select_item', selected_option: 'search_from_list', diff --git a/src/blocks/remote-data-container/components/modals/InputModal.tsx b/src/blocks/remote-data-container/components/modals/InputModal.tsx index b3c35a6c3..8f3744a17 100644 --- a/src/blocks/remote-data-container/components/modals/InputModal.tsx +++ b/src/blocks/remote-data-container/components/modals/InputModal.tsx @@ -16,8 +16,7 @@ interface InputModalProps { blockName: string; headerImage?: string; inputs: InputVariable[]; - onSelect: ( data: RemoteDataQueryInput[], selectorQueryKey?: string ) => void; - selectorQueryKey: string; + onSelect: ( data: RemoteDataQueryInput[] ) => void; title: string; } @@ -35,7 +34,7 @@ export function InputModal( props: InputModalProps ) { } function onSelectItem(): void { - props.onSelect( [ inputState ], props.selectorQueryKey ); + props.onSelect( [ inputState ] ); close(); sendTracksEvent( 'add_block', { action: 'select_item', diff --git a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx index c0b2fbf17..e0dd89a3d 100644 --- a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx +++ b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx @@ -12,7 +12,7 @@ interface ItemSelectQueryTypeProps { blockName: string; selectors: Selector[]; displayQueryKey: string; - onSelect: ( data: RemoteDataQueryInput[], selectorQueryKey?: string ) => void; + onSelect: ( data: RemoteDataQueryInput[] ) => void; } export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) { @@ -52,7 +52,7 @@ export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) { -// -// -// -// ); -// } + // For now, we will use the first compatible selector, but this should be improved. + // Same as InlineBindingSelection.tsx + const selectorQueryKey = + remoteData?.selectorQueryKey ?? + selectors.find( selector => [ 'list', 'search' ].includes( selector.type ) )?.query_key ?? + ''; + + return ( + + + + { __( 'Select a field to bind', 'remote-data-blocks' ) } + + + + props.onSelectField( { ...data, action: 'update_field_shortcode' }, fieldValue ) + } + queryInputs={ remoteData?.queryInputs ?? [ {} ] } + selectedField={ selectedField } + /> + + + + + + + ); +} diff --git a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectMeta.tsx b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectMeta.tsx index eae132187..bba41f2e0 100644 --- a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectMeta.tsx +++ b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectMeta.tsx @@ -1,45 +1,45 @@ -// import { DropdownMenu, MenuGroup } from '@wordpress/components'; -// import { __ } from '@wordpress/i18n'; -// import { chevronRightSmall } from '@wordpress/icons'; +import { DropdownMenu, MenuGroup } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { chevronRightSmall } from '@wordpress/icons'; -// import { FieldSelectionFromMetaFields } from '@/block-editor/format-types/inline-binding/components/InlineBindingSelection'; -// import { useExistingRemoteData } from '@/block-editor/format-types/inline-binding/hooks/useExistingRemoteData'; -// import { getBlocksConfig } from '@/utils/localized-block-data'; +import { FieldSelectionFromMetaFields } from '@/block-editor/format-types/inline-binding/components/InlineBindingSelection'; +import { useExistingRemoteData } from '@/block-editor/format-types/inline-binding/hooks/useExistingRemoteData'; +import { getBlocksConfig } from '@/utils/localized-block-data'; -// interface InlineBindingSelectMetaProps { -// onSelectField: ( data: FieldSelection, fieldValue: string ) => void; -// } +interface InlineBindingSelectMetaProps { + onSelectField: ( data: FieldSelection, fieldValue: string ) => void; +} -// export function InlineBindingSelectMeta( props: InlineBindingSelectMetaProps ) { -// const blockConfigs = getBlocksConfig(); -// const remoteDatas: RemoteData[] = useExistingRemoteData(); +export function InlineBindingSelectMeta( props: InlineBindingSelectMetaProps ) { + const blockConfigs = getBlocksConfig(); + const remoteDatas: RemoteData[] = useExistingRemoteData(); -// return remoteDatas.length > 0 ? ( -// -// { () => -// remoteDatas.map( remoteData => ( -// -// -// props.onSelectField( { ...data, selectionPath: 'select_meta_tab' }, fieldValue ) -// } -// remoteData={ remoteData } -// /> -// -// ) ) -// } -// -// ) : undefined; -// } + return remoteDatas.length > 0 ? ( + + { () => + remoteDatas.map( remoteData => ( + + + props.onSelectField( { ...data, selectionPath: 'select_meta_tab' }, fieldValue ) + } + remoteData={ remoteData } + /> + + ) ) + } + + ) : undefined; +} diff --git a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectNew.tsx b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectNew.tsx index 8b241b245..df88f21dc 100644 --- a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectNew.tsx +++ b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectNew.tsx @@ -1,74 +1,81 @@ -// import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components'; -// import { DropdownMenuProps } from '@wordpress/components/build-types/dropdown-menu/types'; -// import { __ } from '@wordpress/i18n'; -// import { chevronRightSmall } from '@wordpress/icons'; +import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components'; +import { DropdownMenuProps } from '@wordpress/components/build-types/dropdown-menu/types'; +import { __ } from '@wordpress/i18n'; +import { chevronRightSmall } from '@wordpress/icons'; -// import { DataViewsModal } from '@/blocks/remote-data-container/components/modals/DataViewsModal'; -// import { getBlocksConfig } from '@/utils/localized-block-data'; +import { DataViewsModal } from '@/blocks/remote-data-container/components/modals/DataViewsModal'; +import { getBlocksConfig } from '@/utils/localized-block-data'; -// type InlineBindingSelectNewProps = Omit< DropdownMenuProps, 'label' > & { -// onSelectField: ( data: FieldSelection, fieldValue: string ) => void; -// label?: string; -// }; +type InlineBindingSelectNewProps = Omit< DropdownMenuProps, 'label' > & { + onSelectField: ( data: FieldSelection, fieldValue: string ) => void; + label?: string; +}; -// export function InlineBindingSelectNew( props: InlineBindingSelectNewProps ) { -// const { onSelectField, ...restProps } = props; -// const blockConfigs = getBlocksConfig(); -// const blocksByType = Object.values( blockConfigs ).reduce< -// Record< string, Array< BlocksConfig[ keyof BlocksConfig ] > > -// >( ( source, blockConfig ) => { -// const type = blockConfig.dataSourceType; -// if ( ! source[ type ] ) { -// source[ type ] = []; -// } -// source[ type ].push( blockConfig ); -// return source; -// }, {} ); +export function InlineBindingSelectNew( props: InlineBindingSelectNewProps ) { + const { onSelectField, ...restProps } = props; + const blockConfigs = getBlocksConfig(); + const blocksByType = Object.values( blockConfigs ).reduce< + Record< string, Array< BlocksConfig[ keyof BlocksConfig ] > > + >( ( source, blockConfig ) => { + const type = blockConfig.dataSourceType; + if ( ! source[ type ] ) { + source[ type ] = []; + } + source[ type ].push( blockConfig ); + return source; + }, {} ); -// return ( -// -// { () => -// Object.entries( blocksByType ).map( ( [ dataSourceType, configs ] ) => ( -// -// { configs.map( blockConfig => { -// // For now, we will use the first compatible selector, but this -// // should be improved. -// const compatibleSelector = blockConfig.selectors.find( selector => -// [ 'list', 'search' ].includes( selector.type ) -// ); + return ( + + { () => + Object.entries( blocksByType ).map( ( [ dataSourceType, configs ] ) => ( + + { configs.map( blockConfig => { -// if ( ! compatibleSelector ) { -// return null; -// } + // ToDo: Make a dropdown menu picker for this. + const displayQueryKey = 'display'; -// return ( -// ( -// -// { blockConfig.settings?.title ?? blockConfig.name } -// -// ) } -// /> -// ); -// } ) } -// -// ) ) -// } -// -// ); -// } + const selectors = blockConfig.displayQueriesToSelectors[ displayQueryKey ] ?? []; + + // For now, we will use the first compatible selector, but this + // should be improved. + const compatibleSelector = selectors.find( selector => + [ 'list', 'search' ].includes( selector.type ) + ); + + if ( ! compatibleSelector ) { + return null; + } + + return ( + ( + + { blockConfig.settings?.title ?? blockConfig.name } + + ) } + /> + ); + } ) } + + ) ) + } + + ); +} diff --git a/src/block-editor/format-types/inline-binding/components/InlineBindingSelection.tsx b/src/block-editor/format-types/inline-binding/components/InlineBindingSelection.tsx index 80bc06efd..44603d438 100644 --- a/src/block-editor/format-types/inline-binding/components/InlineBindingSelection.tsx +++ b/src/block-editor/format-types/inline-binding/components/InlineBindingSelection.tsx @@ -1,154 +1,156 @@ -// import { BaseControl, Icon, MenuItem, Spinner } from '@wordpress/components'; -// import { useEffect } from '@wordpress/element'; -// import { check } from '@wordpress/icons'; - -// import { TEXT_FIELD_TYPES } from '@/blocks/remote-data-container/config/constants'; -// import { useRemoteData } from '@/blocks/remote-data-container/hooks/useRemoteData'; -// import { getAvailableBindingsForQuery } from '@/utils/localized-block-data'; -// import { getRemoteDataResultValue } from '@/utils/remote-data'; - -// interface FieldSelectionProps { -// fields: Record< string, { name: string; value: string } >; -// onSelectField: ( data: FieldSelection, fieldValue: string ) => void; -// selectedField?: string; -// remoteData: RemoteData; -// fieldType: 'field' | 'meta'; -// } - -// export function FieldSelection( props: FieldSelectionProps ) { -// return ( -// <> -// { Object.entries( props.fields ).map( ( [ fieldName, fieldDetails ], index ) => { -// const fieldSelection: FieldSelection = { -// action: 'add_field_shortcode', -// selectedField: fieldName, -// remoteData: props.remoteData, -// type: props.fieldType, -// selectionPath: 'select_new_tab', -// }; - -// return ( -// { -// evt.preventDefault(); -// props.onSelectField( fieldSelection, fieldDetails.value ); -// } } -// onKeyDown={ evt => { -// if ( evt.key.toLowerCase() === 'enter' ) { -// props.onSelectField( fieldSelection, fieldDetails.value ); -// } -// } } -// suffix={ -// props.selectedField === fieldName ? ( -// -// ) : undefined -// } -// > -// -// -// { fieldDetails.name }: -// -// { fieldDetails.value?.toString() } -// -// -// ); -// } ) } -// -// ); -// } - -// type FieldSelectionWithFieldsProps = Omit< FieldSelectionProps, 'fields' | 'fieldType' >; - -// export function FieldSelectionFromAvailableBindings( props: FieldSelectionWithFieldsProps ) { -// const availableBindings = getAvailableBindingsForQuery( -// props.remoteData.blockName, -// props.remoteData.queryKey ?? '' -// ); - -// const fields = Object.entries( availableBindings ).reduce< FieldSelectionProps[ 'fields' ] >( -// ( acc, [ fieldName, binding ] ) => { -// const fieldValue = getRemoteDataResultValue( props.remoteData.results[ 0 ], fieldName ); -// if ( ! fieldValue || ! TEXT_FIELD_TYPES.includes( binding.type ) ) { -// return acc; -// } - -// return { -// ...acc, -// [ fieldName ]: { -// name: binding.name, -// value: fieldValue, -// }, -// }; -// }, -// {} -// ); - -// return ; -// } - -// export function FieldSelectionFromMetaFields( props: FieldSelectionWithFieldsProps ) { -// const fields: FieldSelectionProps[ 'fields' ] = Object.fromEntries( -// Object.entries( props.remoteData.metadata ?? {} ).map( ( [ fieldName, metadatum ] ) => [ -// fieldName, -// { -// name: metadatum.name, -// value: metadatum.value?.toString() ?? '', -// }, -// ] ) -// ); - -// return ; -// } - -// interface InlineBindingSelectFieldProps { -// blockName: string; -// fieldType: 'field' | 'meta'; -// onSelectField: ( data: FieldSelection, fieldValue: string ) => void; -// queryInputs: RemoteDataQueryInput[]; -// selectedField?: string; -// queryKey: string; -// } - -// export function InlineBindingSelectField( props: InlineBindingSelectFieldProps ) { -// const { data, fetch, loading } = useRemoteData( { -// blockName: props.blockName, -// queryKey: props.queryKey, -// } ); - -// useEffect( () => { -// if ( loading || data ) { -// return; -// } - -// void fetch( props.queryInputs ); -// }, [ loading, data ] ); - -// if ( ! data || loading ) { -// return ; -// } - -// const selectionProps: FieldSelectionWithFieldsProps = { -// onSelectField: props.onSelectField, -// remoteData: data, -// selectedField: props.selectedField, -// }; - -// if ( 'meta' === props.fieldType ) { -// return ; -// } - -// return ; -// } +import { BaseControl, Icon, MenuItem, Spinner } from '@wordpress/components'; +import { useEffect } from '@wordpress/element'; +import { check } from '@wordpress/icons'; + +import { TEXT_FIELD_TYPES } from '@/blocks/remote-data-container/config/constants'; +import { useRemoteData } from '@/blocks/remote-data-container/hooks/useRemoteData'; +import { getAvailableBindingsForQuery } from '@/utils/localized-block-data'; +import { getRemoteDataResultValue } from '@/utils/remote-data'; + +interface FieldSelectionProps { + fields: Record< string, { name: string; value: string } >; + onSelectField: ( data: FieldSelection, fieldValue: string ) => void; + selectedField?: string; + remoteData: RemoteData; + fieldType: 'field' | 'meta'; +} + +export function FieldSelection( props: FieldSelectionProps ) { + return ( + <> + { Object.entries( props.fields ).map( ( [ fieldName, fieldDetails ], index ) => { + const fieldSelection: FieldSelection = { + action: 'add_field_shortcode', + selectedField: fieldName, + remoteData: props.remoteData, + type: props.fieldType, + selectionPath: 'select_new_tab', + }; + + return ( + { + evt.preventDefault(); + props.onSelectField( fieldSelection, fieldDetails.value ); + } } + onKeyDown={ evt => { + if ( evt.key.toLowerCase() === 'enter' ) { + props.onSelectField( fieldSelection, fieldDetails.value ); + } + } } + suffix={ + props.selectedField === fieldName ? ( + + ) : undefined + } + > + + + { fieldDetails.name }: + + { fieldDetails.value?.toString() } + + + ); + } ) } + + ); +} + +type FieldSelectionWithFieldsProps = Omit< FieldSelectionProps, 'fields' | 'fieldType' >; + +export function FieldSelectionFromAvailableBindings( props: FieldSelectionWithFieldsProps ) { + const availableBindings = getAvailableBindingsForQuery( + props.remoteData.blockName, + props.remoteData.displayQueryKey ?? '' + ); + + const fields = Object.entries( availableBindings ).reduce< FieldSelectionProps[ 'fields' ] >( + ( acc, [ fieldName, binding ] ) => { + const fieldValue = getRemoteDataResultValue( props.remoteData.results[ 0 ], fieldName ); + if ( ! fieldValue || ! TEXT_FIELD_TYPES.includes( binding.type ) ) { + return acc; + } + + return { + ...acc, + [ fieldName ]: { + name: binding.name, + value: fieldValue, + }, + }; + }, + {} + ); + + return ; +} + +export function FieldSelectionFromMetaFields( props: FieldSelectionWithFieldsProps ) { + const fields: FieldSelectionProps[ 'fields' ] = Object.fromEntries( + Object.entries( props.remoteData.metadata ?? {} ).map( ( [ fieldName, metadatum ] ) => [ + fieldName, + { + name: metadatum.name, + value: metadatum.value?.toString() ?? '', + }, + ] ) + ); + + return ; +} + +interface InlineBindingSelectFieldProps { + blockName: string; + fieldType: 'field' | 'meta'; + onSelectField: ( data: FieldSelection, fieldValue: string ) => void; + queryInputs: RemoteDataQueryInput[]; + selectedField?: string; + displayQueryKey: string; + selectorQueryKey: string; +} + +export function InlineBindingSelectField( props: InlineBindingSelectFieldProps ) { + const { data, fetch, loading } = useRemoteData( { + blockName: props.blockName, + displayQueryKey: props.displayQueryKey, + selectorQueryKey: props.selectorQueryKey, + } ); + + useEffect( () => { + if ( loading || data ) { + return; + } + + void fetch( props.queryInputs ); + }, [ loading, data ] ); + + if ( ! data || loading ) { + return ; + } + + const selectionProps: FieldSelectionWithFieldsProps = { + onSelectField: props.onSelectField, + remoteData: data, + selectedField: props.selectedField, + }; + + if ( 'meta' === props.fieldType ) { + return ; + } + + return ; +} diff --git a/src/block-editor/format-types/inline-binding/index.ts b/src/block-editor/format-types/inline-binding/index.ts index 8b04b7e3c..9ba452003 100644 --- a/src/block-editor/format-types/inline-binding/index.ts +++ b/src/block-editor/format-types/inline-binding/index.ts @@ -1,10 +1,10 @@ -// import { registerFormatType } from '@wordpress/rich-text'; +import { registerFormatType } from '@wordpress/rich-text'; -// import { InlineBindingButton } from '@/block-editor/format-types/inline-binding/components/InlineBindingButton'; -// import { formatTypeSettings } from '@/block-editor/format-types/inline-binding/settings'; +import { InlineBindingButton } from '@/block-editor/format-types/inline-binding/components/InlineBindingButton'; +import { formatTypeSettings } from '@/block-editor/format-types/inline-binding/settings'; -// // Register the inline binding format type. -// registerFormatType( 'remote-data-blocks/inline-binding', { -// ...formatTypeSettings, -// edit: InlineBindingButton, -// } ); +// Register the inline binding format type. +registerFormatType( 'remote-data-blocks/inline-binding', { + ...formatTypeSettings, + edit: InlineBindingButton, +} ); diff --git a/src/utils/remote-data.ts b/src/utils/remote-data.ts index 6b31d9068..cfb91db6b 100644 --- a/src/utils/remote-data.ts +++ b/src/utils/remote-data.ts @@ -99,6 +99,7 @@ export function migrateRemoteData( remoteData?: RemoteData ): RemoteData | undef return { ...rest, displayQueryKey: remoteData.displayQueryKey ?? 'display', + // ToDo: This may not be correct when it comes to inline bindings as that assumes a key of the first selector with type of list, search. selectorQueryKey: remoteData.selectorQueryKey ?? 'display', queryInputs: remoteData.queryInputs ?? ( queryInput ? [ queryInput ] : [ {} ] ), results: migratedResults, From 4b54f344ed85d0ea325162ade1226974fa3a62bf Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Tue, 10 Jun 2025 17:05:11 +1000 Subject: [PATCH 62/74] Add a display query selector for new inline bindings --- .../InlineBindingSelectFieldPopover.tsx | 4 +- .../components/InlineBindingSelectNew.tsx | 67 ++++++++++--------- 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectFieldPopover.tsx b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectFieldPopover.tsx index 2ef75fed5..7bf5569cc 100644 --- a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectFieldPopover.tsx +++ b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectFieldPopover.tsx @@ -29,13 +29,11 @@ export function InlineBindingSelectFieldPopover( props: InlineBindingSelectField } ); const { remoteData, selectedField, type } = props.fieldSelection; - // ToDo: Make a dropdown menu picker for this. + // ToDo: Store this within the fieldSelection object. This is only set this way to stop compilation errors. const displayQueryKey = remoteData?.displayQueryKey ?? 'display'; - const selectors = getBlockConfig( remoteData?.blockName ?? '' )?.displayQueriesToSelectors[ displayQueryKey ] ?? []; - // For now, we will use the first compatible selector, but this should be improved. // Same as InlineBindingSelection.tsx const selectorQueryKey = diff --git a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectNew.tsx b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectNew.tsx index df88f21dc..c0af2ea32 100644 --- a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectNew.tsx +++ b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectNew.tsx @@ -40,39 +40,46 @@ export function InlineBindingSelectNew( props: InlineBindingSelectNewProps ) { { () => Object.entries( blocksByType ).map( ( [ dataSourceType, configs ] ) => ( - { configs.map( blockConfig => { + { configs.map( blockConfig => ( + + { Object.keys( blockConfig.displayQueriesToSelectors ).map( displayQueryKey => { + const selectors = blockConfig.displayQueriesToSelectors[ displayQueryKey ] ?? []; - // ToDo: Make a dropdown menu picker for this. - const displayQueryKey = 'display'; + // For now, we will use the first compatible selector, but this + // should be improved. + const compatibleSelector = selectors.find( selector => + [ 'list', 'search' ].includes( selector.type ) + ); - const selectors = blockConfig.displayQueriesToSelectors[ displayQueryKey ] ?? []; + if ( ! compatibleSelector ) { + return null; + } - // For now, we will use the first compatible selector, but this - // should be improved. - const compatibleSelector = selectors.find( selector => - [ 'list', 'search' ].includes( selector.type ) - ); - - if ( ! compatibleSelector ) { - return null; - } - - return ( - ( - - { blockConfig.settings?.title ?? blockConfig.name } - - ) } - /> - ); - } ) } + return ( + ( + + { displayQueryKey + .replace( /[^a-zA-Z0-9]/g, ' ' ) + .replace( /\b\w/g, ( initialLetter: string ) => + initialLetter.toUpperCase() + ) } + + ) } + /> + ); + } ) } + + ) ) } ) ) } From 5995b7151487b12333f4c556dfc97fee88df18f1 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Wed, 11 Jun 2025 08:10:03 +1000 Subject: [PATCH 63/74] Got inline bindings to fully function. Assumed the first display query is picked --- .../InlineBindingSelectFieldPopover.tsx | 18 ++--- .../components/InlineBindingSelectNew.tsx | 65 ++++++++----------- src/utils/remote-data.ts | 1 - 3 files changed, 34 insertions(+), 50 deletions(-) diff --git a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectFieldPopover.tsx b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectFieldPopover.tsx index 7bf5569cc..be2101925 100644 --- a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectFieldPopover.tsx +++ b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectFieldPopover.tsx @@ -29,16 +29,12 @@ export function InlineBindingSelectFieldPopover( props: InlineBindingSelectField } ); const { remoteData, selectedField, type } = props.fieldSelection; - // ToDo: Store this within the fieldSelection object. This is only set this way to stop compilation errors. - const displayQueryKey = remoteData?.displayQueryKey ?? 'display'; - const selectors = - getBlockConfig( remoteData?.blockName ?? '' )?.displayQueriesToSelectors[ displayQueryKey ] ?? - []; - // For now, we will use the first compatible selector, but this should be improved. - // Same as InlineBindingSelection.tsx - const selectorQueryKey = - remoteData?.selectorQueryKey ?? - selectors.find( selector => [ 'list', 'search' ].includes( selector.type ) )?.query_key ?? + // ToDo: Store this within the fieldSelection object. + const displayQueryKey = + remoteData?.displayQueryKey ?? + Object.keys( + getBlockConfig( remoteData?.blockName ?? '' )?.displayQueriesToSelectors ?? {} + )[ 0 ] ?? ''; return ( @@ -60,7 +56,7 @@ export function InlineBindingSelectFieldPopover( props: InlineBindingSelectField props.onSelectField( { ...data, action: 'update_field_shortcode' }, fieldValue ) diff --git a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectNew.tsx b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectNew.tsx index c0af2ea32..76fab2bc7 100644 --- a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectNew.tsx +++ b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectNew.tsx @@ -40,46 +40,35 @@ export function InlineBindingSelectNew( props: InlineBindingSelectNewProps ) { { () => Object.entries( blocksByType ).map( ( [ dataSourceType, configs ] ) => ( - { configs.map( blockConfig => ( - - { Object.keys( blockConfig.displayQueriesToSelectors ).map( displayQueryKey => { - const selectors = blockConfig.displayQueriesToSelectors[ displayQueryKey ] ?? []; + { configs.map( blockConfig => { + // ToDo: We are picking the first display query, and the first compatible selector for now. + const displayQueryKey = + Object.keys( blockConfig.displayQueriesToSelectors )[ 0 ] ?? ''; + const selectors = blockConfig.displayQueriesToSelectors[ displayQueryKey ] ?? []; + const compatibleSelector = selectors.find( selector => + [ 'list', 'search' ].includes( selector.type ) + ); - // For now, we will use the first compatible selector, but this - // should be improved. - const compatibleSelector = selectors.find( selector => - [ 'list', 'search' ].includes( selector.type ) - ); + if ( ! compatibleSelector ) { + return null; + } - if ( ! compatibleSelector ) { - return null; - } - - return ( - ( - - { displayQueryKey - .replace( /[^a-zA-Z0-9]/g, ' ' ) - .replace( /\b\w/g, ( initialLetter: string ) => - initialLetter.toUpperCase() - ) } - - ) } - /> - ); - } ) } - - ) ) } + return ( + ( + + { blockConfig.settings?.title ?? blockConfig.name } + + ) } + /> + ); + } ) } ) ) } diff --git a/src/utils/remote-data.ts b/src/utils/remote-data.ts index cfb91db6b..6b31d9068 100644 --- a/src/utils/remote-data.ts +++ b/src/utils/remote-data.ts @@ -99,7 +99,6 @@ export function migrateRemoteData( remoteData?: RemoteData ): RemoteData | undef return { ...rest, displayQueryKey: remoteData.displayQueryKey ?? 'display', - // ToDo: This may not be correct when it comes to inline bindings as that assumes a key of the first selector with type of list, search. selectorQueryKey: remoteData.selectorQueryKey ?? 'display', queryInputs: remoteData.queryInputs ?? ( queryInput ? [ queryInput ] : [ {} ] ), results: migratedResults, From 97bfd4233a5beafd0ddace0b30ae8ebdd5ec9384 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Wed, 11 Jun 2025 10:07:38 +1000 Subject: [PATCH 64/74] Added in the name property, and cleaned up the frontend code as a result --- example/blocks/art-block/art-block.php | 1 - inc/Editor/BlockManagement/ConfigRegistry.php | 223 +++++++----------- inc/Validation/ConfigSchemas.php | 1 - .../components/InlineBindingSelectNew.tsx | 11 +- .../components/QueryComponent.tsx | 3 +- .../QuerySelectionPlaceholder.tsx | 32 ++- .../hooks/useRemoteData.ts | 5 +- src/utils/localized-block-data.ts | 18 ++ .../filters/withBlockBinding.test.tsx | 19 +- types/localized-block-data.d.ts | 7 +- 10 files changed, 142 insertions(+), 178 deletions(-) diff --git a/example/blocks/art-block/art-block.php b/example/blocks/art-block/art-block.php index 65336d2cb..c6a526cd1 100644 --- a/example/blocks/art-block/art-block.php +++ b/example/blocks/art-block/art-block.php @@ -138,7 +138,6 @@ function register_art_remote_data_block(): void { [ 'name' => 'Get Art', 'query_key' => 'display', - 'icon' => 'art', ], ], ] ); diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index efa76af27..7984fa489 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -51,171 +51,110 @@ public static function register_block( array $block_config = [] ): bool|WP_Error // ToDo: Add support for name, and icon. $display_queries_to_selectors_map = []; - // ToDo: Refactor to not be this crazy. + // Build the $placeholders array based on config if ( ! empty( $block_config[ self::PLACEHOLDERS_KEY ] ) ) { + $placeholders = $block_config[ self::PLACEHOLDERS_KEY ]; // Pre-validate the placeholders, to ensure they exist. - foreach ( $block_config[ self::PLACEHOLDERS_KEY ] as $placeholder ) { + foreach ( $placeholders as $placeholder ) { if ( ! isset( $block_config[ self::QUERIES_KEY ][ $placeholder['query_key'] ] ) ) { return self::create_error( $block_title, sprintf( 'Query "%s" not found for placeholder "%s"', $placeholder['query_key'], $placeholder['name'] ) ); } } - - // Generate the selectors for the selector queries. + } else { + $placeholders = []; foreach ( $block_config[ self::QUERIES_KEY ] as $query_key => $query ) { - // Inflate the query, and add it to the queries array. - $query = self::inflate_query( $query ); - $queries[ $query_key ] = $query; - - $input_schema = $query->get_input_schema(); - $output_schema = $query->get_output_schema(); - - // match the query_key against the query_key property in a placeholder entry. - $filtered_placeholders = array_filter( $block_config[ self::PLACEHOLDERS_KEY ], fn( $placeholder ) => $placeholder['query_key'] === $query_key ); - - // Generate the selector for the display query, and then continue on to the next query. - if ( ! empty( $filtered_placeholders ) ) { - $is_collection = true === ( $output_schema['is_collection'] ?? false ); - $has_required_variables = array_reduce( - array_column( $input_schema, 'required' ), - fn( $carry, $required ) => $carry || ( $required ?? true ), - false - ); - - $selector_config = [ - 'image_url' => $query->get_image_url(), - 'inputs' => self::map_input_variables( $input_schema ), - 'name' => $has_required_variables ? 'Manual input' : ( $is_collection ? 'Load collection' : 'Load item' ), - 'query_key' => $query_key, - 'type' => $has_required_variables ? 'manual-input' : 'load-without-input', - ]; - - $display_queries_to_selectors_map[ $query_key ][] = $selector_config; + $placeholders[] = [ + 'name' => self::get_query_name_from_key( $query_key ), + 'query_key' => $query_key, + ]; + } + } + // Now, single loop for building selectors and map + foreach ( $placeholders as $placeholder ) { + $selectors = []; + + $placeholder_query_key = $placeholder['query_key']; + $placeholder_query = self::inflate_query( $block_config[ self::QUERIES_KEY ][ $placeholder_query_key ] ); + $queries[ $placeholder_query_key ] = $placeholder_query; + + $placeholder_query_input_schema = $placeholder_query->get_input_schema(); + $placeholder_query_output_schema = $placeholder_query->get_output_schema(); + + $is_collection = true === ( $placeholder_query_output_schema['is_collection'] ?? false ); + $has_required_variables = array_reduce( + array_column( $placeholder_query_input_schema, 'required' ), + fn( $carry, $required ) => $carry || ( $required ?? true ), + false + ); + + $selector_config = [ + 'image_url' => $placeholder_query->get_image_url(), + 'inputs' => self::map_input_variables( $placeholder_query_input_schema ), + 'name' => $has_required_variables ? 'Manual input' : ( $is_collection ? 'Load collection' : 'Load item' ), + 'query_key' => $placeholder_query_key, + 'type' => $has_required_variables ? 'manual-input' : 'load-without-input', + ]; + + $selectors[] = $selector_config; + + // Now, loop over all queries to find compatible selectors + foreach ( $block_config[ self::QUERIES_KEY ] as $selector_query_key => $selector_query ) { + // Don't match the placeholder to itself + if ( $selector_query_key === $placeholder_query_key ) { continue; } + $selector_query = self::inflate_query( $selector_query ); + $queries[ $selector_query_key ] = $selector_query; - // ToDo: Should switch this to be an array of types instead. - // Infer the type of the query, for selector generation and to validate the non-display queries. - $inferred_type = self::infer_query_type( $input_schema, $output_schema ); - if ( 'unknown' === $inferred_type ) { - return self::create_error( 'Unknown query type', 'Could not infer the type of the query. Valid query types are "search" and "list".' ); - } + $selector_query_input_schema = $selector_query->get_input_schema(); + $selector_query_output_schema = $selector_query->get_output_schema(); - // If the output schema's type is not an array, then skip selector generation as that'll not work. - // This has been done because some output schemas have the type set to string. - if ( ! is_array( $output_schema['type'] ) ) { + $inferred_selector_query_type = self::infer_query_type( $selector_query_input_schema, $selector_query_output_schema ); + if ( 'unknown' === $inferred_selector_query_type ) { continue; } - foreach ( $block_config[ self::PLACEHOLDERS_KEY ] as $placeholder ) { - $display_query = self::inflate_query( $block_config[ self::QUERIES_KEY ][ $placeholder['query_key'] ] ); - $display_query_input_schema = $display_query->get_input_schema(); - - // Check if the query's output schema intersects with the display query's input schema. - $intersecting_keys = array_intersect_key( $output_schema['type'], $display_query_input_schema ); - - // Skip this, if they don't intersect. - if ( empty( $intersecting_keys ) ) { - continue; - } - - // Ensure the name and type of the schemas are truly valid. - $valid_intersecting_keys = self::validate_selector_query_mapping( $intersecting_keys, $display_query_input_schema, $output_schema ); - if ( empty( $valid_intersecting_keys ) ) { - continue; - } - - // Validate the query mapping. - $validation_result = self::validate_query_mapping( $display_query_input_schema, $output_schema, $block_title, $query_key ); - if ( is_wp_error( $validation_result ) ) { - return $validation_result; - } - - // Add the selector for the query to the beginning of the selectors array. - $selector_config = [ - 'image_url' => $query->get_image_url(), - 'inputs' => self::map_input_variables( $input_schema ), - 'name' => self::get_query_name_from_key( $query_key ), - 'query_key' => $query_key, - 'type' => $inferred_type, - ]; - - array_unshift( - $display_queries_to_selectors_map[ $placeholder['query_key'] ], - $selector_config - ); - - // We have found the relevant display query, so we can break out of the loop. - break; + if ( ! is_array( $selector_query_output_schema['type'] ) ) { + continue; } - } - } else { - foreach ( $block_config[ self::QUERIES_KEY ] as $placeholder_query_key => $placeholder_query ) { - $placeholder_query = self::inflate_query( $placeholder_query ); - $queries[ $placeholder_query_key ] = $placeholder_query; - - $placeholder_query_input_schema = $placeholder_query->get_input_schema(); - $placeholder_query_output_schema = $placeholder_query->get_output_schema(); - - $is_collection = true === ( $placeholder_query_output_schema['is_collection'] ?? false ); - $has_required_variables = array_reduce( - array_column( $placeholder_query_input_schema, 'required' ), - fn( $carry, $required ) => $carry || ( $required ?? true ), - false - ); - - $selector_config = [ - 'image_url' => $placeholder_query->get_image_url(), - 'inputs' => self::map_input_variables( $placeholder_query_input_schema ), - 'name' => $has_required_variables ? 'Manual input' : ( $is_collection ? 'Load collection' : 'Load item' ), - 'query_key' => $placeholder_query_key, - 'type' => $has_required_variables ? 'manual-input' : 'load-without-input', - ]; - - $display_queries_to_selectors_map[ $placeholder_query_key ][] = $selector_config; - foreach ( $block_config[ self::QUERIES_KEY ] as $selector_query_key => $selector_query ) { - $selector_query = self::inflate_query( $selector_query ); - $queries[ $selector_query_key ] = $selector_query; + $intersecting_keys = array_intersect_key( $selector_query_output_schema['type'], $placeholder_query_input_schema ); - $selector_query_input_schema = $selector_query->get_input_schema(); - $selector_query_output_schema = $selector_query->get_output_schema(); - - $inferred_selector_query_type = self::infer_query_type( $selector_query_input_schema, $selector_query_output_schema ); - if ( 'unknown' === $inferred_selector_query_type ) { - continue; - } - - if ( ! is_array( $selector_query_output_schema['type'] ) ) { - continue; - } - - $intersecting_keys = array_intersect_key( $selector_query_output_schema['type'], $placeholder_query_input_schema ); + // Skip this, if they don't intersect. + if ( empty( $intersecting_keys ) ) { + continue; + } - // Skip this, if they don't intersect. - if ( empty( $intersecting_keys ) ) { - continue; - } + // Ensure the name and type of the schemas are truly valid. + $valid_intersecting_keys = self::validate_selector_query_mapping( $intersecting_keys, $placeholder_query_input_schema, $selector_query_output_schema ); + if ( empty( $valid_intersecting_keys ) ) { + continue; + } - $validation_result = self::validate_query_mapping( $placeholder_query_input_schema, $selector_query_output_schema, $block_title, $placeholder_query_key ); - if ( is_wp_error( $validation_result ) ) { - return $validation_result; - } + $validation_result = self::validate_query_mapping( $placeholder_query_input_schema, $selector_query_output_schema, $block_title, $placeholder_query_key ); + if ( is_wp_error( $validation_result ) ) { + return $validation_result; + } - $selector_config = [ - 'image_url' => $selector_query->get_image_url(), - 'inputs' => self::map_input_variables( $selector_query_input_schema ), - 'name' => self::get_query_name_from_key( $selector_query_key ), - 'query_key' => $selector_query_key, - 'type' => $inferred_selector_query_type, - ]; + $selector_config = [ + 'image_url' => $selector_query->get_image_url(), + 'inputs' => self::map_input_variables( $selector_query_input_schema ), + 'name' => self::get_query_name_from_key( $selector_query_key ), + 'query_key' => $selector_query_key, + 'type' => $inferred_selector_query_type, + ]; - array_unshift( - $display_queries_to_selectors_map[ $placeholder_query_key ], - $selector_config - ); - } + array_unshift( + $selectors, + $selector_config + ); } + + $display_queries_to_selectors_map[ $placeholder_query_key ] = [ + 'name' => $placeholder['name'], + 'selectors' => $selectors, + ]; } $config = [ diff --git a/inc/Validation/ConfigSchemas.php b/inc/Validation/ConfigSchemas.php index 2d797d091..0828436c2 100644 --- a/inc/Validation/ConfigSchemas.php +++ b/inc/Validation/ConfigSchemas.php @@ -90,7 +90,6 @@ private static function generate_remote_data_block_config_schema(): array { Types::object( [ 'name' => Types::string(), 'query_key' => Types::string(), - 'icon' => Types::nullable( Types::string() ), ] ), ) ), diff --git a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectNew.tsx b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectNew.tsx index 76fab2bc7..58d5e6196 100644 --- a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectNew.tsx +++ b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectNew.tsx @@ -4,7 +4,11 @@ import { __ } from '@wordpress/i18n'; import { chevronRightSmall } from '@wordpress/icons'; import { DataViewsModal } from '@/blocks/remote-data-container/components/modals/DataViewsModal'; -import { getBlocksConfig } from '@/utils/localized-block-data'; +import { + getBlocksConfig, + getFirstDisplayQueryKey, + getSelectorsForDisplayQuery, +} from '@/utils/localized-block-data'; type InlineBindingSelectNewProps = Omit< DropdownMenuProps, 'label' > & { onSelectField: ( data: FieldSelection, fieldValue: string ) => void; @@ -42,9 +46,8 @@ export function InlineBindingSelectNew( props: InlineBindingSelectNewProps ) { { configs.map( blockConfig => { // ToDo: We are picking the first display query, and the first compatible selector for now. - const displayQueryKey = - Object.keys( blockConfig.displayQueriesToSelectors )[ 0 ] ?? ''; - const selectors = blockConfig.displayQueriesToSelectors[ displayQueryKey ] ?? []; + const displayQueryKey = getFirstDisplayQueryKey( blockConfig.name ); + const selectors = getSelectorsForDisplayQuery( blockConfig.name, displayQueryKey ); const compatibleSelector = selectors.find( selector => [ 'list', 'search' ].includes( selector.type ) ); diff --git a/src/blocks/remote-data-container/components/QueryComponent.tsx b/src/blocks/remote-data-container/components/QueryComponent.tsx index 9da7ed528..ba8e1096e 100644 --- a/src/blocks/remote-data-container/components/QueryComponent.tsx +++ b/src/blocks/remote-data-container/components/QueryComponent.tsx @@ -18,6 +18,7 @@ import { CONTAINER_CLASS_NAME } from '@/blocks/remote-data-container/config/cons import { usePatterns } from '@/blocks/remote-data-container/hooks/usePatterns'; import { useRemoteData } from '@/blocks/remote-data-container/hooks/useRemoteData'; import { hasRemoteDataChanged } from '@/utils/block-binding'; +import { getSelectorsForDisplayQuery } from '@/utils/localized-block-data'; export interface QueryComponentProps { displayQueryKey: string; @@ -154,7 +155,7 @@ export function QueryComponent( props: QueryComponentProps ) { ) } diff --git a/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx b/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx index fcdf3f5d2..4d0df19a3 100644 --- a/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx +++ b/src/blocks/remote-data-container/components/placeholders/QuerySelectionPlaceholder.tsx @@ -9,6 +9,7 @@ import { __ } from '@wordpress/i18n'; import { cloud } from '@wordpress/icons'; import { ItemSelectQueryType } from './ItemSelectQueryType'; +import { getSelectorsForDisplayQuery } from '@/utils/localized-block-data'; export interface QuerySelectionPlaceholderProps { blockConfig: BlockConfig; @@ -22,9 +23,6 @@ export function QuerySelectionPlaceholder( props: QuerySelectionPlaceholderProps const iconElement: IconType = ( settings.icon as IconType ) ?? cloud; - // The keys represent the display query keys. - const displayQueryKeys: string[] = Object.keys( displayQueriesToSelectors ); - const [ selectedDisplayQueryKey, setSelectedDisplayQueryKey ] = useState< string >( '' ); const [ showSelectors, setShowSelectors ] = useState< boolean >( false ); @@ -54,25 +52,25 @@ export function QuerySelectionPlaceholder( props: QuerySelectionPlaceholderProps __nextHasNoMarginBottom __next40pxDefaultSize > - { displayQueryKeys.map( ( displayQueryKey: string ) => ( - - ) ) } + { Object.entries( displayQueriesToSelectors ).map( + ( [ displayQueryKey, displayQueryConfig ] ) => ( + + ) + ) } ) } { showSelectors && ( diff --git a/src/blocks/remote-data-container/hooks/useRemoteData.ts b/src/blocks/remote-data-container/hooks/useRemoteData.ts index 54d3dc925..ce58bd1eb 100644 --- a/src/blocks/remote-data-container/hooks/useRemoteData.ts +++ b/src/blocks/remote-data-container/hooks/useRemoteData.ts @@ -7,7 +7,7 @@ import { useSearchVariables } from '@/blocks/remote-data-container/hooks/useSear import { ensureError } from '@/utils/errors'; import { memoizeFn } from '@/utils/function'; import { isQueryInputValid, validateQueryInput } from '@/utils/input-validation'; -import { getBlockConfig } from '@/utils/localized-block-data'; +import { getBlockConfig, getSelectorsForDisplayQuery } from '@/utils/localized-block-data'; async function unmemoizedfetchRemoteData( requestData: RemoteDataApiRequest @@ -98,8 +98,7 @@ export function useRemoteData( { const resolvedUpdater = externallyManagedUpdateRemoteData ?? setData; const hasResolvedData = Boolean( resolvedData ); - const blockConfig = getBlockConfig( blockName ); - const selectors = blockConfig?.displayQueriesToSelectors[ displayQueryKey ] ?? []; + const selectors = getSelectorsForDisplayQuery( blockName, displayQueryKey ); const query = selectors.find( selector => selector.query_key === selectorQueryKey ); if ( ! query ) { diff --git a/src/utils/localized-block-data.ts b/src/utils/localized-block-data.ts index 9cb52d62f..42de5d96d 100644 --- a/src/utils/localized-block-data.ts +++ b/src/utils/localized-block-data.ts @@ -17,6 +17,24 @@ export function getBlockDataSourceType( blockName?: string ): string { return getBlockConfig( blockName )?.dataSourceType ?? ''; } +export function getSelectorsForDisplayQuery( + blockName: string, + displayQueryKey: string +): Selector[] { + return ( + getBlockConfig( blockName )?.displayQueriesToSelectors?.[ displayQueryKey ]?.selectors ?? [] + ); +} + +export function getFirstDisplayQueryKey( blockName?: string ): string { + if ( ! blockName ) { + return ''; + } + const displayQueriesToSelectors = getBlockConfig( blockName )?.displayQueriesToSelectors ?? {}; + const displayQueryKeys = Object.keys( displayQueriesToSelectors ); + return displayQueryKeys[ 0 ] ?? ''; +} + /** * Get the title of a remote data block. * diff --git a/tests/src/block-editor/filters/withBlockBinding.test.tsx b/tests/src/block-editor/filters/withBlockBinding.test.tsx index 891c9abd1..ab544bebb 100644 --- a/tests/src/block-editor/filters/withBlockBinding.test.tsx +++ b/tests/src/block-editor/filters/withBlockBinding.test.tsx @@ -48,14 +48,17 @@ describe( 'withBlockBinding', () => { name: 'test/block', patterns: { key: 'test/block/pattern' }, displayQueriesToSelectors: { - key: [ - { - query_key: 'key', - type: 'manual-input', - inputs: [], - name: 'test-name', - }, - ], + key: { + name: 'test-name', + selectors: [ + { + query_key: 'key', + type: 'manual-input', + inputs: [], + name: 'test-name', + }, + ], + }, }, settings: { category: 'widget', diff --git a/types/localized-block-data.d.ts b/types/localized-block-data.d.ts index 8b96a0942..07161a29e 100644 --- a/types/localized-block-data.d.ts +++ b/types/localized-block-data.d.ts @@ -18,6 +18,11 @@ interface InputVariable { type: string; } +interface DisplayQueryConfig { + name: string; + selectors: Selector[]; +} + interface Selector { image_url?: string; inputs: InputVariable[]; @@ -39,7 +44,7 @@ interface BlockConfig { instructions?: string; name: string; patterns: Record< string, string >; - displayQueriesToSelectors: Record< string, Selector[] >; + displayQueriesToSelectors: Record< string, DisplayQueryConfig >; settings: { category: string; description?: string; From f6904c6988538ab4cebb36eeee435c4dde6c3493 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Wed, 11 Jun 2025 10:22:59 +1000 Subject: [PATCH 65/74] tweak the inline bindings to re-use the existing method --- .../components/InlineBindingSelectFieldPopover.tsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectFieldPopover.tsx b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectFieldPopover.tsx index be2101925..c597bc15d 100644 --- a/src/block-editor/format-types/inline-binding/components/InlineBindingSelectFieldPopover.tsx +++ b/src/block-editor/format-types/inline-binding/components/InlineBindingSelectFieldPopover.tsx @@ -11,7 +11,7 @@ import { __ } from '@wordpress/i18n'; import { WPFormat, useAnchor } from '@wordpress/rich-text'; import { InlineBindingSelectField } from '@/block-editor/format-types/inline-binding/components/InlineBindingSelection'; -import { getBlockConfig } from '@/utils/localized-block-data'; +import { getFirstDisplayQueryKey } from '@/utils/localized-block-data'; interface InlineBindingSelectFieldPopoverProps { contentRef: React.RefObject< HTMLElement >; @@ -29,13 +29,9 @@ export function InlineBindingSelectFieldPopover( props: InlineBindingSelectField } ); const { remoteData, selectedField, type } = props.fieldSelection; - // ToDo: Store this within the fieldSelection object. + // ToDo: We are picking the first display query for now. const displayQueryKey = - remoteData?.displayQueryKey ?? - Object.keys( - getBlockConfig( remoteData?.blockName ?? '' )?.displayQueriesToSelectors ?? {} - )[ 0 ] ?? - ''; + remoteData?.displayQueryKey ?? getFirstDisplayQueryKey( remoteData?.blockName ?? '' ); return ( Date: Wed, 11 Jun 2025 10:34:54 +1000 Subject: [PATCH 66/74] Add a quick comment --- src/blocks/remote-data-container/components/QueryComponent.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/blocks/remote-data-container/components/QueryComponent.tsx b/src/blocks/remote-data-container/components/QueryComponent.tsx index ba8e1096e..d97b020f3 100644 --- a/src/blocks/remote-data-container/components/QueryComponent.tsx +++ b/src/blocks/remote-data-container/components/QueryComponent.tsx @@ -110,6 +110,7 @@ export function QueryComponent( props: QueryComponentProps ) { newSelectorQueryKey: string, inputs: RemoteDataQueryInput[] ): void { + if ( ! remoteDataAttribute ) { return; } @@ -117,6 +118,7 @@ export function QueryComponent( props: QueryComponentProps ) { updateRemoteData( { ...remoteDataAttribute, queryInputs: inputs, + // This will always be the display query key. selectorQueryKey: newSelectorQueryKey, displayQueryKey, } ); From 4518af71abeb3c3d448b962c1a8a625865867644 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Wed, 11 Jun 2025 11:08:39 +1000 Subject: [PATCH 67/74] Fixing some linting errors. There's still a bug with Google Sheets --- inc/Editor/BlockManagement/ConfigRegistry.php | 5 ++++- .../remote-data-container/components/QueryComponent.tsx | 1 - src/blocks/remote-data-container/hooks/useRemoteData.ts | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index 7984fa489..8e1b387fe 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -62,7 +62,10 @@ public static function register_block( array $block_config = [] ): bool|WP_Error } } else { $placeholders = []; - foreach ( $block_config[ self::QUERIES_KEY ] as $query_key => $query ) { + // Using array_keys here triggers a psalm error, so it's set to $_ instead. + // Supressing the psalm error is a not a good idea, so instead this is the better solution. + // ToDo: Fix the psalm error, and see if array_keys could be used here again. + foreach ( $block_config[ self::QUERIES_KEY ] as $query_key => $_ ) { $placeholders[] = [ 'name' => self::get_query_name_from_key( $query_key ), 'query_key' => $query_key, diff --git a/src/blocks/remote-data-container/components/QueryComponent.tsx b/src/blocks/remote-data-container/components/QueryComponent.tsx index d97b020f3..ff131231e 100644 --- a/src/blocks/remote-data-container/components/QueryComponent.tsx +++ b/src/blocks/remote-data-container/components/QueryComponent.tsx @@ -110,7 +110,6 @@ export function QueryComponent( props: QueryComponentProps ) { newSelectorQueryKey: string, inputs: RemoteDataQueryInput[] ): void { - if ( ! remoteDataAttribute ) { return; } diff --git a/src/blocks/remote-data-container/hooks/useRemoteData.ts b/src/blocks/remote-data-container/hooks/useRemoteData.ts index ce58bd1eb..ece17351f 100644 --- a/src/blocks/remote-data-container/hooks/useRemoteData.ts +++ b/src/blocks/remote-data-container/hooks/useRemoteData.ts @@ -7,7 +7,7 @@ import { useSearchVariables } from '@/blocks/remote-data-container/hooks/useSear import { ensureError } from '@/utils/errors'; import { memoizeFn } from '@/utils/function'; import { isQueryInputValid, validateQueryInput } from '@/utils/input-validation'; -import { getBlockConfig, getSelectorsForDisplayQuery } from '@/utils/localized-block-data'; +import { getSelectorsForDisplayQuery } from '@/utils/localized-block-data'; async function unmemoizedfetchRemoteData( requestData: RemoteDataApiRequest From 0aa95106807e3dfd76e3ac2d2511de65125162af Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Wed, 11 Jun 2025 12:05:42 +1000 Subject: [PATCH 68/74] Add an extra filter to fix the memory caching bug --- .../Google/Sheets/GoogleSheetsIntegration.php | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/inc/Integrations/Google/Sheets/GoogleSheetsIntegration.php b/inc/Integrations/Google/Sheets/GoogleSheetsIntegration.php index dba36c40a..65589c0c1 100644 --- a/inc/Integrations/Google/Sheets/GoogleSheetsIntegration.php +++ b/inc/Integrations/Google/Sheets/GoogleSheetsIntegration.php @@ -11,6 +11,7 @@ class GoogleSheetsIntegration { public static function init(): void { add_action( 'init', [ __CLASS__, 'register_blocks' ], 10, 0 ); + add_filter( 'remote_data_blocks_request_details', [ __CLASS__, 'enhance_request_details' ], 10, 3 ); } public static function register_blocks(): void { @@ -186,4 +187,28 @@ public static function get_block_registration_snippets( array $data_source_confi return $snippets; } + + /** + * Due to the fact that we are using the same query for both the get and list queries, and + * only filtering out the results based on the input variables, the in-memory cache will not + * work as expected. This enhances the request details to include the input variables, so that + * the in-memory cache will be able to differenciate each request. + * + * @param array $request_details The request details. + * @param string $query The query being executed. + * @param array $input_variables The input variables for the current request. + * @return array, + * origin: string, + * uri: string, + * }> + */ + public static function enhance_request_details( array $request_details, string $query, array $input_variables ): array { + if ( isset( $request_details['origin'] ) && 'https://sheets.googleapis.com' === $request_details['origin'] && ! empty( $input_variables ) ) { + $request_details['input_variables'] = $input_variables; + } + + return $request_details; + } } From ef0d3e90361d0dc020e1c77bd141e51cd509a964 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Wed, 11 Jun 2025 12:14:58 +1000 Subject: [PATCH 69/74] Fix the psalm erro --- inc/Integrations/Google/Sheets/GoogleSheetsIntegration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/Integrations/Google/Sheets/GoogleSheetsIntegration.php b/inc/Integrations/Google/Sheets/GoogleSheetsIntegration.php index 65589c0c1..b497cd240 100644 --- a/inc/Integrations/Google/Sheets/GoogleSheetsIntegration.php +++ b/inc/Integrations/Google/Sheets/GoogleSheetsIntegration.php @@ -204,7 +204,7 @@ public static function get_block_registration_snippets( array $data_source_confi * uri: string, * }> */ - public static function enhance_request_details( array $request_details, string $query, array $input_variables ): array { + public static function enhance_request_details( array $request_details, string $_query, array $input_variables ): array { if ( isset( $request_details['origin'] ) && 'https://sheets.googleapis.com' === $request_details['origin'] && ! empty( $input_variables ) ) { $request_details['input_variables'] = $input_variables; } From d62c56af4f6438da270ce8fae7c3fb3cbe003323 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Wed, 11 Jun 2025 12:18:08 +1000 Subject: [PATCH 70/74] Fix psalm again --- inc/Integrations/Google/Sheets/GoogleSheetsIntegration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/Integrations/Google/Sheets/GoogleSheetsIntegration.php b/inc/Integrations/Google/Sheets/GoogleSheetsIntegration.php index b497cd240..d020163e8 100644 --- a/inc/Integrations/Google/Sheets/GoogleSheetsIntegration.php +++ b/inc/Integrations/Google/Sheets/GoogleSheetsIntegration.php @@ -195,7 +195,7 @@ public static function get_block_registration_snippets( array $data_source_confi * the in-memory cache will be able to differenciate each request. * * @param array $request_details The request details. - * @param string $query The query being executed. + * @param string $_query The query being executed. * @param array $input_variables The input variables for the current request. * @return array Date: Wed, 11 Jun 2025 13:37:06 +1000 Subject: [PATCH 71/74] Fix all the tests --- inc/Editor/BlockManagement/ConfigRegistry.php | 41 +++++++------ inc/Editor/BlockManagement/ConfigStore.php | 14 ++--- tests/inc/Functions/FunctionsTest.php | 59 +++++++++++++++---- types/remote-data.d.ts | 5 +- 4 files changed, 77 insertions(+), 42 deletions(-) diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php index 8e1b387fe..d747d5abe 100644 --- a/inc/Editor/BlockManagement/ConfigRegistry.php +++ b/inc/Editor/BlockManagement/ConfigRegistry.php @@ -47,11 +47,17 @@ public static function register_block( array $block_config = [] ): bool|WP_Error return self::create_error( $block_title, sprintf( 'Block %s has already been registered', $block_name ) ); } + // Ensure that the block has queries. + if ( empty( $block_config[ self::QUERIES_KEY ] ) ) { + return self::create_error( $block_title, 'Block configuration must have a non-empty "queries" array' ); + } + $queries = []; - // ToDo: Add support for name, and icon. $display_queries_to_selectors_map = []; - // Build the $placeholders array based on config + // Either use the placeholders from the config, or build them from the queries. + // Placeholders are meant to determine the queries that'll be visually represented + // in the block's UI upon initial insertion. if ( ! empty( $block_config[ self::PLACEHOLDERS_KEY ] ) ) { $placeholders = $block_config[ self::PLACEHOLDERS_KEY ]; // Pre-validate the placeholders, to ensure they exist. @@ -73,7 +79,6 @@ public static function register_block( array $block_config = [] ): bool|WP_Error } } - // Now, single loop for building selectors and map foreach ( $placeholders as $placeholder ) { $selectors = []; @@ -84,6 +89,7 @@ public static function register_block( array $block_config = [] ): bool|WP_Error $placeholder_query_input_schema = $placeholder_query->get_input_schema(); $placeholder_query_output_schema = $placeholder_query->get_output_schema(); + // We first generate the manual input selector for the placeholder query. $is_collection = true === ( $placeholder_query_output_schema['is_collection'] ?? false ); $has_required_variables = array_reduce( array_column( $placeholder_query_input_schema, 'required' ), @@ -101,27 +107,32 @@ public static function register_block( array $block_config = [] ): bool|WP_Error $selectors[] = $selector_config; - // Now, loop over all queries to find compatible selectors + // We run through all the queries to find compatible selectors. foreach ( $block_config[ self::QUERIES_KEY ] as $selector_query_key => $selector_query ) { - // Don't match the placeholder to itself + // Don't match the placeholder to itself, as that's already been done. if ( $selector_query_key === $placeholder_query_key ) { continue; } + $selector_query = self::inflate_query( $selector_query ); $queries[ $selector_query_key ] = $selector_query; $selector_query_input_schema = $selector_query->get_input_schema(); $selector_query_output_schema = $selector_query->get_output_schema(); + // Infer the type of the selector query. + // ToDo: Add support for multiple types. $inferred_selector_query_type = self::infer_query_type( $selector_query_input_schema, $selector_query_output_schema ); if ( 'unknown' === $inferred_selector_query_type ) { continue; } + // If the output schema is not an array, skip. if ( ! is_array( $selector_query_output_schema['type'] ) ) { continue; } + // Find the fields that are present in both the selector's output schema and the placeholder's input schema. $intersecting_keys = array_intersect_key( $selector_query_output_schema['type'], $placeholder_query_input_schema ); // Skip this, if they don't intersect. @@ -129,17 +140,13 @@ public static function register_block( array $block_config = [] ): bool|WP_Error continue; } - // Ensure the name and type of the schemas are truly valid. + // Ensure the fields found have the same name and type in both the schemas. $valid_intersecting_keys = self::validate_selector_query_mapping( $intersecting_keys, $placeholder_query_input_schema, $selector_query_output_schema ); if ( empty( $valid_intersecting_keys ) ) { continue; } - $validation_result = self::validate_query_mapping( $placeholder_query_input_schema, $selector_query_output_schema, $block_title, $placeholder_query_key ); - if ( is_wp_error( $validation_result ) ) { - return $validation_result; - } - + // Now we generate the selector query's config as a selector for the placeholder query. $selector_config = [ 'image_url' => $selector_query->get_image_url(), 'inputs' => self::map_input_variables( $selector_query_input_schema ), @@ -148,6 +155,7 @@ public static function register_block( array $block_config = [] ): bool|WP_Error 'type' => $inferred_selector_query_type, ]; + // Add the selector to the beginning of the selectors array. array_unshift( $selectors, $selector_config @@ -160,6 +168,7 @@ public static function register_block( array $block_config = [] ): bool|WP_Error ]; } + // Build the block configuration. $config = [ 'description' => '', 'icon' => $block_config['icon'] ?? 'cloud', @@ -216,16 +225,6 @@ private static function validate_selector_query_mapping( array $intersecting_key }, ARRAY_FILTER_USE_KEY ); } - private static function validate_query_mapping( array $to_query_input_schema, array $from_query_output_schema, string $block_title, string $from_query_key ): WP_Error|bool { - foreach ( array_keys( $to_query_input_schema ) as $to ) { - if ( ! isset( $from_query_output_schema['type'][ $to ] ) ) { - return self::create_error( $block_title, sprintf( 'Cannot map key "%1$s" from %2$s query. The display query for this block requires a "%1$s" key as an input, but it is not present in the output schema for the %2$s query. Try adding a "%1$s" mapping to the output schema for the %2$s query.', esc_html( $to ), $from_query_key ) ); - } - } - - return true; - } - private static function register_block_pattern( string $block_name, string $pattern_title, string $pattern_content ): string { // Add the block arg to any bindings present in the pattern. $pattern_name = 'remote-data-blocks/' . sanitize_title_with_dashes( $pattern_title, '', 'save' ); diff --git a/inc/Editor/BlockManagement/ConfigStore.php b/inc/Editor/BlockManagement/ConfigStore.php index d2d4ee35d..562c5f7f2 100644 --- a/inc/Editor/BlockManagement/ConfigStore.php +++ b/inc/Editor/BlockManagement/ConfigStore.php @@ -82,14 +82,12 @@ public static function get_data_source_type( string $block_name ): ?string { return null; } - // ToDo: Should there be any verification that the display queries are all from the same data source? - // We are getting just the first display query in this instance, as they'd belong to the same data source. - foreach ( array_keys( $display_queries_to_selectors ) as $display_query_key ) { - $display_query = $config['queries'][ $display_query_key ]; - $data_source = $display_query->get_data_source(); - if ( $data_source instanceof GenericHttpDataSource ) { - return $data_source->get_service_name(); - } + // Get the first display query's data source type. + $display_query_key = array_keys( $display_queries_to_selectors )[0]; + $display_query = $config['queries'][ $display_query_key ]; + $data_source = $display_query->get_data_source(); + if ( $data_source instanceof GenericHttpDataSource ) { + return $data_source->get_service_name(); } return 'code-configured'; diff --git a/tests/inc/Functions/FunctionsTest.php b/tests/inc/Functions/FunctionsTest.php index e2c20513c..28336968e 100644 --- a/tests/inc/Functions/FunctionsTest.php +++ b/tests/inc/Functions/FunctionsTest.php @@ -58,7 +58,7 @@ protected function setUp(): void { ConfigRegistry::init( $this->mock_logger ); } - public function testRegisterBlock(): void { + public function testRegisterBlockWithOldConfigSchema(): void { register_remote_data_block( [ 'title' => 'Test Block', 'render_query' => [ @@ -118,7 +118,17 @@ public function testRegisterListQuery(): void { $block_name = 'remote-data-blocks/test-block-with-list-query'; $config = ConfigStore::get_block_configuration( $block_name ); - $this->assertSame( 'list', $config['selectors'][0]['type'] ?? null ); + + // Ensure that display query is the only key in the display_queries_to_selectors. + $this->assertCount( 1, $config['display_queries_to_selectors'] ); + $this->assertSame( 'display', array_keys( $config['display_queries_to_selectors'] )[0] ); + + // Ensure that there are 2 selectors for the display query. + $this->assertCount( 2, $config['display_queries_to_selectors']['display']['selectors'] ); + + // Ensure that the query_key of the first selector is list, and the second one is display. + $this->assertSame( 'list', $config['display_queries_to_selectors']['display']['selectors'][0]['query_key'] ); + $this->assertSame( 'display', $config['display_queries_to_selectors']['display']['selectors'][1]['query_key'] ); } public function testRegisterSearchQuery(): void { @@ -137,7 +147,17 @@ public function testRegisterSearchQuery(): void { $block_name = 'remote-data-blocks/test-block-with-search-query'; $config = ConfigStore::get_block_configuration( $block_name ); - $this->assertSame( 'search', $config['selectors'][0]['type'] ?? null ); + + // Ensure that display query is the only key in the display_queries_to_selectors. + $this->assertCount( 1, $config['display_queries_to_selectors'] ); + $this->assertSame( 'display', array_keys( $config['display_queries_to_selectors'] )[0] ); + + // Ensure that there are 2 selectors for the display query. + $this->assertCount( 2, $config['display_queries_to_selectors']['display']['selectors'] ); + + // Ensure that the query_key of the first selector is search, and the second one is display. + $this->assertSame( 'search', $config['display_queries_to_selectors']['display']['selectors'][0]['query_key'] ); + $this->assertSame( 'display', $config['display_queries_to_selectors']['display']['selectors'][1]['query_key'] ); } public function testIsRegisteredBlockReturnsTrueForRegisteredBlock(): void { @@ -195,9 +215,18 @@ public function testRegisterSearchQueryWithoutSearchTerms(): void { ], ] ); - $this->assertTrue( $this->mock_logger->hasLoggedLevel( LogLevel::ERROR ) ); - $error_logs = $this->mock_logger->getLogsByLevel( LogLevel::ERROR ); - $this->assertStringContainsString( 'Error registering block Unknown query type: Could not infer the type of the query. Valid query types are "search" and "list".', $error_logs[0]['message'] ); + $block_name = 'remote-data-blocks/invalid-search-block'; + $config = ConfigStore::get_block_configuration( $block_name ); + + // Ensure that display query is the only key in the display_queries_to_selectors. + $this->assertCount( 1, $config['display_queries_to_selectors'] ); + $this->assertSame( 'display', array_keys( $config['display_queries_to_selectors'] )[0] ); + + // Ensure that there is 1 selector for the display query. + $this->assertCount( 1, $config['display_queries_to_selectors']['display']['selectors'] ); + + // Ensure that the query_key of the selector is search. + $this->assertSame( 'display', $config['display_queries_to_selectors']['display']['selectors'][0]['query_key'] ); } public function testRegisterBlockWithOldSchemaFormatNoRenderQuery(): void { @@ -207,7 +236,7 @@ public function testRegisterBlockWithOldSchemaFormatNoRenderQuery(): void { $this->assertTrue( $this->mock_logger->hasLoggedLevel( LogLevel::ERROR ) ); $error_logs = $this->mock_logger->getLogsByLevel( LogLevel::ERROR ); - $this->assertStringContainsString( 'Error registering block Test Block with Old Schema Format No Render Query: Block configuration must have a non-empty "display_queries" array', $error_logs[0]['message'] ); + $this->assertStringContainsString( 'Error registering block Test Block with Old Schema Format No Render Query: Block configuration must have a non-empty "queries" array', $error_logs[0]['message'] ); } public function testRegisterBlockWithNewConfigSchema(): void { @@ -233,11 +262,17 @@ public function testRegisterBlockWithNewConfigSchema(): void { $block_name = 'remote-data-blocks/test-block-with-new-config-schema'; $config = ConfigStore::get_block_configuration( $block_name ); - // Ensure that display is in the display_queries_to_selectors for display along with search. - $this->assertSame( [ 'display', 'search' ], $config['display_queries_to_selectors']['display'] ); + // Ensure that there are 2 selectors for the display query. + $this->assertCount( 3, $config['display_queries_to_selectors']['display']['selectors'] ); + + // Ensure that the query_key of the first selector is search, the second one is list, and the third one is display. + $this->assertSame( 'list', $config['display_queries_to_selectors']['display']['selectors'][0]['query_key'] ); + $this->assertSame( 'search', $config['display_queries_to_selectors']['display']['selectors'][1]['query_key'] ); + $this->assertSame( 'display', $config['display_queries_to_selectors']['display']['selectors'][2]['query_key'] ); - // Ensure that list is in the display_queries_to_selectors for list along with display and search. - $this->assertSame( [ 'list' ], $config['display_queries_to_selectors']['list'] ); + // Ensure that there is 1 selector for the list query. + $this->assertCount( 1, $config['display_queries_to_selectors']['list']['selectors'] ); + $this->assertSame( 'list', $config['display_queries_to_selectors']['list']['selectors'][0]['query_key'] ); } public function testRegisterBlockWithBadDisplayQueries(): void { @@ -264,6 +299,6 @@ public function testRegisterBlockWithBadDisplayQueries(): void { $this->assertTrue( $this->mock_logger->hasLoggedLevel( LogLevel::ERROR ) ); $error_logs = $this->mock_logger->getLogsByLevel( LogLevel::ERROR ); - $this->assertStringContainsString( 'Error registering block Test Block with Bad Display Queries: Display query "list" not found', $error_logs[0]['message'] ); + $this->assertStringContainsString( 'Error registering block Test Block with Bad Display Queries: Query "list" not found for placeholder "List"', $error_logs[0]['message'] ); } } diff --git a/types/remote-data.d.ts b/types/remote-data.d.ts index 698a6b8c6..3e8186f6d 100644 --- a/types/remote-data.d.ts +++ b/types/remote-data.d.ts @@ -55,7 +55,10 @@ interface RemoteDataTemplateBlockAttributes {} interface FieldSelection { action: 'add_field_shortcode' | 'update_field_shortcode' | 'reset_field_shortcode'; - remoteData?: Pick< RemoteData, 'blockName' | 'metadata' | 'queryInputs' | 'displayQueryKey' | 'selectorQueryKey' >; + remoteData?: Pick< + RemoteData, + 'blockName' | 'metadata' | 'queryInputs' | 'displayQueryKey' | 'selectorQueryKey' + >; selectedField: string; selectionPath: 'select_new_tab' | 'select_existing_tab' | 'select_meta_tab' | 'popover'; type: 'field' | 'meta'; From af45470677f39132585a0ce217e76678dbf6004d Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Wed, 11 Jun 2025 13:39:16 +1000 Subject: [PATCH 72/74] Fix js test --- tests/src/utils/remote-data.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/src/utils/remote-data.test.ts b/tests/src/utils/remote-data.test.ts index d3a22b8b7..985bd7427 100644 --- a/tests/src/utils/remote-data.test.ts +++ b/tests/src/utils/remote-data.test.ts @@ -129,7 +129,8 @@ describe( 'remote-data utils', () => { const migrated = migrateRemoteData( remoteData ); expect( migrated ).toEqual( { - queryKey: 'display', + displayQueryKey: 'display', + selectorQueryKey: 'display', queryInputs: [ { title: 'Title 1' } ], results: [ { From f6dcba5e042554cb4d01863d547d4550b6dadf63 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Wed, 11 Jun 2025 13:59:09 +1000 Subject: [PATCH 73/74] Migrate the config of the example blocks, and fix the lack of error boundary --- example/blocks/art-block/art-block.php | 1 - example/blocks/book-block/book-block.php | 11 ++-- .../github-markdown-block.php | 11 ++-- .../blocks/weather-block/weather-block.php | 10 +++- .../blocks/zip-code-block/zip-code-block.php | 10 +++- inc/ExampleApi/ExampleApi.php | 1 - remote-data-blocks.php | 12 ++--- .../components/QueryComponent.tsx | 54 +++++++++++-------- 8 files changed, 66 insertions(+), 44 deletions(-) diff --git a/example/blocks/art-block/art-block.php b/example/blocks/art-block/art-block.php index c6a526cd1..fae2deef0 100644 --- a/example/blocks/art-block/art-block.php +++ b/example/blocks/art-block/art-block.php @@ -130,7 +130,6 @@ function register_art_remote_data_block(): void { 'title' => 'Art Institute of Chicago', 'icon' => 'art', 'queries' => [ - // Changing the name of this query to anything but display will break existing blocks content. 'display' => $get_art_query, 'search_art' => $search_art_query, ], diff --git a/example/blocks/book-block/book-block.php b/example/blocks/book-block/book-block.php index 819c15830..a258c0c51 100644 --- a/example/blocks/book-block/book-block.php +++ b/example/blocks/book-block/book-block.php @@ -189,13 +189,14 @@ function register_open_library_remote_data_block(): void { register_remote_data_block( [ 'title' => 'Open Library Book', 'icon' => 'book', - 'render_query' => [ - 'query' => $get_book_query, + 'queries' => [ + 'display' => $get_book_query, + 'search' => $search_books_query, ], - 'selection_queries' => [ + 'placeholders' => [ [ - 'query' => $search_books_query, - 'type' => 'search', + 'name' => 'Get Book', + 'query_key' => 'display', ], ], 'patterns' => [ diff --git a/example/blocks/github-markdown-block/github-markdown-block.php b/example/blocks/github-markdown-block/github-markdown-block.php index 4f0b1b25e..932ed60b1 100644 --- a/example/blocks/github-markdown-block/github-markdown-block.php +++ b/example/blocks/github-markdown-block/github-markdown-block.php @@ -123,13 +123,14 @@ function register_github_markdown_remote_data_block(): void { register_remote_data_block( [ 'title' => $block_title, - 'render_query' => [ - 'query' => $get_file_as_html_query, + 'queries' => [ + 'display' => $get_file_as_html_query, + 'list' => $get_list_files_query, ], - 'selection_queries' => [ + 'placeholders' => [ [ - 'query' => $get_list_files_query, - 'type' => 'list', + 'name' => 'Get GitHub File', + 'query_key' => 'display', ], ], 'overrides' => [ diff --git a/example/blocks/weather-block/weather-block.php b/example/blocks/weather-block/weather-block.php index 41abb8ce1..c87a40bcb 100644 --- a/example/blocks/weather-block/weather-block.php +++ b/example/blocks/weather-block/weather-block.php @@ -209,8 +209,14 @@ function register_weather_remote_data_block(): void { register_remote_data_block( [ 'title' => 'Weather', 'icon' => 'cloud', - 'render_query' => [ - 'query' => $get_weather_query, + 'queries' => [ + 'display' => $get_weather_query, + ], + 'placeholders' => [ + [ + 'name' => 'Get Weather', + 'query_key' => 'display', + ], ], // Supply a pattern for the block that will be used to display the weather // data. This takes the place of the default pattern provided by the plugin. diff --git a/example/blocks/zip-code-block/zip-code-block.php b/example/blocks/zip-code-block/zip-code-block.php index c464b909e..3facd5ff7 100644 --- a/example/blocks/zip-code-block/zip-code-block.php +++ b/example/blocks/zip-code-block/zip-code-block.php @@ -51,8 +51,14 @@ function register_zip_code_remote_data_block(): void { register_remote_data_block( [ 'title' => 'Zip Code', - 'render_query' => [ - 'query' => $zip_code_query, + 'queries' => [ + 'display' => $zip_code_query, + ], + 'placeholders' => [ + [ + 'name' => 'Get Zip Code', + 'query_key' => 'display', + ], ], ] ); } diff --git a/inc/ExampleApi/ExampleApi.php b/inc/ExampleApi/ExampleApi.php index eb2d459d4..8f27a257b 100644 --- a/inc/ExampleApi/ExampleApi.php +++ b/inc/ExampleApi/ExampleApi.php @@ -120,7 +120,6 @@ public static function register_remote_data_block(): void { register_remote_data_block( [ 'title' => self::$block_title, 'queries' => [ - // Changing the name of this query to anything but display will break existing blocks content. 'display' => $get_record_query, 'get_table' => $get_table_query, ], diff --git a/remote-data-blocks.php b/remote-data-blocks.php index 32677be3d..b199020a1 100644 --- a/remote-data-blocks.php +++ b/remote-data-blocks.php @@ -66,9 +66,9 @@ // Plugin developers: If you need to register additional code for testing, you // can do so here, e.g.: -// require_once __DIR__ . '/example/blocks/art-block/art-block.php'; -// require_once __DIR__ . '/example/blocks/book-block/book-block.php'; -// require_once __DIR__ . '/example/blocks/github-markdown-block/github-markdown-block.php'; -// require_once __DIR__ . '/example/blocks/shopify-mock-store-block/shopify-mock-store-block.php'; -// require_once __DIR__ . '/example/blocks/weather-block/weather-block.php'; -// require_once __DIR__ . '/example/blocks/zip-code-block/zip-code-block.php'; +require_once __DIR__ . '/example/blocks/art-block/art-block.php'; +require_once __DIR__ . '/example/blocks/book-block/book-block.php'; +require_once __DIR__ . '/example/blocks/github-markdown-block/github-markdown-block.php'; +require_once __DIR__ . '/example/blocks/shopify-mock-store-block/shopify-mock-store-block.php'; +require_once __DIR__ . '/example/blocks/weather-block/weather-block.php'; +require_once __DIR__ . '/example/blocks/zip-code-block/zip-code-block.php'; diff --git a/src/blocks/remote-data-container/components/QueryComponent.tsx b/src/blocks/remote-data-container/components/QueryComponent.tsx index ff131231e..59ae38c35 100644 --- a/src/blocks/remote-data-container/components/QueryComponent.tsx +++ b/src/blocks/remote-data-container/components/QueryComponent.tsx @@ -10,6 +10,7 @@ import { Spinner } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; import { useEffect, useState } from '@wordpress/element'; +import { EditErrorBoundary } from './EditErrorBoundary'; import { DataPanel } from './panels/DataPanel'; import { OverridesPanel } from './panels/OverridesPanel'; import { QueryInputsPanel } from './panels/QueryInputsPanel'; @@ -18,7 +19,7 @@ import { CONTAINER_CLASS_NAME } from '@/blocks/remote-data-container/config/cons import { usePatterns } from '@/blocks/remote-data-container/hooks/usePatterns'; import { useRemoteData } from '@/blocks/remote-data-container/hooks/useRemoteData'; import { hasRemoteDataChanged } from '@/utils/block-binding'; -import { getSelectorsForDisplayQuery } from '@/utils/localized-block-data'; +import { getBlockTitle, getSelectorsForDisplayQuery } from '@/utils/localized-block-data'; export interface QueryComponentProps { displayQueryKey: string; @@ -32,7 +33,7 @@ export interface QueryComponentProps { resetQuery: () => void; } -export function QueryComponent( props: QueryComponentProps ) { +export function RemoteDataBlockComponent( props: QueryComponentProps ) { const { displayQueryKey, blockConfig, @@ -44,7 +45,6 @@ export function QueryComponent( props: QueryComponentProps ) { resetQuery, } = props; - const blockProps = useBlockProps( { className: CONTAINER_CLASS_NAME } ); const { getSupportedPatterns, innerBlocksPattern, insertPatternBlocks, resetInnerBlocks } = usePatterns( blockName, rootClientId, displayQueryKey ); const { data, fetch, reset, supportsPagination, loading } = useRemoteData( { @@ -128,14 +128,12 @@ export function QueryComponent( props: QueryComponentProps ) { const supportedPatterns = getSupportedPatterns( data?.results[ 0 ] ); return ( -
- -
+ ); } @@ -160,18 +158,30 @@ export function QueryComponent( props: QueryComponentProps ) { /> ) } + { loading && ( +
+ +
+ ) } + + + ); +} + +export function QueryComponent( props: QueryComponentProps ) { + const blockProps = useBlockProps( { className: CONTAINER_CLASS_NAME } ); + + return ( + <>
- { loading && ( -
- -
- ) } - + + +
); From d8eef25669e632bbab3fc9c19d6b765de6709b28 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Fri, 13 Jun 2025 07:34:46 +1000 Subject: [PATCH 74/74] Revert new config changes to the example blocks --- example/blocks/art-block/art-block.php | 11 +++++------ example/blocks/book-block/book-block.php | 11 +++++------ .../github-markdown-block/github-markdown-block.php | 11 +++++------ example/blocks/weather-block/weather-block.php | 10 ++-------- example/blocks/zip-code-block/zip-code-block.php | 10 ++-------- inc/ExampleApi/ExampleApi.php | 6 ++++++ remote-data-blocks.php | 12 ++++++------ 7 files changed, 31 insertions(+), 40 deletions(-) diff --git a/example/blocks/art-block/art-block.php b/example/blocks/art-block/art-block.php index fae2deef0..ce68703a9 100644 --- a/example/blocks/art-block/art-block.php +++ b/example/blocks/art-block/art-block.php @@ -129,14 +129,13 @@ function register_art_remote_data_block(): void { register_remote_data_block( [ 'title' => 'Art Institute of Chicago', 'icon' => 'art', - 'queries' => [ - 'display' => $get_art_query, - 'search_art' => $search_art_query, + 'render_query' => [ + 'query' => $get_art_query, ], - 'placeholders' => [ + 'selection_queries' => [ [ - 'name' => 'Get Art', - 'query_key' => 'display', + 'query' => $search_art_query, + 'type' => 'search', ], ], ] ); diff --git a/example/blocks/book-block/book-block.php b/example/blocks/book-block/book-block.php index a258c0c51..819c15830 100644 --- a/example/blocks/book-block/book-block.php +++ b/example/blocks/book-block/book-block.php @@ -189,14 +189,13 @@ function register_open_library_remote_data_block(): void { register_remote_data_block( [ 'title' => 'Open Library Book', 'icon' => 'book', - 'queries' => [ - 'display' => $get_book_query, - 'search' => $search_books_query, + 'render_query' => [ + 'query' => $get_book_query, ], - 'placeholders' => [ + 'selection_queries' => [ [ - 'name' => 'Get Book', - 'query_key' => 'display', + 'query' => $search_books_query, + 'type' => 'search', ], ], 'patterns' => [ diff --git a/example/blocks/github-markdown-block/github-markdown-block.php b/example/blocks/github-markdown-block/github-markdown-block.php index 932ed60b1..4f0b1b25e 100644 --- a/example/blocks/github-markdown-block/github-markdown-block.php +++ b/example/blocks/github-markdown-block/github-markdown-block.php @@ -123,14 +123,13 @@ function register_github_markdown_remote_data_block(): void { register_remote_data_block( [ 'title' => $block_title, - 'queries' => [ - 'display' => $get_file_as_html_query, - 'list' => $get_list_files_query, + 'render_query' => [ + 'query' => $get_file_as_html_query, ], - 'placeholders' => [ + 'selection_queries' => [ [ - 'name' => 'Get GitHub File', - 'query_key' => 'display', + 'query' => $get_list_files_query, + 'type' => 'list', ], ], 'overrides' => [ diff --git a/example/blocks/weather-block/weather-block.php b/example/blocks/weather-block/weather-block.php index c87a40bcb..41abb8ce1 100644 --- a/example/blocks/weather-block/weather-block.php +++ b/example/blocks/weather-block/weather-block.php @@ -209,14 +209,8 @@ function register_weather_remote_data_block(): void { register_remote_data_block( [ 'title' => 'Weather', 'icon' => 'cloud', - 'queries' => [ - 'display' => $get_weather_query, - ], - 'placeholders' => [ - [ - 'name' => 'Get Weather', - 'query_key' => 'display', - ], + 'render_query' => [ + 'query' => $get_weather_query, ], // Supply a pattern for the block that will be used to display the weather // data. This takes the place of the default pattern provided by the plugin. diff --git a/example/blocks/zip-code-block/zip-code-block.php b/example/blocks/zip-code-block/zip-code-block.php index 3facd5ff7..c464b909e 100644 --- a/example/blocks/zip-code-block/zip-code-block.php +++ b/example/blocks/zip-code-block/zip-code-block.php @@ -51,14 +51,8 @@ function register_zip_code_remote_data_block(): void { register_remote_data_block( [ 'title' => 'Zip Code', - 'queries' => [ - 'display' => $zip_code_query, - ], - 'placeholders' => [ - [ - 'name' => 'Get Zip Code', - 'query_key' => 'display', - ], + 'render_query' => [ + 'query' => $zip_code_query, ], ] ); } diff --git a/inc/ExampleApi/ExampleApi.php b/inc/ExampleApi/ExampleApi.php index 8f27a257b..94e06114a 100644 --- a/inc/ExampleApi/ExampleApi.php +++ b/inc/ExampleApi/ExampleApi.php @@ -123,6 +123,12 @@ public static function register_remote_data_block(): void { 'display' => $get_record_query, 'get_table' => $get_table_query, ], + 'placeholders' => [ + [ + 'query_key' => 'display', + 'name' => 'Get Record', + ], + ], ] ); } } diff --git a/remote-data-blocks.php b/remote-data-blocks.php index b199020a1..32677be3d 100644 --- a/remote-data-blocks.php +++ b/remote-data-blocks.php @@ -66,9 +66,9 @@ // Plugin developers: If you need to register additional code for testing, you // can do so here, e.g.: -require_once __DIR__ . '/example/blocks/art-block/art-block.php'; -require_once __DIR__ . '/example/blocks/book-block/book-block.php'; -require_once __DIR__ . '/example/blocks/github-markdown-block/github-markdown-block.php'; -require_once __DIR__ . '/example/blocks/shopify-mock-store-block/shopify-mock-store-block.php'; -require_once __DIR__ . '/example/blocks/weather-block/weather-block.php'; -require_once __DIR__ . '/example/blocks/zip-code-block/zip-code-block.php'; +// require_once __DIR__ . '/example/blocks/art-block/art-block.php'; +// require_once __DIR__ . '/example/blocks/book-block/book-block.php'; +// require_once __DIR__ . '/example/blocks/github-markdown-block/github-markdown-block.php'; +// require_once __DIR__ . '/example/blocks/shopify-mock-store-block/shopify-mock-store-block.php'; +// require_once __DIR__ . '/example/blocks/weather-block/weather-block.php'; +// require_once __DIR__ . '/example/blocks/zip-code-block/zip-code-block.php';