Implements feature 100 (Alert Targets): - US1: “Send test message” action (RBAC + confirmation + rate limit + audit + async job) - US2: Derived “Last test” status badge (Never/Sent/Failed/Pending) on view + edit surfaces - US3: “View last delivery” deep link + deliveries viewer filters (event_type, destination) incl. tenantless test deliveries Tests: - Full suite green (1348 passed, 7 skipped) - Added focused feature tests for send test, last test resolver/badges, and deep-link filters Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #122
155 lines
5.9 KiB
PHP
155 lines
5.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Filament\Resources\AlertDestinationResource\Pages;
|
|
|
|
use App\Filament\Resources\AlertDeliveryResource;
|
|
use App\Filament\Resources\AlertDestinationResource;
|
|
use App\Models\AlertDestination;
|
|
use App\Models\User;
|
|
use App\Services\Alerts\AlertDestinationLastTestResolver;
|
|
use App\Services\Alerts\AlertDestinationTestMessageService;
|
|
use App\Support\Alerts\AlertDestinationLastTestStatus;
|
|
use App\Support\Badges\BadgeDomain;
|
|
use App\Support\Badges\BadgeRenderer;
|
|
use Filament\Actions\Action;
|
|
use Filament\Infolists\Components\TextEntry;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Resources\Pages\ViewRecord;
|
|
use Filament\Schemas\Components\Section;
|
|
use Filament\Schemas\Schema;
|
|
|
|
class ViewAlertDestination extends ViewRecord
|
|
{
|
|
protected static string $resource = AlertDestinationResource::class;
|
|
|
|
private ?AlertDestinationLastTestStatus $lastTestStatus = null;
|
|
|
|
public function mount(int|string $record): void
|
|
{
|
|
parent::mount($record);
|
|
$this->resolveLastTestStatus();
|
|
}
|
|
|
|
protected function getHeaderActions(): array
|
|
{
|
|
$user = auth()->user();
|
|
$record = $this->record;
|
|
$canManage = $user instanceof User
|
|
&& $record instanceof AlertDestination
|
|
&& $user->can('update', $record);
|
|
|
|
return [
|
|
Action::make('send_test_message')
|
|
->label('Send test message')
|
|
->icon('heroicon-o-paper-airplane')
|
|
->requiresConfirmation()
|
|
->modalHeading('Send test message')
|
|
->modalDescription('A test delivery will be queued for this destination. This verifies the delivery pipeline is working.')
|
|
->modalSubmitActionLabel('Send')
|
|
->visible(fn (): bool => $record instanceof AlertDestination)
|
|
->disabled(fn (): bool => ! $canManage)
|
|
->action(function () use ($record): void {
|
|
$user = auth()->user();
|
|
|
|
if (! $user instanceof User || ! $record instanceof AlertDestination) {
|
|
return;
|
|
}
|
|
|
|
$service = app(AlertDestinationTestMessageService::class);
|
|
$result = $service->sendTest($record, $user);
|
|
|
|
if ($result['success']) {
|
|
Notification::make()
|
|
->title($result['message'])
|
|
->success()
|
|
->send();
|
|
} else {
|
|
Notification::make()
|
|
->title($result['message'])
|
|
->warning()
|
|
->send();
|
|
}
|
|
|
|
$this->resolveLastTestStatus();
|
|
}),
|
|
|
|
Action::make('view_last_delivery')
|
|
->label('View last delivery')
|
|
->icon('heroicon-o-arrow-top-right-on-square')
|
|
->url(fn (): ?string => $this->buildDeepLinkUrl())
|
|
->openUrlInNewTab()
|
|
->visible(fn (): bool => $this->lastTestStatus?->deliveryId !== null),
|
|
];
|
|
}
|
|
|
|
public function infolist(Schema $schema): Schema
|
|
{
|
|
$lastTest = $this->lastTestStatus ?? AlertDestinationLastTestStatus::never();
|
|
|
|
return $schema
|
|
->schema([
|
|
Section::make('Last test')
|
|
->schema([
|
|
TextEntry::make('last_test_status')
|
|
->label('Status')
|
|
->badge()
|
|
->state($lastTest->status->value)
|
|
->formatStateUsing(BadgeRenderer::label(BadgeDomain::AlertDestinationLastTestStatus))
|
|
->color(BadgeRenderer::color(BadgeDomain::AlertDestinationLastTestStatus))
|
|
->icon(BadgeRenderer::icon(BadgeDomain::AlertDestinationLastTestStatus)),
|
|
TextEntry::make('last_test_timestamp')
|
|
->label('Timestamp')
|
|
->state($lastTest->timestamp?->toDateTimeString())
|
|
->placeholder('—'),
|
|
])
|
|
->columns(2),
|
|
Section::make('Details')
|
|
->schema([
|
|
TextEntry::make('name'),
|
|
TextEntry::make('type')
|
|
->badge()
|
|
->formatStateUsing(fn (?string $state): string => AlertDestinationResource::typeLabel((string) $state)),
|
|
TextEntry::make('is_enabled')
|
|
->label('Enabled')
|
|
->badge()
|
|
->formatStateUsing(fn (bool $state): string => $state ? 'Yes' : 'No')
|
|
->color(fn (bool $state): string => $state ? 'success' : 'gray'),
|
|
TextEntry::make('created_at')
|
|
->dateTime(),
|
|
TextEntry::make('updated_at')
|
|
->dateTime(),
|
|
])
|
|
->columns(2),
|
|
]);
|
|
}
|
|
|
|
private function resolveLastTestStatus(): void
|
|
{
|
|
$record = $this->record;
|
|
|
|
if (! $record instanceof AlertDestination) {
|
|
return;
|
|
}
|
|
|
|
$this->lastTestStatus = app(AlertDestinationLastTestResolver::class)->resolve($record);
|
|
}
|
|
|
|
private function buildDeepLinkUrl(): ?string
|
|
{
|
|
$record = $this->record;
|
|
|
|
if (! $record instanceof AlertDestination || $this->lastTestStatus?->deliveryId === null) {
|
|
return null;
|
|
}
|
|
|
|
return AlertDeliveryResource::getUrl(panel: 'admin').'?'.http_build_query([
|
|
'filters' => [
|
|
'event_type' => ['value' => 'alerts.test'],
|
|
'alert_destination_id' => ['value' => (string) $record->getKey()],
|
|
],
|
|
]);
|
|
}
|
|
}
|