diff --git a/projects/packages/scheduled-updates/changelog/add-scheduled-update-pause b/projects/packages/scheduled-updates/changelog/add-scheduled-update-pause new file mode 100644 index 0000000000000..45f3be829bc6b --- /dev/null +++ b/projects/packages/scheduled-updates/changelog/add-scheduled-update-pause @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Add scheduled updates active flag diff --git a/projects/packages/scheduled-updates/composer.json b/projects/packages/scheduled-updates/composer.json index 22b912f1ee405..b056c0e78de4f 100644 --- a/projects/packages/scheduled-updates/composer.json +++ b/projects/packages/scheduled-updates/composer.json @@ -49,7 +49,7 @@ }, "autotagger": true, "branch-alias": { - "dev-trunk": "0.10.x-dev" + "dev-trunk": "0.11.x-dev" }, "textdomain": "jetpack-scheduled-updates", "version-constants": { diff --git a/projects/packages/scheduled-updates/src/class-scheduled-updates-active.php b/projects/packages/scheduled-updates/src/class-scheduled-updates-active.php new file mode 100644 index 0000000000000..cdc917ab1a4cf --- /dev/null +++ b/projects/packages/scheduled-updates/src/class-scheduled-updates-active.php @@ -0,0 +1,100 @@ +response ?? array(); $plugins_to_update = array_intersect_key( $plugins_to_update, array_flip( $plugins ) ); @@ -118,10 +132,10 @@ public static function run_scheduled_update( ...$plugins ) { 'no_plugins_to_update' ); - return; + return false; } - ( new Connection\Client() )->wpcom_json_api_request_as_blog( + $response = ( new Connection\Client() )->wpcom_json_api_request_as_blog( sprintf( '/sites/%d/hosting/scheduled-update', \Jetpack_Options::get_option( 'id' ) ), '2', array( 'method' => 'POST' ), @@ -132,6 +146,8 @@ public static function run_scheduled_update( ...$plugins ) { ), 'wpcom' ); + + return is_array( $response ); } /** @@ -460,6 +476,28 @@ public static function deleted_plugin( $plugin_file, $deleted ) { } } + /** + * REST prepare_item_for_response filter. + * + * @param array $item WP Cron event. + * @param \WP_REST_Request $request Request object. + * @return array Response array on success. + */ + public static function response_filter( $item, $request ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + $status = self::get_scheduled_update_status( $item['schedule_id'] ); + + if ( ! $status ) { + $status = array( + 'last_run_timestamp' => null, + 'last_run_status' => null, + ); + } + + $item = array_merge( $item, $status ); + + return $item; + } + /** * Generates a unique schedule ID. * diff --git a/projects/packages/scheduled-updates/src/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-update-schedules.php b/projects/packages/scheduled-updates/src/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-update-schedules.php index 6caa1aa7372db..8b9097a5c9758 100644 --- a/projects/packages/scheduled-updates/src/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-update-schedules.php +++ b/projects/packages/scheduled-updates/src/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-update-schedules.php @@ -541,18 +541,7 @@ public function prepare_response_for_collection( $response ) { */ public function prepare_item_for_response( $item, $request ) { $item = (array) $item; - - $status = Scheduled_Updates::get_scheduled_update_status( $item['schedule_id'] ); - if ( ! $status ) { - $status = array( - 'last_run_timestamp' => null, - 'last_run_status' => null, - ); - } - - $item = array_merge( $item, $status ); - $item['health_check_paths'] = Scheduled_Updates_Health_Paths::get( $item['schedule_id'] ); - + $item = apply_filters( 'jetpack_scheduled_response_item', $item, $request ); $item = $this->add_additional_fields_to_object( $item, $request ); // Remove schedule ID, not needed in the response. @@ -771,6 +760,10 @@ public function get_item_schema() { 'type' => 'string', 'enum' => array( 'success', 'failure-and-rollback', 'failure-and-rollback-fail' ), ), + 'active' => array( + 'description' => 'Whether the schedule is active.', + 'type' => 'boolean', + ), 'health_check_paths' => array( 'description' => 'Paths to check for site health.', 'type' => 'array', @@ -818,6 +811,12 @@ public function get_object_params() { 'type' => 'integer', 'required' => true, ), + 'active' => array( + 'description' => 'Whether the schedule is active.', + 'type' => 'boolean', + 'required' => false, + 'default' => true, + ), 'health_check_paths' => array( 'description' => 'List of paths to check for site health after the update.', 'type' => 'array', diff --git a/projects/packages/scheduled-updates/tests/php/class-scheduled-updates-active-test.php b/projects/packages/scheduled-updates/tests/php/class-scheduled-updates-active-test.php new file mode 100644 index 0000000000000..96be46bc767da --- /dev/null +++ b/projects/packages/scheduled-updates/tests/php/class-scheduled-updates-active-test.php @@ -0,0 +1,239 @@ +clear_all_users(); + + $this->admin_id = wp_insert_user( + array( + 'user_login' => 'dummy_path_user', + 'user_pass' => 'dummy_path_pass', + 'role' => 'administrator', + ) + ); + + wp_set_current_user( $this->admin_id ); + + Scheduled_Updates::init(); + do_action( 'rest_api_init' ); + } + + /** + * Clean up after test + * + * @after + */ + public function tear_down() { + wp_delete_user( $this->admin_id ); + delete_option( Scheduled_Updates_Active::OPTION_NAME ); + + parent::tear_down_wordbless(); + } + + /** + * Test create_item. + * + * @covers WPCOM_REST_API_V2_Endpoint_Update_Schedules::create_item + */ + public function test_create_scheduled_update_with_active_true() { + $plugins = array( + 'custom-plugin/custom-plugin.php', + 'gutenberg/gutenberg.php', + ); + $request = new WP_REST_Request( 'POST', '/wpcom/v2/update-schedules' ); + $post_data = array( + 'plugins' => $plugins, + 'schedule' => array( + 'timestamp' => strtotime( 'next Monday 8:00' ), + 'interval' => 'weekly', + 'health_check_paths' => array(), + 'active' => true, + ), + ); + + $request->set_body_params( $post_data ); + + $schedule_id = Scheduled_Updates::generate_schedule_id( $plugins ); + $result = rest_do_request( $request ); + $id = $result->get_data(); + + $this->assertSame( 200, $result->get_status() ); + $this->assertSame( $schedule_id, $id ); + $this->assertTrue( Scheduled_Updates_Active::get( $schedule_id ) ); + + $request = new WP_REST_Request( 'GET', '/wpcom/v2/update-schedules' ); + $result = rest_do_request( $request ); + $this->assertSame( 200, $result->get_status() ); + + // Set active to false. + $post_data['schedule']['active'] = false; + + $request->set_method( 'PUT' ); + $request->set_route( '/wpcom/v2/update-schedules/' . $id ); + $request->set_body_params( $post_data ); + $result = rest_do_request( $request ); + + $this->assertSame( 200, $result->get_status() ); + + $request->set_method( 'GET' ); + $result = rest_do_request( $request ); + $this->assertSame( 200, $result->get_status() ); + $this->assertFalse( $result->get_data()['active'] ); + + $request->set_method( 'DELETE' ); + $result = rest_do_request( $request ); + $this->assertSame( 200, $result->get_status() ); + + // The option should be removed. + $this->assertSame( 'test', get_option( Scheduled_Updates_Active::OPTION_NAME, 'test' ) ); + } + + /** + * Test create_item. + * + * @covers WPCOM_REST_API_V2_Endpoint_Update_Schedules::create_item + */ + public function test_create_scheduled_update_is_active() { + $plugins = array( 'gutenberg/gutenberg.php' ); + $request = new WP_REST_Request( 'POST', '/wpcom/v2/update-schedules' ); + $post_data = array( + 'plugins' => $plugins, + 'schedule' => array( + 'timestamp' => strtotime( 'next Monday 8:00' ), + 'interval' => 'weekly', + 'health_check_paths' => array(), + ), + ); + + $request->set_body_params( $post_data ); + + $schedule_id = Scheduled_Updates::generate_schedule_id( $plugins ); + $result = rest_do_request( $request ); + + $this->assertSame( 200, $result->get_status() ); + delete_option( Scheduled_Updates_Active::OPTION_NAME ); + + $request->set_method( 'GET' ); + $result = rest_do_request( $request ); + $this->assertSame( 200, $result->get_status() ); + + // Still active (for backwards compatibility) + $this->assertTrue( $result->get_data()[ $schedule_id ]['active'] ); + } + + /** + * Test run_scheduled_update. + * + * @covers Scheduled_Updates::run_scheduled_update + */ + public function test_run_inactive_schedule() { + $plugins = array( + 'custom-plugin/custom-plugin.php', + 'gutenberg/gutenberg.php', + ); + $request = new WP_REST_Request( 'POST', '/wpcom/v2/update-schedules' ); + $post_data = array( + 'plugins' => $plugins, + 'schedule' => array( + 'timestamp' => strtotime( 'next Monday 8:00' ), + 'interval' => 'weekly', + 'health_check_paths' => array(), + 'active' => false, + ), + ); + + $request->set_body_params( $post_data ); + $result = rest_do_request( $request ); + $this->assertSame( 200, $result->get_status() ); + + $schedule_id = Scheduled_Updates::generate_schedule_id( $plugins ); + $this->assertSame( $schedule_id, $result->get_data() ); + $this->assertFalse( call_user_func_array( array( Scheduled_Updates::class, 'run_scheduled_update' ), $plugins ) ); + } + + /** + * Test run_scheduled_update. + * + * @covers Scheduled_Updates::run_scheduled_update + */ + public function test_run_active_schedule() { + $plugins = array( 'gutenberg/gutenberg.php' ); + $request = new WP_REST_Request( 'POST', '/wpcom/v2/update-schedules' ); + $post_data = array( + 'plugins' => $plugins, + 'schedule' => array( + 'timestamp' => strtotime( 'next Monday 8:00' ), + 'interval' => 'weekly', + 'health_check_paths' => array(), + ), + ); + + $request->set_body_params( $post_data ); + $result = rest_do_request( $request ); + $this->assertSame( 200, $result->get_status() ); + + $schedule_id = Scheduled_Updates::generate_schedule_id( $plugins ); + $this->assertSame( $schedule_id, $result->get_data() ); + $this->assertFalse( call_user_func_array( array( Scheduled_Updates::class, 'run_scheduled_update' ), $plugins ) ); + + $logs = Scheduled_Updates_Logs::get( $schedule_id ); + $this->assertCount( 1, $logs ); + $this->assertCount( 2, $logs[0] ); + + // The scheduled update started and succeeded. + $this->assertSame( Scheduled_Updates_Logs::PLUGIN_UPDATES_START, $logs[0][0]['action'] ); + $this->assertSame( Scheduled_Updates_Logs::PLUGIN_UPDATES_SUCCESS, $logs[0][1]['action'] ); + } +} diff --git a/projects/packages/scheduled-updates/tests/php/class-wpcom-rest-api-v2-endpoint-update-schedules-test.php b/projects/packages/scheduled-updates/tests/php/class-wpcom-rest-api-v2-endpoint-update-schedules-test.php index 7dcf3f210f519..ce1b1a3b9b178 100644 --- a/projects/packages/scheduled-updates/tests/php/class-wpcom-rest-api-v2-endpoint-update-schedules-test.php +++ b/projects/packages/scheduled-updates/tests/php/class-wpcom-rest-api-v2-endpoint-update-schedules-test.php @@ -135,6 +135,7 @@ public function test_get_items() { 'last_run_timestamp' => null, 'last_run_status' => null, 'health_check_paths' => array(), + 'active' => true, ), Scheduled_Updates::generate_schedule_id( $plugins ) => array( 'hook' => Scheduled_Updates::PLUGIN_CRON_HOOK, @@ -145,6 +146,7 @@ public function test_get_items() { 'last_run_timestamp' => null, 'last_run_status' => null, 'health_check_paths' => array(), + 'active' => true, ), ), $result->get_data() @@ -422,6 +424,7 @@ public function test_empty_last_run() { 'last_run_timestamp' => null, 'last_run_status' => null, 'health_check_paths' => array(), + 'active' => true, ), ), $result->get_data() @@ -505,6 +508,7 @@ public function test_get_item() { 'last_run_timestamp' => null, 'last_run_status' => null, 'health_check_paths' => array(), + 'active' => true, ), $result->get_data() ); diff --git a/projects/plugins/mu-wpcom-plugin/changelog/add-scheduled-update-pause b/projects/plugins/mu-wpcom-plugin/changelog/add-scheduled-update-pause new file mode 100644 index 0000000000000..9aa70e3ec1f75 --- /dev/null +++ b/projects/plugins/mu-wpcom-plugin/changelog/add-scheduled-update-pause @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Updated composer.lock. + + diff --git a/projects/plugins/mu-wpcom-plugin/composer.json b/projects/plugins/mu-wpcom-plugin/composer.json index dd275ee975bfe..41144285dcd8d 100644 --- a/projects/plugins/mu-wpcom-plugin/composer.json +++ b/projects/plugins/mu-wpcom-plugin/composer.json @@ -46,6 +46,6 @@ ] }, "config": { - "autoloader-suffix": "d9d132a783958a00a2c7cccff60ca42d_jetpack_mu_wpcom_pluginⓥ2_1_19" + "autoloader-suffix": "d9d132a783958a00a2c7cccff60ca42d_jetpack_mu_wpcom_pluginⓥ2_1_20_alpha" } } diff --git a/projects/plugins/mu-wpcom-plugin/composer.lock b/projects/plugins/mu-wpcom-plugin/composer.lock index c4d1b1d1488bc..bcf7898935b04 100644 --- a/projects/plugins/mu-wpcom-plugin/composer.lock +++ b/projects/plugins/mu-wpcom-plugin/composer.lock @@ -978,7 +978,7 @@ "dist": { "type": "path", "url": "../../packages/scheduled-updates", - "reference": "f54ff98d9b5b3ba08e1684e06d722f2ac9f899be" + "reference": "83cc27751c6f1c887d3c04fd4d3c367da7b98745" }, "require": { "php": ">=7.0" @@ -1001,7 +1001,7 @@ }, "autotagger": true, "branch-alias": { - "dev-trunk": "0.10.x-dev" + "dev-trunk": "0.11.x-dev" }, "textdomain": "jetpack-scheduled-updates", "version-constants": { diff --git a/projects/plugins/mu-wpcom-plugin/mu-wpcom-plugin.php b/projects/plugins/mu-wpcom-plugin/mu-wpcom-plugin.php index 10b41128a57b7..a7fabfab9eff4 100644 --- a/projects/plugins/mu-wpcom-plugin/mu-wpcom-plugin.php +++ b/projects/plugins/mu-wpcom-plugin/mu-wpcom-plugin.php @@ -3,7 +3,7 @@ * * Plugin Name: WordPress.com Features * Description: Test plugin for the jetpack-mu-wpcom package - * Version: 2.1.19 + * Version: 2.1.20-alpha * Author: Automattic * License: GPLv2 or later * Text Domain: jetpack-mu-wpcom-plugin diff --git a/projects/plugins/mu-wpcom-plugin/package.json b/projects/plugins/mu-wpcom-plugin/package.json index 4e1ddc3b80141..c7afa35f4f439 100644 --- a/projects/plugins/mu-wpcom-plugin/package.json +++ b/projects/plugins/mu-wpcom-plugin/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-mu-wpcom-plugin", - "version": "2.1.19", + "version": "2.1.20-alpha", "description": "Test plugin for the jetpack-mu-wpcom package", "homepage": "https://jetpack.com", "bugs": {