Kurzbeschreibung Versteckt die Rerun-Row-Action für archivierte (soft-deleted) RestoreRuns und verhindert damit fehlerhafte Neu-Starts aus dem Archiv; ergänzt einen Regressionstest. Änderungen Code: RestoreRunResource.php — Sichtbarkeit der rerun-Action geprüft auf ! $record->trashed() und defensive Abbruchprüfung im Action-Handler. Tests: RestoreRunRerunTest.php — neuer Test rerun action is hidden for archived restore runs. Warum Archivierte RestoreRuns durften nicht neu gestartet werden; UI zeigte trotzdem die Option. Das führte zu verwirrendem Verhalten und möglichen Fehlern beim Enqueueing. Verifikation / QA Unit/Feature: ./vendor/bin/sail artisan test tests/Feature/RestoreRunRerunTest.php Stil/format: ./vendor/bin/pint --dirty Manuell (UI): Als Tenant-Admin Filament → Restore Runs öffnen. Filter Archived aktivieren (oder Trashed filter auswählen). Sicherstellen, dass für archivierte Einträge die Rerun-Action nicht sichtbar ist. Auf einem aktiven (nicht-archivierten) Run prüfen, dass Rerun sichtbar bleibt und wie erwartet eine neue RestoreRun erzeugt. Wichtige Hinweise Kein DB-Migration required. Diese PR enthält nur den UI-/Filament-Fix; die zuvor gemachten operative Fixes für Queue/adapter-Reconciliation bleiben ebenfalls auf dem Branch (z. B. frühere commits während der Debugging-Session). T055 (Schema squash) wurde bewusst zurückgestellt und ist nicht Teil dieses PRs. Merge-Checklist Tests lokal laufen (RestoreRunRerunTest grünt) Pint läuft ohne ungepatchte Fehler Branch gepusht: 056-remove-legacy-bulkops (PR-URL: https://git.cloudarix.de/ahmido/TenantAtlas/compare/dev...056-remove-legacy-bulkops) Co-authored-by: Ahmed Darrazi <ahmeddarrazi@adsmac.local> Reviewed-on: #65
117 lines
4.2 KiB
PHP
117 lines
4.2 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Services\AdapterRunReconciler;
|
|
use Illuminate\Console\Command;
|
|
use Throwable;
|
|
|
|
class OpsReconcileAdapterRuns extends Command
|
|
{
|
|
/**
|
|
* The name and signature of the console command.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $signature = 'ops:reconcile-adapter-runs
|
|
{--type= : Adapter run type (e.g. restore.execute)}
|
|
{--tenant= : Tenant ID}
|
|
{--older-than=60 : Only consider runs older than N minutes}
|
|
{--dry-run=true : Preview only (true/false)}
|
|
{--limit=50 : Max number of runs to inspect}';
|
|
|
|
/**
|
|
* The console command description.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $description = 'Reconcile stale adapter-backed operation runs from DB-only source-of-truth records.';
|
|
|
|
/**
|
|
* Execute the console command.
|
|
*/
|
|
public function handle()
|
|
{
|
|
try {
|
|
/** @var AdapterRunReconciler $reconciler */
|
|
$reconciler = app(AdapterRunReconciler::class);
|
|
|
|
$type = $this->option('type');
|
|
$type = is_string($type) && trim($type) !== '' ? trim($type) : null;
|
|
|
|
$tenantId = $this->option('tenant');
|
|
$tenantId = is_numeric($tenantId) ? (int) $tenantId : null;
|
|
|
|
$olderThanMinutes = $this->option('older-than');
|
|
$olderThanMinutes = is_numeric($olderThanMinutes) ? (int) $olderThanMinutes : 60;
|
|
$olderThanMinutes = max(1, $olderThanMinutes);
|
|
|
|
$limit = $this->option('limit');
|
|
$limit = is_numeric($limit) ? (int) $limit : 50;
|
|
$limit = max(1, $limit);
|
|
|
|
$dryRun = $this->option('dry-run');
|
|
$dryRun = filter_var($dryRun, FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE);
|
|
$dryRun = $dryRun ?? true;
|
|
|
|
$result = $reconciler->reconcile([
|
|
'type' => $type,
|
|
'tenant_id' => $tenantId,
|
|
'older_than_minutes' => $olderThanMinutes,
|
|
'limit' => $limit,
|
|
'dry_run' => $dryRun,
|
|
]);
|
|
|
|
$changes = $result['changes'] ?? [];
|
|
|
|
usort($changes, static fn (array $a, array $b): int => ((int) ($a['operation_run_id'] ?? 0)) <=> ((int) ($b['operation_run_id'] ?? 0)));
|
|
|
|
$this->info('Adapter run reconciliation');
|
|
$this->line('dry_run: '.($dryRun ? 'true' : 'false'));
|
|
$this->line('type: '.($type ?? '(all supported)'));
|
|
$this->line('tenant: '.($tenantId ? (string) $tenantId : '(all)'));
|
|
$this->line('older_than_minutes: '.$olderThanMinutes);
|
|
$this->line('limit: '.$limit);
|
|
$this->newLine();
|
|
|
|
$this->line('candidates: '.(int) ($result['candidates'] ?? 0));
|
|
$this->line('reconciled: '.(int) ($result['reconciled'] ?? 0));
|
|
$this->line('skipped: '.(int) ($result['skipped'] ?? 0));
|
|
$this->newLine();
|
|
|
|
if ($changes === []) {
|
|
$this->info('No changes.');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
$rows = [];
|
|
|
|
foreach ($changes as $change) {
|
|
$before = is_array($change['before'] ?? null) ? $change['before'] : [];
|
|
$after = is_array($change['after'] ?? null) ? $change['after'] : [];
|
|
|
|
$rows[] = [
|
|
'applied' => ($change['applied'] ?? false) ? 'yes' : 'no',
|
|
'operation_run_id' => (int) ($change['operation_run_id'] ?? 0),
|
|
'type' => (string) ($change['type'] ?? ''),
|
|
'source_id' => (int) ($change['restore_run_id'] ?? 0),
|
|
'before' => (string) (($before['status'] ?? '').'/'.($before['outcome'] ?? '')),
|
|
'after' => (string) (($after['status'] ?? '').'/'.($after['outcome'] ?? '')),
|
|
];
|
|
}
|
|
|
|
$this->table(
|
|
['applied', 'operation_run_id', 'type', 'source_id', 'before', 'after'],
|
|
$rows,
|
|
);
|
|
|
|
return self::SUCCESS;
|
|
} catch (Throwable $e) {
|
|
$this->error('Reconciliation failed: '.$e->getMessage());
|
|
|
|
return self::FAILURE;
|
|
}
|
|
}
|
|
}
|