diff --git a/README.md b/README.md index f55bcd5..2436347 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,8 @@ In a non-production environment you should manually run `schedule-monitor:sync`. **Note:** Running the sync command will remove any other cron monitors that you've defined other than the application schedule. +If you would like to use non-destructive syncs to Oh Dear so that you can monitor other cron tasks outside of Laravel, you can use the `--keep-old` flag. This will only push new tasks to Oh Dear, rather than a full sync. Note that this will not remove any tasks from Oh Dear that are no longer in your schedule. + ## Usage To monitor your schedule you should first run `schedule-monitor:sync`. This command will take a look at your schedule and create an entry for each task in the `monitored_scheduled_tasks` table. diff --git a/composer.json b/composer.json index d3c9f57..5fcb2df 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,7 @@ "orchestra/testbench": "^7.0|^8.0", "pestphp/pest": "^1.20", "pestphp/pest-plugin-laravel": "^1.2", + "spatie/pest-plugin-snapshots": "^1.1", "spatie/phpunit-snapshot-assertions": "^4.2", "spatie/test-time": "^1.2" }, diff --git a/src/Commands/SyncCommand.php b/src/Commands/SyncCommand.php index 88cb880..6511f4b 100644 --- a/src/Commands/SyncCommand.php +++ b/src/Commands/SyncCommand.php @@ -15,7 +15,7 @@ class SyncCommand extends Command { use UsesScheduleMonitoringModels; - public $signature = 'schedule-monitor:sync'; + public $signature = 'schedule-monitor:sync {--keep-old}'; public $description = 'Sync the schedule of the app with the schedule monitor'; @@ -27,8 +27,8 @@ public function handle() ])); $this - ->syncScheduledTasksWithDatabase() - ->syncMonitoredScheduledTaskWithOhDear(); + ->storeScheduledTasksInDatabase() + ->storeMonitoredScheduledTasksInOhDear(); $monitoredScheduledTasksCount = $this->getMonitoredScheduleTaskModel()->count(); @@ -37,7 +37,7 @@ public function handle() ])); } - protected function syncScheduledTasksWithDatabase(): self + protected function storeScheduledTasksInDatabase(): self { render(view('schedule-monitor::alert', [ 'message' => 'Start syncing schedule with database...', @@ -57,14 +57,16 @@ protected function syncScheduledTasksWithDatabase(): self ); }); - $this->getMonitoredScheduleTaskModel()->query() - ->whereNotIn('id', $monitoredScheduledTasks->pluck('id')) - ->delete(); + if (! $this->option('keep-old')) { + $this->getMonitoredScheduleTaskModel()->query() + ->whereNotIn('id', $monitoredScheduledTasks->pluck('id')) + ->delete(); + } return $this; } - protected function syncMonitoredScheduledTaskWithOhDear(): self + protected function storeMonitoredScheduledTasksInOhDear(): self { if (! class_exists(OhDear::class)) { return $this; @@ -93,6 +95,32 @@ protected function syncMonitoredScheduledTaskWithOhDear(): self 'message' => 'Start syncing schedule with Oh Dear...', ])); + $cronChecks = $this->option('keep-old') + ? $this->pushMonitoredScheduledTaskToOhDear($siteId) + : $this->syncMonitoredScheduledTaskWithOhDear($siteId); + + render(view('schedule-monitor::alert', [ + 'message' => 'Successfully synced schedule with Oh Dear!', + 'class' => 'text-green', + ])); + + collect($cronChecks) + ->each( + function (CronCheck $cronCheck) { + if (! $monitoredScheduledTask = $this->getMonitoredScheduleTaskModel()->findForCronCheck($cronCheck)) { + return; + } + + $monitoredScheduledTask->update(['ping_url' => $cronCheck->pingUrl]); + $monitoredScheduledTask->markAsRegisteredOnOhDear(); + } + ); + + return $this; + } + + protected function syncMonitoredScheduledTaskWithOhDear(int $siteId): array + { $monitoredScheduledTasks = $this->getMonitoredScheduleTaskModel()->get(); $cronChecks = $monitoredScheduledTasks @@ -110,23 +138,27 @@ protected function syncMonitoredScheduledTaskWithOhDear(): self $cronChecks = app(OhDear::class)->site($siteId)->syncCronChecks($cronChecks); - render(view('schedule-monitor::alert', [ - 'message' => 'Successfully synced schedule with Oh Dear!', - 'class' => 'text-green', - ])); - - collect($cronChecks) - ->each( - function (CronCheck $cronCheck) { - if (! $monitoredScheduledTask = $this->getMonitoredScheduleTaskModel()->findForCronCheck($cronCheck)) { - return; - } + return $cronChecks; + } - $monitoredScheduledTask->update(['ping_url' => $cronCheck->pingUrl]); - $monitoredScheduledTask->markAsRegisteredOnOhDear(); - } + protected function pushMonitoredScheduledTaskToOhDear(int $siteId): array + { + $tasksToRegister = $this->getMonitoredScheduleTaskModel() + ->whereNull('registered_on_oh_dear_at') + ->get(); + + $cronChecks = []; + foreach ($tasksToRegister as $taskToRegister) { + $cronChecks[] = app(OhDear::class)->createCronCheck( + siteId: $siteId, + name: $taskToRegister->name, + cronExpression: $taskToRegister->cron_expression, + graceTimeInMinutes: $taskToRegister->grace_time_in_minutes, + description: '', + serverTimezone: $taskToRegister->timezone, ); + } - return $this; + return $cronChecks; } } diff --git a/tests/Commands/SyncCommandTest.php b/tests/Commands/SyncCommandTest.php index b6d3b2b..88a4b57 100644 --- a/tests/Commands/SyncCommandTest.php +++ b/tests/Commands/SyncCommandTest.php @@ -6,6 +6,7 @@ use Spatie\ScheduleMonitor\Tests\TestClasses\TestJob; use Spatie\ScheduleMonitor\Tests\TestClasses\TestKernel; use Spatie\TestTime\TestTime; +use function Spatie\Snapshots\{assertMatchesSnapshot}; beforeEach(function () { TestTime::freeze('Y-m-d H:i:s', '2020-01-01 00:00:00'); @@ -76,7 +77,73 @@ 'timezone' => 'Asia/Kolkata', ]); - $this->assertMatchesSnapshot($this->ohDear->getSyncedCronCheckAttributes()); + assertMatchesSnapshot($this->ohDear->getSyncedCronCheckAttributes()); +}); + +it('can use the keep old option to non destructively update the schedule with db and oh dear', function () { + MonitoredScheduledTask::create([ + 'name' => 'dummy-1', + 'type' => 'command', + 'cron_expression' => '* * * * *', + 'ping_url' => 'https://ping.ohdear.app/test-ping-url-dummy-1', + 'registered_on_oh_dear_at' => now()->format('Y-m-d H:i:s'), + 'grace_time_in_minutes' => 5, + 'last_pinged_at' => null, + 'last_started_at' => null, + 'last_finished_at' => null, + 'timezone' => 'UTC', + ]); + + TestKernel::registerScheduledTasks(function (Schedule $schedule) { + $schedule->command('dummy-2')->hourly(); + $schedule->command('dummy-3')->daily(); + }); + + $this->artisan(SyncCommand::class, ['--keep-old' => true]); + + $monitoredScheduledTasks = MonitoredScheduledTask::get(); + expect($monitoredScheduledTasks)->toHaveCount(3); + + $this->assertDatabaseHas('monitored_scheduled_tasks', [ + 'name' => 'dummy-1', + 'type' => 'command', + 'cron_expression' => '* * * * *', + 'ping_url' => 'https://ping.ohdear.app/test-ping-url-dummy-1', + 'registered_on_oh_dear_at' => now()->format('Y-m-d H:i:s'), + 'grace_time_in_minutes' => 5, + 'last_pinged_at' => null, + 'last_started_at' => null, + 'last_finished_at' => null, + 'timezone' => 'UTC', + ]); + + $this->assertDatabaseHas('monitored_scheduled_tasks', [ + 'name' => 'dummy-2', + 'type' => 'command', + 'cron_expression' => '0 * * * *', + 'ping_url' => 'https://ping.ohdear.app/test-ping-url-dummy-2', + 'registered_on_oh_dear_at' => now()->format('Y-m-d H:i:s'), + 'grace_time_in_minutes' => 5, + 'last_pinged_at' => null, + 'last_started_at' => null, + 'last_finished_at' => null, + 'timezone' => 'UTC', + ]); + + $this->assertDatabaseHas('monitored_scheduled_tasks', [ + 'name' => 'dummy-3', + 'type' => 'command', + 'cron_expression' => '0 0 * * *', + 'ping_url' => 'https://ping.ohdear.app/test-ping-url-dummy-3', + 'registered_on_oh_dear_at' => now()->format('Y-m-d H:i:s'), + 'grace_time_in_minutes' => 5, + 'last_pinged_at' => null, + 'last_started_at' => null, + 'last_finished_at' => null, + 'timezone' => 'UTC', + ]); + + assertMatchesSnapshot($this->ohDear->getSyncedCronCheckAttributes()); }); it('will not monitor commands without a name', function () { diff --git a/tests/TestClasses/FakeOhDear.php b/tests/TestClasses/FakeOhDear.php index fc87306..f8fcdb1 100644 --- a/tests/TestClasses/FakeOhDear.php +++ b/tests/TestClasses/FakeOhDear.php @@ -28,6 +28,30 @@ public function getSyncedCronCheckAttributes(): array { return $this->syncedCronCheckAttributes; } + + public function createCronCheck( + int $siteId, + string $name, + string $cronExpression, + int $graceTimeInMinutes, + $description, + string $serverTimezone + ): CronCheck { + $attributes = [ + 'name' => $name, + 'type' => 'cron', + 'cron_expression' => $cronExpression, + 'grace_time_in_minutes' => $graceTimeInMinutes, + 'description' => $description ?? '', + 'server_timezone' => $serverTimezone, + ]; + + $attributes['ping_url'] = 'https://ping.ohdear.app/test-ping-url-' . urlencode($attributes['name']); + + $this->syncedCronCheckAttributes[] = $attributes; + + return new CronCheck($attributes, $this); + } } class FakeSite extends Site diff --git a/tests/Commands/__snapshots__/SyncCommandTest__it_can_sync_the_schedule_with_the_db_and_oh_dear__1.yml b/tests/__snapshots__/SyncCommandTest__it_can_sync_the_schedule_with_the_db_and_oh_dear__1.yml similarity index 100% rename from tests/Commands/__snapshots__/SyncCommandTest__it_can_sync_the_schedule_with_the_db_and_oh_dear__1.yml rename to tests/__snapshots__/SyncCommandTest__it_can_sync_the_schedule_with_the_db_and_oh_dear__1.yml diff --git a/tests/__snapshots__/SyncCommandTest__it_can_use_the_keep_old_option_to_non_destructively_update_the_schedule_with_db_and_oh_dear__1.yml b/tests/__snapshots__/SyncCommandTest__it_can_use_the_keep_old_option_to_non_destructively_update_the_schedule_with_db_and_oh_dear__1.yml new file mode 100644 index 0000000..35180ba --- /dev/null +++ b/tests/__snapshots__/SyncCommandTest__it_can_use_the_keep_old_option_to_non_destructively_update_the_schedule_with_db_and_oh_dear__1.yml @@ -0,0 +1,16 @@ +- + name: dummy-2 + type: cron + cron_expression: '0 * * * *' + grace_time_in_minutes: 5 + description: '' + server_timezone: UTC + ping_url: 'https://ping.ohdear.app/test-ping-url-dummy-2' +- + name: dummy-3 + type: cron + cron_expression: '0 0 * * *' + grace_time_in_minutes: 5 + description: '' + server_timezone: UTC + ping_url: 'https://ping.ohdear.app/test-ping-url-dummy-3'