Skip to content

Block Shipping, Products and Coupons when Sync Push is disabled #2810

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

Open
wants to merge 10 commits into
base: feature/api-pull-sync-endpoint
Choose a base branch
from
6 changes: 5 additions & 1 deletion src/API/Google/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@

namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Google;

use Automattic\WooCommerce\GoogleListingsAndAds\API\WP\NotificationsService;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Query\ShippingRateQuery;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Query\ShippingTimeQuery;
use Automattic\WooCommerce\GoogleListingsAndAds\Internal\ContainerAwareTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\Internal\Interfaces\ContainerAwareInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\MerchantCenterService;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\TargetAudience;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\WC;
Expand Down Expand Up @@ -65,7 +67,9 @@
* Sync the shipping settings with Google.
*/
public function sync_shipping() {
if ( ! $this->should_sync_shipping() ) {
/** @var MerchantCenterService $merchant_center */
$merchant_center = $this->container->get( MerchantCenterService::class );
if ( ! $this->should_sync_shipping() || ! $merchant_center->is_enabled_for_datatype( NotificationsService::DATATYPE_SHIPPING ) ) {

Check warning on line 72 in src/API/Google/Settings.php

View check run for this annotation

Codecov / codecov/patch

src/API/Google/Settings.php#L71-L72

Added lines #L71 - L72 were not covered by tests
return;
}

Expand Down
42 changes: 40 additions & 2 deletions src/ConnectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@
$this->response .= 'Failed to connect to Google.';
}

$notification_service = new NotificationsService( $this->container->get( MerchantCenterService::class ), $this->container->get( AccountService::class ) );
$notification_service->set_options_object( $options );

Check warning on line 140 in src/ConnectionTest.php

View check run for this annotation

Codecov / codecov/patch

src/ConnectionTest.php#L139-L140

Added lines #L139 - L140 were not covered by tests

?>
<div class="wrap">
<h2>Connection Test</h2>
Expand Down Expand Up @@ -559,7 +562,44 @@
<hr />

<h2 class="title">Product Sync</h2>
<table class="form-table" role="presentation">
<tr>
<th><label>Products MC PUSH:</label></th>
<td>
<p>

<code><?php echo $this->enabled_or_disabled( $notification_service->is_push_enabled_for_datatype( NotificationsService::DATATYPE_PRODUCT ) ); ?></code>

Check warning on line 571 in src/ConnectionTest.php

View check run for this annotation

Codecov / codecov/patch

src/ConnectionTest.php#L571

Added line #L571 was not covered by tests
</p>
</td>
</tr>
<tr>
<th><label>Coupons MC PUSH:</label></th>
<td>
<p>

<code><?php echo $this->enabled_or_disabled( $notification_service->is_push_enabled_for_datatype( NotificationsService::DATATYPE_COUPON ) ); ?></code>

Check warning on line 580 in src/ConnectionTest.php

View check run for this annotation

Codecov / codecov/patch

src/ConnectionTest.php#L580

Added line #L580 was not covered by tests
</p>
</td>
</tr>
<tr>
<th><label>Shipping MC PUSH:</label></th>
<td>
<p>

<code><?php echo $this->enabled_or_disabled( $notification_service->is_push_enabled_for_datatype( NotificationsService::DATATYPE_SHIPPING ) ); ?></code>

Check warning on line 589 in src/ConnectionTest.php

View check run for this annotation

Codecov / codecov/patch

src/ConnectionTest.php#L589

Added line #L589 was not covered by tests
</p>
</td>
</tr>
<tr>
<th><label>Settings MC PUSH:</label></th>
<td>
<p>

<code><?php echo $this->enabled_or_disabled( $notification_service->is_push_enabled_for_datatype( NotificationsService::DATATYPE_SETTINGS ) ); ?></code>

Check warning on line 598 in src/ConnectionTest.php

View check run for this annotation

Codecov / codecov/patch

src/ConnectionTest.php#L598

Added line #L598 was not covered by tests
</p>
</td>
</tr>
</table>
<form action="<?php echo esc_url( admin_url( 'admin.php' ) ); ?>" method="GET">
<table class="form-table" role="presentation">
<tr>
Expand Down Expand Up @@ -660,8 +700,6 @@
<?php
$options = $this->container->get( OptionsInterface::class );
$wp_api_status = $options->get( OptionsInterface::WPCOM_REST_API_STATUS );
$notification_service = new NotificationsService( $this->container->get( MerchantCenterService::class ), $this->container->get( AccountService::class ) );
$notification_service->set_options_object( $options );
?>
<h2 class="title">Partner API Pull Integration</h2>
<form action="<?php echo esc_url( admin_url( 'admin.php' ) ); ?>" method="GET">
Expand Down
20 changes: 18 additions & 2 deletions src/Coupon/CouponSyncer.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
declare(strict_types = 1);
namespace Automattic\WooCommerce\GoogleListingsAndAds\Coupon;

use Automattic\WooCommerce\GoogleListingsAndAds\API\WP\NotificationsService;
use Automattic\WooCommerce\GoogleListingsAndAds\Google\DeleteCouponEntry;
use Automattic\WooCommerce\GoogleListingsAndAds\Google\GooglePromotionService;
use Automattic\WooCommerce\GoogleListingsAndAds\Google\InvalidCouponEntry;
Expand Down Expand Up @@ -479,13 +480,28 @@
if ( ! $this->merchant_center->should_push() ) {
do_action(
'woocommerce_gla_error',
'Cannot push any coupons because they are being fetched automatically.',
'Cannot push any coupons because your store is not ready for syncing.',

Check warning on line 483 in src/Coupon/CouponSyncer.php

View check run for this annotation

Codecov / codecov/patch

src/Coupon/CouponSyncer.php#L483

Added line #L483 was not covered by tests
__METHOD__
);

throw new CouponSyncerException(
__(
'Pushing Coupons will not run if the automatic data fetching is enabled. Please review your configuration in Google Listing and Ads settings.',
'Pushing coupons will not run if the the store is not ready for syncing.',
'google-listings-and-ads'
)
);

Check warning on line 492 in src/Coupon/CouponSyncer.php

View check run for this annotation

Codecov / codecov/patch

src/Coupon/CouponSyncer.php#L489-L492

Added lines #L489 - L492 were not covered by tests
}

if ( ! $this->merchant_center->is_enabled_for_datatype( NotificationsService::DATATYPE_COUPON ) ) {
do_action(
'woocommerce_gla_error',
'Cannot push any coupons because the syncing feature has been disabled on your store.',
__METHOD__
);

throw new CouponSyncerException(
__(
'Pushing Coupons will not run if the PUSH Sync functionality has been disabled.',
'google-listings-and-ads'
)
);
Expand Down
3 changes: 2 additions & 1 deletion src/Jobs/AbstractCouponSyncerJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;

use Automattic\WooCommerce\GoogleListingsAndAds\ActionScheduler\ActionSchedulerInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\API\WP\NotificationsService;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\MerchantCenterService;
use Automattic\WooCommerce\GoogleListingsAndAds\Coupon\CouponHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\Coupon\CouponSyncer;
Expand Down Expand Up @@ -72,6 +73,6 @@
* @return bool Returns true if the job can be scheduled.
*/
public function can_schedule( $args = [] ): bool {
return ! $this->is_running( $args ) && $this->merchant_center->should_push();
return ! $this->is_running( $args ) && $this->merchant_center->is_enabled_for_datatype( NotificationsService::DATATYPE_COUPON );

Check warning on line 76 in src/Jobs/AbstractCouponSyncerJob.php

View check run for this annotation

Codecov / codecov/patch

src/Jobs/AbstractCouponSyncerJob.php#L76

Added line #L76 was not covered by tests
}
}
3 changes: 2 additions & 1 deletion src/Jobs/AbstractProductSyncerBatchedJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;

use Automattic\WooCommerce\GoogleListingsAndAds\ActionScheduler\ActionSchedulerInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\API\WP\NotificationsService;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\MerchantCenterService;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\MerchantStatuses;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\BatchProductHelper;
Expand Down Expand Up @@ -80,6 +81,6 @@ public function __construct(
* @return bool Returns true if the job can be scheduled.
*/
public function can_schedule( $args = [] ): bool {
return ! $this->is_running( $args ) && $this->merchant_center->should_push();
return ! $this->is_running( $args ) && $this->merchant_center->is_enabled_for_datatype( NotificationsService::DATATYPE_PRODUCT );
}
}
3 changes: 2 additions & 1 deletion src/Jobs/AbstractProductSyncerJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;

use Automattic\WooCommerce\GoogleListingsAndAds\ActionScheduler\ActionSchedulerInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\API\WP\NotificationsService;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\MerchantCenterService;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductRepository;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductSyncer;
Expand Down Expand Up @@ -62,6 +63,6 @@ public function __construct(
* @return bool Returns true if the job can be scheduled.
*/
public function can_schedule( $args = [] ): bool {
return ! $this->is_running( $args ) && $this->merchant_center->should_push();
return ! $this->is_running( $args ) && $this->merchant_center->is_enabled_for_datatype( NotificationsService::DATATYPE_PRODUCT );
Copy link
Contributor

Choose a reason for hiding this comment

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

As I outlined in the previous comment, we should prevent pushing at an earlier stage. I can however understand we want some logic checking within the syncer jobs, but shouldn't that be for when a job item is being processed.

Here we are only blocking a job from being scheduled. But what happens if we perform the actions in this order:

  1. Schedule a product update
  2. Disable product push
  3. Scheduled actions runs (at this step I'd expect it to bail as push is no longer enabled)

woocommerce_gla_batch_retry_update_products will also be happily triggered again without being blocked if the previous sync failed.

}
}
6 changes: 4 additions & 2 deletions src/Jobs/UpdateShippingSettings.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use Automattic\WooCommerce\GoogleListingsAndAds\ActionScheduler\ActionSchedulerInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\Settings as GoogleSettings;
use Automattic\WooCommerce\GoogleListingsAndAds\API\WP\NotificationsService;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\MerchantCenterService;

defined( 'ABSPATH' ) || exit;
Expand All @@ -21,6 +22,7 @@
* @since 2.1.0
*/
class UpdateShippingSettings extends AbstractActionSchedulerJob {

/**
* @var MerchantCenterService
*/
Expand Down Expand Up @@ -97,7 +99,7 @@ public function schedule( array $args = [] ) {
* @return bool
*/
protected function can_sync_shipping(): bool {
// Confirm that the Merchant Center account is connected and the user has chosen for the shipping rates to be synced from WooCommerce settings.
return $this->merchant_center->is_connected() && $this->google_settings->should_get_shipping_rates_from_woocommerce();
// Confirm that the Merchant Center account is connected, the user has chosen for the shipping rates to be synced from WooCommerce settings and the Push Sync is enabled for Shipping.
return $this->merchant_center->is_connected() && $this->google_settings->should_get_shipping_rates_from_woocommerce() && $this->merchant_center->is_enabled_for_datatype( NotificationsService::DATATYPE_SHIPPING );
Copy link
Contributor

Choose a reason for hiding this comment

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

So here we are making the change in the right location. Because can_sync_shipping will be checked when scheduling the job but also when processing the items of the job. So if a job was already scheduled and the push disabled later it will still prevent the job from being run.

At most we might want to tweak the failure message to indicate it could also be because push is disabled:

Cannot sync shipping settings. Confirm that the merchant center account is connected and the option to automatically sync the shipping settings is selected.

}
}
17 changes: 14 additions & 3 deletions src/MerchantCenter/MerchantCenterService.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,17 +126,28 @@ public function is_ready_for_syncing(): bool {
}

/**
* Whether we should push data into MC. Only if:
* - MC is ready for syncing {@see is_ready_for_syncing}
* - Notifications Service is not enabled
* Whether we should push data into MC. Only is MC is ready for syncing.
*
* @see is_ready_for_syncing
* @return bool
* @since 2.8.0
*/
public function should_push(): bool {
return $this->is_ready_for_syncing();
}

/**
* Whether push is enabled for a specific data type.
*
* @param string $data_type The data type to check.
* @return bool
* @since x.x.x
*/
public function is_enabled_for_datatype( string $data_type ): bool {
/** @var NotificationsService $notifications_service */
$notifications_service = $this->container->get( NotificationsService::class );
return $notifications_service->is_push_enabled_for_datatype( $data_type );
}
Comment on lines +146 to +150
Copy link
Contributor

Choose a reason for hiding this comment

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

So two confusing things about this function:

  1. If it's actually just a wrapper to call is_push_enabled_for_datatype then why doesn't the name also reflect that it's checking if push is enabled?
  2. Push as nothing to do with notifications, so why is it calling a notification service?

Reasoning why I think we shouldn't call the notification service is because we should think of push and pull as two completely independent parts, which can both run together (dark launch), or toggled between the two. We could also decide to later remove one. So making push logic dependent on notifications (which is pull only) doesn't separate the two. Should we have a separate service handling the overlapping logic between the two?


/**
* Get whether the country is supported by the Merchant Center.
Expand Down
20 changes: 18 additions & 2 deletions src/Product/ProductSyncer.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

namespace Automattic\WooCommerce\GoogleListingsAndAds\Product;

use Automattic\WooCommerce\GoogleListingsAndAds\API\WP\NotificationsService;
use Automattic\WooCommerce\GoogleListingsAndAds\Google\BatchInvalidProductEntry;
use Automattic\WooCommerce\GoogleListingsAndAds\Google\BatchProductIDRequestEntry;
use Automattic\WooCommerce\GoogleListingsAndAds\Google\BatchProductRequestEntry;
Expand Down Expand Up @@ -365,13 +366,28 @@
if ( ! $this->merchant_center->should_push() ) {
do_action(
'woocommerce_gla_error',
'Cannot push any products because they are being fetched automatically.',
'Cannot push any products because your store is not ready for syncing.',

Check warning on line 369 in src/Product/ProductSyncer.php

View check run for this annotation

Codecov / codecov/patch

src/Product/ProductSyncer.php#L369

Added line #L369 was not covered by tests
__METHOD__
);

throw new ProductSyncerException(
__(
'Pushing products will not run if the automatic data fetching is enabled. Please review your configuration in Google Listing and Ads settings.',
'Pushing products will not run if the the store is not ready for syncing.',
'google-listings-and-ads'
)
);

Check warning on line 378 in src/Product/ProductSyncer.php

View check run for this annotation

Codecov / codecov/patch

src/Product/ProductSyncer.php#L375-L378

Added lines #L375 - L378 were not covered by tests
}

if ( ! $this->merchant_center->is_enabled_for_datatype( NotificationsService::DATATYPE_PRODUCT ) ) {
do_action(
'woocommerce_gla_error',
'Cannot push any products because the syncing feature has been disabled on your store.',
__METHOD__
);

Check warning on line 386 in src/Product/ProductSyncer.php

View check run for this annotation

Codecov / codecov/patch

src/Product/ProductSyncer.php#L382-L386

Added lines #L382 - L386 were not covered by tests

throw new ProductSyncerException(
__(
'Pushing products will not run if the PUSH Sync functionality has been disabled.',

Check warning on line 390 in src/Product/ProductSyncer.php

View check run for this annotation

Codecov / codecov/patch

src/Product/ProductSyncer.php#L388-L390

Added lines #L388 - L390 were not covered by tests
'google-listings-and-ads'
)
);
Expand Down
4 changes: 3 additions & 1 deletion src/Shipping/SyncerHooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,9 @@ protected function handle_update_shipping_settings() {
$this->job_repository->get( ShippingNotificationJob::class )->schedule( [ 'topic' => NotificationsService::TOPIC_SHIPPING_UPDATED ] );
}

$this->job_repository->get( UpdateShippingSettings::class )->schedule();
if ( $this->merchant_center->is_enabled_for_datatype( NotificationsService::DATATYPE_SHIPPING ) ) {
$this->job_repository->get( UpdateShippingSettings::class )->schedule();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

So the changes here seem pretty straightforward with simple logic:

  • If pull is enabled: send a notification
  • If push is enabled: schedule a job to start the sync

But when I look at other SyncerHooks I don't see that same clear separation. For example for products I can see that if push is disabled, it will still perform the following steps for a single product (or a batch of products in case it's a variable product):

  1. Check if the product is sync ready
  2. Mark the product as pending sync
  3. If not sync ready prepare delete requests
  4. Schedule the products to be updated
  5. Schedule the products to be deleted

Now because can_scheduler will check if push is enabled, step 4 and 5 won't actually schedule the job in action scheduler. But in step 2 we already modified the product.

So I think we should apply the same simple logic like we did for the shipping syncerhooks, and not even attempt to do anything if push is not enabled.

Note

Same logic applies for coupons.


$this->already_scheduled = true;
}
Expand Down
12 changes: 12 additions & 0 deletions tests/Unit/Coupon/CouponSyncerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ public function test_update_throws_exception_when_mc_is_blocked() {
->willReturn( true );
$merchant_center->expects( $this->once() )
->method( 'should_push' )
->willReturn( true );
$merchant_center->expects( $this->once() )
->method( 'is_enabled_for_datatype' )
->with( 'coupons' )
->willReturn( false );
$this->coupon_syncer = $this->get_coupon_syncer( [ 'merchant_center' => $merchant_center ] );

Expand All @@ -170,6 +174,10 @@ public function test_delete_throws_exception_when_mc_is_blocked() {
->willReturn( true );
$merchant_center->expects( $this->once() )
->method( 'should_push' )
->willReturn( true );
$merchant_center->expects( $this->once() )
->method( 'is_enabled_for_datatype' )
->with( 'coupons' )
->willReturn( false );
$this->coupon_syncer = $this->get_coupon_syncer( [ 'merchant_center' => $merchant_center ] );

Expand Down Expand Up @@ -234,6 +242,10 @@ public function setUp(): void {
$this->merchant_center->expects( $this->any() )
->method( 'is_promotion_supported_country' )
->willReturn( true );
$this->merchant_center->expects( $this->any() )
->method( 'is_enabled_for_datatype' )
->with( 'coupons' )
->willReturn( true );

$this->target_audience = $this->createMock( TargetAudience::class );
$this->target_audience->expects( $this->any() )
Expand Down
32 changes: 32 additions & 0 deletions tests/Unit/Jobs/UpdateAllProductsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ public function test_single_empty_batch() {
->method( 'has_scheduled_action' )
->willReturn( false );

$this->merchant_center
->method( 'is_enabled_for_datatype' )
->willReturn( true );

/*
* We expect only single call to `has_scheduled_action` when scheduling
* the batch and no calls further since batch is empty.
Expand Down Expand Up @@ -165,6 +169,10 @@ public function test_single_partial_batch() {
[ self::PROCESS_ITEM_HOOK, [ $filtered_product_list->get_product_ids() ] ]
);

$this->merchant_center
->method( 'is_enabled_for_datatype' )
->willReturn( true );

$this->job->schedule();

do_action( self::CREATE_BATCH_HOOK, 1 );
Expand Down Expand Up @@ -197,6 +205,10 @@ public function test_multiple_full_batches() {
->method( 'has_scheduled_action' )
->willReturn( false );

$this->merchant_center
->method( 'is_enabled_for_datatype' )
->willReturn( true );

$this->job->schedule();

do_action( self::CREATE_BATCH_HOOK, 1 );
Expand Down Expand Up @@ -231,6 +243,10 @@ public function test_multiple_incomplete_batches() {
->method( 'has_scheduled_action' )
->willReturn( false );

$this->merchant_center
->method( 'is_enabled_for_datatype' )
->willReturn( true );

$this->job->schedule();

do_action( self::CREATE_BATCH_HOOK, 1 );
Expand Down Expand Up @@ -265,6 +281,10 @@ public function test_multiple_semi_empty_batches() {
->withConsecutive( [ [], 2, 0 ], [ [], 2, 2 ], [ [], 2, 4 ] )
->willReturnOnConsecutiveCalls( $batch_a, $batch_b, $batch_c );

$this->merchant_center
->method( 'is_enabled_for_datatype' )
->willReturn( true );

$this->job->schedule();

do_action( self::CREATE_BATCH_HOOK, 1 );
Expand Down Expand Up @@ -335,6 +355,18 @@ public function test_delayed_schedule() {
->method( 'schedule_single' )
->with( gmdate( 'U' ) + 100, self::CREATE_BATCH_HOOK, [ 1 ] );

$this->merchant_center
->method( 'is_enabled_for_datatype' )
->willReturn( true );

$this->job->schedule_delayed( 100 );
}

public function test_cannot_schedule_when_mc_push_is_blocked() {
$this->merchant_center
->method( 'is_enabled_for_datatype' )
->willReturn( false );

$this->assertFalse( $this->job->can_schedule() );
}
}
Loading