From 44b4a6adf0ae8ff47223007b4d3f8887558644ef Mon Sep 17 00:00:00 2001 From: Ahmed Darrazi Date: Wed, 31 Dec 2025 13:27:50 +0100 Subject: [PATCH] fix: prime group mapping state --- app/Filament/Resources/RestoreRunResource.php | 112 +++++++++++++----- tests/Feature/RestoreGroupMappingTest.php | 13 +- 2 files changed, 95 insertions(+), 30 deletions(-) diff --git a/app/Filament/Resources/RestoreRunResource.php b/app/Filament/Resources/RestoreRunResource.php index 8959cc7..ff2500b 100644 --- a/app/Filament/Resources/RestoreRunResource.php +++ b/app/Filament/Resources/RestoreRunResource.php @@ -177,10 +177,15 @@ public static function getWizardSteps(): array }); }) ->reactive() - ->afterStateUpdated(function (Set $set): void { + ->afterStateUpdated(function (Set $set, Get $get): void { $set('scope_mode', 'all'); $set('backup_item_ids', null); - $set('group_mapping', []); + $set('group_mapping', static::groupMappingPlaceholders( + backupSetId: $get('backup_set_id'), + scopeMode: 'all', + selectedItemIds: null, + tenant: Tenant::current(), + )); $set('is_dry_run', true); $set('acknowledged_impact', false); $set('tenant_confirm', null); @@ -204,8 +209,9 @@ public static function getWizardSteps(): array ]) ->default('all') ->reactive() - ->afterStateUpdated(function (Set $set, $state): void { - $set('group_mapping', []); + ->afterStateUpdated(function (Set $set, Get $get, $state): void { + $backupSetId = $get('backup_set_id'); + $tenant = Tenant::current(); $set('is_dry_run', true); $set('acknowledged_impact', false); $set('tenant_confirm', null); @@ -218,10 +224,17 @@ public static function getWizardSteps(): array if ($state === 'all') { $set('backup_item_ids', null); + $set('group_mapping', static::groupMappingPlaceholders( + backupSetId: $backupSetId, + scopeMode: 'all', + selectedItemIds: null, + tenant: $tenant, + )); return; } + $set('group_mapping', []); $set('backup_item_ids', []); }) ->required(), @@ -234,8 +247,18 @@ public static function getWizardSteps(): array ->optionsLimit(300) ->options(fn (Get $get) => static::restoreItemGroupedOptions($get('backup_set_id'))) ->reactive() - ->afterStateUpdated(function (Set $set): void { - $set('group_mapping', []); + ->afterStateUpdated(function (Set $set, Get $get): void { + $backupSetId = $get('backup_set_id'); + $selectedItemIds = $get('backup_item_ids'); + $selectedItemIds = is_array($selectedItemIds) ? $selectedItemIds : null; + $tenant = Tenant::current(); + + $set('group_mapping', static::groupMappingPlaceholders( + backupSetId: $backupSetId, + scopeMode: 'selected', + selectedItemIds: $selectedItemIds, + tenant: $tenant, + )); $set('is_dry_run', true); $set('acknowledged_impact', false); $set('tenant_confirm', null); @@ -249,29 +272,29 @@ public static function getWizardSteps(): array ->visible(fn (Get $get): bool => $get('scope_mode') === 'selected') ->required(fn (Get $get): bool => $get('scope_mode') === 'selected') ->hintActions([ - Actions\Action::make('select_all_backup_items') - ->label('Select all') - ->icon('heroicon-o-check') - ->color('gray') - ->visible(fn (Get $get): bool => filled($get('backup_set_id')) && $get('scope_mode') === 'selected') - ->action(function (Get $get, Set $set): void { - $groupedOptions = static::restoreItemGroupedOptions($get('backup_set_id')); + Actions\Action::make('select_all_backup_items') + ->label('Select all') + ->icon('heroicon-o-check') + ->color('gray') + ->visible(fn (Get $get): bool => filled($get('backup_set_id')) && $get('scope_mode') === 'selected') + ->action(function (Get $get, Set $set): void { + $groupedOptions = static::restoreItemGroupedOptions($get('backup_set_id')); - $allItemIds = []; + $allItemIds = []; - foreach ($groupedOptions as $options) { - $allItemIds = array_merge($allItemIds, array_keys($options)); - } + foreach ($groupedOptions as $options) { + $allItemIds = array_merge($allItemIds, array_keys($options)); + } - $set('backup_item_ids', array_values($allItemIds), shouldCallUpdatedHooks: true); - }), - Actions\Action::make('clear_backup_items') - ->label('Clear') - ->icon('heroicon-o-x-mark') - ->color('gray') - ->visible(fn (Get $get): bool => $get('scope_mode') === 'selected') - ->action(fn (Set $set) => $set('backup_item_ids', [], shouldCallUpdatedHooks: true)), - ]) + $set('backup_item_ids', array_values($allItemIds), shouldCallUpdatedHooks: true); + }), + Actions\Action::make('clear_backup_items') + ->label('Clear') + ->icon('heroicon-o-x-mark') + ->color('gray') + ->visible(fn (Get $get): bool => $get('scope_mode') === 'selected') + ->action(fn (Set $set) => $set('backup_item_ids', [], shouldCallUpdatedHooks: true)), + ]) ->helperText('Search by name or ID. Include foundations (scope tags, assignment filters) with policies to re-map IDs. Options are grouped by category, type, and platform.'), Section::make('Group mapping') ->description('Some source groups do not exist in the target tenant. Map them or choose Skip.') @@ -1383,8 +1406,43 @@ private static function unresolvedGroups(?int $backupSetId, ?array $selectedItem } /** + * @param array|null $selectedItemIds * @return array */ + private static function groupMappingPlaceholders(?int $backupSetId, string $scopeMode, ?array $selectedItemIds, ?Tenant $tenant): array + { + if (! $tenant || ! $backupSetId) { + return []; + } + + if ($scopeMode === 'selected' && ($selectedItemIds === null || $selectedItemIds === [])) { + return []; + } + + $unresolved = static::unresolvedGroups( + backupSetId: $backupSetId, + selectedItemIds: $scopeMode === 'selected' ? $selectedItemIds : null, + tenant: $tenant, + ); + + $placeholders = []; + + foreach ($unresolved as $group) { + $groupId = $group['id'] ?? null; + + if (! is_string($groupId) || $groupId === '') { + continue; + } + + $placeholders[$groupId] = null; + } + + return $placeholders; + } + + /** + * @return array + */ private static function normalizeGroupMapping(mixed $mapping): array { if ($mapping instanceof \Illuminate\Contracts\Support\Arrayable) { @@ -1449,7 +1507,7 @@ private static function normalizeGroupMapping(mixed $mapping): array $result[$sourceGroupId] = null; } - return $result; + return array_filter($result, static fn (?string $value): bool => is_string($value) && $value !== ''); } /** diff --git a/tests/Feature/RestoreGroupMappingTest.php b/tests/Feature/RestoreGroupMappingTest.php index de3b9cc..5746b5b 100644 --- a/tests/Feature/RestoreGroupMappingTest.php +++ b/tests/Feature/RestoreGroupMappingTest.php @@ -77,7 +77,7 @@ $user = User::factory()->create(); $this->actingAs($user); - Livewire::test(CreateRestoreRun::class) + $component = Livewire::test(CreateRestoreRun::class) ->fillForm([ 'backup_set_id' => $backupSet->id, ]) @@ -85,8 +85,15 @@ ->fillForm([ 'scope_mode' => 'selected', 'backup_item_ids' => [$backupItem->id], - ]) - ->assertFormFieldVisible('group_mapping.source-group-1'); + ]); + + $mapping = $component->get('data.group_mapping'); + + expect($mapping)->toBeArray(); + expect(array_key_exists('source-group-1', $mapping))->toBeTrue(); + expect($mapping['source-group-1'])->toBeNull(); + + $component->assertFormFieldVisible('group_mapping.source-group-1'); }); test('restore wizard persists group mapping selections', function () {