TenantAtlas/specs/005-bulk-operations/quickstart.md
ahmido f4cf1dce6e feat/004-assignments-scope-tags (#4)
## 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
2025-12-23 21:49:58 +00:00

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_runs table
  • ignored_at column on policies table

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)

  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

# 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_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:

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

  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

# 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