146 lines
4.7 KiB
PHP
146 lines
4.7 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Mail\AlertTriggered;
|
|
use App\Models\Alert;
|
|
use App\Services\AnalyticsQueryService;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Facades\Mail;
|
|
|
|
class CheckAlerts extends Command
|
|
{
|
|
protected $signature = 'analytics:check-alerts';
|
|
|
|
protected $description = 'Evaluate active alerts and trigger notifications';
|
|
|
|
public function handle(): int
|
|
{
|
|
$alerts = Alert::with('website.user')
|
|
->where('is_active', true)
|
|
->get();
|
|
|
|
$triggered = 0;
|
|
|
|
foreach ($alerts as $alert) {
|
|
// Skip if triggered recently (within the period to avoid spam)
|
|
if ($this->wasRecentlyTriggered($alert)) {
|
|
continue;
|
|
}
|
|
|
|
$currentValue = $this->getCurrentValue($alert);
|
|
$shouldTrigger = $this->evaluate($alert, $currentValue);
|
|
|
|
if ($shouldTrigger) {
|
|
$this->trigger($alert, $currentValue);
|
|
$triggered++;
|
|
}
|
|
}
|
|
|
|
$this->info("Checked {$alerts->count()} alerts, triggered {$triggered}.");
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
private function getCurrentValue(Alert $alert): float
|
|
{
|
|
$to = Carbon::now();
|
|
$from = $alert->period === 'hourly'
|
|
? Carbon::now()->subHour()
|
|
: Carbon::today()->startOfDay();
|
|
|
|
$service = AnalyticsQueryService::for($alert->website, $from, $to);
|
|
$summary = $service->summary();
|
|
|
|
return match ($alert->metric) {
|
|
'page_views' => $summary['page_views'],
|
|
'visitors' => $summary['visitors'],
|
|
'sessions' => $summary['sessions'],
|
|
'bounce_rate' => $summary['bounce_rate'],
|
|
default => 0,
|
|
};
|
|
}
|
|
|
|
private function evaluate(Alert $alert, float $currentValue): bool
|
|
{
|
|
return match ($alert->condition) {
|
|
'above' => $currentValue > $alert->threshold,
|
|
'below' => $currentValue < $alert->threshold,
|
|
'change_up' => $this->percentageChange($alert) > $alert->threshold,
|
|
'change_down' => $this->percentageChange($alert) < -$alert->threshold,
|
|
default => false,
|
|
};
|
|
}
|
|
|
|
private function percentageChange(Alert $alert): float
|
|
{
|
|
if ($alert->period === 'hourly') {
|
|
// Compare current hour with previous hour
|
|
$currentTo = Carbon::now();
|
|
$currentFrom = Carbon::now()->subHour();
|
|
$prevTo = Carbon::now()->subHour();
|
|
$prevFrom = Carbon::now()->subHours(2);
|
|
|
|
$current = $this->getValueForPeriod($alert, $currentFrom, $currentTo);
|
|
$previous = $this->getValueForPeriod($alert, $prevFrom, $prevTo);
|
|
} else {
|
|
// Compare today so far with yesterday same time
|
|
$currentTo = Carbon::now();
|
|
$currentFrom = Carbon::today()->startOfDay();
|
|
$prevTo = Carbon::yesterday()->setTime($currentTo->hour, $currentTo->minute, $currentTo->second);
|
|
$prevFrom = Carbon::yesterday()->startOfDay();
|
|
|
|
$current = $this->getValueForPeriod($alert, $currentFrom, $currentTo);
|
|
$previous = $this->getValueForPeriod($alert, $prevFrom, $prevTo);
|
|
}
|
|
|
|
if ($previous == 0) {
|
|
return $current > 0 ? 100 : 0;
|
|
}
|
|
|
|
return (($current - $previous) / $previous) * 100;
|
|
}
|
|
|
|
private function getValueForPeriod(Alert $alert, Carbon $from, Carbon $to): float
|
|
{
|
|
$service = AnalyticsQueryService::for($alert->website, $from, $to);
|
|
$summary = $service->summary();
|
|
|
|
return $summary[$alert->metric] ?? 0;
|
|
}
|
|
|
|
private function wasRecentlyTriggered(Alert $alert): bool
|
|
{
|
|
if (!$alert->last_triggered_at) {
|
|
return false;
|
|
}
|
|
|
|
$cooldown = $alert->period === 'hourly' ? 1 : 24; // hours
|
|
$threshold = Carbon::now()->subHours($cooldown);
|
|
|
|
return $alert->last_triggered_at->isAfter($threshold);
|
|
}
|
|
|
|
private function trigger(Alert $alert, float $currentValue): void
|
|
{
|
|
$conditionLabel = Alert::$conditions[$alert->condition] ?? $alert->condition;
|
|
$metricLabel = Alert::$metrics[$alert->metric] ?? $alert->metric;
|
|
|
|
$message = "{$metricLabel} {$conditionLabel} {$alert->threshold} (current: {$currentValue}) for {$alert->website->name}";
|
|
|
|
$notification = $alert->notifications()->create([
|
|
'value' => $currentValue,
|
|
'message' => $message,
|
|
]);
|
|
|
|
$alert->update(['last_triggered_at' => now()]);
|
|
|
|
if (in_array($alert->channel, ['email', 'both'])) {
|
|
Mail::to($alert->website->user)->send(new AlertTriggered($alert, $notification));
|
|
}
|
|
|
|
$this->line(" → Triggered: {$alert->name} — {$message}");
|
|
}
|
|
}
|