-
Notifications
You must be signed in to change notification settings - Fork 5
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
load data source configs from constants #354
base: trunk
Are you sure you want to change the base?
Changes from all commits
abc47af
6281804
8712e2e
ffe7d81
93d1e96
62655dc
901e4db
312add7
8fbfdba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace RemoteDataBlocks\Analytics; | ||
|
||
use RemoteDataBlocks\Analytics\TracksAnalytics; | ||
use RemoteDataBlocks\Config\DataSource\DataSourceConfigManager; | ||
|
||
defined( 'ABSPATH' ) || exit(); | ||
|
||
class DataSourceAnalytics { | ||
const DATA_SOURCE_INTERACTION_EVENT_NAME = 'remotedatablocks_data_source_interaction'; | ||
const DATA_SOURCE_VIEW_EVENT_NAME = 'remotedatablocks_view_data_sources'; | ||
const DATA_SOURCE_VIEW_TRACK_TRANSIENT_KEY = 'remotedatablocks_view_data_sources_tracked'; | ||
Comment on lines
+11
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should be specifying a prefix in the |
||
|
||
private static function get_interaction_track_props( array $config ): array { | ||
$props = []; | ||
|
||
if ( 'generic-http' === $config['service'] ) { | ||
$auth = $config['service_config']['auth'] ?? []; | ||
$props['authentication_type'] = $auth['type'] ?? ''; | ||
$props['api_key_location'] = $auth['addTo'] ?? ''; | ||
} | ||
|
||
return $props; | ||
} | ||
|
||
private static function track_interaction( array $config, string $action ): void { | ||
TracksAnalytics::record_event( self::DATA_SOURCE_INTERACTION_EVENT_NAME, array_merge( [ | ||
'data_source_type' => $config['service'], | ||
'action' => $action, | ||
], self::get_interaction_track_props( $config ) ) ); | ||
} | ||
|
||
public static function track_add( array $config ): void { | ||
self::track_interaction( $config, 'add' ); | ||
} | ||
|
||
public static function track_update( array $config ): void { | ||
self::track_interaction( $config, 'update' ); | ||
} | ||
|
||
public static function track_delete( array $config ): void { | ||
self::track_interaction( $config, 'delete' ); | ||
} | ||
|
||
public static function track_view( array $configs ): void { | ||
/** | ||
* Tracks Analytics. Only once per day to reduce noise. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is it noisy and why wouldn't we want an accurate count? Can we call this when rendering the screen instead of from the REST endpoint? |
||
*/ | ||
if ( ! get_transient( self::DATA_SOURCE_VIEW_TRACK_TRANSIENT_KEY ) ) { | ||
$code_configured_count = count( array_filter( | ||
$configs, | ||
fn ( $config ) => DataSourceConfigManager::CONFIG_SOURCE_CODE === $config['config_source'] | ||
) ); | ||
$storage_configured_count = count( array_filter( | ||
$configs, | ||
fn ( $config ) => DataSourceConfigManager::CONFIG_SOURCE_STORAGE === $config['config_source'] | ||
) ); | ||
|
||
TracksAnalytics::record_event( self::DATA_SOURCE_VIEW_EVENT_NAME, [ | ||
'total_data_sources_count' => count( $configs ), | ||
'code_configured_data_sources_count' => $code_configured_count, | ||
'ui_configured_data_sources_count' => $storage_configured_count, | ||
] ); | ||
|
||
set_transient( self::DATA_SOURCE_VIEW_TRACK_TRANSIENT_KEY, true, DAY_IN_SECONDS ); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,95 @@ | ||||||
<?php declare(strict_types = 1); | ||||||
|
||||||
namespace RemoteDataBlocks\Config\DataSource; | ||||||
|
||||||
use RemoteDataBlocks\Config\DataSource\DataSourceInterface; | ||||||
use RemoteDataBlocks\Logging\LoggerManager; | ||||||
use WP_Error; | ||||||
|
||||||
use const RemoteDataBlocks\REMOTE_DATA_BLOCKS__DATA_SOURCE_CLASSMAP; | ||||||
|
||||||
class ConstantConfigStore { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's important for |
||||||
private const CONFIG_CONSTANT_NAME = 'REMOTE_DATA_BLOCKS_CONFIGS'; | ||||||
|
||||||
/** | ||||||
* Get all configurations from the constant. | ||||||
* | ||||||
* @return array<array{ | ||||||
* service: string, | ||||||
* service_config: array<string, mixed> | ||||||
* }> | ||||||
*/ | ||||||
public static function get_configs(): array { | ||||||
if ( ! self::is_available() ) { | ||||||
return []; | ||||||
} | ||||||
|
||||||
$configs = constant( self::CONFIG_CONSTANT_NAME ); | ||||||
$valid_configs = []; | ||||||
|
||||||
foreach ( $configs as $config ) { | ||||||
$validation_result = self::validate_config( $config ); | ||||||
if ( ! is_wp_error( $validation_result ) ) { | ||||||
$valid_configs[] = $config; | ||||||
} else { | ||||||
LoggerManager::instance()->error( | ||||||
sprintf( | ||||||
'Invalid data source config found (uuid: %s): %s', | ||||||
$config['uuid'] ?? 'unknown', | ||||||
$validation_result->get_error_message() | ||||||
) | ||||||
); | ||||||
} | ||||||
} | ||||||
|
||||||
return $valid_configs; | ||||||
} | ||||||
|
||||||
public static function get_config_by_uuid( string $uuid ): array|WP_Error { | ||||||
$configs = constant( self::CONFIG_CONSTANT_NAME ); | ||||||
$found = array_filter( $configs, fn ( $config ) => $config['uuid'] === $uuid ); | ||||||
|
||||||
if ( empty( $found ) ) { | ||||||
return new WP_Error( | ||||||
'data_source_not_found', | ||||||
__( 'Data source not found', 'remote-data-blocks' ), | ||||||
[ 'status' => 404 ] | ||||||
); | ||||||
} | ||||||
|
||||||
$config = reset( $found ); | ||||||
$validation_result = self::validate_config( $config ); | ||||||
|
||||||
if ( is_wp_error( $validation_result ) ) { | ||||||
return $validation_result; | ||||||
} | ||||||
|
||||||
return $config; | ||||||
} | ||||||
|
||||||
public static function validate_config( array $config ): DataSourceInterface|WP_Error { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you update methods to |
||||||
$data_source_class = REMOTE_DATA_BLOCKS__DATA_SOURCE_CLASSMAP[ $config['service'] ] ?? null; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of using the classmap, we should use the new |
||||||
if ( null === $data_source_class ) { | ||||||
return new WP_Error( | ||||||
'unsupported_data_source', | ||||||
__( 'Unsupported data source service', 'remote-data-blocks' ) | ||||||
); | ||||||
} | ||||||
|
||||||
return $data_source_class::from_array( $config ); | ||||||
} | ||||||
|
||||||
/** | ||||||
* Check if constant configuration is available and valid. | ||||||
* | ||||||
* @return bool Whether the constant configuration is available and valid. | ||||||
*/ | ||||||
public static function is_available(): bool { | ||||||
if ( ! defined( self::CONFIG_CONSTANT_NAME ) || '' === constant( self::CONFIG_CONSTANT_NAME ) ) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. covered by
Suggested change
|
||||||
return false; | ||||||
} | ||||||
|
||||||
$value = constant( self::CONFIG_CONSTANT_NAME ); | ||||||
return is_array( $value ); | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,233 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace RemoteDataBlocks\Config\DataSource; | ||
|
||
use RemoteDataBlocks\Editor\BlockManagement\ConfigStore; | ||
use RemoteDataBlocks\WpdbStorage\DataSourceCrud; | ||
use RemoteDataBlocks\Config\DataSource\ConstantConfigStore; | ||
use WP_Error; | ||
|
||
class DataSourceConfigManager { | ||
public const CONFIG_SOURCE_CODE = 'code'; | ||
public const CONFIG_SOURCE_STORAGE = 'storage'; | ||
public const CONFIG_SOURCE_CONSTANT = 'constant'; | ||
public const MUTABLE_CONFIG_SOURCES = [ self::CONFIG_SOURCE_STORAGE ]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice |
||
|
||
private static function get_all_from_storage(): array { | ||
return array_map( | ||
fn ( array $config ) => array_merge( $config, [ 'config_source' => self::CONFIG_SOURCE_STORAGE ] ), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think we should be consistent and use either all arrow functions or all anonymous function closures? The behavior of scope is different between the two |
||
DataSourceCrud::get_configs() | ||
); | ||
} | ||
|
||
private static function get_all_from_constant(): array { | ||
return array_map( | ||
fn ( array $config ) => array_merge( $config, [ 'config_source' => self::CONFIG_SOURCE_CONSTANT ] ), | ||
ConstantConfigStore::get_configs() | ||
); | ||
} | ||
|
||
private static function get_all_from_code(): array { | ||
return array_map( | ||
fn ( array $config ) => array_merge( $config, [ 'config_source' => self::CONFIG_SOURCE_CODE ] ), | ||
ConfigStore::get_data_sources_as_array() | ||
); | ||
} | ||
|
||
/** | ||
* Quick and dirty de-duplication of data sources. If the data source does | ||
* not have a UUID (because it is registered in code), we generate an | ||
* identifier based on the display name and service name. | ||
*/ | ||
private static function de_duplicate_configs( array $configs ): array { | ||
return array_values( array_reduce( | ||
$configs, | ||
function ( array $acc, array $item ) { | ||
$identifier = $item['uuid'] ?? md5( | ||
sprintf( '%s_%s', $item['service_config']['display_name'], $item['service'] ) | ||
); | ||
$acc[ $identifier ] = $item; | ||
return $acc; | ||
}, | ||
[] | ||
) ); | ||
} | ||
|
||
/** | ||
* Get all data sources from all origins. | ||
* | ||
* @return array<array{ | ||
* uuid?: string, | ||
* service: string, | ||
* service_config: array<string, mixed>, | ||
* config_source: string, | ||
* __metadata?: array{ | ||
* created_at: string, | ||
* updated_at: string | ||
* } | ||
* }> | ||
*/ | ||
public static function get_all(): array { | ||
$code_configured = self::get_all_from_code(); | ||
$constant_configured = self::get_all_from_constant(); | ||
$storage_configured = self::get_all_from_storage(); | ||
|
||
/** | ||
* De-duplicate configs. | ||
* | ||
* Precedence (lowest to highest): | ||
* - Code-configured data sources | ||
* - Constant-configured data sources | ||
* - Storage-configured data sources | ||
*/ | ||
return self::de_duplicate_configs( | ||
array_merge( $code_configured, $constant_configured, $storage_configured ) | ||
); | ||
} | ||
|
||
/** | ||
* Get a data source by its UUID. | ||
* | ||
* @param string $uuid The UUID of the data source to get. | ||
* @return array{ | ||
* uuid: string, | ||
* service: string, | ||
* service_config: array<string, mixed>, | ||
* config_source: string, | ||
* __metadata?: array{ | ||
* created_at: string, | ||
* updated_at: string | ||
* } | ||
* }|WP_Error | ||
*/ | ||
public static function get( string $uuid ): array|WP_Error { | ||
$from_constant = ConstantConfigStore::get_config_by_uuid( $uuid ); | ||
if ( ! is_wp_error( $from_constant ) ) { | ||
return array_merge( | ||
$from_constant, | ||
[ 'config_source' => self::CONFIG_SOURCE_CONSTANT ] | ||
); | ||
} | ||
|
||
$from_storage = DataSourceCrud::get_config_by_uuid( $uuid ); | ||
if ( ! is_wp_error( $from_storage ) ) { | ||
return array_merge( | ||
$from_storage, | ||
[ 'config_source' => self::CONFIG_SOURCE_STORAGE ] | ||
); | ||
} | ||
|
||
return new WP_Error( | ||
'data_source_not_found', | ||
__( 'Data source not found', 'remote-data-blocks' ), | ||
[ 'status' => 404 ] | ||
); | ||
} | ||
|
||
/** | ||
* Create a new data source. | ||
* | ||
* @param array $config The configuration for the new data source. | ||
* @return array{ | ||
* uuid: string, | ||
* service: string, | ||
* service_config: array<string, mixed>, | ||
* config_source: string, | ||
* __metadata: array{ | ||
* created_at: string, | ||
* updated_at: string | ||
* } | ||
* }|WP_Error | ||
*/ | ||
public static function create( array $config ): array|WP_Error { | ||
$result = DataSourceCrud::create_config( $config ); | ||
if ( is_wp_error( $result ) ) { | ||
return $result; | ||
} | ||
|
||
return array_merge( $result, [ 'config_source' => self::CONFIG_SOURCE_STORAGE ] ); | ||
} | ||
|
||
/** | ||
* Update a data source. | ||
* | ||
* @param string $uuid The UUID of the data source to update. | ||
* @param array $config The new configuration for the data source. | ||
* @return array{ | ||
* uuid: string, | ||
* service: string, | ||
* service_config: array<string, mixed>, | ||
* config_source: string, | ||
* __metadata: array{ | ||
* created_at: string, | ||
* updated_at: string | ||
* } | ||
* }|WP_Error | ||
*/ | ||
public static function update( string $uuid, array $config ): array|WP_Error { | ||
if ( | ||
isset( $config['config_source'] ) && | ||
! in_array( $config['config_source'], self::MUTABLE_CONFIG_SOURCES, true ) | ||
) { | ||
/** | ||
* Only storage-configured data sources are mutable. | ||
*/ | ||
return new WP_Error( | ||
'cannot_update_config', | ||
__( 'Cannot update a data source with this config_source', 'remote-data-blocks' ), | ||
[ 'status' => 400 ] | ||
); | ||
} | ||
|
||
$result = DataSourceCrud::update_config_by_uuid( $uuid, $config ); | ||
if ( is_wp_error( $result ) ) { | ||
return $result; | ||
} | ||
|
||
return array_merge( $result, [ 'config_source' => self::CONFIG_SOURCE_STORAGE ] ); | ||
} | ||
|
||
/** | ||
* Delete a data source. | ||
* | ||
* @param string $uuid The UUID of the data source to delete. | ||
* @return true|WP_Error True on success, WP_Error on failure. | ||
*/ | ||
public static function delete( string $uuid ): bool|WP_Error { | ||
return DataSourceCrud::delete_config_by_uuid( $uuid ); | ||
} | ||
|
||
/** | ||
* Get all configured data sources for a specific service from both storage and constants. | ||
* This includes sources configured via storage and constants, but not those defined in code. | ||
Comment on lines
+201
to
+202
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Silently excluding these configs from a method named There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if a code-configured data source wants to have queries registered for it? |
||
* | ||
* @param string $service The service identifier to filter by. | ||
* @return array<array{ | ||
* uuid?: string, | ||
* service: string, | ||
* service_config: array<string, mixed>, | ||
* config_source: string, | ||
* __metadata?: array{ | ||
* created_at: string, | ||
* updated_at: string | ||
* } | ||
* }> | ||
*/ | ||
public static function get_all_configured_by_service( string $service ): array { | ||
$storage_configs = self::get_all_from_storage(); | ||
$constant_configs = self::get_all_from_constant(); | ||
|
||
$all_configs = array_merge( $constant_configs, $storage_configs ); | ||
|
||
/** | ||
* De-duplicate configs. | ||
* | ||
* Precedence (lowest to highest): | ||
* - Constant-configured data sources | ||
* - Storage-configured data sources | ||
*/ | ||
$de_duplicated_configs = self::de_duplicate_configs( $all_configs ); | ||
|
||
return array_filter( $de_duplicated_configs, fn ( array $config ) => $config['service'] === $service ); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we please name this class and folder using
Telemetry
instead of analytics?