Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multiple queries to single block #405

Draft
wants to merge 4 commits into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 50 additions & 4 deletions example/rest-api/art-institute/art-institute.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,18 @@
'data_source' => $aic_data_source,
'endpoint' => function ( array $input_variables ) use ( $aic_data_source ): string {
$endpoint = $aic_data_source->get_endpoint();

// Get and clean IDs from comma-separated string
$ids = array_filter(
array_map( 'trim', explode( ',', (string) $input_variables['id'] ) ),
'strlen'
);
if ( !empty( $ids ) ) {

if ( ! empty( $ids ) ) {
return add_query_arg([
'ids' => implode( ',', $ids ),
'fields' => 'id,title,image_id,artist_title',
], $endpoint);
], $endpoint );
}

return $endpoint;
Expand All @@ -53,6 +53,7 @@
'name' => 'Art ID',
'type' => 'id',
'supports_bulk' => true,
'required' => false,
],
],
'output_schema' => [
Expand Down Expand Up @@ -157,11 +158,56 @@
],
]);

$collection_query = HttpQuery::from_array([
'data_source' => $aic_data_source,
'endpoint' => function ( array $input_variables ) use ( $aic_data_source ): string {

Check failure on line 163 in example/rest-api/art-institute/art-institute.php

View workflow job for this annotation

GitHub Actions / Psalm

UnusedClosureParam

example/rest-api/art-institute/art-institute.php:163:34: UnusedClosureParam: Param input_variables is never referenced in this method (see https://psalm.dev/188)
$endpoint = $aic_data_source->get_endpoint();
return add_query_arg( [
'limit' => 10,
'fields' => 'id,title,image_id,artist_title',
], $endpoint );
},
'output_schema' => [
'is_collection' => true,
'path' => '$.data[*]',
'type' => [
'id' => [
'name' => 'Art ID',
'type' => 'id',
],
'artist_title' => [
'name' => 'Artist Title',
'type' => 'string',
'path' => '$.artist_title',
],
'title' => [
'name' => 'Title',
'type' => 'string',
'path' => '$.title',
],
'image_url' => [
'name' => 'Image URL',
'generate' => function ( $data ): string {
return 'https://www.artic.edu/iiif/2/' . $data['image_id'] . '/full/843,/0/default.jpg';
},
'type' => 'image_url',
],
],
],
]);

register_remote_data_block([
'title' => 'Art Institute of Chicago',
'icon' => 'art',
'render_query' => [
'query' => $get_art_query,
'additional_queries' => [
[
'query' => $collection_query,
'type' => 'collection',
'display_name' => 'Collection',
],
],
],
'selection_queries' => [
[
Expand Down
105 changes: 72 additions & 33 deletions inc/Editor/BlockManagement/ConfigRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class ConfigRegistry {
public const DISPLAY_QUERY_KEY = 'display';
public const LIST_QUERY_KEY = 'list';
public const SEARCH_QUERY_KEY = 'search';
public const COLLECTION_QUERY_KEY = 'collection';

public static function init( ?LoggerInterface $logger = null ): void {
self::$logger = $logger ?? LoggerManager::instance();
Expand All @@ -48,7 +49,21 @@ 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 ) );
}

// Process default render query first
$display_query = self::inflate_query( $user_config[ self::RENDER_QUERY_KEY ]['query'] );

// Initialize queries array with display query as default
$queries = [
self::DISPLAY_QUERY_KEY => $display_query,
];

// Process additional render queries if present
if ( isset( $user_config[ self::RENDER_QUERY_KEY ]['additional_queries'] ) ) {
foreach ( $user_config[ self::RENDER_QUERY_KEY ]['additional_queries'] as $query_config ) {
$queries[ $query_config['type'] ] = self::inflate_query( $query_config['query'] );
}
}

$input_schema = $display_query->get_input_schema();

// Check if render query has any bulk-supporting inputs
Expand Down Expand Up @@ -81,29 +96,35 @@ function ( $input ) {
'loop' => $user_config[ self::RENDER_QUERY_KEY ]['loop'] ?? false,
'overrides' => $user_config['overrides'] ?? [],
'patterns' => [],
'queries' => [
self::DISPLAY_QUERY_KEY => $display_query,
],
'queries' => $queries,
'selectors' => [
[
'image_url' => $display_query->get_image_url(),
'inputs' => array_map( function ( $slug, $input_var ) {
return [
'name' => $input_var['name'] ?? $slug,
'required' => $input_var['required'] ?? true,
'slug' => $slug,
'type' => $input_var['type'] ?? 'string',
'supports_bulk' => $input_var['supports_bulk'] ?? false,
];
}, array_keys( $input_schema ), array_values( $input_schema ) ),
'name' => 'Manual input',
'query_key' => self::DISPLAY_QUERY_KEY,
'type' => 'input',
],
self::create_selector(
$display_query,
self::DISPLAY_QUERY_KEY,
'input',
'Manual input',
$has_bulk_support
),
],
'title' => $block_title,
];

// Add collection queries to selectors if present
if ( isset( $user_config[ self::RENDER_QUERY_KEY ]['additional_queries'] ) ) {
foreach ( $user_config[ self::RENDER_QUERY_KEY ]['additional_queries'] as $query_config ) {
array_unshift(
$config['selectors'],
self::create_selector(
$queries[ $query_config['type'] ],
$query_config['type'],
$query_config['type'],
$query_config['display_name'] ?? null,
$has_bulk_support
)
);
}
}

// 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 ) {
Expand Down Expand Up @@ -135,21 +156,13 @@ function ( $input ) {
// Add the selector to the configuration.
array_unshift(
$config['selectors'],
[
'image_url' => $from_query->get_image_url(),
'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,
'supports_bulk' => $has_bulk_support,
'type' => $from_query_type,
]
self::create_selector(
$from_query,
$from_query::class,
$from_query_type,
$selection_query['display_name'] ?? null,
$has_bulk_support
)
);
}

Expand Down Expand Up @@ -206,4 +219,30 @@ private static function inflate_query( array|QueryInterface $config ): QueryInte

return $config;
}

// Create a selector for a query
private static function create_selector(
QueryInterface $query,
string $query_key,
string $type,
?string $display_name = null,
bool $supports_bulk = false
): array {
return [
'image_url' => $query->get_image_url(),
'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',
'supports_bulk' => $input_var['supports_bulk'] ?? false,
];
}, array_keys( $query->get_input_schema() ), array_values( $query->get_input_schema() ) ),
'name' => $display_name ?? ucfirst( $type ),
'query_key' => $query_key,
'type' => $type,
'supports_bulk' => $supports_bulk,
];
}
}
14 changes: 14 additions & 0 deletions inc/Validation/ConfigSchemas.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,20 @@ private static function generate_remote_data_block_config_schema(): array {
Types::instance_of( QueryInterface::class ),
Types::serialized_config_for( HttpQueryInterface::class ),
),
'additional_queries' => Types::nullable(
Types::list_of(
Types::object( [
'display_name' => Types::nullable( Types::string() ),
'query' => Types::one_of(
Types::instance_of( QueryInterface::class ),
Types::serialized_config_for( HttpQueryInterface::class ),
),
'type' => Types::enum(
ConfigRegistry::COLLECTION_QUERY_KEY,
),
] )
)
),
'loop' => Types::nullable( Types::boolean() ),
] ),
'selection_queries' => Types::nullable(
Expand Down
13 changes: 10 additions & 3 deletions src/blocks/remote-data-container/components/InnerBlocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,26 @@

interface InnerBlocksProps {
blockConfig: BlockConfig;
getInnerBlocks: ( result: RemoteDataResult ) => BlockInstance< RemoteDataInnerBlockAttributes >[];
getInnerBlocks: (
result: RemoteDataApiResult
) => BlockInstance< RemoteDataInnerBlockAttributes >[];
remoteData: RemoteData;
}

export function InnerBlocks( props: InnerBlocksProps ) {
const {
blockConfig: { loop },
blockConfig: { loop, selectors },
getInnerBlocks,
remoteData,
} = props;

if ( loop || remoteData.results.length > 1 ) {
// Use loop template for both loop blocks, multi-selection, or collection queries
if (
loop ||
remoteData.results.length > 1 ||
selectors.some( selector => selector.type === 'collection' )
) {
return <LoopTemplate getInnerBlocks={ getInnerBlocks } remoteData={ remoteData } />;

Check failure on line 27 in src/blocks/remote-data-container/components/InnerBlocks.tsx

View workflow job for this annotation

GitHub Actions / eslint, prettier, wp-scripts

Type '(result: RemoteDataApiResult) => BlockInstance<RemoteDataInnerBlockAttributes>[]' is not assignable to type '(result: RemoteDataResult) => BlockInstance<RemoteDataInnerBlockAttributes>[]'.
}

return <CoreInnerBlocks renderAppender={ CoreInnerBlocks.DefaultBlockAppender } />;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ButtonGroup } from '@wordpress/components';
import { Button, ButtonGroup } from '@wordpress/components';

import { InputModal } from '../modals/InputModal';
import { InputPopover } from '../popovers/InputPopover';
Expand Down Expand Up @@ -38,6 +38,21 @@ export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) {
{ ...selectorProps }
/>
);
case 'collection':
return (
<Button
key={ title }
onClick={ () => {
onSelect( {
type: selector.type,
query_key: selector.query_key,
} );
} }
variant="primary"
>
Load Collection
</Button>
);
case 'input':
return selector.inputs.length === 1 && selector.inputs[ 0 ] ? (
<InputPopover key={ title } input={ selector.inputs[ 0 ] } { ...selectorProps } />
Expand Down
Loading