From 9336ca0baade4ca9dd5fceaeb555f9ead59ab91b Mon Sep 17 00:00:00 2001 From: Ashwin Chandran Date: Thu, 13 Aug 2026 22:19:49 +0200 Subject: [PATCH 1/2] Update last updated timestamp on statuspage to include incident and schedule activity Co-authored-by: Claude --- src/Status.php | 26 +++++++++++++++---- tests/Unit/StatusTest.php | 54 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/src/Status.php b/src/Status.php index 32c0f795..449292b5 100644 --- a/src/Status.php +++ b/src/Status.php @@ -7,9 +7,12 @@ use Cachet\Enums\SystemStatusEnum; use Cachet\Models\Component; use Cachet\Models\Incident; +use Cachet\Models\Schedule; +use Cachet\Models\Update; use Cachet\Settings\AppSettings; use Carbon\CarbonInterface; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Support\Facades\Date; class Status { @@ -104,14 +107,27 @@ public function incidents(): object } /** - * Get the most recent update timestamp across enabled components. + * Get the most recent guest-visible activity timestamp across components, + * incidents, incident updates and schedules. */ public function lastUpdated(): ?CarbonInterface { - return Component::query() - ->enabled() - ->latest('updated_at') - ->first(['updated_at'])?->updated_at; + return collect([ + Component::query()->enabled()->max('updated_at'), + Incident::query()->viewableBy(false)->max('updated_at'), + Schedule::query()->published()->max('updated_at'), + Update::query() + ->whereHasMorph('updateable', [Incident::class, Schedule::class], function (Builder $query, string $type): void { + match ($type) { + Incident::class => $query->viewableBy(false), + Schedule::class => $query->published(), + }; + }) + ->max('updated_at'), + ]) + ->filter() + ->map(fn ($timestamp): CarbonInterface => Date::parse($timestamp)) + ->max(); } /** diff --git a/tests/Unit/StatusTest.php b/tests/Unit/StatusTest.php index fbcd06ce..7fee3e35 100644 --- a/tests/Unit/StatusTest.php +++ b/tests/Unit/StatusTest.php @@ -121,6 +121,60 @@ ->toEqual($component->updated_at); }); +it('considers incidents, incident updates and schedules when calculating the last updated timestamp', function () { + expect((new Status)->lastUpdated())->toBeNull(); + + Component::factory()->create([ + 'enabled' => true, + 'updated_at' => now()->subWeek(), + ]); + $incident = Incident::factory()->create([ + 'visible' => ResourceVisibilityEnum::guest, + 'updated_at' => now()->subDay(), + ]); + + expect((new Status)->lastUpdated())->toEqual($incident->updated_at); + + $update = Update::factory()->forIncident($incident)->create([ + 'status' => null, + 'created_at' => now()->subHour(), + 'updated_at' => now()->subHour(), + ]); + + expect((new Status)->lastUpdated())->toEqual($update->updated_at); + + $schedule = Schedule::factory()->create([ + 'updated_at' => now()->subMinute(), + ]); + + expect((new Status)->lastUpdated())->toEqual($schedule->updated_at); +}); + +it('ignores activity hidden from guests when calculating the last updated timestamp', function () { + $component = Component::factory()->create([ + 'enabled' => true, + 'updated_at' => now()->subWeek(), + ]); + $hidden = Incident::factory()->create([ + 'visible' => ResourceVisibilityEnum::authenticated, + 'updated_at' => now()->subMinute(), + ]); + Update::factory()->forIncident($hidden)->create([ + 'status' => null, + 'created_at' => now()->subMinute(), + 'updated_at' => now()->subMinute(), + ]); + Incident::factory()->scheduled()->create([ + 'visible' => ResourceVisibilityEnum::guest, + 'updated_at' => now()->subMinute(), + ]); + Schedule::factory()->scheduled()->create([ + 'updated_at' => now()->subMinute(), + ]); + + expect((new Status)->lastUpdated())->toEqual($component->updated_at); +}); + it('excludes disabled components from component overview', function () { Component::factory()->create([ 'status' => ComponentStatusEnum::operational->value, From 758e598c75b08ee7835c881c723b58b687753ad2 Mon Sep 17 00:00:00 2001 From: Ashwin Chandran Date: Thu, 13 Aug 2026 22:30:34 +0200 Subject: [PATCH 2/2] Satisfy PHPStan --- src/Status.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Status.php b/src/Status.php index 449292b5..1b82c298 100644 --- a/src/Status.php +++ b/src/Status.php @@ -12,6 +12,7 @@ use Cachet\Settings\AppSettings; use Carbon\CarbonInterface; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Support\Facades\Date; class Status @@ -117,12 +118,12 @@ public function lastUpdated(): ?CarbonInterface Incident::query()->viewableBy(false)->max('updated_at'), Schedule::query()->published()->max('updated_at'), Update::query() - ->whereHasMorph('updateable', [Incident::class, Schedule::class], function (Builder $query, string $type): void { - match ($type) { - Incident::class => $query->viewableBy(false), - Schedule::class => $query->published(), - }; - }) + ->where('updateable_type', Relation::getMorphAlias(Incident::class)) + ->whereIn('updateable_id', Incident::query()->viewableBy(false)->select('id')) + ->max('updated_at'), + Update::query() + ->where('updateable_type', Relation::getMorphAlias(Schedule::class)) + ->whereIn('updateable_id', Schedule::query()->published()->select('id')) ->max('updated_at'), ]) ->filter()