*/ 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); } }