diff --git a/src/Status.php b/src/Status.php index 32c0f795..1b82c298 100644 --- a/src/Status.php +++ b/src/Status.php @@ -7,9 +7,13 @@ 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\Database\Eloquent\Relations\Relation; +use Illuminate\Support\Facades\Date; class Status { @@ -104,14 +108,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() + ->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() + ->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,