TenantAtlas/specs/005-bulk-operations/quickstart.md
Ahmed Darrazi 673fbd6b22 spec(005): Add comprehensive implementation plan for Bulk Operations
Adds:
- plan.md: Technical context, constitution check, phases
- research.md: 7 research decisions (progress tracking, chunking, type-to-confirm)
- data-model.md: BulkOperationRun model, schema changes, query patterns
- quickstart.md: Developer onboarding, testing workflows, debugging

Key Decisions:
- BulkOperationRun model + Livewire polling for progress
- collect()->chunk(10) for memory-efficient processing
- Filament form + validation for type-to-confirm
- ignored_at flag to prevent sync re-adding deleted policies
- Eligibility scopes for safe Policy Version pruning

Estimated: 26-34 hours (3 phases for P1/P2 features)
Next: /speckit.tasks to generate task breakdown
2025-12-22 01:27:42 +01:00

426 lines
9.9 KiB
Markdown

# 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 <job-id>
# 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