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

Enhancement/7602-parsing-custom-dimensions-report #7697

Merged
merged 23 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d59ab88
Add custom dimension string constants.
jimmymadon Oct 13, 2023
dc956b1
Add helpers to convert user and category IDs to names.
jimmymadon Oct 13, 2023
50ba870
Add unit tests for user and category helpers.
jimmymadon Oct 13, 2023
aeca2f4
Convert custom dimension IDs to names in report response.
jimmymadon Oct 13, 2023
c72e6f7
Merge branch 'develop' into enhancement/7602-parsing-custom-dimension…
jimmymadon Oct 13, 2023
e5b753f
Fix potentially existent category in tests.
jimmymadon Oct 13, 2023
9abf2eb
Generalize entity helpers.
jimmymadon Oct 15, 2023
f2477e0
Fix potential null value in term name.
jimmymadon Oct 16, 2023
07e0e7f
Extract custom dimensions swapping method into its own class.
jimmymadon Oct 16, 2023
b2966ac
Remove outdated test.
jimmymadon Oct 16, 2023
259f120
Update references in Response to use new class.
jimmymadon Oct 16, 2023
df7e617
Add custom dimensions ID to name converter tests.
jimmymadon Oct 16, 2023
794f4f2
Refactor WP_Entities_Helpers into new parser class.
jimmymadon Oct 16, 2023
2f0d0e5
Remove redundant helper class and methods.
jimmymadon Oct 16, 2023
5fe120c
Add support for mixed number and string category ID values.
jimmymadon Oct 16, 2023
3a1b1d9
Update return type and dependency of the swap_custom_dimensions method.
jimmymadon Oct 16, 2023
5bf0282
Update unit tests for dependency changes and remove duplicate object …
jimmymadon Oct 16, 2023
f34d9ca
Clarify documentation around the customEvent prefix.
jimmymadon Oct 16, 2023
12d864c
Merge branch 'develop' into enhancement/7602-parsing-custom-dimension…
jimmymadon Oct 16, 2023
3380e3f
Update doc block.
aaemnnosttv Oct 16, 2023
93dadc5
Fix typo.
aaemnnosttv Oct 16, 2023
5511c1e
Make internal methods protected.
aaemnnosttv Oct 16, 2023
c7bb05b
Remove unnecessary variable.
aaemnnosttv Oct 16, 2023
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
11 changes: 11 additions & 0 deletions includes/Modules/Analytics_4.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,17 @@ final class Analytics_4 extends Module
*/
const MODULE_SLUG = 'analytics-4';

/**
* Prefix used to fetch custom dimensions in reports.
*/
const CUSTOM_EVENT_PREFIX = 'customEvent:';

/**
* Custom dimensions tracked by Site Kit.
*/
const CUSTOM_DIMENSION_POST_AUTHOR = 'googlesitekit_post_author';
const CUSTOM_DIMENSION_POST_CATEGORIES = 'googlesitekit_post_categories';

/**
* Registers functionality through WordPress hooks.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
<?php
/**
* Class Google\Site_Kit\Modules\Analytics_4\Report\Custom_Dimensions_Response_Parser
*
* @package Google\Site_Kit\Modules\Analytics_4\Report
* @copyright 2023 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/

namespace Google\Site_Kit\Modules\Analytics_4\Report;

use Google\Site_Kit\Modules\Analytics_4;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\RunReportResponse as Google_Service_AnalyticsData_RunReportResponse;

/**
* Class for swapping custom dimension IDs with their display names.
*
* @since n.e.x.t
* @access private
* @ignore
*/
class Custom_Dimensions_Response_Parser {

/**
* Cache display name results.
*
* @since n.e.x.t
* @var array
*/
protected $cache_map = array(
Analytics_4::CUSTOM_DIMENSION_POST_AUTHOR => array(),
Analytics_4::CUSTOM_DIMENSION_POST_CATEGORIES => array(),
);

/**
* Gets the display name for a given user ID.
*
* If no user is found for a given user ID, the original user ID is
* returned. If a user is found, the display name is cached when processing
* the same response.
*
* @since n.e.x.t
*
* @param string|int $user_id User ID of the user to get the display name of.
* @return string|int Display name of the user or their original ID if no name is found.
*/
protected function get_post_author_name( $user_id ) {
if ( ! is_numeric( $user_id ) ) {
return $user_id;
}

if ( ! isset( $this->cache_map[ Analytics_4::CUSTOM_DIMENSION_POST_AUTHOR ][ $user_id ] ) ) {
$user = get_userdata( $user_id );
$this->cache_map[ Analytics_4::CUSTOM_DIMENSION_POST_AUTHOR ][ $user_id ] = isset( $user->display_name ) ? $user->display_name : $user_id;
}

return $this->cache_map[ Analytics_4::CUSTOM_DIMENSION_POST_AUTHOR ][ $user_id ];
}

/**
* Converts a string list of category IDs to a stringified array of their
* category names.
*
* If no category is found for a given ID, the original ID is preserved in
* the returned string.
*
* @since n.e.x.t
*
* @param string $category_ids_string Comma separated string list of IDs of categories to get names of.
* @return string JSON encoded string of comma separated category names (or their original IDs if no name is found).
*/
protected function get_post_category_names( $category_ids_string ) {
$category_ids = explode( ',', $category_ids_string );

// Explode converts all split values to strings. So we cast any numeric
// strings to `int` so that if a display name is not found for a
// category_id, then the original category_id int can be passed
// through directly in the response.
$category_ids = array_map(
function ( $id ) {
return is_numeric( $id ) ? (int) $id : $id;
},
$category_ids
);

$category_names = array();
foreach ( $category_ids as $category_id ) {
if ( ! is_numeric( $category_id ) ) {
$category_names[] = $category_id;
continue;
}

if ( ! isset( $this->cache_map[ Analytics_4::CUSTOM_DIMENSION_POST_CATEGORIES ][ $category_id ] ) ) {
$term = get_term( $category_id );
$this->cache_map[ Analytics_4::CUSTOM_DIMENSION_POST_CATEGORIES ][ $category_id ] = isset( $term->name ) ? $term->name : $category_id;
}

$category_names[] = $this->cache_map[ Analytics_4::CUSTOM_DIMENSION_POST_CATEGORIES ][ $category_id ];
}

return wp_json_encode( $category_names );
}

/**
* Swaps the IDs of any custom dimensions within the response with their respective display names.
*
* @since n.e.x.t
*
* @param Google_Service_AnalyticsData_RunReportResponse $response The response to swap values in.
* @return void Swaps the IDs of custom dimensions within the given response instance.
*/
public function swap_custom_dimension_ids_with_names( $response ) {
if ( $response->getRowCount() === 0 ) {
return;
}

$dimension_headers = $response->getDimensionHeaders();

// Create a map of any custom dimension to its equivalent parsing function to avoid
// looping through report rows multiple times below.
$custom_dimension_map = array();
foreach ( $dimension_headers as $dimension_key => $dimension ) {
if ( Analytics_4::CUSTOM_EVENT_PREFIX . Analytics_4::CUSTOM_DIMENSION_POST_AUTHOR === $dimension['name'] ) {
$custom_dimension_map[ $dimension_key ] = array( $this, 'get_post_author_name' );
}

if ( Analytics_4::CUSTOM_EVENT_PREFIX . Analytics_4::CUSTOM_DIMENSION_POST_CATEGORIES === $dimension['name'] ) {
$custom_dimension_map[ $dimension_key ] = array( $this, 'get_post_category_names' );
}
}

if ( empty( $custom_dimension_map ) ) {
return;
}

$rows = $response->getRows();

foreach ( $rows as $row ) {
foreach ( $custom_dimension_map as $dimension_key => $callable ) {
$dimension_value = $row['dimensionValues'][ $dimension_key ]->getValue();
$new_dimension_value = call_user_func( $callable, $dimension_value );
$row['dimensionValues'][ $dimension_key ]->setValue( $new_dimension_value );
}
}

$response->setRows( $rows );
}

}
3 changes: 3 additions & 0 deletions includes/Modules/Analytics_4/Report/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ public function parse_response( Data_Request $data, $response ) {
return $response;
}

$custom_dimension_query = new Custom_Dimensions_Response_Parser();
$custom_dimension_query->swap_custom_dimension_ids_with_names( $response );

// Get report dimensions and return early if there is either more than one dimension or
// the only dimension is not "date".
$dimensions = $this->parse_dimensions( $data );
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php
/**
* Class Google\Site_Kit\Tests\Modules\Analytics_4\Report\Custom_Dimensions_Response_ParserTest
*
* @package Google\Site_Kit\Tests\Modules\Analytics_4\Report
* @copyright 2023 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/

namespace Google\Site_Kit\Tests\Modules\Analytics_4\Report;

use Google\Site_Kit\Tests\TestCase;
use Google\Site_Kit\Modules\Analytics_4\Report\Custom_Dimensions_Response_Parser;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\RunReportResponse as Google_Service_AnalyticsData_RunReportResponse;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\DimensionHeader as Google_Service_AnalyticsData_DimensionHeader;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\DimensionValue as Google_Service_AnalyticsData_DimensionValue;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\Row as Google_Service_AnalyticsData_Row;

/**
* @group Modules
* @group Analytics_4
* @group Report
*/
class Custom_Dimensions_Response_ParserTest extends TestCase {

protected function create_rows_with_dimension_values( $values ) {
$rows = array();
foreach ( $values as $key => $value ) {
$dimension_value = new Google_Service_AnalyticsData_DimensionValue();
$dimension_value->setValue( $value );
$rows[ $key ] = new Google_Service_AnalyticsData_Row();
$rows[ $key ]->setDimensionValues( array( $dimension_value ) );
}
return $rows;
}

public function test_swap_custom_dimension_ids_with_names__post_author() {
$response = new Google_Service_AnalyticsData_RunReportResponse();
$dimension_header_post_author = new Google_Service_AnalyticsData_DimensionHeader();
$dimension_header_post_author->setName( 'customEvent:googlesitekit_post_author' );
$response->setDimensionHeaders( array( $dimension_header_post_author ) );

// Existing author with a valid display name.
$author_id = $this->factory()->user->create( array( 'display_name' => 'test author 1' ) );

// 5000 would be a non-existent user and `(not set)` is an invalid user_id.
$rows = $this->create_rows_with_dimension_values( array( (string) $author_id, '5000', '(not set)' ) );

$response->setRows( $rows );
$custom_dimension_query = new Custom_Dimensions_Response_Parser();
$custom_dimension_query->swap_custom_dimension_ids_with_names( $response );

// `$rows` would have mutated within $response now having the swapped values.
$this->assertEquals( 'test author 1', $rows[0]->getDimensionValues()[0]->getValue() );
$this->assertEquals( '5000', $rows[1]->getDimensionValues()[0]->getValue() );
$this->assertEquals( '(not set)', $rows[2]->getDimensionValues()[0]->getValue() );
}

public function test_swap_custom_dimension_ids_with_names__post_categories() {
$response = new Google_Service_AnalyticsData_RunReportResponse();
$dimension_header_post_categories = new Google_Service_AnalyticsData_DimensionHeader();
$dimension_header_post_categories->setName( 'customEvent:googlesitekit_post_categories' );
$response->setDimensionHeaders( array( $dimension_header_post_categories ) );

$category_with_number = $this->factory()->category->create( array( 'name' => '2' ) );
$category_with_commas = $this->factory()->category->create( array( 'name' => 'Category,with,commas' ) );
$normal_category = $this->factory()->category->create( array( 'name' => 'Normal Category' ) );
$category_ids_string = implode( ',', array( $category_with_number, $category_with_commas, 1955, 'Uncategorized', $normal_category ) ); // 1955 would be a non-existent category

$rows = $this->create_rows_with_dimension_values( array( $category_ids_string ) );
$response->setRows( $rows );
$custom_dimension_query = new Custom_Dimensions_Response_Parser();
$custom_dimension_query->swap_custom_dimension_ids_with_names( $response );

// `$rows` would have mutated within $response now having the swapped values.
$this->assertEquals( '["2","Category,with,commas",1955,"Uncategorized","Normal Category"]', $rows[0]->getDimensionValues()[0]->getValue() );
}
}
Loading