Skip to content

Commit

Permalink
Add ui:search_input input variable (#337)
Browse files Browse the repository at this point in the history
* Update useRemoteData to be more explicit about state management

* Add ui:search input variable

* Update useRemoteData to implement search

* Update Art Institute example with ui:search variable

* Update search documentation

* Update useDebouncedState to avoid need for ref

* Revert "Update useDebouncedState to avoid need for ref"

This reverts commit 77656a0.

* Fix selector in FieldShortcodeSelectNew

* Implement query pagination (#338)

* Implement pagination

* Update documentation for pagination

* Update Art Institute example

* Update Query docs

* Fix a few schema references

* Move field filter to useful place

* Move removeNullValues to util

* Refactor ItemList to remove memos

* Tighten up

Co-authored-by: Max Schmeling <[email protected]>

---------

Co-authored-by: Max Schmeling <[email protected]>

---------

Co-authored-by: Max Schmeling <[email protected]>
  • Loading branch information
chriszarate and maxschmeling authored Feb 8, 2025
1 parent 64af67d commit 243d6ea
Show file tree
Hide file tree
Showing 27 changed files with 823 additions and 280 deletions.
8 changes: 4 additions & 4 deletions docs/concepts/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ Below, you'll find specific use cases where Remote Data Blocks shines. We are wo
- **Example:** Create a page and rewrite rule for /products/{product_id}/ and configure a Remote Data Block on that page to display the referenced product.
- Your presentation of remote data aligns with the capabilities of Block Bindings.
- **Example:** Display an item of clothing using a core paragraph, heading, image, and button blocks.
- You do not require complex filtering or pagination.
- **Example:** To select an item of clothing, load a finite list of top-selling products or search all products by a specific term.
- You do not require complex filtering.
- **Example:** To select an item of clothing, load a list of top-selling products or search all products by a specific term.
- Your data is denormalized and relatively flat.
- **Example:** A row from a Google Sheet with no references to external entities.

Expand All @@ -35,8 +35,8 @@ Below, you'll find specific use cases where Remote Data Blocks shines. We are wo
- You have multiple remote data sources that require interaction. Or, you want to implement a complex content architecture using Remote Data Blocks instead of leveraging WordPress custom post types and/or taxonomies.
- These two challenges are directly related to the issues with normalized data. If you have data sources that relate to one another, you have to write custom code to query missing data and stitch them together.
- Judging complexity is difficult, but implementing large applications using Remote Data Blocks is not advisable.
- You require complex filtering or rely heavily on pagination.
- Our UI components for filtering and pagination are still under development.
- You require complex filtering or have complex pagination needs.
- Our UI components for filtering are pagination still under development.

Over time, Remote Data Blocks will grow and improve and these guidelines will change.

Expand Down
12 changes: 6 additions & 6 deletions docs/extending/block-registration.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,21 +95,21 @@ Example:

#### Search queries

Search queries must return a collection and must accept a string input variable of `search_terms`. The [Art Institute of Chicago](https://github.com/Automattic/remote-data-blocks/blob/trunk/example/rest-api/art-institute/README.md) example looks like this:
Search queries must return a collection and must accept one input variable with the special type `ui:search_input`. The [Art Institute of Chicago](https://github.com/Automattic/remote-data-blocks/blob/trunk/example/rest-api/art-institute/README.md) example looks like this:

```php
$search_art_query = HttpQuery::from_array([
'data_source' => $aic_data_source,
'endpoint' => function ( array $input_variables ) use ( $aic_data_source ): string {
$query = $input_variables['search_terms'];
$query = $input_variables['search'];
$endpoint = $aic_data_source->get_endpoint() . '/search';

return add_query_arg( [ 'q' => $query ], $endpoint );
},
'input_schema' => [
'search_terms' => [
'name' => 'Search Terms',
'type' => 'string',
'search' => [
'name' => 'Search terms',
'type' => 'ui:search_input',
],
],
'output_schema' => [
Expand All @@ -129,7 +129,7 @@ $search_art_query = HttpQuery::from_array([
]);
```

Here you can see the input variable of `search_terms` is used in the endpoint method to populate a query string. You can read more about [queries](./query.md) and how to construct them. End users enter the search term to find the specific item.
Here you can see the `search` input variable has a special type of `ui:search_input` and is used in the endpoint method to populate a query string. You can read more about [queries](./query.md) and how to construct them. End users enter the search term to find the specific item.

![Screenshot showing the search inputin the WordPress Editor](https://raw.githubusercontent.com/Automattic/remote-data-blocks/trunk/docs/extending/search-input.png)

Expand Down
67 changes: 64 additions & 3 deletions docs/extending/query.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ The `endpoint` property defines the query endpoint. It can be a string or a call

### input_schema: array

The `input_schema` property defines the input variables expected by the query. The method should return an associative array of input variable definitions. The keys of the array are machine-friendly input variable names, and the values are associative arrays with the following structure:
The `input_schema` property defines the input variables expected by the query. The property should be an associative array of input variable definitions. The keys of the array are machine-friendly input variable names, and the values are associative arrays with the following structure:

- `name` (optional): The human-friendly display name of the input variable
- `default_value` (optional): The default value for the input variable.
Expand All @@ -107,11 +107,40 @@ The `input_schema` property defines the input variables expected by the query. T
],
```

If omitted, it defaults to an empty array.
There are also some special input variable types:

- `ui:search_input`: A variable with this type indicates that the query supports searching. It must accept a `string` containing search terms.
- `ui:pagination_offset`: A variable with this type indicates that the query supports offset pagination. It must accept an `integer` containing the requested offset. See `pagination_schema` for additional information and requirements.
- `ui:pagination_page`: A variable with this type indicates that the query supports page-based pagination. It must accept an `integer` containing the requested results page. See `pagination_schema` for additional information and requirements.
- `ui:pagination_per_page`: A variable with this type indicates that the query supports controlling the number of resultsper page. It must accept an `integer` containing the number of requested results.
- `ui:pagination_cursor_next` and `ui_pagination_cursor_previous`: Variables with these types indicate that the query supports cursor pagination. They accept `strings` containing the requested cursor. See `pagination_schema` for additional information and requirements.

#### Example with search and pagination input variables

```php
'input_schema' => [
'search' => [
'name' => 'Search terms',
'type' => 'ui:search_input',
],
'limit' => [
'default_value' => 10,
'name' => 'Pagination limit',
'type' => 'ui:pagination_per_page',
],
'page' => [
'default_value' => 1,
'name' => 'Pagination page',
'type' => 'ui:pagination_page',
],
],
```

If omitted, `input_schema` defaults to an empty array.

### output_schema: array (required)

The `output_schema` property defines how to extract data from the API response. The method should return an associative array with the following structure:
The `output_schema` property defines how to extract data from the API response. The property should be an associative array with the following structure:

- `format` (optional): A callable function that formats the output variable value.
- `generate` (optional): A callable function that generates or extracts the output variable value from the response, as an alternative to `path`.
Expand Down Expand Up @@ -163,6 +192,38 @@ Accepted primitive types are:

We have more in-depth [`output_schema`](./query-output_schema.md) examples.

### pagination_schema: array

If your query supports pagination, the `pagination_schema` property defines how to extract pagination-related values from the query response. If defined, the property should be an associative array with the following structure:

- `total_items` (required): A variable definition that extracts the total number of items across every page of results.
- `cursor_next`: If your query supports cursor pagination, a variable definition that extracts the cursor for the next page of results.
- `cursor_previous`: If your query supports cursor pagination, a variable definition that extracts the cursor for the previous page of results.

Note that the `total_items` variable is required for all types of pagination.

#### Example

```php
'pagination_schema' => [
'total_items' => [
'name' => 'Total items',
'path' => '$.pagination.totalItems',
'type' => 'integer',
],
'cursor_next' => [
'name' => 'Next page cursor',
'path' => '$.pagination.nextCursor',
'type' => 'string',
],
'cursor_previous' => [
'name' => 'Previous page cursor',
'path' => '$.pagination.previousCursor',
'type' => 'string',
],
],
```

### request_method: string

The `request_method` property defines the HTTP request method used by the query. By default, it is `'GET'`.
Expand Down
36 changes: 30 additions & 6 deletions example/rest-api/art-institute/art-institute.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,32 @@ function register_aic_block(): void {
$search_art_query = HttpQuery::from_array([
'data_source' => $aic_data_source,
'endpoint' => function ( array $input_variables ) use ( $aic_data_source ): string {
$query = $input_variables['search_terms'];
$endpoint = $aic_data_source->get_endpoint() . '/search';
$endpoint = $aic_data_source->get_endpoint();
$search_terms = $input_variables['search'] ?? '';

return add_query_arg( [ 'q' => $query ], $endpoint );
if ( ! empty( $search_terms ) ) {
$endpoint = add_query_arg( [ 'q' => $search_terms ], $endpoint . '/search' );
}

return add_query_arg( [
'limit' => $input_variables['limit'],
'page' => $input_variables['page'],
], $endpoint );
},
'input_schema' => [
'search_terms' => [
'name' => 'Search Terms',
'type' => 'string',
'search' => [
'name' => 'Search terms',
'type' => 'ui:search_input',
],
'limit' => [
'default_value' => 10,
'name' => 'Pagination limit',
'type' => 'ui:pagination_per_page',
],
'page' => [
'default_value' => 1,
'name' => 'Pagination page',
'type' => 'ui:pagination_page',
],
],
'output_schema' => [
Expand All @@ -94,6 +111,13 @@ function register_aic_block(): void {
],
],
],
'pagination_schema' => [
'total_items' => [
'name' => 'Total items',
'path' => '$.pagination.total',
'type' => 'integer',
],
],
]);

register_remote_data_block([
Expand Down
8 changes: 8 additions & 0 deletions inc/Config/Query/HttpQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ public function get_output_schema(): array {
return $this->config['output_schema'];
}

/**
* Get the pagination schema for this query. If null, pagination will be
* disabled.
*/
public function get_pagination_schema(): ?array {
return $this->config['pagination_schema'];
}

/**
* Get the request body for the current query execution. Any non-null result
* will be converted to JSON using `wp_json_encode`.
Expand Down
1 change: 1 addition & 0 deletions inc/Config/Query/QueryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ public function get_data_source(): DataSourceInterface;
public function get_image_url(): ?string;
public function get_input_schema(): array;
public function get_output_schema(): array;
public function get_pagination_schema(): ?array;
}
5 changes: 5 additions & 0 deletions inc/Config/QueryRunner/QueryResponseParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ private function parse_response_objects( mixed $objects, array $type ): array {

// Loop over the defined fields in the schema type and extract the values from the object.
foreach ( $type as $field_name => $mapping ) {
// Skip null values.
if ( null === $mapping ) {
continue;
}

// A generate function accepts the current object and returns the field value.
if ( isset( $mapping['generate'] ) && is_callable( $mapping['generate'] ) ) {
$field_value = call_user_func( $mapping['generate'], json_decode( $json_obj->getJson(), true ) );
Expand Down
16 changes: 16 additions & 0 deletions inc/Config/QueryRunner/QueryRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,25 @@ public function execute( HttpQueryInterface $query, array $input_variables ): ar
$results = $is_collection ? $results : [ $results ];
$metadata = $this->get_response_metadata( $query, $raw_response_data['metadata'], $results );

// Pagination schema defines how to extract pagination data from the response.
$pagination = null;
$pagination_schema = $query->get_pagination_schema();

if ( is_array( $pagination_schema ) ) {
$pagination_data = $parser->parse( $response_data, [ 'type' => $pagination_schema ] )['result'] ?? null;

if ( is_array( $pagination_data ) ) {
$pagination = [];
foreach ( $pagination_data as $key => $value ) {
$pagination[ $key ] = $value['value'];
}
}
}

return [
'is_collection' => $is_collection,
'metadata' => $metadata,
'pagination' => $pagination,
'results' => $results,
];
}
Expand Down
19 changes: 16 additions & 3 deletions inc/Editor/BlockManagement/ConfigRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,29 @@ public static function register_block( array $user_config = [] ): bool|WP_Error
}
}

if ( self::SEARCH_QUERY_KEY === $from_query_type && ! isset( $from_input_schema['search_terms'] ) ) {
return self::create_error( $block_title, 'A search query must have a "search_terms" input variable' );
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' => [],
'inputs' => array_map( function ( $slug, $input_var ) {
return [
'name' => $input_var['name'] ?? $slug,
'required' => $input_var['required'] ?? false,
'slug' => $slug,
'type' => $input_var['type'] ?? 'string',
];
}, array_keys( $from_input_schema ), array_values( $from_input_schema ) ),
'name' => $selection_query['display_name'] ?? ucfirst( $from_query_type ),
'query_key' => $from_query::class,
'type' => $from_query_type,
Expand Down
6 changes: 3 additions & 3 deletions inc/Integrations/SalesforceB2C/SalesforceB2CIntegration.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,12 @@ private static function get_queries( SalesforceB2CDataSource $data_source ): arr
'%s/search/shopper-search/v1/organizations/%s/product-search?siteId=RefArchGlobal&q=%s',
$base_endpoint,
$service_config['organization_id'],
urlencode( $input_variables['search_terms'] )
urlencode( $input_variables['search'] )
);
},
'input_schema' => [
'search_terms' => [
'type' => 'string',
'search' => [
'type' => 'ui:search_input',
],
],
'output_schema' => [
Expand Down
4 changes: 2 additions & 2 deletions inc/Integrations/Shopify/ShopifyIntegration.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ public static function get_queries( ShopifyDataSource $data_source ): array {
'shopify_search_products' => GraphqlQuery::from_array( [
'data_source' => $data_source,
'input_schema' => [
'search_terms' => [
'type' => 'string',
'search' => [
'type' => 'ui:search_input',
],
],
'output_schema' => [
Expand Down
Loading

0 comments on commit 243d6ea

Please sign in to comment.