-
Notifications
You must be signed in to change notification settings - Fork 21
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
Get product stats using the Merchant Center report query #2229
Changes from 140 commits
25db819
81918f5
05ab472
6975f77
1f7ca4c
021017b
b4130fe
ebe9798
dc1fcb6
e77bdbf
61a4c5b
83e016a
6076fc1
2f81f35
686b966
26f8c7b
2a5d4b7
e78d7d0
5ec640e
b84999b
faa9c56
ef3a011
f673381
897e2d1
8587db5
80c27f0
ca4e35a
caa952a
704efb7
1781950
dd7f8de
7072aea
7da36c0
721c0b2
abf46d0
c5f5436
4e80346
0453a6d
138b256
cc8fe7b
2d976e3
ee00514
ebc1830
5722721
789400d
1be66fb
a4f9e73
fd6fc0d
2d50495
95bad26
adf7b88
0577969
39eb4e7
ac29447
96fddfb
ce8b86f
d8cebdb
832d0dc
94c4fb6
0386c74
51b729f
619123f
d85d7ed
a60ac81
c47b7fa
b716805
d4c2d37
545cd22
8ee54d1
fd11304
3d9b4bc
ba7ed8f
b3b0735
8940067
a1eaad6
a1d5f22
d3a641f
b9af9f1
82795a7
99f5b9e
08efcce
d93c7b7
cbb00a2
677e4ec
6315209
de3e24c
65dff81
ac3f6cc
75e4c96
3056459
2bb00ca
3b1ef31
bd3e0b6
252e9ed
75a61ef
c4fde99
7fd1766
b4fa9c5
b2839a1
bbf3d3c
a2fa2f5
e79488c
3fdccab
7b5b178
50e108c
44c7c4d
fb619db
30aa787
2185426
9f78ac3
d4df943
41decb2
82aadc1
9592a80
6c1b818
2657541
16b27b3
fdaaf30
2cfc1f2
d8da89b
8c6807c
7a13ccc
39608de
71ba5c5
3138785
6426ea6
7d1ef5a
ea936e6
a68d980
09ec214
0e92124
9451680
497586b
c0138fa
122d684
930469e
0e90b7d
8b85a6a
1cf0178
264cd27
679b143
2123e0b
4c304f3
56d20b5
319a7a1
af810b1
7b0f3b2
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 | ||||
---|---|---|---|---|---|---|
|
@@ -6,6 +6,7 @@ | |||||
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval; | ||||||
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\Query\MerchantFreeListingReportQuery; | ||||||
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\Query\MerchantProductReportQuery; | ||||||
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\Query\MerchantProductViewReportQuery; | ||||||
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\InvalidValue; | ||||||
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsAwareInterface; | ||||||
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsAwareTrait; | ||||||
|
@@ -14,6 +15,8 @@ | |||||
use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\Google\Service\ShoppingContent; | ||||||
use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\Google\Service\ShoppingContent\ReportRow; | ||||||
use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\Google\Service\ShoppingContent\Segments; | ||||||
use Automattic\WooCommerce\GoogleListingsAndAds\Value\MCStatus; | ||||||
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\ShoppingContentDateTrait; | ||||||
use DateTime; | ||||||
use Exception; | ||||||
|
||||||
|
@@ -26,6 +29,7 @@ class MerchantReport implements OptionsAwareInterface { | |||||
|
||||||
use OptionsAwareTrait; | ||||||
use ReportTrait; | ||||||
use ShoppingContentDateTrait; | ||||||
|
||||||
/** | ||||||
* The shopping service. | ||||||
|
@@ -52,6 +56,89 @@ public function __construct( ShoppingContent $service, ProductHelper $product_he | |||||
$this->product_helper = $product_helper; | ||||||
} | ||||||
|
||||||
/** | ||||||
* Get ProductView Query response. | ||||||
* | ||||||
* @param string|null $next_page_token The next page token. | ||||||
* @return array Array of products along with their statuses. | ||||||
* | ||||||
* @throws Exception If the product view report data can't be retrieved. | ||||||
*/ | ||||||
public function get_product_view_report( $next_page_token = null ): array { | ||||||
$batch_size = apply_filters( 'woocommerce_gla_product_view_report_page_size', 1000 ); | ||||||
|
||||||
try { | ||||||
$product_view_data = [ | ||||||
'statuses' => [], | ||||||
'next_page' => null, | ||||||
]; | ||||||
|
||||||
$query = new MerchantProductViewReportQuery( | ||||||
[ | ||||||
'next_page' => $next_page_token, | ||||||
'per_page' => $batch_size, | ||||||
] | ||||||
); | ||||||
|
||||||
$response = $query | ||||||
->set_client( $this->service, $this->options->get_merchant_id() ) | ||||||
->get_results(); | ||||||
|
||||||
$rows = $response->getResults(); | ||||||
|
||||||
foreach ( $rows as $row ) { | ||||||
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. Not a big deal, but we might as well shorten this to one line since $rows isn't referenced anywhere else. |
||||||
|
||||||
/** @var ProductView $product_view */ | ||||||
$product_view = $row->getProductView(); | ||||||
|
||||||
$wc_product_id = $this->product_helper->get_wc_product_id( $product_view->getId() ); | ||||||
$mc_product_status = $this->convert_aggregated_status_to_mc_status( $product_view->getAggregatedDestinationStatus() ); | ||||||
|
||||||
// Skip if the product id does not exist | ||||||
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. Not a big deal, but technically we've only tried extracting the product ID from the string, we haven't actually checked if it exists. This makes me wonder though, we are extracting from |
||||||
if ( ! $wc_product_id ) { | ||||||
continue; | ||||||
} | ||||||
|
||||||
$product_view_data['statuses'][] = [ | ||||||
'product_id' => $wc_product_id, | ||||||
'status' => $mc_product_status, | ||||||
'expiration_date' => $this->convert_shopping_content_date( $product_view->getExpirationDate() ), | ||||||
]; | ||||||
|
||||||
} | ||||||
|
||||||
$product_view_data['next_page'] = $response->getNextPageToken(); | ||||||
|
||||||
return $product_view_data; | ||||||
} catch ( GoogleException $e ) { | ||||||
do_action( 'woocommerce_gla_mc_client_exception', $e, __METHOD__ ); | ||||||
throw new Exception( __( 'Unable to retrieve Product View Report.', 'google-listings-and-ads' ), $e->getCode() ); | ||||||
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. Just wondering, since this is going to be recorded as a failure in a Scheduled Action scheduler job, do you think it will be useful to include both
Suggested change
Or format the error with sprintf if we want to allow for greater translation/RTL support. |
||||||
} | ||||||
} | ||||||
|
||||||
/** | ||||||
* Convert the product view aggregated status to the MC status. | ||||||
* | ||||||
* @param string $status The status of the product. | ||||||
* | ||||||
* @return array The MC status. | ||||||
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 we just make a small clarification that this is "The aggregated status of the product". |
||||||
*/ | ||||||
protected function convert_aggregated_status_to_mc_status( string $status ): string { | ||||||
switch ( $status ) { | ||||||
case 'ELIGIBLE': | ||||||
return MCStatus::APPROVED; | ||||||
case 'ELIGIBLE_LIMITED': | ||||||
return MCStatus::PARTIALLY_APPROVED; | ||||||
case 'NOT_ELIGIBLE_OR_DISAPPROVED': | ||||||
return MCStatus::DISAPPROVED; | ||||||
case 'PENDING': | ||||||
return MCStatus::PENDING; | ||||||
default: | ||||||
return MCStatus::NOT_SYNCED; | ||||||
} | ||||||
} | ||||||
|
||||||
|
||||||
/** | ||||||
* Get report data for free listings. | ||||||
* | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
<?php | ||
declare( strict_types=1 ); | ||
|
||
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Google\Query; | ||
|
||
defined( 'ABSPATH' ) || exit; | ||
|
||
/** | ||
* Class MerchantProductViewReportQuery | ||
* | ||
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Google\Query | ||
*/ | ||
class MerchantProductViewReportQuery extends MerchantQuery { | ||
|
||
use ReportQueryTrait; | ||
|
||
/** | ||
* Query constructor. | ||
* | ||
* @param array $args Query arguments. | ||
*/ | ||
public function __construct( array $args ) { | ||
parent::__construct( 'ProductView' ); | ||
$this->set_initial_columns(); | ||
$this->handle_query_args( $args ); | ||
} | ||
|
||
|
||
/** | ||
* Filter the query by a list of ID's. | ||
* | ||
* @param array $ids list of ID's to filter by. | ||
* | ||
* @return $this | ||
*/ | ||
public function filter( array $ids ): QueryInterface { | ||
// No filtering used for product view report. | ||
return $this; | ||
} | ||
|
||
/** | ||
* Set the initial columns for this query. | ||
*/ | ||
protected function set_initial_columns() { | ||
$this->columns( | ||
[ | ||
'id' => 'product_view.id', | ||
'offer_id' => 'product_view.offer_id', | ||
'expiration_date' => 'product_view.expiration_date', | ||
'status' => 'product_view.aggregated_destination_status', | ||
] | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
<?php | ||
declare( strict_types=1 ); | ||
|
||
namespace Automattic\WooCommerce\GoogleListingsAndAds\Google; | ||
|
||
use Automattic\Jetpack\Connection\Client; | ||
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Service; | ||
use Jetpack_Options; | ||
|
||
defined( 'ABSPATH' ) || exit; | ||
|
||
/** | ||
* Class NotificationsService | ||
* This class implements a service to Notify a partner about Shop Data Updates | ||
* | ||
* @since x.x.x | ||
* @package Automattic\WooCommerce\GoogleListingsAndAds\Google | ||
*/ | ||
class NotificationsService implements Service { | ||
|
||
// List of Topics to be used. | ||
public const TOPIC_PRODUCT_CREATED = 'product.create'; | ||
public const TOPIC_PRODUCT_DELETED = 'product.delete'; | ||
public const TOPIC_PRODUCT_UPDATED = 'product.update'; | ||
public const TOPIC_COUPON_CREATED = 'coupon.create'; | ||
public const TOPIC_COUPON_DELETED = 'coupon.delete'; | ||
public const TOPIC_COUPON_UPDATED = 'coupon.update'; | ||
public const TOPIC_SHIPPING_SAVED = 'action.woocommerce_after_shipping_zone_object_save'; | ||
public const TOPIC_SHIPPING_DELETED = 'action.woocommerce_delete_shipping_zone'; | ||
|
||
/** | ||
* The url to send the notification | ||
* | ||
* @var string $notification_url | ||
*/ | ||
private $notification_url; | ||
|
||
|
||
/** | ||
* Class constructor | ||
*/ | ||
public function __construct() { | ||
$blog_id = Jetpack_Options::get_option( 'id' ); | ||
$this->notification_url = "https://public-api.wordpress.com/wpcom/v2/sites/{$blog_id}/partners/google/notifications"; | ||
} | ||
|
||
/** | ||
* Calls the Notification endpoint in WPCOM. | ||
* https://public-api.wordpress.com/wpcom/v2/sites/{site}/partners/google/notifications | ||
* | ||
* @param int $item_id | ||
* @param string $topic | ||
* @return bool True is the notification is successful. False otherwise. | ||
*/ | ||
public function notify( int $item_id, string $topic ): bool { | ||
/** | ||
* Allow users to disable the notification request. | ||
* | ||
* @since x.x.x | ||
* | ||
* @param bool $value The current filter value. True by default. | ||
* @param int $item_id The item_id for the notification. | ||
* @param string $topic The topic for the notification. | ||
*/ | ||
if ( ! apply_filters( 'woocommerce_gla_notify', true, $item_id, $topic ) ) { | ||
return false; | ||
} | ||
|
||
do_action( | ||
'woocommerce_gla_debug_message', | ||
sprintf( 'Notification - Item ID: %d - Topic: %s', $item_id, $topic ), | ||
__METHOD__ | ||
); | ||
|
||
$remote_args = [ | ||
'method' => 'POST', | ||
'timeout' => 30, | ||
'headers' => [ | ||
'x-woocommerce-topic' => $topic, | ||
], | ||
'body' => [ | ||
'item_id' => $item_id, | ||
], | ||
'url' => $this->get_notification_url(), | ||
]; | ||
|
||
$response = $this->do_request( $remote_args ); | ||
|
||
if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) >= 400 ) { | ||
$error = is_wp_error( $response ) ? $response->get_error_message() : wp_remote_retrieve_body( $response ); | ||
$this->notification_error( $item_id, $topic, $error ); | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Logs an error. | ||
* | ||
* @param int $item_id | ||
* @param string $topic | ||
* @param string $error | ||
*/ | ||
private function notification_error( int $item_id, string $topic, string $error ): void { | ||
do_action( | ||
'woocommerce_gla_error', | ||
sprintf( 'Error sending notification for Item ID %d with topic %s. %s', $item_id, $topic, $error ), | ||
__METHOD__ | ||
); | ||
} | ||
|
||
/** | ||
* Performs a Remote Request | ||
* | ||
* @param array $args | ||
* @return array|\WP_Error | ||
*/ | ||
protected function do_request( array $args ) { | ||
return Client::remote_request( $args, wp_json_encode( $args['body'] ) ); | ||
} | ||
|
||
/** | ||
* Get the route | ||
* | ||
* @return string The route. | ||
*/ | ||
public function get_notification_url(): string { | ||
return $this->notification_url; | ||
} | ||
|
||
/** | ||
* If the Notifications are enabled | ||
* | ||
* @return bool | ||
*/ | ||
public function is_enabled(): bool { | ||
return apply_filters( 'woocommerce_gla_notifications_enabled', true ); | ||
} | ||
} |
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.
Not sure what the best description is here, but it's also including the next_page token.