From e87ceb60ba959c467ab28fdec36ca58885c8f046 Mon Sep 17 00:00:00 2001 From: ignacionelson Date: Mon, 17 Aug 2026 20:36:01 -0300 Subject: [PATCH] Say what the update check found, not just that it ran MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Scheduler screen printed "Check for updates · Succeeded · —" and left it there. What it found — the whole reason that job exists — was in the settings, which that screen never read. Somebody opening it to ask "is there a new version?" got the answer to "did the job run?" The Message column now carries "Up to date" or the version that is waiting. A failure's own message still wins: what the last successful run found is not the answer to why this one broke. Joined at render time rather than recorded by the command, because Laravel's scheduler fires its finished event after the command returns and overwrites whatever the command wrote — which is exactly why that column was empty in the first place. Reading the settings instead also keeps the line true when the new Check now button did the work rather than the nightly run. Co-Authored-By: Claude Opus 5 --- .../SchedulerMonitoringController.php | 37 ++++++++++++++++++- .../js/pages/system/settings/scheduler.tsx | 7 +++- .../Platform/SchedulerMonitoringTest.php | 37 +++++++++++++++++++ 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/app/Modules/Platform/Http/Controllers/SchedulerMonitoringController.php b/app/Modules/Platform/Http/Controllers/SchedulerMonitoringController.php index 8efb786b..6c8210d4 100644 --- a/app/Modules/Platform/Http/Controllers/SchedulerMonitoringController.php +++ b/app/Modules/Platform/Http/Controllers/SchedulerMonitoringController.php @@ -6,6 +6,9 @@ namespace App\Modules\Platform\Http\Controllers; use App\Http\Controllers\Controller; use App\Modules\Platform\Scheduling\ScheduledTaskRun; +use App\Modules\Platform\Settings\Setting; +use App\Modules\Platform\Settings\Settings; +use App\Modules\Platform\Updates\LatestReleaseInfo; use App\Support\Pagination; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; @@ -48,13 +51,16 @@ class SchedulerMonitoringController extends Controller public function __construct( private readonly FailedJobProviderInterface $failer, + private readonly LatestReleaseInfo $latestRelease, + private readonly Settings $settings, ) {} public function index(Request $request): Response|RedirectResponse { $runs = ScheduledTaskRun::query()->get()->keyBy('command'); + $details = $this->details(); - $tasks = collect(self::KNOWN_COMMANDS)->map(function (string $label, string $command) use ($runs): array { + $tasks = collect(self::KNOWN_COMMANDS)->map(function (string $label, string $command) use ($runs, $details): array { $run = $runs->get($command); return [ @@ -62,6 +68,7 @@ class SchedulerMonitoringController extends Controller 'label' => $label, 'status' => $run?->status->value, 'message' => $run?->message, + 'detail' => $details[$command] ?? null, 'duration_ms' => $run?->duration_ms, 'ran_at' => $run?->ran_at?->toIso8601String(), ]; @@ -137,4 +144,32 @@ class SchedulerMonitoringController extends Controller return back()->with('success', __('All failed jobs deleted.')); } + + /** + * What a task actually found, for the tasks that find something. + * + * The run rows answer "did it work"; they cannot answer "and what did + * it say", because Laravel's ScheduledTaskFinished event fires after + * the command returns and carries no output — which is why a + * successful check for updates has always shown an empty Message. + * Joined here at render time instead, from what the command itself + * wrote to the settings, so the line stays true whether the daily job + * or somebody pressing the button did the work. + * + * @return array + */ + private function details(): array + { + $release = $this->latestRelease->current(); + $checkedAt = $this->settings->get(Setting::LatestVersionCheckedAt); + $everChecked = is_string($checkedAt) && $checkedAt !== ''; + + $updates = match (true) { + $release !== null => (string) __(':version is available', ['version' => $release['version']]), + $everChecked => (string) __('Up to date'), + default => null, + }; + + return array_filter(['projectsend:check-for-updates' => $updates]); + } } diff --git a/resources/js/pages/system/settings/scheduler.tsx b/resources/js/pages/system/settings/scheduler.tsx index 5088d762..84e82d7c 100644 --- a/resources/js/pages/system/settings/scheduler.tsx +++ b/resources/js/pages/system/settings/scheduler.tsx @@ -16,6 +16,8 @@ interface ScheduledTask { label: string; status: 'success' | 'failed' | null; message: string | null; + /** What the task found, for the tasks that find something. Never set on a failure. */ + detail: string | null; duration_ms: number | null; ran_at: string | null; } @@ -130,7 +132,10 @@ export default function SchedulerSettings({ {task.duration_ms !== null ? `${task.duration_ms} ms` : '—'} - {task.message ?? '—'} + {/* A failure's own message wins: what the + last successful run found is not the + answer to why this one broke. */} + {task.message ?? task.detail ?? '—'} ))} diff --git a/tests/Feature/Platform/SchedulerMonitoringTest.php b/tests/Feature/Platform/SchedulerMonitoringTest.php index d951c4a7..e5d680b0 100644 --- a/tests/Feature/Platform/SchedulerMonitoringTest.php +++ b/tests/Feature/Platform/SchedulerMonitoringTest.php @@ -7,6 +7,8 @@ use App\Modules\Identity\Models\Role; use App\Modules\Platform\Capabilities\Edition; use App\Modules\Platform\Scheduling\ScheduledTaskRun; use App\Modules\Platform\Scheduling\TaskRunStatus; +use App\Modules\Platform\Settings\Setting; +use App\Modules\Platform\Settings\Settings; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; use Illuminate\Testing\TestResponse; @@ -167,3 +169,38 @@ test('staff without edit_settings cannot access the scheduler page', function () $this->actingAs($staffer)->get('/system/settings/scheduler')->assertForbidden(); }); + +test('the update check reports what it found, not just that it ran', function () { + config()->set('projectsend.version', '2.0.0'); + $settings = app(Settings::class); + $settings->set(Setting::LatestVersionCheckedAt, now()->toIso8601String()); + $settings->set(Setting::LatestKnownVersion, '2.5.0'); + + $response = $this->actingAs($this->admin)->get('/system/settings/scheduler'); + $tasks = collect(schedulerPageProps($response)['tasks'])->keyBy('command'); + + expect($tasks->get('projectsend:check-for-updates')['detail'])->toContain('2.5.0'); +}); + +test('a check that found nothing newer says so', function () { + config()->set('projectsend.version', '2.0.0'); + $settings = app(Settings::class); + $settings->set(Setting::LatestVersionCheckedAt, now()->toIso8601String()); + $settings->set(Setting::LatestKnownVersion, '2.0.0'); + + $response = $this->actingAs($this->admin)->get('/system/settings/scheduler'); + $tasks = collect(schedulerPageProps($response)['tasks'])->keyBy('command'); + + expect($tasks->get('projectsend:check-for-updates')['detail'])->toBe('Up to date'); +}); + +test('a check that has never run has nothing to report', function () { + $settings = app(Settings::class); + $settings->set(Setting::LatestVersionCheckedAt, ''); + $settings->set(Setting::LatestKnownVersion, ''); + + $response = $this->actingAs($this->admin)->get('/system/settings/scheduler'); + $tasks = collect(schedulerPageProps($response)['tasks'])->keyBy('command'); + + expect($tasks->get('projectsend:check-for-updates')['detail'])->toBeNull(); +});