055-ops-ux-rollout #64
@ -272,7 +272,7 @@ public function mount(): void
|
||||
);
|
||||
});
|
||||
|
||||
$this->dispatch(OpsUxBrowserEvents::RunEnqueued);
|
||||
OpsUxBrowserEvents::dispatchRunEnqueued($this);
|
||||
OperationUxPresenter::queuedToast((string) $opRun->type)
|
||||
->actions([
|
||||
Action::make('view_run')
|
||||
|
||||
@ -156,7 +156,10 @@ protected function getHeaderActions(): array
|
||||
);
|
||||
|
||||
if (! $opRun->wasRecentlyCreated && in_array($opRun->status, ['queued', 'running'], true)) {
|
||||
OperationUxPresenter::queuedToast((string) $opRun->type)
|
||||
Notification::make()
|
||||
->title('Inventory sync already active')
|
||||
->body('This operation is already queued or running.')
|
||||
->warning()
|
||||
->actions([
|
||||
Action::make('view_run')
|
||||
->label('View Run')
|
||||
@ -168,7 +171,6 @@ protected function getHeaderActions(): array
|
||||
|
||||
return;
|
||||
}
|
||||
// ----------------------------------------------
|
||||
|
||||
// Legacy checks (kept for safety if parallel usage needs it, though OpRun handles idempotency now)
|
||||
$existing = InventorySyncRun::query()
|
||||
|
||||
@ -52,9 +52,9 @@ public function table(Table $table): Table
|
||||
TextColumn::make('status')
|
||||
->badge()
|
||||
->colors([
|
||||
'warning' => 'queued',
|
||||
'info' => 'running',
|
||||
'secondary' => 'completed',
|
||||
'secondary' => 'queued',
|
||||
'warning' => 'running',
|
||||
'success' => 'completed',
|
||||
]),
|
||||
|
||||
TextColumn::make('outcome')
|
||||
|
||||
@ -685,7 +685,7 @@ public static function table(Table $table): Table
|
||||
|
||||
$operationRunService->dispatchOrFail($operationRun, function () use ($run, $bulkRun, $operationRun): void {
|
||||
Bus::dispatch(new RunBackupScheduleJob($run->id, $bulkRun?->id, $operationRun));
|
||||
});
|
||||
}, emitQueuedNotification: false);
|
||||
}
|
||||
|
||||
$notification = Notification::make()
|
||||
@ -825,7 +825,7 @@ public static function table(Table $table): Table
|
||||
|
||||
$operationRunService->dispatchOrFail($operationRun, function () use ($run, $bulkRun, $operationRun): void {
|
||||
Bus::dispatch(new RunBackupScheduleJob($run->id, $bulkRun?->id, $operationRun));
|
||||
});
|
||||
}, emitQueuedNotification: false);
|
||||
}
|
||||
|
||||
$notification = Notification::make()
|
||||
|
||||
@ -210,7 +210,7 @@ public function table(Table $table): Table
|
||||
);
|
||||
});
|
||||
|
||||
$this->dispatch(OpsUxBrowserEvents::RunEnqueued);
|
||||
OpsUxBrowserEvents::dispatchRunEnqueued($this);
|
||||
OperationUxPresenter::queuedToast((string) $opRun->type)
|
||||
->actions([
|
||||
Actions\Action::make('view_run')
|
||||
@ -304,17 +304,13 @@ public function table(Table $table): Table
|
||||
);
|
||||
});
|
||||
|
||||
$this->dispatch(OpsUxBrowserEvents::RunEnqueued);
|
||||
Notification::make()
|
||||
->title('Removal queued')
|
||||
->body('A background job has been queued. You can monitor progress in the run details or progress widget.')
|
||||
->success()
|
||||
OpsUxBrowserEvents::dispatchRunEnqueued($this);
|
||||
OperationUxPresenter::queuedToast((string) $opRun->type)
|
||||
->actions([
|
||||
Actions\Action::make('view_run')
|
||||
->label('View run')
|
||||
->url(OperationRunLinks::view($opRun, $tenant)),
|
||||
])
|
||||
->sendToDatabase($user)
|
||||
->send();
|
||||
|
||||
$this->resetTable();
|
||||
|
||||
@ -256,9 +256,9 @@ public static function getPages(): array
|
||||
private static function statusColor(?string $status): string
|
||||
{
|
||||
return match ($status) {
|
||||
'queued' => 'warning',
|
||||
'running' => 'info',
|
||||
'completed' => 'secondary',
|
||||
'queued' => 'secondary',
|
||||
'running' => 'warning',
|
||||
'completed' => 'success',
|
||||
default => 'gray',
|
||||
};
|
||||
}
|
||||
|
||||
@ -435,7 +435,6 @@ public static function table(Table $table): Table
|
||||
policyIds: [(int) $record->getKey()],
|
||||
operationRun: $opRun
|
||||
);
|
||||
|
||||
OpsUxBrowserEvents::dispatchRunEnqueued($livewire);
|
||||
OperationUxPresenter::queuedToast((string) $opRun->type)
|
||||
->actions([
|
||||
@ -635,7 +634,6 @@ public static function table(Table $table): Table
|
||||
policyIds: $ids,
|
||||
operationRun: $opRun
|
||||
);
|
||||
|
||||
OpsUxBrowserEvents::dispatchRunEnqueued($livewire);
|
||||
OperationUxPresenter::queuedToast((string) $opRun->type)
|
||||
->actions([
|
||||
|
||||
@ -90,7 +90,6 @@ protected function getHeaderActions(): array
|
||||
operationRun: $opRun
|
||||
);
|
||||
});
|
||||
|
||||
OpsUxBrowserEvents::dispatchRunEnqueued($livewire);
|
||||
OperationUxPresenter::queuedToast((string) $opRun->type)
|
||||
->actions([
|
||||
|
||||
@ -210,7 +210,7 @@ public static function table(Table $table): Table
|
||||
initiator: auth()->user()
|
||||
);
|
||||
|
||||
if (! $opRun->wasRecentlyCreated && in_array($opRun->status, ['queued', 'running'])) {
|
||||
if (! $opRun->wasRecentlyCreated && in_array($opRun->status, ['queued', 'running'], true)) {
|
||||
Notification::make()
|
||||
->title('Policy sync already active')
|
||||
->body('This operation is already queued or running.')
|
||||
|
||||
@ -28,7 +28,6 @@ public function toDatabase(object $notifiable): array
|
||||
return OperationUxPresenter::terminalDatabaseNotification(
|
||||
run: $this->run,
|
||||
tenant: $tenant instanceof Tenant ? $tenant : null,
|
||||
)
|
||||
->getDatabaseMessage();
|
||||
)->getDatabaseMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,6 @@
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
|
||||
class AdminPanelProvider extends PanelProvider
|
||||
@ -42,7 +41,7 @@ public function panel(Panel $panel): Panel
|
||||
->renderHook(
|
||||
PanelsRenderHook::BODY_END,
|
||||
fn () => (bool) config('tenantpilot.bulk_operations.progress_widget_enabled', true)
|
||||
? Blade::render("@livewire('bulk-operation-progress', [], key('ops-ux-progress'))")
|
||||
? view('livewire.bulk-operation-progress-wrapper')->render()
|
||||
: ''
|
||||
)
|
||||
->discoverResources(in: app_path('Filament/Resources'), for: 'App\Filament\Resources')
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use App\Notifications\OperationRunCompleted as OperationRunCompletedNotification;
|
||||
use App\Notifications\OperationRunQueued as OperationRunQueuedNotification;
|
||||
use App\Support\OperationRunOutcome;
|
||||
use App\Support\OperationRunStatus;
|
||||
use App\Support\OpsUx\SummaryCountsNormalizer;
|
||||
@ -142,10 +143,14 @@ public function updateRun(
|
||||
* If dispatch fails synchronously (misconfiguration, serialization errors, etc.),
|
||||
* the OperationRun is marked terminal failed so we do not leave a misleading queued run behind.
|
||||
*/
|
||||
public function dispatchOrFail(OperationRun $run, callable $dispatcher): void
|
||||
public function dispatchOrFail(OperationRun $run, callable $dispatcher, bool $emitQueuedNotification = true): void
|
||||
{
|
||||
try {
|
||||
$dispatcher();
|
||||
|
||||
if ($emitQueuedNotification && $run->wasRecentlyCreated && $run->user instanceof User) {
|
||||
$run->user->notify(new OperationRunQueuedNotification($run));
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$this->updateRun(
|
||||
$run,
|
||||
|
||||
@ -2,11 +2,12 @@
|
||||
|
||||
use App\Models\OperationRun;
|
||||
use App\Notifications\OperationRunCompleted;
|
||||
use App\Notifications\OperationRunQueued;
|
||||
use App\Services\OperationRunService;
|
||||
use App\Support\OperationRunLinks;
|
||||
use Filament\Facades\Filament;
|
||||
|
||||
it('does not emit a queued database notification after successful dispatch (toast-only)', function () {
|
||||
it('emits a queued notification after successful dispatch (initiator only) with view link', function () {
|
||||
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
||||
$this->actingAs($user);
|
||||
|
||||
@ -25,7 +26,18 @@
|
||||
// no-op (dispatch succeeded)
|
||||
});
|
||||
|
||||
expect($user->notifications()->count())->toBe(0);
|
||||
$this->assertDatabaseHas('notifications', [
|
||||
'notifiable_id' => $user->getKey(),
|
||||
'notifiable_type' => $user->getMorphClass(),
|
||||
'type' => OperationRunQueued::class,
|
||||
'data->format' => 'filament',
|
||||
'data->title' => 'Policy sync queued',
|
||||
]);
|
||||
|
||||
$notification = $user->notifications()->latest('id')->first();
|
||||
expect($notification)->not->toBeNull();
|
||||
expect($notification->data['actions'][0]['url'] ?? null)
|
||||
->toBe(OperationRunLinks::view($run, $tenant));
|
||||
});
|
||||
|
||||
it('does not emit queued notifications for runs without an initiator', function () {
|
||||
|
||||
@ -138,7 +138,6 @@
|
||||
$fresh = $run->fresh();
|
||||
expect($fresh?->status)->toBe('running');
|
||||
expect($fresh?->started_at)->not->toBeNull();
|
||||
|
||||
$service->updateRun($run, 'completed', 'succeeded', ['succeeded' => 1]);
|
||||
|
||||
$fresh = $run->fresh();
|
||||
|
||||
@ -2,10 +2,11 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Notifications\OperationRunQueued;
|
||||
use App\Services\OperationRunService;
|
||||
use Filament\Facades\Filament;
|
||||
|
||||
it('does not emit database notifications when dispatching a queued operation', function (): void {
|
||||
it('emits at most one queued database notification per newly created run', function (): void {
|
||||
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
||||
$this->actingAs($user);
|
||||
|
||||
@ -26,5 +27,10 @@
|
||||
// no-op (dispatch succeeded)
|
||||
});
|
||||
|
||||
expect($user->notifications()->count())->toBe(0);
|
||||
expect($user->notifications()->count())->toBe(1);
|
||||
$this->assertDatabaseHas('notifications', [
|
||||
'notifiable_id' => $user->getKey(),
|
||||
'notifiable_type' => $user->getMorphClass(),
|
||||
'type' => OperationRunQueued::class,
|
||||
]);
|
||||
})->group('ops-ux');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user