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

API Pull - Notify when product changes #2190

Merged
merged 40 commits into from
Dec 29, 2023

Conversation

puntope
Copy link
Contributor

@puntope puntope commented Dec 27, 2023

Changes proposed in this Pull Request:

Closes part of #2146

This PR adds logic for calling the Notification Partner endpoint when a Product changes.

In detail this PR:

  • Add a Service NotificationsService. This service includes the topics, the API call to the partner notifications service in WPCOM, and a public method to notify() to perform the call.
  • Tweak the hooks in SyncerHooks to call the notify() method
  • Logs when an error happens in the API Call
  • Tests for NotificationsService and tweak tests for SyncerHooks

Detailed test instructions:

  1. Checkout this PR
  2. Be sure you are proxied in WPCOM and the branch you are is trunk
  3. Authorize your APP and change the partner ID in the WPCOM sites-woocommerce-partner-notifications.php in the PARTNERS variable (this is because we don't have yet the final Partner APP)
  4. Create a product
  5. Go to WooCommerce - Status - Logs -- and see the error log
  6. Update the product
  7. Go to WooCommerce - Status - Logs -- and see the error log
  8. Delete the product
  9. Go to WooCommerce - Status - Logs -- and see the error log

Additional details:

  • The Topics are not matching the topics in WPCOM Notification PArtners, this is due the new documentation for the endpoint and it's pending to update them in the WPCOM side. So in this PR we only need to test the call and also is a good chance to test the errors.
  • I removed woocommerce_delete_product_variation and woocommerce_trash_product_variation as they are duplicated with trashed_post and deleted_post
  • After rebase PHPCS triggered some issues in src/Infrastructure/GoogleListingsAndAdsPlugin.php but they are not introduced here. So they are out Scope this PR

Changelog entry

Add - Notify when product changes

puntope and others added 30 commits November 30, 2023 20:12
Release 2.5.13 - Merge `trunk` to `develop`
…ing-setup-ads

Add (budget and audiences) tracking for Onboarding completed with Ads
…he page to load to proceed with assertions.
…oogle-ads-connection

Change to require Google Ads connection during the onboarding
Include PHP 8.3 in tested versions for PHPunit
…rrect-in-purchase-event

Fix item price in purchase event
@github-actions github-actions bot added changelog: add A new feature, function, or functionality was added. type: enhancement The issue is a request for an enhancement. labels Dec 27, 2023
Copy link

codecov bot commented Dec 27, 2023

Codecov Report

Attention: 72 lines in your changes are missing coverage. Please review.

Comparison is base (b84999b) 57.7% compared to head (ac29447) 58.6%.
Report is 72 commits behind head on feature/google-api-project.

Additional details and impacted files

Impacted file tree graph

@@                      Coverage Diff                       @@
##             feature/google-api-project   #2190     +/-   ##
==============================================================
+ Coverage                          57.7%   58.6%   +1.0%     
- Complexity                         4181    4211     +30     
==============================================================
  Files                               454     456      +2     
  Lines                             17950   16827   -1123     
==============================================================
- Hits                              10349    9864    -485     
+ Misses                             7601    6963    -638     
Flag Coverage Δ
php-unit-tests 58.6% <40.0%> (+1.0%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files Coverage Δ
...Controllers/Ads/BudgetRecommendationController.php 100.0% <100.0%> (ø)
src/DB/TableInterface.php 0.0% <ø> (ø)
src/Menu/MenuFixesTrait.php 0.0% <ø> (ø)
src/Product/SyncerHooks.php 88.9% <100.0%> (+0.4%) ⬆️
src/Shipping/ZoneMethodsParser.php 98.4% <100.0%> (+0.1%) ⬆️
google-listings-and-ads.php 7.7% <0.0%> (+1.4%) ⬆️
src/Google/GlobalSiteTag.php 0.0% <0.0%> (ø)
...ernal/DependencyManagement/CoreServiceProvider.php 0.0% <0.0%> (ø)
...nternal/DependencyManagement/DBServiceProvider.php 0.0% <0.0%> (ø)
...ternal/DependencyManagement/JobServiceProvider.php 0.0% <0.0%> (ø)
... and 5 more

... and 97 files with indirect coverage changes

@puntope puntope self-assigned this Dec 27, 2023
@puntope puntope marked this pull request as ready for review December 27, 2023 23:38
@puntope puntope changed the title Notify when product changes API Pull - Notify when product changes Dec 28, 2023
Copy link
Contributor

@jorgemd24 jorgemd24 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @puntope, for tackling this. It's running smoothly. I've dropped a few comments on the code and below are some doubts that I have:

I've noticed that when updating a variation, you receive the topic "product.updated" along with the variation's ID. I don't think it's an issue as you can call the WC REST API wc/v3/products/variation_id without explicitly stating it's a variation ID.

However, when creating a product, even as just a draft without any data, it triggers the notification endpoint. Is this expected behavior? It appears that with the pushing mechanism, we were bypassing these scenarios:

2023-12-28T13:23:04+00:00 DEBUG Automattic\WooCommerce\GoogleListingsAndAds\Product\WCProductAdapter::map_product_categories Product category (ID: 933): ["Uncategorized"]. 2023-12-28T13:23:04+00:00 DEBUG Automattic\WooCommerce\GoogleListingsAndAds\Product\BatchProductHelper::validate_and_generate_update_request_entries Skipping product (ID: 953) because it does not pass validation: {"woocommerce_id":953,"errors":["[description] This value should not be blank.","[imageLink] This value should not be blank.","[gtin] This value is not valid."]} 2023-12-28T13:23:06+00:00 ERROR Automattic\WooCommerce\GoogleListingsAndAds\Google\NotificationsService::notification_error Error sending notification for Item ID 1020 with topic product.created. {"code":"wpcom_partner_notifications_proxy_error","message":"Invalid x-woocommerce-topic","data":{"status":400}}

Another question that comes to my mind is, will we be notifying every product, regardless of its channel visibility? Or do you plan to implement some filtering in a following PR?

Comment on lines 24 to 28
public const TOPIC_COUPON_CREATED = 'coupon.created';
public const TOPIC_COUPON_DELETED = 'coupon.deleted';
public const TOPIC_COUPON_UPDATED = 'coupon.updated';
public const TOPIC_SHIPPING_SAVED = 'action.woocommerce_after_shipping_zone_object_save';
public const TOPIC_SHIPPING_DELETED = 'action.woocommerce_delete_shipping_zone';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constants are misaligned.

Comment on lines 68 to 76
if ( is_wp_error( $response ) ) {
$this->notification_error( $item_id, $topic, $response->get_error_message() );
return false;
}

if ( wp_remote_retrieve_response_code( $response ) >= 400 ) {
$this->notification_error( $item_id, $topic, wp_remote_retrieve_body( $response ) );
return false;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about:

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;
}

public const TOPIC_COUPON_CREATED = 'coupon.created';
public const TOPIC_COUPON_DELETED = 'coupon.deleted';
public const TOPIC_COUPON_UPDATED = 'coupon.updated';
public const TOPIC_SHIPPING_SAVED = 'action.woocommerce_after_shipping_zone_object_save';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, why the topics for shipping zones are so different from the rest?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because of the Google Endpoint specification. They are using https://woocommerce.github.io/woocommerce-rest-api-docs/?shell#webhooks as reference.

action.woocommerce_after_shipping_zone_object_save means they are referring to woocommerce_after_shipping_zone_object_save action in WC.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe there is not a resource named shipping (yes they are product and coupon) or if it exists maybe they didn't know about it. In any case, the expect that one. action.woocommerce_after_shipping_zone_object_save

}

/**
* Register a service.
*/
public function register(): void {
// only register the hooks if Merchant Center is connected and ready for syncing data.
// TODO: Potentially change this after API Pull is implemented as we don't need MC to be connected for the API Pull
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we will still need the MC to be connected, but instead of pushing the data, they will pull the data.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm, I thought that authorizing the App and WPCOM Google can actually get data by themselves right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, they will have access to the data however, from what I gather, they still require the MC ID, and the account needs to be connected.

*/
public function get_mock() {
return $this->getMockBuilder( NotificationsService::class)
->setMethods( [ 'do_request' ] )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setMethods seems deprecated.

image

@puntope
Copy link
Contributor Author

puntope commented Dec 28, 2023

However, when creating a product, even as just a draft without any data, it triggers the notification endpoint. Is this expected behavior? It appears that with the pushing mechanism, we were bypassing these scenarios:

I also thought we should avoid notifying drafts. However, when running the tests I discovered the current batch syncer (MC push based) yes does actually load them I guess, I thought was expected and Google will handle that on their side (one advantage of doing Pull approach) . WHat do you think about this?

@puntope
Copy link
Contributor Author

puntope commented Dec 28, 2023

Another question that comes to my mind is, will we be notifying every product, regardless of its channel visibility? Or do you plan to implement some filtering in a following PR?

I assume we will have filters on Google side avoiding access to data without channel visibility set as On. But yes we might implement that maybe.

One question about it is: what happens if a merchant wants to remove the product from the Google App without deleting it? In case we only sync products with Channel Visibility as On, it would be impossible for the merchant to remove it from MC as setting channel visibility as Off will stop the notifications and hence Google won't be able to get the data anymore.

I am thinking about some options here:

  1. We can maybe implement some kind of action when the product changes and call product.delete in case channel visibility is set as Off and product.create in case is set as On
  2. We can rely on Google and on the WPCOM endpoints you created to be sure they don't load Channel Visibility "Off" products.
  3. Any other option you suggest.
  1. We can maybe implement some kind of action when the product changes and call product.delete in case channel visibility is set as Off and product.create in case is set as On

This is how is done in current implementation.

@puntope
Copy link
Contributor Author

puntope commented Dec 29, 2023

Hi @jorgemd24 I applied most of the suggestions and answered your questions.

In regards to the discussion about filtering the "not ready" products, I opened some discussions

pcTzPl-1OX-p2#comment-2903
pcTzPl-1OX-p2#comment-2902

In my point of view, relying on the partner to handle all of that may be a valid solution, as well as doing it ourselves. So I prefer to wait for some input before spending time on the implementation. So in any case, it would be done in a future PR if that happens.

If you agree, this is ready for another round.

Copy link
Contributor

@jorgemd24 jorgemd24 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @puntope for your comments! I'm aligned with the idea that ideally, the partner should manage those scenarios. When you look at the webhooks, they essentially send the data, and it's up to the consumer to decide how to handle it. However, I'm unsure, for instance, why we'd need to notify the partner if they prefer not to share certain products with Google. Nevertheless, with the filter we've put in place, when they request a product that isn't syncable, the response will be empty.

One question about it is: what happens if a merchant wants to remove the product from the Google App without deleting it? In case we only sync products with Channel Visibility as On, it would be impossible for the merchant to remove it from MC as setting channel visibility as Off will stop the notifications and hence Google won't be able to get the data anymore.

I am thinking about some options here:

1.We can maybe implement some kind of action when the product changes and call product.delete in case channel visibility is set as Off and product.create in case is set as On
2.We can rely on Google and on the WPCOM endpoints you created to be sure they don't load Channel Visibility "Off" products.

For option 1, let's see the opinions shared in the internal discussion you initiated 👍

Regarding option 2, in theory, when they use the WPCOM endpoints, if the channel visibility is switched off, they shouldn't receive the data. Hence, it should work as well. However, that merchant will receive unnecessary requests.

@puntope puntope merged commit 96fddfb into feature/google-api-project Dec 29, 2023
9 of 11 checks passed
@puntope puntope deleted the add/api-pull-update-hooks branch December 29, 2023 18:42
@eason9487 eason9487 mentioned this pull request Mar 26, 2024
21 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
changelog: add A new feature, function, or functionality was added. type: enhancement The issue is a request for an enhancement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants