98 lines
2.8 KiB
PHP
98 lines
2.8 KiB
PHP
<?php
|
|||
|
|
|
||
|
|
namespace App\Services;
|
||
|
|
|
||
|
|
use App\Models\AlertRule;
|
||
|
|
use App\Models\Event;
|
||
|
|
use App\Models\Issue;
|
||
|
|
use App\Models\Project;
|
||
|
|
use App\Notifications\EventSpikeNotification;
|
||
|
|
use App\Notifications\IssueRegressionNotification;
|
||
|
|
use App\Notifications\NewIssueNotification;
|
||
|
|
use Illuminate\Support\Carbon;
|
||
|
|
use Illuminate\Support\Facades\Notification;
|
||
|
|
|
||
|
|
class NotificationDispatcher
|
||
|
|
{
|
||
|
|
public function onNewIssue(Project $project, Issue $issue): void
|
||
|
|
{
|
||
|
|
if (! $this->ruleEnabled($project, 'new_issue')) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
$recipients = $this->getRecipients($project);
|
||
|
|
Notification::send($recipients, new NewIssueNotification($project, $issue));
|
||
|
|
}
|
||
|
|
|
||
|
|
public function onRegression(Project $project, Issue $issue): void
|
||
|
|
{
|
||
|
|
if (! $this->ruleEnabled($project, 'regression')) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
$recipients = $this->getRecipients($project);
|
||
|
|
Notification::send($recipients, new IssueRegressionNotification($project, $issue));
|
||
|
|
}
|
||
|
|
|
||
|
|
public function checkSpike(Project $project, Issue $issue): void
|
||
|
|
{
|
||
|
|
$rule = $project->alertRules()
|
||
|
|
->where('type', 'spike')
|
||
|
|
->where('enabled', true)
|
||
|
|
->first();
|
||
|
|
|
||
|
|
if (! $rule) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
$threshold = $rule->getSpikeThreshold();
|
||
|
|
$windowMinutes = $rule->getSpikeWindowMinutes();
|
||
|
|
|
||
|
|
$recentCount = Event::where('issue_id', $issue->id)
|
||
|
|
->where('created_at', '>=', Carbon::now()->subMinutes($windowMinutes))
|
||
|
|
->count();
|
||
|
|
|
||
|
|
if ($recentCount < $threshold) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Only send once per spike window — check if we already notified
|
||
|
|
$alreadyNotified = $project->users()
|
||
|
|
->first()
|
||
|
|
?->notifications()
|
||
|
|
->where('type', EventSpikeNotification::class)
|
||
|
|
->where('created_at', '>=', Carbon::now()->subMinutes($windowMinutes))
|
||
|
|
->whereJsonContains('data->issue_id', $issue->id)
|
||
|
|
->exists();
|
||
|
|
|
||
|
|
if ($alreadyNotified) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
$recipients = $this->getRecipients($project);
|
||
|
|
Notification::send(
|
||
|
|
$recipients,
|
||
|
|
new EventSpikeNotification($project, $issue, $recentCount, $windowMinutes),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function ruleEnabled(Project $project, string $type): bool
|
||
|
|
{
|
||
|
|
$rule = $project->alertRules()->where('type', $type)->first();
|
||
|
|
|
||
|
|
// Default: enabled if no rule exists (opt-out model)
|
||
|
|
return $rule === null || $rule->enabled;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function getRecipients(Project $project)
|
||
|
|
{
|
||
|
|
return $project->users()
|
||
|
|
->get()
|
||
|
|
->filter(function ($user) use ($project) {
|
||
|
|
$pref = $user->notificationPreferenceFor($project);
|
||
|
|
|
||
|
|
return ! $pref->muted;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|