Skip to content

Commit

Permalink
Add scheduled updates sync option (#36877)
Browse files Browse the repository at this point in the history
* Add sync option

* changelog

* Update composer.lock

* Move option to callable

* changelog

* Fix: failing unit tests

* Remove callable

* Fix: missing error

* Save cron option on logs saving

* Rename CHANGELOG file

* Fixed CHANGELOG file content

* Update versions

* Add temporary solution for backward compatibility
  • Loading branch information
zaerl authored Apr 16, 2024
1 parent 6722814 commit c1db9b7
Show file tree
Hide file tree
Showing 34 changed files with 283 additions and 39 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Add a sync option where for scheduled updated.
2 changes: 1 addition & 1 deletion projects/packages/scheduled-updates/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
},
"autotagger": true,
"branch-alias": {
"dev-trunk": "0.7.x-dev"
"dev-trunk": "0.8.x-dev"
},
"textdomain": "jetpack-scheduled-updates",
"version-constants": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class Scheduled_Updates {
*
* @var string
*/
const PACKAGE_VERSION = '0.7.2';
const PACKAGE_VERSION = '0.8.0-alpha';

/**
* The cron event hook for the scheduled plugins update.
Expand Down Expand Up @@ -53,6 +53,23 @@ public static function init() {
add_filter( 'auto_update_plugin', array( __CLASS__, 'allowlist_scheduled_plugins' ), 10, 2 );
add_filter( 'plugin_auto_update_setting_html', array( __CLASS__, 'show_scheduled_updates' ), 10, 2 );
add_action( 'deleted_plugin', array( __CLASS__, 'deleted_plugin' ), 10, 2 );

// Update cron sync option after options update.
$callback = array( __CLASS__, 'update_option_cron' );

// Main cron saving.
add_action( 'add_option_cron', $callback );
add_action( 'update_option_cron', $callback );

// Logs saving.
add_action( 'add_option_' . Scheduled_Updates_Logs::OPTION_NAME, $callback );
add_action( 'update_option_' . Scheduled_Updates_Logs::OPTION_NAME, $callback );

// This is a temporary solution for backward compatibility. It will be removed in the future.
// It's needed to ensure that preexisting schedules are loaded into the sync option.
if ( false === get_option( self::PLUGIN_CRON_HOOK ) ) {
call_user_func( $callback );
}
}

/**
Expand Down Expand Up @@ -134,6 +151,31 @@ public static function delete_scheduled_update( $timestamp, $plugins ) {
return wp_unschedule_event( $timestamp, self::PLUGIN_CRON_HOOK, $plugins, true );
}

/**
* Save the schedules for sync after cron option saving.
*/
public static function update_option_cron() {
$events = wp_get_scheduled_events( self::PLUGIN_CRON_HOOK );

foreach ( array_keys( $events ) as $schedule_id ) {
$events[ $schedule_id ]->schedule_id = $schedule_id;

$status = self::get_scheduled_update_status( $schedule_id );

if ( ! $status ) {
$status = array(
'last_run_timestamp' => null,
'last_run_status' => null,
);
}

$events[ $schedule_id ]->last_run_timestamp = $status['last_run_timestamp'];
$events[ $schedule_id ]->last_run_status = $status['last_run_status'];
}

update_option( self::PLUGIN_CRON_HOOK, $events );
}

/**
* Clear the cron cache.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ public function create_item( $request ) {
usort( $plugins, 'strnatcasecmp' );

$event = Scheduled_Updates::create_scheduled_update( $schedule['timestamp'], $schedule['interval'], $plugins );

if ( is_wp_error( $event ) ) {
return $event;
}
Expand Down Expand Up @@ -419,7 +420,11 @@ public function add_log( $request ) {
$message = $request['message'];
$context = $request['context'];

Scheduled_Updates_Logs::log( $schedule_id, $action, $message, $context );
$log = Scheduled_Updates_Logs::log( $schedule_id, $action, $message, $context );

if ( is_wp_error( $log ) ) {
return new WP_Error( 'rest_invalid_schedule', __( 'The schedule could not be found.', 'jetpack-scheduled-updates' ), array( 'status' => 404 ) );
}

return rest_ensure_response( true );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,14 @@ public function test_get_items() {
* @covers ::create_item
*/
public function test_create_item() {
$plugins = array(
'custom-plugin/custom-plugin.php',
'gutenberg/gutenberg.php',
);
$request = new WP_REST_Request( 'POST', '/wpcom/v2/update-schedules' );
$request->set_body_params(
array(
'plugins' => array(
'custom-plugin/custom-plugin.php',
'gutenberg/gutenberg.php',
),
'plugins' => $plugins,
'schedule' => array(
'timestamp' => strtotime( 'next Monday 8:00' ),
'interval' => 'weekly',
Expand Down Expand Up @@ -181,13 +182,17 @@ public function test_create_item() {
$this->assertSame( 200, $result->get_status() );
$this->assertSame( $schedule_id, $result->get_data() );

$sync_option = get_option( Scheduled_Updates::PLUGIN_CRON_HOOK );
$this->assertIsArray( $sync_option );
$this->assertIsObject( $sync_option[ $schedule_id ] );
$this->assertSame( $plugins, $sync_option[ $schedule_id ]->args );
$this->assertNull( $sync_option[ $schedule_id ]->last_run_timestamp );
$this->assertNull( $sync_option[ $schedule_id ]->last_run_status );

// Can't create a schedule for the same time again.
$request->set_body_params(
array(
'plugins' => array(
'custom-plugin/custom-plugin.php',
'gutenberg/gutenberg.php',
),
'plugins' => $plugins,
'schedule' => array(
'timestamp' => strtotime( 'next Monday 8:00' ),
'interval' => 'weekly',
Expand All @@ -201,6 +206,101 @@ public function test_create_item() {
$this->assertSame( 'rest_forbidden', $result->get_data()['code'] );
}

/**
* Test create multiple item.
*
* @covers ::create_item
*/
public function test_create_multiple_item() {
$plugins = array(
'custom-plugin/custom-plugin.php',
'gutenberg/gutenberg.php',
);
$request = new WP_REST_Request( 'POST', '/wpcom/v2/update-schedules' );
$request->set_body_params(
array(
'plugins' => $plugins,
'schedule' => array(
'timestamp' => strtotime( 'next Monday 8:00' ),
'interval' => 'weekly',
),
)
);
$schedule_id = Scheduled_Updates::generate_schedule_id( $request->get_body_params()['plugins'] );

// Successful request.
wp_set_current_user( $this->admin_id );
$result = rest_do_request( $request );

$this->assertSame( 200, $result->get_status() );
$this->assertSame( $schedule_id, $result->get_data() );

$sync_option = get_option( Scheduled_Updates::PLUGIN_CRON_HOOK );
$this->assertIsArray( $sync_option );
$this->assertIsObject( $sync_option[ $schedule_id ] );
$this->assertSame( $plugins, $sync_option[ $schedule_id ]->args );
$this->assertNull( $sync_option[ $schedule_id ]->last_run_timestamp );
$this->assertNull( $sync_option[ $schedule_id ]->last_run_status );

$plugins[] = 'wp-test-plugin/wp-test-plugin.php';
$request->set_body_params(
array(
'plugins' => $plugins,
'schedule' => array(
'timestamp' => strtotime( 'next Monday 10:00' ),
'interval' => 'weekly',
),
)
);

$schedule_id_2 = Scheduled_Updates::generate_schedule_id( $request->get_body_params()['plugins'] );
$result = rest_do_request( $request );

$this->assertSame( 200, $result->get_status() );
$this->assertSame( $schedule_id_2, $result->get_data() );

$sync_option = get_option( Scheduled_Updates::PLUGIN_CRON_HOOK );
$this->assertIsArray( $sync_option );
$this->assertIsObject( $sync_option[ $schedule_id ] );
$this->assertIsObject( $sync_option[ $schedule_id_2 ] );
}

/**
* Temporary test to ensure backward compatibility. It will be removed in the future.
*/
public function test_init_backward_compatibility() {
$plugins = array(
'custom-plugin/custom-plugin.php',
'gutenberg/gutenberg.php',
);
$request = new WP_REST_Request( 'POST', '/wpcom/v2/update-schedules' );
$request->set_body_params(
array(
'plugins' => $plugins,
'schedule' => array(
'timestamp' => strtotime( 'next Monday 8:00' ),
'interval' => 'weekly',
),
)
);

wp_set_current_user( $this->admin_id );
$result = rest_do_request( $request );

$this->assertSame( 200, $result->get_status() );

$pre_sync_option = get_option( Scheduled_Updates::PLUGIN_CRON_HOOK );
$this->assertIsArray( $pre_sync_option );

// Force deleting the option to test backward compatibility.
$this->assertTrue( delete_option( Scheduled_Updates::PLUGIN_CRON_HOOK ) );

// Simulate an init.
Scheduled_Updates::init();
$post_sync_option = get_option( Scheduled_Updates::PLUGIN_CRON_HOOK );
$this->assertEquals( $pre_sync_option, $post_sync_option );
}

/**
* Can't have multiple schedules for the same time.
*
Expand Down Expand Up @@ -562,6 +662,11 @@ public function test_update_item() {

$this->assertSame( 200, $result->get_status() );
$this->assertSame( $schedule_id, $result->get_data() );

$sync_option = get_option( Scheduled_Updates::PLUGIN_CRON_HOOK );
$this->assertIsArray( $sync_option );
$this->assertIsObject( $sync_option[ $schedule_id ] );
$this->assertSame( $plugins, $sync_option[ $schedule_id ]->args );
}

/**
Expand Down Expand Up @@ -610,6 +715,13 @@ public function test_update_item_with_status() {
// doing these null checks for the static analyzer
$this->assertSame( $timestamp, $updated_status['last_run_timestamp'] ?? null );
$this->assertSame( $status, $updated_status['last_run_status'] ?? null );

$sync_option = get_option( Scheduled_Updates::PLUGIN_CRON_HOOK );
$this->assertIsArray( $sync_option );
$this->assertIsObject( $sync_option[ $schedule_id ] );
$this->assertSame( $plugins, $sync_option[ $schedule_id ]->args );
$this->assertSame( $timestamp, $sync_option[ $schedule_id ]->last_run_timestamp );
$this->assertSame( $status, $sync_option[ $schedule_id ]->last_run_status );
}
}

Expand Down Expand Up @@ -674,6 +786,9 @@ public function test_delete_item() {
$this->assertTrue( $result->get_data() );

$this->assertFalse( wp_get_scheduled_event( Scheduled_Updates::PLUGIN_CRON_HOOK, $plugins ) );

$sync_option = get_option( Scheduled_Updates::PLUGIN_CRON_HOOK );
$this->assertSame( array(), $sync_option );
}

/**
Expand Down Expand Up @@ -766,7 +881,8 @@ public function test_add_log() {
);
$result = rest_do_request( $request );

$this->assertSame( 200, $result->get_status() );
$this->assertSame( 404, $result->get_status() );
$this->assertSame( false, get_option( Scheduled_Updates::PLUGIN_CRON_HOOK ) );
}

/**
Expand All @@ -784,6 +900,11 @@ public function test_get_logs() {

$this->assertSame( 200, $result->get_status() );
$this->assertSame( array(), $result->get_data() );

$sync_option = get_option( Scheduled_Updates::PLUGIN_CRON_HOOK );
$this->assertIsArray( $sync_option );
$this->assertNull( $sync_option[ $schedule_id ]->last_run_timestamp );
$this->assertNull( $sync_option[ $schedule_id ]->last_run_status );
}

/**
Expand Down Expand Up @@ -814,6 +935,11 @@ public function test_add_and_get_log() {
$this->assertSame( 200, $result->get_status() );
$this->assertCount( 1, $result->get_data() );
$this->assertSame( Scheduled_Updates_Logs::PLUGIN_UPDATES_START, $result->get_data()[0][0]['action'] );

$sync_option = get_option( Scheduled_Updates::PLUGIN_CRON_HOOK );
$this->assertIsArray( $sync_option );
$this->assertNull( $sync_option[ $schedule_id ]->last_run_timestamp );
$this->assertSame( 'in-progress', $sync_option[ $schedule_id ]->last_run_status );
}

/**
Expand Down Expand Up @@ -856,6 +982,10 @@ public function test_add_and_get_multiple_logs() {
$this->assertSame( 200, $result->get_status() );
$this->assertCount( Scheduled_Updates_Logs::MAX_RUNS_PER_SCHEDULE, $result->get_data() );
$this->assertSame( Scheduled_Updates_Logs::PLUGIN_UPDATES_START, $result->get_data()[0][0]['action'] );

$sync_option = get_option( Scheduled_Updates::PLUGIN_CRON_HOOK );
$this->assertNotNull( $sync_option[ $schedule_id ]->last_run_timestamp );
$this->assertSame( 'success', $sync_option[ $schedule_id ]->last_run_status );
}

/**
Expand Down
4 changes: 4 additions & 0 deletions projects/packages/sync/changelog/add-scheduled-updates-sync
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: changed

Added scheduled updates sync option.
2 changes: 1 addition & 1 deletion projects/packages/sync/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"link-template": "https://github.com/Automattic/jetpack-sync/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "2.12.x-dev"
"dev-trunk": "2.13.x-dev"
}
},
"config": {
Expand Down
2 changes: 1 addition & 1 deletion projects/packages/sync/src/class-package-version.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*/
class Package_Version {

const PACKAGE_VERSION = '2.12.0';
const PACKAGE_VERSION = '2.13.0-alpha';

const PACKAGE_SLUG = 'sync';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Significance: patch
Type: changed
Comment: Updated composer.lock.


4 changes: 2 additions & 2 deletions projects/plugins/automattic-for-agencies-client/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Significance: patch
Type: changed
Comment: Updated composer.lock.


4 changes: 2 additions & 2 deletions projects/plugins/backup/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit c1db9b7

Please sign in to comment.