Skip to content

Commit c516deb

Browse files
committed
Add schedule with status completed to incident timeline cachethq#93
1 parent 7a87a21 commit c516deb

File tree

4 files changed

+106
-4
lines changed

4 files changed

+106
-4
lines changed

resources/views/components/incident-timeline.blade.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
<div class="flex flex-col gap-14 w-full">
1313
@forelse ($incidents as $date => $incident)
14-
<x-cachet::incident :date="$date" :incidents="$incident" />
14+
<x-cachet::incident :date="$date" :incidents="$incident" :schedules="$schedules" />
1515
@empty
1616
<div class="text-zinc-500 dark:text-zinc-400 text-center">
1717
{{ __('No incidents reported between :from and :to.', ['from' => $from, 'to' => $to]) }}

resources/views/components/incident.blade.php

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22
@props([
33
'date',
44
'incidents',
5+
'schedules',
56
])
67

8+
@php($empty = true)
9+
710
<div class="relative flex flex-col gap-5" x-data="{ forDate: new Date(@js($date)) }">
811
<h3 class="text-xl font-semibold"><time datetime="{{ $date }}" x-text="forDate.toLocaleDateString()"></time></h3>
9-
@forelse($incidents as $incident)
12+
@foreach($incidents as $incident)
13+
@php($empty = false)
1014
<div x-data="{ timestamp: new Date(@js($incident->timestamp)) }" class="bg-white border divide-y rounded-lg ml-9 dark:divide-zinc-700 dark:border-zinc-700 dark:bg-zinc-800">
1115
<div @class([
1216
'flex flex-col bg-zinc-50 p-4 dark:bg-zinc-900 gap-2',
@@ -68,9 +72,57 @@
6872
</div>
6973
</div>
7074
</div>
71-
@empty
75+
@endforeach
76+
@foreach($schedules[$date] as $schedule)
77+
@php($empty = false)
78+
<div x-data="{ timestamp: new Date(@js($schedule->completed_at)) }" class="bg-white border divide-y rounded-lg ml-9 dark:divide-zinc-700 dark:border-zinc-700 dark:bg-zinc-800">
79+
<div @class([
80+
'flex flex-col bg-zinc-50 p-4 dark:bg-zinc-900 gap-2',
81+
'rounded-lg',
82+
])>
83+
@if ($schedule->components()->exists())
84+
<div class="text-xs font-medium">
85+
{{ $schedule->components()->pluck('name')->join(', ', ' and ') }}
86+
</div>
87+
@endif
88+
<div class="flex flex-col sm:flex-row justify-between gap-2 flex-col-reverse items-start sm:items-center">
89+
<div class="flex flex-col flex-1">
90+
<div class="flex gap-2 items-center">
91+
<h3 class="max-w-full text-base font-semibold break-words sm:text-xl">
92+
{{ $schedule->name}}
93+
</h3>
94+
@auth
95+
<a href="{{ $schedule->filamentDashboardEditUrl() }}" class="underline text-right text-sm text-zinc-500 hover:text-zinc-400 dark:text-zinc-400 dark:hover:text-zinc-300" title="{{ __('Edit Incident') }}">
96+
<x-heroicon-m-pencil-square class="size-4" />
97+
</a>
98+
@endauth
99+
</div>
100+
<span class="text-xs text-zinc-500 dark:text-zinc-400">
101+
{{ $schedule->completed_at->diffForHumans() }} — <time datetime="{{ $schedule->completed_at->toW3cString() }}" x-text="timestamp.toLocaleString()"></time>
102+
</span>
103+
</div>
104+
<div class="flex justify-start sm:justify-end">
105+
<x-cachet::badge :status="$schedule->status" />
106+
</div>
107+
</div>
108+
</div>
109+
<div class="relative">
110+
<div class="absolute inset-y-0 -left-9">
111+
<div class="ml-3.5 h-full border-l-2 border-dashed dark:border-zinc-700"></div>
112+
<div class="absolute inset-x-0 top-0 w-full h-24 bg-gradient-to-t from-transparent to-zinc-50 dark:from-transparent dark:to-zinc-900"></div>
113+
<div class="absolute inset-x-0 bottom-0 w-full h-24 bg-gradient-to-b from-transparent to-zinc-50 dark:from-transparent dark:to-zinc-900"></div>
114+
</div>
115+
<div class="flex flex-col px-4 divide-y dark:divide-zinc-700">
116+
<div class="relative py-4" x-data="{ timestamp: new Date(@js($schedule->completed_at)) }">
117+
<div class="prose-sm md:prose prose-zinc dark:prose-invert prose-a:text-primary-500 prose-a:underline prose-p:leading-normal">{!! $schedule->formattedMessage() !!}</div>
118+
</div>
119+
</div>
120+
</div>
121+
</div>
122+
@endforeach
123+
@if($empty)
72124
<div class="mt-1 prose-sm md:prose prose-zinc dark:prose-invert prose-a:text-primary-500 prose-a:underline">
73125
{{ __('No incidents reported.') }}
74126
</div>
75-
@endforelse
127+
@endif
76128
</div>

src/Models/Schedule.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
namespace Cachet\Models;
44

55
use Cachet\Enums\ScheduleStatusEnum;
6+
use Cachet\Filament\Resources\ScheduleResource;
67
use Illuminate\Database\Eloquent\Builder;
8+
use Illuminate\Database\Eloquent\Casts\Attribute;
79
use Illuminate\Database\Eloquent\Factories\HasFactory;
810
use Illuminate\Database\Eloquent\Model;
911
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@@ -93,4 +95,17 @@ public function scopeCompletedPreviously(Builder $query): Builder
9395
return $query->where('status', '=', ScheduleStatusEnum::complete)
9496
->where('completed_at', '<=', Carbon::now());
9597
}
98+
99+
/**
100+
* Get the URL to the schedule page within the dashboard.
101+
*/
102+
public function filamentDashboardEditUrl(): string
103+
{
104+
return ScheduleResource::getUrl(name: 'edit', parameters: ['record' => $this->id]);
105+
}
106+
107+
public function timestamp(): Attribute
108+
{
109+
return Attribute::get(fn () => $this->completed_at ?: $this->scheduled_at);
110+
}
96111
}

src/View/Components/IncidentTimeline.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Cachet\View\Components;
44

55
use Cachet\Models\Incident;
6+
use Cachet\Models\Schedule;
67
use Cachet\Settings\AppSettings;
78
use Illuminate\Contracts\View\View;
89
use Illuminate\Database\Eloquent\Builder;
@@ -29,6 +30,11 @@ public function render(): View
2930
$endDate,
3031
$this->appSettings->only_disrupted_days
3132
),
33+
'schedules' => $this->schedules(
34+
$startDate,
35+
$endDate,
36+
$this->appSettings->only_disrupted_days
37+
),
3238
'from' => $startDate->toDateString(),
3339
'to' => $endDate->toDateString(),
3440
'nextPeriodFrom' => $startDate->clone()->subDays($incidentDays + 1)->toDateString(),
@@ -73,4 +79,33 @@ private function incidents(Carbon $startDate, Carbon $endDate, bool $onlyDisrupt
7379
->when($onlyDisruptedDays, fn ($collection) => $collection->filter(fn ($incidents) => $incidents->isNotEmpty()))
7480
->sortKeysDesc();
7581
}
82+
83+
/**
84+
* Fetch the schedules that occurred between the given start and end date.
85+
* Schedules will be grouped by days.
86+
*/
87+
private function schedules(Carbon $startDate, Carbon $endDate, bool $onlyDisruptedDays = false): Collection
88+
{
89+
return Schedule::query()
90+
->with([
91+
'components',
92+
])
93+
->where(function (Builder $query) use ($endDate, $startDate) {
94+
$query->whereBetween('completed_at', [
95+
$endDate->startOfDay()->toDateTimeString(),
96+
$startDate->endofDay()->toDateTimeString(),
97+
]);
98+
})
99+
->orderBy('completed_at', 'desc')
100+
->get()
101+
->groupBy(fn (Schedule $schedule) => $schedule->completed_at->toDateString())
102+
->union(
103+
// Back-fill any missing dates...
104+
collect($endDate->toPeriod($startDate))
105+
->keyBy(fn ($period) => $period->toDateString())
106+
->map(fn ($period) => collect())
107+
)
108+
->when($onlyDisruptedDays, fn ($collection) => $collection->filter(fn ($schedules) => $schedules->isNotEmpty()))
109+
->sortKeysDesc();
110+
}
76111
}

0 commit comments

Comments
 (0)