feat: move assignment options to capture actions
This commit is contained in:
parent
c3bdcf4d2d
commit
2269904857
@ -36,10 +36,6 @@ public static function form(Schema $schema): Schema
|
||||
->label('Backup name')
|
||||
->default(fn () => now()->format('Y-m-d H:i:s').' backup')
|
||||
->required(),
|
||||
Forms\Components\Checkbox::make('include_assignments')
|
||||
->label('Include Assignments & Scope Tags')
|
||||
->helperText('Captures group/user targeting and RBAC scope. Adds ~2-5 KB per policy.')
|
||||
->default(false),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -202,6 +198,7 @@ public static function createBackupSet(array $data): BackupSet
|
||||
actorEmail: auth()->user()?->email,
|
||||
actorName: auth()->user()?->name,
|
||||
includeAssignments: $data['include_assignments'] ?? false,
|
||||
includeScopeTags: $data['include_scope_tags'] ?? false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,6 +65,7 @@ public function table(Table $table): Table
|
||||
return implode(', ', $tags['names']);
|
||||
}
|
||||
}
|
||||
|
||||
return '—';
|
||||
}),
|
||||
Tables\Columns\TextColumn::make('captured_at')->dateTime(),
|
||||
@ -97,9 +98,13 @@ public function table(Table $table): Table
|
||||
->pluck('display_name', 'id');
|
||||
}),
|
||||
Forms\Components\Checkbox::make('include_assignments')
|
||||
->label('Include Assignments')
|
||||
->label('Include assignments')
|
||||
->default(true)
|
||||
->helperText('Capture policy assignments and scope tags'),
|
||||
->helperText('Captures assignment include/exclude targeting and filters.'),
|
||||
Forms\Components\Checkbox::make('include_scope_tags')
|
||||
->label('Include scope tags')
|
||||
->default(true)
|
||||
->helperText('Captures policy scope tag IDs.'),
|
||||
])
|
||||
->action(function (array $data, BackupService $service) {
|
||||
if (empty($data['policy_ids'])) {
|
||||
@ -121,6 +126,7 @@ public function table(Table $table): Table
|
||||
actorEmail: auth()->user()?->email,
|
||||
actorName: auth()->user()?->name,
|
||||
includeAssignments: $data['include_assignments'] ?? false,
|
||||
includeScopeTags: $data['include_scope_tags'] ?? false,
|
||||
);
|
||||
|
||||
Notification::make()
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
use App\Filament\Resources\PolicyResource;
|
||||
use App\Services\Intune\VersionService;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
use Filament\Support\Enums\Width;
|
||||
@ -23,7 +24,17 @@ protected function getActions(): array
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Capture snapshot now')
|
||||
->modalSubheading('This will fetch the latest configuration from Microsoft Graph and store a new policy version.')
|
||||
->action(function () {
|
||||
->form([
|
||||
Forms\Components\Checkbox::make('include_assignments')
|
||||
->label('Include assignments')
|
||||
->default(true)
|
||||
->helperText('Captures assignment include/exclude targeting and filters.'),
|
||||
Forms\Components\Checkbox::make('include_scope_tags')
|
||||
->label('Include scope tags')
|
||||
->default(true)
|
||||
->helperText('Captures policy scope tag IDs.'),
|
||||
])
|
||||
->action(function (array $data) {
|
||||
$policy = $this->record;
|
||||
|
||||
try {
|
||||
@ -38,7 +49,13 @@ protected function getActions(): array
|
||||
return;
|
||||
}
|
||||
|
||||
app(VersionService::class)->captureFromGraph($tenant, $policy, auth()->user()?->email ?? null);
|
||||
app(VersionService::class)->captureFromGraph(
|
||||
tenant: $tenant,
|
||||
policy: $policy,
|
||||
createdBy: auth()->user()?->email ?? null,
|
||||
includeAssignments: $data['include_assignments'] ?? false,
|
||||
includeScopeTags: $data['include_scope_tags'] ?? false,
|
||||
);
|
||||
|
||||
Notification::make()
|
||||
->title('Snapshot captured successfully.')
|
||||
|
||||
@ -33,6 +33,7 @@ public function createBackupSet(
|
||||
?string $actorName = null,
|
||||
?string $name = null,
|
||||
bool $includeAssignments = false,
|
||||
bool $includeScopeTags = false,
|
||||
): BackupSet {
|
||||
$this->assertActiveTenant($tenant);
|
||||
|
||||
@ -41,7 +42,7 @@ public function createBackupSet(
|
||||
->whereIn('id', $policyIds)
|
||||
->get();
|
||||
|
||||
$backupSet = DB::transaction(function () use ($tenant, $policies, $actorEmail, $name, $includeAssignments) {
|
||||
$backupSet = DB::transaction(function () use ($tenant, $policies, $actorEmail, $name, $includeAssignments, $includeScopeTags) {
|
||||
$backupSet = BackupSet::create([
|
||||
'tenant_id' => $tenant->id,
|
||||
'name' => $name ?? CarbonImmutable::now()->format('Y-m-d H:i:s').' backup',
|
||||
@ -54,7 +55,14 @@ public function createBackupSet(
|
||||
$itemsCreated = 0;
|
||||
|
||||
foreach ($policies as $policy) {
|
||||
[$item, $failure] = $this->snapshotPolicy($tenant, $backupSet, $policy, $actorEmail, $includeAssignments);
|
||||
[$item, $failure] = $this->snapshotPolicy(
|
||||
$tenant,
|
||||
$backupSet,
|
||||
$policy,
|
||||
$actorEmail,
|
||||
$includeAssignments,
|
||||
$includeScopeTags
|
||||
);
|
||||
|
||||
if ($failure !== null) {
|
||||
$failures[] = $failure;
|
||||
@ -136,6 +144,7 @@ public function addPoliciesToSet(
|
||||
?string $actorEmail = null,
|
||||
?string $actorName = null,
|
||||
bool $includeAssignments = false,
|
||||
bool $includeScopeTags = false,
|
||||
): BackupSet {
|
||||
$this->assertActiveTenant($tenant);
|
||||
|
||||
@ -171,7 +180,14 @@ public function addPoliciesToSet(
|
||||
$itemsCreated = 0;
|
||||
|
||||
foreach ($policies as $policy) {
|
||||
[$item, $failure] = $this->snapshotPolicy($tenant, $backupSet, $policy, $actorEmail, $includeAssignments);
|
||||
[$item, $failure] = $this->snapshotPolicy(
|
||||
$tenant,
|
||||
$backupSet,
|
||||
$policy,
|
||||
$actorEmail,
|
||||
$includeAssignments,
|
||||
$includeScopeTags
|
||||
);
|
||||
|
||||
if ($failure !== null) {
|
||||
$failures[] = $failure;
|
||||
@ -231,13 +247,15 @@ private function snapshotPolicy(
|
||||
BackupSet $backupSet,
|
||||
Policy $policy,
|
||||
?string $actorEmail = null,
|
||||
bool $includeAssignments = false
|
||||
bool $includeAssignments = false,
|
||||
bool $includeScopeTags = false
|
||||
): array {
|
||||
// Use orchestrator to capture policy + assignments into PolicyVersion first
|
||||
$captureResult = $this->captureOrchestrator->capture(
|
||||
policy: $policy,
|
||||
tenant: $tenant,
|
||||
includeAssignments: $includeAssignments,
|
||||
includeScopeTags: $includeScopeTags,
|
||||
createdBy: $actorEmail,
|
||||
metadata: [
|
||||
'source' => 'backup',
|
||||
|
||||
@ -66,6 +66,8 @@ public function captureFromGraph(
|
||||
Policy $policy,
|
||||
?string $createdBy = null,
|
||||
array $metadata = [],
|
||||
bool $includeAssignments = true,
|
||||
bool $includeScopeTags = true,
|
||||
): PolicyVersion {
|
||||
$snapshot = $this->snapshotService->fetch($tenant, $policy, $createdBy);
|
||||
|
||||
@ -75,18 +77,19 @@ public function captureFromGraph(
|
||||
throw new \RuntimeException($reason);
|
||||
}
|
||||
|
||||
// Fetch assignments from Graph
|
||||
$assignments = [];
|
||||
$scopeTags = [];
|
||||
$payload = $snapshot['payload'];
|
||||
$assignments = null;
|
||||
$scopeTags = null;
|
||||
$assignmentMetadata = [];
|
||||
|
||||
if ($includeAssignments) {
|
||||
try {
|
||||
$rawAssignments = $this->assignmentFetcher->fetch($tenant->id, $policy->external_id);
|
||||
|
||||
if (! empty($rawAssignments)) {
|
||||
$assignments = $rawAssignments;
|
||||
|
||||
// Resolve groups and scope tags
|
||||
// Resolve groups
|
||||
$groupIds = collect($rawAssignments)
|
||||
->pluck('target.groupId')
|
||||
->filter()
|
||||
@ -96,11 +99,6 @@ public function captureFromGraph(
|
||||
|
||||
$resolvedGroups = $this->groupResolver->resolve($tenant->id, $groupIds);
|
||||
|
||||
$scopeTags = [
|
||||
'ids' => $policy->roleScopeTagIds ?? ['0'],
|
||||
'names' => ['Default'], // Could be fetched from Graph if needed
|
||||
];
|
||||
|
||||
$assignmentMetadata['has_orphaned_assignments'] = ! empty($resolvedGroups['orphaned']);
|
||||
$assignmentMetadata['assignments_count'] = count($rawAssignments);
|
||||
}
|
||||
@ -108,6 +106,14 @@ public function captureFromGraph(
|
||||
$assignmentMetadata['assignments_fetch_failed'] = true;
|
||||
$assignmentMetadata['assignments_fetch_error'] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
if ($includeScopeTags) {
|
||||
$scopeTags = [
|
||||
'ids' => $payload['roleScopeTagIds'] ?? ['0'],
|
||||
'names' => ['Default'], // Could be fetched from Graph if needed
|
||||
];
|
||||
}
|
||||
|
||||
$metadata = array_merge(
|
||||
['source' => 'version_capture'],
|
||||
@ -117,11 +123,11 @@ public function captureFromGraph(
|
||||
|
||||
return $this->captureVersion(
|
||||
policy: $policy,
|
||||
payload: $snapshot['payload'],
|
||||
payload: $payload,
|
||||
createdBy: $createdBy,
|
||||
metadata: $metadata,
|
||||
assignments: ! empty($assignments) ? $assignments : null,
|
||||
scopeTags: ! empty($scopeTags) ? $scopeTags : null,
|
||||
assignments: $assignments,
|
||||
scopeTags: $scopeTags,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
use App\Filament\Resources\BackupSetResource;
|
||||
use App\Models\Policy;
|
||||
use App\Models\PolicyVersion;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use App\Services\Graph\GraphClientInterface;
|
||||
@ -109,6 +110,8 @@ public function request(string $method, string $path, array $options = []): Grap
|
||||
'pageClass' => \App\Filament\Resources\BackupSetResource\Pages\ViewBackupSet::class,
|
||||
])->callTableAction('addPolicies', data: [
|
||||
'policy_ids' => [$policyA->id, $policyB->id],
|
||||
'include_assignments' => false,
|
||||
'include_scope_tags' => true,
|
||||
]);
|
||||
|
||||
$backupSet->refresh();
|
||||
@ -117,6 +120,14 @@ public function request(string $method, string $path, array $options = []): Grap
|
||||
expect($backupSet->items)->toHaveCount(2);
|
||||
expect($backupSet->items->first()->payload['id'])->toBe('policy-1');
|
||||
|
||||
$firstVersion = PolicyVersion::find($backupSet->items->first()->policy_version_id);
|
||||
expect($firstVersion)->not->toBeNull();
|
||||
expect($firstVersion->scope_tags)->toBe([
|
||||
'ids' => ['0'],
|
||||
'names' => ['Default'],
|
||||
]);
|
||||
expect($firstVersion->assignments)->toBeNull();
|
||||
|
||||
$this->assertDatabaseHas('audit_logs', [
|
||||
'action' => 'backup.created',
|
||||
'resource_type' => 'backup_set',
|
||||
|
||||
55
tests/Feature/Filament/PolicyCaptureSnapshotOptionsTest.php
Normal file
55
tests/Feature/Filament/PolicyCaptureSnapshotOptionsTest.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
use App\Filament\Resources\PolicyResource\Pages\ViewPolicy;
|
||||
use App\Models\Policy;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use App\Services\Graph\AssignmentFetcher;
|
||||
use App\Services\Intune\PolicySnapshotService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Livewire\Livewire;
|
||||
use Mockery\MockInterface;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('captures a policy snapshot with scope tags when requested', function () {
|
||||
$tenant = Tenant::factory()->create();
|
||||
$tenant->makeCurrent();
|
||||
$policy = Policy::factory()->for($tenant)->create([
|
||||
'external_id' => 'policy-123',
|
||||
]);
|
||||
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$this->mock(PolicySnapshotService::class, function (MockInterface $mock) use ($policy) {
|
||||
$mock->shouldReceive('fetch')
|
||||
->once()
|
||||
->andReturn([
|
||||
'payload' => [
|
||||
'id' => $policy->external_id,
|
||||
'name' => $policy->display_name,
|
||||
'roleScopeTagIds' => ['0'],
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
$this->mock(AssignmentFetcher::class, function (MockInterface $mock) {
|
||||
$mock->shouldReceive('fetch')->never();
|
||||
});
|
||||
|
||||
Livewire::test(ViewPolicy::class, ['record' => $policy->getRouteKey()])
|
||||
->callAction('capture_snapshot', data: [
|
||||
'include_assignments' => false,
|
||||
'include_scope_tags' => true,
|
||||
]);
|
||||
|
||||
$version = $policy->versions()->first();
|
||||
|
||||
expect($version)->not->toBeNull();
|
||||
expect($version->assignments)->toBeNull();
|
||||
expect($version->scope_tags)->toBe([
|
||||
'ids' => ['0'],
|
||||
'names' => ['Default'],
|
||||
]);
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user