# 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 ```bash cd /path/to/TenantAtlas ./vendor/bin/sail up -d ``` ### 2. Run Migrations ```bash ./vendor/bin/sail artisan migrate ``` This creates: - `bulk_operation_runs` table - `ignored_at` column on `policies` table ### 3. Seed Test Data (Optional) ```bash ./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: ```bash ./vendor/bin/sail artisan queue:work --tries=3 --timeout=300 ``` Or run in background with Supervisor (production): ```bash ./vendor/bin/sail artisan queue:restart ``` ### 5. Access Filament Panel ```bash 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): ```bash ./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): ```bash ./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 ```bash ./vendor/bin/sail artisan test --filter=Bulk ``` ### Browser Tests (Pest v4) Test UI interactions: ```bash ./vendor/bin/sail artisan test tests/Browser/BulkOperationsTest.php ``` --- ## Manual Testing Workflow ### Scenario 1: Bulk Delete Policies (< 20 items) 1. **Navigate**: Admin → Policies 2. **Select**: Check 10 policies 3. **Action**: Click "Delete" in bulk actions dropdown 4. **Confirm**: Modal appears: "Delete 10 policies?" 5. **Submit**: Click "Confirm" 6. **Verify**: - Success notification: "Deleted 10 policies" - Policies have `ignored_at` timestamp set - Policies still exist in Intune (no Graph DELETE call) - Audit log entry created ### Scenario 2: Bulk Delete Policies (≥ 20 items, queued) 1. **Navigate**: Admin → Policies 2. **Select**: Check 25 policies 3. **Action**: Click "Delete" 4. **Confirm**: Modal requires typing "DELETE" 5. **Type**: Enter "DELETE" (case-sensitive) 6. **Submit**: Click "Confirm" 7. **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" - `BulkOperationRun` record created with status `completed` - Audit log entry ### Scenario 3: Bulk Export to Backup 1. **Navigate**: Admin → Policies 2. **Select**: Check 30 policies 3. **Action**: Click "Export to Backup" 4. **Form**: - Backup Set Name: "Production Snapshot" - Include Assignments: ☑ (if Feature 004 implemented) 5. **Submit**: Click "Confirm" 6. **Verify**: - Job dispatched - Progress: "Backing up... 10/30" - Final: "Backup Set 'Production Snapshot' created (30 items)" - New `BackupSet` record - 30 `BackupItem` records - Audit log entry ### Scenario 4: Bulk Prune Policy Versions 1. **Navigate**: Admin → Policy Versions 2. **Filter**: Show only non-current versions older than 90 days 3. **Select**: Check 15 versions 4. **Action**: Click "Delete" 5. **Confirm**: Type "DELETE" 6. **Submit**: Click "Confirm" 7. **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) 1. **Setup**: Mock Graph API to fail for 60% of items 2. **Navigate**: Admin → Policies 3. **Select**: Check 100 policies 4. **Action**: Bulk Delete 5. **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 ```bash # List failed jobs ./vendor/bin/sail artisan queue:failed # Retry failed job ./vendor/bin/sail artisan queue:retry # Flush failed jobs ./vendor/bin/sail artisan queue:flush ``` ### Inspect BulkOperationRun Records ```bash ./vendor/bin/sail tinker ``` ```php // 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 ```bash # 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 ```bash ./vendor/bin/sail artisan tinker ``` ```php // 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 ```bash ./vendor/bin/sail artisan tinker ``` ```php 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: ```php ->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_at` is 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: ```php 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: ```php ->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: ```bash ./vendor/bin/sail composer pint ``` Formats all PHP files per PSR-12. --- ## Next Steps 1. ✅ Complete Phase 2.1 (Foundation) tasks 2. ✅ Run all tests: `./vendor/bin/sail artisan test --filter=Bulk` 3. ✅ Manual QA: Follow scenarios above 4. ✅ Code review: Check PSR-12, permissions, audit logs 5. ✅ Load testing: Bulk delete 500 items 6. → Deploy to staging 7. → Manual QA on staging 8. → Deploy to production --- ## Useful Commands ```bash # 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 - [Laravel Queue Documentation](https://laravel.com/docs/12.x/queues) - [Filament Bulk Actions](https://filamentphp.com/docs/4.x/tables/actions#bulk-actions) - [Livewire Polling](https://livewire.laravel.com/docs/polling) - [Pest Testing](https://pestphp.com/docs) --- **Status**: Quickstart Complete **Next Step**: Update agent context with new learnings