Skip to content

Commit a06b231

Browse files
authored
Show banner for next scheduled test (alexjustesen#2507)
Co-authored-by: Alex Justesen <[email protected]>
1 parent 9a10136 commit a06b231

File tree

8 files changed

+143
-36
lines changed

8 files changed

+143
-36
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace App\Livewire;
4+
5+
use App\Services\ScheduledSpeedtestService;
6+
use Carbon\Carbon;
7+
use Livewire\Attributes\Computed;
8+
use Livewire\Component;
9+
10+
class NextSpeedtestBanner extends Component
11+
{
12+
#[Computed]
13+
public function nextSpeedtest(): ?Carbon
14+
{
15+
return ScheduledSpeedtestService::getNextScheduledTest();
16+
}
17+
18+
public function render()
19+
{
20+
return view('livewire.next-speedtest-banner');
21+
}
22+
}

app/Livewire/PlatformStats.php

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,12 @@
44

55
use App\Enums\ResultStatus;
66
use App\Models\Result;
7-
use Carbon\Carbon;
8-
use Cron\CronExpression;
97
use Illuminate\Support\Number;
108
use Livewire\Attributes\Computed;
119
use Livewire\Component;
1210

1311
class PlatformStats extends Component
1412
{
15-
#[Computed]
16-
public function nextSpeedtest(): ?Carbon
17-
{
18-
if ($schedule = config('speedtest.schedule')) {
19-
$cronExpression = new CronExpression($schedule);
20-
21-
return Carbon::parse(time: $cronExpression->getNextRunDate(timeZone: config('app.display_timezone')));
22-
}
23-
24-
return null;
25-
}
26-
2713
#[Computed]
2814
public function platformStats(): array
2915
{
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace App\Services;
4+
5+
use Carbon\Carbon;
6+
use Cron\CronExpression;
7+
8+
class ScheduledSpeedtestService
9+
{
10+
/**
11+
* Assess if there are scheduled speedtests and return the next scheduled time.
12+
*
13+
* @return Carbon|null Returns null if no tests are scheduled, or Carbon instance with next scheduled test
14+
*/
15+
public static function getNextScheduledTest(): ?Carbon
16+
{
17+
$schedule = config('speedtest.schedule');
18+
19+
if (blank($schedule) || $schedule === false) {
20+
return null;
21+
}
22+
23+
$cronExpression = new CronExpression($schedule);
24+
25+
return Carbon::parse(
26+
time: $cronExpression->getNextRunDate(timeZone: config('app.display_timezone'))
27+
);
28+
}
29+
}

resources/views/dashboard.blade.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<x-app-layout title="Dashboard">
22
<div class="space-y-6 md:space-y-12 dashboard-page">
3+
<livewire:next-speedtest-banner />
4+
35
@auth
46
<livewire:platform-stats />
57
@endauth

resources/views/filament/pages/dashboard.blade.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<x-filament-panels::page class="dashboard-page">
22
<div class="space-y-6 md:space-y-12">
3+
<livewire:next-speedtest-banner />
4+
35
<livewire:platform-stats />
46

57
<livewire:latest-result-stats />
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<div wire:poll.60s>
2+
@if ($this->nextSpeedtest)
3+
<div class="rounded-md bg-blue-50 dark:bg-blue-500/10 p-4 outline outline-blue-500/20">
4+
<div class="flex">
5+
<div class="shrink-0">
6+
<x-tabler-info-circle class="size-5 text-blue-400" />
7+
</div>
8+
9+
<div class="ml-3 flex-1">
10+
<p class="text-sm text-blue-700 dark:text-blue-300">
11+
Next scheduled test at <span class="font-medium">{{ $this->nextSpeedtest->timezone(config('app.display_timezone'))->format('F jS, Y, g:i a') }}</span>.
12+
</p>
13+
</div>
14+
</div>
15+
</div>
16+
@endif
17+
</div>

resources/views/livewire/platform-stats.blade.php

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<div wire:poll.60s>
2-
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
2+
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
33
<h2 class="flex items-center gap-x-2 text-base md:text-lg font-semibold text-zinc-900 dark:text-zinc-100 col-span-full">
44
<x-tabler-chart-bar class="size-5" />
55
{{ __('general.statistics') }}
@@ -23,41 +23,23 @@
2323
</div>
2424
</x-filament::section> --}}
2525

26-
@filled($this->nextSpeedtest)
27-
<x-filament::section class="col-span-1">
28-
<x-slot name="heading">
29-
Next Speedtest in
30-
</x-slot>
31-
32-
<p class="text-xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-100" title="{{ $this->nextSpeedtest->format('F jS, Y g:i A') }}">{{ $this->nextSpeedtest->diffForHumans() }}</p>
33-
</x-filament::section>
34-
@else
35-
<x-filament::section class="col-span-1 bg-zinc-100 shadow-none">
36-
<x-slot name="heading">
37-
Next Speedtest in
38-
</x-slot>
39-
40-
<p class="text-xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-100">No scheduled speedtests</p>
41-
</x-filament::section>
42-
@endfilled
43-
44-
<x-filament::section class="col-span-1">
26+
<x-filament::section class="col-span-1" icon="tabler-hash">
4527
<x-slot name="heading">
4628
Total tests
4729
</x-slot>
4830

4931
<p class="text-xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-100">{{ $this->platformStats['total'] }}</p>
5032
</x-filament::section>
5133

52-
<x-filament::section class="col-span-1">
34+
<x-filament::section class="col-span-1" icon="tabler-circle-check">
5335
<x-slot name="heading">
5436
Total completed tests
5537
</x-slot>
5638

5739
<p class="text-xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-100">{{ $this->platformStats['completed'] }}</p>
5840
</x-filament::section>
5941

60-
<x-filament::section class="col-span-1">
42+
<x-filament::section class="col-span-1" icon="tabler-alert-circle">
6143
<x-slot name="heading">
6244
Total failed tests
6345
</x-slot>
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
use App\Services\ScheduledSpeedtestService;
4+
use Carbon\Carbon;
5+
6+
test('returns null when schedule config is null', function () {
7+
config()->set('speedtest.schedule', null);
8+
9+
$result = ScheduledSpeedtestService::getNextScheduledTest();
10+
11+
expect($result)->toBeNull();
12+
});
13+
14+
test('returns null when schedule config is false', function () {
15+
config()->set('speedtest.schedule', false);
16+
17+
$result = ScheduledSpeedtestService::getNextScheduledTest();
18+
19+
expect($result)->toBeNull();
20+
});
21+
22+
test('returns null when schedule config is blank string', function () {
23+
config()->set('speedtest.schedule', '');
24+
25+
$result = ScheduledSpeedtestService::getNextScheduledTest();
26+
27+
expect($result)->toBeNull();
28+
});
29+
30+
test('returns Carbon instance when schedule is configured', function () {
31+
config()->set('speedtest.schedule', '*/5 * * * *'); // Every 5 minutes
32+
33+
$result = ScheduledSpeedtestService::getNextScheduledTest();
34+
35+
expect($result)->toBeInstanceOf(Carbon::class);
36+
});
37+
38+
test('returns correct next scheduled time for hourly cron', function () {
39+
config()->set('speedtest.schedule', '0 * * * *'); // Every hour at minute 0
40+
config()->set('app.display_timezone', 'UTC');
41+
42+
$result = ScheduledSpeedtestService::getNextScheduledTest();
43+
44+
expect($result)->toBeInstanceOf(Carbon::class);
45+
expect($result->minute)->toBe(0);
46+
});
47+
48+
test('returns correct next scheduled time for daily cron', function () {
49+
config()->set('speedtest.schedule', '0 0 * * *'); // Every day at midnight
50+
config()->set('app.display_timezone', 'UTC');
51+
52+
$result = ScheduledSpeedtestService::getNextScheduledTest();
53+
54+
expect($result)->toBeInstanceOf(Carbon::class);
55+
expect($result->hour)->toBe(0);
56+
expect($result->minute)->toBe(0);
57+
});
58+
59+
test('returns future date for next scheduled test', function () {
60+
config()->set('speedtest.schedule', '*/5 * * * *'); // Every 5 minutes
61+
config()->set('app.display_timezone', 'UTC');
62+
63+
$result = ScheduledSpeedtestService::getNextScheduledTest();
64+
65+
expect($result)->toBeInstanceOf(Carbon::class);
66+
expect($result->isFuture())->toBeTrue();
67+
});

0 commit comments

Comments
 (0)