diff --git a/inc/Editor/BlockManagement/BlockRegistration.php b/inc/Editor/BlockManagement/BlockRegistration.php index 1bfd88c86..a625f821c 100644 --- a/inc/Editor/BlockManagement/BlockRegistration.php +++ b/inc/Editor/BlockManagement/BlockRegistration.php @@ -5,7 +5,7 @@ defined( 'ABSPATH' ) || exit(); use RemoteDataBlocks\Editor\Assets\Assets; -use RemoteDataBlocks\Telemetry\TracksTelemetry; +use RemoteDataBlocks\Telemetry\Telemetry; use RemoteDataBlocks\Editor\BlockPatterns\BlockPatterns; use RemoteDataBlocks\REST\RemoteDataController; use function register_block_type; @@ -66,7 +66,7 @@ public static function register_container_blocks(): void { wp_localize_script( $script_handle, 'REMOTE_DATA_BLOCKS', [ 'config' => $all_remote_block_configs, 'rest_url' => RemoteDataController::get_url(), - 'tracks_global_properties' => TracksTelemetry::get_global_properties(), + 'tracks_global_properties' => Telemetry::get_global_properties(), ] ); } } diff --git a/inc/Telemetry/DataSourceTelemetry.php b/inc/Telemetry/DataSourceTelemetry.php index c6dd711f0..ae79c45cc 100644 --- a/inc/Telemetry/DataSourceTelemetry.php +++ b/inc/Telemetry/DataSourceTelemetry.php @@ -2,8 +2,9 @@ namespace RemoteDataBlocks\Telemetry; -use RemoteDataBlocks\Telemetry\TracksTelemetry; use RemoteDataBlocks\Store\DataSource\DataSourceConfigManager; +use function do_action; + defined( 'ABSPATH' ) || exit(); class DataSourceTelemetry { @@ -23,7 +24,7 @@ private static function get_interaction_track_props( array $config ): array { } private static function track_interaction( array $config, string $action ): void { - TracksTelemetry::record_event( self::DATA_SOURCE_INTERACTION_EVENT_NAME, array_merge( [ + do_action( 'remote_data_blocks_track_event', self::DATA_SOURCE_INTERACTION_EVENT_NAME, array_merge( [ 'data_source_type' => $config['service'], 'action' => $action, ], self::get_interaction_track_props( $config ) ) ); @@ -61,7 +62,7 @@ function ( $config ) { } ) ); - TracksTelemetry::record_event( self::DATA_SOURCE_VIEW_EVENT_NAME, [ + do_action( 'remote_data_blocks_track_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, diff --git a/inc/Telemetry/EnvironmentConfig.php b/inc/Telemetry/EnvironmentConfig.php deleted file mode 100644 index 21712c3ce..000000000 --- a/inc/Telemetry/EnvironmentConfig.php +++ /dev/null @@ -1,79 +0,0 @@ -get_tracks_core_properties(); - if ( ! isset( $core_props['hosting_provider'] ) ) { - return false; - } - - return 'wpvip' === $core_props['hosting_provider']; - } - - public function is_local_env(): bool { - $core_props = $this->get_tracks_core_properties(); - if ( ! isset( $core_props['vip_env'] ) ) { - return false; - } - - return 'local' === $core_props['vip_env']; - } - - public function is_remote_data_blocks_plugin( string|null $plugin_path ): bool { - return 'remote-data-blocks/remote-data-blocks.php' === $plugin_path; - } - - public function should_track_post_having_remote_data_blocks( int $post_id ): bool { - // Ensure this is not an auto-save or revision. - if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) { - return false; - } - - return true; - } - - /** - * Get the core properties to be sent with each event. - */ - public function get_tracks_core_properties(): array { - if ( function_exists( 'Automattic\VIP\Telemetry\Tracks\get_tracks_core_properties' ) ) { - return get_tracks_core_properties(); - } - - return []; - } - - /** - * Get `Tracks` properties which are specific to "Remote Data Blocks". These properties are sent with each event. - */ - public function get_remote_data_blocks_properties(): array { - return [ - 'plugin_version' => defined( 'REMOTE_DATA_BLOCKS__PLUGIN_VERSION' ) ? constant( 'REMOTE_DATA_BLOCKS__PLUGIN_VERSION' ) : '', - ]; - } -} diff --git a/inc/Telemetry/Telemetry.php b/inc/Telemetry/Telemetry.php new file mode 100644 index 000000000..b92f24845 --- /dev/null +++ b/inc/Telemetry/Telemetry.php @@ -0,0 +1,151 @@ +setup_tracking_via_hooks(); + } + + /** + * Get global event properties. These properties are sent with each event. + */ + public static function get_global_properties(): array { + $plugin_version = defined( 'REMOTE_DATA_BLOCKS__PLUGIN_VERSION' ) ? constant( 'REMOTE_DATA_BLOCKS__PLUGIN_VERSION' ) : 'unknown'; + + return [ + 'plugin_version' => $plugin_version, + ]; + } + + private function setup_tracking_via_hooks(): void { + // WordPress hooks. + add_action( 'activated_plugin', [ $this, 'track_plugin_activation' ], 10, 1 ); + add_action( 'deactivated_plugin', [ $this, 'track_plugin_deactivation' ], 10, 1 ); + add_action( 'save_post', [ $this, 'track_remote_data_blocks_usage' ], 10, 2 ); + + // Custom hook to allow other plugin code to track events + add_action( 'remote_data_blocks_track_event', [ $this, 'record_event' ], 10, 2 ); + } + + /** + * Activation hook. + * + * @param string $plugin_path Path of the plugin that was activated. + */ + public function track_plugin_activation( string $plugin_path ): void { + if ( ! str_ends_with( $this->plugin_path, $plugin_path ) ) { + return; + } + + $this->record_event( 'plugin_toggle', [ 'action' => 'activate' ] ); + } + + /** + * Deactivation hook. + * + * @param string $plugin_path Path of the plugin that was deactivated. + */ + public function track_plugin_deactivation( string $plugin_path ): void { + if ( ! str_ends_with( $this->plugin_path, $plugin_path ) ) { + return; + } + + $this->record_event( 'plugin_toggle', [ 'action' => 'deactivate' ] ); + } + + /** + * Track usage of Remote Data Blocks. + * + * @param int $post_id Post ID. + * @param WP_Post $post Post object. + */ + public function track_remote_data_blocks_usage( int $post_id, WP_Post $post ): void { + if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) { + return; + } + + $post_status = $post->post_status; + if ( 'publish' !== $post_status ) { + return; + } + + // Regular expression to match all remote data blocks present in the post content. + $reg_exp = '/

Tonal Accessories Shelf (Coffee Oak)

Our floating shelf is the perfect way to store all your Tonal accessories. Its sleek, versatile design makes this an easy fit with any style room. Available in Coffee Oak (seen here), as well as Matte Black and Light Aged Ash. Made in the U.S.

$272.99

T-Locks (Pack of 4)

No detail is too small. Tonal’s proprietary T-Locks let you swap out Tonal accessories with a quick push and twist to lock everything in place.

$42.99

Break

Pearl room

Panel

', - ] ); - } - - public function testRecordEventDoesNothingIfInstanceIsNotSet(): void { - /** @var TracksTelemetry|MockObject */ - $obj = new TracksTelemetry(); - - $result = $obj->record_event( 'name', [] ); - - $this->assertEquals( false, $result ); - } - - public function testRecordEventTracksTheEventIfInstanceIsSet(): void { - $mock_tracks = $this->getMockBuilder( MockTracks::class )->onlyMethods( [ 'record_event' ] )->getMock(); - $mock_tracks->expects( $this->exactly( 1 ) )->method( 'record_event' )->with( 'remotedatablocks_event_name', [ 'event_props' ] ); - /** @var TracksTelemetry|MockObject */ - $obj = new TracksTelemetry(); - set_private_property( TracksTelemetry::class, $obj, 'instance', $mock_tracks ); - - $result = $obj->record_event( 'event_name', [ 'event_props' ] ); - - $this->assertEquals( true, $result ); - } - - public function testResetMethod(): void { - $obj = new TracksTelemetry(); - set_private_property( TracksTelemetry::class, $obj, 'instance', new MockTracks() ); - TracksTelemetry::init( new EnvironmentConfig() ); - - $this->assertInstanceOf( MockTracks::class, TracksTelemetry::get_instance() ); - $this->assertInstanceOf( EnvironmentConfig::class, TracksTelemetry::get_env_config() ); - - TracksTelemetry::reset(); - - $this->assertEquals( null, TracksTelemetry::get_instance() ); - $this->assertEquals( null, TracksTelemetry::get_env_config() ); - } -} diff --git a/tests/integration/telemetry/TelemetryTest.php b/tests/integration/telemetry/TelemetryTest.php new file mode 100644 index 000000000..d971b55ff --- /dev/null +++ b/tests/integration/telemetry/TelemetryTest.php @@ -0,0 +1,112 @@ +plugin_path = '/path/to/plugin'; + $this->mock_telemetry = $this->createMock( MockTelemetry::class ); + Telemetry::reset(); + } + + public function test_track_plugin_activation_calls_record_event(): void { + $this->mock_telemetry + ->expects( $this->once() ) + ->method( 'record_event' ) + ->with( + 'plugin_toggle', + $this->equalTo( [ 'action' => 'activate' ] ) + ); + + Telemetry::init( $this->plugin_path, $this->mock_telemetry ); + do_action( 'activated_plugin', $this->plugin_path ); + } + + public function test_track_plugin_activation_does_not_call_record_event_for_other_plugins(): void { + $this->mock_telemetry + ->expects( $this->never() ) + ->method( 'record_event' ); + + Telemetry::init( $this->plugin_path, $this->mock_telemetry ); + do_action( 'activated_plugin', '/path/to/other-plugin' ); + } + + public function test_track_plugin_deactivation_calls_record_event(): void { + $this->mock_telemetry + ->expects( $this->once() ) + ->method( 'record_event' ) + ->with( + 'plugin_toggle', + $this->equalTo( [ 'action' => 'deactivate' ] ) + ); + + Telemetry::init( $this->plugin_path, $this->mock_telemetry ); + do_action( 'deactivated_plugin', $this->plugin_path ); + } + + public function test_track_plugin_deactivation_does_not_call_record_event_for_other_plugins(): void { + $this->mock_telemetry + ->expects( $this->never() ) + ->method( 'record_event' ); + + Telemetry::init( $this->plugin_path, $this->mock_telemetry ); + do_action( 'deactivated_plugin', '/path/to/other-plugin' ); + } + + public function test_track_remote_data_blocks_usage_calls_record_event(): void { + $post_id = $this->factory()->post->create( [ + 'post_status' => 'publish', + 'post_type' => 'post', + 'post_content' => '', + ] ); + + ConfigStore::set_block_configuration( 'remote-data-blocks/example', [ + 'queries' => [ + 'display' => MockQuery::create(), + ], + ] ); + + $this->mock_telemetry + ->expects( $this->once() ) + ->method( 'record_event' ) + ->with( + 'blocks_usage_stats', + $this->equalTo( [ + 'post_status' => 'publish', + 'post_type' => 'post', + 'remote_data_blocks_total_count' => 1, + 'generic-http_data_source_count' => 1, + ] ), + ); + + Telemetry::init( $this->plugin_path, $this->mock_telemetry ); + do_action( 'save_post', $post_id, get_post( $post_id ) ); + } + + public function test_track_remote_data_blocks_usage_does_not_call_record_event_for_non_published_posts(): void { + $post_id = $this->factory()->post->create( [ + 'post_status' => 'draft', + 'post_type' => 'post', + 'post_content' => '', + ] ); + + $this->mock_telemetry + ->expects( $this->never() ) + ->method( 'record_event' ); + + Telemetry::init( $this->plugin_path, $this->mock_telemetry ); + do_action( 'save_post', $post_id, get_post( $post_id ) ); + } +}