feat/012-windows-update-rings #18

Merged
ahmido merged 24 commits from feat/012-windows-update-rings into dev 2026-01-01 10:44:18 +00:00
2 changed files with 95 additions and 30 deletions
Showing only changes of commit 44b4a6adf0 - Show all commits

View File

@ -177,10 +177,15 @@ public static function getWizardSteps(): array
}); });
}) })
->reactive() ->reactive()
->afterStateUpdated(function (Set $set): void { ->afterStateUpdated(function (Set $set, Get $get): void {
$set('scope_mode', 'all'); $set('scope_mode', 'all');
$set('backup_item_ids', null); $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('is_dry_run', true);
$set('acknowledged_impact', false); $set('acknowledged_impact', false);
$set('tenant_confirm', null); $set('tenant_confirm', null);
@ -204,8 +209,9 @@ public static function getWizardSteps(): array
]) ])
->default('all') ->default('all')
->reactive() ->reactive()
->afterStateUpdated(function (Set $set, $state): void { ->afterStateUpdated(function (Set $set, Get $get, $state): void {
$set('group_mapping', []); $backupSetId = $get('backup_set_id');
$tenant = Tenant::current();
$set('is_dry_run', true); $set('is_dry_run', true);
$set('acknowledged_impact', false); $set('acknowledged_impact', false);
$set('tenant_confirm', null); $set('tenant_confirm', null);
@ -218,10 +224,17 @@ public static function getWizardSteps(): array
if ($state === 'all') { if ($state === 'all') {
$set('backup_item_ids', null); $set('backup_item_ids', null);
$set('group_mapping', static::groupMappingPlaceholders(
backupSetId: $backupSetId,
scopeMode: 'all',
selectedItemIds: null,
tenant: $tenant,
));
return; return;
} }
$set('group_mapping', []);
$set('backup_item_ids', []); $set('backup_item_ids', []);
}) })
->required(), ->required(),
@ -234,8 +247,18 @@ public static function getWizardSteps(): array
->optionsLimit(300) ->optionsLimit(300)
->options(fn (Get $get) => static::restoreItemGroupedOptions($get('backup_set_id'))) ->options(fn (Get $get) => static::restoreItemGroupedOptions($get('backup_set_id')))
->reactive() ->reactive()
->afterStateUpdated(function (Set $set): void { ->afterStateUpdated(function (Set $set, Get $get): void {
$set('group_mapping', []); $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('is_dry_run', true);
$set('acknowledged_impact', false); $set('acknowledged_impact', false);
$set('tenant_confirm', null); $set('tenant_confirm', null);
@ -249,29 +272,29 @@ public static function getWizardSteps(): array
->visible(fn (Get $get): bool => $get('scope_mode') === 'selected') ->visible(fn (Get $get): bool => $get('scope_mode') === 'selected')
->required(fn (Get $get): bool => $get('scope_mode') === 'selected') ->required(fn (Get $get): bool => $get('scope_mode') === 'selected')
->hintActions([ ->hintActions([
Actions\Action::make('select_all_backup_items') Actions\Action::make('select_all_backup_items')
->label('Select all') ->label('Select all')
->icon('heroicon-o-check') ->icon('heroicon-o-check')
->color('gray') ->color('gray')
->visible(fn (Get $get): bool => filled($get('backup_set_id')) && $get('scope_mode') === 'selected') ->visible(fn (Get $get): bool => filled($get('backup_set_id')) && $get('scope_mode') === 'selected')
->action(function (Get $get, Set $set): void { ->action(function (Get $get, Set $set): void {
$groupedOptions = static::restoreItemGroupedOptions($get('backup_set_id')); $groupedOptions = static::restoreItemGroupedOptions($get('backup_set_id'));
$allItemIds = []; $allItemIds = [];
foreach ($groupedOptions as $options) { foreach ($groupedOptions as $options) {
$allItemIds = array_merge($allItemIds, array_keys($options)); $allItemIds = array_merge($allItemIds, array_keys($options));
} }
$set('backup_item_ids', array_values($allItemIds), shouldCallUpdatedHooks: true); $set('backup_item_ids', array_values($allItemIds), shouldCallUpdatedHooks: true);
}), }),
Actions\Action::make('clear_backup_items') Actions\Action::make('clear_backup_items')
->label('Clear') ->label('Clear')
->icon('heroicon-o-x-mark') ->icon('heroicon-o-x-mark')
->color('gray') ->color('gray')
->visible(fn (Get $get): bool => $get('scope_mode') === 'selected') ->visible(fn (Get $get): bool => $get('scope_mode') === 'selected')
->action(fn (Set $set) => $set('backup_item_ids', [], shouldCallUpdatedHooks: true)), ->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.'), ->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') Section::make('Group mapping')
->description('Some source groups do not exist in the target tenant. Map them or choose Skip.') ->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<int>|null $selectedItemIds
* @return array<string, string|null> * @return array<string, string|null>
*/ */
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<string, string>
*/
private static function normalizeGroupMapping(mixed $mapping): array private static function normalizeGroupMapping(mixed $mapping): array
{ {
if ($mapping instanceof \Illuminate\Contracts\Support\Arrayable) { if ($mapping instanceof \Illuminate\Contracts\Support\Arrayable) {
@ -1449,7 +1507,7 @@ private static function normalizeGroupMapping(mixed $mapping): array
$result[$sourceGroupId] = null; $result[$sourceGroupId] = null;
} }
return $result; return array_filter($result, static fn (?string $value): bool => is_string($value) && $value !== '');
} }
/** /**

View File

@ -77,7 +77,7 @@
$user = User::factory()->create(); $user = User::factory()->create();
$this->actingAs($user); $this->actingAs($user);
Livewire::test(CreateRestoreRun::class) $component = Livewire::test(CreateRestoreRun::class)
->fillForm([ ->fillForm([
'backup_set_id' => $backupSet->id, 'backup_set_id' => $backupSet->id,
]) ])
@ -85,8 +85,15 @@
->fillForm([ ->fillForm([
'scope_mode' => 'selected', 'scope_mode' => 'selected',
'backup_item_ids' => [$backupItem->id], '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 () { test('restore wizard persists group mapping selections', function () {