diff --git a/resources/views/components/schedule-item.blade.php b/resources/views/components/schedule-item.blade.php
new file mode 100644
index 00000000..45edd166
--- /dev/null
+++ b/resources/views/components/schedule-item.blade.php
@@ -0,0 +1,69 @@
+@use('Cachet\Enums\ScheduleStatusEnum')
+@props([
+ 'schedule',
+])
+
+
+
+
$schedule->updates->isNotEmpty(),
+ 'rounded-lg' => $schedule->updates->isEmpty(),
+ ])>
+ @if ($schedule->components()->exists())
+
+ {{ $schedule->components->pluck('name')->join(', ', ' and ') }}
+
+ @endif
+
+
+
+
+
+ @auth
+
+
+
+ @endauth
+
+
+ {{ $schedule->timestamp->diffForHumans() }} —
+
+
+
+
+
+
+
+
+
+
+
+
+ @foreach ($schedule->updates as $update)
+
+
+
{{ $update->status->getLabel() }}
+
+ {{ $update->created_at->diffForHumans() }} —
+
+
{!! $update->formattedMessage() !!}
+
+ @endforeach
+
+
+
+
+ {{ $schedule->timestamp->diffForHumans() }} —
+
+
{!! $schedule->formattedMessage() !!}
+
+
+
+
diff --git a/resources/views/components/schedule-update-status.blade.php b/resources/views/components/schedule-update-status.blade.php
new file mode 100644
index 00000000..64590b46
--- /dev/null
+++ b/resources/views/components/schedule-update-status.blade.php
@@ -0,0 +1,12 @@
+
style([
+ Illuminate\Support\Arr::toCssStyles([
+ \Filament\Support\get_color_css_variables(
+ $color,
+ shades: [200, 400, 700, 900],
+ ),
+ ]),
+])->merge(['title' => $title]) }}>
+
+ @svg($icon, 'size-5')
+
+
diff --git a/resources/views/components/timeline-badge.blade.php b/resources/views/components/timeline-badge.blade.php
new file mode 100644
index 00000000..2322b310
--- /dev/null
+++ b/resources/views/components/timeline-badge.blade.php
@@ -0,0 +1,15 @@
+
style([
+ Illuminate\Support\Arr::toCssStyles([
+ \Filament\Support\get_color_css_variables(
+ $color,
+ shades: [200, 400, 700, 900],
+ ),
+ ]),
+])->merge(['title' => $label]) }}>
+
+ @svg($icon, 'size-5')
+
+
+{{--
--}}
+{{-- {{ $label }}--}}
+{{-- --}}
diff --git a/src/Collections/TimelineCollection.php b/src/Collections/TimelineCollection.php
new file mode 100644
index 00000000..fcac9dc9
--- /dev/null
+++ b/src/Collections/TimelineCollection.php
@@ -0,0 +1,25 @@
+sortByDesc(fn (Sequencable $item) => $item->getSequenceTimestamp())
+ ->groupBy(fn (Sequencable $item) => $item->getSequenceTimestamp()->toDateString())
+ ->union(
+ // Back-fill any missing dates...
+ collect($endDate->toPeriod($startDate))
+ ->keyBy(fn ($period) => $period->toDateString())
+ ->map(fn ($period) => collect())
+ )
+ ->when($onlyDisruptedDays, fn ($collection) => $collection->filter(fn ($items) => $items->isNotEmpty()))
+ ->sortKeysDesc();
+ }
+}
diff --git a/src/Contracts/Support/Sequencable.php b/src/Contracts/Support/Sequencable.php
new file mode 100644
index 00000000..6b9cf061
--- /dev/null
+++ b/src/Contracts/Support/Sequencable.php
@@ -0,0 +1,10 @@
+|static unresolved()
* @method static Builder
|static stickied()
*/
-class Incident extends Model
+class Incident extends Model implements Sequencable
{
/** @use HasFactory */
use HasFactory;
@@ -181,6 +183,12 @@ protected function timestamp(): Attribute
);
}
+ public function getSequenceTimestamp(): CarbonInterface
+ {
+ return $this->timestamp;
+ }
+
+
/**
* Determine the latest status of the incident.
*
diff --git a/src/Models/Schedule.php b/src/Models/Schedule.php
index 4b95f7bb..a9d0b1a4 100644
--- a/src/Models/Schedule.php
+++ b/src/Models/Schedule.php
@@ -2,8 +2,11 @@
namespace Cachet\Models;
+use Cachet\Contracts\Support\Sequencable;
use Cachet\Database\Factories\ScheduleFactory;
use Cachet\Enums\ScheduleStatusEnum;
+use Cachet\Filament\Resources\ScheduleResource;
+use Carbon\CarbonInterface;
use Cachet\QueryBuilders\ScheduleBuilder;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
@@ -35,7 +38,7 @@
* @method static \Cachet\QueryBuilders\ScheduleBuilder inTheFuture()
* @method static \Cachet\QueryBuilders\ScheduleBuilder inThePast()
*/
-class Schedule extends Model
+class Schedule extends Model implements Sequencable
{
/** @use HasFactory */
use HasFactory;
@@ -108,6 +111,41 @@ public function formattedMessage(): string
return Str::of($this->message)->markdown();
}
+ /**
+ * Determine the latest status of the incident.
+ *
+ * @return Attribute
+ */
+ protected function latestStatus(): Attribute
+ {
+ return Attribute::make(
+ get: function ($value) {
+ return $this->updates()->latest()->first()->status ?? $this->status;
+ }
+ );
+ }
+
+ /**
+ * @return Attribute
+ */
+ protected function timestamp(): Attribute
+ {
+ return Attribute::make(
+ get: fn () => $this->completed_at ?? $this->scheduled_at
+ );
+ }
+
+ public function getSequenceTimestamp(): CarbonInterface
+ {
+ return $this->timestamp;
+ }
+
+ public function filamentDashboardEditUrl(): string
+ {
+ return ScheduleResource::getUrl(name: 'edit', parameters: ['record' => $this->id]);
+ }
+
+
/**
* Create a new Eloquent query builder for the model.
*
diff --git a/src/View/Components/IncidentTimeline.php b/src/View/Components/IncidentTimeline.php
index 9cd69961..e1ad9b8d 100644
--- a/src/View/Components/IncidentTimeline.php
+++ b/src/View/Components/IncidentTimeline.php
@@ -2,7 +2,9 @@
namespace Cachet\View\Components;
+use Cachet\Collections\TimelineCollection;
use Cachet\Models\Incident;
+use Cachet\Models\Schedule;
use Cachet\Settings\AppSettings;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
@@ -26,7 +28,7 @@ public function render(): View
$endDate = $startDate->clone()->subDays($incidentDays);
return view('cachet::components.incident-timeline', [
- 'incidents' => $this->incidents(
+ 'incidents' => $this->timeline(
$startDate,
$endDate,
$this->appSettings->only_disrupted_days
@@ -41,6 +43,14 @@ public function render(): View
]);
}
+
+ private function timeline(Carbon $startDate, Carbon $endDate, bool $onlyDisruptedDays = false): Collection
+ {
+ return TimelineCollection::make($this->incidents($startDate, $endDate, $onlyDisruptedDays))
+ ->merge($this->schedules($startDate, $endDate, $onlyDisruptedDays))
+ ->getSorted($startDate, $endDate, $onlyDisruptedDays);
+ }
+
/**
* Fetch the incidents that occurred between the given start and end date.
* Incidents will be grouped by days.
@@ -81,16 +91,43 @@ private function incidents(Carbon $startDate, Carbon $endDate, bool $onlyDisrupt
});
});
})
- ->get()
- ->sortByDesc(fn (Incident $incident) => $incident->timestamp)
- ->groupBy(fn (Incident $incident) => $incident->timestamp->toDateString())
- ->union(
- // Back-fill any missing dates...
- collect($endDate->toPeriod($startDate))
- ->keyBy(fn ($period) => $period->toDateString())
- ->map(fn ($period) => collect())
- )
- ->when($onlyDisruptedDays, fn ($collection) => $collection->filter(fn ($incidents) => $incidents->isNotEmpty()))
- ->sortKeysDesc();
+ ->get();
+ }
+ private function schedules(Carbon $startDate, Carbon $endDate, bool $onlyDisruptedDays = false): Collection
+ {
+ return Schedule::query()
+ ->with([
+ 'components',
+ 'updates' => fn ($query) => $query->orderByDesc('created_at'),
+ ])
+ ->when($this->appSettings->recent_incidents_only, function ($query) {
+ $query->where(function ($query) {
+ $query->whereDate(
+ 'completed_at',
+ '>',
+ Carbon::now()->subDays($this->appSettings->recent_incidents_days)->format('Y-m-d')
+ )->orWhere(function ($query) {
+ $query->whereNull('completed_at')->whereDate(
+ 'scheduled_at',
+ '>',
+ Carbon::now()->subDays($this->appSettings->recent_incidents_days)->format('Y-m-d')
+ );
+ });
+ });
+ })
+ ->when(! $this->appSettings->recent_incidents_only, function ($query) use ($endDate, $startDate) {
+ $query->where(function (Builder $query) use ($endDate, $startDate) {
+ $query->whereBetween('completed_at', [
+ $endDate->startOfDay()->toDateTimeString(),
+ $startDate->endofDay()->toDateTimeString(),
+ ])->orWhere(function (Builder $query) use ($endDate, $startDate) {
+ $query->whereNull('completed_at')->whereBetween('scheduled_at', [
+ $endDate->startOfDay()->toDateTimeString(),
+ $startDate->endofDay()->toDateTimeString(),
+ ]);
+ });
+ });
+ })
+ ->get();
}
}
diff --git a/src/View/Components/ScheduleUpdateStatus.php b/src/View/Components/ScheduleUpdateStatus.php
new file mode 100644
index 00000000..09462190
--- /dev/null
+++ b/src/View/Components/ScheduleUpdateStatus.php
@@ -0,0 +1,31 @@
+ $this->status?->getLabel(),
+ 'color' => $this->status?->getColor() ?? 'gray',
+ 'icon' => $this->status?->getIcon(),
+ ]);
+ }
+}
diff --git a/src/View/Components/TimelineBadge.php b/src/View/Components/TimelineBadge.php
new file mode 100644
index 00000000..bbae1631
--- /dev/null
+++ b/src/View/Components/TimelineBadge.php
@@ -0,0 +1,24 @@
+ $this->color ?? 'gray',
+ 'label' => $this->label,
+ 'icon' => $this->icon,
+ ]);
+ }
+}