Skip to content

Commit b6e27ea

Browse files
authored
Merge pull request #1699 from woocommerce/add/1610-syncable-products-api
Free Listings + Paid Ads: Add two APIs to retrieve the syncable products count and schedule the job to calculate the count
2 parents 84397ce + 13e1a57 commit b6e27ea

12 files changed

+638
-132
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<?php
2+
declare( strict_types=1 );
3+
4+
namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter;
5+
6+
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseOptionsController;
7+
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
8+
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\JobRepository;
9+
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\UpdateSyncableProductsCount;
10+
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsInterface;
11+
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
12+
use WP_REST_Request as Request;
13+
use WP_REST_Response as Response;
14+
15+
defined( 'ABSPATH' ) || exit;
16+
17+
/**
18+
* Class SyncableProductsCountController
19+
*
20+
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter
21+
*/
22+
class SyncableProductsCountController extends BaseOptionsController {
23+
24+
/**
25+
* @var JobRepository
26+
*/
27+
protected $job_repository;
28+
29+
/**
30+
* SyncableProductsCountController constructor.
31+
*
32+
* @param RESTServer $server
33+
* @param JobRepository $job_repository
34+
*/
35+
public function __construct( RESTServer $server, JobRepository $job_repository ) {
36+
parent::__construct( $server );
37+
$this->job_repository = $job_repository;
38+
}
39+
40+
41+
/**
42+
* Registers the routes for the objects of the controller.
43+
*/
44+
public function register_routes() {
45+
$this->register_route(
46+
'mc/syncable-products-count',
47+
[
48+
[
49+
'methods' => TransportMethods::READABLE,
50+
'callback' => $this->get_syncable_products_count_callback(),
51+
'permission_callback' => $this->get_permission_callback(),
52+
],
53+
[
54+
'methods' => TransportMethods::CREATABLE,
55+
'callback' => $this->update_syncable_products_count_callback(),
56+
'permission_callback' => $this->get_permission_callback(),
57+
],
58+
]
59+
);
60+
}
61+
62+
/**
63+
* Get the callback function for marking setup complete.
64+
*
65+
* @return callable
66+
*/
67+
protected function get_syncable_products_count_callback(): callable {
68+
return function( Request $request ) {
69+
$response = [
70+
'count' => null,
71+
];
72+
73+
$count = $this->options->get( OptionsInterface::SYNCABLE_PRODUCTS_COUNT );
74+
75+
if ( isset( $count ) ) {
76+
$response['count'] = (int) $count;
77+
}
78+
79+
return $this->prepare_item_for_response( $response, $request );
80+
};
81+
}
82+
83+
/**
84+
* Get the callback for syncing shipping.
85+
*
86+
* @return callable
87+
*/
88+
protected function update_syncable_products_count_callback(): callable {
89+
return function( Request $request ) {
90+
$this->options->delete( OptionsInterface::SYNCABLE_PRODUCTS_COUNT );
91+
92+
$job = $this->job_repository->get( UpdateSyncableProductsCount::class );
93+
$job->schedule();
94+
95+
return new Response(
96+
[
97+
'status' => 'success',
98+
'message' => __( 'Successfully scheduled a job to update the number of syncable products.', 'google-listings-and-ads' ),
99+
],
100+
201
101+
);
102+
};
103+
}
104+
105+
/**
106+
* Get the item schema properties for the controller.
107+
*
108+
* @return array
109+
*/
110+
protected function get_schema_properties(): array {
111+
return [
112+
'count' => [
113+
'type' => 'number',
114+
'description' => __( 'The number of products that are ready to be synced to Google.', 'google-listings-and-ads' ),
115+
'context' => [ 'view' ],
116+
],
117+
];
118+
}
119+
120+
/**
121+
* Get the item schema name for the controller.
122+
*
123+
* Used for building the API response schema.
124+
*
125+
* @return string
126+
*/
127+
protected function get_schema_title(): string {
128+
return 'syncable_products_count';
129+
}
130+
}

src/Internal/DependencyManagement/JobServiceProvider.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Update\CleanupProductTargetCountriesJob;
3333
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Update\PluginUpdate;
3434
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\UpdateShippingSettings;
35+
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\UpdateSyncableProductsCount;
3536
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\MerchantCenterService;
3637
use Automattic\WooCommerce\GoogleListingsAndAds\Coupon\CouponHelper;
3738
use Automattic\WooCommerce\GoogleListingsAndAds\Coupon\CouponSyncer;
@@ -140,6 +141,9 @@ public function register(): void {
140141

141142
// Share plugin update jobs
142143
$this->share_product_syncer_job( CleanupProductTargetCountriesJob::class );
144+
145+
// Share update syncable products count job
146+
$this->share_action_scheduler_job( UpdateSyncableProductsCount::class, ProductRepository::class );
143147
}
144148

145149
/**

src/Internal/DependencyManagement/RESTServiceProvider.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,15 @@
3636
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter\ShippingTimeBatchController;
3737
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter\ShippingTimeController;
3838
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter\SupportedCountriesController;
39+
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter\SyncableProductsCountController;
3940
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\MerchantCenter\TargetAudienceController;
4041
use Automattic\WooCommerce\GoogleListingsAndAds\DB\ProductFeedQueryHelper;
4142
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Query\BudgetRecommendationQuery;
4243
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Query\MerchantIssueQuery;
4344
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Query\ShippingRateQuery;
4445
use Automattic\WooCommerce\GoogleListingsAndAds\Google\RequestReviewStatuses;
4546
use Automattic\WooCommerce\GoogleListingsAndAds\Google\GoogleHelper;
47+
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\JobRepository;
4648
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\ProductSyncStats;
4749
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\AccountService as MerchantAccountService;
4850
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\MerchantStatuses;
@@ -113,6 +115,7 @@ public function register() {
113115
$this->share( SettingsSyncController::class, Settings::class );
114116
$this->share( DisconnectController::class );
115117
$this->share( SetupCompleteController::class );
118+
$this->share( SyncableProductsCountController::class, JobRepository::class );
116119
}
117120

118121
/**

src/Jobs/AbstractBatchedActionSchedulerJob.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,10 @@ public function handle_create_batch_action( int $batch_number ) {
8181
// if items, schedule the process action
8282
$this->schedule_process_action( $items );
8383

84-
if ( count( $items ) >= $this->get_batch_size() ) {
85-
// if there might be more items, add another "create_batch" action to handle them
86-
$this->schedule_create_batch_action( $batch_number + 1 );
87-
}
84+
// Add another "create_batch" action to handle unfiltered items.
85+
// The last batch created here will be an empty batch, it
86+
// will call "handle_complete" to finish the job.
87+
$this->schedule_create_batch_action( $batch_number + 1 );
8888
}
8989

9090
$this->monitor->detach_timeout_monitor( $create_batch_hook, $create_batch_args );
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
declare( strict_types=1 );
3+
4+
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
5+
6+
use Automattic\WooCommerce\GoogleListingsAndAds\Product\FilteredProductList;
7+
use Automattic\WooCommerce\GoogleListingsAndAds\Product\JobException;
8+
use Exception;
9+
use WC_Product;
10+
11+
/*
12+
* Contains AbstractBatchedActionSchedulerJob methods.
13+
*
14+
* @since x.x.x
15+
*/
16+
trait SyncableProductsBatchedActionSchedulerJobTrait {
17+
18+
/**
19+
* Get a single batch of items.
20+
*
21+
* If no items are returned the job will stop.
22+
*
23+
* @param int $batch_number The batch number increments for each new batch in the job cycle.
24+
*
25+
* @return WC_Product[]
26+
*/
27+
public function get_batch( int $batch_number ): array {
28+
return $this->get_filtered_batch( $batch_number )->get();
29+
}
30+
31+
/**
32+
* Get a single filtered batch of items.
33+
*
34+
* If no items are returned the job will stop.
35+
*
36+
* @since 1.4.0
37+
*
38+
* @param int $batch_number The batch number increments for each new batch in the job cycle.
39+
*
40+
* @return FilteredProductList
41+
*/
42+
protected function get_filtered_batch( int $batch_number ): FilteredProductList {
43+
return $this->product_repository->find_sync_ready_products( [], $this->get_batch_size(), $this->get_query_offset( $batch_number ) );
44+
}
45+
46+
/**
47+
* Handles batch creation action hook.
48+
*
49+
* @hooked gla/jobs/{$job_name}/create_batch
50+
*
51+
* Schedules an action to run immediately for the items in the batch.
52+
* Uses the unfiltered count to check if there are additional batches.
53+
*
54+
* @since 1.4.0
55+
*
56+
* @param int $batch_number The batch number increments for each new batch in the job cycle.
57+
*
58+
* @throws Exception If an error occurs.
59+
* @throws JobException If the job failure rate is too high.
60+
*/
61+
public function handle_create_batch_action( int $batch_number ) {
62+
$create_batch_hook = $this->get_create_batch_hook();
63+
$create_batch_args = [ $batch_number ];
64+
65+
$this->monitor->validate_failure_rate( $this, $create_batch_hook, $create_batch_args );
66+
if ( $this->retry_on_timeout ) {
67+
$this->monitor->attach_timeout_monitor( $create_batch_hook, $create_batch_args );
68+
}
69+
70+
$items = $this->get_filtered_batch( $batch_number );
71+
72+
if ( 0 === $items->get_unfiltered_count() ) {
73+
// if no more items the job is complete
74+
$this->handle_complete( $batch_number );
75+
} else {
76+
// if items, schedule the process action
77+
if ( count( $items ) ) {
78+
$this->schedule_process_action( $items->get_product_ids() );
79+
}
80+
81+
// Add another "create_batch" action to handle unfiltered items.
82+
// The last batch created here will be an empty batch, it
83+
// will call "handle_complete" to finish the job.
84+
$this->schedule_create_batch_action( $batch_number + 1 );
85+
}
86+
87+
$this->monitor->detach_timeout_monitor( $create_batch_hook, $create_batch_args );
88+
}
89+
}

src/Jobs/UpdateAllProducts.php

Lines changed: 2 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@
33

44
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
55

6-
use Automattic\WooCommerce\GoogleListingsAndAds\Product\FilteredProductList;
6+
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\SyncableProductsBatchedActionSchedulerJobTrait;
77
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductSyncerException;
8-
use Exception;
9-
use WC_Product;
108

119
defined( 'ABSPATH' ) || exit;
1210

@@ -18,6 +16,7 @@
1816
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
1917
*/
2018
class UpdateAllProducts extends AbstractProductSyncerBatchedJob {
19+
use SyncableProductsBatchedActionSchedulerJobTrait;
2120

2221
/**
2322
* Get the name of the job.
@@ -28,78 +27,6 @@ public function get_name(): string {
2827
return 'update_all_products';
2928
}
3029

31-
/**
32-
* Get a single batch of items.
33-
*
34-
* If no items are returned the job will stop.
35-
*
36-
* @param int $batch_number The batch number increments for each new batch in the job cycle.
37-
*
38-
* @return WC_Product[]
39-
*/
40-
public function get_batch( int $batch_number ): array {
41-
return $this->get_filtered_batch( $batch_number )->get();
42-
}
43-
44-
/**
45-
* Get a single filtered batch of items.
46-
*
47-
* If no items are returned the job will stop.
48-
*
49-
* @since 1.4.0
50-
*
51-
* @param int $batch_number The batch number increments for each new batch in the job cycle.
52-
*
53-
* @return FilteredProductList
54-
*/
55-
protected function get_filtered_batch( int $batch_number ): FilteredProductList {
56-
return $this->product_repository->find_sync_ready_products( [], $this->get_batch_size(), $this->get_query_offset( $batch_number ) );
57-
}
58-
59-
/**
60-
* Handles batch creation action hook.
61-
*
62-
* @hooked gla/jobs/{$job_name}/create_batch
63-
*
64-
* Schedules an action to run immediately for the items in the batch.
65-
* Uses the unfiltered count to check if there are additional batches.
66-
*
67-
* @since 1.4.0
68-
*
69-
* @param int $batch_number The batch number increments for each new batch in the job cycle.
70-
*
71-
* @throws Exception If an error occurs.
72-
* @throws JobException If the job failure rate is too high.
73-
*/
74-
public function handle_create_batch_action( int $batch_number ) {
75-
$create_batch_hook = $this->get_create_batch_hook();
76-
$create_batch_args = [ $batch_number ];
77-
78-
$this->monitor->validate_failure_rate( $this, $create_batch_hook, $create_batch_args );
79-
if ( $this->retry_on_timeout ) {
80-
$this->monitor->attach_timeout_monitor( $create_batch_hook, $create_batch_args );
81-
}
82-
83-
$items = $this->get_filtered_batch( $batch_number );
84-
85-
if ( 0 === $items->get_unfiltered_count() ) {
86-
// if no more items the job is complete
87-
$this->handle_complete( $batch_number );
88-
} else {
89-
// if items, schedule the process action
90-
if ( count( $items ) ) {
91-
$this->schedule_process_action( $items->get_product_ids() );
92-
}
93-
94-
if ( $items->get_unfiltered_count() >= $this->get_batch_size() ) {
95-
// if there might be more items, add another "create_batch" action to handle them
96-
$this->schedule_create_batch_action( $batch_number + 1 );
97-
}
98-
}
99-
100-
$this->monitor->detach_timeout_monitor( $create_batch_hook, $create_batch_args );
101-
}
102-
10330
/**
10431
* Process batch items.
10532
*

0 commit comments

Comments
 (0)