104 lines
3.1 KiB
PHP
104 lines
3.1 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Models\Concerns\HasUuid;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
|
|
/**
|
|
* A disruption, written down.
|
|
*
|
|
* The point of the record is that it outlives the disruption. "Alle Dienste in
|
|
* Betrieb" the morning after an outage is true and useless to somebody who was
|
|
* locked out the evening before; what answers them is the timeline — when it
|
|
* was noticed, what it turned out to be, when it was fixed.
|
|
*/
|
|
class Incident extends Model
|
|
{
|
|
/** @use HasFactory<\Database\Factories\IncidentFactory> */
|
|
use HasFactory, HasUuid;
|
|
|
|
/** How the incident colours the components it touches. */
|
|
public const IMPACT_DEGRADED = 'degraded';
|
|
|
|
public const IMPACT_DOWN = 'down';
|
|
|
|
public const IMPACT_MAINTENANCE = 'maintenance';
|
|
|
|
/** The stages of an update, in the order they normally happen. */
|
|
public const STATUSES = ['investigating', 'identified', 'monitoring', 'resolved'];
|
|
|
|
protected $fillable = [
|
|
'title', 'impact', 'components', 'started_at', 'resolved_at', 'published_at', 'created_by',
|
|
];
|
|
|
|
protected function casts(): array
|
|
{
|
|
return [
|
|
'components' => 'array',
|
|
'started_at' => 'datetime',
|
|
'resolved_at' => 'datetime',
|
|
'published_at' => 'datetime',
|
|
];
|
|
}
|
|
|
|
public function updates(): HasMany
|
|
{
|
|
return $this->hasMany(IncidentUpdate::class)->orderByDesc('created_at')->orderByDesc('id');
|
|
}
|
|
|
|
/** Only what a visitor may see. Drafts are working notes, not statements. */
|
|
public function scopePublished(Builder $query): Builder
|
|
{
|
|
return $query->whereNotNull('published_at');
|
|
}
|
|
|
|
public function scopeOngoing(Builder $query): Builder
|
|
{
|
|
return $query->whereNull('resolved_at');
|
|
}
|
|
|
|
public function isResolved(): bool
|
|
{
|
|
return $this->resolved_at !== null;
|
|
}
|
|
|
|
public function isPublished(): bool
|
|
{
|
|
return $this->published_at !== null;
|
|
}
|
|
|
|
/**
|
|
* The stage the incident is at, taken from its newest update.
|
|
*
|
|
* Derived rather than stored: a stored column and an update log are two
|
|
* records of the same fact, and they drift the first time somebody posts
|
|
* an update without touching the column.
|
|
*/
|
|
public function stage(): string
|
|
{
|
|
if ($this->isResolved()) {
|
|
return 'resolved';
|
|
}
|
|
|
|
return $this->updates->first()?->status ?? 'investigating';
|
|
}
|
|
|
|
/**
|
|
* How long it lasted, in whole minutes; null while it is still running.
|
|
*
|
|
* Start first, end second. diffInMinutes is SIGNED — written the other way
|
|
* round it produced "Dauer -86 Minuten" on the public page, which is the
|
|
* kind of detail that costs a status page the only thing it has.
|
|
*/
|
|
public function durationMinutes(): ?int
|
|
{
|
|
return $this->resolved_at === null
|
|
? null
|
|
: (int) $this->started_at->diffInMinutes($this->resolved_at);
|
|
}
|
|
}
|