## Summary <!-- Kurz: Was ändert sich und warum? --> ## Spec-Driven Development (SDD) - [ ] Es gibt eine Spec unter `specs/<NNN>-<feature>/` - [ ] Enthaltene Dateien: `plan.md`, `tasks.md`, `spec.md` - [ ] Spec beschreibt Verhalten/Acceptance Criteria (nicht nur Implementation) - [ ] Wenn sich Anforderungen während der Umsetzung geändert haben: Spec/Plan/Tasks wurden aktualisiert ## Implementation - [ ] Implementierung entspricht der Spec - [ ] Edge cases / Fehlerfälle berücksichtigt - [ ] Keine unbeabsichtigten Änderungen außerhalb des Scopes ## Tests - [ ] Tests ergänzt/aktualisiert (Pest/PHPUnit) - [ ] Relevante Tests lokal ausgeführt (`./vendor/bin/sail artisan test` oder `php artisan test`) ## Migration / Config / Ops (falls relevant) - [ ] Migration(en) enthalten und getestet - [ ] Rollback bedacht (rückwärts kompatibel, sichere Migration) - [ ] Neue Env Vars dokumentiert (`.env.example` / Doku) - [ ] Queue/cron/storage Auswirkungen geprüft ## UI (Filament/Livewire) (falls relevant) - [ ] UI-Flows geprüft - [ ] Screenshots/Notizen hinzugefügt ## Notes <!-- Links, Screenshots, Follow-ups, offene Punkte --> Co-authored-by: Ahmed Darrazi <ahmeddarrazi@adsmac.local> Reviewed-on: #4
9.9 KiB
Quickstart: Feature 005 - Bulk Operations
Feature: Bulk Operations for Resource Management
Date: 2025-12-22
Overview
This quickstart guide helps developers get up and running with Feature 005 (Bulk Operations) for local development, testing, and debugging.
Prerequisites
- Laravel Sail installed and running
- Composer dependencies installed
- NPM dependencies installed
- Database migrated
- At least one Tenant configured
- Sample Policies, PolicyVersions, BackupSets, RestoreRuns seeded
Local Development Setup
1. Start Sail
cd /path/to/TenantAtlas
./vendor/bin/sail up -d
2. Run Migrations
./vendor/bin/sail artisan migrate
This creates:
bulk_operation_runstableignored_atcolumn onpoliciestable
3. Seed Test Data (Optional)
./vendor/bin/sail artisan db:seed --class=BulkOperationsTestSeeder
Creates:
- 100 test policies
- 50 policy versions (some old, some referenced)
- 10 backup sets
- 20 restore runs (mixed statuses)
4. Start Queue Worker
Bulk operations require queue processing:
./vendor/bin/sail artisan queue:work --tries=3 --timeout=300
Or run in background with Supervisor (production):
./vendor/bin/sail artisan queue:restart
5. Access Filament Panel
open http://localhost/admin
Navigate to:
- Policies → Select multiple → Bulk Actions dropdown
- Policy Versions → Bulk Prune
- Backup Sets → Bulk Delete
- Restore Runs → Bulk Delete
Running Tests
Unit Tests
Test individual components (jobs, scopes, helpers):
./vendor/bin/sail artisan test tests/Unit/BulkPolicyDeleteJobTest.php
./vendor/bin/sail artisan test tests/Unit/BulkActionPermissionTest.php
./vendor/bin/sail artisan test tests/Unit/BulkEligibilityCheckTest.php
Feature Tests
Test E2E flows (UI → job → database):
./vendor/bin/sail artisan test tests/Feature/BulkDeletePoliciesTest.php
./vendor/bin/sail artisan test tests/Feature/BulkExportToBackupTest.php
./vendor/bin/sail artisan test tests/Feature/BulkProgressNotificationTest.php
./vendor/bin/sail artisan test tests/Feature/BulkTypeToConfirmTest.php
All Tests
./vendor/bin/sail artisan test --filter=Bulk
Browser Tests (Pest v4)
Test UI interactions:
./vendor/bin/sail artisan test tests/Browser/BulkOperationsTest.php
Manual Testing Workflow
Scenario 1: Bulk Delete Policies (< 20 items)
- Navigate: Admin → Policies
- Select: Check 10 policies
- Action: Click "Delete" in bulk actions dropdown
- Confirm: Modal appears: "Delete 10 policies?"
- Submit: Click "Confirm"
- Verify:
- Success notification: "Deleted 10 policies"
- Policies have
ignored_attimestamp set - Policies still exist in Intune (no Graph DELETE call)
- Audit log entry created
Scenario 2: Bulk Delete Policies (≥ 20 items, queued)
- Navigate: Admin → Policies
- Select: Check 25 policies
- Action: Click "Delete"
- Confirm: Modal requires typing "DELETE"
- Type: Enter "DELETE" (case-sensitive)
- Submit: Click "Confirm"
- Verify:
- Job dispatched to queue
- Progress notification: "Deleting policies... 0/25"
- Notification updates every 5s: "Deleting... 10/25", "20/25"
- Final notification: "Deleted 25 policies"
BulkOperationRunrecord created with statuscompleted- Audit log entry
Scenario 3: Bulk Export to Backup
- Navigate: Admin → Policies
- Select: Check 30 policies
- Action: Click "Export to Backup"
- Form:
- Backup Set Name: "Production Snapshot"
- Include Assignments: ☑ (if Feature 004 implemented)
- Submit: Click "Confirm"
- Verify:
- Job dispatched
- Progress: "Backing up... 10/30"
- Final: "Backup Set 'Production Snapshot' created (30 items)"
- New
BackupSetrecord - 30
BackupItemrecords - Audit log entry
Scenario 4: Bulk Prune Policy Versions
- Navigate: Admin → Policy Versions
- Filter: Show only non-current versions older than 90 days
- Select: Check 15 versions
- Action: Click "Delete"
- Confirm: Type "DELETE"
- Submit: Click "Confirm"
- Verify:
- Eligibility check runs
- Eligible versions deleted (hard delete)
- Ineligible versions skipped
- Notification: "Deleted 12 policy versions (3 skipped)"
- Failures array shows skip reasons:
- "Referenced in Backup Set #5"
- "Current version"
- "Too recent (< 90 days)"
Scenario 5: Circuit Breaker (abort on >50% fail)
- Setup: Mock Graph API to fail for 60% of items
- Navigate: Admin → Policies
- Select: Check 100 policies
- Action: Bulk Delete
- Verify:
- Job processes ~50 items
- Detects >50% failure rate
- Aborts remaining items
- Notification: "Bulk operation aborted: 55/100 failures exceeded threshold"
BulkOperationRun.status=aborted
Debugging
View Queue Jobs
# List failed jobs
./vendor/bin/sail artisan queue:failed
# Retry failed job
./vendor/bin/sail artisan queue:retry <job-id>
# Flush failed jobs
./vendor/bin/sail artisan queue:flush
Inspect BulkOperationRun Records
./vendor/bin/sail tinker
// Get recent runs
$runs = \App\Models\BulkOperationRun::recent()->limit(10)->get();
// View failures
$run = \App\Models\BulkOperationRun::find(1);
dd($run->failures);
// Check progress
echo "{$run->processed_items}/{$run->total_items} ({$run->progressPercentage()}%)";
View Audit Logs
# Via Filament UI
# Navigate to: Admin → Audit Logs → Filter by event: "policies.bulk_deleted"
# Via Tinker
$logs = \App\Models\AuditLog::where('event', 'policies.bulk_deleted')->get();
Database Queries
./vendor/bin/sail artisan tinker
// Policies marked as ignored
$ignored = \App\Models\Policy::ignored()->count();
// Policy versions eligible for pruning
$eligible = \App\Models\PolicyVersion::pruneEligible(90)->count();
// Deletable restore runs
$deletable = \App\Models\RestoreRun::deletable()->count();
Test Queue Job Manually
./vendor/bin/sail artisan tinker
use App\Jobs\BulkPolicyDeleteJob;
use App\Models\BulkOperationRun;
$policyIds = [1, 2, 3];
$tenantId = 1;
$userId = 1;
$run = BulkOperationRun::create([
'tenant_id' => $tenantId,
'user_id' => $userId,
'resource' => 'policies',
'action' => 'delete',
'status' => 'pending',
'total_items' => count($policyIds),
'item_ids' => $policyIds,
]);
// Dispatch synchronously for debugging
BulkPolicyDeleteJob::dispatchSync($policyIds, $tenantId, $userId, $run->id);
// Check result
$run->refresh();
echo $run->summaryText();
Common Issues & Solutions
Issue 1: Type-to-confirm not working
Symptom: Confirm button remains enabled without typing "DELETE"
Solution: Check Filament form validation rule:
->rule('in:DELETE') // Case-sensitive
Issue 2: Progress notifications don't update
Symptom: Progress stuck at "0/100"
Solution:
- Ensure queue worker is running:
./vendor/bin/sail artisan queue:work - Check Livewire polling:
wire:poll.5s="refresh" - Verify BulkOperationRun is updated in job
Issue 3: Policies reappear after deletion
Symptom: Deleted policies show up again after sync
Solution:
- Check
ignored_atis set:Policy::find($id)->ignored_at - Verify SyncPoliciesJob filters:
->whereNull('ignored_at')
Issue 4: Circuit breaker not aborting
Symptom: Job continues despite >50% failures
Solution: Check circuit breaker logic in job:
if ($results['failed'] > count($this->policyIds) * 0.5) {
$run->update(['status' => 'aborted']);
throw new \Exception('Bulk operation aborted: >50% failure rate');
}
Issue 5: Policy versions deleted despite references
Symptom: Referenced versions are deleted
Solution: Verify eligibility scope includes:
->whereDoesntHave('backupItems')
->whereNotIn('id', function ($subquery) {
$subquery->select(DB::raw("CAST(metadata->>'policy_version_id' AS INTEGER)"))
->from('restore_runs')
->whereNotNull(DB::raw("metadata->>'policy_version_id'"));
});
Performance Benchmarks
Expected performance (local Sail environment):
| Operation | Item Count | Duration | Notes |
|---|---|---|---|
| Bulk Delete (sync) | 10 | <1s | Immediate feedback |
| Bulk Delete (queued) | 100 | <2min | Chunked, progress updates |
| Bulk Export | 50 | <3min | Includes Graph API calls |
| Bulk Prune | 30 | <30s | Eligibility checks |
| Progress Update | - | 5s | Polling interval |
Code Formatting
Before committing:
./vendor/bin/sail composer pint
Formats all PHP files per PSR-12.
Next Steps
- ✅ Complete Phase 2.1 (Foundation) tasks
- ✅ Run all tests:
./vendor/bin/sail artisan test --filter=Bulk - ✅ Manual QA: Follow scenarios above
- ✅ Code review: Check PSR-12, permissions, audit logs
- ✅ Load testing: Bulk delete 500 items
- → Deploy to staging
- → Manual QA on staging
- → Deploy to production
Useful Commands
# Watch queue jobs in real-time
./vendor/bin/sail artisan queue:work --verbose
# Monitor bulk operations
./vendor/bin/sail artisan tinker
>>> BulkOperationRun::inProgress()->get()
# Seed more test data
./vendor/bin/sail artisan db:seed --class=BulkOperationsTestSeeder
# Clear cache
./vendor/bin/sail artisan optimize:clear
# Restart queue workers (after code changes)
./vendor/bin/sail artisan queue:restart
Resources
Status: Quickstart Complete
Next Step: Update agent context with new learnings