From 28d07f051131deb23b6b0c2d1c3a24d6417941b1 Mon Sep 17 00:00:00 2001 From: Moshe Brodsky <44633930+moshe-autoleadstar@users.noreply.github.com> Date: Wed, 7 Dec 2022 17:31:19 +0200 Subject: [PATCH] Add sort option for schedule:list (#45198) * Add sort option for schedule:list Currently, in order to see what's schedule will be run, I need to run schedule:list and eyeball the list to find the relevant ones. This PR adds an option to sort the list by the next due date. Before: ``` php artisan schedule:list 0 * * * * php artisan command-one .......... Next Due: 55 minutes from now 15 * * * * php artisan command-three ........ Next Due: 10 minutes from now 17 * * * * php artisan command-two .......... Next Due: 12 minutes from now ``` After: ``` php artisan schedule:list 15 * * * * php artisan command-three ........ Next Due: 10 minutes from now 17 * * * * php artisan command-two .......... Next Due: 12 minutes from now 0 * * * * php artisan command-one .......... Next Due: 55 minutes from now ``` * Add a test * fix styling * need to actually use the flag in the test * formatting Co-authored-by: Taylor Otwell --- .../Scheduling/ScheduleListCommand.php | 44 ++++++++++++++++--- .../Scheduling/ScheduleListCommandTest.php | 24 ++++++++++ 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/src/Illuminate/Console/Scheduling/ScheduleListCommand.php b/src/Illuminate/Console/Scheduling/ScheduleListCommand.php index 04ca6a09bbec..03a2b23f6e30 100644 --- a/src/Illuminate/Console/Scheduling/ScheduleListCommand.php +++ b/src/Illuminate/Console/Scheduling/ScheduleListCommand.php @@ -21,7 +21,10 @@ class ScheduleListCommand extends Command * * @var string */ - protected $signature = 'schedule:list {--timezone= : The timezone that times should be displayed in}'; + protected $signature = 'schedule:list + {--timezone= : The timezone that times should be displayed in} + {--next : Sort the listed tasks by their next due date} + '; /** * The name of the console command. @@ -39,7 +42,7 @@ class ScheduleListCommand extends Command * * @var string */ - protected $description = 'List the scheduled commands'; + protected $description = 'List all scheduled tasks'; /** * The terminal width resolver callback. @@ -72,6 +75,8 @@ public function handle(Schedule $schedule) $timezone = new DateTimeZone($this->option('timezone') ?? config('app.timezone')); + $events = $this->sortEvents($events, $timezone); + $events = $events->map(function ($event) use ($terminalWidth, $expressionSpacing, $timezone) { $expression = $this->formatCronExpression($event->expression, $expressionSpacing); @@ -98,10 +103,7 @@ public function handle(Schedule $schedule) $nextDueDateLabel = 'Next Due:'; - $nextDueDate = Carbon::create((new CronExpression($event->expression)) - ->getNextRunDate(Carbon::now()->setTimezone($event->timezone)) - ->setTimezone($timezone) - ); + $nextDueDate = $this->getNextDueDateForEvent($event, $timezone); $nextDueDate = $this->output->isVerbose() ? $nextDueDate->format('Y-m-d H:i:s P') @@ -150,6 +152,36 @@ private function getCronExpressionSpacing($events) return collect($rows[0] ?? [])->keys()->map(fn ($key) => $rows->max($key)); } + /** + * Sorts the events by due date if option set. + * + * @param \Illuminate\Support\Collection $events + * @param \DateTimeZone $timezone + * @return \Illuminate\Support\Collection + */ + private function sortEvents(\Illuminate\Support\Collection $events, DateTimeZone $timezone) + { + return $this->option('next') + ? $events->sortBy(fn ($event) => $this->getNextDueDateForEvent($event, $timezone)) + : $events; + } + + /** + * Get the next due date for an event. + * + * @param \Illuminate\Console\Scheduling\Event $event + * @param \DateTimeZone $timezone + * @return \Illuminate\Support\Carbon + */ + private function getNextDueDateForEvent($event, DateTimeZone $timezone) + { + return Carbon::create( + (new CronExpression($event->expression)) + ->getNextRunDate(Carbon::now()->setTimezone($event->timezone)) + ->setTimezone($timezone) + ); + } + /** * Formats the cron expression based on the spacing provided. * diff --git a/tests/Integration/Console/Scheduling/ScheduleListCommandTest.php b/tests/Integration/Console/Scheduling/ScheduleListCommandTest.php index 64d024a37fdd..efa6f60d8b20 100644 --- a/tests/Integration/Console/Scheduling/ScheduleListCommandTest.php +++ b/tests/Integration/Console/Scheduling/ScheduleListCommandTest.php @@ -52,6 +52,30 @@ public function testDisplaySchedule() ->expectsOutput(' * * * * * Closure at: '.$closureFilePath.':'.$closureLineNumber.' Next Due: 1 minute from now'); } + public function testDisplayScheduleWithSort() + { + $this->schedule->command(FooCommand::class)->quarterly(); + $this->schedule->command('inspire')->twiceDaily(14, 18); + $this->schedule->command('foobar', ['a' => 'b'])->everyMinute(); + $this->schedule->job(FooJob::class)->everyMinute(); + $this->schedule->command('inspire')->cron('0 9,17 * * *'); + $this->schedule->command('inspire')->cron("0 10\t* * *"); + + $this->schedule->call(fn () => '')->everyMinute(); + $closureLineNumber = __LINE__ - 1; + $closureFilePath = __FILE__; + + $this->artisan(ScheduleListCommand::class, ['--next' => true]) + ->assertSuccessful() + ->expectsOutput(' * * * * * php artisan foobar a='.ProcessUtils::escapeArgument('b').' ... Next Due: 1 minute from now') + ->expectsOutput(' * * * * * Illuminate\Tests\Integration\Console\Scheduling\FooJob Next Due: 1 minute from now') + ->expectsOutput(' * * * * * Closure at: '.$closureFilePath.':'.$closureLineNumber.' Next Due: 1 minute from now') + ->expectsOutput(' 0 9,17 * * * php artisan inspire ......... Next Due: 9 hours from now') + ->expectsOutput(' 0 10 * * * php artisan inspire ........ Next Due: 10 hours from now') + ->expectsOutput(' 0 14,18 * * * php artisan inspire ........ Next Due: 14 hours from now') + ->expectsOutput(' 0 0 1 1-12/3 * php artisan foo:command .... Next Due: 3 months from now'); + } + public function testDisplayScheduleInVerboseMode() { $this->schedule->command(FooCommand::class)->everyMinute();