Skip to content

Commit

Permalink
Expand interface-driven datasource configs (#39)
Browse files Browse the repository at this point in the history
* yolo

* bluh bluh bluh

* fix elden

* lint

* hhhhii

* remove airtable stuff
mhsdef authored Sep 12, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent c239104 commit 16e65e5
Showing 24 changed files with 228 additions and 91 deletions.
Original file line number Diff line number Diff line change
@@ -10,6 +10,10 @@ class AirtableEldenRingMapDatasource extends HttpDatasource {

public function __construct( private string $access_token ) {}

public function get_display_name(): string {
return 'Elden Ring Map';
}

public function get_endpoint(): string {
return 'https://api.airtable.com/v0/appqI3sJ9R2NcML8Y';
}
Original file line number Diff line number Diff line change
@@ -7,6 +7,10 @@
class AirtableEventsDatasource extends HttpDatasource {
public function __construct( private string $access_token ) {}

public function get_display_name(): string {
return 'Events';
}

public function get_endpoint(): string {
return 'https://api.airtable.com/v0/appVQ2PAl95wQSo9S/tblyGtuxblLtmoqMI';
}
Original file line number Diff line number Diff line change
@@ -15,6 +15,10 @@ public function __construct( string $repo_owner, string $repo_name, string $ref
$this->ref = $ref;
}

public function get_display_name(): string {
return 'GitHub';
}

public function get_endpoint(): string {
return sprintf(
'https://api.github.com/repos/%s/%s/git/trees/%s?recursive=1',
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@

namespace RemoteDataBlocks\Example\GitHub;

use RemoteDataBlocks\Config\HttpDatasourceConfig;
use RemoteDataBlocks\Config\HttpDatasource;
use RemoteDataBlocks\Config\QueryContext;

class GitHubListFilesQuery extends QueryContext {
@@ -40,7 +40,7 @@ class GitHubListFilesQuery extends QueryContext {
],
];

public function __construct( private HttpDatasourceConfig $datasource, string $file_extension ) {
public function __construct( private HttpDatasource $datasource, string $file_extension ) {
parent::__construct( $datasource );
$this->output_variables['root_path'] = sprintf( '$.tree[?(@.path =~ /\\.%s$/)]', ltrim( $file_extension, '.' ) );
}
Original file line number Diff line number Diff line change
@@ -5,6 +5,10 @@
use RemoteDataBlocks\Config\HttpDatasource;

class ArtInstituteOfChicagoDatasource extends HttpDatasource {
public function get_display_name(): string {
return 'Art Institute of Chicago';
}

public function get_endpoint(): string {
return 'https://api.artic.edu/api/v1/artworks';
}
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@

namespace RemoteDataBlocks\Example\ArtInstituteOfChicago;

use RemoteDataBlocks\Config\HttpDatasourceConfig;
use RemoteDataBlocks\Config\HttpDatasource;
use RemoteDataBlocks\Config\QueryContext;

class ArtInstituteOfChicagoGetArtQuery extends QueryContext {
@@ -12,7 +12,7 @@ class ArtInstituteOfChicagoGetArtQuery extends QueryContext {
],
];

public function __construct( HttpDatasourceConfig $datasource ) {
public function __construct( HttpDatasource $datasource ) {
parent::__construct( $datasource );

// Defining the output variables in the constructor allows us to provide
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@

namespace RemoteDataBlocks\Example\ArtInstituteOfChicago;

use RemoteDataBlocks\Config\HttpDatasourceConfig;
use RemoteDataBlocks\Config\HttpDatasource;
use RemoteDataBlocks\Config\QueryContext;

class ArtInstituteOfChicagoSearchArtQuery extends QueryContext {
@@ -14,7 +14,7 @@ class ArtInstituteOfChicagoSearchArtQuery extends QueryContext {

public array $output_variables = [];

public function __construct( HttpDatasourceConfig $datasource ) {
public function __construct( HttpDatasource $datasource ) {
parent::__construct( $datasource );

// Defining the output variables in the constructor allows us to provide
Original file line number Diff line number Diff line change
@@ -5,6 +5,10 @@
use RemoteDataBlocks\Config\HttpDatasource;

class ZipCodeDatasource extends HttpDatasource {
public function get_display_name(): string {
return 'Zip Code Datasource';
}

public function get_endpoint(): string {
return 'https://api.zippopotam.us/us/';
}
13 changes: 7 additions & 6 deletions example/shopify/register.php
Original file line number Diff line number Diff line change
@@ -2,29 +2,30 @@

namespace RemoteDataBlocks\Example\Shopify;

use RemoteDataBlocks\Config\ShopifyDatasource;
use RemoteDataBlocks\Config\ShopifyGetProductQuery;
use RemoteDataBlocks\Config\ShopifySearchProductsQuery;
use RemoteDataBlocks\Editor\ConfigurationLoader;
use RemoteDataBlocks\Logging\LoggerManager;
use function add_action;

require_once __DIR__ . '/inc/interactivity-store/interactivity-store.php';
require_once __DIR__ . '/inc/queries/class-shopify-datasource.php';
require_once __DIR__ . '/inc/queries/class-shopify-add-to-cart-mutation.php';
require_once __DIR__ . '/inc/queries/class-shopify-create-cart-mutation.php';
require_once __DIR__ . '/inc/queries/class-shopify-get-product-query.php';
require_once __DIR__ . '/inc/queries/class-shopify-remove-from-cart-mutation.php';
require_once __DIR__ . '/inc/queries/class-shopify-search-products-query.php';

function register_shopify_block() {
$block_name = 'Shopify Product';
$block_name = 'Shopify (hardcode example)';
$access_token = \RemoteDataBlocks\Example\get_access_token( 'shopify' );
$store_name = 'stoph-test';

if ( empty( $access_token ) ) {
$logger = LoggerManager::instance();
$logger->warning( sprintf( '%s is not defined, cannot register %s block', 'EXAMPLE_SHOPIFY_ACCESS_TOKEN', $block_name ) );
return;
}

$shopify_datasource = new ShopifyDatasource( $access_token );
$shopify_datasource = new ShopifyDatasource( $access_token, $store_name );
$shopify_search_products_query = new ShopifySearchProductsQuery( $shopify_datasource );
$shopify_get_product_query = new ShopifyGetProductQuery( $shopify_datasource );

@@ -35,7 +36,7 @@ function register_shopify_block() {
ConfigurationLoader::register_query( $block_name, new ShopifyAddToCartMutation( $shopify_datasource ) );
ConfigurationLoader::register_query( $block_name, new ShopifyRemoveFromCartMutation( $shopify_datasource ) );

$block_pattern = file_get_contents( __DIR__ . '/inc/patterns/product-teaser.html' );
$block_pattern = file_get_contents( REMOTE_DATA_BLOCKS__PLUGIN_DIRECTORY . '/inc/integrations/shopify-integration/patterns/product-teaser.html' );
ConfigurationLoader::register_block_pattern( $block_name, 'remote-data-blocks/shopify-product-teaser', $block_pattern, [ 'title' => 'Shopify Product Teaser' ] );

register_block_type( __DIR__ . '/build/blocks/shopify-cart' );
38 changes: 38 additions & 0 deletions inc/config/datasource/datasource-interface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

/**
* DatasourceInterface
*
* @package remote-data-blocks
* @since 0.1.0
*/

namespace RemoteDataBlocks\Config;

/**
* Interface used to define a Remote Data Blocks Datasource. It defines the
* properties of a datasource that will be shared by queries against that
* datasource.
*
* If you are a WPVIP customer, datasources are automatically provided by VIP.
* Only implement this interface if you have additional custom datasources.
*/
interface DatasourceInterface {
/**
* Get a human-readable name for this datasource.
*
* This method should return a display name for the datasource that can be
* used in user interfaces or for identification purposes.
*
* @return string The display name of the datasource.
*/
public function get_display_name(): string;

/**
* An optional image URL that can represent the datasource in the block editor
* (e.g., in modals or in the block inspector).
*
* @return string|null The image URL or null if not set.
*/
public function get_image_url(): ?string;
}
16 changes: 0 additions & 16 deletions inc/config/http-datasource-config/http-datasource-config.php

This file was deleted.

46 changes: 46 additions & 0 deletions inc/config/http-datasource/http-datasource-interface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

/**
* HttpDatasourceInterface
*
* @package remote-data-blocks
* @since 0.1.0
*/

namespace RemoteDataBlocks\Config;

/**
* Interface used to define a Remote Data Blocks Datasource for an HTTP API. It
* defines the properties of an API that will be shared by queries against that
* API.
*
* Assumptions:
* - The API speaks valid JSON, both for request and response bodies.
* - The API returns 2XX for successful requests.
* - The API returns 3XX for redirects with a maximum of 5 redirects.
* - The API returns 4XX or 5XX for unrecoverable errors, in which case the
* response should be ignored.
*
* If you are a WPVIP customer, datasources are automatically provided by VIP.
* Only implement this interface if you have custom datasources not provided by VIP.
*/
interface HttpDatasourceInterface {
/**
* Get the endpoint for the query. Note that the query configuration has an
* opportunity to change / override the endpoint at request time. For REST
* APIs, a useful pattern is for the datasource to define a base endpoint and
* the query config to target a specific resource.
*
* @return string The endpoint for the query.
*/
public function get_endpoint(): string;

/**
* Get the request headers. Override this method to provide authorization or
* other custom request headers. Note that the query configuration can override
* or extend these headers at request time.
*
* @return array Associative array of request headers.
*/
public function get_request_headers(): array;
}
48 changes: 13 additions & 35 deletions inc/config/http-datasource/http-datasource.php
Original file line number Diff line number Diff line change
@@ -1,57 +1,35 @@
<?php

/**
* HttpDatasource class
*
* @package remote-data-blocks
* @since 0.1.0
*/

namespace RemoteDataBlocks\Config;

defined( 'ABSPATH' ) || exit();

/**
* Base class used to define a Remote Data Blocks Datasource for an HTTP API. It
* defines the properties of an API that will be shared by queries against that
* API.
* HttpDatasource class
*
* Assumptions:
* - The API speaks valid JSON, both for request and response bodies.
* - The API returns 2XX for successful requests.
* - The API returns 3XX for redirects with a maximum of 5 redirects.
* - The API returns 4XX or 5XX for unrecoverable errors, in which case the
* response should be ignored.
* Implements the HttpDatasourceInterface to define a generic HTTP datasource.
*
* If you are a WPVIP customer, datasources are automatically provided by VIP.
* Only extend this class if you have custom datasources not provided by VIP.
* @package remote-data-blocks
* @since 0.1.0
*/
abstract class HttpDatasource implements HttpDatasourceConfig {
abstract class HttpDatasource implements DatasourceInterface, HttpDatasourceInterface {
/**
* @inheritDoc
*/
abstract public function get_display_name(): string;

/**
* Get the endpoint for the query. Note that the query configuration has an
* opportunity to change / override the endpoint at request time. For REST
* APIs, a useful pattern is for the datasource to define a base endpoint and
* the query config to target a specific resource.
*
* @return string The endpoint for the query.
* @inheritDoc
*/
abstract public function get_endpoint(): string;

/**
* Get the request headers. Override this method to provide authorization or
* other custom request headers. Note that the query configuration can override
* or extend these headers at request time.
*
* @return array Associative array of request headers.
* @inheritDoc
*/
abstract public function get_request_headers(): array;

/**
* An optional image URL that can represent the datasource in the block editor
* (e.g., in modals or in the block inspector).
* @inheritDoc
*/
public function get_image_url(): string|null {
public function get_image_url(): ?string {
return null;
}
}
6 changes: 3 additions & 3 deletions inc/config/query-context/query-context.php
Original file line number Diff line number Diff line change
@@ -57,15 +57,15 @@ class QueryContext implements HttpQueryContext {
/**
* Constructor.
*
* @param HttpDatasourceConfig $datasource The datasource that this query will use.
* @param HttpDatasource $datasource The datasource that this query will use.
*/
public function __construct( private HttpDatasourceConfig $datasource ) {
public function __construct( private HttpDatasource $datasource ) {
}

/**
* Get the datasource associated with this query.
*/
protected function get_datasource(): HttpDatasourceConfig {
protected function get_datasource(): HttpDatasource {
return $this->datasource;
}

Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
<?php

namespace RemoteDataBlocks\Example\Shopify;
namespace RemoteDataBlocks\Config;

use RemoteDataBlocks\Config\HttpDatasource;
use function plugins_url;

defined( 'ABSPATH' ) || exit();

class ShopifyDatasource extends HttpDatasource {
public function __construct( private string $access_token ) {}
public function __construct( private string $access_token, private string $store_name ) {}

public function get_store_name(): string {
return $this->store_name;
}

public function get_display_name(): string {
return 'Shopify (' . $this->store_name . ')';
}

public function get_endpoint(): string {
return 'https://stoph-test.myshopify.com/api/2024-04/graphql.json';
return 'https://' . $this->store_name . '.myshopify.com/api/2024-04/graphql.json';
}

public function get_request_headers(): array {
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<?php

namespace RemoteDataBlocks\Example\Shopify;

use RemoteDataBlocks\Config\GraphqlQueryContext;
namespace RemoteDataBlocks\Config;

class ShopifyGetProductQuery extends GraphqlQueryContext {
public array $input_variables = [
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<?php

namespace RemoteDataBlocks\Example\Shopify;

use RemoteDataBlocks\Config\GraphqlQueryContext;
namespace RemoteDataBlocks\Config;

class ShopifySearchProductsQuery extends GraphqlQueryContext {
public array $input_variables = [
50 changes: 50 additions & 0 deletions inc/integrations/shopify-integration/shopify-integration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace RemoteDataBlocks\Integrations;

use RemoteDataBlocks\Config\ShopifyDatasource;
use RemoteDataBlocks\Config\ShopifyGetProductQuery;
use RemoteDataBlocks\Config\ShopifySearchProductsQuery;
use RemoteDataBlocks\Editor\ConfigurationLoader;
use RemoteDataBlocks\Logging\LoggerManager;
use RemoteDataBlocks\REST\DatasourceCRUD;

require_once __DIR__ . '/datasources/shopify-datasource.php';
require_once __DIR__ . '/queries/shopify-get-product-query.php';
require_once __DIR__ . '/queries/shopify-search-products-query.php';

class ShopifyIntegration {
public static function init(): void {
self::register_dynamic_data_source_blocks();
}

private static function register_dynamic_data_source_blocks(): void {
$data_sources = DatasourceCRUD::get_data_sources( REMOTE_DATA_BLOCKS_SHOPIFY_SERVICE );

foreach ( $data_sources as $config ) {
// Transform data to our experimental format, which is all array based
$config = array_map(
function ( $value ) {
return is_object( $value ) ? (array) $value : $value;
},
(array) $config
);
self::register_blocks_for_shopify_data_source( $config );
}
}

private static function register_blocks_for_shopify_data_source( array $config ): void {
$shopify_datasource = new ShopifyDatasource( $config['token'], $config['store'] );
$shopify_search_products_query = new ShopifySearchProductsQuery( $shopify_datasource );
$shopify_get_product_query = new ShopifyGetProductQuery( $shopify_datasource );

$block_name = $shopify_datasource->get_display_name();
$block_pattern = file_get_contents( __DIR__ . '/patterns/product-teaser.html' );

ConfigurationLoader::register_block( $block_name, $shopify_get_product_query );
ConfigurationLoader::register_search_query( $block_name, $shopify_search_products_query );
ConfigurationLoader::register_block_pattern( $block_name, 'remote-data-blocks/shopify-product-teaser', $block_pattern, [ 'title' => 'Shopify Product Teaser' ] );

LoggerManager::instance()->info( 'Registered Shopify block', [ 'block_name' => $block_name ] );
}
}
12 changes: 10 additions & 2 deletions inc/rest/datasource-crud/datasource-crud.php
Original file line number Diff line number Diff line change
@@ -158,8 +158,16 @@ public static function get_config() {
return (array) get_option( self::CONFIG_OPTION_NAME, [] );
}

public static function get_data_sources() {
return self::get_config() ?? [];
public static function get_data_sources( string $service = '' ) {
$data_sources = self::get_config();

if ( $service ) {
return array_filter( $data_sources, function ( $config ) use ( $service ) {
return $config->service === $service;
} );
}

return $data_sources;
}

public static function get_item_by_uuid( $data_sources, string $uuid ) {
12 changes: 9 additions & 3 deletions remote-data-blocks.php
Original file line number Diff line number Diff line change
@@ -22,6 +22,11 @@

define( 'REMOTE_DATA_BLOCKS__REST_NAMESPACE', 'remote-data-blocks/v1' );

// Datasource services
define( 'REMOTE_DATA_BLOCKS_AIRTABLE_SERVICE', 'airtable' );
define( 'REMOTE_DATA_BLOCKS_GITHUB_SERVICE', 'github' );
define( 'REMOTE_DATA_BLOCKS_SHOPIFY_SERVICE', 'shopify' );

// Autoloader
require_once __DIR__ . '/inc/autoloader.php';
require_once __DIR__ . '/vendor/autoload.php';
@@ -35,11 +40,12 @@
Editor\QueryOverrides::init();
Editor\PatternEditor::init();

// Load Settings Page
PluginSettings::init();

// Integrations
Integrations\ShopifyIntegration::init();
Integrations\VipBlockDataApi::init();

// REST endpoints
REST\RemoteData::init();

// Load Settings Page
PluginSettings::init();
2 changes: 1 addition & 1 deletion tests/inc/config/QueryContextTest.php
Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@ protected function setUp(): void {

public function testGetEndpoint() {
$result = $this->query_context->get_endpoint( [] );
$this->assertEquals( 'https://example.com', $result );
$this->assertEquals( 'https://example.com/api', $result );
}

public function testGetImageUrl() {
13 changes: 5 additions & 8 deletions tests/inc/config/QueryRunnerTest.php
Original file line number Diff line number Diff line change
@@ -7,9 +7,10 @@
use Psr\Http\Message\ResponseInterface;
use RemoteDataBlocks\Config\QueryRunner;
use RemoteDataBlocks\Config\QueryRunnerInterface;
use RemoteDataBlocks\Config\HttpDatasourceConfig;
use RemoteDataBlocks\Config\HttpDatasource;
use RemoteDataBlocks\Config\QueryContext;
use RemoteDataBlocks\HttpClient;
use RemoteDataBlocks\Test\TestDatasource;
use WP_Error;

class QueryRunnerTest extends TestCase {
@@ -23,18 +24,14 @@ protected function setUp(): void {

$this->http_client = $this->createMock( HttpClient::class );

$this->http_datasource = new class() implements HttpDatasourceConfig {
private $endpoint = 'https://api.example.com';
$this->http_datasource = new class() extends TestDatasource {
private $endpoint = 'https://example.com/api';
private $headers = [ 'Content-Type' => 'application/json' ];

public function get_endpoint(): string {
return $this->endpoint;
}

public function get_image_url(): null {
return null;
}

public function get_request_headers(): array {
return $this->headers;
}
@@ -55,7 +52,7 @@ public function set_headers( array $headers ): void {
private $request_body = [ 'query' => 'test' ];
private $response_data = null;

public function __construct( HttpDatasourceConfig $http_datasource, HttpClient $http_client ) {
public function __construct( HttpDatasource $http_datasource, HttpClient $http_client ) {
$this->http_datasource = $http_datasource;
$this->http_client = $http_client;
}
6 changes: 5 additions & 1 deletion tests/mocks/mock-data-source.php
Original file line number Diff line number Diff line change
@@ -4,8 +4,12 @@
use RemoteDataBlocks\Config\HttpDatasource;

class TestDatasource extends HttpDatasource {
public function get_display_name(): string {
return 'Test Datasource';
}

public function get_endpoint(): string {
return 'https://example.com';
return 'https://example.com/api';
}

public function get_request_headers(): array {

0 comments on commit 16e65e5

Please sign in to comment.