TenantAtlas/specs/005-bulk-operations/quickstart.md
ahmido a97beefda3 056-remove-legacy-bulkops (#65)
Kurzbeschreibung

Versteckt die Rerun-Row-Action für archivierte (soft-deleted) RestoreRuns und verhindert damit fehlerhafte Neu-Starts aus dem Archiv; ergänzt einen Regressionstest.
Änderungen

Code: RestoreRunResource.php — Sichtbarkeit der rerun-Action geprüft auf ! $record->trashed() und defensive Abbruchprüfung im Action-Handler.
Tests: RestoreRunRerunTest.php — neuer Test rerun action is hidden for archived restore runs.
Warum

Archivierte RestoreRuns durften nicht neu gestartet werden; UI zeigte trotzdem die Option. Das führte zu verwirrendem Verhalten und möglichen Fehlern beim Enqueueing.
Verifikation / QA

Unit/Feature:
./vendor/bin/sail artisan test tests/Feature/RestoreRunRerunTest.php
Stil/format:
./vendor/bin/pint --dirty
Manuell (UI):
Als Tenant-Admin Filament → Restore Runs öffnen.
Filter Archived aktivieren (oder Trashed filter auswählen).
Sicherstellen, dass für archivierte Einträge die Rerun-Action nicht sichtbar ist.
Auf einem aktiven (nicht-archivierten) Run prüfen, dass Rerun sichtbar bleibt und wie erwartet eine neue RestoreRun erzeugt.
Wichtige Hinweise

Kein DB-Migration required.
Diese PR enthält nur den UI-/Filament-Fix; die zuvor gemachten operative Fixes für Queue/adapter-Reconciliation bleiben ebenfalls auf dem Branch (z. B. frühere commits während der Debugging-Session).
T055 (Schema squash) wurde bewusst zurückgestellt und ist nicht Teil dieses PRs.
Merge-Checklist

 Tests lokal laufen (RestoreRunRerunTest grünt)
 Pint läuft ohne ungepatchte Fehler
 Branch gepusht: 056-remove-legacy-bulkops (PR-URL: https://git.cloudarix.de/ahmido/TenantAtlas/compare/dev...056-remove-legacy-bulkops)

Co-authored-by: Ahmed Darrazi <ahmeddarrazi@adsmac.local>
Reviewed-on: #65
2026-01-19 23:27:52 +00:00

438 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
# NOTE: Removed by Feature 056 (OperationRun migration).
# There is no BulkOperationsTestSeeder anymore.
```
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
```
---
## Configuration
These defaults are safe for staging/production, but can be tuned per environment.
- **Chunk size** (job refresh/progress cadence):
- `TENANTPILOT_BULK_CHUNK_SIZE` (default `10`)
- **Progress polling interval** (UI updates):
- `TENANTPILOT_BULK_POLL_INTERVAL_SECONDS` (default `3`, clamped to 110 seconds)
- **Policy version prune retention window**:
- Default `90` days (editable in the prune modal as “Retention Days”)
## 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
>>> \App\Models\OperationRun::query()->where('status', 'running')->get()
# Seed more test data
# NOTE: Removed by Feature 056 (OperationRun migration).
# 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