Implements spec `099-alerts-v1-teams-email`. - Monitoring navigation: Alerts as a cluster under Monitoring; default landing is Alert deliveries. - Tenant panel: Alerts points to `/admin/alerts` and the cluster navigation is hidden in tenant panel. - Guard compliance: removes direct `Gate::` usage from Alert resources so `NoAdHocFilamentAuthPatternsTest` passes. Verification: - Full suite: `1348 passed, 7 skipped` (EXIT=0). Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #121
107 lines
3.3 KiB
PHP
107 lines
3.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Jobs\Alerts\DeliverAlertsJob;
|
|
use App\Jobs\Alerts\EvaluateAlertsJob;
|
|
use App\Models\Workspace;
|
|
use App\Services\OperationRunService;
|
|
use Carbon\CarbonImmutable;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Collection;
|
|
|
|
class TenantpilotDispatchAlerts extends Command
|
|
{
|
|
protected $signature = 'tenantpilot:alerts:dispatch {--workspace=* : Limit dispatch to one or more workspace IDs}';
|
|
|
|
protected $description = 'Queue workspace-scoped alert evaluation and delivery jobs idempotently.';
|
|
|
|
public function handle(OperationRunService $operationRuns): int
|
|
{
|
|
if (! (bool) config('tenantpilot.alerts.enabled', true)) {
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
$workspaceFilter = array_values(array_filter(array_map(
|
|
static fn (mixed $value): int => (int) $value,
|
|
(array) $this->option('workspace'),
|
|
)));
|
|
|
|
$workspaces = $this->resolveWorkspaces($workspaceFilter);
|
|
|
|
$slotKey = CarbonImmutable::now('UTC')->format('YmdHi').'Z';
|
|
|
|
$queuedEvaluate = 0;
|
|
$queuedDeliver = 0;
|
|
$skippedEvaluate = 0;
|
|
$skippedDeliver = 0;
|
|
|
|
foreach ($workspaces as $workspace) {
|
|
$evaluateRun = $operationRuns->ensureWorkspaceRunWithIdentity(
|
|
workspace: $workspace,
|
|
type: 'alerts.evaluate',
|
|
identityInputs: ['slot_key' => $slotKey],
|
|
context: [
|
|
'trigger' => 'scheduled_dispatch',
|
|
'slot_key' => $slotKey,
|
|
],
|
|
initiator: null,
|
|
);
|
|
|
|
if ($evaluateRun->wasRecentlyCreated) {
|
|
EvaluateAlertsJob::dispatch((int) $workspace->getKey(), (int) $evaluateRun->getKey());
|
|
$queuedEvaluate++;
|
|
} else {
|
|
$skippedEvaluate++;
|
|
}
|
|
|
|
$deliverRun = $operationRuns->ensureWorkspaceRunWithIdentity(
|
|
workspace: $workspace,
|
|
type: 'alerts.deliver',
|
|
identityInputs: ['slot_key' => $slotKey],
|
|
context: [
|
|
'trigger' => 'scheduled_dispatch',
|
|
'slot_key' => $slotKey,
|
|
],
|
|
initiator: null,
|
|
);
|
|
|
|
if ($deliverRun->wasRecentlyCreated) {
|
|
DeliverAlertsJob::dispatch((int) $workspace->getKey(), (int) $deliverRun->getKey());
|
|
$queuedDeliver++;
|
|
} else {
|
|
$skippedDeliver++;
|
|
}
|
|
}
|
|
|
|
$this->info(sprintf(
|
|
'Alert dispatch scanned %d workspace(s): evaluate queued=%d skipped=%d, deliver queued=%d skipped=%d.',
|
|
$workspaces->count(),
|
|
$queuedEvaluate,
|
|
$skippedEvaluate,
|
|
$queuedDeliver,
|
|
$skippedDeliver,
|
|
));
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @param array<int, int> $workspaceIds
|
|
* @return Collection<int, Workspace>
|
|
*/
|
|
private function resolveWorkspaces(array $workspaceIds): Collection
|
|
{
|
|
return Workspace::query()
|
|
->when(
|
|
$workspaceIds !== [],
|
|
fn ($query) => $query->whereIn('id', $workspaceIds),
|
|
fn ($query) => $query->whereHas('tenants'),
|
|
)
|
|
->orderBy('id')
|
|
->get();
|
|
}
|
|
}
|